src/services/fs/contents/FFProbeService.js
// @flow
import { exec } from "child-process-promise";
export default class FFProbeService {
createExecCommand: (targetPath: string) => string = (
targetPath: string
): string =>
[
"ffprobe",
"-v",
"warning",
"-threads",
"0",
"-of",
"flat=s=_",
"-select_streams",
"v:0",
"-show_entries",
"stream=height,width",
JSON.stringify(targetPath)
].join(" ");
createExecCommandForAudio: (targetPath: string) => string = (
targetPath: string
): string =>
[
"ffprobe",
"-show_data_hash",
"SHA160",
"-v",
"warning",
"-threads",
"0",
"-of",
"json",
"-select_streams",
"a:0",
"-show_streams",
JSON.stringify(targetPath)
].join(" ");
parseOutput: ({ stderr: string, stdout: string, ... }) => {
damaged: boolean,
height: number,
ratio: number,
width: number,
...
} = ({
stdout,
stderr
}: {
stdout: string,
stderr: string
}): { width: number, height: number, ratio: number, damaged: boolean } => {
const width = Number((/width=(\d+)/.exec(stdout) || [0]).pop());
const height = Number((/height=(\d+)/.exec(stdout) || [0]).pop());
return {
width,
height,
ratio: width / height || 0,
damaged: this.isDamaged(stderr)
};
};
parseOutputForAudio: ({ stderr: string, stdout: string, ... }) => {
codec_name: string,
damaged: boolean,
extradata_hash: string,
...
} = ({
stdout,
stderr
}: {
stdout: string,
stderr: string
}): {
codec_name: string,
extradata_hash: string,
damaged: boolean
} => ({
codec_name: "",
extradata_hash: "",
...JSON.parse(stdout).streams[0],
damaged: this.isDamaged(stderr)
});
isDamaged: (stderr: string) => boolean = (stderr: string) => {
let damaged = false;
if (stderr) {
damaged = Boolean(
(stderr || "").split("\n").filter(
l =>
// XXX: depends ffprobe message.
![
"Unsupported codec with id ",
"Estimating duration from bitrate"
].some(w => l.includes(w) || !l)
).length
);
}
return damaged;
};
read: (
targetPath: string
) => Promise<{
damaged: boolean,
height: number,
ratio: number,
width: number,
...
}> = async (
targetPath: string
): Promise<{
width: number,
height: number,
ratio: number,
damaged: boolean
}> =>
exec(this.createExecCommand(targetPath))
.then(this.parseOutput)
.catch(this.parseOutput)
.catch({ width: 0, height: 0, ratio: 0, damaged: true });
/**
* XXX: Do not use this method! The same hash is returned between different files.
*/
readForAudio: (
targetPath: string
) => Promise<{ damaged: boolean, extradata_hash: string, ... }> = async (
targetPath: string
): Promise<{ damaged: boolean, extradata_hash: string }> =>
exec(this.createExecCommandForAudio(targetPath))
.then(this.parseOutputForAudio)
.catch({ damaged: true, extradata_hash: "" });
}