1
0
mirror of https://github.com/mifi/lossless-cut.git synced 2024-11-22 10:22:31 +01:00

add preference for color intensity

https://github.com/mifi/lossless-cut/discussions/1507
This commit is contained in:
Mikael Finstad 2023-04-03 09:27:30 +09:00
parent 10f8092d15
commit 0afa76a165
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26
12 changed files with 316 additions and 283 deletions

View File

@ -123,6 +123,7 @@ const defaults = {
},
allowMultipleInstances: false,
darkMode: true,
preferStrongColors: false,
};
// For portable app: https://github.com/mifi/lossless-cut/issues/645

View File

@ -26,7 +26,7 @@ import useFrameCapture from './hooks/useFrameCapture';
import useSegments from './hooks/useSegments';
import useDirectoryAccess, { DirectoryAccessDeclinedError } from './hooks/useDirectoryAccess';
import UserSettingsContext from './contexts/UserSettingsContext';
import { UserSettingsContext, SegColorsContext } from './contexts';
import NoFileLoaded from './NoFileLoaded';
import Canvas from './Canvas';
@ -50,6 +50,7 @@ import OutputFormatSelect from './components/OutputFormatSelect';
import { loadMifiLink, runStartupCheck } from './mifi';
import { controlsBackground, darkModeTransition } from './colors';
import { getSegColor } from './util/colors';
import {
getStreamFps, isCuttingStart, isCuttingEnd,
readFileMeta, getSmarterOutFormat, renderThumbnails as ffmpegRenderThumbnails,
@ -175,7 +176,7 @@ const App = memo(() => {
const allUserSettings = useUserSettingsRoot();
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;
useEffect(() => {
@ -510,6 +511,13 @@ const App = memo(() => {
...allUserSettings, toggleCaptureFormat, changeOutDir, toggleKeyframeCut, togglePreserveMovData, toggleMovFastStart, toggleExportConfirmEnabled, toggleSegmentsToChapters, togglePreserveMetadataOnMerge, toggleSimpleMode, toggleSafeOutputFileName, effectiveExportMode,
}), [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) => (
!!(copyStreamIdsByFile[path] || {})[streamId]
), [copyStreamIdsByFile]);
@ -2153,269 +2161,271 @@ const App = memo(() => {
// throw new Error('Test error boundary');
return (
<UserSettingsContext.Provider value={userSettingsContext}>
<ThemeProvider value={theme}>
<div className={darkMode ? 'dark-theme' : undefined} style={{ display: 'flex', flexDirection: 'column', height: '100vh', color: 'var(--gray12)', background: 'var(--gray1)', transition: darkModeTransition }}>
<TopMenu
filePath={filePath}
fileFormat={fileFormat}
copyAnyAudioTrack={copyAnyAudioTrack}
toggleStripAudio={toggleStripAudio}
clearOutDir={clearOutDir}
isCustomFormatSelected={isCustomFormatSelected}
renderOutFmt={renderOutFmt}
toggleSettings={toggleSettings}
numStreamsToCopy={numStreamsToCopy}
numStreamsTotal={numStreamsTotal}
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>
)}
<SegColorsContext.Provider value={segColorsContext}>
<UserSettingsContext.Provider value={userSettingsContext}>
<ThemeProvider value={theme}>
<div className={darkMode ? 'dark-theme' : undefined} style={{ display: 'flex', flexDirection: 'column', height: '100vh', color: 'var(--gray12)', background: 'var(--gray1)', transition: darkModeTransition }}>
<TopMenu
filePath={filePath}
fileFormat={fileFormat}
copyAnyAudioTrack={copyAnyAudioTrack}
toggleStripAudio={toggleStripAudio}
clearOutDir={clearOutDir}
isCustomFormatSelected={isCustomFormatSelected}
renderOutFmt={renderOutFmt}
toggleSettings={toggleSettings}
numStreamsToCopy={numStreamsToCopy}
numStreamsTotal={numStreamsTotal}
setStreamsSelectorShown={setStreamsSelectorShown}
selectedSegments={selectedSegmentsOrInverse}
/>
<div style={{ flexGrow: 1, display: 'flex', overflowY: 'hidden' }}>
<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>
{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>
<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}
<div className="no-user-select" style={bottomStyle}>
<Timeline
shouldShowKeyframes={shouldShowKeyframes}
waveforms={waveforms}
shouldShowWaveform={shouldShowWaveform}
waveformEnabled={waveformEnabled}
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
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>
</div>
</Sheet>
<div className="no-user-select" style={bottomStyle}>
<Timeline
shouldShowKeyframes={shouldShowKeyframes}
waveforms={waveforms}
shouldShowWaveform={shouldShowWaveform}
waveformEnabled={waveformEnabled}
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}
<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} />
<LastCommandsSheet
visible={lastCommandsVisible}
onTogglePress={toggleLastCommands}
ffmpegCommandLog={ffmpegCommandLog}
/>
<BottomBar
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}
<Sheet visible={settingsVisible} onClosePress={toggleSettings} style={{ padding: '1em 0' }}>
<Settings
onTunerRequested={onTunerRequested}
onKeyboardShortcutsDialogRequested={toggleKeyboardShortcuts}
askForCleanupChoices={askForCleanupChoices}
toggleStoreProjectInWorkingDir={toggleStoreProjectInWorkingDir}
/>
)}
</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
visible={lastCommandsVisible}
onTogglePress={toggleLastCommands}
ffmpegCommandLog={ffmpegCommandLog}
/>
<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>
<KeyboardShortcuts isShown={keyboardShortcutsVisible} onHide={() => setKeyboardShortcutsVisible(false)} keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} />
</div>
</ThemeProvider>
</UserSettingsContext.Provider>
</SegColorsContext.Provider>
);
});

