{"record":{"id":"0c9b0f7e8863a59c","repo":"jeecgboot/JeecgBoot","slug":"ffmpeg-exitcode","errorCode":null,"errorMessage":"FFmpeg 合成失败，退出码: {exitCode}","messagePattern":"FFmpeg 合成失败，退出码: (.+?)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/video/service/impl/VideoGenerationServiceImpl.java","lineNumber":634,"sourceCode":"     */\n    private void mergeVideoAudio(Path videoPath, Path audioPath, Path outputPath) throws IOException, InterruptedException {\n        ProcessBuilder pb = new ProcessBuilder(\n                ffmpegPath,\n                \"-i\", videoPath.toAbsolutePath().toString(),\n                \"-i\", audioPath.toAbsolutePath().toString(),\n                \"-c:v\", \"copy\",\n                \"-c:a\", \"aac\",\n                \"-shortest\",\n                \"-y\",\n                outputPath.toAbsolutePath().toString()\n        );\n        pb.redirectErrorStream(true);\n        Process process = pb.start();\n        String output = new String(process.getInputStream().readAllBytes());\n        int exitCode = process.waitFor();\n        if (exitCode != 0) {\n            log.error(\"FFmpeg 合成失败: {}\", output);\n            throw new RuntimeException(\"FFmpeg 合成失败，退出码: \" + exitCode);\n        }\n    }\n\n    /**\n     * 将 size（如 \"1920x1080\"）转为 vidu2 的 aspect_ratio 格式（如 \"16:9\"）\n     * 支持的比例：16:9、9:16、1:1，无法识别时默认 16:9\n     */\n    private String convertSizeToAspectRatio(String size) {\n        if (size == null || size.isBlank()) {\n            return \"16:9\";\n        }\n        // 已经是比例格式则直接返回\n        if (size.matches(\"\\\\d+:\\\\d+\")) {\n            return size;\n        }\n        // 解析 WxH 格式\n        String[] parts = size.toLowerCase().split(\"x\");\n        if (parts.length == 2) {","sourceCodeStart":616,"sourceCodeEnd":652,"githubUrl":"https://github.com/jeecgboot/JeecgBoot/blob/96fb33f5ec68516da0b0147da06b2eb0419e063a/jeecg-boot/jeecg-boot-module/jeecg-boot-module-airag/src/main/java/org/jeecg/modules/airag/video/service/impl/VideoGenerationServiceImpl.java#L616-L652","documentation":"Thrown by VideoGenerationServiceImpl.mergeVideoAudio() when the ffmpeg subprocess (which muxes a silent video and an audio track with -c:v copy -c:a aac -shortest) exits non-zero. The captured combined stdout/stderr is logged separately but omitted from the thrown message.","triggerScenarios":"mergeVideoAudio() runs ffmpegPath against silentVideoFile + audioPath. Non-zero exit when: ffmpegPath invalid/missing; either input file absent; video stream copy fails because the source container/codec is incompatible; the audio sample format is unsupported by aac; output directory not writable.","commonSituations":"ffmpeg not installed or ffmpegPath misconfigured; upstream silent video never written (so -i points at nothing); wav audio that ffmpeg cannot decode; permission denied on the upload/video output dir.","solutions":["Read the 'FFmpeg 合成失败: {}' log line for the actual ffmpeg stderr.","Verify ffmpegPath with `ffmpeg -version` and that both input files exist before calling (see autoAddVoiceover's existing Files.exists logging).","If -c:v copy fails due to codec mismatch, re-encode instead: replace \"copy\" with \"libx264\" for -c:v.","Confirm the output directory is writable and has disk space."],"exampleFix":"// before\n\"-c:v\", \"copy\",\n// after - fall back to re-encode when stream copy is incompatible\n\"-c:v\", \"libx264\", \"-preset\", \"fast\",","handlingStrategy":"validation","validationCode":"private void ensureFfmpegInputs(Path video, Path audio, Path out) throws IOException {\n    if (ffmpegPath == null) { throw new IllegalStateException(\"ffmpeg 未配置\"); }\n    if (!Files.exists(video)) { throw new FileNotFoundException(video.toString()); }\n    if (!Files.exists(audio)) { throw new FileNotFoundException(audio.toString()); }\n    Files.createDirectories(out.getParent());\n}","typeGuard":null,"tryCatchPattern":"// mergeVideoAudio is only called from autoAddVoiceover, which already wraps in try/catch\n// and degrades to a silent video -- retain that fallback rather than rethrowing.","preventionTips":["Pre-validate ffmpegPath with `ffmpeg -version` during app startup.","Log Files.exists() for both inputs right before invoking ffmpeg (already done in autoAddVoiceover).","Prefer re-encoding (-c:v libx264) when stream-copy compatibility is uncertain."],"tags":["ffmpeg","subprocess","video","codec","external-process","io"],"backgroundTag":null,"analyzedSha":"96fb33f5ec68516da0b0147da06b2eb0419e063a","analyzedAt":"2026-08-14T00:04:16.786Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}