export data streams as separate files by default

pull/276/head
Mikael Finstad 6 years ago
parent 542d4ef946
commit 76995daee4

@ -34,8 +34,17 @@ const HelpSheet = ({ visible, onTogglePress, renderSettings }) => (
<p style={{ fontWeight: 'bold' }}>Hover mouse over buttons to see which function they have.</p>
<h1 style={{ marginTop: 40 }}>Settings</h1>
{renderSettings()}
<table style={{ marginTop: 40 }}>
<thead>
<tr style={{ textAlign: 'left' }}>
<th>Settings</th>
<th>Current setting</th>
</tr>
</thead>
<tbody>
{renderSettings()}
</tbody>
</table>
</motion.div>
)}
</AnimatePresence>

@ -361,24 +361,21 @@ function getPreferredCodecFormat(codec, type) {
}
// https://stackoverflow.com/questions/32922226/extract-every-audio-and-subtitles-from-a-video-with-ffmpeg
async function extractAllStreams({ customOutDir, filePath }) {
const { streams } = await getAllStreams(filePath);
console.log('streams', streams);
const outStreams = streams.map((s, i) => ({
i,
async function extractStreams({ filePath, customOutDir, streams }) {
const outStreams = streams.map((s) => ({
index: s.index,
codec: s.codec_name || s.codec_tag_string || s.codec_type,
type: s.codec_type,
format: getPreferredCodecFormat(s.codec_name, s.codec_type),
}))
.filter(it => it && it.format);
.filter(it => it && it.format && it.index != null);
// console.log(outStreams);
const streamArgs = flatMap(outStreams, ({
i, codec, type, format: { format, ext },
index, codec, type, format: { format, ext },
}) => [
'-map', `0:${i}`, '-c', 'copy', '-f', format, '-y', getOutPath(customOutDir, filePath, `${i}-${type}-${codec}.${ext}`),
'-map', `0:${index}`, '-c', 'copy', '-f', format, '-y', getOutPath(customOutDir, filePath, `stream-${index}-${type}-${codec}.${ext}`),
]);
const ffmpegArgs = [
@ -448,7 +445,7 @@ module.exports = {
html5ifyDummy,
mergeAnyFiles,
autoMergeSegments,
extractAllStreams,
extractStreams,
renderFrame,
getAllStreams,
defaultProcessedCodecTypes,

@ -80,6 +80,7 @@ function doesPlayerSupportFile(streams) {
const queue = new PQueue({ concurrency: 1 });
const App = memo(() => {
// Per project state
const [framePath, setFramePath] = useState();
const [html5FriendlyPath, setHtml5FriendlyPath] = useState();
const [working, setWorking] = useState(false);
@ -106,7 +107,7 @@ const App = memo(() => {
const [streamsSelectorShown, setStreamsSelectorShown] = useState(false);
const [zoom, setZoom] = useState(1);
// Global state
// Global state & preferences
const [captureFormat, setCaptureFormat] = useState('jpeg');
const [customOutDir, setCustomOutDir] = useState();
const [keyframeCut, setKeyframeCut] = useState(true);
@ -115,6 +116,7 @@ const App = memo(() => {
const [timecodeShowFrames, setTimecodeShowFrames] = useState(false);
const [mifiLink, setMifiLink] = useState();
const [invertCutSegments, setInvertCutSegments] = useState(false);
const [autoExportExtraStreams, setAutoExportExtraStreams] = useState(true);
const videoRef = useRef();
const timelineWrapperRef = useRef();
@ -429,6 +431,14 @@ const App = memo(() => {
const copyAnyAudioTrack = mainStreams.some(stream => isCopyingStreamId(filePath, stream.index) && stream.codec_type === 'audio');
// Streams that are not copy enabled by default
const extraStreams = mainStreams
.filter((stream) => !defaultProcessedCodecTypes.includes(stream.codec_type));
// Extra streams that the user has not selected for copy
const nonCopiedExtraStreams = extraStreams
.filter((stream) => !isCopyingStreamId(filePath, stream.index));
const copyStreamIds = Object.entries(copyStreamIdsByFile).map(([path, streamIdsMap]) => ({
path,
streamIds: Object.keys(streamIdsMap).filter(index => streamIdsMap[index]),
@ -618,7 +628,18 @@ const App = memo(() => {
});
}
toast.fire({ timer: 5000, icon: 'success', title: `Export completed! Output file(s) can be found at: ${getOutDir(customOutDir, filePath)}. You can change the output directory in settings` });
const exportExtraStreams = autoExportExtraStreams && nonCopiedExtraStreams.length > 0;
if (exportExtraStreams) {
try {
await ffmpeg.extractStreams({
filePath, customOutDir, streams: nonCopiedExtraStreams,
});
} catch (err) {
console.error('Extra stream export failed', err);
}
}
toast.fire({ timer: 5000, icon: 'success', title: `Export completed! Output file(s) can be found at: ${getOutDir(customOutDir, filePath)}.${exportExtraStreams ? ' Extra unprocessable stream(s) exported as separate files.' : ''}` });
} catch (err) {
console.error('stdout:', err.stdout);
console.error('stderr:', err.stderr);
@ -634,7 +655,7 @@ const App = memo(() => {
}
}, [
effectiveRotation, apparentCutSegments, invertCutSegments, inverseCutSegments,
working, duration, filePath, keyframeCut, detectedFileFormat,
working, duration, filePath, keyframeCut, detectedFileFormat, extraStreams,
autoMerge, customOutDir, fileFormat, haveInvalidSegs, copyStreamIds, numStreamsToCopy,
]);
@ -676,7 +697,6 @@ const App = memo(() => {
return ret;
}, [getHtml5ifiedPath]);
const load = useCallback(async (fp, html5FriendlyPathRequested) => {
console.log('Load', { fp, html5FriendlyPathRequested });
if (working) {
@ -792,7 +812,7 @@ const App = memo(() => {
try {
setWorking(true);
await ffmpeg.extractAllStreams({ customOutDir, filePath });
await ffmpeg.extractStreams({ customOutDir, filePath, streams: mainStreams });
toast.fire({ icon: 'success', title: `All streams can be found as separate files at: ${getOutDir(customOutDir, filePath)}` });
} catch (err) {
errorToast('Failed to extract all streams');
@ -1034,100 +1054,113 @@ const App = memo(() => {
function renderSettings() {
return (
<table>
<tbody>
<tr>
<td>Output format (default autodetected)</td>
<td style={{ width: '50%' }}>{renderOutFmt()}</td>
</tr>
<tr>
<td>Output directory</td>
<td>
<button
type="button"
onClick={setOutputDir}
>
{customOutDir ? 'Custom output directory' : 'Output files to same directory as current file'}
</button>
<div>{customOutDir}</div>
</td>
</tr>
<tr>
<td>Auto merge segments to one file after export?</td>
<td>
<button
type="button"
onClick={toggleAutoMerge}
>
{autoMerge ? 'Auto merge segments to one file' : 'Export separate files'}
</button>
</td>
</tr>
<tr>
<td>keyframe cut mode</td>
<td>
<button
type="button"
onClick={toggleKeyframeCut}
>
{keyframeCut ? 'Nearest keyframe cut - will cut at the nearest keyframe' : 'Normal cut - cut accurate position but could leave an empty portion'}
</button>
</td>
</tr>
<tr>
<td>
Discard (cut away) or keep selected segments from video when exporting
</td>
<td>
<button
type="button"
onClick={withBlur(() => setInvertCutSegments(v => !v))}
>
{invertCutSegments ? 'Discard' : 'Keep'}
</button>
</td>
</tr>
<tr>
<td>
Discard audio?
</td>
<td>
<button
type="button"
onClick={toggleStripAudio}
>
{!copyAnyAudioTrack ? 'Discard all audio tracks' : 'Keep audio tracks'}
</button>
</td>
</tr>
<tr>
<td>
Snapshot capture format
</td>
<td>
{renderCaptureFormatButton()}
</td>
</tr>
<tr>
<td>In timecode show</td>
<td>
<button
type="button"
onClick={() => setTimecodeShowFrames(v => !v)}
>
{timecodeShowFrames ? 'Frame numbers' : 'Millisecond fractions'}
</button>
</td>
</tr>
</tbody>
</table>
<Fragment>
<tr>
<td>Output format (default autodetected)</td>
<td style={{ width: '50%' }}>{renderOutFmt()}</td>
</tr>
<tr>
<td>Output directory</td>
<td>
<button
type="button"
onClick={setOutputDir}
>
{customOutDir ? 'Custom output directory' : 'Output files to same directory as current file'}
</button>
<div>{customOutDir}</div>
</td>
</tr>
<tr>
<td>Auto merge segments to one file after export?</td>
<td>
<button
type="button"
onClick={toggleAutoMerge}
>
{autoMerge ? 'Auto merge segments to one file' : 'Export separate files'}
</button>
</td>
</tr>
<tr>
<td>keyframe cut mode</td>
<td>
<button
type="button"
onClick={toggleKeyframeCut}
>
{keyframeCut ? 'Nearest keyframe cut - will cut at the nearest keyframe' : 'Normal cut - cut accurate position but could leave an empty portion'}
</button>
</td>
</tr>
<tr>
<td>
Discard (cut away) or keep selected segments from video when exporting
</td>
<td>
<button
type="button"
onClick={withBlur(() => setInvertCutSegments(v => !v))}
>
{invertCutSegments ? 'Discard' : 'Keep'}
</button>
</td>
</tr>
<tr>
<td>
Discard audio?
</td>
<td>
<button
type="button"
onClick={toggleStripAudio}
>
{copyAnyAudioTrack ? 'Keep audio tracks' : 'Discard all audio tracks'}
</button>
</td>
</tr>
<tr>
<td>
Extract unprocessable tracks to separate files?<br />
(data tracks such as GoPro GPS, telemetry etc.)
</td>
<td>
<button
type="button"
onClick={() => setAutoExportExtraStreams(v => !v)}
>
{autoExportExtraStreams ? 'Extract unprocessable tracks' : 'Discard all unprocessable tracks'}
</button>
</td>
</tr>
<tr>
<td>
Snapshot capture format
</td>
<td>
{renderCaptureFormatButton()}
</td>
</tr>
<tr>
<td>In timecode show</td>
<td>
<button
type="button"
onClick={() => setTimecodeShowFrames(v => !v)}
>
{timecodeShowFrames ? 'Frame numbers' : 'Millisecond fractions'}
</button>
</td>
</tr>
</Fragment>
);
}

Loading…
Cancel
Save