Skip to content

Commit 096db54

Browse files
committed
allow custom smart cut bitrate
closes #1997 #1824 see #126
1 parent b5028dc commit 096db54

File tree

3 files changed

+46
-9
lines changed

3 files changed

+46
-9
lines changed

src/renderer/src/App.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ function App() {
165165
const [editingSegmentTagsSegmentIndex, setEditingSegmentTagsSegmentIndex] = useState<number>();
166166
const [editingSegmentTags, setEditingSegmentTags] = useState<SegmentTags>();
167167
const [mediaSourceQuality, setMediaSourceQuality] = useState(0);
168+
const [smartCutBitrate, setSmartCutBitrate] = useState<number | undefined>();
168169

169170
const incrementMediaSourceQuality = useCallback(() => setMediaSourceQuality((v) => (v + 1) % mediaSourceQualities.length), []);
170171

@@ -836,7 +837,7 @@ function App() {
836837

837838
const {
838839
concatFiles, html5ifyDummy, cutMultiple, autoConcatCutSegments, html5ify, fixInvalidDuration,
839-
} = useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, treatOutputFileModifiedTimeAsStart, needSmartCut, enableOverwriteOutput, outputPlaybackRate, cutFromAdjustmentFrames, appendLastCommandsLog });
840+
} = useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, treatOutputFileModifiedTimeAsStart, needSmartCut, enableOverwriteOutput, outputPlaybackRate, cutFromAdjustmentFrames, appendLastCommandsLog, smartCutCustomBitrate: smartCutBitrate });
840841

