UNPKG

ffmpeg-toolkit

Version:

A modern FFmpeg toolkit for Node.js

158 lines 5.5 kB
import { unlinkSync, writeFileSync } from 'fs-extra'; import { join } from 'path'; import { generateId } from '../utils/index.js'; export class AudioModule { constructor(base) { this.base = base; } async concatAudios(options) { const inputs = options.inputPaths.map((path) => `file '${path}'`); const tmpFile = join(this.base.getOutputFolder(), generateId('audio') + '.txt'); writeFileSync(tmpFile, inputs.join('\n')); return await this.base.process({ callback: () => this.base .ffmpeg() .input(tmpFile) .inputOptions(['-f', 'concat', '-safe', '0']) .output(options.pathOutput), data: { outputPath: options.pathOutput, }, onEnd: async () => unlinkSync(tmpFile), onError: async () => unlinkSync(tmpFile), }); } async hasAudio(inputVideo) { return await new Promise((resolve, reject) => { this.base.ffmpeg.ffprobe(inputVideo, (err, data) => { if (err) { return reject(err); } const audioStream = data.streams.find((s) => s.codec_type === 'audio'); resolve(!!audioStream); }); }); } getMixAudioFilters(volumeScaled) { return [ '[0:a]volume=1[a0]', `[1:a]volume=${volumeScaled}[a1]`, '[a0][a1]amix=inputs=2:duration=shortest[aout]', ]; } getSingleAudioFilters(volumeScaled) { return [`[1:a]volume=${volumeScaled}[aout]`]; } getDefaultOutputOptions(needShortest = false) { const options = [ '-map', '0:v', '-map', '[aout]', '-c:v', 'copy', '-c:a', 'aac', '-b:a', '192k', ]; if (needShortest) { options.push('-shortest'); } return options; } async mergeAudioToVideo(options) { const { inputPath, audioPath, pathOutput, volume = 1, isNoAudio = true } = options; const volumeScaled = (volume ** 2).toFixed(2); const hasAudio = await this.hasAudio(inputPath); const command = this.base.ffmpeg().input(inputPath).input(audioPath); let filters; let needShortest = false; if (isNoAudio) { filters = this.getSingleAudioFilters(volumeScaled); needShortest = true; } else if (hasAudio) { filters = [ '[0:a]volume=1[a0]', `[1:a]volume=${volumeScaled}[a1]`, '[a0][a1]amix=inputs=2:duration=longest[aout]', ]; needShortest = true; } else { filters = this.getSingleAudioFilters(volumeScaled); needShortest = true; } command .complexFilter(filters) .outputOptions(this.getDefaultOutputOptions(needShortest)) .output(pathOutput); return await this.base.process({ callback: () => command, data: { outputPath: pathOutput, }, isDefaultOptions: false, }); } getAudioSettings(format) { const settings = { mp3: { format: 'mp3', codec: 'libmp3lame', bitrate: '192k' }, wav: { format: 'wav', codec: 'pcm_s16le', bitrate: '1411k' }, aac: { format: 'aac', codec: 'aac', bitrate: '192k' }, ogg: { format: 'ogg', codec: 'libvorbis', bitrate: '192k', options: ['-ac', '2', '-ar', '44100', '-q:a', '6'], }, wma: { format: 'asf', codec: 'wmav2', bitrate: '192k', options: ['-ac', '2', '-ar', '44100', '-b:a', '192k'], }, flac: { format: 'flac', codec: 'flac', bitrate: '0' }, m4a: { format: 'mp4', codec: 'aac', bitrate: '192k', options: ['-vn'] }, aiff: { format: 'aiff', codec: 'pcm_s16be', bitrate: '1411k' }, alac: { format: 'alac', codec: 'alac', bitrate: '0', options: ['-vn', '-ac', '2', '-ar', '44100', '-compression_level', '0'], }, opus: { format: 'opus', codec: 'libopus', bitrate: '128k' }, }; return settings[format]; } async convertAudio(options) { const { inputPath, pathOutput, outputFormat } = options; const command = this.base.ffmpeg().input(inputPath); const settings = this.getAudioSettings(outputFormat); command.toFormat(settings.format).audioCodec(settings.codec).audioBitrate(settings.bitrate); if (settings.options) { command.outputOptions(settings.options); } command.outputOptions([ '-y', '-strict', 'experimental', '-threads', '4', '-max_muxing_queue_size', '8192', '-movflags', '+faststart', '-shortest', ]); return await this.base.process({ callback: () => command.output(pathOutput), data: { outputPath: pathOutput, }, isDefaultOptions: false, }); } } //# sourceMappingURL=audio.js.map