change segments to export into select segments

allows more flexibility
pull/901/head
Mikael Finstad 4 years ago
parent 6d8981798d
commit c48f2dd2f3
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26

@ -132,7 +132,7 @@ const App = memo(() => {
const [thumbnails, setThumbnails] = useState([]); const [thumbnails, setThumbnails] = useState([]);
const [shortestFlag, setShortestFlag] = useState(false); const [shortestFlag, setShortestFlag] = useState(false);
const [zoomWindowStartTime, setZoomWindowStartTime] = useState(0); const [zoomWindowStartTime, setZoomWindowStartTime] = useState(0);
const [disabledSegmentIds, setDisabledSegmentIds] = useState({}); const [deselectedSegmentIds, setDeselectedSegmentIds] = useState({});
const [subtitlesByStreamId, setSubtitlesByStreamId] = useState({}); const [subtitlesByStreamId, setSubtitlesByStreamId] = useState({});
const [activeSubtitleStreamIndex, setActiveSubtitleStreamIndex] = useState(); const [activeSubtitleStreamIndex, setActiveSubtitleStreamIndex] = useState();
const [hideCanvasPreview, setHideCanvasPreview] = useState(false); const [hideCanvasPreview, setHideCanvasPreview] = useState(false);
@ -929,7 +929,7 @@ const App = memo(() => {
setThumbnails([]); setThumbnails([]);
setShortestFlag(false); setShortestFlag(false);
setZoomWindowStartTime(0); setZoomWindowStartTime(0);
setDisabledSegmentIds({}); setDeselectedSegmentIds({});
setSubtitlesByStreamId({}); setSubtitlesByStreamId({});
setActiveSubtitleStreamIndex(); setActiveSubtitleStreamIndex();
setHideCanvasPreview(false); setHideCanvasPreview(false);
@ -1127,31 +1127,31 @@ const App = memo(() => {
invertCutSegments ? inverseCutSegments : apparentCutSegments invertCutSegments ? inverseCutSegments : apparentCutSegments
), [invertCutSegments, inverseCutSegments, apparentCutSegments]); ), [invertCutSegments, inverseCutSegments, apparentCutSegments]);
const enabledSegmentsRaw = useMemo(() => { const selectedSegmentsRaw = useMemo(() => {
// For invertCutSegments we do not support filtering // For invertCutSegments we do not support filtering
if (invertCutSegments) return inverseCutSegments; if (invertCutSegments) return inverseCutSegments;
return apparentCutSegments.filter((s) => !disabledSegmentIds[s.segId]); return apparentCutSegments.filter((s) => !deselectedSegmentIds[s.segId]);
}, [invertCutSegments, inverseCutSegments, apparentCutSegments, disabledSegmentIds]); }, [invertCutSegments, inverseCutSegments, apparentCutSegments, deselectedSegmentIds]);
// If user has selected none to export, it makes no sense, so export all instead // If user has selected none to export, it makes no sense, so export all instead
const enabledSegments = enabledSegmentsRaw.length > 0 ? enabledSegmentsRaw : inverseOrNormalSegments; const selectedSegments = selectedSegmentsRaw.length > 0 ? selectedSegmentsRaw : inverseOrNormalSegments;
const enableOnlySegment = useCallback((seg) => setDisabledSegmentIds(Object.fromEntries(cutSegments.filter((s) => s.segId !== seg.segId).map((s) => [s.segId, true]))), [cutSegments]); const selectOnlySegment = useCallback((seg) => setDeselectedSegmentIds(Object.fromEntries(cutSegments.filter((s) => s.segId !== seg.segId).map((s) => [s.segId, true]))), [cutSegments]);
const toggleSegmentEnabled = useCallback((seg) => setDisabledSegmentIds((existing) => ({ ...existing, [seg.segId]: !existing[seg.segId] })), []); const toggleSegmentSelected = useCallback((seg) => setDeselectedSegmentIds((existing) => ({ ...existing, [seg.segId]: !existing[seg.segId] })), []);
const disableAllSegments = useCallback(() => setDisabledSegmentIds(Object.fromEntries(cutSegments.map((s) => [s.segId, true]))), [cutSegments]); const deselectAllSegments = useCallback(() => setDeselectedSegmentIds(Object.fromEntries(cutSegments.map((s) => [s.segId, true]))), [cutSegments]);
const enableAllSegments = useCallback(() => setDisabledSegmentIds({}), []); const selectAllSegments = useCallback(() => setDeselectedSegmentIds({}), []);
const enableOnlyCurrentSegment = useCallback(() => enableOnlySegment(currentCutSeg), [currentCutSeg, enableOnlySegment]); const selectOnlyCurrentSegment = useCallback(() => selectOnlySegment(currentCutSeg), [currentCutSeg, selectOnlySegment]);
const toggleCurrentSegmentEnabled = useCallback(() => toggleSegmentEnabled(currentCutSeg), [currentCutSeg, toggleSegmentEnabled]); const toggleCurrentSegmentSelected = useCallback(() => toggleSegmentSelected(currentCutSeg), [currentCutSeg, toggleSegmentSelected]);
const filenamifyOrNot = useCallback((name) => (safeOutputFileName ? filenamify(name) : name).substr(0, maxLabelLength), [safeOutputFileName, maxLabelLength]); const filenamifyOrNot = useCallback((name) => (safeOutputFileName ? filenamify(name) : name).substr(0, maxLabelLength), [safeOutputFileName, maxLabelLength]);
const segmentsToExport = useMemo(() => { const segmentsToExport = useMemo(() => {
if (!segmentsToChaptersOnly) return enabledSegments; if (!segmentsToChaptersOnly) return selectedSegments;
// segmentsToChaptersOnly 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 // segmentsToChaptersOnly 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
// Chapters export mode: Emulate a single segment with no cuts (full timeline) // Chapters export mode: Emulate a single segment with no cuts (full timeline)
return [{ start: 0, end: getSegApparentEnd({}) }]; return [{ start: 0, end: getSegApparentEnd({}) }];
}, [enabledSegments, getSegApparentEnd, segmentsToChaptersOnly]); }, [selectedSegments, getSegApparentEnd, segmentsToChaptersOnly]);
const areWeCutting = useMemo(() => segmentsToExport.some(({ start, end }) => isCuttingStart(start) || isCuttingEnd(end, duration)), [duration, segmentsToExport]); const areWeCutting = useMemo(() => segmentsToExport.some(({ start, end }) => isCuttingStart(start) || isCuttingEnd(end, duration)), [duration, segmentsToExport]);
@ -1223,7 +1223,7 @@ const App = memo(() => {
// Special segments-to-chapters mode: // Special segments-to-chapters mode:
let chaptersToAdd; let chaptersToAdd;
if (segmentsToChaptersOnly) { if (segmentsToChaptersOnly) {
const sortedSegments = sortSegments(enabledSegments); const sortedSegments = sortSegments(selectedSegments);
if (hasAnySegmentOverlap(sortedSegments)) { if (hasAnySegmentOverlap(sortedSegments)) {
errorToast(i18n.t('Make sure you have no overlapping segments.')); errorToast(i18n.t('Make sure you have no overlapping segments.'));
return; return;
@ -1322,7 +1322,7 @@ const App = memo(() => {
setWorking(); setWorking();
setCutProgress(); setCutProgress();
} }
}, [numStreamsToCopy, setWorking, segmentsToChaptersOnly, enabledSegments, outSegTemplateOrDefault, generateOutSegFileNames, segmentsToExport, getOutSegError, cutMultiple, outputDir, customOutDir, fileFormat, duration, isRotationSet, effectiveRotation, copyFileStreams, allFilesMeta, keyframeCut, shortestFlag, ffmpegExperimental, preserveMovData, preserveMetadataOnMerge, movFastStart, avoidNegativeTs, customTagsByFile, customTagsByStreamId, dispositionByStreamId, detectedFps, enableSmartCut, willMerge, mainFileFormatData, mainStreams, exportExtraStreams, hideAllNotifications, segmentsToChapters, invertCutSegments, autoConcatCutSegments, isCustomFormatSelected, autoDeleteMergedSegments, filePath, nonCopiedExtraStreams, handleCutFailed]); }, [numStreamsToCopy, setWorking, segmentsToChaptersOnly, selectedSegments, outSegTemplateOrDefault, generateOutSegFileNames, segmentsToExport, getOutSegError, cutMultiple, outputDir, customOutDir, fileFormat, duration, isRotationSet, effectiveRotation, copyFileStreams, allFilesMeta, keyframeCut, shortestFlag, ffmpegExperimental, preserveMovData, preserveMetadataOnMerge, movFastStart, avoidNegativeTs, customTagsByFile, customTagsByStreamId, dispositionByStreamId, detectedFps, enableSmartCut, willMerge, mainFileFormatData, mainStreams, exportExtraStreams, hideAllNotifications, segmentsToChapters, invertCutSegments, autoConcatCutSegments, isCustomFormatSelected, autoDeleteMergedSegments, filePath, nonCopiedExtraStreams, handleCutFailed]);
const onExportPress = useCallback(async () => { const onExportPress = useCallback(async () => {
if (!filePath || workingRef.current) return; if (!filePath || workingRef.current) return;
@ -1836,10 +1836,10 @@ const App = memo(() => {
toggleCaptureFormat, toggleCaptureFormat,
toggleStripAudio, toggleStripAudio,
setStartTimeOffset: askSetStartTimeOffset, setStartTimeOffset: askSetStartTimeOffset,
disableAllSegments, disableAllSegments: deselectAllSegments,
enableAllSegments, enableAllSegments: selectAllSegments,
enableOnlyCurrentSegment, enableOnlyCurrentSegment: selectOnlyCurrentSegment,
toggleCurrentSegmentEnabled, toggleCurrentSegmentEnabled: toggleCurrentSegmentSelected,
fixInvalidDuration: tryFixInvalidDuration, fixInvalidDuration: tryFixInvalidDuration,
increaseVolume: () => setPlaybackVolume((val) => Math.min(1, val + 0.07)), increaseVolume: () => setPlaybackVolume((val) => Math.min(1, val + 0.07)),
decreaseVolume: () => setPlaybackVolume((val) => Math.max(0, val - 0.07)), decreaseVolume: () => setPlaybackVolume((val) => Math.max(0, val - 0.07)),
@ -1884,7 +1884,7 @@ const App = memo(() => {
if (match) return bubble; if (match) return bubble;
return true; // bubble the event return true; // bubble the event
}, [addCutSegment, askSetStartTimeOffset, batchFileJump, batchOpenSelectedFile, captureSnapshot, changePlaybackRate, cleanupFilesDialog, clearSegments, closeBatch, closeExportConfirm, concatCurrentBatch, concatDialogVisible, convertFormatBatch, createFixedDurationSegments, createNumSegments, currentSegIndexSafe, cutSegmentsHistory, disableAllSegments, enableAllSegments, enableOnlyCurrentSegment, exportConfirmVisible, extractAllStreams, goToTimecode, increaseRotation, invertAllCutSegments, jumpCutEnd, jumpCutStart, jumpSeg, jumpTimelineEnd, jumpTimelineStart, keyboardNormalSeekSpeed, keyboardSeekAccFactor, keyboardShortcutsVisible, onExportConfirm, onExportPress, onLabelSegmentPress, pause, play, removeCutSegment, reorderSegsByStartTime, seekClosestKeyframe, seekRel, seekRelPercent, setCutEnd, setCutStart, setPlaybackVolume, shortStep, shuffleSegments, splitCurrentSegment, timelineToggleComfortZoom, toggleCaptureFormat, toggleCurrentSegmentEnabled, toggleHelp, toggleKeyboardShortcuts, toggleKeyframeCut, togglePlay, toggleSegmentsList, toggleStreamsSelector, toggleStripAudio, tryFixInvalidDuration, userHtml5ifyCurrentFile, zoomRel]); }, [addCutSegment, askSetStartTimeOffset, batchFileJump, batchOpenSelectedFile, captureSnapshot, changePlaybackRate, cleanupFilesDialog, clearSegments, closeBatch, closeExportConfirm, concatCurrentBatch, concatDialogVisible, convertFormatBatch, createFixedDurationSegments, createNumSegments, currentSegIndexSafe, cutSegmentsHistory, deselectAllSegments, exportConfirmVisible, extractAllStreams, goToTimecode, increaseRotation, invertAllCutSegments, jumpCutEnd, jumpCutStart, jumpSeg, jumpTimelineEnd, jumpTimelineStart, keyboardNormalSeekSpeed, keyboardSeekAccFactor, keyboardShortcutsVisible, onExportConfirm, onExportPress, onLabelSegmentPress, pause, play, removeCutSegment, reorderSegsByStartTime, seekClosestKeyframe, seekRel, seekRelPercent, selectAllSegments, selectOnlyCurrentSegment, setCutEnd, setCutStart, setPlaybackVolume, shortStep, shuffleSegments, splitCurrentSegment, timelineToggleComfortZoom, toggleCaptureFormat, toggleCurrentSegmentSelected, toggleHelp, toggleKeyboardShortcuts, toggleKeyframeCut, togglePlay, toggleSegmentsList, toggleStreamsSelector, toggleStripAudio, tryFixInvalidDuration, userHtml5ifyCurrentFile, zoomRel]);
useKeyboard({ keyBindings, onKeyPress }); useKeyboard({ keyBindings, onKeyPress });
@ -2209,7 +2209,7 @@ const App = memo(() => {
numStreamsToCopy={numStreamsToCopy} numStreamsToCopy={numStreamsToCopy}
numStreamsTotal={numStreamsTotal} numStreamsTotal={numStreamsTotal}
setStreamsSelectorShown={setStreamsSelectorShown} setStreamsSelectorShown={setStreamsSelectorShown}
enabledSegments={enabledSegments} selectedSegments={selectedSegments}
/> />
<div style={{ flexGrow: 1, display: 'flex', overflowY: 'hidden' }}> <div style={{ flexGrow: 1, display: 'flex', overflowY: 'hidden' }}>
@ -2305,12 +2305,12 @@ const App = memo(() => {
removeCutSegment={removeCutSegment} removeCutSegment={removeCutSegment}
toggleSegmentsList={toggleSegmentsList} toggleSegmentsList={toggleSegmentsList}
splitCurrentSegment={splitCurrentSegment} splitCurrentSegment={splitCurrentSegment}
enabledSegmentsRaw={enabledSegmentsRaw} selectedSegmentsRaw={selectedSegmentsRaw}
enabledSegments={enabledSegments} selectedSegments={selectedSegments}
onExportSingleSegmentClick={enableOnlySegment} onSelectSingleSegment={selectOnlySegment}
onExportSegmentEnabledToggle={toggleSegmentEnabled} onToggleSegmentSelected={toggleSegmentSelected}
onExportSegmentDisableAll={disableAllSegments} onDeselectAllSegments={deselectAllSegments}
onExportSegmentEnableAll={enableAllSegments} onSelectAllSegments={selectAllSegments}
jumpSegStart={jumpSegStart} jumpSegStart={jumpSegStart}
jumpSegEnd={jumpSegEnd} jumpSegEnd={jumpSegEnd}
onViewSegmentTagsPress={onViewSegmentTagsPress} onViewSegmentTagsPress={onViewSegmentTagsPress}
@ -2360,7 +2360,7 @@ const App = memo(() => {
cleanupFilesDialog={cleanupFilesDialog} cleanupFilesDialog={cleanupFilesDialog}
captureSnapshot={captureSnapshot} captureSnapshot={captureSnapshot}
onExportPress={onExportPress} onExportPress={onExportPress}
enabledSegments={enabledSegments} selectedSegments={selectedSegments}
seekAbs={seekAbs} seekAbs={seekAbs}
currentSegIndexSafe={currentSegIndexSafe} currentSegIndexSafe={currentSegIndexSafe}
cutSegments={cutSegments} cutSegments={cutSegments}
@ -2426,7 +2426,7 @@ const App = memo(() => {
/> />
</SideSheet> </SideSheet>
<ExportConfirm filePath={filePath} areWeCutting={areWeCutting} enabledSegments={enabledSegments} willMerge={willMerge} visible={exportConfirmVisible} onClosePress={closeExportConfirm} onExportConfirm={onExportConfirm} renderOutFmt={renderOutFmt} outputDir={outputDir} numStreamsTotal={numStreamsTotal} numStreamsToCopy={numStreamsToCopy} setStreamsSelectorShown={setStreamsSelectorShown} outFormat={fileFormat} setOutSegTemplate={setOutSegTemplate} outSegTemplate={outSegTemplateOrDefault} generateOutSegFileNames={generateOutSegFileNames} currentSegIndexSafe={currentSegIndexSafe} getOutSegError={getOutSegError} /> <ExportConfirm filePath={filePath} areWeCutting={areWeCutting} selectedSegments={selectedSegments} willMerge={willMerge} visible={exportConfirmVisible} onClosePress={closeExportConfirm} onExportConfirm={onExportConfirm} renderOutFmt={renderOutFmt} outputDir={outputDir} numStreamsTotal={numStreamsTotal} numStreamsToCopy={numStreamsToCopy} setStreamsSelectorShown={setStreamsSelectorShown} outFormat={fileFormat} setOutSegTemplate={setOutSegTemplate} outSegTemplate={outSegTemplateOrDefault} generateOutSegFileNames={generateOutSegFileNames} currentSegIndexSafe={currentSegIndexSafe} getOutSegError={getOutSegError} />
<HelpSheet <HelpSheet
visible={helpVisible} visible={helpVisible}

@ -31,7 +31,7 @@ const leftRightWidth = 100;
const BottomBar = memo(({ const BottomBar = memo(({
zoom, setZoom, timelineToggleComfortZoom, zoom, setZoom, timelineToggleComfortZoom,
isRotationSet, rotation, areWeCutting, increaseRotation, cleanupFilesDialog, isRotationSet, rotation, areWeCutting, increaseRotation, cleanupFilesDialog,
captureSnapshot, onExportPress, enabledSegments, hasVideo, captureSnapshot, onExportPress, selectedSegments, hasVideo,
seekAbs, currentSegIndexSafe, cutSegments, currentCutSeg, setCutStart, setCutEnd, seekAbs, currentSegIndexSafe, cutSegments, currentCutSeg, setCutStart, setCutEnd,
setCurrentSegIndex, cutStartTimeManual, setCutStartTimeManual, cutEndTimeManual, setCutEndTimeManual, setCurrentSegIndex, cutStartTimeManual, setCutStartTimeManual, cutEndTimeManual, setCutEndTimeManual,
jumpTimelineStart, jumpTimelineEnd, jumpCutEnd, jumpCutStart, startTimeOffset, setCutTime, currentApparentCutSeg, jumpTimelineStart, jumpTimelineEnd, jumpCutEnd, jumpCutStart, startTimeOffset, setCutTime, currentApparentCutSeg,
@ -359,7 +359,7 @@ const BottomBar = memo(({
{!simpleMode && <ToggleExportConfirm style={{ marginRight: 5 }} />} {!simpleMode && <ToggleExportConfirm style={{ marginRight: 5 }} />}
<ExportButton size={1.3} enabledSegments={enabledSegments} areWeCutting={areWeCutting} onClick={onExportPress} /> <ExportButton size={1.3} selectedSegments={selectedSegments} areWeCutting={areWeCutting} onClick={onExportPress} />
</div> </div>
</> </>
); );

@ -40,7 +40,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 HelpIcon = ({ onClick }) => <IoIosHelpCircle size={20} role="button" onClick={withBlur(onClick)} style={{ cursor: 'pointer', verticalAlign: 'middle', marginLeft: 5 }} />;
const ExportConfirm = memo(({ const ExportConfirm = memo(({
areWeCutting, enabledSegments, willMerge, visible, onClosePress, onExportConfirm, areWeCutting, selectedSegments, willMerge, visible, onClosePress, onExportConfirm,
renderOutFmt, renderOutFmt,
outputDir, numStreamsTotal, numStreamsToCopy, setStreamsSelectorShown, outputDir, numStreamsTotal, numStreamsToCopy, setStreamsSelectorShown,
outFormat, outFormat,
@ -123,7 +123,7 @@ const ExportConfirm = memo(({
<h2 style={{ marginTop: 0 }}>{t('Export options')}</h2> <h2 style={{ marginTop: 0 }}>{t('Export options')}</h2>
<ul> <ul>
{enabledSegments.length >= 2 && <li>{t('Merge {{segments}} cut segments to one file?', { segments: enabledSegments.length })} <MergeExportButton enabledSegments={enabledSegments} /></li>} {selectedSegments.length >= 2 && <li>{t('Merge {{segments}} cut segments to one file?', { segments: selectedSegments.length })} <MergeExportButton selectedSegments={selectedSegments} /></li>}
<li> <li>
{t('Output container format:')} {renderOutFmt({ height: 20, maxWidth: 150 })} {t('Output container format:')} {renderOutFmt({ height: 20, maxWidth: 150 })}
<HelpIcon onClick={onOutFmtHelpPress} /> <HelpIcon onClick={onOutFmtHelpPress} />
@ -224,7 +224,7 @@ const ExportConfirm = memo(({
exit={{ scale: 0.7, opacity: 0 }} exit={{ scale: 0.7, opacity: 0 }}
transition={{ duration: 0.4, easings: ['easeOut'] }} transition={{ duration: 0.4, easings: ['easeOut'] }}
> >
<ExportButton enabledSegments={enabledSegments} areWeCutting={areWeCutting} onClick={() => onExportConfirm()} size={1.7} /> <ExportButton selectedSegments={selectedSegments} areWeCutting={areWeCutting} onClick={() => onExportConfirm()} size={1.7} />
</motion.div> </motion.div>
</div> </div>
</> </>

@ -1,5 +1,5 @@
import React, { memo, useMemo, useRef, useCallback } from 'react'; import React, { memo, useMemo, useRef, useCallback } from 'react';
import { FaSave, FaPlus, FaMinus, FaTag, FaSortNumericDown, FaAngleRight, FaCheck, FaTimes } from 'react-icons/fa'; import { FaSave, FaPlus, FaMinus, FaTag, FaSortNumericDown, FaAngleRight, FaRegCheckCircle, FaRegCircle } from 'react-icons/fa';
import { AiOutlineSplitCells } from 'react-icons/ai'; import { AiOutlineSplitCells } from 'react-icons/ai';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import Swal from 'sweetalert2'; import Swal from 'sweetalert2';
@ -21,7 +21,7 @@ const buttonBaseStyle = {
const neutralButtonColor = 'rgba(255, 255, 255, 0.2)'; const neutralButtonColor = 'rgba(255, 255, 255, 0.2)';
const Segment = memo(({ seg, index, currentSegIndex, formatTimecode, getFrameCount, updateOrder, invertCutSegments, onClick, onRemovePress, onReorderPress, onLabelPress, enabled, onExportSingleSegmentClick, onExportSegmentEnabledToggle, onExportSegmentDisableAll, onExportSegmentEnableAll, jumpSegStart, jumpSegEnd, addCutSegment, onViewSegmentTagsPress }) => { const Segment = memo(({ seg, index, currentSegIndex, formatTimecode, getFrameCount, updateOrder, invertCutSegments, onClick, onRemovePress, onReorderPress, onLabelPress, enabled, onSelectSingleSegment, onToggleSegmentSelected, onDeselectAllSegments, onSelectAllSegments, jumpSegStart, jumpSegEnd, addCutSegment, onViewSegmentTagsPress }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const ref = useRef(); const ref = useRef();
@ -46,16 +46,15 @@ const Segment = memo(({ seg, index, currentSegIndex, formatTimecode, getFrameCou
{ type: 'separator' }, { type: 'separator' },
{ label: t('Include ONLY this segment in export'), click: () => onExportSingleSegmentClick(seg) }, { label: t('Select ONLY current segment'), click: () => onSelectSingleSegment(seg) },
{ label: enabled ? t('Exclude this segment from export') : t('Include this segment in export'), click: () => onExportSegmentEnabledToggle(seg) }, { label: t('Select all segments'), click: () => onSelectAllSegments() },
{ label: t('Include all segments in export'), click: () => onExportSegmentEnableAll(seg) }, { label: t('Deselect all segments'), click: () => onDeselectAllSegments() },
{ label: t('Exclude all segments from export'), click: () => onExportSegmentDisableAll(seg) },
{ type: 'separator' }, { type: 'separator' },
{ label: t('Segment tags'), click: () => onViewSegmentTagsPress(index) }, { label: t('Segment tags'), click: () => onViewSegmentTagsPress(index) },
]; ];
}, [addCutSegment, enabled, index, invertCutSegments, jumpSegEnd, jumpSegStart, onExportSegmentDisableAll, onExportSegmentEnableAll, onExportSegmentEnabledToggle, onExportSingleSegmentClick, onLabelPress, onRemovePress, onReorderPress, onViewSegmentTagsPress, seg, t, updateOrder]); }, [addCutSegment, index, invertCutSegments, jumpSegEnd, jumpSegStart, onDeselectAllSegments, onSelectAllSegments, onSelectSingleSegment, onLabelPress, onRemovePress, onReorderPress, onViewSegmentTagsPress, seg, t, updateOrder]);
useContextMenu(ref, contextMenuTemplate); useContextMenu(ref, contextMenuTemplate);
@ -81,7 +80,7 @@ const Segment = memo(({ seg, index, currentSegIndex, formatTimecode, getFrameCou
function onDoubleClick() { function onDoubleClick() {
if (invertCutSegments) return; if (invertCutSegments) return;
if (!enabled) { if (!enabled) {
onExportSegmentEnabledToggle(seg); onToggleSegmentSelected(seg);
return; return;
} }
jumpSegStart(); jumpSegStart();
@ -90,6 +89,10 @@ const Segment = memo(({ seg, index, currentSegIndex, formatTimecode, getFrameCou
const durationMsFormatted = Math.floor(durationMs); const durationMsFormatted = Math.floor(durationMs);
const frameCount = getFrameCount(duration); const frameCount = getFrameCount(duration);
const CheckIcon = enabled ? FaRegCheckCircle : FaRegCircle;
const onToggleSegmentSelectedClick = useCallback(() => onToggleSegmentSelected(seg), [onToggleSegmentSelected, seg]);
return ( return (
<motion.div <motion.div
ref={ref} ref={ref}
@ -101,6 +104,7 @@ const Segment = memo(({ seg, index, currentSegIndex, formatTimecode, getFrameCou
initial={{ scaleY: 0 }} initial={{ scaleY: 0 }}
animate={{ scaleY: 1 }} animate={{ scaleY: 1 }}
exit={{ scaleY: 0 }} exit={{ scaleY: 0 }}
className="segment-list-entry"
> >
<div style={{ color: 'white', marginBottom: 3, display: 'flex', alignItems: 'center', height: 16 }}> <div style={{ color: 'white', marginBottom: 3, display: 'flex', alignItems: 'center', height: 16 }}>
{renderNumber()} {renderNumber()}
@ -114,9 +118,9 @@ const Segment = memo(({ seg, index, currentSegIndex, formatTimecode, getFrameCou
<Trans>{{ durationMsFormatted }} ms, {{ frameCount }} frames</Trans> <Trans>{{ durationMsFormatted }} ms, {{ frameCount }} frames</Trans>
</div> </div>
{!enabled && !invertCutSegments && ( {!invertCutSegments && (
<div style={{ position: 'absolute', pointerEvents: 'none', top: 0, right: 0, bottom: 0, left: 0, overflow: 'hidden', display: 'flex', justifyContent: 'center', alignItems: 'center' }}> <div style={{ position: 'absolute', right: 3, bottom: 3 }}>
<FaTimes style={{ fontSize: 100, color: 'rgba(255,0,0,0.8)' }} /> <CheckIcon className="enabled" size={20} onClick={onToggleSegmentSelectedClick} />
</div> </div>
)} )}
</motion.div> </motion.div>
@ -128,7 +132,7 @@ const SegmentList = memo(({
currentSegIndex, currentSegIndex,
updateSegOrder, updateSegOrders, addCutSegment, removeCutSegment, updateSegOrder, updateSegOrders, addCutSegment, removeCutSegment,
onLabelSegmentPress, currentCutSeg, segmentAtCursor, toggleSegmentsList, splitCurrentSegment, onLabelSegmentPress, currentCutSeg, segmentAtCursor, toggleSegmentsList, splitCurrentSegment,
enabledSegments, enabledSegmentsRaw, onExportSingleSegmentClick, onExportSegmentEnabledToggle, onExportSegmentDisableAll, onExportSegmentEnableAll, selectedSegments, selectedSegmentsRaw, onSelectSingleSegment, onToggleSegmentSelected, onDeselectAllSegments, onSelectAllSegments,
jumpSegStart, jumpSegEnd, onViewSegmentTagsPress, jumpSegStart, jumpSegEnd, onViewSegmentTagsPress,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -174,14 +178,7 @@ const SegmentList = memo(({
const currentSegColor = getSegColor(currentCutSeg).alpha(0.5).string(); const currentSegColor = getSegColor(currentCutSeg).alpha(0.5).string();
const segAtCursorColor = getSegColor(segmentAtCursor).alpha(0.5).string(); const segAtCursorColor = getSegColor(segmentAtCursor).alpha(0.5).string();
function renderExportEnabledCheckBox() { const segmentsTotal = selectedSegments.reduce((acc, { start, end }) => (end - start) + acc, 0);
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 = enabledSegments.reduce((acc, { start, end }) => (end - start) + acc, 0);
return ( return (
<> <>
@ -219,8 +216,6 @@ const SegmentList = memo(({
style={{ ...buttonBaseStyle, padding: 4, background: currentSegColor }} style={{ ...buttonBaseStyle, padding: 4, background: currentSegColor }}
onClick={() => onLabelSegmentPress(currentSegIndex)} onClick={() => onLabelSegmentPress(currentSegIndex)}
/> />
{renderExportEnabledCheckBox()}
</> </>
)} )}
@ -264,7 +259,7 @@ const SegmentList = memo(({
<ReactSortable list={sortableList} setList={setSortableList} sort={!invertCutSegments}> <ReactSortable list={sortableList} setList={setSortableList} sort={!invertCutSegments}>
{sortableList.map(({ id, seg }, index) => { {sortableList.map(({ id, seg }, index) => {
const enabled = !invertCutSegments && enabledSegmentsRaw.includes(seg); const enabled = !invertCutSegments && selectedSegmentsRaw.includes(seg);
return ( return (
<Segment <Segment
key={id} key={id}
@ -283,10 +278,10 @@ const SegmentList = memo(({
formatTimecode={formatTimecode} formatTimecode={formatTimecode}
currentSegIndex={currentSegIndex} currentSegIndex={currentSegIndex}
invertCutSegments={invertCutSegments} invertCutSegments={invertCutSegments}
onExportSingleSegmentClick={onExportSingleSegmentClick} onSelectSingleSegment={onSelectSingleSegment}
onExportSegmentEnabledToggle={onExportSegmentEnabledToggle} onToggleSegmentSelected={onToggleSegmentSelected}
onExportSegmentDisableAll={onExportSegmentDisableAll} onDeselectAllSegments={onDeselectAllSegments}
onExportSegmentEnableAll={onExportSegmentEnableAll} onSelectAllSegments={onSelectAllSegments}
onViewSegmentTagsPress={onViewSegmentTagsPress} onViewSegmentTagsPress={onViewSegmentTagsPress}
/> />
); );

@ -14,7 +14,7 @@ import useUserSettings from './hooks/useUserSettings';
const TopMenu = memo(({ const TopMenu = memo(({
filePath, fileFormat, copyAnyAudioTrack, toggleStripAudio, filePath, fileFormat, copyAnyAudioTrack, toggleStripAudio,
renderOutFmt, toggleHelp, numStreamsToCopy, numStreamsTotal, setStreamsSelectorShown, toggleSettings, renderOutFmt, toggleHelp, numStreamsToCopy, numStreamsTotal, setStreamsSelectorShown, toggleSettings,
enabledSegments, isCustomFormatSelected, clearOutDir, selectedSegments, isCustomFormatSelected, clearOutDir,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { customOutDir, changeOutDir, simpleMode, outFormatLocked, setOutFormatLocked } = useUserSettings(); const { customOutDir, changeOutDir, simpleMode, outFormatLocked, setOutFormatLocked } = useUserSettings();
@ -78,7 +78,7 @@ const TopMenu = memo(({
{!simpleMode && (isCustomFormatSelected || outFormatLocked) && renderFormatLock()} {!simpleMode && (isCustomFormatSelected || outFormatLocked) && renderFormatLock()}
<MergeExportButton enabledSegments={enabledSegments} /> <MergeExportButton selectedSegments={selectedSegments} />
</> </>
)} )}

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

@ -309,19 +309,19 @@ const KeyboardShortcuts = memo(({
category: segmentsAndCutpointsCategory, category: segmentsAndCutpointsCategory,
}, },
enableOnlyCurrentSegment: { enableOnlyCurrentSegment: {
name: t('Include ONLY this segment in export'), name: t('Select ONLY current segment'),
category: segmentsAndCutpointsCategory, category: segmentsAndCutpointsCategory,
}, },
disableAllSegments: { disableAllSegments: {
name: t('Exclude all segments from export'), name: t('Deselect all segments'),
category: segmentsAndCutpointsCategory, category: segmentsAndCutpointsCategory,
}, },
enableAllSegments: { enableAllSegments: {
name: t('Include all segments in export'), name: t('Select all segments'),
category: segmentsAndCutpointsCategory, category: segmentsAndCutpointsCategory,
}, },
toggleCurrentSegmentEnabled: { toggleCurrentSegmentEnabled: {
name: t('Toggle inclusion of this segment in export'), name: t('Toggle current segment selected'),
category: segmentsAndCutpointsCategory, category: segmentsAndCutpointsCategory,
}, },

@ -7,7 +7,7 @@ import { withBlur } from '../util';
import useUserSettings from '../hooks/useUserSettings'; import useUserSettings from '../hooks/useUserSettings';
const MergeExportButton = memo(({ enabledSegments }) => { const MergeExportButton = memo(({ selectedSegments }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { autoMerge, setAutoMerge, autoDeleteMergedSegments, setAutoDeleteMergedSegments, segmentsToChaptersOnly, setSegmentsToChaptersOnly } = useUserSettings(); const { autoMerge, setAutoMerge, autoDeleteMergedSegments, setAutoDeleteMergedSegments, segmentsToChaptersOnly, setSegmentsToChaptersOnly } = useUserSettings();
@ -66,7 +66,7 @@ const MergeExportButton = memo(({ enabledSegments }) => {
return ( return (
<Button <Button
height={20} height={20}
style={{ minWidth: 120, textAlign: 'center', opacity: enabledSegments && enabledSegments.length < 2 ? 0.4 : undefined }} style={{ minWidth: 120, textAlign: 'center', opacity: selectedSegments && selectedSegments.length < 2 ? 0.4 : undefined }}
title={description} title={description}
onClick={withBlur(onClick)} onClick={withBlur(onClick)}
iconBefore={AutoMergeIcon && <AutoMergeIcon />} iconBefore={AutoMergeIcon && <AutoMergeIcon />}

@ -57,3 +57,11 @@ kbd {
align-items: flex-start !important; align-items: flex-start !important;
text-align: left; text-align: left;
} }
.segment-list-entry .enabled {
display: none;
}
.segment-list-entry:hover .enabled {
display: inherit;
}

Loading…
Cancel
Save