const ws = new WebSocket(Deno.args[0]) export class TextLineStream extends TransformStream { 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: "yt-dlp video downloader", 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" })) } }