implement segments to chapters mode #993

exiftool-integration
Mikael Finstad 4 years ago
parent 7b040c8ae9
commit 3615268a46
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26

@ -13,6 +13,7 @@ const defaults = {
keyframeCut: true,
autoMerge: false,
autoDeleteMergedSegments: true,
segmentsToChaptersOnly: false,
timecodeFormat: 'timecodeWithDecimalFraction',
invertCutSegments: false,
autoExportExtraStreams: true,

@ -194,7 +194,7 @@ const App = memo(() => {
const isCustomFormatSelected = fileFormat !== detectedFileFormat;
const {
captureFormat, setCaptureFormat, customOutDir, setCustomOutDir, keyframeCut, setKeyframeCut, preserveMovData, setPreserveMovData, movFastStart, setMovFastStart, avoidNegativeTs, setAvoidNegativeTs, autoMerge, setAutoMerge, timecodeFormat, setTimecodeFormat, invertCutSegments, setInvertCutSegments, autoExportExtraStreams, setAutoExportExtraStreams, askBeforeClose, setAskBeforeClose, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, playbackVolume, setPlaybackVolume, autoSaveProjectFile, setAutoSaveProjectFile, wheelSensitivity, setWheelSensitivity, invertTimelineScroll, setInvertTimelineScroll, language, setLanguage, ffmpegExperimental, setFfmpegExperimental, hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode, autoDeleteMergedSegments, setAutoDeleteMergedSegments, exportConfirmEnabled, setExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, simpleMode, setSimpleMode, outSegTemplate, setOutSegTemplate, keyboardSeekAccFactor, setKeyboardSeekAccFactor, keyboardNormalSeekSpeed, setKeyboardNormalSeekSpeed, enableTransferTimestamps, setEnableTransferTimestamps, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, enableAutoHtml5ify, setEnableAutoHtml5ify,
captureFormat, setCaptureFormat, customOutDir, setCustomOutDir, keyframeCut, setKeyframeCut, preserveMovData, setPreserveMovData, movFastStart, setMovFastStart, avoidNegativeTs, setAvoidNegativeTs, autoMerge, setAutoMerge, timecodeFormat, setTimecodeFormat, invertCutSegments, setInvertCutSegments, autoExportExtraStreams, setAutoExportExtraStreams, askBeforeClose, setAskBeforeClose, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, playbackVolume, setPlaybackVolume, autoSaveProjectFile, setAutoSaveProjectFile, wheelSensitivity, setWheelSensitivity, invertTimelineScroll, setInvertTimelineScroll, language, setLanguage, ffmpegExperimental, setFfmpegExperimental, hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode, autoDeleteMergedSegments, setAutoDeleteMergedSegments, exportConfirmEnabled, setExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, simpleMode, setSimpleMode, outSegTemplate, setOutSegTemplate, keyboardSeekAccFactor, setKeyboardSeekAccFactor, keyboardNormalSeekSpeed, setKeyboardNormalSeekSpeed, enableTransferTimestamps, setEnableTransferTimestamps, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, enableAutoHtml5ify, setEnableAutoHtml5ify, segmentsToChaptersOnly, setSegmentsToChaptersOnly,
} = useUserPreferences();
const {
@ -1121,12 +1121,23 @@ const App = memo(() => {
try {
setWorking(i18n.t('Exporting'));
// This is a special mode where segments will be simply written out as chapters: https://github.com/mifi/lossless-cut/issues/993#issuecomment-1037927595
let chaptersToAdd;
let segmentsToExport = enabledOutSegments;
if (segmentsToChaptersOnly) {
if (invertCutSegments) throw new Error('Inverted cut segments not supported for chapters only export');
// Emulate a single segment with no cuts (full timeline)
segmentsToExport = [{ start: 0, end: getSegApparentEnd({}) }];
chaptersToAdd = sortBy(enabledOutSegments, 'start').map((segment) => ({ start: segment.start, end: segment.end, name: segment.name }));
console.log(chaptersToAdd);
}
console.log('outSegTemplateOrDefault', outSegTemplateOrDefault);
let outSegFileNames = generateOutSegFileNames({ segments: enabledOutSegments, template: outSegTemplateOrDefault });
let outSegFileNames = generateOutSegFileNames({ segments: segmentsToExport, template: outSegTemplateOrDefault });
if (!isOutSegFileNamesValid(outSegFileNames) || hasDuplicates(outSegFileNames)) {
console.error('Output segments file name invalid, using default instead', outSegFileNames);
outSegFileNames = generateOutSegFileNames({ segments: enabledOutSegments, template: defaultOutSegTemplate });
outSegFileNames = generateOutSegFileNames({ segments: segmentsToExport, template: defaultOutSegTemplate });
}
// throw (() => { const err = new Error('test'); err.code = 'ENOENT'; return err; })();
@ -1137,7 +1148,7 @@ const App = memo(() => {
rotation: isRotationSet ? effectiveRotation : undefined,
copyFileStreams,
keyframeCut,
segments: enabledOutSegments,
segments: segmentsToExport,
segmentsFileNames: outSegFileNames,
onProgress: setCutProgress,
appendFfmpegCommandLog,
@ -1149,13 +1160,14 @@ const App = memo(() => {
customTagsByFile,
customTagsByStreamId,
dispositionByStreamId,
chapters: chaptersToAdd,
});
if (outFiles.length > 1 && autoMerge) {
setCutProgress(0);
setWorking(i18n.t('Merging'));
const chapterNames = segmentsToChapters && !invertCutSegments ? enabledOutSegments.map((s) => s.name) : undefined;
const chapterNames = segmentsToChapters && !invertCutSegments ? segmentsToExport.map((s) => s.name) : undefined;
await autoMergeSegments({
customOutDir,
@ -1205,7 +1217,7 @@ const App = memo(() => {
setWorking();
setCutProgress();
}
}, [numStreamsToCopy, enabledOutSegments, outSegTemplateOrDefault, generateOutSegFileNames, customOutDir, filePath, fileFormat, duration, isRotationSet, effectiveRotation, copyFileStreams, keyframeCut, shortestFlag, ffmpegExperimental, preserveMovData, movFastStart, avoidNegativeTs, customTagsByFile, customTagsByStreamId, dispositionByStreamId, autoMerge, exportExtraStreams, fileFormatData, mainStreams, hideAllNotifications, outputDir, segmentsToChapters, invertCutSegments, isCustomFormatSelected, autoDeleteMergedSegments, preserveMetadataOnMerge, nonCopiedExtraStreams, handleCutFailed, isOutSegFileNamesValid, cutMultiple, autoMergeSegments, setWorking]);
}, [numStreamsToCopy, setWorking, enabledOutSegments, segmentsToChaptersOnly, outSegTemplateOrDefault, generateOutSegFileNames, isOutSegFileNamesValid, cutMultiple, outputDir, fileFormat, duration, isRotationSet, effectiveRotation, copyFileStreams, keyframeCut, shortestFlag, ffmpegExperimental, preserveMovData, movFastStart, avoidNegativeTs, customTagsByFile, customTagsByStreamId, dispositionByStreamId, autoMerge, fileFormatData, mainStreams, exportExtraStreams, hideAllNotifications, invertCutSegments, getSegApparentEnd, segmentsToChapters, autoMergeSegments, customOutDir, isCustomFormatSelected, autoDeleteMergedSegments, preserveMetadataOnMerge, filePath, nonCopiedExtraStreams, handleCutFailed]);
const onExportPress = useCallback(async () => {
if (!filePath || workingRef.current) return;
@ -2251,6 +2263,8 @@ const App = memo(() => {
outFormatLocked={outFormatLocked}
onOutFormatLockedClick={onOutFormatLockedClick}
simpleMode={simpleMode}
segmentsToChaptersOnly={segmentsToChaptersOnly}
setSegmentsToChaptersOnly={setSegmentsToChaptersOnly}
/>
<div style={{ flexGrow: 1, display: 'flex', overflowY: 'hidden' }}>
@ -2522,7 +2536,7 @@ const App = memo(() => {
/>
</SideSheet>
<ExportConfirm filePath={filePath} autoMerge={autoMerge} setAutoMerge={setAutoMerge} areWeCutting={areWeCutting} enabledOutSegments={enabledOutSegments} visible={exportConfirmVisible} onClosePress={closeExportConfirm} onExportConfirm={onExportConfirm} keyframeCut={keyframeCut} toggleKeyframeCut={toggleKeyframeCut} renderOutFmt={renderOutFmt} preserveMovData={preserveMovData} togglePreserveMovData={togglePreserveMovData} movFastStart={movFastStart} toggleMovFastStart={toggleMovFastStart} avoidNegativeTs={avoidNegativeTs} setAvoidNegativeTs={setAvoidNegativeTs} changeOutDir={changeOutDir} outputDir={outputDir} numStreamsTotal={numStreamsTotal} numStreamsToCopy={numStreamsToCopy} setStreamsSelectorShown={setStreamsSelectorShown} exportConfirmEnabled={exportConfirmEnabled} toggleExportConfirmEnabled={toggleExportConfirmEnabled} segmentsToChapters={segmentsToChapters} toggleSegmentsToChapters={toggleSegmentsToChapters} outFormat={fileFormat} preserveMetadataOnMerge={preserveMetadataOnMerge} togglePreserveMetadataOnMerge={togglePreserveMetadataOnMerge} setOutSegTemplate={setOutSegTemplate} outSegTemplate={outSegTemplateOrDefault} generateOutSegFileNames={generateOutSegFileNames} currentSegIndexSafe={currentSegIndexSafe} isOutSegFileNamesValid={isOutSegFileNamesValid} autoDeleteMergedSegments={autoDeleteMergedSegments} setAutoDeleteMergedSegments={setAutoDeleteMergedSegments} safeOutputFileName={safeOutputFileName} toggleSafeOutputFileName={toggleSafeOutputFileName} />
<ExportConfirm filePath={filePath} autoMerge={autoMerge} setAutoMerge={setAutoMerge} areWeCutting={areWeCutting} enabledOutSegments={enabledOutSegments} visible={exportConfirmVisible} onClosePress={closeExportConfirm} onExportConfirm={onExportConfirm} keyframeCut={keyframeCut} toggleKeyframeCut={toggleKeyframeCut} renderOutFmt={renderOutFmt} preserveMovData={preserveMovData} togglePreserveMovData={togglePreserveMovData} movFastStart={movFastStart} toggleMovFastStart={toggleMovFastStart} avoidNegativeTs={avoidNegativeTs} setAvoidNegativeTs={setAvoidNegativeTs} changeOutDir={changeOutDir} outputDir={outputDir} numStreamsTotal={numStreamsTotal} numStreamsToCopy={numStreamsToCopy} setStreamsSelectorShown={setStreamsSelectorShown} exportConfirmEnabled={exportConfirmEnabled} toggleExportConfirmEnabled={toggleExportConfirmEnabled} segmentsToChapters={segmentsToChapters} toggleSegmentsToChapters={toggleSegmentsToChapters} outFormat={fileFormat} preserveMetadataOnMerge={preserveMetadataOnMerge} togglePreserveMetadataOnMerge={togglePreserveMetadataOnMerge} setOutSegTemplate={setOutSegTemplate} outSegTemplate={outSegTemplateOrDefault} generateOutSegFileNames={generateOutSegFileNames} currentSegIndexSafe={currentSegIndexSafe} isOutSegFileNamesValid={isOutSegFileNamesValid} autoDeleteMergedSegments={autoDeleteMergedSegments} setAutoDeleteMergedSegments={setAutoDeleteMergedSegments} safeOutputFileName={safeOutputFileName} toggleSafeOutputFileName={toggleSafeOutputFileName} segmentsToChaptersOnly={segmentsToChaptersOnly} setSegmentsToChaptersOnly={setSegmentsToChaptersOnly} />
<HelpSheet
visible={helpVisible}

@ -45,7 +45,7 @@ const ExportConfirm = memo(({
exportConfirmEnabled, toggleExportConfirmEnabled, segmentsToChapters, toggleSegmentsToChapters, outFormat,
preserveMetadataOnMerge, togglePreserveMetadataOnMerge, outSegTemplate, setOutSegTemplate, generateOutSegFileNames,
filePath, currentSegIndexSafe, isOutSegFileNamesValid, autoDeleteMergedSegments, setAutoDeleteMergedSegments,
safeOutputFileName, toggleSafeOutputFileName,
safeOutputFileName, toggleSafeOutputFileName, segmentsToChaptersOnly, setSegmentsToChaptersOnly,
}) => {
const { t } = useTranslation();
@ -97,6 +97,8 @@ const ExportConfirm = memo(({
const outSegTemplateHelpIcon = <HelpIcon onClick={onOutSegTemplateHelpPress} />;
const willMerge = autoMerge && enabledOutSegments.length >= 2;
// https://stackoverflow.com/questions/33454533/cant-scroll-to-top-of-flex-item-that-is-overflowing-container
return (
<AnimatePresence>
@ -115,7 +117,7 @@ const ExportConfirm = memo(({
<h2 style={{ marginTop: 0 }}>{t('Export options')}</h2>
<ul>
{enabledOutSegments.length >= 2 && <li>{t('Merge {{segments}} cut segments to one file?', { segments: enabledOutSegments.length })} <MergeExportButton autoMerge={autoMerge} enabledOutSegments={enabledOutSegments} setAutoMerge={setAutoMerge} autoDeleteMergedSegments={autoDeleteMergedSegments} setAutoDeleteMergedSegments={setAutoDeleteMergedSegments} /></li>}
{enabledOutSegments.length >= 2 && <li>{t('Merge {{segments}} cut segments to one file?', { segments: enabledOutSegments.length })} <MergeExportButton autoMerge={autoMerge} enabledOutSegments={enabledOutSegments} setAutoMerge={setAutoMerge} autoDeleteMergedSegments={autoDeleteMergedSegments} setAutoDeleteMergedSegments={setAutoDeleteMergedSegments} segmentsToChaptersOnly={segmentsToChaptersOnly} setSegmentsToChaptersOnly={setSegmentsToChaptersOnly} /></li>}
<li>
{t('Output container format:')} {renderOutFmt({ height: 20, maxWidth: 150 })}
<HelpIcon onClick={onOutFmtHelpPress} />
@ -127,7 +129,7 @@ const ExportConfirm = memo(({
<li>
{t('Save output to path:')} <span role="button" onClick={changeOutDir} style={outDirStyle}>{outputDir}</span>
</li>
{(enabledOutSegments.length === 1 || !autoMerge) && (
{!willMerge && !segmentsToChaptersOnly && (
<li>
<OutSegTemplateEditor filePath={filePath} helpIcon={outSegTemplateHelpIcon} outSegTemplate={outSegTemplate} setOutSegTemplate={setOutSegTemplate} generateOutSegFileNames={generateOutSegFileNames} currentSegIndexSafe={currentSegIndexSafe} isOutSegFileNamesValid={isOutSegFileNamesValid} safeOutputFileName={safeOutputFileName} toggleSafeOutputFileName={toggleSafeOutputFileName} />
</li>
@ -136,7 +138,7 @@ const ExportConfirm = memo(({
<h3>{t('Advanced options')}</h3>
{autoMerge && enabledOutSegments.length >= 2 && (
{willMerge && !segmentsToChaptersOnly && (
<ul>
<li>
{t('Create chapters from merged segments? (slow)')} <Button height={20} onClick={toggleSegmentsToChapters}>{segmentsToChapters ? t('Yes') : t('No')}</Button>

@ -14,6 +14,7 @@ const TopMenu = memo(({
filePath, copyAnyAudioTrack, toggleStripAudio, customOutDir, changeOutDir,
renderOutFmt, toggleHelp, numStreamsToCopy, numStreamsTotal, setStreamsSelectorShown, toggleSettings,
enabledOutSegments, autoMerge, setAutoMerge, autoDeleteMergedSegments, setAutoDeleteMergedSegments, isCustomFormatSelected, onOutFormatLockedClick, simpleMode, outFormatLocked, clearOutDir,
segmentsToChaptersOnly, setSegmentsToChaptersOnly,
}) => {
const { t } = useTranslation();
@ -74,7 +75,7 @@ const TopMenu = memo(({
{!simpleMode && (isCustomFormatSelected || outFormatLocked) && renderFormatLock()}
<MergeExportButton autoMerge={autoMerge} enabledOutSegments={enabledOutSegments} setAutoMerge={setAutoMerge} autoDeleteMergedSegments={autoDeleteMergedSegments} setAutoDeleteMergedSegments={setAutoDeleteMergedSegments} />
<MergeExportButton autoMerge={autoMerge} enabledOutSegments={enabledOutSegments} setAutoMerge={setAutoMerge} autoDeleteMergedSegments={autoDeleteMergedSegments} setAutoDeleteMergedSegments={setAutoDeleteMergedSegments} segmentsToChaptersOnly={segmentsToChaptersOnly} setSegmentsToChaptersOnly={setSegmentsToChaptersOnly} />
</>
)}

@ -6,7 +6,7 @@ import { MdCallSplit, MdCallMerge } from 'react-icons/md';
import { withBlur } from '../util';
const MergeExportButton = memo(({ autoMerge, enabledOutSegments, setAutoMerge, autoDeleteMergedSegments, setAutoDeleteMergedSegments }) => {
const MergeExportButton = memo(({ autoMerge, enabledOutSegments, setAutoMerge, autoDeleteMergedSegments, setAutoDeleteMergedSegments, segmentsToChaptersOnly, setSegmentsToChaptersOnly }) => {
const { t } = useTranslation();
let AutoMergeIcon;
@ -14,7 +14,12 @@ const MergeExportButton = memo(({ autoMerge, enabledOutSegments, setAutoMerge, a
let effectiveMode;
let title;
let description;
if (autoMerge && autoDeleteMergedSegments) {
if (segmentsToChaptersOnly) {
effectiveMode = 'sesgments_to_chapters';
title = t('Chapters only');
description = t('Don\'t cut the file, but instead create chapters from segments');
} else if (autoMerge && autoDeleteMergedSegments) {
effectiveMode = 'merge';
AutoMergeIcon = MdCallMerge;
title = t('Merge cuts');
@ -41,8 +46,13 @@ const MergeExportButton = memo(({ autoMerge, enabledOutSegments, setAutoMerge, a
break;
}
case 'separate': {
setSegmentsToChaptersOnly(true);
break;
}
case 'sesgments_to_chapters': {
setAutoMerge(true);
setAutoDeleteMergedSegments(true);
setSegmentsToChaptersOnly(false);
break;
}
default:

@ -50,6 +50,8 @@ function getMatroskaFlags() {
];
}
const getChaptersInputArgs = (ffmetadataPath) => (ffmetadataPath ? ['-f', 'ffmetadata', '-i', ffmetadataPath] : []);
function useFfmpegOperations({ filePath, enableTransferTimestamps }) {
const optionalTransferTimestamps = useCallback(async (...args) => {
if (enableTransferTimestamps) await transferTimestamps(...args);
@ -61,8 +63,11 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps }) {
outputDir, segments, segmentsFileNames, videoDuration, rotation,
onProgress: onTotalProgress, keyframeCut, copyFileStreams, outFormat,
appendFfmpegCommandLog, shortestFlag, ffmpegExperimental, preserveMovData, movFastStart, avoidNegativeTs,
customTagsByFile, customTagsByStreamId, dispositionByStreamId,
customTagsByFile, customTagsByStreamId, dispositionByStreamId, chapters,
}) => {
// We can optionally write chapters
const chaptersPath = chapters ? await writeChaptersFfmetadata(outputDir, chapters) : undefined;
async function cutSingle({ cutFrom, cutTo, onProgress, outPath }) {
const cuttingStart = isCuttingStart(cutFrom);
const cuttingEnd = isCuttingEnd(cutTo, videoDuration);
@ -81,18 +86,25 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps }) {
// remove -avoid_negative_ts make_zero when not cutting start (no -ss), or else some videos get blank first frame in QuickLook
const avoidNegativeTsArgs = cuttingStart && avoidNegativeTs ? ['-avoid_negative_ts', avoidNegativeTs] : [];
const inputArgs = flatMap(copyFileStreamsFiltered, ({ path }) => ['-i', path]);
const inputCutArgs = ssBeforeInput ? [
const inputFilesArgs = flatMap(copyFileStreamsFiltered, ({ path }) => ['-i', path]);
const inputFilesArgsWithCuts = ssBeforeInput ? [
...cutFromArgs,
...inputArgs,
...inputFilesArgs,
...cutToArgs,
...avoidNegativeTsArgs,
] : [
...inputArgs,
...inputFilesArgs,
...cutFromArgs,
...cutToArgs,
];
const inputArgs = [
...inputFilesArgsWithCuts,
...getChaptersInputArgs(chaptersPath),
];
const chaptersInputIndex = copyFileStreamsFiltered.length;
const rotationArgs = rotation !== undefined ? ['-metadata:s:v:0', `rotate=${360 - rotation}`] : [];
// This function tries to calculate the output stream index needed for -metadata:s:x and -disposition:x arguments
@ -152,15 +164,18 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps }) {
// No progress if we set loglevel warning :(
// '-loglevel', 'warning',
...inputCutArgs,
...inputArgs,
'-c', 'copy',
...(shortestFlag ? ['-shortest'] : []),
...flatMapDeep(copyFileStreamsFiltered, ({ streamIds }, fileIndex) => streamIds.map(streamId => ['-map', `${fileIndex}:${streamId}`])),
'-map_metadata', '0',
...(chaptersPath ? ['-map_chapters', chaptersInputIndex] : []),
...getMovFlags({ preserveMovData, movFastStart }),
...getMatroskaFlags(),
@ -225,9 +240,40 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps }) {
const durations = await pMap(paths, getDuration, { concurrency: 1 });
const totalDuration = sum(durations);
const ffmetadataPath = await writeChaptersFfmetadata(outDir, chapters);
const chaptersPath = await writeChaptersFfmetadata(outDir, chapters);
try {
let inputArgs = [];
let inputIndex = 0;
// Keep track of input index to be used later
// eslint-disable-next-line no-inner-declarations
function addInput(args) {
inputArgs = [...inputArgs, ...args];
const retIndex = inputIndex;
inputIndex += 1;
return retIndex;
}
// concat list - always first
addInput([
// https://blog.yo1.dog/fix-for-ffmpeg-protocol-not-on-whitelist-error-for-urls/
'-f', 'concat', '-safe', '0', '-protocol_whitelist', 'file,pipe',
'-i', '-',
]);
let metadataSourceIndex;
if (preserveMetadataOnMerge) {
// If preserve metadata, add the first file (we will get metadata from this input)
metadataSourceIndex = addInput(['-i', paths[0]]);
}
let chaptersInputIndex;
if (chaptersPath) {
// if chapters, add chapters source file
chaptersInputIndex = addInput(getChaptersInputArgs(chaptersPath));
}
let map;
if (allStreams) map = ['-map', '0'];
// If preserveMetadataOnMerge option is enabled, we need to explicitly map even if allStreams=false.
@ -243,15 +289,7 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps }) {
// No progress if we set loglevel warning :(
// '-loglevel', 'warning',
// https://blog.yo1.dog/fix-for-ffmpeg-protocol-not-on-whitelist-error-for-urls/
'-f', 'concat', '-safe', '0', '-protocol_whitelist', 'file,pipe',
'-i', '-',
// Add the first file (we will get metadata from this input)
...(preserveMetadataOnMerge ? ['-i', paths[0]] : []),
// Chapters?
...(ffmetadataPath ? ['-f', 'ffmetadata', '-i', ffmetadataPath] : []),
...inputArgs,
'-c', 'copy',
@ -260,7 +298,9 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps }) {
// -map_metadata 0 with concat demuxer doesn't transfer metadata from the concat'ed file input (index 0) when merging.
// So we use the first file file (index 1) for metadata
// Can only do this if allStreams (-map 0) is set
...(preserveMetadataOnMerge ? ['-map_metadata', '1'] : []),
...(metadataSourceIndex != null ? ['-map_metadata', metadataSourceIndex] : []),
...(chaptersInputIndex != null ? ['-map_chapters', chaptersInputIndex] : []),
...getMovFlags({ preserveMovData, movFastStart }),
...getMatroskaFlags(),
@ -294,7 +334,7 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps }) {
const { stdout } = await process;
console.log(stdout);
} finally {
if (ffmetadataPath) await fs.unlink(ffmetadataPath).catch((err) => console.error('Failed to delete', ffmetadataPath, err));
if (chaptersPath) await fs.unlink(chaptersPath).catch((err) => console.error('Failed to delete', chaptersPath, err));
}
await optionalTransferTimestamps(paths[0], outPath);

@ -90,7 +90,8 @@ export default () => {
useEffect(() => safeSetConfig('safeOutputFileName', safeOutputFileName), [safeOutputFileName]);
const [enableAutoHtml5ify, setEnableAutoHtml5ify] = useState(configStore.get('enableAutoHtml5ify'));
useEffect(() => safeSetConfig('enableAutoHtml5ify', enableAutoHtml5ify), [enableAutoHtml5ify]);
const [segmentsToChaptersOnly, setSegmentsToChaptersOnly] = useState(configStore.get('segmentsToChaptersOnly'));
useEffect(() => safeSetConfig('segmentsToChaptersOnly', segmentsToChaptersOnly), [segmentsToChaptersOnly]);
// NOTE! This useEffect must be placed after all usages of firstUpdateRef.current (safeSetConfig)
useEffect(() => {
@ -167,5 +168,7 @@ export default () => {
setSafeOutputFileName,
enableAutoHtml5ify,
setEnableAutoHtml5ify,
segmentsToChaptersOnly,
setSegmentsToChaptersOnly,
};
};

Loading…
Cancel
Save