Skip to content

Instantly share code, notes, and snippets.

@Danielduel
Created February 3, 2024 12:33
Show Gist options
  • Save Danielduel/289ddf73a3c6a53dd434048e1bacd7ee to your computer and use it in GitHub Desktop.
Save Danielduel/289ddf73a3c6a53dd434048e1bacd7ee to your computer and use it in GitHub Desktop.
Tool for creating a m3u8 playlist file out of a folder of fragmented AV files (.ts).
/**
* Example usages
*
* deno run -A LINK_TO_RAW_OF_THIS_GIST
*
* deno run -A LINK_TO_RAW_OF_THIS_GIST \
* --path "/path/to/your/folder/with/data"
*
* deno run -A LINK_TO_RAW_OF_THIS_GIST \
* --path "/path/to/your/folder/with/data" \
* --name "customname.m3u8"
*
* deno run -A LINK_TO_RAW_OF_THIS_GIST \
* --name "customname.m3u8"
*
*
*
* Manual
*
* Tool for creating a m3u8 playlist file out of a folder of fragmented AV files (.ts).
* The main use-case of this script is to link together fragments after you've got
* a recording in obs and it is known to create wrong playlist file (.m3u8).
* This script assumes:
* Your fragment files have `.ts` extension.
* Your fragment files have ordering suffix after a dash.
* Your fragment files are in a single directory.
* In short, those are first 3, then 100th, 101th and n segment file:
* `something-0.ts`,
* `something-1.ts`,
* `something-2.ts`,
* ...
* `something-99.ts`,
* `something-100.ts`,
* ...
* `something-n.ts`
*
* In my case, I am recording my daily streams and my obs configuration is as follows:
* Settings/Output/Recording:
* Recording path: `/home/user/Videos/streams/`
* Recording format: `HLS(.m3u8 + .ts)`
* Settings/Advanced/Recording:
* Filename Formatting: `%CCYY-%MM-%DD/%CCYY-%MM-%DD %hh-%mm-%ss`
*
* Optional arguments:
* --path <path> Path to a single directory, without leading slash, can be absolute,
* defaults to `.`
* --name <name> Name of generated playlist file, it is saved in the same directory as
* your path argument points to, defaults to `playlist.m3u8`
*
*/
import { parseArgs } from "https://deno.land/std@0.207.0/cli/parse_args.ts";
const args = parseArgs(Deno.args, {
string: ["path", "name"]
});
const path = args.path || ".";
const name = args.name || "playlist.m3u8";
const fileIterator = Deno.readDirSync(path)
const fileArrayUnsorted = [...fileIterator];
type SegmentFileEntry = Deno.DirEntry & {
segmentName: string;
last: number;
}
const segmentArray = fileArrayUnsorted
.filter(({ name }) => {
return name.includes(".ts");
})
.map((data): SegmentFileEntry => {
const [segmentName] = data.name.split(".ts");
const last = segmentName.split("-").pop()!;
return {
...data,
segmentName,
last: +last
};
})
.sort((a, b) => {
return a.last - b.last;
});
const duration = 2;
const header = `
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:${duration}
#EXT-X-MEDIA-SEQUENCE:2426
`;
const suffix = `
#EXT-X-ENDLIST
`;
const item = (name: string) => `
#EXTINF:2.000000,
${name}
`;
const result = `
${header}
${segmentArray.map(({name}) => item(name)).join("")}
${suffix}
`
.split("\n")
.filter(line => line !== "")
.join("\n")
Deno.writeTextFileSync(`${path}/${name}`, result);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment