kopia lustrzana https://github.com/mifi/lossless-cut
allow adjusting cut too also #2256
rodzic
241210fd98
commit
241210db47
|
@ -149,6 +149,7 @@ const defaults: Config = {
|
|||
preferStrongColors: false,
|
||||
outputFileNameMinZeroPadding: 1,
|
||||
cutFromAdjustmentFrames: 0,
|
||||
cutToAdjustmentFrames: 0,
|
||||
invertTimelineScroll: undefined,
|
||||
storeWindowBounds: true,
|
||||
};
|
||||
|
|
|
@ -172,7 +172,7 @@ function App() {
|
|||
const allUserSettings = useUserSettingsRoot();
|
||||
|
||||
const {
|
||||
captureFormat, setCaptureFormat, customOutDir, setCustomOutDir, keyframeCut, setKeyframeCut, preserveMetadata, preserveChapters, preserveMovData, movFastStart, avoidNegativeTs, autoMerge, timecodeFormat, invertCutSegments, setInvertCutSegments, autoExportExtraStreams, askBeforeClose, enableAskForImportChapters, enableAskForFileOpenAction, playbackVolume, setPlaybackVolume, autoSaveProjectFile, wheelSensitivity, invertTimelineScroll, language, ffmpegExperimental, hideNotifications, hideOsNotifications, autoLoadTimecode, autoDeleteMergedSegments, exportConfirmEnabled, setExportConfirmEnabled, segmentsToChapters, preserveMetadataOnMerge, simpleMode, setSimpleMode, outSegTemplate, setOutSegTemplate, mergedFileTemplate, setMergedFileTemplate, keyboardSeekAccFactor, keyboardNormalSeekSpeed, keyboardSeekSpeed2, keyboardSeekSpeed3, treatInputFileModifiedTimeAsStart, treatOutputFileModifiedTimeAsStart, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, enableAutoHtml5ify, segmentsToChaptersOnly, keyBindings, setKeyBindings, resetKeyBindings, enableSmartCut, customFfPath, storeProjectInWorkingDir, setStoreProjectInWorkingDir, enableOverwriteOutput, mouseWheelZoomModifierKey, mouseWheelFrameSeekModifierKey, mouseWheelKeyframeSeekModifierKey, captureFrameMethod, captureFrameQuality, captureFrameFileNameFormat, enableNativeHevc, cleanupChoices, setCleanupChoices, darkMode, setDarkMode, preferStrongColors, outputFileNameMinZeroPadding, cutFromAdjustmentFrames,
|
||||
captureFormat, setCaptureFormat, customOutDir, setCustomOutDir, keyframeCut, setKeyframeCut, preserveMetadata, preserveChapters, preserveMovData, movFastStart, avoidNegativeTs, autoMerge, timecodeFormat, invertCutSegments, setInvertCutSegments, autoExportExtraStreams, askBeforeClose, enableAskForImportChapters, enableAskForFileOpenAction, playbackVolume, setPlaybackVolume, autoSaveProjectFile, wheelSensitivity, invertTimelineScroll, language, ffmpegExperimental, hideNotifications, hideOsNotifications, autoLoadTimecode, autoDeleteMergedSegments, exportConfirmEnabled, setExportConfirmEnabled, segmentsToChapters, preserveMetadataOnMerge, simpleMode, setSimpleMode, outSegTemplate, setOutSegTemplate, mergedFileTemplate, setMergedFileTemplate, keyboardSeekAccFactor, keyboardNormalSeekSpeed, keyboardSeekSpeed2, keyboardSeekSpeed3, treatInputFileModifiedTimeAsStart, treatOutputFileModifiedTimeAsStart, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, enableAutoHtml5ify, segmentsToChaptersOnly, keyBindings, setKeyBindings, resetKeyBindings, enableSmartCut, customFfPath, storeProjectInWorkingDir, setStoreProjectInWorkingDir, enableOverwriteOutput, mouseWheelZoomModifierKey, mouseWheelFrameSeekModifierKey, mouseWheelKeyframeSeekModifierKey, captureFrameMethod, captureFrameQuality, captureFrameFileNameFormat, enableNativeHevc, cleanupChoices, setCleanupChoices, darkMode, setDarkMode, preferStrongColors, outputFileNameMinZeroPadding, cutFromAdjustmentFrames, cutToAdjustmentFrames,
|
||||
} = allUserSettings;
|
||||
|
||||
const { working, setWorking, workingRef, abortWorking } = useLoading();
|
||||
|
@ -597,7 +597,7 @@ function App() {
|
|||
|
||||
const {
|
||||
concatFiles, html5ifyDummy, cutMultiple, autoConcatCutSegments, html5ify, fixInvalidDuration, extractStreams,
|
||||
} = useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, treatOutputFileModifiedTimeAsStart, needSmartCut, enableOverwriteOutput, outputPlaybackRate, cutFromAdjustmentFrames, appendLastCommandsLog, smartCutCustomBitrate: smartCutBitrate, appendFfmpegCommandLog });
|
||||
} = useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, treatOutputFileModifiedTimeAsStart, needSmartCut, enableOverwriteOutput, outputPlaybackRate, cutFromAdjustmentFrames, cutToAdjustmentFrames, appendLastCommandsLog, smartCutCustomBitrate: smartCutBitrate, appendFfmpegCommandLog });
|
||||
|
||||
const { captureFrameFromTag, captureFrameFromFfmpeg, captureFramesRange } = useFrameCapture({ appendFfmpegCommandLog, formatTimecode, treatOutputFileModifiedTimeAsStart });
|
||||
|
||||
|
|
|
@ -34,8 +34,20 @@ const outDirStyle: CSSProperties = { ...highlightedTextStyle, wordBreak: 'break-
|
|||
|
||||
const warningStyle: CSSProperties = { color: 'var(--orange8)', fontSize: '80%', marginBottom: '.5em' };
|
||||
|
||||
const adjustCutFromValues = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
const adjustCutToValues = [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
|
||||
const HelpIcon = ({ onClick, style }: { onClick: () => void, style?: CSSProperties }) => <IoIosHelpCircle size={20} role="button" onClick={withBlur(onClick)} style={{ cursor: 'pointer', color: primaryTextColor, verticalAlign: 'middle', ...style }} />;
|
||||
|
||||
function ShiftTimes({ values, num, setNum }: { values: number[], num: number, setNum: (n: number) => void }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Select value={num} onChange={(e) => setNum(Number(e.target.value))} style={{ height: 20, marginLeft: 5 }}>
|
||||
{values.map((v) => <option key={v} value={v}>{t('{{numFrames}} frames', { numFrames: v >= 0 ? `+${v}` : v, count: v })}</option>)}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
function ExportConfirm({
|
||||
areWeCutting,
|
||||
selectedSegments,
|
||||
|
@ -95,7 +107,7 @@ function ExportConfirm({
|
|||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { changeOutDir, keyframeCut, toggleKeyframeCut, preserveMovData, setPreserveMovData, preserveMetadata, setPreserveMetadata, preserveChapters, setPreserveChapters, movFastStart, setMovFastStart, avoidNegativeTs, setAvoidNegativeTs, autoDeleteMergedSegments, exportConfirmEnabled, toggleExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, enableSmartCut, setEnableSmartCut, effectiveExportMode, enableOverwriteOutput, setEnableOverwriteOutput, ffmpegExperimental, setFfmpegExperimental, cutFromAdjustmentFrames, setCutFromAdjustmentFrames } = useUserSettings();
|
||||
const { changeOutDir, keyframeCut, toggleKeyframeCut, preserveMovData, setPreserveMovData, preserveMetadata, setPreserveMetadata, preserveChapters, setPreserveChapters, movFastStart, setMovFastStart, avoidNegativeTs, setAvoidNegativeTs, autoDeleteMergedSegments, exportConfirmEnabled, toggleExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, enableSmartCut, setEnableSmartCut, effectiveExportMode, enableOverwriteOutput, setEnableOverwriteOutput, ffmpegExperimental, setFfmpegExperimental, cutFromAdjustmentFrames, setCutFromAdjustmentFrames, cutToAdjustmentFrames, setCutToAdjustmentFrames } = useUserSettings();
|
||||
|
||||
const togglePreserveChapters = useCallback(() => setPreserveChapters((val) => !val), [setPreserveChapters]);
|
||||
const togglePreserveMovData = useCallback(() => setPreserveMovData((val) => !val), [setPreserveMovData]);
|
||||
|
@ -350,19 +362,28 @@ function ExportConfirm({
|
|||
<tbody>
|
||||
|
||||
{areWeCutting && (
|
||||
<tr>
|
||||
<td>
|
||||
{t('Shift all start times')}
|
||||
</td>
|
||||
<td>
|
||||
<Select value={cutFromAdjustmentFrames} onChange={(e) => setCutFromAdjustmentFrames(Number(e.target.value))} style={{ height: 20, marginLeft: 5 }}>
|
||||
{[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((v) => <option key={v} value={v}>{t('+{{numFrames}} frames', { numFrames: v, count: v })}</option>)}
|
||||
</Select>
|
||||
</td>
|
||||
<td>
|
||||
<HelpIcon onClick={onCutFromAdjustmentFramesHelpPress} />
|
||||
</td>
|
||||
</tr>
|
||||
<>
|
||||
<tr>
|
||||
<td>
|
||||
{t('Shift all start times')}
|
||||
</td>
|
||||
<td>
|
||||
<ShiftTimes values={adjustCutFromValues} num={cutFromAdjustmentFrames} setNum={setCutFromAdjustmentFrames} />
|
||||
</td>
|
||||
<td>
|
||||
<HelpIcon onClick={onCutFromAdjustmentFramesHelpPress} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{t('Shift all end times')}
|
||||
</td>
|
||||
<td>
|
||||
<ShiftTimes values={adjustCutToValues} num={cutToAdjustmentFrames} setNum={setCutToAdjustmentFrames} />
|
||||
</td>
|
||||
<td />
|
||||
</tr>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isMov && (
|
||||
|
|
|
@ -76,7 +76,7 @@ async function pathExists(path: string) {
|
|||
}
|
||||
}
|
||||
|
||||
function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, treatOutputFileModifiedTimeAsStart, needSmartCut, enableOverwriteOutput, outputPlaybackRate, cutFromAdjustmentFrames, appendLastCommandsLog, smartCutCustomBitrate, appendFfmpegCommandLog }: {
|
||||
function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, treatOutputFileModifiedTimeAsStart, needSmartCut, enableOverwriteOutput, outputPlaybackRate, cutFromAdjustmentFrames, cutToAdjustmentFrames, appendLastCommandsLog, smartCutCustomBitrate, appendFfmpegCommandLog }: {
|
||||
filePath: string | undefined,
|
||||
treatInputFileModifiedTimeAsStart: boolean | null | undefined,
|
||||
treatOutputFileModifiedTimeAsStart: boolean | null | undefined,
|
||||
|
@ -84,6 +84,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
|
|||
needSmartCut: boolean,
|
||||
outputPlaybackRate: number,
|
||||
cutFromAdjustmentFrames: number,
|
||||
cutToAdjustmentFrames: number,
|
||||
appendLastCommandsLog: (a: string) => void,
|
||||
smartCutCustomBitrate: number | undefined,
|
||||
appendFfmpegCommandLog: (args: string[]) => void,
|
||||
|
@ -263,11 +264,12 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
|
|||
|
||||
const cuttingStart = isCuttingStart(cutFrom);
|
||||
const cutFromWithAdjustment = cutFrom + cutFromAdjustmentFrames * frameDuration;
|
||||
const cutToWithAdjustment = cutTo + cutToAdjustmentFrames * frameDuration;
|
||||
const cuttingEnd = isCuttingEnd(cutTo, videoDuration);
|
||||
const areWeCutting = cuttingStart || cuttingEnd;
|
||||
if (areWeCutting) console.log('Cutting from', cuttingStart ? `${cutFrom} (${cutFromWithAdjustment} adjusted ${cutFromAdjustmentFrames} frames)` : 'start', 'to', cuttingEnd ? cutTo : 'end');
|
||||
if (areWeCutting) console.log('Cutting from', cuttingStart ? `${cutFrom} (${cutFromWithAdjustment} adjusted ${cutFromAdjustmentFrames} frames)` : 'start', 'to', cuttingEnd ? `${cutTo} (adjusted ${cutToAdjustmentFrames} frames)` : 'end');
|
||||
|
||||
let cutDuration = cutTo - cutFromWithAdjustment;
|
||||
let cutDuration = cutToWithAdjustment - cutFromWithAdjustment;
|
||||
if (detectedFps != null) cutDuration = Math.max(cutDuration, frameDuration); // ensure at least one frame duration
|
||||
|
||||
// Don't cut if no need: https://github.com/mifi/lossless-cut/issues/50
|
||||
|
@ -415,7 +417,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
|
|||
logStdoutStderr(result);
|
||||
|
||||
await transferTimestamps({ inPath: filePath, outPath, cutFrom, cutTo, treatInputFileModifiedTimeAsStart, duration: isDurationValid(videoDuration) ? videoDuration : undefined, treatOutputFileModifiedTimeAsStart });
|
||||
}, [appendFfmpegCommandLog, cutFromAdjustmentFrames, filePath, getOutputPlaybackRateArgs, shouldSkipExistingFile, treatInputFileModifiedTimeAsStart, treatOutputFileModifiedTimeAsStart]);
|
||||
}, [appendFfmpegCommandLog, cutFromAdjustmentFrames, cutToAdjustmentFrames, filePath, getOutputPlaybackRateArgs, shouldSkipExistingFile, treatInputFileModifiedTimeAsStart, treatOutputFileModifiedTimeAsStart]);
|
||||
|
||||
// inspired by https://gist.github.com/fernandoherreradelasheras/5eca67f4200f1a7cc8281747da08496e
|
||||
const cutEncodeSmartPart = useCallback(async ({ cutFrom, cutTo, outPath, outFormat, videoCodec, videoBitrate, videoTimebase, allFilesMeta, copyFileStreams, videoStreamIndex, ffmpegExperimental }: {
|
||||
|
|
|
@ -163,6 +163,8 @@ export default () => {
|
|||
useEffect(() => safeSetConfig({ outputFileNameMinZeroPadding }), [outputFileNameMinZeroPadding]);
|
||||
const [cutFromAdjustmentFrames, setCutFromAdjustmentFrames] = useState(safeGetConfigInitial('cutFromAdjustmentFrames'));
|
||||
useEffect(() => safeSetConfig({ cutFromAdjustmentFrames }), [cutFromAdjustmentFrames]);
|
||||
const [cutToAdjustmentFrames, setCutToAdjustmentFrames] = useState(safeGetConfigInitial('cutToAdjustmentFrames'));
|
||||
useEffect(() => safeSetConfig({ cutToAdjustmentFrames }), [cutToAdjustmentFrames]);
|
||||
const [storeWindowBounds, setStoreWindowBounds] = useState(safeGetConfigInitial('storeWindowBounds'));
|
||||
useEffect(() => safeSetConfig({ storeWindowBounds }), [storeWindowBounds]);
|
||||
|
||||
|
@ -301,6 +303,8 @@ export default () => {
|
|||
setOutputFileNameMinZeroPadding,
|
||||
cutFromAdjustmentFrames,
|
||||
setCutFromAdjustmentFrames,
|
||||
cutToAdjustmentFrames,
|
||||
setCutToAdjustmentFrames,
|
||||
storeWindowBounds,
|
||||
setStoreWindowBounds,
|
||||
};
|
||||
|
|
1
types.ts
1
types.ts
|
@ -111,6 +111,7 @@ export interface Config {
|
|||
preferStrongColors: boolean,
|
||||
outputFileNameMinZeroPadding: number,
|
||||
cutFromAdjustmentFrames: number,
|
||||
cutToAdjustmentFrames: number,
|
||||
invertTimelineScroll: boolean | undefined,
|
||||
}
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue