{"record":{"id":"6d452cae9cbe22a7","repo":"mifi/lossless-cut","slug":"invalid-duration","errorCode":null,"errorMessage":"Invalid duration","messagePattern":"Invalid duration","errorType":"exception","errorClass":"UserFacingError","httpStatus":null,"severity":"error","filePath":"src/renderer/src/App.tsx","lineNumber":2297,"sourceCode":"            // but in that case we also get: \"DEMUXER_ERROR_COULD_NOT_PARSE: FFmpegDemuxer: PTS is not defined 4\"\n            // and we don't want to auto convert in that case:\n            && !error.message?.startsWith('DEMUXER_ERROR_COULD_NOT_PARSE')\n          )\n          || error.code === PIPELINE_ERROR_DECODE\n        )\n        && !usingPreviewFile // if we are already using preview file, we shouldn't try to do it again\n        && filePath\n        && !(error.code === MEDIA_ERR_SRC_NOT_SUPPORTED && error.message?.startsWith('DEMUXER_ERROR_COULD_NOT_PARSE'))\n      ) {\n        if (workingRef.current) return;\n        try {\n          setWorking({ text: i18n.t('Converting to supported format') });\n\n          console.log('Trying to convert to supported format');\n\n          // A valid duration is needed to create a html5ified dummy (`fastest`).\n          if (!isDurationValid(parseFfprobeDuration(mainFileFormat?.duration))) {\n            throw new UserFacingError(i18n.t('Invalid duration'));\n          }\n\n          if (hasVideo || hasAudio) {\n            await html5ifyAndLoadWithPreferences(customOutDir, filePath, 'fastest', hasVideo, hasAudio);\n            showNotNativelySupportedMessage();\n          }\n        } catch (err) {\n          if (err instanceof UserFacingError) {\n            throw err;\n          }\n          console.error(err);\n          showPlaybackFailedMessage();\n        } finally {\n          setWorking(undefined);\n        }\n      } else if (error.code === PIPELINE_ERROR_READ) { // file is not readable or was removed\n        getSwal().toast.fire({ icon: 'error', timer: 10000, text: i18n.t('Failed to read file. Perhaps it has been moved?') });\n      }","sourceCodeStart":2279,"sourceCodeEnd":2315,"githubUrl":"https://github.com/mifi/lossless-cut/blob/3b9a59c288bf6e11076b583c932cfa48ddab3b02/src/renderer/src/App.tsx#L2279-L2315","documentation":"Thrown in App.tsx during the 'convert to supported format' recovery path when the main file's ffprobe duration fails isDurationValid(parseFfprobeDuration(...)). The html5ify-fastest dummy-file strategy needs a valid duration to construct a seekable preview, so an unknown/invalid duration aborts the conversion attempt. It is re-thrown as a UserFacingError to bypass the generic 'playback failed' fallback.","triggerScenarios":"A media file the browser cannot play natively triggers the recovery path, but ffprobe reported no duration (format.duration undefined/NaN/'N/A'); a live stream or growing file whose duration is not yet known; a container ffprobe read without a duration field.","commonSituations":"Uncommon containers (e.g. some MKV/AV1/HEVC variants) the browser refuses, combined with a missing duration; live streams; partially-recorded files still open in another process; corrupted headers where ffprobe parsed format but not duration.","solutions":["Re-mux the file so ffprobe reports a duration: `ffmpeg -i in -c copy out.mkv`.","Provide the duration manually if the app allows it, or disable the html5ify preview to use direct playback.","If the file is a live stream, stop recording and finalize the container before opening.","Repair the container (e.g. untrunc for MP4) to restore the duration metadata."],"exampleFix":"// before\nif (!isDurationValid(parseFfprobeDuration(mainFileFormat?.duration))) {\n  throw new UserFacingError(i18n.t('Invalid duration'));\n}\n\n// after\nconst duration = parseFfprobeDuration(mainFileFormat?.duration);\nif (!isDurationValid(duration)) {\n  showPlaybackFailedMessage(); // skip html5ify, let user try native playback\n  return;\n}","handlingStrategy":"validation","validationCode":"import { isDurationValid, parseFfprobeDuration } from './duration';\nconst duration = parseFfprobeDuration(mainFileFormat?.duration);\nif (!isDurationValid(duration)) {\n  // skip html5ify, do not throw\n  showPlaybackFailedMessage();\n  return;\n}","typeGuard":"const hasValidDuration = (fmt: { duration?: unknown }): boolean => {\n  const d = typeof fmt?.duration === 'string' ? parseFloat(fmt.duration) : fmt?.duration;\n  return typeof d === 'number' && Number.isFinite(d) && d > 0;\n};","tryCatchPattern":"try {\n  await html5ifyAndLoadWithPreferences(...);\n} catch (err) {\n  if (err instanceof UserFacingError && /Invalid duration/.test(err.message)) {\n    showPlaybackFailedMessage();\n    return;\n  }\n  throw err;\n}","preventionTips":["Re-mux sources so ffprobe reports a duration before opening them.","Finalize live recordings before opening (stop the writer so the container gets its duration).","Provide a manual duration override when the container omits one.","Repair broken containers (untrunc, re-mux) to restore duration metadata."],"tags":["playback","html5ify","duration","ffprobe","recovery"],"backgroundTag":null,"analyzedSha":"3b9a59c288bf6e11076b583c932cfa48ddab3b02","analyzedAt":"2026-08-12T20:54:25.651Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}