Skip to content

Instantly share code, notes, and snippets.

@wlib
Created July 14, 2024 02:37
Show Gist options
  • Save wlib/0874b8bfe2efefe6edcfeb7cfd70e925 to your computer and use it in GitHub Desktop.
Save wlib/0874b8bfe2efefe6edcfeb7cfd70e925 to your computer and use it in GitHub Desktop.
From an m3u8 video stream link, download a zip file that contains the ts stream chunks and a index.txt that can be used with ffmpeg
const sourceUrl = "https://m3u8-0.c-spanvideo.org/clip/clip.5081597.576.tsc.m3u8"
const source = await fetch(sourceUrl)
.then(res => res.ok ? res.text() : undefined)
if (!source)
throw new Error("it's over")
const tsBlobs = await Promise.all(
source
.split("\n")
.flatMap(line => {
const trimmed = line.trim()
if (trimmed.startsWith("#"))
return []
try {
return [new URL(trimmed, sourceUrl)]
}
catch {
return []
}
})
.filter(url => url.pathname.endsWith(".ts"))
.map(url =>
fetch(url).then(res => {
if (!res.ok)
throw new Error("Failed to download", url)
return res.blob()
})
)
)
const tsByteArrays = await Promise.all(
tsBlobs.map(async blob => new Uint8Array(await blob.arrayBuffer()))
)
const { zip } = await import("https://esm.sh/fflate@0.8.2")
const indexFile = tsBlobs.map((blob, i) => `file 'chunks/${i}.ts'`).join("\n")
const zipFile = await new Promise(async (resolve, reject) => {
zip(
{
"index.txt": new TextEncoder().encode(indexFile),
chunks: Object.fromEntries(
tsByteArrays.map((byteArray, i) => [`${i}.ts`, byteArray])
)
},
{ level: 0 },
(error, data) => {
if (error)
reject(error)
else
resolve(data)
}
)
})
const downloadLink = document.createElement("a")
downloadLink.href = URL.createObjectURL(
new Blob([zipFile], { type: "application/zip" })
)
downloadLink.download = "video-source.zip"
downloadLink.click()
console.log("cd Downloads/video-source")
console.log("ffmpeg -f concat -i index.txt -c copy video.mp4")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment