Download src/lib/misc.ts from slapbox/S1: direct link, hf CLI and curl.
- Browser
- Download file 8.05 kB
-
https://huggingface.co/spaces/slapbox/S1/resolve/main/src/lib/misc.ts
- Command line
-
hf download hf://spaces/slapbox/S1/src/lib/misc.ts
-
curl -L -o misc.ts https://huggingface.co/spaces/slapbox/S1/resolve/main/src/lib/misc.ts
8.05 kB
| import { NS_playback_helper } from "@x/core/server/playback.ts"; | |
| import createError from "http-errors"; | |
| function isUrlEncoded(str: string) { | |
| try { | |
| return decodeURIComponent(str) !== str; | |
| } catch (e) { | |
| return false; | |
| } | |
| } | |
| function isValidUrlFormat(url: string) { | |
| try { | |
| new URL(url); | |
| return true; | |
| } catch (e) { | |
| return false; | |
| } | |
| } | |
| async function F_test_connection(arg0: { | |
| url: URL; | |
| headers?: Record<string, any> | null; | |
| }) { | |
| const { url, headers } = arg0; | |
| const controller = new AbortController(); | |
| const t = setTimeout(() => { | |
| controller.abort(); | |
| }, 5_000); | |
| try { | |
| const response = await fetch(url.href, { | |
| method: "GET", | |
| headers: headers || {}, | |
| signal: controller.signal | |
| }); | |
| clearTimeout(t); | |
| if (!response.ok) { | |
| console.error("✗ Network test failed:", response.statusText); | |
| throw createError( | |
| response.status, | |
| `Ping failed: ${response.statusText} ${response.status}` | |
| ); | |
| } | |
| console.log("✓ Network connection OK:", response.status); | |
| return true; | |
| } catch (error: any) { | |
| clearTimeout(t); | |
| console.error(error); | |
| // throw createError(`Cannot reach URL: ${error.message}`); | |
| throw error; | |
| } | |
| } | |
| function F_get_obj_kv(arg0: { | |
| name: string; | |
| obj?: Record<string, any> | null; | |
| }) { | |
| const { name, obj } = arg0; | |
| if (!obj || typeof obj !== "object") return; | |
| const key = Object.keys(obj).find( | |
| (k) => k.toLowerCase() === name.toLowerCase() | |
| ); | |
| return key ? obj[key] : null; | |
| } | |
| function is_plain_object(obj: any): obj is Record<string, any> { | |
| return !!obj && obj.constructor === Object; | |
| } | |
| function cleanName(name: string): string { | |
| // Replace any character that is not a letter, number, bracket, -, or _ with an underscore | |
| return name.replace(/[^\w\-()[\]{}]/g, "_"); | |
| } | |
| function cleanNameV2(name: string): string { | |
| // Keep only letters, numbers, -, _, (, ), [, ], {, }, and . | |
| // Replace everything else with _ | |
| return name.replace(/[^\w\-()[\]{}.]/g, "_"); | |
| } | |
| function isValidUnixTimestamp(timestamp: number): boolean { | |
| const date = new Date(timestamp); | |
| return !isNaN(date.getTime()); | |
| } | |
| /* | |
| If given a master playlist, follow the highest-bandwidth variant. | |
| `separate_audio` is true when that variant takes its audio from a separate | |
| rendition playlist: a single-file passthrough would silently lose the audio. | |
| */ | |
| async function F_resolve_media_hls_playlist(arg0: { | |
| url: string; | |
| headers: Record<string, any>; | |
| }): Promise<{ url: string; text: string; separate_audio: boolean }> { | |
| const { url, headers } = arg0; | |
| const text = await fetchText({ url, headers }); | |
| if (!text.includes("#EXT-X-STREAM-INF")) { | |
| return { url, text, separate_audio: false }; | |
| } | |
| const lines = text.split("\n").map((l) => l.trim()); | |
| let best = { bw: -1, uri: "", audio: undefined as string | undefined }; | |
| for (let i = 0; i < lines.length; i++) { | |
| const line = lines[i]; | |
| if (!line?.startsWith("#EXT-X-STREAM-INF")) continue; | |
| const bw = Number(/BANDWIDTH=(\d+)/.exec(line)?.[1] ?? 0); | |
| const audio = /AUDIO="([^"]+)"/.exec(line)?.[1]; | |
| const uri = lines[i + 1]; | |
| if (uri && !uri.startsWith("#") && bw > best.bw) { | |
| best = { bw, uri, audio }; | |
| } | |
| } | |
| // without this an empty uri resolves to the same url and recurses forever | |
| if (!best.uri) throw new Error("No playable variant in master playlist"); | |
| const separate_audio = | |
| !!best.audio && | |
| lines.some( | |
| (l) => | |
| l.startsWith("#EXT-X-MEDIA") && | |
| /TYPE=AUDIO/.test(l) && | |
| l.includes(`GROUP-ID="${best.audio}"`) && | |
| /URI="/.test(l) | |
| ); | |
| const next = await F_resolve_media_hls_playlist({ | |
| url: new URL(best.uri, url).href, | |
| headers | |
| }); | |
| return { ...next, separate_audio: next.separate_audio || separate_audio }; | |
| async function fetchText(arg0: { | |
| url: string; | |
| headers: Record<string, any>; | |
| }) { | |
| const { url, headers } = arg0; | |
| const res = await fetch(url, { | |
| headers: headers | |
| }); | |
| if (!res.ok) | |
| throw new Error(`Fetch failed: status: (${res.status}): ${url}`); | |
| const content_type = res.headers.get("content-type"); | |
| const is_m3u8 = | |
| content_type?.includes("application/vnd.apple.mpegurl") || | |
| content_type?.includes("application/x-mpegurl") || | |
| (await NS_playback_helper.F_is_M3U8_response({ | |
| response: res.clone() as any | |
| })); | |
| if (!is_m3u8) { | |
| throw new Error(`Content type not supported`); | |
| } | |
| return res.text(); | |
| } | |
| } | |
| type Range = { offset: number; length: number }; | |
| type Init = { url: string; key: Key; range?: Range }; | |
| type Key = { uri: string; iv?: string } | null; | |
| type Segment = { url: string; seq: number; key: Key; range?: Range }; | |
| type Media = { | |
| segments: Segment[]; | |
| initUrl?: string; | |
| init?: Init; | |
| init_changes: boolean; | |
| ended: boolean; | |
| }; | |
| /* | |
| parse an HLS media playlist (.m3u8) into segments, encryption keys, the | |
| initialization segment (fMP4/CMAF), byte ranges and playlist status. | |
| */ | |
| function F_parse_hls_media(arg0: { text: string; base: string }): Media { | |
| const { text, base } = arg0; | |
| let seq = Number(/#EXT-X-MEDIA-SEQUENCE:(\d+)/.exec(text)?.[1] ?? 0); | |
| let key: Key = null; | |
| let init: Init | undefined; | |
| let init_changes = false; | |
| let pending_range: Range | undefined; | |
| let prev_end: number | undefined; | |
| const segments: Segment[] = []; | |
| for (const raw of text.split("\n")) { | |
| const line = raw.trim(); | |
| if (!line) continue; | |
| if (line.startsWith("#EXT-X-KEY")) { | |
| const method = /METHOD=([^,]+)/.exec(line)?.[1]; | |
| if (method === "NONE") { | |
| key = null; | |
| } else if (method === "AES-128") { | |
| const uri = /URI="([^"]+)"/.exec(line)![1]!; | |
| key = { | |
| uri: new URL(uri, base).href, | |
| iv: /IV=(0x[0-9a-fA-F]+)/i.exec(line)?.[1] | |
| }; | |
| } else { | |
| throw new Error(`Unsupported encryption: ${method}`); | |
| } | |
| } else if (line.startsWith("#EXT-X-MAP")) { | |
| const uri = /URI="([^"]+)"/.exec(line)?.[1]; | |
| if (!uri) throw new Error("EXT-X-MAP without a URI"); | |
| const br = /BYTERANGE="([^"]+)"/.exec(line)?.[1]; | |
| const next: Init = { | |
| url: new URL(uri, base).href, | |
| key, | |
| range: br ? parseByteRange(br, undefined) : undefined | |
| }; | |
| if (!init) { | |
| init = next; | |
| } else if ( | |
| init.url !== next.url || | |
| init.range?.offset !== next.range?.offset || | |
| init.range?.length !== next.range?.length | |
| ) { | |
| init_changes = true; | |
| } | |
| } else if (line.startsWith("#EXT-X-BYTERANGE")) { | |
| pending_range = parseByteRange( | |
| line.slice(line.indexOf(":") + 1).trim(), | |
| prev_end | |
| ); | |
| } else if (!line.startsWith("#")) { | |
| segments.push({ | |
| url: new URL(line, base).href, | |
| seq: seq++, | |
| key, | |
| ...(pending_range ? { range: pending_range } : {}) | |
| }); | |
| prev_end = pending_range | |
| ? pending_range.offset + pending_range.length | |
| : undefined; | |
| pending_range = undefined; | |
| } | |
| } | |
| return { | |
| segments, | |
| init, | |
| init_changes, | |
| // a VOD playlist is complete even when a host forgot the ENDLIST tag | |
| ended: | |
| text.includes("#EXT-X-ENDLIST") || | |
| /#EXT-X-PLAYLIST-TYPE:\s*VOD/.test(text) | |
| }; | |
| /* | |
| "len@offset" or "len". Without an offset the range continues right after the | |
| previous segment's range (RFC 8216 4.3.2.2). | |
| */ | |
| function parseByteRange(value: string, prev_end: number | undefined): Range { | |
| const [len, off] = value.split("@"); | |
| const length = Number(len); | |
| const offset = off !== undefined ? Number(off) : (prev_end ?? 0); | |
| if (!Number.isFinite(length) || !Number.isFinite(offset) || length <= 0) { | |
| throw new Error(`Invalid BYTERANGE: ${value}`); | |
| } | |
| return { length, offset }; | |
| } | |
| } | |
| export const NS_misc = { | |
| isUrlEncoded, | |
| isValidUrlFormat, | |
| F_test_connection, | |
| F_get_obj_kv, | |
| is_plain_object, | |
| cleanName, | |
| cleanNameV2, | |
| isValidUnixTimestamp, | |
| F_resolve_media_hls_playlist, | |
| F_parse_hls_media | |
| }; | |