kopia lustrzana https://github.com/mifi/lossless-cut
implement customisable timestamp transfer #1017
rodzic
9c633cbbdf
commit
5bc5715bf9
|
@ -106,7 +106,8 @@ const defaults = {
|
|||
outSegTemplate: undefined,
|
||||
keyboardSeekAccFactor: 1.03,
|
||||
keyboardNormalSeekSpeed: 1,
|
||||
enableTransferTimestamps: true,
|
||||
treatInputFileModifiedTimeAsStart: true,
|
||||
treatOutputFileModifiedTimeAsStart: true,
|
||||
outFormatLocked: undefined,
|
||||
safeOutputFileName: true,
|
||||
windowBounds: undefined,
|
||||
|
@ -150,6 +151,19 @@ async function getCustomStoragePath() {
|
|||
|
||||
let store;
|
||||
|
||||
function get(key) {
|
||||
return store.get(key);
|
||||
}
|
||||
|
||||
function set(key, val) {
|
||||
if (val === undefined) store.delete(key);
|
||||
else store.set(key, val);
|
||||
}
|
||||
|
||||
function reset(key) {
|
||||
set(key, defaults[key]);
|
||||
}
|
||||
|
||||
async function init() {
|
||||
const customStoragePath = await getCustomStoragePath();
|
||||
if (customStoragePath) logger.info('customStoragePath', customStoragePath);
|
||||
|
@ -165,22 +179,17 @@ async function init() {
|
|||
}
|
||||
}
|
||||
|
||||
// migrate old configs:
|
||||
const enableTransferTimestamps = store.get('enableTransferTimestamps'); // todo remove after a while
|
||||
if (enableTransferTimestamps != null) {
|
||||
logger.info('Migrating enableTransferTimestamps');
|
||||
store.delete('enableTransferTimestamps');
|
||||
set('treatOutputFileModifiedTimeAsStart', enableTransferTimestamps ? true : undefined);
|
||||
}
|
||||
|
||||
throw new Error('Timed out while creating config store');
|
||||
}
|
||||
|
||||
function get(key) {
|
||||
return store.get(key);
|
||||
}
|
||||
|
||||
function set(key, val) {
|
||||
if (val === undefined) store.delete(key);
|
||||
else store.set(key, val);
|
||||
}
|
||||
|
||||
function reset(key) {
|
||||
set(key, defaults[key]);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init,
|
||||
get,
|
||||
|
|
16
src/App.jsx
16
src/App.jsx
|
@ -185,7 +185,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, preferStrongColors,
|
||||
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, treatInputFileModifiedTimeAsStart, treatOutputFileModifiedTimeAsStart, 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(() => {
|
||||
|
@ -378,7 +378,7 @@ const App = memo(() => {
|
|||
return formatDuration({ seconds, shorten, fileNameFriendly });
|
||||
}, [detectedFps, timecodeFormat, getFrameCount]);
|
||||
|
||||
const { captureFrameFromTag, captureFrameFromFfmpeg, captureFramesRange } = useFrameCapture({ formatTimecode });
|
||||
const { captureFrameFromTag, captureFrameFromFfmpeg, captureFramesRange } = useFrameCapture({ formatTimecode, treatOutputFileModifiedTimeAsStart });
|
||||
|
||||
// const getSafeCutTime = useCallback((cutTime, next) => ffmpeg.getSafeCutTime(neighbouringFrames, cutTime, next), [neighbouringFrames]);
|
||||
|
||||
|
@ -733,7 +733,7 @@ const App = memo(() => {
|
|||
|
||||
const {
|
||||
concatFiles, html5ifyDummy, cutMultiple, autoConcatCutSegments, html5ify, fixInvalidDuration,
|
||||
} = useFfmpegOperations({ filePath, enableTransferTimestamps, needSmartCut, enableOverwriteOutput });
|
||||
} = useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, treatOutputFileModifiedTimeAsStart, needSmartCut, enableOverwriteOutput });
|
||||
|
||||
const html5ifyAndLoad = useCallback(async (cod, fp, speed, hv, ha) => {
|
||||
const usesDummyVideo = ['fastest-audio', 'fastest-audio-remux', 'fastest'].includes(speed);
|
||||
|
@ -1251,15 +1251,15 @@ const App = memo(() => {
|
|||
const video = videoRef.current;
|
||||
const useFffmpeg = usingPreviewFile || captureFrameMethod === 'ffmpeg';
|
||||
const outPath = useFffmpeg
|
||||
? await captureFrameFromFfmpeg({ customOutDir, filePath, fromTime: currentTime, captureFormat, enableTransferTimestamps, quality: captureFrameQuality })
|
||||
: await captureFrameFromTag({ customOutDir, filePath, currentTime, captureFormat, video, enableTransferTimestamps, quality: captureFrameQuality });
|
||||
? await captureFrameFromFfmpeg({ customOutDir, filePath, fromTime: currentTime, captureFormat, quality: captureFrameQuality })
|
||||
: await captureFrameFromTag({ customOutDir, filePath, currentTime, captureFormat, video, quality: captureFrameQuality });
|
||||
|
||||
if (!hideAllNotifications) openDirToast({ icon: 'success', filePath: outPath, text: `${i18n.t('Screenshot captured to:')} ${outPath}` });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
errorToast(i18n.t('Failed to capture frame'));
|
||||
}
|
||||
}, [filePath, getRelevantTime, usingPreviewFile, captureFrameMethod, captureFrameFromFfmpeg, customOutDir, captureFormat, enableTransferTimestamps, captureFrameQuality, captureFrameFromTag, hideAllNotifications]);
|
||||
}, [filePath, getRelevantTime, usingPreviewFile, captureFrameMethod, captureFrameFromFfmpeg, customOutDir, captureFormat, captureFrameQuality, captureFrameFromTag, hideAllNotifications]);
|
||||
|
||||
const extractSegmentFramesAsImages = useCallback(async (index) => {
|
||||
if (!filePath || detectedFps == null || workingRef.current) return;
|
||||
|
@ -1687,14 +1687,14 @@ const App = memo(() => {
|
|||
if (!filePath) return;
|
||||
try {
|
||||
const currentTime = getRelevantTime();
|
||||
const path = await captureFrameFromFfmpeg({ customOutDir, filePath, fromTime: currentTime, captureFormat, enableTransferTimestamps, quality: captureFrameQuality });
|
||||
const path = await captureFrameFromFfmpeg({ customOutDir, filePath, fromTime: currentTime, captureFormat, quality: captureFrameQuality });
|
||||
if (!(await addFileAsCoverArt(path))) return;
|
||||
if (!hideAllNotifications) toast.fire({ text: i18n.t('Current frame has been set as cover art') });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
errorToast(i18n.t('Failed to capture frame'));
|
||||
}
|
||||
}, [addFileAsCoverArt, captureFormat, captureFrameFromFfmpeg, captureFrameQuality, customOutDir, enableTransferTimestamps, filePath, getRelevantTime, hideAllNotifications]);
|
||||
}, [addFileAsCoverArt, captureFormat, captureFrameFromFfmpeg, captureFrameQuality, customOutDir, filePath, getRelevantTime, hideAllNotifications]);
|
||||
|
||||
const batchLoadPaths = useCallback((newPaths, append) => {
|
||||
setBatchFiles((existingFiles) => {
|
||||
|
|
|
@ -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, preferStrongColors, setPreferStrongColors } = 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, enableAutoHtml5ify, setEnableAutoHtml5ify, customFfPath, setCustomFfPath, storeProjectInWorkingDir, enableOverwriteOutput, setEnableOverwriteOutput, mouseWheelZoomModifierKey, setMouseWheelZoomModifierKey, captureFrameMethod, setCaptureFrameMethod, captureFrameQuality, setCaptureFrameQuality, captureFrameFileNameFormat, setCaptureFrameFileNameFormat, enableNativeHevc, setEnableNativeHevc, enableUpdateCheck, setEnableUpdateCheck, allowMultipleInstances, setAllowMultipleInstances, preferStrongColors, setPreferStrongColors, treatInputFileModifiedTimeAsStart, setTreatInputFileModifiedTimeAsStart, treatOutputFileModifiedTimeAsStart, setTreatOutputFileModifiedTimeAsStart } = useUserSettings();
|
||||
|
||||
const onLangChange = useCallback((e) => {
|
||||
const { value } = e.target;
|
||||
|
@ -190,9 +190,21 @@ const Settings = memo(({
|
|||
<Row>
|
||||
<KeyCell>{t('Set file modification date/time of output files to:')}</KeyCell>
|
||||
<td>
|
||||
<Button iconBefore={enableTransferTimestamps ? DocumentIcon : TimeIcon} onClick={() => setEnableTransferTimestamps((v) => !v)}>
|
||||
{enableTransferTimestamps ? t('Source file\'s time') : t('Current time')}
|
||||
</Button>
|
||||
<Select value={treatOutputFileModifiedTimeAsStart ?? 'disabled'} onChange={(e) => setTreatOutputFileModifiedTimeAsStart(e.target.value === 'disabled' ? null : (e.target.value === 'true'))}>
|
||||
<option value="disabled">{t('Current time')}</option>
|
||||
<option value="true">{t('Source file\'s time plus segment start cut time')}</option>
|
||||
<option value="false">{t('Source file\'s time minus segment end cut time')}</option>
|
||||
</Select>
|
||||
</td>
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<KeyCell>{t('Treat source file modification date/time as:')}</KeyCell>
|
||||
<td>
|
||||
<Select disabled={treatOutputFileModifiedTimeAsStart == null} value={treatInputFileModifiedTimeAsStart} onChange={(e) => setTreatInputFileModifiedTimeAsStart((e.target.value === 'true'))}>
|
||||
<option value="true">{t('Start of video')}</option>
|
||||
<option value="false">{t('End of video')}</option>
|
||||
</Select>
|
||||
</td>
|
||||
</Row>
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import { getSuffixedOutPath, transferTimestamps, getOutFileExtension, getOutDir,
|
|||
import { isCuttingStart, isCuttingEnd, runFfmpegWithProgress, getFfCommandLine, getDuration, createChaptersFromSegments, readFileMeta, cutEncodeSmartPart, getExperimentalArgs, html5ify as ffmpegHtml5ify, getVideoTimescaleArgs, logStdoutStderr, runFfmpegConcat } from '../ffmpeg';
|
||||
import { getMapStreamsArgs, getStreamIdsToCopy } from '../util/streams';
|
||||
import { getSmartCutParams } from '../smartcut';
|
||||
import { isDurationValid } from '../segments';
|
||||
|
||||
const { join, resolve, dirname } = window.require('path');
|
||||
const { pathExists } = window.require('fs-extra');
|
||||
|
@ -55,11 +56,7 @@ const tryDeleteFiles = async (paths) => pMap(paths, (path) => {
|
|||
unlink(path).catch((err) => console.error('Failed to delete', path, err));
|
||||
}, { concurrency: 5 });
|
||||
|
||||
function useFfmpegOperations({ filePath, enableTransferTimestamps, needSmartCut, enableOverwriteOutput }) {
|
||||
const optionalTransferTimestamps = useCallback(async (...args) => {
|
||||
if (enableTransferTimestamps) await transferTimestamps(...args);
|
||||
}, [enableTransferTimestamps]);
|
||||
|
||||
function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, treatOutputFileModifiedTimeAsStart, needSmartCut, enableOverwriteOutput }) {
|
||||
const shouldSkipExistingFile = useCallback(async (path) => {
|
||||
const skip = !enableOverwriteOutput && await pathExists(path);
|
||||
if (skip) console.log('Not overwriting existing file', path);
|
||||
|
@ -165,13 +162,13 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps, needSmartCut,
|
|||
const result = await runFfmpegConcat({ ffmpegArgs, concatTxt, totalDuration, onProgress });
|
||||
logStdoutStderr(result);
|
||||
|
||||
await optionalTransferTimestamps(metadataFromPath, outPath);
|
||||
await transferTimestamps({ inPath: metadataFromPath, outPath, treatOutputFileModifiedTimeAsStart });
|
||||
|
||||
return { haveExcludedStreams: excludedStreamIds.length > 0 };
|
||||
} finally {
|
||||
if (chaptersPath) await tryDeleteFiles([chaptersPath]);
|
||||
}
|
||||
}, [optionalTransferTimestamps, shouldSkipExistingFile]);
|
||||
}, [shouldSkipExistingFile, treatOutputFileModifiedTimeAsStart]);
|
||||
|
||||
const cutSingle = useCallback(async ({
|
||||
keyframeCut: ssBeforeInput, avoidNegativeTs, copyFileStreams, cutFrom, cutTo, chaptersPath, onProgress, outPath,
|
||||
|
@ -319,8 +316,8 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps, needSmartCut,
|
|||
const result = await runFfmpegWithProgress({ ffmpegArgs, duration: cutDuration, onProgress });
|
||||
logStdoutStderr(result);
|
||||
|
||||
await optionalTransferTimestamps(filePath, outPath, cutFrom);
|
||||
}, [filePath, optionalTransferTimestamps, shouldSkipExistingFile]);
|
||||
await transferTimestamps({ inPath: filePath, outPath, cutFrom, cutTo, treatInputFileModifiedTimeAsStart, duration: isDurationValid(videoDuration) ? videoDuration : undefined, treatOutputFileModifiedTimeAsStart });
|
||||
}, [filePath, shouldSkipExistingFile, treatInputFileModifiedTimeAsStart, treatOutputFileModifiedTimeAsStart]);
|
||||
|
||||
const cutMultiple = useCallback(async ({
|
||||
outputDir, customOutDir, segments, outSegFileNames, videoDuration, rotation, detectedFps,
|
||||
|
@ -459,9 +456,9 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps, needSmartCut,
|
|||
const html5ify = useCallback(async ({ customOutDir, filePath: filePathArg, speed, hasAudio, hasVideo, onProgress }) => {
|
||||
const outPath = getHtml5ifiedPath(customOutDir, filePathArg, speed);
|
||||
await ffmpegHtml5ify({ filePath: filePathArg, outPath, speed, hasAudio, hasVideo, onProgress });
|
||||
await optionalTransferTimestamps(filePathArg, outPath);
|
||||
await transferTimestamps({ inPath: filePathArg, outPath, treatOutputFileModifiedTimeAsStart });
|
||||
return outPath;
|
||||
}, [optionalTransferTimestamps]);
|
||||
}, [treatOutputFileModifiedTimeAsStart]);
|
||||
|
||||
// This is just used to load something into the player with correct length,
|
||||
// so user can seek and then we render frames using ffmpeg
|
||||
|
@ -483,8 +480,8 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps, needSmartCut,
|
|||
const result = await runFfmpegWithProgress({ ffmpegArgs, duration, onProgress });
|
||||
logStdoutStderr(result);
|
||||
|
||||
await optionalTransferTimestamps(filePathArg, outPath);
|
||||
}, [optionalTransferTimestamps]);
|
||||
await transferTimestamps({ inPath: filePathArg, outPath, treatOutputFileModifiedTimeAsStart });
|
||||
}, [treatOutputFileModifiedTimeAsStart]);
|
||||
|
||||
// https://stackoverflow.com/questions/34118013/how-to-determine-webm-duration-using-ffprobe
|
||||
const fixInvalidDuration = useCallback(async ({ fileFormat, customOutDir, duration, onProgress }) => {
|
||||
|
@ -508,10 +505,10 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps, needSmartCut,
|
|||
const result = await runFfmpegWithProgress({ ffmpegArgs, duration, onProgress });
|
||||
logStdoutStderr(result);
|
||||
|
||||
await optionalTransferTimestamps(filePath, outPath);
|
||||
await transferTimestamps({ inPath: filePath, outPath, treatOutputFileModifiedTimeAsStart });
|
||||
|
||||
return outPath;
|
||||
}, [filePath, optionalTransferTimestamps]);
|
||||
}, [filePath, treatOutputFileModifiedTimeAsStart]);
|
||||
|
||||
return {
|
||||
cutMultiple, concatFiles, html5ify, html5ifyDummy, fixInvalidDuration, autoConcatCutSegments,
|
||||
|
|
|
@ -23,7 +23,7 @@ function getFrameFromVideo(video, format, quality) {
|
|||
return dataUriToBuffer(dataUri);
|
||||
}
|
||||
|
||||
export default ({ formatTimecode }) => {
|
||||
export default ({ formatTimecode, treatOutputFileModifiedTimeAsStart }) => {
|
||||
async function captureFramesRange({ customOutDir, filePath, fps, fromTime, toTime, estimatedMaxNumFiles, captureFormat, quality, filter, onProgress, outputTimestamps }) {
|
||||
const getSuffix = (prefix) => `${prefix}.${captureFormat}`;
|
||||
|
||||
|
@ -71,17 +71,17 @@ export default ({ formatTimecode }) => {
|
|||
return outPaths[0];
|
||||
}
|
||||
|
||||
async function captureFrameFromFfmpeg({ customOutDir, filePath, fromTime, captureFormat, enableTransferTimestamps, quality }) {
|
||||
async function captureFrameFromFfmpeg({ customOutDir, filePath, fromTime, captureFormat, quality }) {
|
||||
const time = formatTimecode({ seconds: fromTime, fileNameFriendly: true });
|
||||
const nameSuffix = `${time}.${captureFormat}`;
|
||||
const outPath = getSuffixedOutPath({ customOutDir, filePath, nameSuffix });
|
||||
await ffmpegCaptureFrame({ timestamp: fromTime, videoPath: filePath, outPath, quality });
|
||||
|
||||
if (enableTransferTimestamps) await transferTimestamps(filePath, outPath, fromTime);
|
||||
await transferTimestamps({ inPath: filePath, outPath, cutFrom: fromTime, treatOutputFileModifiedTimeAsStart });
|
||||
return outPath;
|
||||
}
|
||||
|
||||
async function captureFrameFromTag({ customOutDir, filePath, currentTime, captureFormat, video, enableTransferTimestamps, quality }) {
|
||||
async function captureFrameFromTag({ customOutDir, filePath, currentTime, captureFormat, video, quality }) {
|
||||
const buf = getFrameFromVideo(video, captureFormat, quality);
|
||||
|
||||
const ext = mime.extension(buf.type);
|
||||
|
@ -90,7 +90,7 @@ export default ({ formatTimecode }) => {
|
|||
const outPath = getSuffixedOutPath({ customOutDir, filePath, nameSuffix: `${time}.${ext}` });
|
||||
await fs.writeFile(outPath, buf);
|
||||
|
||||
if (enableTransferTimestamps) await transferTimestamps(filePath, outPath, currentTime);
|
||||
await transferTimestamps({ inPath: filePath, outPath, cutFrom: currentTime, treatOutputFileModifiedTimeAsStart });
|
||||
return outPath;
|
||||
}
|
||||
|
||||
|
|
|
@ -97,8 +97,12 @@ export default () => {
|
|||
useEffect(() => safeSetConfig({ keyboardSeekAccFactor }), [keyboardSeekAccFactor]);
|
||||
const [keyboardNormalSeekSpeed, setKeyboardNormalSeekSpeed] = useState(safeGetConfigInitial('keyboardNormalSeekSpeed'));
|
||||
useEffect(() => safeSetConfig({ keyboardNormalSeekSpeed }), [keyboardNormalSeekSpeed]);
|
||||
const [enableTransferTimestamps, setEnableTransferTimestamps] = useState(safeGetConfigInitial('enableTransferTimestamps'));
|
||||
useEffect(() => safeSetConfig({ enableTransferTimestamps }), [enableTransferTimestamps]);
|
||||
|
||||
const [treatInputFileModifiedTimeAsStart, setTreatInputFileModifiedTimeAsStart] = useState(safeGetConfigInitial('treatInputFileModifiedTimeAsStart'));
|
||||
useEffect(() => safeSetConfig({ treatInputFileModifiedTimeAsStart }), [treatInputFileModifiedTimeAsStart]);
|
||||
const [treatOutputFileModifiedTimeAsStart, setTreatOutputFileModifiedTimeAsStart] = useState(safeGetConfigInitial('treatOutputFileModifiedTimeAsStart'));
|
||||
useEffect(() => safeSetConfig({ treatOutputFileModifiedTimeAsStart }), [treatOutputFileModifiedTimeAsStart]);
|
||||
|
||||
const [outFormatLocked, setOutFormatLocked] = useState(safeGetConfigInitial('outFormatLocked'));
|
||||
useEffect(() => safeSetConfig({ outFormatLocked }), [outFormatLocked]);
|
||||
const [safeOutputFileName, setSafeOutputFileName] = useState(safeGetConfigInitial('safeOutputFileName'));
|
||||
|
@ -211,8 +215,10 @@ export default () => {
|
|||
setKeyboardSeekAccFactor,
|
||||
keyboardNormalSeekSpeed,
|
||||
setKeyboardNormalSeekSpeed,
|
||||
enableTransferTimestamps,
|
||||
setEnableTransferTimestamps,
|
||||
treatInputFileModifiedTimeAsStart,
|
||||
setTreatInputFileModifiedTimeAsStart,
|
||||
treatOutputFileModifiedTimeAsStart,
|
||||
setTreatOutputFileModifiedTimeAsStart,
|
||||
outFormatLocked,
|
||||
setOutFormatLocked,
|
||||
safeOutputFileName,
|
||||
|
|
21
src/util.js
21
src/util.js
|
@ -89,10 +89,27 @@ export async function dirExists(dirPath) {
|
|||
return (await pathExists(dirPath)) && (await fsExtra.lstat(dirPath)).isDirectory();
|
||||
}
|
||||
|
||||
export async function transferTimestamps(inPath, outPath, offset = 0) {
|
||||
export async function transferTimestamps({ inPath, outPath, cutFrom = 0, cutTo = 0, duration = 0, treatInputFileModifiedTimeAsStart = true, treatOutputFileModifiedTimeAsStart }) {
|
||||
if (treatOutputFileModifiedTimeAsStart == null) return; // null means disabled;
|
||||
|
||||
// see https://github.com/mifi/lossless-cut/issues/1017#issuecomment-1049097115
|
||||
function calculateTime(fileTime) {
|
||||
if (treatInputFileModifiedTimeAsStart && treatOutputFileModifiedTimeAsStart) {
|
||||
return fileTime + cutFrom;
|
||||
}
|
||||
if (!treatInputFileModifiedTimeAsStart && !treatOutputFileModifiedTimeAsStart) {
|
||||
return fileTime - duration + cutTo;
|
||||
}
|
||||
if (treatInputFileModifiedTimeAsStart && !treatOutputFileModifiedTimeAsStart) {
|
||||
return fileTime + cutTo;
|
||||
}
|
||||
// if (!treatInputFileModifiedTimeAsStart && treatOutputFileModifiedTimeAsStart) {
|
||||
return fileTime - duration + cutFrom;
|
||||
}
|
||||
|
||||
try {
|
||||
const { atime, mtime } = await stat(inPath);
|
||||
await fsExtra.utimes(outPath, (atime.getTime() / 1000) + offset, (mtime.getTime() / 1000) + offset);
|
||||
await fsExtra.utimes(outPath, calculateTime((atime.getTime() / 1000)), calculateTime((mtime.getTime() / 1000)));
|
||||
} catch (err) {
|
||||
console.error('Failed to set output file modified time', err);
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue