// media.js // Copyright (C) 2024 DTP Technologies, LLC // All Rights Reserved 'use strict'; import util from 'node:util'; import { execFile, spawn } from 'node:child_process'; const ExecFile = util.promisify(execFile); import { SiteService } from '../../lib/site-lib.js'; export default class MediaService extends SiteService { static get name ( ) { return 'MediaService'; } static get slug ( ) { return 'media'; } constructor (dtp) { super(dtp, MediaService); } ffmpeg (ffmpegArgs) { return new Promise((resolve, reject) => { const child = spawn(process.env.DTP_FFMPEG_PATH, ffmpegArgs, { cwd: this.dtp.config.root, stdio: [], }); child.stdout.on('data', (data) => { this.log.info(data.toString()); }); child.stderr.on('data', (data) => { const message = data.toString(); this.log.info(message); }); child.on('close', (code) => { if (code !== 0) { return reject(code); } return resolve(); }); }); } async ffprobe (input, options) { options = Object.assign({ streams: true, format: true, error: true, }, options); const ffprobeOpts = ['-print_format', 'json']; if (options.streams) { ffprobeOpts.push('-show_streams'); } if (options.format) { ffprobeOpts.push('-show_format'); } if (options.error) { ffprobeOpts.push('-show_error'); } ffprobeOpts.push(input); try { const { stdout } = await ExecFile(process.env.DTP_FFPROBE_PATH, ffprobeOpts, { cwd: this.dtp.config.root, encoding: 'utf8', }); const probe = JSON.parse(stdout); if (probe.format && probe.format.tags) { let keys = Object.keys(probe.format.tags); keys.forEach((key) => { probe.format.tags[key.replace(/\./g, '_')] = probe.format.tags[key]; delete probe.format.tags[key]; }); } probe.duration = probe.format.duration; probe.width = probe.format.width; if (Array.isArray(probe.streams) && probe.streams.length) { const stream = probe.streams .find((stream) => stream.codec_type === 'video') || probe.streams[0]; if (stream.duration) { probe.duration = parseFloat(stream.duration); } else { probe.duration = parseFloat(probe.format.duration); } probe.width = stream.width; probe.height = stream.height; const fpsFraction = stream.avg_frame_rate.split('/').map((value) => parseInt(value, 10)); if (Array.isArray(fpsFraction) && fpsFraction[1] !== 0) { probe.fps = fpsFraction[0] / fpsFraction[1]; } } else { probe.duration = undefined; probe.width = undefined; probe.height = undefined; } return probe; } catch (error) { this.log.error('failed to execute ffprobe', { input, error }); throw error; } } }