Keep pause across Coolify restarts, apply the chosen still from the image picker, and remux clips with Range/faststart so finished video can be scrubbed.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+54
-2
@@ -1,4 +1,4 @@
|
||||
import { existsSync, writeFileSync, readFileSync, unlinkSync } from 'node:fs'
|
||||
import { existsSync, writeFileSync, readFileSync, unlinkSync, statSync, openSync, readSync, closeSync, copyFileSync } from 'node:fs'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { spawn } from 'node:child_process'
|
||||
|
||||
@@ -171,6 +171,58 @@ async function probeSize(filePath: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function mp4MoovIsAtStart(filePath: string) {
|
||||
const fd = openSync(filePath, 'r')
|
||||
try {
|
||||
let offset = 0
|
||||
const header = Buffer.alloc(16)
|
||||
for (let i = 0; i < 12; i++) {
|
||||
if (readSync(fd, header, 0, 8, offset) < 8) return false
|
||||
let size = header.readUInt32BE(0)
|
||||
const type = header.toString('ascii', 4, 8)
|
||||
if (size === 1) {
|
||||
if (readSync(fd, header, 8, 8, offset + 8) < 8) return false
|
||||
size = Number(header.readBigUInt64BE(8))
|
||||
}
|
||||
if (!size || size < 8) return false
|
||||
if (type === 'moov') return true
|
||||
if (type === 'mdat') return false
|
||||
offset += size
|
||||
}
|
||||
return false
|
||||
} finally {
|
||||
closeSync(fd)
|
||||
}
|
||||
}
|
||||
|
||||
export async function remuxFaststart(inputPath: string) {
|
||||
if (!existsSync(inputPath)) return
|
||||
try {
|
||||
if (mp4MoovIsAtStart(inputPath)) return
|
||||
} catch {
|
||||
// Fall through and try a remux anyway.
|
||||
}
|
||||
const tmp = `${inputPath}.faststart.tmp.mp4`
|
||||
try {
|
||||
await runFfmpeg([
|
||||
'-y',
|
||||
'-i', inputPath,
|
||||
'-c', 'copy',
|
||||
'-map', '0',
|
||||
'-movflags', '+faststart',
|
||||
tmp
|
||||
], 120000)
|
||||
if (!existsSync(tmp) || statSync(tmp).size < 32) {
|
||||
try { unlinkSync(tmp) } catch { /* ignore */ }
|
||||
return
|
||||
}
|
||||
copyFileSync(tmp, inputPath)
|
||||
try { unlinkSync(tmp) } catch { /* ignore */ }
|
||||
} catch {
|
||||
try { unlinkSync(tmp) } catch { /* ignore */ }
|
||||
}
|
||||
}
|
||||
|
||||
function encodeArgs(outputPath: string, withAudio: boolean) {
|
||||
const args = [
|
||||
'-c:v', 'libx264',
|
||||
@@ -271,7 +323,7 @@ export async function concatMp4(part1Path: string, part2Path: string, outputPath
|
||||
const listPath = join(dirname(outputPath), 'concat_list.txt')
|
||||
writeFileSync(listPath, `file '${concatPath(part1Path)}'\nfile '${concatPath(part2Path)}'\n`)
|
||||
try {
|
||||
await runFfmpeg(['-y', '-f', 'concat', '-safe', '0', '-i', listPath, '-c', 'copy', outputPath], 180000)
|
||||
await runFfmpeg(['-y', '-f', 'concat', '-safe', '0', '-i', listPath, '-c', 'copy', '-movflags', '+faststart', outputPath], 180000)
|
||||
await assertStitchLength(part1Path, part2Path, outputPath)
|
||||
} catch {
|
||||
await concatReencode(part1Path, part2Path, outputPath)
|
||||
|
||||
Reference in New Issue
Block a user