mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-31 06:40:50 +01:00
feat(parser): implement C# parser microservice with regex-based title parsing
- Added RegexReplace class for handling regex replacements. - Created ReleaseGroupParser for extracting release groups from titles. - Developed TitleParser for parsing movie titles, including editions and IDs. - Introduced QualitySource, Resolution, QualityModifier enums and QualityResult class for quality metadata. - Set up Dockerfile and docker-compose for containerized deployment. - Implemented ASP.NET Core web API for parsing requests. - Added TypeScript client for interacting with the parser service. - Enhanced configuration to support dynamic parser service URL.
This commit is contained in:
136
src/lib/server/utils/arr/parser/client.ts
Normal file
136
src/lib/server/utils/arr/parser/client.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* Parser Service Client
|
||||
* Calls the C# parser microservice
|
||||
*/
|
||||
|
||||
import { config } from '$config';
|
||||
import {
|
||||
QualitySource,
|
||||
QualityModifier,
|
||||
Language,
|
||||
ReleaseType,
|
||||
type QualityInfo,
|
||||
type ParseResult,
|
||||
type EpisodeInfo,
|
||||
type Resolution,
|
||||
type MediaType
|
||||
} from './types.ts';
|
||||
|
||||
interface EpisodeResponse {
|
||||
seriesTitle: string | null;
|
||||
seasonNumber: number;
|
||||
episodeNumbers: number[];
|
||||
absoluteEpisodeNumbers: number[];
|
||||
airDate: string | null;
|
||||
fullSeason: boolean;
|
||||
isPartialSeason: boolean;
|
||||
isMultiSeason: boolean;
|
||||
isMiniSeries: boolean;
|
||||
special: boolean;
|
||||
releaseType: string;
|
||||
}
|
||||
|
||||
interface ParseResponse {
|
||||
title: string;
|
||||
type: MediaType;
|
||||
source: string;
|
||||
resolution: number;
|
||||
modifier: string;
|
||||
revision: {
|
||||
version: number;
|
||||
real: number;
|
||||
isRepack: boolean;
|
||||
};
|
||||
languages: string[];
|
||||
releaseGroup: string | null;
|
||||
movieTitles: string[];
|
||||
year: number;
|
||||
edition: string | null;
|
||||
imdbId: string | null;
|
||||
tmdbId: number;
|
||||
hardcodedSubs: string | null;
|
||||
releaseHash: string | null;
|
||||
episode: EpisodeResponse | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a release title - returns quality, resolution, modifier, revision, and languages
|
||||
* @param title - The release title to parse
|
||||
* @param type - The media type: 'movie' or 'series'
|
||||
*/
|
||||
export async function parse(title: string, type: MediaType): Promise<ParseResult> {
|
||||
const res = await fetch(`${config.parserUrl}/parse`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ title, type })
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Parser error: ${res.status}`);
|
||||
}
|
||||
|
||||
const data: ParseResponse = await res.json();
|
||||
|
||||
return {
|
||||
title: data.title,
|
||||
type: data.type,
|
||||
source: QualitySource[data.source as keyof typeof QualitySource] ?? QualitySource.Unknown,
|
||||
resolution: data.resolution as Resolution,
|
||||
modifier:
|
||||
QualityModifier[data.modifier as keyof typeof QualityModifier] ?? QualityModifier.None,
|
||||
revision: data.revision,
|
||||
languages: data.languages.map(
|
||||
(l) => Language[l as keyof typeof Language] ?? Language.Unknown
|
||||
),
|
||||
releaseGroup: data.releaseGroup,
|
||||
movieTitles: data.movieTitles,
|
||||
year: data.year,
|
||||
edition: data.edition,
|
||||
imdbId: data.imdbId,
|
||||
tmdbId: data.tmdbId,
|
||||
hardcodedSubs: data.hardcodedSubs,
|
||||
releaseHash: data.releaseHash,
|
||||
episode: data.episode
|
||||
? {
|
||||
seriesTitle: data.episode.seriesTitle,
|
||||
seasonNumber: data.episode.seasonNumber,
|
||||
episodeNumbers: data.episode.episodeNumbers,
|
||||
absoluteEpisodeNumbers: data.episode.absoluteEpisodeNumbers,
|
||||
airDate: data.episode.airDate,
|
||||
fullSeason: data.episode.fullSeason,
|
||||
isPartialSeason: data.episode.isPartialSeason,
|
||||
isMultiSeason: data.episode.isMultiSeason,
|
||||
isMiniSeries: data.episode.isMiniSeries,
|
||||
special: data.episode.special,
|
||||
releaseType:
|
||||
ReleaseType[data.episode.releaseType as keyof typeof ReleaseType] ??
|
||||
ReleaseType.Unknown
|
||||
}
|
||||
: null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse quality info from a release title (legacy - use parse() for full results)
|
||||
*/
|
||||
export async function parseQuality(title: string, type: MediaType): Promise<QualityInfo> {
|
||||
const result = await parse(title, type);
|
||||
return {
|
||||
source: result.source,
|
||||
resolution: result.resolution,
|
||||
modifier: result.modifier,
|
||||
revision: result.revision
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check parser service health
|
||||
*/
|
||||
export async function isParserHealthy(): Promise<boolean> {
|
||||
try {
|
||||
const res = await fetch(`${config.parserUrl}/health`);
|
||||
return res.ok;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
7
src/lib/server/utils/arr/parser/index.ts
Normal file
7
src/lib/server/utils/arr/parser/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Release Title Parser
|
||||
* Client for the C# parser microservice
|
||||
*/
|
||||
|
||||
export * from './types.ts';
|
||||
export { parse, parseQuality, isParserHealthy } from './client.ts';
|
||||
154
src/lib/server/utils/arr/parser/types.ts
Normal file
154
src/lib/server/utils/arr/parser/types.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* Parser Types
|
||||
* Matches the C# parser microservice types
|
||||
*/
|
||||
|
||||
export enum QualitySource {
|
||||
Unknown = 0,
|
||||
Cam = 1,
|
||||
Telesync = 2,
|
||||
Telecine = 3,
|
||||
Workprint = 4,
|
||||
DVD = 5,
|
||||
TV = 6,
|
||||
WebDL = 7,
|
||||
WebRip = 8,
|
||||
Bluray = 9
|
||||
}
|
||||
|
||||
export enum QualityModifier {
|
||||
None = 0,
|
||||
Regional = 1,
|
||||
Screener = 2,
|
||||
RawHD = 3,
|
||||
BRDisk = 4,
|
||||
Remux = 5
|
||||
}
|
||||
|
||||
export enum Resolution {
|
||||
Unknown = 0,
|
||||
R360p = 360,
|
||||
R480p = 480,
|
||||
R540p = 540,
|
||||
R576p = 576,
|
||||
R720p = 720,
|
||||
R1080p = 1080,
|
||||
R2160p = 2160
|
||||
}
|
||||
|
||||
export enum Language {
|
||||
Unknown = 0,
|
||||
English = 1,
|
||||
French = 2,
|
||||
Spanish = 3,
|
||||
German = 4,
|
||||
Italian = 5,
|
||||
Danish = 6,
|
||||
Dutch = 7,
|
||||
Japanese = 8,
|
||||
Icelandic = 9,
|
||||
Chinese = 10,
|
||||
Russian = 11,
|
||||
Polish = 12,
|
||||
Vietnamese = 13,
|
||||
Swedish = 14,
|
||||
Norwegian = 15,
|
||||
Finnish = 16,
|
||||
Turkish = 17,
|
||||
Portuguese = 18,
|
||||
Flemish = 19,
|
||||
Greek = 20,
|
||||
Korean = 21,
|
||||
Hungarian = 22,
|
||||
Hebrew = 23,
|
||||
Lithuanian = 24,
|
||||
Czech = 25,
|
||||
Hindi = 26,
|
||||
Romanian = 27,
|
||||
Thai = 28,
|
||||
Bulgarian = 29,
|
||||
PortugueseBR = 30,
|
||||
Arabic = 31,
|
||||
Ukrainian = 32,
|
||||
Persian = 33,
|
||||
Bengali = 34,
|
||||
Slovak = 35,
|
||||
Latvian = 36,
|
||||
SpanishLatino = 37,
|
||||
Catalan = 38,
|
||||
Croatian = 39,
|
||||
Serbian = 40,
|
||||
Bosnian = 41,
|
||||
Estonian = 42,
|
||||
Tamil = 43,
|
||||
Indonesian = 44,
|
||||
Telugu = 45,
|
||||
Macedonian = 46,
|
||||
Slovenian = 47,
|
||||
Malayalam = 48,
|
||||
Kannada = 49,
|
||||
Albanian = 50,
|
||||
Afrikaans = 51,
|
||||
Marathi = 52,
|
||||
Tagalog = 53,
|
||||
Urdu = 54,
|
||||
Romansh = 55,
|
||||
Mongolian = 56,
|
||||
Georgian = 57,
|
||||
Original = 58
|
||||
}
|
||||
|
||||
export enum ReleaseType {
|
||||
Unknown = 0,
|
||||
SingleEpisode = 1,
|
||||
MultiEpisode = 2,
|
||||
SeasonPack = 3
|
||||
}
|
||||
|
||||
export interface Revision {
|
||||
version: number;
|
||||
real: number;
|
||||
isRepack: boolean;
|
||||
}
|
||||
|
||||
export interface QualityInfo {
|
||||
source: QualitySource;
|
||||
resolution: Resolution;
|
||||
modifier: QualityModifier;
|
||||
revision: Revision;
|
||||
}
|
||||
|
||||
export interface EpisodeInfo {
|
||||
seriesTitle: string | null;
|
||||
seasonNumber: number;
|
||||
episodeNumbers: number[];
|
||||
absoluteEpisodeNumbers: number[];
|
||||
airDate: string | null;
|
||||
fullSeason: boolean;
|
||||
isPartialSeason: boolean;
|
||||
isMultiSeason: boolean;
|
||||
isMiniSeries: boolean;
|
||||
special: boolean;
|
||||
releaseType: ReleaseType;
|
||||
}
|
||||
|
||||
export type MediaType = 'movie' | 'series';
|
||||
|
||||
export interface ParseResult {
|
||||
title: string;
|
||||
type: MediaType;
|
||||
source: QualitySource;
|
||||
resolution: Resolution;
|
||||
modifier: QualityModifier;
|
||||
revision: Revision;
|
||||
languages: Language[];
|
||||
releaseGroup: string | null;
|
||||
movieTitles: string[];
|
||||
year: number;
|
||||
edition: string | null;
|
||||
imdbId: string | null;
|
||||
tmdbId: number;
|
||||
hardcodedSubs: string | null;
|
||||
releaseHash: string | null;
|
||||
episode: EpisodeInfo | null;
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
class Config {
|
||||
private basePath: string;
|
||||
public readonly timezone: string;
|
||||
public readonly parserUrl: string;
|
||||
|
||||
constructor() {
|
||||
// Default base path logic:
|
||||
@@ -24,6 +25,11 @@ class Config {
|
||||
// 1. Check TZ environment variable
|
||||
// 2. Fall back to system timezone
|
||||
this.timezone = Deno.env.get('TZ') || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
||||
// Parser service configuration
|
||||
const parserHost = Deno.env.get('PARSER_HOST') || 'localhost';
|
||||
const parserPort = Deno.env.get('PARSER_PORT') || '5000';
|
||||
this.parserUrl = `http://${parserHost}:${parserPort}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user