You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
109 lines
2.9 KiB
109 lines
2.9 KiB
// 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;
|
|
}
|
|
}
|
|
}
|