import _ from 'lodash' import fs from 'fs' import { Plugin } from './plugin.js'; import { FFMpegUtils } from 'common-utils'; export class SpeedPlugin extends Plugin { constructor(name, options) { super(name, options); } async applyPrerender(originalManuscript, jobId) { } async applyPostrender(originalManuscript, jobId, outFiles) { for (let outFile of outFiles) { const speedFactor = this.options.speed || 1.0; if (speedFactor !== 1.0) { const tempFile = outFile + '.original.mp4'; fs.renameSync(outFile, tempFile); try { const pts = 1.0 / speedFactor; if (pts <= 0) { return } // atempo only supports 0.5-2.0, so we chain filters to approximate the target let remaining = speedFactor; const atempoFilters = []; while (remaining > 2.0) { atempoFilters.push('atempo=2.0'); remaining /= 2.0; } while (remaining < 0.5) { atempoFilters.push('atempo=0.5'); remaining *= 2.0; } atempoFilters.push(`atempo=${remaining.toFixed(6)}`); const audioFilter = atempoFilters.join(','); this.log(`Speeding the video ${outFile} by a factor of x${speedFactor}`); const cmd = `ffmpeg -i ${tempFile} -filter_complex "[0:v]setpts=${pts.toFixed(6)}*PTS[v];[0:a]${audioFilter}[a]" -map "[v]" -map "[a]" ${outFile}`; await FFMpegUtils.execute(cmd); fs.unlinkSync(tempFile); } catch (err) { this.log(`Error applying speed change: ${err}`); if (fs.existsSync(outFile)) { fs.unlinkSync(outFile); } fs.renameSync(tempFile, outFile); } } } } }