import { Context, Elysia, t } from "elysia"; import { NS_misc } from "../../lib/misc"; import createError from "http-errors"; import { NS_promise } from "../../lib/promise"; import { NS_ffmpeg_v2 } from "../../lib/ffmpeg-v2"; import { NS_crypto_client } from "../../../../../../packages/lib/helpers/client/crypto"; import { redis } from "../../lib/upstash"; import { offline_store } from "../../lib/offline-store"; import { NS_validator_v2, T_live, T_playback } from "./validator-v2"; import UserAgent from "user-agents"; import { NS_runtime } from "../../../../../../packages/lib/env.runtime"; import { NS_unpack_proxima } from "../../../../../../packages/lib/helpers/server/proxima/unpack"; const APP_ACCOUNT_ABC = () => NS_runtime.ENV_VAR_DB?.APP_ACCOUNT_ABC || process.env.APP_ACCOUNT_ABC; const APP_ENV = () => NS_runtime.ENV_VAR_DB?.APP_ENV || process.env.APP_ENV; export type T_delivery_payload = { playback: T_playback; out_mime: "video/mp4" | "video/x-matroska" | "video/mp2t"; subtitles?: T_live["subtitles"]; output_destination: NonNullable; delivery_mode: T_live["delivery_mode"]; delivery_format: T_live["delivery_format"]; progress_update_endpoint?: T_live["progress_update_endpoint"]; sbe_id: NonNullable; probe_metadata: Record; qLevel?: string; }; const sub_router = new Elysia({ prefix: "/ffmpeg/v2" }).post( "/probe", async (ctx) => { const { query, request, status, body } = ctx; const REQ0 = new URL(request.url); const REQ_ORIGIN = (ctx as any).publicOrigin || REQ0.origin; const REQ_headers = request.headers; const req0_headers_object = Object.fromEntries(REQ_headers.entries()); const req_signal = request.signal; const { playback, live, timeout } = body; if (!NS_misc.isValidUrlFormat(playback.url)) { if (playback.protocol !== "local") { throw createError(400, "invalid playback URL"); } } if ( live?.progress_update_endpoint && !NS_misc.isValidUrlFormat(live.progress_update_endpoint) ) { throw createError(400, "invalid progress update URL"); } // HANDLE HEADERS (() => { const playback_headers = playback?.headers; if ( !playback_headers || !( typeof playback_headers === "object" && !Array.isArray(playback_headers) ) ) { playback.headers = {}; } const ua = NS_misc.F_get_obj_kv({ name: "User-Agent", obj: playback_headers }); if (ua) return; try { const userAgent = UserAgent.random({ deviceCategory: "desktop" }) || new UserAgent(); if (userAgent?.toString()) { (playback.headers as any)["User-Agent"] = userAgent.toString(); } } catch {} })(); // handle picture quality (() => { if (!playback.picture_quality) { return; } const picture_quality = playback.picture_quality; if ( typeof picture_quality !== "string" || !picture_quality.trim() || picture_quality.trim().toLowerCase() === "n/a" ) { playback.picture_quality = undefined; return; } playback.picture_quality = picture_quality .trim() .split(" ") .filter((xx) => xx.length) .join(""); })(); // decoded proxima const dencoded_px = await (async () => { const _ = new URL(playback.url); if (!_.pathname.startsWith("/proxima") || !playback.fail_back) { return; } const encoded_data = _.pathname.split("/").slice(-1)[0]; if (!encoded_data) return; const [E_, R_] = await NS_promise.F_catch_promise({ promise: NS_unpack_proxima.F_unpack_proxima_endpoint_n_commands({ proxima_encoded: encoded_data }) }); if (!R_) return; const decoded_proxy_endpoint = R_.decoded_proxy_endpoint; const decoded_proxy_commands = R_.decoded_proxy_commands; if (!decoded_proxy_endpoint) return; const target = decoded_proxy_endpoint; const headers = (() => { const _h = decoded_proxy_commands?.headers; if (_h && NS_misc.is_plain_object(_h)) { return { ..._h, "User-Agent": (playback as any)?.headers?.["User-Agent"] }; } })(); return { target, headers }; })(); // RUN RAW PROBE async function run(arg0: { playback: T_playback }) { const { playback } = arg0; const [E_, R_] = await NS_promise.F_catch_promise({ promise: NS_ffmpeg_v2.F_probe_t1({ context: ctx, playback, timeout }) }); if (E_) throw E_; return R_; } const [E_, R_] = await NS_promise.F_catch_promise({ promise: (async () => { const og_playback = { ...playback }; let fail_back = false; if (playback.fail_back && dencoded_px?.target) { playback.headers = dencoded_px.headers; playback.url = dencoded_px.target.toString(); (playback as any).fail_back = false; fail_back = true; } let [E_, R_] = await NS_promise.F_catch_promise({ promise: run({ playback }) }); if (E_ && fail_back) { (playback as any).fail_back = true; playback.headers = og_playback.headers; playback.url = og_playback.url; if (APP_ENV() === "dev") { console.log("RUNNING FAIL BACK", "\n"); } [E_, R_] = await NS_promise.F_catch_promise({ promise: run({ playback }) }); } if (E_) { throw E_; } return R_; })() }); if (E_) { throw E_; } const probe_metadata = R_ as any; const qLevel = (() => { const _ = NS_ffmpeg_v2.F_get_qLevel({ metadata: probe_metadata }); return _; })(); if (!live) { return { qLevel, fail_back: (playback as any).fail_back, ...probe_metadata }; } const clean_metadata = NS_ffmpeg_v2.F_scan_raw_probe_metadata({ metadata: probe_metadata }); if (clean_metadata?.format) { const format_name = clean_metadata.format.name; if (typeof format_name === "string" && format_name.includes("hls")) { playback.type = "hls"; } else { playback.type = playback.type || "video"; } } const subtitles = (() => { if (!live.subtitles || !live.subtitles.length) { return; } if (clean_metadata?.subStreams && clean_metadata.subStreams.length) { return; } const original_subs = live.subtitles; const filtered = original_subs .filter((xx) => { return NS_misc.isValidUrlFormat(xx.url); }) .slice(0, 5); return filtered; })(); const out_mime: T_delivery_payload["out_mime"] = (() => { if (live.delivery_mode === "stream") { return "video/mp4"; } switch (live.delivery_format) { case "mp4": { return "video/mp4"; } break; case "mkv": { return "video/x-matroska"; } break; case "ts": { return "video/mp2t"; } break; default: { return "video/mp4"; } break; } })(); let output_destination = ( live.delivery_name || `${live.delivery_mode === "stream" ? "stream" : "video"}-${Date.now()}.${live.delivery_format}` ).trim(); if ( !output_destination .toLowerCase() .endsWith(`.${live.delivery_format}`.toLowerCase()) ) { output_destination = `${output_destination}.${live.delivery_format}`; } if (!live.sbe_id) { live.sbe_id = "000000"; } const demand_data = { playback, out_mime, subtitles, output_destination, delivery_mode: live.delivery_mode, delivery_format: live.delivery_format, progress_update_endpoint: live.progress_update_endpoint, sbe_id: live.sbe_id, qLevel }; const delivery_payload: T_delivery_payload = { ...demand_data, probe_metadata }; // store the data in Redis const cache_key = await (async () => { const cache_id = NS_crypto_client.F_crypto_randomUUID({ length: 40 }); let key = `llsvkm:${cache_id}`; if (APP_ACCOUNT_ABC()) key = `${key}:${APP_ACCOUNT_ABC()}`; // 30 days = 30 * 24 * 60 * 60 = 2,592,000 seconds const ONE_MONTH_IN_SECONDS = 2592000; (delivery_payload as any).cache_id = key; if (APP_ENV() === "dev") { offline_store.set(key, delivery_payload); } else { await redis().set(key, delivery_payload, { ex: ONE_MONTH_IN_SECONDS }); } return key; })(); const url_o = new URL(`/showtimeV2/delivery/${cache_key}`, REQ_ORIGIN); return { delivery_endpoint: url_o.toString(), metadata: delivery_payload }; }, { body: t.Object({ playback: NS_validator_v2.playback, live: t.Optional(NS_validator_v2.live), timeout: t.Optional( t.Number({ minimum: 0, error: () => "Timeout must be a positive integer greater than zero" }) ) }) } ); export const FfmpegV2Router = sub_router;