{"record":{"id":"e09e5cd933c5cdd8","repo":"mifi/lossless-cut","slug":"smart-cut-is-not-possible-when-fps-is-unknown","errorCode":null,"errorMessage":"Smart cut is not possible when FPS is unknown","messagePattern":"Smart cut is not possible when FPS is unknown","errorType":"exception","errorClass":"UserFacingError","httpStatus":null,"severity":"error","filePath":"src/renderer/src/hooks/useFfmpegOperations.ts","lineNumber":655,"sourceCode":"        invariant(sourceCodecParams.videoBitrate != null);\n        invariant(sourceCodecParams.videoTimebase != null);\n        invariant(filePath != null);\n        invariant(outFormat != null);\n        await cutEncodeSmartPart({ cutFrom, cutTo, outPath, outFormat, videoCodec, videoBitrate: encCustomBitrate != null ? encCustomBitrate * 1000 : sourceCodecParams.videoBitrate, videoStreamIndex: videoStream.index, videoTimebase: sourceCodecParams.videoTimebase, allFilesMeta, copyFileStreams: copyFileStreamsFiltered, ffmpegExperimental, hasBFrames: sourceCodecParams.videoStream.has_b_frames });\n      }\n\n      const cutEncodeWholePart = async () => {\n        await cutEncodeSmartPartWrapper({ cutFrom: desiredCutFrom, cutTo, outPath: finalOutPath });\n        return { path: finalOutPath, created: true };\n      };\n\n      if (lossyMode) {\n        console.log('Lossy mode: cutting/encoding the whole segment', { desiredCutFrom, cutTo });\n        return cutEncodeWholePart();\n      }\n\n      const { losslessCutFrom, segmentNeedsSmartCut } = await needsSmartCut({ path: filePath, desiredCutFrom, videoStream });\n      if (segmentNeedsSmartCut && !detectedFps) throw new UserFacingError(i18n.t('Smart cut is not possible when FPS is unknown'));\n      console.log('Smart cut on video stream', videoStream.index);\n\n      // If we are cutting within two keyframes, just encode the whole part and return that\n      // See https://github.com/mifi/lossless-cut/pull/1267#issuecomment-1236381740\n      if (segmentNeedsSmartCut && losslessCutFrom > cutTo) {\n        console.log('Segment is between two keyframes, cutting/encoding the whole segment', { desiredCutFrom, losslessCutFrom, cutTo });\n        return cutEncodeWholePart();\n      }\n\n      invariant(outFormat != null);\n\n      const ext = getOutFileExtension({ isCustomFormatSelected: true, outFormat, filePath });\n\n      if (segmentNeedsSmartCut) {\n        console.log('Cutting/encoding lossless part', { from: losslessCutFrom, to: cutTo });\n      }\n\n      const losslessPartOutPath = segmentNeedsSmartCut","sourceCodeStart":637,"sourceCodeEnd":673,"githubUrl":"https://github.com/mifi/lossless-cut/blob/3b9a59c288bf6e11076b583c932cfa48ddab3b02/src/renderer/src/hooks/useFfmpegOperations.ts#L637-L673","documentation":"Thrown during smart-cut export when a segment is flagged as needing a smart (re-encode) cut but the detected video FPS is falsy. Smart cut must convert between frame numbers and timestamps and re-encode the partial GOP, both of which require a known frame rate; without it the cut would be frame-inaccurate. The guard fires after needsSmartCut() reports segmentNeedsSmartCut and before the encode begins.","triggerScenarios":"Exporting with smart cut enabled on a video stream whose ffprobe reported no avg_frame_rate / r_frame_rate (variable frame rate, or a container that omits the field); a stream whose FPS parsed to 0/NaN; enabling smart cut on an image sequence or still stream.","commonSituations":"VFR (variable frame rate) screen recordings or mobile clips; streams with FPS as '0/0' in ffprobe; damaged headers; mixing a no-FPS stream into a smart-cut job; force-enabling smart cut on a stream type it does not support.","solutions":["Disable smart cut (normal/keyframe cut) for streams whose FPS cannot be detected.","Detect and supply the FPS explicitly: parse avg_frame_rate/r_frame_rate and fall back to a manual value if absent.","Re-mux/re-encode the source with a fixed frame rate so ffprobe reports it (ffmpeg -r).","Validate detectedFps is a finite positive number before entering the smart-cut code path."],"exampleFix":"// before\nif (segmentNeedsSmartCut && !detectedFps) throw new UserFacingError(...);\n\n// after\nconst fps = detectedFps || fallbackFpsFromProbe(videoStream);\nif (segmentNeedsSmartCut && !fps) {\n  // degrade gracefully to lossless keyframe cut instead of throwing\n  return cutEncodeWholePart();\n}","handlingStrategy":"validation","validationCode":"// Detect FPS robustly from ffprobe stream fields\nexport function detectFps(stream: { avg_frame_rate?: string; r_frame_rate?: string }): number | undefined {\n  const parse = (s?: string) => {\n    if (!s) return undefined;\n    const [n, d] = s.split('/').map(Number);\n    if (!Number.isFinite(n) || !Number.isFinite(d) || d === 0) return undefined;\n    const fps = n / d;\n    return Number.isFinite(fps) && fps > 0 ? fps : undefined;\n  };\n  return parse(stream.avg_frame_rate) ?? parse(stream.r_frame_rate);\n}\nconst detectedFps = detectFps(videoStream);\nif (!detectedFps && wantSmartCut) throw new Error('Cannot smart cut: FPS unknown');","typeGuard":"const hasUsableFps = (fps: number | undefined): fps is number => typeof fps === 'number' && Number.isFinite(fps) && fps > 0;","tryCatchPattern":"try {\n  await smartCutExport(...);\n} catch (err) {\n  if (err instanceof UserFacingError && /FPS is unknown/.test(err.message)) {\n    // fall back to lossless keyframe cut\n    await losslessCutExport(...);\n    return;\n  }\n  throw err;\n}","preventionTips":["Parse avg_frame_rate and r_frame_rate defensively (handle 0/0 and N/A).","Let the user supply a manual FPS override for VFR/unknown streams.","Disable the smart-cut option in the UI when no FPS can be detected.","Re-encode VFR sources to a constant frame rate before smart cutting."],"tags":["smart-cut","ffmpeg","fps","export","video"],"backgroundTag":null,"analyzedSha":"3b9a59c288bf6e11076b583c932cfa48ddab3b02","analyzedAt":"2026-08-12T20:54:25.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}