diff options
author | metamuffin <metamuffin@disroot.org> | 2025-05-18 17:06:15 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-05-18 17:06:15 +0200 |
commit | 630559d69f4e6adba776186bcebea49a40386cc7 (patch) | |
tree | 9074ca6cabc185cac0c74e344aa5a79be4cc1ad3 /scripts/ytdlp_download.ts | |
parent | a723073738aa512514a9cb2f9826184a2507b709 (diff) | |
download | isda-630559d69f4e6adba776186bcebea49a40386cc7.tar isda-630559d69f4e6adba776186bcebea49a40386cc7.tar.bz2 isda-630559d69f4e6adba776186bcebea49a40386cc7.tar.zst |
downloader script and status reporting works
Diffstat (limited to 'scripts/ytdlp_download.ts')
-rw-r--r-- | scripts/ytdlp_download.ts | 92 |
1 files changed, 92 insertions, 0 deletions
diff --git a/scripts/ytdlp_download.ts b/scripts/ytdlp_download.ts index e69de29..cc45367 100644 --- a/scripts/ytdlp_download.ts +++ b/scripts/ytdlp_download.ts @@ -0,0 +1,92 @@ + +const ws = new WebSocket(Deno.args[0]) + +export class TextLineStream extends TransformStream<string, string> { + line = ""; + constructor() { + super({ + transform: (chunk, controller) => { + this.line += chunk; + while (true) { + const newline = this.line.indexOf("\n"); + if (newline === -1) break; + controller.enqueue(this.line.slice(0, newline)); + this.line = this.line.slice(newline + 1); + } + }, + flush: (controller) => { + if (this.line === "") return; + controller.enqueue(this.line); + }, + }); + } +} + + +function key_to_url(key: string): string { + const [kind, id] = key.split(":", 2) + if (kind == "youtube") return `https://youtube.com/watch?v=${id}` + throw new Error("unknown kind"); +} + +async function do_download(key: string, output: string) { + const url = key_to_url(key) + await Deno.mkdir(output, { recursive: true }) + const child = new Deno.Command("yt-dlp", { + args: [ + "--quiet", + "--progress", + "--progress-template", "%(progress)j", + "--newline", + "--download-archive", "archive", + "-f", "bestvideo+bestaudio", + "--embed-metadata", + "--embed-thumbnail", + "--embed-info-json", + "--embed-subs", + "--embed-chapters", + "--ignore-no-formats", + "--remux", "mkv", + "-o", "%(id)s", + url + ], + stdout: "piped", + stderr: "inherit", + cwd: output + }).spawn() + + const lines = child.stdout.pipeThrough(new TextDecoderStream()).pipeThrough(new TextLineStream()) + for await (const line of lines) { + if (!line.length) continue + console.log(JSON.stringify(line)); + const k = JSON.parse(line) + ws.send(JSON.stringify({ + t: "metadata", key, data: { + progress: (k._percent ?? 0) / 100, + status: k._default_template?.trim() ?? "" + } + })) + } + const status = await child.status + if (!status.success) throw new Error("download failed"); + + ws.send(JSON.stringify({ t: "complete", key })) +} + +ws.onerror = () => console.error("ws error") +ws.onclose = () => console.error("ws closed") +ws.onopen = () => { + console.log("ws open"); + ws.send(JSON.stringify({ t: "register", name: "dummy worker", task_kinds: ["youtube"] })) + ws.send(JSON.stringify({ t: "accept" })) +} +ws.onmessage = async ev => { + if (typeof ev.data != "string") return + const p = JSON.parse(ev.data) + if (p.t == "error") console.error(`error: ${p.message}`); + if (p.t == "work") { + if (!p.data.output) throw new Error("no output"); + await do_download(p.key, p.data.output) + ws.send(JSON.stringify({ t: "accept" })) + } +} |