{"record":{"id":"01718d8788d34b36","repo":"tonhowtf/omniget","slug":"no-stdout-from-ffmpeg","errorCode":null,"errorMessage":"No stdout from ffmpeg","messagePattern":"No stdout from ffmpeg","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/ffmpeg.rs","lineNumber":351,"sourceCode":"\n    args.extend([\n        \"-progress\".to_string(),\n        \"pipe:1\".to_string(),\n        \"-nostats\".to_string(),\n        opts.output_path.clone(),\n    ]);\n\n    let mut child = crate::core::process::command(\"ffmpeg\")\n        .args(&args)\n        .stdout(std::process::Stdio::piped())\n        .stderr(std::process::Stdio::piped())\n        .spawn()\n        .map_err(|e| anyhow!(\"Failed to start ffmpeg: {}\", e))?;\n\n    let stdout = child\n        .stdout\n        .take()\n        .ok_or_else(|| anyhow!(\"No stdout from ffmpeg\"))?;\n    let reader = BufReader::new(stdout);\n    let mut lines = reader.lines();\n\n    let cancel = cancel_token.clone();\n    let progress = progress_tx.clone();\n    let line_reader = tokio::spawn(async move {\n        while let Ok(Some(line)) = lines.next_line().await {\n            if cancel.is_cancelled() {\n                break;\n            }\n            if let Some(us) = parse_out_time_us(&line) {\n                if total_duration_us > 0 {\n                    let pct = (us as f64 / total_duration_us as f64 * 100.0).min(100.0);\n                    let _ = progress.send(ProgressUpdate::percent(pct)).await;\n                }\n            }\n        }\n    });","sourceCodeStart":333,"sourceCodeEnd":369,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/ffmpeg.rs#L333-L369","documentation":"After spawning ffmpeg, convert() takes the piped stdout handle. If the handle is missing (Stdio::piped() was not applied or was already consumed), it fails with this error. In practice this is an internal invariant: the spawn above always pipes stdout, so this fires only if that contract is broken.","triggerScenarios":"Essentially only from a code regression where .stdout(std::process::Stdio::piped()) is removed or a Child is reused/standard streams taken twice before convert reads it.","commonSituations":"Refactoring the spawn chain and accidentally dropping the piped stdout; wrapping the command in a helper that sets its own stdio config; tests mocking the process without a stdout pipe.","solutions":["Verify the spawn chain still includes .stdout(std::process::Stdio::piped()) exactly once.","Ensure no other code calls child.stdout.take() before convert reads it.","Refactor to construct the Command inside convert rather than passing a pre-configured Child.","Treat it as a bug report if it reproduces: file with the spawn configuration in use."],"exampleFix":"// before\nlet stdout = child\n    .stdout\n    .take()\n    .ok_or_else(|| anyhow!(\"No stdout from ffmpeg\"))?;\n// after\nlet stdout = child.stdout.take().ok_or_else(|| {\n    anyhow!(\n        \"No stdout from ffmpeg: stdout was not piped or was already taken (internal config bug)\"\n    )\n})?;","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"fn piped_child(child: &std::process::Child) -> Result<&std::process::Child, String> {\n    if child.stdout.is_some() { Ok(child) } else { Err(\"child stdout is not piped\".into()) }\n}","tryCatchPattern":"match convert(/* ... */).await {\n    Err(e) if e.to_string().contains(\"No stdout from ffmpeg\") => {\n        // internal bug: report to maintainers with the spawn configuration\n        report_internal_bug(e);\n    }\n    other => other?,\n}","preventionTips":["Keep Stdio::piped() configuration adjacent to spawn in the same function","Write a unit test asserting the spawned command's stdout is piped","Take child.stdout exactly once, immediately after spawn"],"tags":["ffmpeg","process-pipe","internal-invariant","stdio"],"backgroundTag":"internal-invariant-violation","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}