improve cut/merge/chapters ui logic

pull/982/head
Mikael Finstad 4 years ago
parent fe88b3d6f6
commit 81653eeb6b
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26

@ -356,9 +356,6 @@ const App = memo(() => {
const currentSegIndexSafe = Math.min(currentSegIndex, cutSegments.length - 1);
const currentCutSeg = useMemo(() => cutSegments[currentSegIndexSafe], [currentSegIndexSafe, cutSegments]);
const currentApparentCutSeg = useMemo(() => apparentCutSegments[currentSegIndexSafe], [apparentCutSegments, currentSegIndexSafe]);
const areWeCutting = apparentCutSegments.length > 1
|| isCuttingStart(currentApparentCutSeg.start)
|| isCuttingEnd(currentApparentCutSeg.end, duration);
const jumpSegStart = useCallback((index) => seekAbs(apparentCutSegments[index].start), [apparentCutSegments, seekAbs]);
const jumpSegEnd = useCallback((index) => seekAbs(apparentCutSegments[index].end), [apparentCutSegments, seekAbs]);
@ -1023,13 +1020,17 @@ const App = memo(() => {
}
}, [previewFilePath, filePath, edlFilePath, cleanupChoices, isFileOpened, resetState, removeBatchFile, setWorking]);
const outSegments = useMemo(() => (invertCutSegments ? inverseCutSegments : apparentCutSegments),
const inverseOrNormalSegments = useMemo(() => (invertCutSegments ? inverseCutSegments : apparentCutSegments),
[invertCutSegments, inverseCutSegments, apparentCutSegments]);
// For invertCutSegments we do not support filtering
const enabledOutSegmentsRaw = useMemo(() => (invertCutSegments ? outSegments : outSegments.filter((s) => !disabledSegmentIds[s.segId])), [outSegments, invertCutSegments, disabledSegmentIds]);
const enabledSegmentsRaw = useMemo(() => {
// For invertCutSegments we do not support filtering
if (invertCutSegments) return inverseCutSegments;
return apparentCutSegments.filter((s) => !disabledSegmentIds[s.segId]);
}, [invertCutSegments, inverseCutSegments, apparentCutSegments, disabledSegmentIds]);
// If user has selected none to export, it makes no sense, so export all instead
const enabledOutSegments = enabledOutSegmentsRaw.length > 0 ? enabledOutSegmentsRaw : outSegments;
const enabledSegments = enabledSegmentsRaw.length > 0 ? enabledSegmentsRaw : inverseOrNormalSegments;
const onExportSingleSegmentClick = useCallback((activeSeg) => setDisabledSegmentIds(Object.fromEntries(cutSegments.filter((s) => s.segId !== activeSeg.segId).map((s) => [s.segId, true]))), [cutSegments]);
const onExportSegmentEnabledToggle = useCallback((toggleSeg) => setDisabledSegmentIds((existing) => ({ ...existing, [toggleSeg.segId]: !existing[toggleSeg.segId] })), []);
@ -1038,7 +1039,16 @@ const App = memo(() => {
const filenamifyOrNot = useCallback((name) => (safeOutputFileName ? filenamify(name) : name).substr(0, maxLabelLength), [safeOutputFileName, maxLabelLength]);
const generateOutSegFileNames = useCallback(({ segments = enabledOutSegments, template }) => (
const segmentsToExport = useMemo(() => {
// This is a special mode where all segments will be simply written out as chapters to one file: https://github.com/mifi/lossless-cut/issues/993#issuecomment-1037927595
if (!segmentsToChaptersOnly) return enabledSegments;
// Chapters export mode: Emulate a single segment with no cuts (full timeline)
return [{ start: 0, end: getSegApparentEnd({}) }];
}, [enabledSegments, getSegApparentEnd, segmentsToChaptersOnly]);
const areWeCutting = useMemo(() => segmentsToExport.some(({ start, end }) => isCuttingStart(start) || isCuttingEnd(end, duration)), [duration, segmentsToExport]);
const generateOutSegFileNames = useCallback(({ segments = segmentsToExport, template }) => (
segments.map((segment, i) => {
const { start, end, name = '' } = segment;
const cutFromStr = formatDuration({ seconds: start, fileNameFriendly: true });
@ -1061,7 +1071,7 @@ const App = memo(() => {
const generated = generateSegFileName({ template, segSuffix, inputFileNameWithoutExt: fileNameWithoutExt, ext, segNum, segLabel: nameSanitized, cutFrom: cutFromStr, cutTo: cutToStr, tags: tagsSanitized });
return safeOutputFileName ? generated.substr(0, 200) : generated; // If sanitation is enabled, make sure filename is not too long
})
), [enabledOutSegments, filenamifyOrNot, isCustomFormatSelected, fileFormat, filePath, safeOutputFileName]);
), [segmentsToExport, filenamifyOrNot, isCustomFormatSelected, fileFormat, filePath, safeOutputFileName]);
// TODO improve user feedback
const isOutSegFileNamesValid = useCallback((fileNames) => fileNames.every((fileName) => {
@ -1107,6 +1117,8 @@ const App = memo(() => {
const closeExportConfirm = useCallback(() => setExportConfirmVisible(false), []);
const willMerge = segmentsToExport.length > 1 && autoMerge;
const onExportConfirm = useCallback(async () => {
if (numStreamsToCopy === 0) {
errorToast(i18n.t('No tracks selected for export'));
@ -1120,15 +1132,8 @@ 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 }));
}
// Special segments-to-chapters mode:
const chaptersToAdd = segmentsToChaptersOnly && sortBy(enabledSegments, 'start').map((segment) => ({ start: segment.start, end: segment.end, name: segment.name }));
console.log('outSegTemplateOrDefault', outSegTemplateOrDefault);
@ -1161,7 +1166,7 @@ const App = memo(() => {
chapters: chaptersToAdd,
});
if (outFiles.length > 1 && autoMerge) {
if (willMerge) {
setCutProgress(0);
setWorking(i18n.t('Merging'));
@ -1215,7 +1220,7 @@ const App = memo(() => {
setWorking();
setCutProgress();
}
}, [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]);
}, [numStreamsToCopy, setWorking, segmentsToChaptersOnly, enabledSegments, outSegTemplateOrDefault, generateOutSegFileNames, segmentsToExport, isOutSegFileNamesValid, cutMultiple, outputDir, fileFormat, duration, isRotationSet, effectiveRotation, copyFileStreams, keyframeCut, shortestFlag, ffmpegExperimental, preserveMovData, movFastStart, avoidNegativeTs, customTagsByFile, customTagsByStreamId, dispositionByStreamId, willMerge, fileFormatData, mainStreams, exportExtraStreams, hideAllNotifications, segmentsToChapters, invertCutSegments, autoMergeSegments, customOutDir, isCustomFormatSelected, autoDeleteMergedSegments, preserveMetadataOnMerge, filePath, nonCopiedExtraStreams, handleCutFailed]);
const onExportPress = useCallback(async () => {
if (!filePath || workingRef.current) return;
@ -1225,14 +1230,14 @@ const App = memo(() => {
return;
}
if (enabledOutSegments.length < 1) {
if (segmentsToExport.length < 1) {
errorToast(i18n.t('No segments to export'));
return;
}
if (exportConfirmEnabled) setExportConfirmVisible(true);
else await onExportConfirm();
}, [filePath, haveInvalidSegs, enabledOutSegments, exportConfirmEnabled, onExportConfirm]);
}, [filePath, haveInvalidSegs, segmentsToExport, exportConfirmEnabled, onExportConfirm]);
const capture = useCallback(async () => {
if (!filePath) return;
@ -2258,7 +2263,7 @@ const App = memo(() => {
numStreamsToCopy={numStreamsToCopy}
numStreamsTotal={numStreamsTotal}
setStreamsSelectorShown={setStreamsSelectorShown}
enabledOutSegments={enabledOutSegments}
enabledSegments={enabledSegments}
autoMerge={autoMerge}
setAutoMerge={setAutoMerge}
autoDeleteMergedSegments={autoDeleteMergedSegments}
@ -2395,8 +2400,8 @@ const App = memo(() => {
<SegmentList
simpleMode={simpleMode}
currentSegIndex={currentSegIndexSafe}
outSegments={outSegments}
cutSegments={apparentCutSegments}
apparentCutSegments={apparentCutSegments}
inverseCutSegments={inverseCutSegments}
getFrameCount={getFrameCount}
formatTimecode={formatTimecode}
invertCutSegments={invertCutSegments}
@ -2410,8 +2415,8 @@ const App = memo(() => {
removeCutSegment={removeCutSegment}
toggleSideBar={toggleRightBar}
splitCurrentSegment={splitCurrentSegment}
enabledOutSegmentsRaw={enabledOutSegmentsRaw}
enabledOutSegments={enabledOutSegments}
enabledSegmentsRaw={enabledSegmentsRaw}
enabledSegments={enabledSegments}
onExportSingleSegmentClick={onExportSingleSegmentClick}
onExportSegmentEnabledToggle={onExportSegmentEnabledToggle}
onExportSegmentDisableAll={onExportSegmentDisableAll}
@ -2473,7 +2478,7 @@ const App = memo(() => {
renderCaptureFormatButton={renderCaptureFormatButton}
capture={capture}
onExportPress={onExportPress}
enabledOutSegments={enabledOutSegments}
enabledSegments={enabledSegments}
exportConfirmEnabled={exportConfirmEnabled}
toggleExportConfirmEnabled={toggleExportConfirmEnabled}
seekAbs={seekAbs}
@ -2539,7 +2544,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} segmentsToChaptersOnly={segmentsToChaptersOnly} setSegmentsToChaptersOnly={setSegmentsToChaptersOnly} />
<ExportConfirm filePath={filePath} autoMerge={autoMerge} setAutoMerge={setAutoMerge} areWeCutting={areWeCutting} enabledSegments={enabledSegments} willMerge={willMerge} 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}

@ -29,7 +29,7 @@ const leftRightWidth = 100;
const BottomBar = memo(({
zoom, setZoom, invertCutSegments, setInvertCutSegments, toggleComfortZoom, simpleMode, toggleSimpleMode,
isRotationSet, rotation, areWeCutting, increaseRotation, cleanupFiles, renderCaptureFormatButton,
capture, onExportPress, enabledOutSegments, hasVideo, autoMerge, exportConfirmEnabled, toggleExportConfirmEnabled,
capture, onExportPress, enabledSegments, hasVideo, autoMerge, exportConfirmEnabled, toggleExportConfirmEnabled,
seekAbs, currentSegIndexSafe, cutSegments, currentCutSeg, setCutStart, setCutEnd,
setCurrentSegIndex, cutStartTimeManual, setCutStartTimeManual, cutEndTimeManual, setCutEndTimeManual,
duration, jumpCutEnd, jumpCutStart, startTimeOffset, setCutTime, currentApparentCutSeg,
@ -345,7 +345,7 @@ const BottomBar = memo(({
{!simpleMode && <ToggleExportConfirm style={{ marginRight: 5 }} exportConfirmEnabled={exportConfirmEnabled} toggleExportConfirmEnabled={toggleExportConfirmEnabled} />}
<ExportButton size={1.3} enabledOutSegments={enabledOutSegments} areWeCutting={areWeCutting} autoMerge={autoMerge} onClick={onExportPress} />
<ExportButton size={1.3} enabledSegments={enabledSegments} areWeCutting={areWeCutting} autoMerge={autoMerge} onClick={onExportPress} />
</div>
</>
);

@ -39,7 +39,7 @@ const warningStyle = { color: '#faa', fontSize: '80%' };
const HelpIcon = ({ onClick }) => <IoIosHelpCircle size={20} role="button" onClick={withBlur(onClick)} style={{ cursor: 'pointer', verticalAlign: 'middle', marginLeft: 5 }} />;
const ExportConfirm = memo(({
autoMerge, areWeCutting, enabledOutSegments, visible, onClosePress, onExportConfirm, keyframeCut, toggleKeyframeCut,
autoMerge, areWeCutting, enabledSegments, willMerge, visible, onClosePress, onExportConfirm, keyframeCut, toggleKeyframeCut,
setAutoMerge, renderOutFmt, preserveMovData, togglePreserveMovData, movFastStart, toggleMovFastStart, avoidNegativeTs, setAvoidNegativeTs,
changeOutDir, outputDir, numStreamsTotal, numStreamsToCopy, setStreamsSelectorShown,
exportConfirmEnabled, toggleExportConfirmEnabled, segmentsToChapters, toggleSegmentsToChapters, outFormat,
@ -97,9 +97,7 @@ const ExportConfirm = memo(({
const outSegTemplateHelpIcon = <HelpIcon onClick={onOutSegTemplateHelpPress} />;
const willMerge = autoMerge && enabledOutSegments.length >= 2;
const canEditTemplate = !autoDeleteMergedSegments && !segmentsToChaptersOnly && enabledOutSegments.length >= 2;
const canEditTemplate = !willMerge || !autoDeleteMergedSegments;
// https://stackoverflow.com/questions/33454533/cant-scroll-to-top-of-flex-item-that-is-overflowing-container
return (
@ -119,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} segmentsToChaptersOnly={segmentsToChaptersOnly} setSegmentsToChaptersOnly={setSegmentsToChaptersOnly} /></li>}
{enabledSegments.length >= 2 && <li>{t('Merge {{segments}} cut segments to one file?', { segments: enabledSegments.length })} <MergeExportButton autoMerge={autoMerge} enabledSegments={enabledSegments} setAutoMerge={setAutoMerge} autoDeleteMergedSegments={autoDeleteMergedSegments} setAutoDeleteMergedSegments={setAutoDeleteMergedSegments} segmentsToChaptersOnly={segmentsToChaptersOnly} setSegmentsToChaptersOnly={setSegmentsToChaptersOnly} /></li>}
<li>
{t('Output container format:')} {renderOutFmt({ height: 20, maxWidth: 150 })}
<HelpIcon onClick={onOutFmtHelpPress} />
@ -140,7 +138,7 @@ const ExportConfirm = memo(({
<h3>{t('Advanced options')}</h3>
{willMerge && !segmentsToChaptersOnly && (
{willMerge && (
<ul>
<li>
{t('Create chapters from merged segments? (slow)')} <Button height={20} onClick={toggleSegmentsToChapters}>{segmentsToChapters ? t('Yes') : t('No')}</Button>
@ -156,7 +154,7 @@ const ExportConfirm = memo(({
<p>{t('Depending on your specific file/player, you may have to try different options for best results.')}</p>
<ul>
{!segmentsToChaptersOnly && (
{areWeCutting && (
<li>
{t('Cut mode:')} <KeyframeCutButton keyframeCut={keyframeCut} onClick={withBlur(() => toggleKeyframeCut(false))} />
<HelpIcon onClick={onKeyframeCutHelpPress} /> {!keyframeCut && <span style={warningStyle}>{t('Note: Keyframe cut is recommended for most common files')}</span>}
@ -209,7 +207,7 @@ const ExportConfirm = memo(({
exit={{ scale: 0.7, opacity: 0 }}
transition={{ duration: 0.4, easings: ['easeOut'] }}
>
<ExportButton enabledOutSegments={enabledOutSegments} areWeCutting={areWeCutting} autoMerge={autoMerge} onClick={() => onExportConfirm()} size={1.7} />
<ExportButton enabledSegments={enabledSegments} areWeCutting={areWeCutting} autoMerge={autoMerge} onClick={() => onExportConfirm()} size={1.7} />
</motion.div>
</div>
</>

@ -123,39 +123,41 @@ const Segment = memo(({ seg, index, currentSegIndex, formatTimecode, getFrameCou
});
const SegmentList = memo(({
formatTimecode, cutSegments, outSegments, getFrameCount, onSegClick,
formatTimecode, apparentCutSegments, inverseCutSegments, getFrameCount, onSegClick,
currentSegIndex, invertCutSegments,
updateSegOrder, updateSegOrders, addCutSegment, removeCutSegment,
onLabelSegmentPress, currentCutSeg, segmentAtCursor, toggleSideBar, splitCurrentSegment,
enabledOutSegments, enabledOutSegmentsRaw, onExportSingleSegmentClick, onExportSegmentEnabledToggle, onExportSegmentDisableAll, onExportSegmentEnableAll,
enabledSegments, enabledSegmentsRaw, onExportSingleSegmentClick, onExportSegmentEnabledToggle, onExportSegmentDisableAll, onExportSegmentEnableAll,
jumpSegStart, jumpSegEnd, simpleMode, onViewSegmentTagsPress,
}) => {
const { t } = useTranslation();
const sortableList = outSegments.map((seg) => ({ id: seg.segId, seg }));
const segments = invertCutSegments ? inverseCutSegments : apparentCutSegments;
const sortableList = segments.map((seg) => ({ id: seg.segId, seg }));
const setSortableList = useCallback((newList) => {
if (isEqual(outSegments.map((s) => s.segId), newList.map((l) => l.id))) return; // No change
if (isEqual(segments.map((s) => s.segId), newList.map((l) => l.id))) return; // No change
updateSegOrders(newList.map((list) => list.id));
}, [outSegments, updateSegOrders]);
}, [segments, updateSegOrders]);
let headerText = t('Segments to export:');
if (outSegments.length === 0) {
if (segments.length === 0) {
if (invertCutSegments) headerText = t('Make sure you have no overlapping segments.');
else headerText = t('No segments to export.');
}
async function onReorderSegsPress(index) {
if (cutSegments.length < 2) return;
if (apparentCutSegments.length < 2) return;
const { value } = await Swal.fire({
title: `${t('Change order of segment')} ${index + 1}`,
text: `Please enter a number from 1 to ${cutSegments.length} to be the new order for the current segment`,
text: `Please enter a number from 1 to ${apparentCutSegments.length} to be the new order for the current segment`,
input: 'text',
inputValue: index + 1,
showCancelButton: true,
inputValidator: (v) => {
const parsed = parseInt(v, 10);
return Number.isNaN(parsed) || parsed > cutSegments.length || parsed < 1 ? t('Invalid number entered') : undefined;
return Number.isNaN(parsed) || parsed > apparentCutSegments.length || parsed < 1 ? t('Invalid number entered') : undefined;
},
});
@ -170,13 +172,13 @@ const SegmentList = memo(({
const segAtCursorColor = getSegColor(segmentAtCursor).desaturate(0.4).alpha(0.5).string();
function renderExportEnabledCheckBox() {
const segmentExportEnabled = currentCutSeg && enabledOutSegmentsRaw.some((s) => s.segId === currentCutSeg.segId);
const segmentExportEnabled = currentCutSeg && enabledSegmentsRaw.some((s) => s.segId === currentCutSeg.segId);
const Icon = segmentExportEnabled ? FaCheck : FaTimes;
return <Icon size={24} title={segmentExportEnabled ? t('Include this segment in export') : t('Exclude this segment from export')} style={{ ...buttonBaseStyle, backgroundColor: currentSegColor }} role="button" onClick={() => onExportSegmentEnabledToggle(currentCutSeg)} />;
}
const segmentsTotal = enabledOutSegments.reduce((acc, { start, end }) => (end - start) + acc, 0);
const segmentsTotal = enabledSegments.reduce((acc, { start, end }) => (end - start) + acc, 0);
return (
<>
@ -191,7 +193,7 @@ const SegmentList = memo(({
<FaMinus
size={24}
style={{ ...buttonBaseStyle, background: cutSegments.length >= 2 ? currentSegColor : neutralButtonColor }}
style={{ ...buttonBaseStyle, background: apparentCutSegments.length >= 2 ? currentSegColor : neutralButtonColor }}
role="button"
title={`${t('Remove segment')} ${currentSegIndex + 1}`}
onClick={() => removeCutSegment(currentSegIndex)}
@ -253,7 +255,7 @@ const SegmentList = memo(({
<ReactSortable list={sortableList} setList={setSortableList} sort={!invertCutSegments}>
{sortableList.map(({ id, seg }, index) => {
const enabled = !invertCutSegments && enabledOutSegmentsRaw.includes(seg);
const enabled = !invertCutSegments && enabledSegmentsRaw.includes(seg);
return (
<Segment
key={id}
@ -283,7 +285,7 @@ const SegmentList = memo(({
</ReactSortable>
</div>
{outSegments.length > 0 && renderFooter()}
{segments.length > 0 && renderFooter()}
</>
);
});

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

@ -6,19 +6,19 @@ import { useTranslation } from 'react-i18next';
import { primaryColor } from '../colors';
const ExportButton = memo(({ enabledOutSegments, areWeCutting, autoMerge, onClick, size = 1 }) => {
const ExportButton = memo(({ enabledSegments, areWeCutting, autoMerge, onClick, size = 1 }) => {
const CutIcon = areWeCutting ? FiScissors : FaFileExport;
const { t } = useTranslation();
let exportButtonTitle = t('Export');
if (enabledOutSegments.length === 1) {
if (enabledSegments.length === 1) {
exportButtonTitle = t('Export selection');
} else if (enabledOutSegments.length > 1) {
exportButtonTitle = t('Export {{ num }} segments', { num: enabledOutSegments.length });
} else if (enabledSegments.length > 1) {
exportButtonTitle = t('Export {{ num }} segments', { num: enabledSegments.length });
}
const exportButtonText = autoMerge && enabledOutSegments && enabledOutSegments.length > 1 ? t('Export+merge') : t('Export');
const exportButtonText = autoMerge && enabledSegments && enabledSegments.length > 1 ? t('Export+merge') : t('Export');
return (
<div

@ -6,7 +6,7 @@ import { MdCallSplit, MdCallMerge } from 'react-icons/md';
import { withBlur } from '../util';
const MergeExportButton = memo(({ autoMerge, enabledOutSegments, setAutoMerge, autoDeleteMergedSegments, setAutoDeleteMergedSegments, segmentsToChaptersOnly, setSegmentsToChaptersOnly }) => {
const MergeExportButton = memo(({ autoMerge, enabledSegments, setAutoMerge, autoDeleteMergedSegments, setAutoDeleteMergedSegments, segmentsToChaptersOnly, setSegmentsToChaptersOnly }) => {
const { t } = useTranslation();
let AutoMergeIcon;
@ -63,7 +63,7 @@ const MergeExportButton = memo(({ autoMerge, enabledOutSegments, setAutoMerge, a
return (
<Button
height={20}
style={{ minWidth: 120, textAlign: 'center', opacity: enabledOutSegments && enabledOutSegments.length < 2 ? 0.4 : undefined }}
style={{ minWidth: 120, textAlign: 'center', opacity: enabledSegments && enabledSegments.length < 2 ? 0.4 : undefined }}
title={description}
onClick={withBlur(onClick)}
iconBefore={AutoMergeIcon && (() => <AutoMergeIcon />)}

@ -13,7 +13,7 @@ const fs = window.require('fs-extra');
const stringToStream = window.require('string-to-stream');
async function writeChaptersFfmetadata(outDir, chapters) {
if (!chapters) return undefined;
if (!chapters || chapters.length === 0) return undefined;
const path = join(outDir, `ffmetadata-${new Date().getTime()}.txt`);
@ -21,7 +21,7 @@ async function writeChaptersFfmetadata(outDir, chapters) {
const nameOut = name || `Chapter ${i + 1}`;
return `[CHAPTER]\nTIMEBASE=1/1000\nSTART=${Math.floor(start * 1000)}\nEND=${Math.floor(end * 1000)}\ntitle=${nameOut}`;
}).join('\n\n');
// console.log(ffmetadata);
console.log('Writing chapters', ffmetadata);
await fs.writeFile(path, ffmetadata);
return path;
}
@ -216,7 +216,7 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps }) {
const outFiles = [];
const chaptersPath = chapters ? await writeChaptersFfmetadata(outputDir, chapters) : undefined;
const chaptersPath = await writeChaptersFfmetadata(outputDir, chapters);
try {
// eslint-disable-next-line no-restricted-syntax

Loading…
Cancel
Save