Skip to content

Commit 575c884

Browse files
committed
purstream: expose stream facts and leave presentation to Core
1 parent a0d34f3 commit 575c884

1 file changed

Lines changed: 64 additions & 9 deletions

File tree

engine_v2/providers/purstream.mjs

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -206,15 +206,28 @@ function normalizeSource(item, endpoint, userAgent, request) {
206206
const explicitFormat = cleanText(item.format)?.toLowerCase();
207207
const direct = lower.endsWith(".m3u8") || lower.endsWith(".mp4") || explicitFormat === "m3u8" || explicitFormat === "mp4";
208208
if (!direct) return null;
209+
209210
const label = cleanText(item.source_name ?? item.name ?? item.label) ?? "Purstream";
210-
const language = parseLanguage(label);
211-
const quality = parseQuality(label);
211+
const declaredQuality = cleanText(item.quality ?? item.resolution);
212+
const declaredLanguage = cleanText(item.language ?? item.lang ?? item.audio_language);
213+
const declaredCodec = cleanText(item.codec ?? item.video_codec ?? item.videoCodec);
214+
const declaredAudio = cleanText(item.audio ?? item.audio_codec ?? item.audioCodec);
215+
const declaredDuration = item.duration ?? item.duration_minutes ?? item.durationMinutes ?? item.runtime ?? null;
216+
const declaredSourceType = cleanText(item.source_type ?? item.sourceType ?? item.release_type ?? item.releaseType);
217+
const declaredAgeRating = cleanText(item.age_rating ?? item.ageRating ?? item.certification);
218+
212219
return {
213-
title: `Purstream ${quality} | ${language}`,
220+
// Purstream supplies facts only. The Core owns all user-facing presentation.
221+
title: "Purstream",
214222
name: "Purstream",
215223
url,
216-
quality,
217-
language,
224+
quality: declaredQuality ?? parseQuality(label),
225+
language: declaredLanguage ?? parseLanguage(label),
226+
codec: declaredCodec ?? parseCodec(label),
227+
audio: declaredAudio ?? parseAudio(label),
228+
duration: declaredDuration,
229+
sourceType: declaredSourceType ?? parseSourceType(label),
230+
ageRating: declaredAgeRating,
218231
format: lower.endsWith(".mp4") || explicitFormat === "mp4" ? "mp4" : "m3u8",
219232
headers: {
220233
"User-Agent": userAgent,
@@ -231,25 +244,62 @@ function parseLanguage(value) {
231244
if (text.includes("VOSTFR")) return "VOSTFR";
232245
if (text.includes("VFQ")) return "VFQ";
233246
if (text.includes("VFF")) return "VFF";
234-
if (text.includes("VF")) return "VF";
247+
if (/\bVF\b/.test(text)) return "VF";
235248
if (text.includes("MULTI") || text.includes("DUAL")) return "MULTI";
236-
return "MULTI";
249+
if (/\bVO\b/.test(text)) return "VO";
250+
return null;
237251
}
238252

239253
function parseQuality(value) {
240254
const text = String(value ?? "").toUpperCase();
241-
if (text.includes("2160") || text.includes("4K")) return "4K";
255+
if (text.includes("2160") || /\b4K\b/.test(text) || text.includes("UHD")) return "4K";
256+
if (text.includes("1440")) return "1440p";
242257
if (text.includes("1080")) return "1080p";
243258
if (text.includes("720")) return "720p";
259+
if (text.includes("576")) return "576p";
244260
if (text.includes("480")) return "480p";
245-
return "HD";
261+
return null;
262+
}
263+
264+
function parseCodec(value) {
265+
const text = String(value ?? "").toUpperCase();
266+
if (/\b(?:HEVC|H\.?265|X265)\b/.test(text)) return "HEVC";
267+
if (/\b(?:AVC|H\.?264|X264)\b/.test(text)) return "H.264";
268+
if (/\bAV1\b/.test(text)) return "AV1";
269+
if (/\bVP9\b/.test(text)) return "VP9";
270+
return null;
271+
}
272+
273+
function parseAudio(value) {
274+
const text = String(value ?? "").toUpperCase();
275+
const codec = text.match(/\b(?:E-?AC-?3|DDP|DD\+|AC-?3|AAC|DTS(?:-HD)?|TRUEHD|ATMOS|FLAC|OPUS)\b/);
276+
const channels = text.match(/\b(?:7\.1|5\.1|2\.0|2\.1|1\.0)\b/);
277+
if (!codec && !channels) return null;
278+
let name = codec?.[0] ?? "";
279+
name = name.replace(/^DDP$/i, "E-AC3").replace(/^DD\+$/i, "E-AC3").replace(/^E-?AC-?3$/i, "E-AC3").replace(/^AC-?3$/i, "AC3");
280+
return [name, channels?.[0]].filter(Boolean).join(" ");
281+
}
282+
283+
function parseSourceType(value) {
284+
const text = String(value ?? "").toUpperCase();
285+
if (/BLU[ ._-]?RAY|BDRIP|BRRIP/.test(text)) return "BLU-RAY";
286+
if (/WEB[ ._-]?DL/.test(text)) return "WEB-DL";
287+
if (/WEB[ ._-]?RIP/.test(text)) return "WEBRIP";
288+
if (/\bHDTV\b/.test(text)) return "HDTV";
289+
if (/\bDVD(?:RIP)?\b/.test(text)) return "DVD";
290+
if (/\bCAM\b|\bTELESYNC\b/.test(text)) return "CAM";
291+
return null;
246292
}
247293

248294
async function resolveMetadata(request, resolver) {
249295
const base = {
250296
title: cleanText(request.title),
251297
aliases: [],
252298
year: asYear(request.year),
299+
runtime: null,
300+
durationMinutes: null,
301+
certification: null,
302+
ageRating: null,
253303
};
254304
if (!resolver) {
255305
if (!base.title) throw new Error("Purstream search requires title or metadataResolver");
@@ -260,6 +310,11 @@ async function resolveMetadata(request, resolver) {
260310
title: cleanText(resolved?.title ?? resolved?.fr ?? base.title),
261311
aliases: unique([...(resolved?.aliases ?? []), resolved?.originalTitle, resolved?.orig, base.title].map(cleanText).filter(Boolean)),
262312
year: asYear(resolved?.year ?? base.year),
313+
runtime: resolved?.runtime ?? resolved?.duration ?? null,
314+
durationMinutes: resolved?.durationMinutes ?? resolved?.duration_minutes ?? null,
315+
certification: cleanText(resolved?.certification ?? resolved?.contentRating ?? resolved?.content_rating),
316+
ageRating: cleanText(resolved?.ageRating ?? resolved?.age_rating),
317+
releaseDate: cleanText(resolved?.releaseDate ?? resolved?.release_date ?? resolved?.firstAirDate ?? resolved?.first_air_date),
263318
};
264319
}
265320

0 commit comments

Comments
 (0)