2024-10-25 09:39:35 +00:00

147 lines
3.9 KiB
JavaScript

import fs from "node:fs"
import path from "node:path"
import { exec } from "node:child_process"
import { EventEmitter } from "node:events"
export default class MultiqualityHLSJob {
constructor({
input,
outputDir,
outputMasterName = "master.m3u8",
levels,
}) {
this.input = input
this.outputDir = outputDir
this.levels = levels
this.outputMasterName = outputMasterName
this.bin = require("ffmpeg-static")
return this
}
events = new EventEmitter()
buildCommand = () => {
const cmdStr = [
this.bin,
`-v quiet -stats`,
`-i ${this.input}`,
`-filter_complex`,
]
// set split args
let splitLevels = [
`[0:v]split=${this.levels.length}`
]
this.levels.forEach((level, i) => {
splitLevels[0] += (`[v${i + 1}]`)
})
for (const [index, level] of this.levels.entries()) {
if (level.original) {
splitLevels.push(`[v1]copy[v1out]`)
continue
}
let scaleFilter = `[v${index + 1}]scale=w=${level.width}:h=trunc(ow/a/2)*2[v${index + 1}out]`
splitLevels.push(scaleFilter)
}
cmdStr.push(`"${splitLevels.join(";")}"`)
// set levels map
for (const [index, level] of this.levels.entries()) {
let mapArgs = [
`-map "[v${index + 1}out]"`,
`-x264-params "nal-hrd=cbr:force-cfr=1"`,
`-c:v:${index} ${level.codec}`,
`-b:v:${index} ${level.bitrate}`,
`-maxrate:v:${index} ${level.bitrate}`,
`-minrate:v:${index} ${level.bitrate}`,
`-bufsize:v:${index} ${level.bitrate}`,
`-preset ${level.preset}`,
`-g 48`,
`-sc_threshold 0`,
`-keyint_min 48`,
]
cmdStr.push(...mapArgs)
}
// set output
cmdStr.push(`-f hls`)
cmdStr.push(`-hls_time 2`)
cmdStr.push(`-hls_playlist_type vod`)
cmdStr.push(`-hls_flags independent_segments`)
cmdStr.push(`-hls_segment_type mpegts`)
cmdStr.push(`-hls_segment_filename stream_%v/data%02d.ts`)
cmdStr.push(`-master_pl_name ${this.outputMasterName}`)
cmdStr.push(`-var_stream_map`)
let streamMapVar = []
for (const [index, level] of this.levels.entries()) {
streamMapVar.push(`v:${index}`)
}
cmdStr.push(`"${streamMapVar.join(" ")}"`)
cmdStr.push(`"stream_%v/stream.m3u8"`)
return cmdStr.join(" ")
}
run = () => {
const cmdStr = this.buildCommand()
console.log(cmdStr)
const cwd = `${path.dirname(this.input)}/hls`
if (!fs.existsSync(cwd)) {
fs.mkdirSync(cwd, { recursive: true })
}
console.log(`[HLS] Started multiquality transcode`, {
input: this.input,
cwd: cwd,
})
const process = exec(
cmdStr,
{
cwd: cwd,
},
(error, stdout, stderr) => {
if (error) {
console.log(`[HLS] Failed to transcode >`, error)
return this.events.emit("error", error)
}
if (stderr) {
//return this.events.emit("error", stderr)
}
console.log(`[HLS] Finished transcode >`, cwd)
return this.events.emit("end", {
filepath: path.join(cwd, this.outputMasterName),
isDirectory: true,
})
}
)
process.stdout.on("data", (data) => {
console.log(data.toString())
})
}
on = (key, cb) => {
this.events.on(key, cb)
return this
}
}