1
0
mirror of https://github.com/mifi/lossless-cut.git synced 2024-11-25 03:33:14 +01:00

implement extract all streams #106

This commit is contained in:
Mikael Finstad 2019-01-28 03:23:05 +01:00
parent cfaa11028e
commit e7f61dca61
3 changed files with 113 additions and 24 deletions

View File

@ -4,7 +4,7 @@ const which = bluebird.promisify(require('which'));
const path = require('path');
const fileType = require('file-type');
const readChunk = require('read-chunk');
const _ = require('lodash');
const flatMap = require('lodash/flatMap');
const readline = require('readline');
const moment = require('moment');
const stringToStream = require('string-to-stream');
@ -29,6 +29,11 @@ function getFfmpegPath() {
});
}
async function getFFprobePath() {
const ffmpegPath = await getFfmpegPath();
return path.join(path.dirname(ffmpegPath), getWithExt('ffprobe'));
}
function handleProgress(process, cutDuration, onProgress) {
const rl = readline.createInterface({ input: process.stderr });
rl.on('line', (line) => {
@ -176,34 +181,95 @@ function mapFormat(requestedFormat) {
}
function determineOutputFormat(ffprobeFormats, ft) {
if (_.includes(ffprobeFormats, ft.ext)) return ft.ext;
if (ffprobeFormats.includes(ft.ext)) return ft.ext;
return ffprobeFormats[0] || undefined;
}
function getFormat(filePath) {
return bluebird.try(() => {
console.log('getFormat', filePath);
async function getFormat(filePath) {
console.log('getFormat', filePath);
return getFfmpegPath()
.then(ffmpegPath => path.join(path.dirname(ffmpegPath), getWithExt('ffprobe')))
.then(ffprobePath => execa(ffprobePath, [
'-of', 'json', '-show_format', '-i', filePath,
]))
.then((result) => {
const formatsStr = JSON.parse(result.stdout).format.format_name;
console.log('formats', formatsStr);
const formats = (formatsStr || '').split(',');
const ffprobePath = await getFFprobePath();
const result = await execa(ffprobePath, [
'-of', 'json', '-show_format', '-i', filePath,
]);
const formatsStr = JSON.parse(result.stdout).format.format_name;
console.log('formats', formatsStr);
const formats = (formatsStr || '').split(',');
// ffprobe sometimes returns a list of formats, try to be a bit smarter about it.
return readChunk(filePath, 0, 4100)
.then((bytes) => {
const ft = fileType(bytes) || {};
console.log(`fileType detected format ${JSON.stringify(ft)}`);
const assumedFormat = determineOutputFormat(formats, ft);
return mapFormat(assumedFormat);
});
});
});
// ffprobe sometimes returns a list of formats, try to be a bit smarter about it.
const bytes = await readChunk(filePath, 0, 4100);
const ft = fileType(bytes) || {};
console.log(`fileType detected format ${JSON.stringify(ft)}`);
const assumedFormat = determineOutputFormat(formats, ft);
return mapFormat(assumedFormat);
}
async function getAllStreams(filePath) {
const ffprobePath = await getFFprobePath();
const result = await execa(ffprobePath, [
'-of', 'json', '-show_entries', 'stream', '-i', filePath,
]);
return JSON.parse(result.stdout);
}
// https://stackoverflow.com/questions/32922226/extract-every-audio-and-subtitles-from-a-video-with-ffmpeg
async function extractAllStreams(filePath) {
const { streams } = await getAllStreams(filePath);
console.log('streams', streams);
function mapCodecToOutputFormat(codec, type) {
const map = {
// See mapFormat
m4a: { ext: 'm4a', format: 'ipod' },
aac: { ext: 'm4a', format: 'ipod' },
mp3: { ext: 'mp3', format: 'mp3' },
opus: { ext: 'opus', format: 'opus' },
vorbis: { ext: 'ogg', format: 'ogg' },
h264: { ext: 'mp4', format: 'mp4' },
eac3: { ext: 'eac3', format: 'eac3' },
subrip: { ext: 'srt', format: 'srt' },
// TODO add more
// TODO allow user to change?
};
if (map[codec]) return map[codec];
if (type === 'video') return { ext: 'mkv', format: 'mkv' };
if (type === 'audio') return { ext: 'mkv', format: 'mkv' };
return undefined;
}
const outStreams = streams.map((s, i) => ({
i,
codec: s.codec_name,
type: s.codec_type,
format: mapCodecToOutputFormat(s.codec_name, s.codec_type),
}))
.filter(it => it);
// console.log(outStreams);
const streamArgs = flatMap(outStreams, ({
i, codec, type, format: { format, ext },
}) => [
'-map', `0:${i}`, '-c', 'copy', '-f', format, '-y', `${filePath}-${i}-${type}-${codec}.${ext}`,
]);
const ffmpegArgs = [
'-i', filePath,
...streamArgs,
];
console.log(ffmpegArgs);
// TODO progress
const ffmpegPath = await getFfmpegPath();
const process = execa(ffmpegPath, ffmpegArgs);
const result = await process;
console.log(result.stdout);
}
module.exports = {
@ -211,4 +277,5 @@ module.exports = {
getFormat,
html5ify,
mergeFiles,
extractAllStreams,
};

View File

@ -34,6 +34,12 @@ module.exports = (app, mainWindow, newVersion) => {
mainWindow.webContents.send('html5ify', true);
},
},
{
label: 'Extract all streams',
click() {
mainWindow.webContents.send('extract-all-streams', false);
},
},
{
label: 'Set custom start time offset',
click() {

View File

@ -182,6 +182,22 @@ class App extends React.Component {
this.setState({ startTimeOffset });
});
electron.ipcRenderer.on('extract-all-streams', async () => {
const { filePath } = this.state;
if (!filePath) return;
try {
this.setState({ working: true });
// TODO customOutDir ?
await ffmpeg.extractAllStreams(filePath);
this.setState({ working: false });
} catch (err) {
errorToast('Failed to extract all streams');
console.error('Failed to extract all streams', err);
this.setState({ working: false });
}
});
document.ondragover = ev => ev.preventDefault();
document.ondragend = document.ondragover;