mirror of
https://github.com/Viren070/AIOStreams.git
synced 2025-12-01 23:14:04 +01:00
feat: improve regex sorting performance, allow naming regexes with name<::>prefix, and give name in custom formatter with stream.regexMatch
This commit is contained in:
+62
-16
@@ -43,6 +43,8 @@ import {
|
||||
safeRegexTest,
|
||||
compileRegex,
|
||||
formRegexFromKeywords,
|
||||
Cache,
|
||||
getTextHash,
|
||||
} from '@aiostreams/utils';
|
||||
import { errorStream } from './responses';
|
||||
|
||||
@@ -158,12 +160,28 @@ export class AIOStreams {
|
||||
? this.config.regexSortPatterns || Settings.DEFAULT_REGEX_SORT_PATTERNS
|
||||
: undefined;
|
||||
|
||||
const sortRegexes = sortRegexPatterns
|
||||
? sortRegexPatterns
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.map((pattern) => compileRegex(pattern, 'i'))
|
||||
: undefined;
|
||||
const sortRegexes: { name?: string; regex: RegExp }[] | undefined =
|
||||
sortRegexPatterns
|
||||
? sortRegexPatterns
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.map((pattern) => {
|
||||
const delimiter = '<::>';
|
||||
const delimiterIndex = pattern.indexOf(delimiter);
|
||||
if (delimiterIndex !== -1) {
|
||||
const name = pattern
|
||||
.slice(0, delimiterIndex)
|
||||
.replace(/_/g, ' ');
|
||||
const regexPattern = pattern.slice(
|
||||
delimiterIndex + delimiter.length
|
||||
);
|
||||
|
||||
const regex = compileRegex(regexPattern, 'i');
|
||||
return { name, regex };
|
||||
}
|
||||
return { regex: compileRegex(pattern, 'i') };
|
||||
})
|
||||
: undefined;
|
||||
|
||||
excludeRegex ||
|
||||
excludeKeywordsRegex ||
|
||||
@@ -176,7 +194,7 @@ export class AIOStreams {
|
||||
`Exclude Keywords: ${excludeKeywordsRegex}\n` +
|
||||
`Required Regex: ${requiredRegex}\n` +
|
||||
`Required Keywords: ${requiredKeywordsRegex}\n` +
|
||||
`Sort Regexes: ${sortRegexes?.join(' --> ')}\n`
|
||||
`Sort Regexes: ${sortRegexes?.map((regex) => `${regex.name || 'Unnamed'}: ${regex.regex}`).join(' --> ')}\n`
|
||||
)
|
||||
: [];
|
||||
|
||||
@@ -492,6 +510,30 @@ export class AIOStreams {
|
||||
filteredResults.length - cleanedStreams.length;
|
||||
filteredResults = cleanedStreams;
|
||||
}
|
||||
// pre compute highest indexes for regexSortPatterns
|
||||
const startPrecomputeTime = new Date().getTime();
|
||||
const sortRegexCache = Cache.getInstance<string, number>('sortRegexCache');
|
||||
filteredResults.forEach((stream: ParsedStream) => {
|
||||
if (sortRegexes) {
|
||||
for (let i = 0; i < sortRegexes.length; i++) {
|
||||
const regex = sortRegexes[i];
|
||||
if (stream.filename) {
|
||||
const match = safeRegexTest(regex.regex, stream.filename);
|
||||
if (match) {
|
||||
stream.regexMatch = regex.name;
|
||||
sortRegexCache.set(getTextHash(stream.filename), i, 60);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
logger.info(
|
||||
`Precomputed sortRegex indexes for ${filteredResults.length} streams in ${getTimeTakenSincePoint(
|
||||
startPrecomputeTime
|
||||
)}`
|
||||
);
|
||||
// Apply sorting
|
||||
const sortStartTime = new Date().getTime();
|
||||
// initially sort by filename to ensure consistent results
|
||||
@@ -813,18 +855,22 @@ export class AIOStreams {
|
||||
if (!a.filename) return direction === 'asc' ? -1 : 1;
|
||||
if (!b.filename) return direction === 'asc' ? 1 : -1;
|
||||
|
||||
// Test patterns in order
|
||||
for (const regex of compiledRegexPatterns) {
|
||||
const aMatch = safeRegexTest(regex, a.filename);
|
||||
const bMatch = safeRegexTest(regex, b.filename);
|
||||
// If both match or both don't match, continue to next pattern
|
||||
if ((aMatch && bMatch) || (!aMatch && !bMatch)) continue;
|
||||
const cache = Cache.getInstance<string, number>('sortRegexCache');
|
||||
|
||||
// If one matches and the other doesn't, use direction to determine order
|
||||
return direction === 'asc' ? (aMatch ? 1 : -1) : aMatch ? -1 : 1;
|
||||
const aHighestIndex = cache.get(getTextHash(a.filename));
|
||||
const bHighestIndex = cache.get(getTextHash(b.filename));
|
||||
|
||||
// If both have a regex match, sort by the highest index
|
||||
if (aHighestIndex !== undefined && bHighestIndex !== undefined) {
|
||||
return direction === 'asc'
|
||||
? bHighestIndex - aHighestIndex
|
||||
: aHighestIndex - bHighestIndex;
|
||||
}
|
||||
// If one has a regex match and the other doesn't, sort by the one that does
|
||||
if (aHighestIndex !== undefined) return direction === 'asc' ? 1 : -1;
|
||||
if (bHighestIndex !== undefined) return direction === 'asc' ? -1 : 1;
|
||||
|
||||
// If we get here, no patterns matched or all patterns matched the same way
|
||||
// If both have no regex match, they are equal
|
||||
return 0;
|
||||
} catch (e) {
|
||||
return 0;
|
||||
|
||||
@@ -83,6 +83,7 @@ export type ParseValue = {
|
||||
visualTags: string[] | null;
|
||||
audioTags: string[] | null;
|
||||
releaseGroup: string | null;
|
||||
regexMatch: string | null;
|
||||
encode: string | null;
|
||||
indexer: string | null;
|
||||
year: string | null;
|
||||
@@ -137,6 +138,7 @@ const convertStreamToParseValue = (stream: ParsedStream): ParseValue => {
|
||||
audioTags: stream.audioTags,
|
||||
releaseGroup:
|
||||
stream.releaseGroup === 'Unknown' ? null : stream.releaseGroup,
|
||||
regexMatch: stream.regexMatch || null,
|
||||
encode: stream.encode === 'Unknown' ? null : stream.encode,
|
||||
indexer: stream.indexers || null,
|
||||
seeders: stream.torrent?.seeders || null,
|
||||
|
||||
@@ -46,6 +46,7 @@ export interface ParsedStream extends ParsedNameData {
|
||||
indexers?: string;
|
||||
releaseGroup: string;
|
||||
personal?: boolean;
|
||||
regexMatch?: string;
|
||||
stream?: {
|
||||
subtitles?: Subtitle[];
|
||||
behaviorHints?: {
|
||||
|
||||
@@ -63,9 +63,14 @@ const multipleRegex = makeValidator((x) => {
|
||||
);
|
||||
}
|
||||
// try compiling each pattern to check for validity
|
||||
|
||||
// each "pattern" is regexName::regexPattern, where regexName is optional
|
||||
// we need to only extract the regexPattern part,
|
||||
patterns.forEach((p) => {
|
||||
try {
|
||||
const delimiterIndex = p.indexOf('<::>');
|
||||
if (delimiterIndex !== -1) {
|
||||
p = p.slice(delimiterIndex + 2);
|
||||
}
|
||||
new RegExp(p);
|
||||
} catch (e) {
|
||||
throw new EnvError(`Invalid regex pattern: ${p}`);
|
||||
|
||||
Reference in New Issue
Block a user