mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-25 19:52:44 +01:00
add preference for color intensity
https://github.com/mifi/lossless-cut/discussions/1507
This commit is contained in:
parent
10f8092d15
commit
0afa76a165
@ -123,6 +123,7 @@ const defaults = {
|
|||||||
},
|
},
|
||||||
allowMultipleInstances: false,
|
allowMultipleInstances: false,
|
||||||
darkMode: true,
|
darkMode: true,
|
||||||
|
preferStrongColors: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// For portable app: https://github.com/mifi/lossless-cut/issues/645
|
// For portable app: https://github.com/mifi/lossless-cut/issues/645
|
||||||
|
512
src/App.jsx
512
src/App.jsx
@ -26,7 +26,7 @@ import useFrameCapture from './hooks/useFrameCapture';
|
|||||||
import useSegments from './hooks/useSegments';
|
import useSegments from './hooks/useSegments';
|
||||||
import useDirectoryAccess, { DirectoryAccessDeclinedError } from './hooks/useDirectoryAccess';
|
import useDirectoryAccess, { DirectoryAccessDeclinedError } from './hooks/useDirectoryAccess';
|
||||||
|
|
||||||
import UserSettingsContext from './contexts/UserSettingsContext';
|
import { UserSettingsContext, SegColorsContext } from './contexts';
|
||||||
|
|
||||||
import NoFileLoaded from './NoFileLoaded';
|
import NoFileLoaded from './NoFileLoaded';
|
||||||
import Canvas from './Canvas';
|
import Canvas from './Canvas';
|
||||||
@ -50,6 +50,7 @@ import OutputFormatSelect from './components/OutputFormatSelect';
|
|||||||
|
|
||||||
import { loadMifiLink, runStartupCheck } from './mifi';
|
import { loadMifiLink, runStartupCheck } from './mifi';
|
||||||
import { controlsBackground, darkModeTransition } from './colors';
|
import { controlsBackground, darkModeTransition } from './colors';
|
||||||
|
import { getSegColor } from './util/colors';
|
||||||
import {
|
import {
|
||||||
getStreamFps, isCuttingStart, isCuttingEnd,
|
getStreamFps, isCuttingStart, isCuttingEnd,
|
||||||
readFileMeta, getSmarterOutFormat, renderThumbnails as ffmpegRenderThumbnails,
|
readFileMeta, getSmarterOutFormat, renderThumbnails as ffmpegRenderThumbnails,
|
||||||
@ -175,7 +176,7 @@ const App = memo(() => {
|
|||||||
const allUserSettings = useUserSettingsRoot();
|
const allUserSettings = useUserSettingsRoot();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
captureFormat, setCaptureFormat, customOutDir, setCustomOutDir, keyframeCut, setKeyframeCut, preserveMovData, setPreserveMovData, movFastStart, setMovFastStart, avoidNegativeTs, autoMerge, timecodeFormat, invertCutSegments, setInvertCutSegments, autoExportExtraStreams, askBeforeClose, enableAskForImportChapters, enableAskForFileOpenAction, playbackVolume, setPlaybackVolume, autoSaveProjectFile, wheelSensitivity, invertTimelineScroll, language, ffmpegExperimental, hideNotifications, autoLoadTimecode, autoDeleteMergedSegments, exportConfirmEnabled, setExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, setSimpleMode, outSegTemplate, setOutSegTemplate, keyboardSeekAccFactor, keyboardNormalSeekSpeed, enableTransferTimestamps, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, enableAutoHtml5ify, segmentsToChaptersOnly, keyBindings, setKeyBindings, resetKeyBindings, enableSmartCut, customFfPath, storeProjectInWorkingDir, setStoreProjectInWorkingDir, enableOverwriteOutput, mouseWheelZoomModifierKey, captureFrameMethod, captureFrameQuality, captureFrameFileNameFormat, enableNativeHevc, cleanupChoices, setCleanupChoices, darkMode, setDarkMode,
|
captureFormat, setCaptureFormat, customOutDir, setCustomOutDir, keyframeCut, setKeyframeCut, preserveMovData, setPreserveMovData, movFastStart, setMovFastStart, avoidNegativeTs, autoMerge, timecodeFormat, invertCutSegments, setInvertCutSegments, autoExportExtraStreams, askBeforeClose, enableAskForImportChapters, enableAskForFileOpenAction, playbackVolume, setPlaybackVolume, autoSaveProjectFile, wheelSensitivity, invertTimelineScroll, language, ffmpegExperimental, hideNotifications, autoLoadTimecode, autoDeleteMergedSegments, exportConfirmEnabled, setExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, setSimpleMode, outSegTemplate, setOutSegTemplate, keyboardSeekAccFactor, keyboardNormalSeekSpeed, enableTransferTimestamps, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, enableAutoHtml5ify, segmentsToChaptersOnly, keyBindings, setKeyBindings, resetKeyBindings, enableSmartCut, customFfPath, storeProjectInWorkingDir, setStoreProjectInWorkingDir, enableOverwriteOutput, mouseWheelZoomModifierKey, captureFrameMethod, captureFrameQuality, captureFrameFileNameFormat, enableNativeHevc, cleanupChoices, setCleanupChoices, darkMode, setDarkMode, preferStrongColors,
|
||||||
} = allUserSettings;
|
} = allUserSettings;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -510,6 +511,13 @@ const App = memo(() => {
|
|||||||
...allUserSettings, toggleCaptureFormat, changeOutDir, toggleKeyframeCut, togglePreserveMovData, toggleMovFastStart, toggleExportConfirmEnabled, toggleSegmentsToChapters, togglePreserveMetadataOnMerge, toggleSimpleMode, toggleSafeOutputFileName, effectiveExportMode,
|
...allUserSettings, toggleCaptureFormat, changeOutDir, toggleKeyframeCut, togglePreserveMovData, toggleMovFastStart, toggleExportConfirmEnabled, toggleSegmentsToChapters, togglePreserveMetadataOnMerge, toggleSimpleMode, toggleSafeOutputFileName, effectiveExportMode,
|
||||||
}), [allUserSettings, changeOutDir, effectiveExportMode, toggleCaptureFormat, toggleExportConfirmEnabled, toggleKeyframeCut, toggleMovFastStart, togglePreserveMetadataOnMerge, togglePreserveMovData, toggleSafeOutputFileName, toggleSegmentsToChapters, toggleSimpleMode]);
|
}), [allUserSettings, changeOutDir, effectiveExportMode, toggleCaptureFormat, toggleExportConfirmEnabled, toggleKeyframeCut, toggleMovFastStart, togglePreserveMetadataOnMerge, togglePreserveMovData, toggleSafeOutputFileName, toggleSegmentsToChapters, toggleSimpleMode]);
|
||||||
|
|
||||||
|
const segColorsContext = useMemo(() => ({
|
||||||
|
getSegColor: (seg) => {
|
||||||
|
const color = getSegColor(seg);
|
||||||
|
return preferStrongColors ? color.desaturate(0.2) : color.desaturate(0.6);
|
||||||
|
},
|
||||||
|
}), [preferStrongColors]);
|
||||||
|
|
||||||
const isCopyingStreamId = useCallback((path, streamId) => (
|
const isCopyingStreamId = useCallback((path, streamId) => (
|
||||||
!!(copyStreamIdsByFile[path] || {})[streamId]
|
!!(copyStreamIdsByFile[path] || {})[streamId]
|
||||||
), [copyStreamIdsByFile]);
|
), [copyStreamIdsByFile]);
|
||||||
@ -2153,269 +2161,271 @@ const App = memo(() => {
|
|||||||
// throw new Error('Test error boundary');
|
// throw new Error('Test error boundary');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserSettingsContext.Provider value={userSettingsContext}>
|
<SegColorsContext.Provider value={segColorsContext}>
|
||||||
<ThemeProvider value={theme}>
|
<UserSettingsContext.Provider value={userSettingsContext}>
|
||||||
<div className={darkMode ? 'dark-theme' : undefined} style={{ display: 'flex', flexDirection: 'column', height: '100vh', color: 'var(--gray12)', background: 'var(--gray1)', transition: darkModeTransition }}>
|
<ThemeProvider value={theme}>
|
||||||
<TopMenu
|
<div className={darkMode ? 'dark-theme' : undefined} style={{ display: 'flex', flexDirection: 'column', height: '100vh', color: 'var(--gray12)', background: 'var(--gray1)', transition: darkModeTransition }}>
|
||||||
filePath={filePath}
|
<TopMenu
|
||||||
fileFormat={fileFormat}
|
filePath={filePath}
|
||||||
copyAnyAudioTrack={copyAnyAudioTrack}
|
fileFormat={fileFormat}
|
||||||
toggleStripAudio={toggleStripAudio}
|
copyAnyAudioTrack={copyAnyAudioTrack}
|
||||||
clearOutDir={clearOutDir}
|
toggleStripAudio={toggleStripAudio}
|
||||||
isCustomFormatSelected={isCustomFormatSelected}
|
clearOutDir={clearOutDir}
|
||||||
renderOutFmt={renderOutFmt}
|
isCustomFormatSelected={isCustomFormatSelected}
|
||||||
toggleSettings={toggleSettings}
|
renderOutFmt={renderOutFmt}
|
||||||
numStreamsToCopy={numStreamsToCopy}
|
toggleSettings={toggleSettings}
|
||||||
numStreamsTotal={numStreamsTotal}
|
numStreamsToCopy={numStreamsToCopy}
|
||||||
setStreamsSelectorShown={setStreamsSelectorShown}
|
numStreamsTotal={numStreamsTotal}
|
||||||
selectedSegments={selectedSegmentsOrInverse}
|
setStreamsSelectorShown={setStreamsSelectorShown}
|
||||||
/>
|
selectedSegments={selectedSegmentsOrInverse}
|
||||||
|
/>
|
||||||
<div style={{ flexGrow: 1, display: 'flex', overflowY: 'hidden' }}>
|
|
||||||
<AnimatePresence>
|
|
||||||
{showLeftBar && (
|
|
||||||
<BatchFilesList
|
|
||||||
selectedBatchFiles={selectedBatchFiles}
|
|
||||||
filePath={filePath}
|
|
||||||
width={leftBarWidth}
|
|
||||||
batchFiles={batchFiles}
|
|
||||||
setBatchFiles={setBatchFiles}
|
|
||||||
onBatchFileSelect={onBatchFileSelect}
|
|
||||||
batchListRemoveFile={batchListRemoveFile}
|
|
||||||
closeBatch={closeBatch}
|
|
||||||
onMergeFilesClick={concatCurrentBatch}
|
|
||||||
onBatchConvertToSupportedFormatClick={convertFormatBatch}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
|
|
||||||
{/* Middle part: */}
|
|
||||||
<div style={{ position: 'relative', flexGrow: 1, overflow: 'hidden' }}>
|
|
||||||
{!isFileOpened && <NoFileLoaded mifiLink={mifiLink} currentCutSeg={currentCutSeg} />}
|
|
||||||
|
|
||||||
<div className="no-user-select" style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, visibility: !isFileOpened || !hasVideo || bigWaveformEnabled ? 'hidden' : undefined }} onWheel={onTimelineWheel}>
|
|
||||||
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
|
|
||||||
<video
|
|
||||||
muted={playbackVolume === 0}
|
|
||||||
ref={videoRef}
|
|
||||||
style={videoStyle}
|
|
||||||
src={fileUri}
|
|
||||||
onPlay={onSartPlaying}
|
|
||||||
onPause={onStopPlaying}
|
|
||||||
onDurationChange={onDurationChange}
|
|
||||||
onTimeUpdate={onTimeUpdate}
|
|
||||||
onError={onVideoError}
|
|
||||||
>
|
|
||||||
{renderSubtitles()}
|
|
||||||
</video>
|
|
||||||
|
|
||||||
{canvasPlayerEnabled && <Canvas rotate={effectiveRotation} filePath={filePath} width={mainVideoStream.width} height={mainVideoStream.height} streamIndex={mainVideoStream.index} playerTime={playerTime} commandedTime={commandedTime} playing={playing} eventId={canvasPlayerEventId} />}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{bigWaveformEnabled && <BigWaveform waveforms={waveforms} relevantTime={relevantTime} playing={playing} durationSafe={durationSafe} zoom={zoomUnrounded} seekRel={seekRel} />}
|
|
||||||
|
|
||||||
{isRotationSet && !hideCanvasPreview && (
|
|
||||||
<div style={{ position: 'absolute', top: 0, right: 0, left: 0, marginTop: '1em', marginLeft: '1em', color: 'white', display: 'flex', alignItems: 'center' }}>
|
|
||||||
<MdRotate90DegreesCcw size={26} style={{ marginRight: 5 }} />
|
|
||||||
{t('Rotation preview')}
|
|
||||||
{!canvasPlayerRequired && <FaWindowClose role="button" style={{ cursor: 'pointer', verticalAlign: 'middle', padding: 10 }} onClick={() => setHideCanvasPreview(true)} />}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isFileOpened && (
|
|
||||||
<div className="no-user-select" style={{ position: 'absolute', right: 0, bottom: 0, marginBottom: 10, display: 'flex', alignItems: 'center' }}>
|
|
||||||
<VolumeControl playbackVolume={playbackVolume} setPlaybackVolume={setPlaybackVolume} usingDummyVideo={usingDummyVideo} />
|
|
||||||
|
|
||||||
{subtitleStreams.length > 0 && <SubtitleControl subtitleStreams={subtitleStreams} activeSubtitleStreamIndex={activeSubtitleStreamIndex} onActiveSubtitleChange={onActiveSubtitleChange} />}
|
|
||||||
|
|
||||||
{!showRightBar && (
|
|
||||||
<FaAngleLeft
|
|
||||||
title={t('Show sidebar')}
|
|
||||||
size={30}
|
|
||||||
role="button"
|
|
||||||
style={{ marginRight: 10, color: 'var(--gray12)', opacity: 0.7 }}
|
|
||||||
onClick={toggleSegmentsList}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
<div style={{ flexGrow: 1, display: 'flex', overflowY: 'hidden' }}>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{working && <Working text={working} cutProgress={cutProgress} onAbortClick={abortFfmpegs} />}
|
{showLeftBar && (
|
||||||
|
<BatchFilesList
|
||||||
|
selectedBatchFiles={selectedBatchFiles}
|
||||||
|
filePath={filePath}
|
||||||
|
width={leftBarWidth}
|
||||||
|
batchFiles={batchFiles}
|
||||||
|
setBatchFiles={setBatchFiles}
|
||||||
|
onBatchFileSelect={onBatchFileSelect}
|
||||||
|
batchListRemoveFile={batchListRemoveFile}
|
||||||
|
closeBatch={closeBatch}
|
||||||
|
onMergeFilesClick={concatCurrentBatch}
|
||||||
|
onBatchConvertToSupportedFormatClick={convertFormatBatch}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
||||||
{tunerVisible && <ValueTuners type={tunerVisible} onFinished={() => setTunerVisible()} />}
|
{/* Middle part: */}
|
||||||
|
<div style={{ position: 'relative', flexGrow: 1, overflow: 'hidden' }}>
|
||||||
|
{!isFileOpened && <NoFileLoaded mifiLink={mifiLink} currentCutSeg={currentCutSeg} />}
|
||||||
|
|
||||||
|
<div className="no-user-select" style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, visibility: !isFileOpened || !hasVideo || bigWaveformEnabled ? 'hidden' : undefined }} onWheel={onTimelineWheel}>
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
|
||||||
|
<video
|
||||||
|
muted={playbackVolume === 0}
|
||||||
|
ref={videoRef}
|
||||||
|
style={videoStyle}
|
||||||
|
src={fileUri}
|
||||||
|
onPlay={onSartPlaying}
|
||||||
|
onPause={onStopPlaying}
|
||||||
|
onDurationChange={onDurationChange}
|
||||||
|
onTimeUpdate={onTimeUpdate}
|
||||||
|
onError={onVideoError}
|
||||||
|
>
|
||||||
|
{renderSubtitles()}
|
||||||
|
</video>
|
||||||
|
|
||||||
|
{canvasPlayerEnabled && <Canvas rotate={effectiveRotation} filePath={filePath} width={mainVideoStream.width} height={mainVideoStream.height} streamIndex={mainVideoStream.index} playerTime={playerTime} commandedTime={commandedTime} playing={playing} eventId={canvasPlayerEventId} />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{bigWaveformEnabled && <BigWaveform waveforms={waveforms} relevantTime={relevantTime} playing={playing} durationSafe={durationSafe} zoom={zoomUnrounded} seekRel={seekRel} />}
|
||||||
|
|
||||||
|
{isRotationSet && !hideCanvasPreview && (
|
||||||
|
<div style={{ position: 'absolute', top: 0, right: 0, left: 0, marginTop: '1em', marginLeft: '1em', color: 'white', display: 'flex', alignItems: 'center' }}>
|
||||||
|
<MdRotate90DegreesCcw size={26} style={{ marginRight: 5 }} />
|
||||||
|
{t('Rotation preview')}
|
||||||
|
{!canvasPlayerRequired && <FaWindowClose role="button" style={{ cursor: 'pointer', verticalAlign: 'middle', padding: 10 }} onClick={() => setHideCanvasPreview(true)} />}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isFileOpened && (
|
||||||
|
<div className="no-user-select" style={{ position: 'absolute', right: 0, bottom: 0, marginBottom: 10, display: 'flex', alignItems: 'center' }}>
|
||||||
|
<VolumeControl playbackVolume={playbackVolume} setPlaybackVolume={setPlaybackVolume} usingDummyVideo={usingDummyVideo} />
|
||||||
|
|
||||||
|
{subtitleStreams.length > 0 && <SubtitleControl subtitleStreams={subtitleStreams} activeSubtitleStreamIndex={activeSubtitleStreamIndex} onActiveSubtitleChange={onActiveSubtitleChange} />}
|
||||||
|
|
||||||
|
{!showRightBar && (
|
||||||
|
<FaAngleLeft
|
||||||
|
title={t('Show sidebar')}
|
||||||
|
size={30}
|
||||||
|
role="button"
|
||||||
|
style={{ marginRight: 10, color: 'var(--gray12)', opacity: 0.7 }}
|
||||||
|
onClick={toggleSegmentsList}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<AnimatePresence>
|
||||||
|
{working && <Working text={working} cutProgress={cutProgress} onAbortClick={abortFfmpegs} />}
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
{tunerVisible && <ValueTuners type={tunerVisible} onFinished={() => setTunerVisible()} />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AnimatePresence>
|
||||||
|
{showRightBar && isFileOpened && (
|
||||||
|
<SegmentList
|
||||||
|
width={rightBarWidth}
|
||||||
|
currentSegIndex={currentSegIndexSafe}
|
||||||
|
apparentCutSegments={apparentCutSegments}
|
||||||
|
inverseCutSegments={inverseCutSegments}
|
||||||
|
getFrameCount={getFrameCount}
|
||||||
|
formatTimecode={formatTimecode}
|
||||||
|
onSegClick={setCurrentSegIndex}
|
||||||
|
updateSegOrder={updateSegOrder}
|
||||||
|
updateSegOrders={updateSegOrders}
|
||||||
|
onLabelSegment={onLabelSegment}
|
||||||
|
currentCutSeg={currentCutSeg}
|
||||||
|
segmentAtCursor={segmentAtCursor}
|
||||||
|
addSegment={addSegment}
|
||||||
|
removeCutSegment={removeCutSegment}
|
||||||
|
onRemoveSelected={removeSelectedSegments}
|
||||||
|
toggleSegmentsList={toggleSegmentsList}
|
||||||
|
splitCurrentSegment={splitCurrentSegment}
|
||||||
|
isSegmentSelected={isSegmentSelected}
|
||||||
|
selectedSegments={selectedSegmentsOrInverse}
|
||||||
|
onSelectSingleSegment={selectOnlySegment}
|
||||||
|
onToggleSegmentSelected={toggleSegmentSelected}
|
||||||
|
onDeselectAllSegments={deselectAllSegments}
|
||||||
|
onSelectAllSegments={selectAllSegments}
|
||||||
|
onInvertSelectedSegments={invertSelectedSegments}
|
||||||
|
onExtractSegmentFramesAsImages={extractSegmentFramesAsImages}
|
||||||
|
jumpSegStart={jumpSegStart}
|
||||||
|
jumpSegEnd={jumpSegEnd}
|
||||||
|
onViewSegmentTags={onViewSegmentTags}
|
||||||
|
onSelectSegmentsByLabel={onSelectSegmentsByLabel}
|
||||||
|
onLabelSelectedSegments={onLabelSelectedSegments}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AnimatePresence>
|
<div className="no-user-select" style={bottomStyle}>
|
||||||
{showRightBar && isFileOpened && (
|
<Timeline
|
||||||
<SegmentList
|
shouldShowKeyframes={shouldShowKeyframes}
|
||||||
width={rightBarWidth}
|
waveforms={waveforms}
|
||||||
currentSegIndex={currentSegIndexSafe}
|
shouldShowWaveform={shouldShowWaveform}
|
||||||
apparentCutSegments={apparentCutSegments}
|
waveformEnabled={waveformEnabled}
|
||||||
inverseCutSegments={inverseCutSegments}
|
showThumbnails={showThumbnails}
|
||||||
getFrameCount={getFrameCount}
|
neighbouringKeyFrames={neighbouringKeyFrames}
|
||||||
formatTimecode={formatTimecode}
|
thumbnails={thumbnailsSorted}
|
||||||
onSegClick={setCurrentSegIndex}
|
playerTime={playerTime}
|
||||||
updateSegOrder={updateSegOrder}
|
commandedTime={commandedTime}
|
||||||
updateSegOrders={updateSegOrders}
|
relevantTime={relevantTime}
|
||||||
onLabelSegment={onLabelSegment}
|
getRelevantTime={getRelevantTime}
|
||||||
currentCutSeg={currentCutSeg}
|
commandedTimeRef={commandedTimeRef}
|
||||||
segmentAtCursor={segmentAtCursor}
|
startTimeOffset={startTimeOffset}
|
||||||
addSegment={addSegment}
|
zoom={zoom}
|
||||||
removeCutSegment={removeCutSegment}
|
seekAbs={userSeekAbs}
|
||||||
onRemoveSelected={removeSelectedSegments}
|
durationSafe={durationSafe}
|
||||||
toggleSegmentsList={toggleSegmentsList}
|
apparentCutSegments={apparentCutSegments}
|
||||||
splitCurrentSegment={splitCurrentSegment}
|
setCurrentSegIndex={setCurrentSegIndex}
|
||||||
isSegmentSelected={isSegmentSelected}
|
currentSegIndexSafe={currentSegIndexSafe}
|
||||||
selectedSegments={selectedSegmentsOrInverse}
|
inverseCutSegments={inverseCutSegments}
|
||||||
onSelectSingleSegment={selectOnlySegment}
|
formatTimecode={formatTimecode}
|
||||||
onToggleSegmentSelected={toggleSegmentSelected}
|
onZoomWindowStartTimeChange={setZoomWindowStartTime}
|
||||||
onDeselectAllSegments={deselectAllSegments}
|
playing={playing}
|
||||||
onSelectAllSegments={selectAllSegments}
|
isFileOpened={isFileOpened}
|
||||||
onInvertSelectedSegments={invertSelectedSegments}
|
onWheel={onTimelineWheel}
|
||||||
onExtractSegmentFramesAsImages={extractSegmentFramesAsImages}
|
goToTimecode={goToTimecode}
|
||||||
jumpSegStart={jumpSegStart}
|
isSegmentSelected={isSegmentSelected}
|
||||||
jumpSegEnd={jumpSegEnd}
|
/>
|
||||||
onViewSegmentTags={onViewSegmentTags}
|
|
||||||
onSelectSegmentsByLabel={onSelectSegmentsByLabel}
|
<BottomBar
|
||||||
onLabelSelectedSegments={onLabelSelectedSegments}
|
zoom={zoom}
|
||||||
|
setZoom={setZoom}
|
||||||
|
timelineToggleComfortZoom={timelineToggleComfortZoom}
|
||||||
|
hasVideo={hasVideo}
|
||||||
|
isRotationSet={isRotationSet}
|
||||||
|
rotation={rotation}
|
||||||
|
areWeCutting={areWeCutting}
|
||||||
|
increaseRotation={increaseRotation}
|
||||||
|
cleanupFilesDialog={cleanupFilesDialog}
|
||||||
|
captureSnapshot={captureSnapshot}
|
||||||
|
onExportPress={onExportPress}
|
||||||
|
segmentsToExport={segmentsToExport}
|
||||||
|
seekAbs={userSeekAbs}
|
||||||
|
currentSegIndexSafe={currentSegIndexSafe}
|
||||||
|
cutSegments={cutSegments}
|
||||||
|
currentCutSeg={currentCutSeg}
|
||||||
|
selectedSegments={selectedSegments}
|
||||||
|
setCutStart={setCutStart}
|
||||||
|
setCutEnd={setCutEnd}
|
||||||
|
setCurrentSegIndex={setCurrentSegIndex}
|
||||||
|
jumpCutEnd={jumpCutEnd}
|
||||||
|
jumpCutStart={jumpCutStart}
|
||||||
|
jumpTimelineStart={jumpTimelineStart}
|
||||||
|
jumpTimelineEnd={jumpTimelineEnd}
|
||||||
|
startTimeOffset={startTimeOffset}
|
||||||
|
setCutTime={setCutTime}
|
||||||
|
currentApparentCutSeg={currentApparentCutSeg}
|
||||||
|
playing={playing}
|
||||||
|
shortStep={shortStep}
|
||||||
|
seekClosestKeyframe={seekClosestKeyframe}
|
||||||
|
togglePlay={togglePlay}
|
||||||
|
showThumbnails={showThumbnails}
|
||||||
|
toggleEnableThumbnails={toggleEnableThumbnails}
|
||||||
|
toggleWaveformMode={toggleWaveformMode}
|
||||||
|
waveformMode={waveformMode}
|
||||||
|
hasAudio={hasAudio}
|
||||||
|
keyframesEnabled={keyframesEnabled}
|
||||||
|
toggleKeyframesEnabled={toggleKeyframesEnabled}
|
||||||
|
detectedFps={detectedFps}
|
||||||
|
toggleLoopSelectedSegments={toggleLoopSelectedSegments}
|
||||||
|
isFileOpened={isFileOpened}
|
||||||
|
darkMode={darkMode}
|
||||||
|
setDarkMode={setDarkMode}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Sheet visible={streamsSelectorShown} onClosePress={() => setStreamsSelectorShown(false)} style={{ padding: '1em 0' }}>
|
||||||
|
{mainStreams && (
|
||||||
|
<StreamsSelector
|
||||||
|
mainFilePath={filePath}
|
||||||
|
mainFileFormatData={mainFileFormatData}
|
||||||
|
mainFileChapters={mainFileChapters}
|
||||||
|
allFilesMeta={allFilesMeta}
|
||||||
|
externalFilesMeta={externalFilesMeta}
|
||||||
|
setExternalFilesMeta={setExternalFilesMeta}
|
||||||
|
showAddStreamSourceDialog={showAddStreamSourceDialog}
|
||||||
|
mainFileStreams={mainStreams}
|
||||||
|
isCopyingStreamId={isCopyingStreamId}
|
||||||
|
toggleCopyStreamId={toggleCopyStreamId}
|
||||||
|
setCopyStreamIdsForPath={setCopyStreamIdsForPath}
|
||||||
|
onExtractAllStreamsPress={extractAllStreams}
|
||||||
|
onExtractStreamPress={extractSingleStream}
|
||||||
|
areWeCutting={areWeCutting}
|
||||||
|
shortestFlag={shortestFlag}
|
||||||
|
setShortestFlag={setShortestFlag}
|
||||||
|
nonCopiedExtraStreams={nonCopiedExtraStreams}
|
||||||
|
customTagsByFile={customTagsByFile}
|
||||||
|
setCustomTagsByFile={setCustomTagsByFile}
|
||||||
|
customTagsByStreamId={customTagsByStreamId}
|
||||||
|
setCustomTagsByStreamId={setCustomTagsByStreamId}
|
||||||
|
dispositionByStreamId={dispositionByStreamId}
|
||||||
|
setDispositionByStreamId={setDispositionByStreamId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</Sheet>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="no-user-select" style={bottomStyle}>
|
<ExportConfirm filePath={filePath} areWeCutting={areWeCutting} nonFilteredSegmentsOrInverse={nonFilteredSegmentsOrInverse} selectedSegments={selectedSegmentsOrInverse} segmentsToExport={segmentsToExport} 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} mainCopiedThumbnailStreams={mainCopiedThumbnailStreams} needSmartCut={needSmartCut} />
|
||||||
<Timeline
|
|
||||||
shouldShowKeyframes={shouldShowKeyframes}
|
<LastCommandsSheet
|
||||||
waveforms={waveforms}
|
visible={lastCommandsVisible}
|
||||||
shouldShowWaveform={shouldShowWaveform}
|
onTogglePress={toggleLastCommands}
|
||||||
waveformEnabled={waveformEnabled}
|
ffmpegCommandLog={ffmpegCommandLog}
|
||||||
showThumbnails={showThumbnails}
|
|
||||||
neighbouringKeyFrames={neighbouringKeyFrames}
|
|
||||||
thumbnails={thumbnailsSorted}
|
|
||||||
playerTime={playerTime}
|
|
||||||
commandedTime={commandedTime}
|
|
||||||
relevantTime={relevantTime}
|
|
||||||
getRelevantTime={getRelevantTime}
|
|
||||||
commandedTimeRef={commandedTimeRef}
|
|
||||||
startTimeOffset={startTimeOffset}
|
|
||||||
zoom={zoom}
|
|
||||||
seekAbs={userSeekAbs}
|
|
||||||
durationSafe={durationSafe}
|
|
||||||
apparentCutSegments={apparentCutSegments}
|
|
||||||
setCurrentSegIndex={setCurrentSegIndex}
|
|
||||||
currentSegIndexSafe={currentSegIndexSafe}
|
|
||||||
inverseCutSegments={inverseCutSegments}
|
|
||||||
formatTimecode={formatTimecode}
|
|
||||||
onZoomWindowStartTimeChange={setZoomWindowStartTime}
|
|
||||||
playing={playing}
|
|
||||||
isFileOpened={isFileOpened}
|
|
||||||
onWheel={onTimelineWheel}
|
|
||||||
goToTimecode={goToTimecode}
|
|
||||||
isSegmentSelected={isSegmentSelected}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<BottomBar
|
<Sheet visible={settingsVisible} onClosePress={toggleSettings} style={{ padding: '1em 0' }}>
|
||||||
zoom={zoom}
|
<Settings
|
||||||
setZoom={setZoom}
|
onTunerRequested={onTunerRequested}
|
||||||
timelineToggleComfortZoom={timelineToggleComfortZoom}
|
onKeyboardShortcutsDialogRequested={toggleKeyboardShortcuts}
|
||||||
hasVideo={hasVideo}
|
askForCleanupChoices={askForCleanupChoices}
|
||||||
isRotationSet={isRotationSet}
|
toggleStoreProjectInWorkingDir={toggleStoreProjectInWorkingDir}
|
||||||
rotation={rotation}
|
|
||||||
areWeCutting={areWeCutting}
|
|
||||||
increaseRotation={increaseRotation}
|
|
||||||
cleanupFilesDialog={cleanupFilesDialog}
|
|
||||||
captureSnapshot={captureSnapshot}
|
|
||||||
onExportPress={onExportPress}
|
|
||||||
segmentsToExport={segmentsToExport}
|
|
||||||
seekAbs={userSeekAbs}
|
|
||||||
currentSegIndexSafe={currentSegIndexSafe}
|
|
||||||
cutSegments={cutSegments}
|
|
||||||
currentCutSeg={currentCutSeg}
|
|
||||||
selectedSegments={selectedSegments}
|
|
||||||
setCutStart={setCutStart}
|
|
||||||
setCutEnd={setCutEnd}
|
|
||||||
setCurrentSegIndex={setCurrentSegIndex}
|
|
||||||
jumpCutEnd={jumpCutEnd}
|
|
||||||
jumpCutStart={jumpCutStart}
|
|
||||||
jumpTimelineStart={jumpTimelineStart}
|
|
||||||
jumpTimelineEnd={jumpTimelineEnd}
|
|
||||||
startTimeOffset={startTimeOffset}
|
|
||||||
setCutTime={setCutTime}
|
|
||||||
currentApparentCutSeg={currentApparentCutSeg}
|
|
||||||
playing={playing}
|
|
||||||
shortStep={shortStep}
|
|
||||||
seekClosestKeyframe={seekClosestKeyframe}
|
|
||||||
togglePlay={togglePlay}
|
|
||||||
showThumbnails={showThumbnails}
|
|
||||||
toggleEnableThumbnails={toggleEnableThumbnails}
|
|
||||||
toggleWaveformMode={toggleWaveformMode}
|
|
||||||
waveformMode={waveformMode}
|
|
||||||
hasAudio={hasAudio}
|
|
||||||
keyframesEnabled={keyframesEnabled}
|
|
||||||
toggleKeyframesEnabled={toggleKeyframesEnabled}
|
|
||||||
detectedFps={detectedFps}
|
|
||||||
toggleLoopSelectedSegments={toggleLoopSelectedSegments}
|
|
||||||
isFileOpened={isFileOpened}
|
|
||||||
darkMode={darkMode}
|
|
||||||
setDarkMode={setDarkMode}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Sheet visible={streamsSelectorShown} onClosePress={() => setStreamsSelectorShown(false)} style={{ padding: '1em 0' }}>
|
|
||||||
{mainStreams && (
|
|
||||||
<StreamsSelector
|
|
||||||
mainFilePath={filePath}
|
|
||||||
mainFileFormatData={mainFileFormatData}
|
|
||||||
mainFileChapters={mainFileChapters}
|
|
||||||
allFilesMeta={allFilesMeta}
|
|
||||||
externalFilesMeta={externalFilesMeta}
|
|
||||||
setExternalFilesMeta={setExternalFilesMeta}
|
|
||||||
showAddStreamSourceDialog={showAddStreamSourceDialog}
|
|
||||||
mainFileStreams={mainStreams}
|
|
||||||
isCopyingStreamId={isCopyingStreamId}
|
|
||||||
toggleCopyStreamId={toggleCopyStreamId}
|
|
||||||
setCopyStreamIdsForPath={setCopyStreamIdsForPath}
|
|
||||||
onExtractAllStreamsPress={extractAllStreams}
|
|
||||||
onExtractStreamPress={extractSingleStream}
|
|
||||||
areWeCutting={areWeCutting}
|
|
||||||
shortestFlag={shortestFlag}
|
|
||||||
setShortestFlag={setShortestFlag}
|
|
||||||
nonCopiedExtraStreams={nonCopiedExtraStreams}
|
|
||||||
customTagsByFile={customTagsByFile}
|
|
||||||
setCustomTagsByFile={setCustomTagsByFile}
|
|
||||||
customTagsByStreamId={customTagsByStreamId}
|
|
||||||
setCustomTagsByStreamId={setCustomTagsByStreamId}
|
|
||||||
dispositionByStreamId={dispositionByStreamId}
|
|
||||||
setDispositionByStreamId={setDispositionByStreamId}
|
|
||||||
/>
|
/>
|
||||||
)}
|
</Sheet>
|
||||||
</Sheet>
|
|
||||||
|
|
||||||
<ExportConfirm filePath={filePath} areWeCutting={areWeCutting} nonFilteredSegmentsOrInverse={nonFilteredSegmentsOrInverse} selectedSegments={selectedSegmentsOrInverse} segmentsToExport={segmentsToExport} 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} mainCopiedThumbnailStreams={mainCopiedThumbnailStreams} needSmartCut={needSmartCut} />
|
<ConcatDialog isShown={batchFiles.length > 0 && concatDialogVisible} onHide={() => setConcatDialogVisible(false)} paths={batchFilePaths} onConcat={userConcatFiles} setAlwaysConcatMultipleFiles={setAlwaysConcatMultipleFiles} alwaysConcatMultipleFiles={alwaysConcatMultipleFiles} />
|
||||||
|
|
||||||
<LastCommandsSheet
|
<KeyboardShortcuts isShown={keyboardShortcutsVisible} onHide={() => setKeyboardShortcutsVisible(false)} keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} />
|
||||||
visible={lastCommandsVisible}
|
</div>
|
||||||
onTogglePress={toggleLastCommands}
|
</ThemeProvider>
|
||||||
ffmpegCommandLog={ffmpegCommandLog}
|
</UserSettingsContext.Provider>
|
||||||
/>
|
</SegColorsContext.Provider>
|
||||||
|
|
||||||
<Sheet visible={settingsVisible} onClosePress={toggleSettings} style={{ padding: '1em 0' }}>
|
|
||||||
<Settings
|
|
||||||
onTunerRequested={onTunerRequested}
|
|
||||||
onKeyboardShortcutsDialogRequested={toggleKeyboardShortcuts}
|
|
||||||
askForCleanupChoices={askForCleanupChoices}
|
|
||||||
toggleStoreProjectInWorkingDir={toggleStoreProjectInWorkingDir}
|
|
||||||
/>
|
|
||||||
</Sheet>
|
|
||||||
|
|
||||||
<ConcatDialog isShown={batchFiles.length > 0 && concatDialogVisible} onHide={() => setConcatDialogVisible(false)} paths={batchFilePaths} onConcat={userConcatFiles} setAlwaysConcatMultipleFiles={setAlwaysConcatMultipleFiles} alwaysConcatMultipleFiles={alwaysConcatMultipleFiles} />
|
|
||||||
|
|
||||||
<KeyboardShortcuts isShown={keyboardShortcutsVisible} onHide={() => setKeyboardShortcutsVisible(false)} keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} />
|
|
||||||
</div>
|
|
||||||
</ThemeProvider>
|
|
||||||
</UserSettingsContext.Provider>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -18,7 +18,8 @@ import Select from './components/Select';
|
|||||||
import SimpleModeButton from './components/SimpleModeButton';
|
import SimpleModeButton from './components/SimpleModeButton';
|
||||||
import { withBlur, mirrorTransform, checkAppPath } from './util';
|
import { withBlur, mirrorTransform, checkAppPath } from './util';
|
||||||
import { toast } from './swal';
|
import { toast } from './swal';
|
||||||
import { getSegColor } from './util/colors';
|
import { getSegColor as getSegColorRaw } from './util/colors';
|
||||||
|
import { useSegColors } from './contexts';
|
||||||
import { formatDuration, parseDuration, isExactDurationMatch } from './util/duration';
|
import { formatDuration, parseDuration, isExactDurationMatch } from './util/duration';
|
||||||
import useUserSettings from './hooks/useUserSettings';
|
import useUserSettings from './hooks/useUserSettings';
|
||||||
|
|
||||||
@ -31,6 +32,7 @@ const leftRightWidth = 100;
|
|||||||
|
|
||||||
const CutTimeInput = memo(({ darkMode, cutTime, setCutTime, startTimeOffset, seekAbs, currentCutSeg, currentApparentCutSeg, isStart }) => {
|
const CutTimeInput = memo(({ darkMode, cutTime, setCutTime, startTimeOffset, seekAbs, currentCutSeg, currentApparentCutSeg, isStart }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { getSegColor } = useSegColors();
|
||||||
|
|
||||||
const [cutTimeManual, setCutTimeManual] = useState();
|
const [cutTimeManual, setCutTimeManual] = useState();
|
||||||
|
|
||||||
@ -41,8 +43,10 @@ const CutTimeInput = memo(({ darkMode, cutTime, setCutTime, startTimeOffset, see
|
|||||||
|
|
||||||
const isCutTimeManualSet = () => cutTimeManual !== undefined;
|
const isCutTimeManualSet = () => cutTimeManual !== undefined;
|
||||||
|
|
||||||
const segColor = getSegColor(currentCutSeg);
|
const border = useMemo(() => {
|
||||||
const border = `.1em solid ${darkMode ? segColor.desaturate(0.9).lightness(50).string() : segColor.desaturate(0.7).lightness(60).string()}`;
|
const segColor = getSegColor(currentCutSeg);
|
||||||
|
return `.1em solid ${darkMode ? segColor.desaturate(0.4).lightness(50).string() : segColor.desaturate(0.2).lightness(60).string()}`;
|
||||||
|
}, [currentCutSeg, darkMode, getSegColor]);
|
||||||
|
|
||||||
const cutTimeInputStyle = {
|
const cutTimeInputStyle = {
|
||||||
border, borderRadius: 5, backgroundColor: 'var(--gray5)', transition: darkModeTransition, fontSize: 13, textAlign: 'center', padding: '1px 5px', marginTop: 0, marginBottom: 0, marginLeft: isStart ? 0 : 5, marginRight: isStart ? 5 : 0, boxSizing: 'border-box', fontFamily: 'inherit', width: 90, outline: 'none',
|
border, borderRadius: 5, backgroundColor: 'var(--gray5)', transition: darkModeTransition, fontSize: 13, textAlign: 'center', padding: '1px 5px', marginTop: 0, marginBottom: 0, marginLeft: isStart ? 0 : 5, marginRight: isStart ? 5 : 0, boxSizing: 'border-box', fontFamily: 'inherit', width: 90, outline: 'none',
|
||||||
@ -142,6 +146,7 @@ const BottomBar = memo(({
|
|||||||
toggleEnableThumbnails, toggleWaveformMode, waveformMode, showThumbnails,
|
toggleEnableThumbnails, toggleWaveformMode, waveformMode, showThumbnails,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { getSegColor } = useSegColors();
|
||||||
|
|
||||||
// ok this is a bit over-engineered but what the hell!
|
// ok this is a bit over-engineered but what the hell!
|
||||||
const loopSelectedSegmentsButtonStyle = useMemo(() => {
|
const loopSelectedSegmentsButtonStyle = useMemo(() => {
|
||||||
@ -149,7 +154,7 @@ const BottomBar = memo(({
|
|||||||
const selectedSegmentsSafe = (selectedSegments.length > 1 ? selectedSegments : [selectedSegments[0], selectedSegments[0]]).slice(0, 10);
|
const selectedSegmentsSafe = (selectedSegments.length > 1 ? selectedSegments : [selectedSegments[0], selectedSegments[0]]).slice(0, 10);
|
||||||
|
|
||||||
const gradientColors = selectedSegmentsSafe.map((seg, i) => {
|
const gradientColors = selectedSegmentsSafe.map((seg, i) => {
|
||||||
const segColor = getSegColor(seg);
|
const segColor = getSegColorRaw(seg);
|
||||||
// make colors stronger, the more segments
|
// make colors stronger, the more segments
|
||||||
return `${segColor.alpha(Math.max(0.4, Math.min(0.8, selectedSegmentsSafe.length / 3))).string()} ${((i / (selectedSegmentsSafe.length - 1)) * 100).toFixed(1)}%`;
|
return `${segColor.alpha(Math.max(0.4, Math.min(0.8, selectedSegmentsSafe.length / 3))).string()} ${((i / (selectedSegmentsSafe.length - 1)) * 100).toFixed(1)}%`;
|
||||||
}).join(', ');
|
}).join(', ');
|
||||||
@ -191,7 +196,7 @@ const BottomBar = memo(({
|
|||||||
const newIndex = currentSegIndexSafe + direction;
|
const newIndex = currentSegIndexSafe + direction;
|
||||||
const seg = cutSegments[newIndex];
|
const seg = cutSegments[newIndex];
|
||||||
|
|
||||||
const backgroundColor = seg && getSegColor(seg).desaturate(0.9).lightness(darkMode ? 35 : 55).string();
|
const backgroundColor = seg && getSegColor(seg).desaturate(0.6).lightness(darkMode ? 35 : 55).string();
|
||||||
const opacity = seg ? undefined : 0.5;
|
const opacity = seg ? undefined : 0.5;
|
||||||
const text = seg ? `${newIndex + 1}` : '-';
|
const text = seg ? `${newIndex + 1}` : '-';
|
||||||
const wide = text.length > 1;
|
const wide = text.length > 1;
|
||||||
|
@ -12,7 +12,7 @@ import Swal from './swal';
|
|||||||
import useContextMenu from './hooks/useContextMenu';
|
import useContextMenu from './hooks/useContextMenu';
|
||||||
import useUserSettings from './hooks/useUserSettings';
|
import useUserSettings from './hooks/useUserSettings';
|
||||||
import { saveColor, controlsBackground, primaryTextColor, darkModeTransition } from './colors';
|
import { saveColor, controlsBackground, primaryTextColor, darkModeTransition } from './colors';
|
||||||
import { getSegColor } from './util/colors';
|
import { useSegColors } from './contexts';
|
||||||
import { mySpring } from './animations';
|
import { mySpring } from './animations';
|
||||||
|
|
||||||
const buttonBaseStyle = {
|
const buttonBaseStyle = {
|
||||||
@ -24,6 +24,7 @@ const neutralButtonColor = 'var(--gray8)';
|
|||||||
|
|
||||||
const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, getFrameCount, updateOrder, invertCutSegments, onClick, onRemovePress, onRemoveSelected, onLabelSelectedSegments, onReorderPress, onLabelPress, selected, onSelectSingleSegment, onToggleSegmentSelected, onDeselectAllSegments, onSelectSegmentsByLabel, onSelectAllSegments, jumpSegStart, jumpSegEnd, addSegment, onViewSegmentTags, onExtractSegmentFramesAsImages, onInvertSelectedSegments }) => {
|
const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, getFrameCount, updateOrder, invertCutSegments, onClick, onRemovePress, onRemoveSelected, onLabelSelectedSegments, onReorderPress, onLabelPress, selected, onSelectSingleSegment, onToggleSegmentSelected, onDeselectAllSegments, onSelectSegmentsByLabel, onSelectAllSegments, jumpSegStart, jumpSegEnd, addSegment, onViewSegmentTags, onExtractSegmentFramesAsImages, onInvertSelectedSegments }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { getSegColor } = useSegColors();
|
||||||
|
|
||||||
const ref = useRef();
|
const ref = useRef();
|
||||||
|
|
||||||
@ -81,7 +82,7 @@ const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, g
|
|||||||
|
|
||||||
const segColor = getSegColor(seg);
|
const segColor = getSegColor(seg);
|
||||||
|
|
||||||
const color = segColor.desaturate(0.75).lightness(darkMode ? 35 : 55);
|
const color = segColor.desaturate(0.25).lightness(darkMode ? 35 : 55);
|
||||||
const borderColor = darkMode ? color.lighten(0.5) : color.darken(0.3);
|
const borderColor = darkMode ? color.lighten(0.5) : color.darken(0.3);
|
||||||
|
|
||||||
return <b style={{ cursor: 'grab', color: 'white', padding: '0 4px', marginRight: 3, marginLeft: -3, background: color.string(), border: `1px solid ${isActive ? borderColor.string() : 'transparent'}`, borderRadius: 10, fontSize: 12 }}>{index + 1}</b>;
|
return <b style={{ cursor: 'grab', color: 'white', padding: '0 4px', marginRight: 3, marginLeft: -3, background: color.string(), border: `1px solid ${isActive ? borderColor.string() : 'transparent'}`, borderRadius: 10, fontSize: 12 }}>{index + 1}</b>;
|
||||||
@ -154,6 +155,7 @@ const SegmentList = memo(({
|
|||||||
jumpSegStart, jumpSegEnd, onViewSegmentTags,
|
jumpSegStart, jumpSegEnd, onViewSegmentTags,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { getSegColor } = useSegColors();
|
||||||
|
|
||||||
const { invertCutSegments, simpleMode, darkMode } = useUserSettings();
|
const { invertCutSegments, simpleMode, darkMode } = useUserSettings();
|
||||||
|
|
||||||
@ -198,7 +200,7 @@ const SegmentList = memo(({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderFooter() {
|
function renderFooter() {
|
||||||
const getButtonColor = (seg) => getSegColor(seg).desaturate(0.8).lightness(darkMode ? 45 : 55).string();
|
const getButtonColor = (seg) => getSegColor(seg).desaturate(0.3).lightness(darkMode ? 45 : 55).string();
|
||||||
const currentSegColor = getButtonColor(currentCutSeg);
|
const currentSegColor = getButtonColor(currentCutSeg);
|
||||||
const segAtCursorColor = getButtonColor(segmentAtCursor);
|
const segAtCursorColor = getButtonColor(segmentAtCursor);
|
||||||
|
|
||||||
|
@ -12,8 +12,6 @@ import useUserSettings from './hooks/useUserSettings';
|
|||||||
|
|
||||||
import { timelineBackground, darkModeTransition } from './colors';
|
import { timelineBackground, darkModeTransition } from './colors';
|
||||||
|
|
||||||
import { getSegColor } from './util/colors';
|
|
||||||
|
|
||||||
const currentTimeWidth = 1;
|
const currentTimeWidth = 1;
|
||||||
|
|
||||||
const Waveform = memo(({ waveform, calculateTimelinePercent, durationSafe }) => {
|
const Waveform = memo(({ waveform, calculateTimelinePercent, durationSafe }) => {
|
||||||
@ -295,8 +293,6 @@ const Timeline = memo(({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{apparentCutSegments.map((seg, i) => {
|
{apparentCutSegments.map((seg, i) => {
|
||||||
const segColor = getSegColor(seg);
|
|
||||||
|
|
||||||
if (seg.start === 0 && seg.end === 0) return null; // No video loaded
|
if (seg.start === 0 && seg.end === 0) return null; // No video loaded
|
||||||
|
|
||||||
const selected = invertCutSegments || isSegmentSelected({ segId: seg.segId });
|
const selected = invertCutSegments || isSegmentSelected({ segId: seg.segId });
|
||||||
@ -304,14 +300,11 @@ const Timeline = memo(({
|
|||||||
return (
|
return (
|
||||||
<TimelineSeg
|
<TimelineSeg
|
||||||
key={seg.segId}
|
key={seg.segId}
|
||||||
|
seg={seg}
|
||||||
segNum={i}
|
segNum={i}
|
||||||
segColor={segColor}
|
|
||||||
onSegClick={setCurrentSegIndex}
|
onSegClick={setCurrentSegIndex}
|
||||||
isActive={i === currentSegIndexSafe}
|
isActive={i === currentSegIndexSafe}
|
||||||
duration={durationSafe}
|
duration={durationSafe}
|
||||||
name={seg.name}
|
|
||||||
cutStart={seg.start}
|
|
||||||
cutEnd={seg.end}
|
|
||||||
invertCutSegments={invertCutSegments}
|
invertCutSegments={invertCutSegments}
|
||||||
formatTimecode={formatTimecode}
|
formatTimecode={formatTimecode}
|
||||||
selected={selected}
|
selected={selected}
|
||||||
|
@ -4,13 +4,18 @@ import { FaTrashAlt } from 'react-icons/fa';
|
|||||||
|
|
||||||
import { mySpring } from './animations';
|
import { mySpring } from './animations';
|
||||||
import useUserSettings from './hooks/useUserSettings';
|
import useUserSettings from './hooks/useUserSettings';
|
||||||
|
import { useSegColors } from './contexts';
|
||||||
|
|
||||||
|
|
||||||
const TimelineSeg = memo(({
|
const TimelineSeg = memo(({
|
||||||
duration, cutStart, cutEnd, isActive, segNum, name,
|
seg, duration, isActive, segNum, onSegClick, invertCutSegments, formatTimecode, selected,
|
||||||
onSegClick, invertCutSegments, segColor, formatTimecode, selected,
|
|
||||||
}) => {
|
}) => {
|
||||||
const { darkMode } = useUserSettings();
|
const { darkMode } = useUserSettings();
|
||||||
|
const { getSegColor } = useSegColors();
|
||||||
|
|
||||||
|
const segColor = useMemo(() => getSegColor(seg), [getSegColor, seg]);
|
||||||
|
|
||||||
|
const { name, start: cutStart, end: cutEnd } = seg;
|
||||||
|
|
||||||
const cutSectionWidth = `${((cutEnd - cutStart) / duration) * 100}%`;
|
const cutSectionWidth = `${((cutEnd - cutStart) / duration) * 100}%`;
|
||||||
|
|
||||||
@ -18,13 +23,13 @@ const TimelineSeg = memo(({
|
|||||||
|
|
||||||
const markerBorder = useMemo(() => {
|
const markerBorder = useMemo(() => {
|
||||||
if (!isActive) return '2px solid transparent';
|
if (!isActive) return '2px solid transparent';
|
||||||
return `2px solid ${(darkMode ? segColor.desaturate(0.6).lightness(70) : segColor.desaturate(0.9).lightness(20)).string()}`;
|
return `2px solid ${darkMode ? segColor.desaturate(0.1).lightness(70).string() : segColor.desaturate(0.2).lightness(40).string()}`;
|
||||||
}, [darkMode, isActive, segColor]);
|
}, [darkMode, isActive, segColor]);
|
||||||
|
|
||||||
const backgroundColor = useMemo(() => {
|
const backgroundColor = useMemo(() => {
|
||||||
if (invertCutSegments || !selected) return darkMode ? segColor.desaturate(0.9).lightness(25).string() : segColor.desaturate(0.9).lightness(80).string();
|
if (invertCutSegments || !selected) return darkMode ? segColor.desaturate(0.4).lightness(25).string() : segColor.desaturate(0.4).lightness(83).string();
|
||||||
if (isActive) return darkMode ? segColor.desaturate(0.7).lightness(50).string() : segColor.desaturate(0.7).lightness(40).string();
|
if (isActive) return darkMode ? segColor.desaturate(0.2).lightness(50).string() : segColor.desaturate(0.2).lightness(60).string();
|
||||||
return darkMode ? segColor.desaturate(0.7).lightness(40).string() : segColor.desaturate(0.9).lightness(55).string();
|
return darkMode ? segColor.desaturate(0.2).lightness(40).string() : segColor.desaturate(0.5).lightness(65).string();
|
||||||
}, [darkMode, invertCutSegments, isActive, segColor, selected]);
|
}, [darkMode, invertCutSegments, isActive, segColor, selected]);
|
||||||
const markerBorderRadius = 5;
|
const markerBorderRadius = 5;
|
||||||
|
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
import { getSegColor } from '../util/colors';
|
import { useSegColors } from '../contexts';
|
||||||
import useUserSettings from '../hooks/useUserSettings';
|
import useUserSettings from '../hooks/useUserSettings';
|
||||||
|
|
||||||
const SegmentCutpointButton = ({ currentCutSeg, side, Icon, onClick, title, style }) => {
|
const SegmentCutpointButton = ({ currentCutSeg, side, Icon, onClick, title, style }) => {
|
||||||
const { darkMode } = useUserSettings();
|
const { darkMode } = useUserSettings();
|
||||||
const segColor = getSegColor(currentCutSeg);
|
const { getSegColor } = useSegColors();
|
||||||
|
const segColor = useMemo(() => getSegColor(currentCutSeg), [currentCutSeg, getSegColor]);
|
||||||
|
|
||||||
const start = side === 'start';
|
const start = side === 'start';
|
||||||
const border = `3px solid ${segColor.desaturate(0.9).lightness(darkMode ? 45 : 35).string()}`;
|
const border = `3px solid ${segColor.desaturate(0.6).lightness(darkMode ? 45 : 35).string()}`;
|
||||||
const backgroundColor = segColor.desaturate(0.9).lightness(darkMode ? 35 : 55).string();
|
const backgroundColor = segColor.desaturate(0.6).lightness(darkMode ? 35 : 55).string();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Icon
|
<Icon
|
||||||
|
@ -38,7 +38,7 @@ const Settings = memo(({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { customOutDir, changeOutDir, keyframeCut, toggleKeyframeCut, timecodeFormat, setTimecodeFormat, invertCutSegments, setInvertCutSegments, askBeforeClose, setAskBeforeClose, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, autoSaveProjectFile, setAutoSaveProjectFile, invertTimelineScroll, setInvertTimelineScroll, language, setLanguage, ffmpegExperimental, setFfmpegExperimental, hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode, enableTransferTimestamps, setEnableTransferTimestamps, enableAutoHtml5ify, setEnableAutoHtml5ify, customFfPath, setCustomFfPath, storeProjectInWorkingDir, enableOverwriteOutput, setEnableOverwriteOutput, mouseWheelZoomModifierKey, setMouseWheelZoomModifierKey, captureFrameMethod, setCaptureFrameMethod, captureFrameQuality, setCaptureFrameQuality, captureFrameFileNameFormat, setCaptureFrameFileNameFormat, enableNativeHevc, setEnableNativeHevc, enableUpdateCheck, setEnableUpdateCheck, allowMultipleInstances, setAllowMultipleInstances } = useUserSettings();
|
const { customOutDir, changeOutDir, keyframeCut, toggleKeyframeCut, timecodeFormat, setTimecodeFormat, invertCutSegments, setInvertCutSegments, askBeforeClose, setAskBeforeClose, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, autoSaveProjectFile, setAutoSaveProjectFile, invertTimelineScroll, setInvertTimelineScroll, language, setLanguage, ffmpegExperimental, setFfmpegExperimental, hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode, enableTransferTimestamps, setEnableTransferTimestamps, enableAutoHtml5ify, setEnableAutoHtml5ify, customFfPath, setCustomFfPath, storeProjectInWorkingDir, enableOverwriteOutput, setEnableOverwriteOutput, mouseWheelZoomModifierKey, setMouseWheelZoomModifierKey, captureFrameMethod, setCaptureFrameMethod, captureFrameQuality, setCaptureFrameQuality, captureFrameFileNameFormat, setCaptureFrameFileNameFormat, enableNativeHevc, setEnableNativeHevc, enableUpdateCheck, setEnableUpdateCheck, allowMultipleInstances, setAllowMultipleInstances, preferStrongColors, setPreferStrongColors } = useUserSettings();
|
||||||
|
|
||||||
const onLangChange = useCallback((e) => {
|
const onLangChange = useCallback((e) => {
|
||||||
const { value } = e.target;
|
const { value } = e.target;
|
||||||
@ -311,6 +311,15 @@ const Settings = memo(({
|
|||||||
</td>
|
</td>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
<Header title={t('User interface')} />
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<KeyCell>{t('Prefer strong colors')}</KeyCell>
|
||||||
|
<td>
|
||||||
|
<Switch checked={preferStrongColors} onCheckedChange={setPreferStrongColors} />
|
||||||
|
</td>
|
||||||
|
</Row>
|
||||||
|
|
||||||
<Header title={t('Advanced options')} />
|
<Header title={t('Advanced options')} />
|
||||||
|
|
||||||
{!isMasBuild && (
|
{!isMasBuild && (
|
||||||
|
6
src/contexts.js
Normal file
6
src/contexts.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
|
||||||
|
export const UserSettingsContext = React.createContext();
|
||||||
|
export const SegColorsContext = React.createContext();
|
||||||
|
|
||||||
|
export const useSegColors = () => useContext(SegColorsContext);
|
@ -1,3 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
export default React.createContext();
|
|
@ -1,5 +1,5 @@
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
|
|
||||||
import UserSettingsContext from '../contexts/UserSettingsContext';
|
import { UserSettingsContext } from '../contexts';
|
||||||
|
|
||||||
export default () => useContext(UserSettingsContext);
|
export default () => useContext(UserSettingsContext);
|
||||||
|
@ -135,6 +135,8 @@ export default () => {
|
|||||||
useEffect(() => safeSetConfig({ allowMultipleInstances }), [allowMultipleInstances]);
|
useEffect(() => safeSetConfig({ allowMultipleInstances }), [allowMultipleInstances]);
|
||||||
const [darkMode, setDarkMode] = useState(safeGetConfigInitial('darkMode'));
|
const [darkMode, setDarkMode] = useState(safeGetConfigInitial('darkMode'));
|
||||||
useEffect(() => safeSetConfig({ darkMode }), [darkMode]);
|
useEffect(() => safeSetConfig({ darkMode }), [darkMode]);
|
||||||
|
const [preferStrongColors, setPreferStrongColors] = useState(safeGetConfigInitial('preferStrongColors'));
|
||||||
|
useEffect(() => safeSetConfig({ preferStrongColors }), [preferStrongColors]);
|
||||||
|
|
||||||
|
|
||||||
const resetKeyBindings = useCallback(() => {
|
const resetKeyBindings = useCallback(() => {
|
||||||
@ -248,5 +250,7 @@ export default () => {
|
|||||||
setAllowMultipleInstances,
|
setAllowMultipleInstances,
|
||||||
darkMode,
|
darkMode,
|
||||||
setDarkMode,
|
setDarkMode,
|
||||||
|
preferStrongColors,
|
||||||
|
setPreferStrongColors,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user