841842
const html5ifyAndLoad = useCallback(async (cod: string | undefined, fp: string, speed: Html5ifyMode, hv: boolean, ha: boolean) => {
842843
const usesDummyVideo = speed === 'fastest';
@@ -2733,7 +2734,7 @@ function App() {
27332734
/>
27342735
</div>
27352736

2736-
<ExportConfirm areWeCutting={areWeCutting} nonFilteredSegmentsOrInverse={nonFilteredSegmentsOrInverse} selectedSegments={selectedSegmentsOrInverse} segmentsToExport={segmentsToExport} willMerge={willMerge} visible={exportConfirmVisible} onClosePress={closeExportConfirm} onExportConfirm={onExportConfirm} renderOutFmt={renderOutFmt} outputDir={outputDir} numStreamsTotal={numStreamsTotal} numStreamsToCopy={numStreamsToCopy} onShowStreamsSelectorClick={handleShowStreamsSelectorClick} outFormat={fileFormat} setOutSegTemplate={setOutSegTemplate} outSegTemplate={outSegTemplateOrDefault} generateOutSegFileNames={generateOutSegFileNames} currentSegIndexSafe={currentSegIndexSafe} mainCopiedThumbnailStreams={mainCopiedThumbnailStreams} needSmartCut={needSmartCut} mergedOutFileName={mergedOutFileName} setMergedOutFileName={setMergedOutFileName} />
2737+
<ExportConfirm areWeCutting={areWeCutting} nonFilteredSegmentsOrInverse={nonFilteredSegmentsOrInverse} selectedSegments={selectedSegmentsOrInverse} segmentsToExport={segmentsToExport} willMerge={willMerge} visible={exportConfirmVisible} onClosePress={closeExportConfirm} onExportConfirm={onExportConfirm} renderOutFmt={renderOutFmt} outputDir={outputDir} numStreamsTotal={numStreamsTotal} numStreamsToCopy={numStreamsToCopy} onShowStreamsSelectorClick={handleShowStreamsSelectorClick} outFormat={fileFormat} setOutSegTemplate={setOutSegTemplate} outSegTemplate={outSegTemplateOrDefault} generateOutSegFileNames={generateOutSegFileNames} currentSegIndexSafe={currentSegIndexSafe} mainCopiedThumbnailStreams={mainCopiedThumbnailStreams} needSmartCut={needSmartCut} mergedOutFileName={mergedOutFileName} setMergedOutFileName={setMergedOutFileName} smartCutBitrate={smartCutBitrate} setSmartCutBitrate={setSmartCutBitrate} />
27372738

27382739
<Sheet visible={streamsSelectorShown} onClosePress={() => setStreamsSelectorShown(false)} maxWidth={1000}>
27392740
{mainStreams && filePath != null && (

src/renderer/src/components/ExportConfirm.tsx

+36-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CSSProperties, memo, useCallback, useMemo } from 'react';
1+
import { CSSProperties, Dispatch, SetStateAction, memo, useCallback, useMemo } from 'react';
22
import { motion, AnimatePresence } from 'framer-motion';
33
import { WarningSignIcon, CrossIcon } from 'evergreen-ui';
44
import { FaRegCheckCircle } from 'react-icons/fa';
@@ -28,6 +28,7 @@ import { InverseCutSegment, SegmentToExport } from '../types';
2828
import { GenerateOutSegFileNames } from '../util/outputNameTemplate';
2929
import { FFprobeStream } from '../../../../ffprobe';
3030
import { AvoidNegativeTs } from '../../../../types';
31+
import TextInput from './TextInput';
3132

3233

3334
const boxStyle: CSSProperties = { margin: '15px 15px 50px 15px', borderRadius: 10, padding: '10px 20px', minHeight: 500, position: 'relative' };
@@ -61,6 +62,8 @@ const ExportConfirm = memo(({
6162
needSmartCut,
6263
mergedOutFileName,
6364
setMergedOutFileName,
65+
smartCutBitrate,
66+
setSmartCutBitrate,
6467
} : {
6568
areWeCutting: boolean,
6669
selectedSegments: InverseCutSegment[],
@@ -84,6 +87,8 @@ const ExportConfirm = memo(({
8487
needSmartCut: boolean,
8588
mergedOutFileName: string | undefined,
8689
setMergedOutFileName: (a: string) => void,
90+
smartCutBitrate: number | undefined,
91+
setSmartCutBitrate: Dispatch<SetStateAction<number | undefined>>,
8792
}) => {
8893
const { t } = useTranslation();
8994

@@ -165,6 +170,16 @@ const ExportConfirm = memo(({
165170

166171
const canEditTemplate = !willMerge || !autoDeleteMergedSegments;
167172

173+
const handleSmartCutBitrateToggle = useCallback((checked: boolean) => {
174+
setSmartCutBitrate(() => (checked ? undefined : 10000));
175+
}, [setSmartCutBitrate]);
176+
177+
const handleSmartCutBitrateChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
178+
const v = parseInt(e.target.value, 10);
179+
if (Number.isNaN(v) || v <= 0) return;
180+
setSmartCutBitrate(v);
181+
}, [setSmartCutBitrate]);
182+
168183
// https://stackoverflow.com/questions/33454533/cant-scroll-to-top-of-flex-item-that-is-overflowing-container
169184
return (
170185
<AnimatePresence>
@@ -347,6 +362,26 @@ const ExportConfirm = memo(({
347362
</td>
348363
</tr>
349364

365+
{needSmartCut && (
366+
<tr>
367+
<td>
368+
{t('Smart cut auto detect bitrate')}
369+
</td>
370+
<td>
371+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}>
372+
{smartCutBitrate != null && (
373+
<>
374+
<TextInput value={smartCutBitrate} onChange={handleSmartCutBitrateChange} style={{ width: '4em', flexGrow: 0, marginRight: '.3em' }} />
375+
<span style={{ marginRight: '.3em' }}>{t('kbit/s')}</span>
376+
</>
377+
)}
378+
<span><Switch checked={smartCutBitrate == null} onCheckedChange={handleSmartCutBitrateToggle} /></span>
379+
</div>
380+
</td>
381+
<td />
382+
</tr>
383+
)}
384+
350385
{!needSmartCut && (
351386
<tr>
352387
<td>

src/renderer/src/hooks/useFfmpegOperations.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ async function tryDeleteFiles(paths: string[]) {
6060
return pMap(paths, (path) => unlinkWithRetry(path).catch((err) => console.error('Failed to delete', path, err)), { concurrency: 5 });
6161
}
6262

63-
function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, treatOutputFileModifiedTimeAsStart, needSmartCut, enableOverwriteOutput, outputPlaybackRate, cutFromAdjustmentFrames, appendLastCommandsLog }: {
63+
function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, treatOutputFileModifiedTimeAsStart, needSmartCut, enableOverwriteOutput, outputPlaybackRate, cutFromAdjustmentFrames, appendLastCommandsLog, smartCutCustomBitrate }: {
6464
filePath: string | undefined,
6565
treatInputFileModifiedTimeAsStart: boolean | null | undefined,
6666
treatOutputFileModifiedTimeAsStart: boolean | null | undefined,
@@ -69,6 +69,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
6969
outputPlaybackRate: number,
7070
cutFromAdjustmentFrames: number,
7171
appendLastCommandsLog: (a: string) => void,
72+
smartCutCustomBitrate: number | undefined,
7273
}) {
7374
const appendFfmpegCommandLog = useCallback((args: string[]) => appendLastCommandsLog(getFfCommandLine('ffmpeg', args)), [appendLastCommandsLog]);
7475

@@ -412,7 +413,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
412413
return match ? [match] : [];
413414
});
414415

415-
const { losslessCutFrom, segmentNeedsSmartCut, videoCodec, videoBitrate, videoStreamIndex, videoTimebase } = await getSmartCutParams({ path: filePath, videoDuration, desiredCutFrom, streams: streamsToCopyFromMainFile });
416+
const { losslessCutFrom, segmentNeedsSmartCut, videoCodec, videoBitrate: detectedVideoBitrate, videoStreamIndex, videoTimebase } = await getSmartCutParams({ path: filePath, videoDuration, desiredCutFrom, streams: streamsToCopyFromMainFile });
416417

417418
if (segmentNeedsSmartCut && !detectedFps) throw new Error('Smart cut is not possible when FPS is unknown');
418419

@@ -430,10 +431,10 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
430431
// eslint-disable-next-line no-shadow
431432
async function cutEncodeSmartPartWrapper({ cutFrom, cutTo, outPath }) {
432433
if (await shouldSkipExistingFile(outPath)) return;
433-
if (videoCodec == null || videoBitrate == null || videoTimebase == null) throw new Error();
434+
if (videoCodec == null || detectedVideoBitrate == null || videoTimebase == null) throw new Error();
434435
invariant(filePath != null);
435436
invariant(outFormat != null);
436-
const args = await cutEncodeSmartPart({ filePath, cutFrom, cutTo, outPath, outFormat, videoCodec, videoBitrate, videoStreamIndex, videoTimebase, allFilesMeta, copyFileStreams: copyFileStreamsFiltered, ffmpegExperimental });
437+
const args = await cutEncodeSmartPart({ filePath, cutFrom, cutTo, outPath, outFormat, videoCodec, videoBitrate: smartCutCustomBitrate != null ? smartCutCustomBitrate * 1000 : detectedVideoBitrate, videoStreamIndex, videoTimebase, allFilesMeta, copyFileStreams: copyFileStreamsFiltered, ffmpegExperimental });
437438
appendFfmpegCommandLog(args);
438439
}
439440

@@ -497,7 +498,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
497498
} finally {
498499
if (chaptersPath) await tryDeleteFiles([chaptersPath]);
499500
}
500-
}, [needSmartCut, filePath, losslessCutSingle, shouldSkipExistingFile, concatFiles]);
501+
}, [needSmartCut, filePath, losslessCutSingle, shouldSkipExistingFile, smartCutCustomBitrate, appendFfmpegCommandLog, concatFiles]);
501502

502503
const autoConcatCutSegments = useCallback(async ({ customOutDir, outFormat, segmentPaths, ffmpegExperimental, onProgress, preserveMovData, movFastStart, autoDeleteMergedSegments, chapterNames, preserveMetadataOnMerge, mergedOutFilePath }) => {
503504
const outDir = getOutDir(customOutDir, filePath);
@@ -576,7 +577,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
576577
await transferTimestamps({ inPath: filePath, outPath, treatOutputFileModifiedTimeAsStart });
577578

578579
return outPath;
579-
}, [filePath, treatOutputFileModifiedTimeAsStart]);
580+
}, [appendFfmpegCommandLog, filePath, treatOutputFileModifiedTimeAsStart]);
580581

581582
return {
582583
cutMultiple, concatFiles, html5ify, html5ifyDummy, fixInvalidDuration, autoConcatCutSegments,

0 commit comments

Comments
 (0)