View File

@ -18,7 +18,8 @@ import Select from './components/Select';
import SimpleModeButton from './components/SimpleModeButton';
import { withBlur, mirrorTransform, checkAppPath } from './util';
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 useUserSettings from './hooks/useUserSettings';
@ -31,6 +32,7 @@ const leftRightWidth = 100;
const CutTimeInput = memo(({ darkMode, cutTime, setCutTime, startTimeOffset, seekAbs, currentCutSeg, currentApparentCutSeg, isStart }) => {
const { t } = useTranslation();
const { getSegColor } = useSegColors();
const [cutTimeManual, setCutTimeManual] = useState();
@ -41,8 +43,10 @@ const CutTimeInput = memo(({ darkMode, cutTime, setCutTime, startTimeOffset, see
const isCutTimeManualSet = () => cutTimeManual !== undefined;
const segColor = getSegColor(currentCutSeg);
const border = `.1em solid ${darkMode ? segColor.desaturate(0.9).lightness(50).string() : segColor.desaturate(0.7).lightness(60).string()}`;
const border = useMemo(() => {
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 = {
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,
}) => {
const { t } = useTranslation();
const { getSegColor } = useSegColors();
// ok this is a bit over-engineered but what the hell!
const loopSelectedSegmentsButtonStyle = useMemo(() => {
@ -149,7 +154,7 @@ const BottomBar = memo(({
const selectedSegmentsSafe = (selectedSegments.length > 1 ? selectedSegments : [selectedSegments[0], selectedSegments[0]]).slice(0, 10);
const gradientColors = selectedSegmentsSafe.map((seg, i) => {
const segColor = getSegColor(seg);
const segColor = getSegColorRaw(seg);
// 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)}%`;
}).join(', ');
@ -191,7 +196,7 @@ const BottomBar = memo(({
const newIndex = currentSegIndexSafe + direction;
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 text = seg ? `${newIndex + 1}` : '-';
const wide = text.length > 1;

View File

@ -12,7 +12,7 @@ import Swal from './swal';
import useContextMenu from './hooks/useContextMenu';
import useUserSettings from './hooks/useUserSettings';
import { saveColor, controlsBackground, primaryTextColor, darkModeTransition } from './colors';
import { getSegColor } from './util/colors';
import { useSegColors } from './contexts';
import { mySpring } from './animations';
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 { t } = useTranslation();
const { getSegColor } = useSegColors();
const ref = useRef();
@ -81,7 +82,7 @@ const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, g
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);
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,
}) => {
const { t } = useTranslation();
const { getSegColor } = useSegColors();
const { invertCutSegments, simpleMode, darkMode } = useUserSettings();
@ -198,7 +200,7 @@ const SegmentList = memo(({
}
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 segAtCursorColor = getButtonColor(segmentAtCursor);

View File

@ -12,8 +12,6 @@ import useUserSettings from './hooks/useUserSettings';
import { timelineBackground, darkModeTransition } from './colors';
import { getSegColor } from './util/colors';
const currentTimeWidth = 1;
const Waveform = memo(({ waveform, calculateTimelinePercent, durationSafe }) => {
@ -295,8 +293,6 @@ const Timeline = memo(({
)}
{apparentCutSegments.map((seg, i) => {
const segColor = getSegColor(seg);
if (seg.start === 0 && seg.end === 0) return null; // No video loaded
const selected = invertCutSegments || isSegmentSelected({ segId: seg.segId });
@ -304,14 +300,11 @@ const Timeline = memo(({
return (
<TimelineSeg
key={seg.segId}
seg={seg}
segNum={i}
segColor={segColor}
onSegClick={setCurrentSegIndex}
isActive={i === currentSegIndexSafe}
duration={durationSafe}
name={seg.name}
cutStart={seg.start}
cutEnd={seg.end}
invertCutSegments={invertCutSegments}
formatTimecode={formatTimecode}
selected={selected}

View File

@ -4,13 +4,18 @@ import { FaTrashAlt } from 'react-icons/fa';
import { mySpring } from './animations';
import useUserSettings from './hooks/useUserSettings';
import { useSegColors } from './contexts';
const TimelineSeg = memo(({
duration, cutStart, cutEnd, isActive, segNum, name,
onSegClick, invertCutSegments, segColor, formatTimecode, selected,
seg, duration, isActive, segNum, onSegClick, invertCutSegments, formatTimecode, selected,
}) => {
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}%`;
@ -18,13 +23,13 @@ const TimelineSeg = memo(({
const markerBorder = useMemo(() => {
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]);
const backgroundColor = useMemo(() => {
if (invertCutSegments || !selected) return darkMode ? segColor.desaturate(0.9).lightness(25).string() : segColor.desaturate(0.9).lightness(80).string();
if (isActive) return darkMode ? segColor.desaturate(0.7).lightness(50).string() : segColor.desaturate(0.7).lightness(40).string();
return darkMode ? segColor.desaturate(0.7).lightness(40).string() : segColor.desaturate(0.9).lightness(55).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.2).lightness(50).string() : segColor.desaturate(0.2).lightness(60).string();
return darkMode ? segColor.desaturate(0.2).lightness(40).string() : segColor.desaturate(0.5).lightness(65).string();
}, [darkMode, invertCutSegments, isActive, segColor, selected]);
const markerBorderRadius = 5;

View File

@ -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';
const SegmentCutpointButton = ({ currentCutSeg, side, Icon, onClick, title, style }) => {
const { darkMode } = useUserSettings();
const segColor = getSegColor(currentCutSeg);
const { getSegColor } = useSegColors();
const segColor = useMemo(() => getSegColor(currentCutSeg), [currentCutSeg, getSegColor]);
const start = side === 'start';
const border = `3px solid ${segColor.desaturate(0.9).lightness(darkMode ? 45 : 35).string()}`;
const backgroundColor = segColor.desaturate(0.9).lightness(darkMode ? 35 : 55).string();
const border = `3px solid ${segColor.desaturate(0.6).lightness(darkMode ? 45 : 35).string()}`;
const backgroundColor = segColor.desaturate(0.6).lightness(darkMode ? 35 : 55).string();
return (
<Icon

View File

@ -38,7 +38,7 @@ const Settings = memo(({
}) => {
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 { value } = e.target;
@ -311,6 +311,15 @@ const Settings = memo(({
</td>
</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')} />
{!isMasBuild && (

6
src/contexts.js Normal file
View 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);

View File

@ -1,3 +0,0 @@
import React from 'react';
export default React.createContext();

View File

@ -1,5 +1,5 @@
import { useContext } from 'react';
import UserSettingsContext from '../contexts/UserSettingsContext';
import { UserSettingsContext } from '../contexts';
export default () => useContext(UserSettingsContext);

View File

@ -135,6 +135,8 @@ export default () => {
useEffect(() => safeSetConfig({ allowMultipleInstances }), [allowMultipleInstances]);
const [darkMode, setDarkMode] = useState(safeGetConfigInitial('darkMode'));
useEffect(() => safeSetConfig({ darkMode }), [darkMode]);
const [preferStrongColors, setPreferStrongColors] = useState(safeGetConfigInitial('preferStrongColors'));
useEffect(() => safeSetConfig({ preferStrongColors }), [preferStrongColors]);
const resetKeyBindings = useCallback(() => {
@ -248,5 +250,7 @@ export default () => {
setAllowMultipleInstances,
darkMode,
setDarkMode,
preferStrongColors,
setPreferStrongColors,
};
};