diff --git a/src/App.jsx b/src/App.jsx index a21aafa3..dac899ce 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -308,12 +308,13 @@ const App = memo(() => { }, [seekRel, zoomedDuration]); const shortStep = useCallback((direction) => { - if (!detectedFps) return; + // If we don't know fps, just assume 30 (for example if audio file) + const fps = detectedFps || 30; // try to align with frame - const currentTimeNearestFrameNumber = getFrameCountRaw(detectedFps, videoRef.current.currentTime); + const currentTimeNearestFrameNumber = getFrameCountRaw(fps, videoRef.current.currentTime); const nextFrame = currentTimeNearestFrameNumber + direction; - seekAbs(nextFrame / detectedFps); + seekAbs(nextFrame / fps); }, [seekAbs, detectedFps]); // 360 means we don't modify rotation @@ -475,8 +476,6 @@ const App = memo(() => { const getFrameCount = useCallback((sec) => getFrameCountRaw(detectedFps, sec), [detectedFps]); - const getTimeFromFrameNum = useCallback((frameNum) => getTimeFromFrameNumRaw(detectedFps, frameNum), [detectedFps]); - const formatTimecode = useCallback(({ seconds, shorten }) => { if (timecodeFormat === 'frameCount') { const frameCount = getFrameCount(seconds); @@ -1566,14 +1565,13 @@ const App = memo(() => { const haveVideoStream = !!videoStream; const haveAudioStream = !!audioStream; - const detectedFpsNew = haveVideoStream ? getStreamFps(videoStream) : undefined; - const copyStreamIdsForPathNew = fromPairs(fileMeta.streams.map((stream) => [ stream.index, shouldCopyStreamByDefault(stream), ])); if (timecode) setStartTimeOffset(timecode); - if (detectedFpsNew != null) setDetectedFps(detectedFpsNew); + + setDetectedFps(haveVideoStream ? getStreamFps(videoStream) : undefined); if (isAudioDefinitelyNotSupported(fileMeta.streams)) { toast.fire({ icon: 'info', text: i18n.t('The audio track is not supported. You can convert to a supported format from the menu') }); @@ -2150,7 +2148,7 @@ const App = memo(() => { if (!checkFileOpened()) return; try { - const edl = await askForEdlImport({ type, getTimeFromFrameNum }); + const edl = await askForEdlImport({ type, fps: detectedFps }); if (edl.length > 0) loadCutSegments(edl, true); } catch (err) { handleError(err); @@ -2187,7 +2185,7 @@ const App = memo(() => { const entries = Object.entries(action); entries.forEach(([key, value]) => electron.ipcRenderer.on(key, value)); return () => entries.forEach(([key, value]) => electron.ipcRenderer.removeListener(key, value)); - }, [apparentCutSegments, askSetStartTimeOffset, checkFileOpened, clearSegments, closeBatch, closeFileWithConfirm, concatCurrentBatch, createFixedDurationSegments, createNumSegments, customOutDir, cutSegments, extractAllStreams, fileFormat, filePath, fillSegmentsGaps, getFrameCount, getTimeFromFrameNum, invertAllSegments, loadCutSegments, loadMedia, openSendReportDialogWithState, reorderSegsByStartTime, setWorking, shiftAllSegmentTimes, shuffleSegments, toggleHelp, toggleSettings, tryFixInvalidDuration, userHtml5ifyCurrentFile, userOpenFiles]); + }, [apparentCutSegments, askSetStartTimeOffset, checkFileOpened, clearSegments, closeBatch, closeFileWithConfirm, concatCurrentBatch, createFixedDurationSegments, createNumSegments, customOutDir, cutSegments, detectedFps, extractAllStreams, fileFormat, filePath, fillSegmentsGaps, getFrameCount, invertAllSegments, loadCutSegments, loadMedia, openSendReportDialogWithState, reorderSegsByStartTime, setWorking, shiftAllSegmentTimes, shuffleSegments, toggleHelp, toggleSettings, tryFixInvalidDuration, userHtml5ifyCurrentFile, userOpenFiles]); const showAddStreamSourceDialog = useCallback(async () => { try { diff --git a/src/edlStore.js b/src/edlStore.js index 3e6f6416..98a61820 100644 --- a/src/edlStore.js +++ b/src/edlStore.js @@ -1,7 +1,7 @@ import JSON5 from 'json5'; import i18n from 'i18next'; -import { parseCuesheet, parseXmeml, parseCsv, parsePbf, parseMplayerEdl, formatCsvHuman, formatTsv, formatCsvFrames, formatCsvSeconds } from './edlFormats'; +import { parseCuesheet, parseXmeml, parseCsv, parsePbf, parseMplayerEdl, formatCsvHuman, formatTsv, formatCsvFrames, formatCsvSeconds, getTimeFromFrameNum } from './edlFormats'; import { askForYouTubeInput } from './dialogs'; const fs = window.require('fs-extra'); @@ -15,8 +15,9 @@ export async function loadCsvSeconds(path) { return parseCsv(await fs.readFile(path, 'utf-8')); } -export async function loadCsvFrames(path, getTimeFromFrameNum) { - return parseCsv(await fs.readFile(path, 'utf-8'), (frameNum) => getTimeFromFrameNum(frameNum)); +export async function loadCsvFrames(path, fps) { + if (!fps) throw new Error('The loaded file has an unknown framerate'); + return parseCsv(await fs.readFile(path, 'utf-8'), (frameNum) => getTimeFromFrameNum(fps, frameNum)); } export async function loadXmeml(path) { @@ -64,9 +65,10 @@ export async function loadLlcProject(path) { return JSON5.parse(await fs.readFile(path)); } -export async function readEdlFile({ type, path, getTimeFromFrameNum }) { + +export async function readEdlFile({ type, path, fps }) { if (type === 'csv') return loadCsvSeconds(path); - if (type === 'csv-frames') return loadCsvFrames(path, getTimeFromFrameNum); + if (type === 'csv-frames') return loadCsvFrames(path, fps); if (type === 'xmeml') return loadXmeml(path); if (type === 'cue') return loadCue(path); if (type === 'pbf') return loadPbf(path); @@ -78,7 +80,7 @@ export async function readEdlFile({ type, path, getTimeFromFrameNum }) { throw new Error('Invalid EDL type'); } -export async function askForEdlImport({ type, getTimeFromFrameNum }) { +export async function askForEdlImport({ type, fps }) { if (type === 'youtube') return askForYouTubeInput(); let filters; @@ -91,7 +93,7 @@ export async function askForEdlImport({ type, getTimeFromFrameNum }) { const { canceled, filePaths } = await dialog.showOpenDialog({ properties: ['openFile'], filters }); if (canceled || filePaths.length < 1) return []; - return readEdlFile({ type, path: filePaths[0], getTimeFromFrameNum }); + return readEdlFile({ type, path: filePaths[0], fps }); } export async function exportEdlFile({ type, cutSegments, filePath, getFrameCount }) { diff --git a/src/hooks/useFfmpegOperations.js b/src/hooks/useFfmpegOperations.js index 67846c66..b9e4f61b 100644 --- a/src/hooks/useFfmpegOperations.js +++ b/src/hooks/useFfmpegOperations.js @@ -391,6 +391,7 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps }) { if (!needsSmartCut) return smartCutMainPartOutPath; try { + if (!detectedFps) throw new Error('Smart cut is not possible when FPS is unknown'); const frameDuration = 1 / detectedFps; const encodeCutTo = Math.max(desiredCutFrom + frameDuration, smartCutFrom - frameDuration); // Subtract one frame so we don't end up with duplicates when concating, and make sure we don't create a 0 length segment