{"record":{"id":"b0d77885a1ab3939","repo":"heygen-com/hyperframes","slug":"grade-analysis-failed-for-mediapath-message","errorCode":null,"errorMessage":"grade analysis failed for ${mediaPath}: ${message}","messagePattern":"grade analysis failed for (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/mediaGradeAnalyzer.ts","lineNumber":348,"sourceCode":"        mediaPath,\n        \"-vf\",\n        filters,\n        \"-frames:v\",\n        String(SAMPLE_FRAMES),\n        \"-f\",\n        \"null\",\n        \"-\",\n      ],\n      {\n        encoding: \"utf8\",\n        timeout: Number(process.env.HYPERFRAMES_ANALYZE_TIMEOUT_MS) || DEFAULT_TIMEOUT_MS,\n        stdio: [\"ignore\", \"pipe\", \"pipe\"],\n      },\n    );\n    return summarizeMediaTreatmentAnalysis(probe, parseMediaTreatmentSignalStats(raw));\n  } catch (error) {\n    const message = error instanceof Error ? error.message : String(error);\n    throw new Error(`grade analysis failed for ${mediaPath}: ${message}`);\n  }\n}\n\nexport function formatMeasuredNote(\n  mediaPath: string,\n  measured: MediaTreatmentMeasurements,\n): string {\n  return `media-use: measured ${basename(mediaPath)}: frames=${measured.frames}, YMIN=${measured.yMin}, YLOW=${measured.yLow}, YAVG=${measured.yAvg}, YHIGH=${measured.yHigh}, YMAX=${measured.yMax}, UAVG=${measured.uAvg}, VAVG=${measured.vAvg}; adjust is a starting suggestion`;\n}\n","sourceCodeStart":330,"sourceCodeEnd":358,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/core/src/mediaGradeAnalyzer.ts#L330-L358","documentation":"The catch-all wrapper in analyzeMediaGrade(). Every failure in the probe-or-measure sequence — execFileSync throwing (binary missing, non-zero exit, timeout), or summarizeFrames() throwing error 340 — is re-thrown with the media path prepended and the underlying message preserved. It is the single error surface callers see from the public analyzeMediaGrade() API.","triggerScenarios":"Calling analyzeMediaGrade(mediaPath, { ffmpegPath?, ffprobePath? }) when: ffmpeg/ffprobe binaries are not on PATH (ENOENT), the input path does not exist or is unreadable, ffmpeg exits non-zero on a malformed file, the run exceeds `HYPERFRAMES_ANALYZE_TIMEOUT_MS` (default 15000ms), or the inner parser produced zero frames (error 340). probeMedia() itself swallows errors and returns `unknown`, so it never reaches this catch.","commonSituations":"CI/containers where ffmpeg is not installed; Docker image missing the ffmpeg dependency; Windows hosts where ffmpeg is not on PATH; rendering on a slow machine where 5-frame sampling of a long GOP video exceeds 15s; passing a relative path from the wrong cwd; pointing at a file still being written by an upstream render step.","solutions":["Read the `${message}` suffix first — ENOENT means the ffmpeg/ffprobe binary is missing, a numeric exit code means ffmpeg rejected the input, and `Command timed out` means raise HYPERFRAMES_ANALYZE_TIMEOUT_MS.","Ensure ffmpeg and ffprobe are installed and resolvable: `ffmpeg -version` and `ffprobe -version` must succeed in the same shell/env the render runs in.","Pass explicit paths via options: `analyzeMediaGrade(path, { ffmpegPath: \"/usr/bin/ffmpeg\", ffprobePath: \"/usr/bin/ffprobe\" })`.","Verify the path exists and is a complete file (`fs.statSync`), not a pipe or partial write.","If the suffix is the error-340 message, follow the 340 remediation (no analyzable video frames)."],"exampleFix":"// before\nconst a = analyzeMediaGrade(mediaPath);\n\n// after\ntry {\n  const a = analyzeMediaGrade(mediaPath, { ffmpegPath: process.env.FFMPEG_PATH });\n} catch (e) {\n  if (!/grade analysis failed/.test(String(e))) throw e;\n  console.warn(`skipping grade analysis: ${e.message}`); // non-fatal, fall back to default grade\n}","handlingStrategy":"try-catch","validationCode":"import { existsSync, statSync } from \"node:fs\";\nimport { execFileSync } from \"node:child_process\";\n\nfunction preflightMedia(path: string, ffmpegPath = \"ffmpeg\", ffprobePath = \"ffprobe\"): void {\n  if (!existsSync(path) || !statSync(path).isFile()) throw new Error(`media not found: ${path}`);\n  for (const bin of [ffmpegPath, ffprobePath]) {\n    try { execFileSync(bin, [\"-version\"], { stdio: \"ignore\" }); }\n    catch { throw new Error(`missing binary on PATH: ${bin}`); }\n  }\n}","typeGuard":"null","tryCatchPattern":"let analysis;\ntry {\n  analysis = analyzeMediaGrade(mediaPath, { ffmpegPath, ffprobePath });\n} catch (e) {\n  const msg = e instanceof Error ? e.message : String(e);\n  if (/Command timed out|ENOENT|grade analysis failed/.test(msg)) {\n    logger.warn(`grade analysis unavailable (${msg}); using default grade`);\n    analysis = defaultAnalysis();\n  } else throw e;\n}","preventionTips":["Install ffmpeg+ffprobe in every environment that runs the render (Docker, CI, local).","Treat grade analysis as non-fatal: wrap in try/catch and degrade to a default grade.","Pass explicit ffmpegPath/ffprobePath rather than relying on PATH in containers.","Set HYPERFRAMES_ANALYZE_TIMEOUT_MS high enough for the largest source you grade."],"tags":["ffmpeg","media-analysis","wrapper","execfile"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}