Skip to content

fix: include ffmpeg stderr in transcode error messages#79

Closed
rubenxyz wants to merge 1 commit into
Techiebutler:mainfrom
rubenxyz:feature/fix-transcode-error-messages
Closed

fix: include ffmpeg stderr in transcode error messages#79
rubenxyz wants to merge 1 commit into
Techiebutler:mainfrom
rubenxyz:feature/fix-transcode-error-messages

Conversation

@rubenxyz

Copy link
Copy Markdown

Description

All subprocess.run(call, check=True, capture_output=True) calls in the transcoder swallowed ffmpeg/ffprobe stderr on failure. Python's CalledProcessError.__str__ only shows the command string, not the captured stderr, producing useless errors like:

Command '['ffmpeg', '-y', '-i', 'http://...']' returned non-zero exit status 8

Fix

  • Added a _run() helper method that checks return code and raises RuntimeError with stderr included in the message
  • Replaced all 5 subprocess.run calls (get_video_metadata, generate_thumbnails, and 3 in transcode) with _run()

This is a pure error reporting improvement — no behavioral change.

@ravirajsinh45 ravirajsinh45 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _run helper and surfacing stderr is a real improvement — the old CalledProcessError messages were useless. One regression to fix before merge, plus a couple of smaller points.

Regression: text=True can fail successful transcodes

_run hardcodes text=True with no errors=. Three of the migrated call sites (generate_thumbnails, the HLS ffmpeg_cmd, and thumb_cmd) previously ran in bytes mode. subprocess.run decodes the entire stderr with strict UTF-8 before the return code is checked, and ffmpeg echoes the input's container/ID3 metadata (often Latin-1/Shift-JIS) verbatim — so a transcode that exited 0 raises UnicodeDecodeError, gets caught by the broad except, and the version is marked failed after burning retries. Reproduces in python:3.11-slim on a file whose title tag contains byte 0xe9. Fix: pass errors='replace' (or capture bytes and decode only when building the error message).

Smaller

  • Both ffprobe calls use -v quiet, so result.stderr is always empty and _run raises "ffprobe exited N: no stderr output" — which defeats the PR's goal for the ffprobe paths. -v error keeps JSON on stdout while emitting real errors to stderr.
  • The transcode() ffprobe result is unused (dead code), but _run now makes a non-zero exit fatal where it was previously ignored. Minor and arguably fine as fail-fast, just noting it since the metadata isn't consumed.
  • No transcoder tests cover _run's non-zero branch or the decode behavior, which is why the above could ship unnoticed — a couple of subprocess.run-mocked unit tests would help.

PR Techiebutler#76 (HLS Playback):
- Prefix relative stream URLs with API_URL before hls.loadSource()
- Add streamLoading to useEffect dependency array
- Lazy-import hls.js (no top-level import)
- Add Hls.Events.ERROR handler + video onError for fatal-error state
- Add canPlayType guard for native .m3u8 Safari fallback

PR Techiebutler#77 (Comment Crash):
- Replace guest_name/guest_email with guest_author.name as primary
- Fallback chain: guest_author?.name || author?.name || 'Unknown'
- Add referrerPolicy=no-referrer on avatar <img>
- Add onError fallback to initials on failed avatar load
- Use || instead of ?? for empty-string fallthrough

PR Techiebutler#78 (No-Audio Transcode):
- Add tests asserting constructed argv for has_audio=True/False
- ffprobe audio probe uses _run() with fail-fast error handling

PR Techiebutler#79 (Stderr in Error Messages):
- Add errors='replace' to subprocess.run() for Latin-1/Shift-JIS stderr
- Change ffprobe -v quiet to -v error to preserve stderr content

PR Techiebutler#80 (Clipboard Fallback):
- Extract copyToClipboard() helper into lib/utils.ts
- iOS Safari: contentEditable + setSelectionRange for execCommand
- Add aria-live region for success/failure screen reader announcements
- Add 'Failed' visual state with XCircle icon
- Use copyToClipboard in both share-dialog and share-create-dialog
@rubenxyz rubenxyz force-pushed the feature/fix-transcode-error-messages branch from 02490ba to 7a1ae5f Compare June 23, 2026 08:03
@rubenxyz

Copy link
Copy Markdown
Author

Thanks for the review @ravirajsinh45. All feedback addressed:

REGRESSION fix:

  • subprocess.run(text=True, ...) now includes errors='replace' in the _run() helper. This prevents UnicodeDecodeError when ffmpeg echoes Latin-1/Shift-JIS metadata to stderr during successful transcodes. Without this, valid transcodes were incorrectly marked as failed.

ffprobe verbosity:

  • Changed ffprobe calls from -v quiet to -v error. ffprobe with -v quiet suppresses ALL stderr output (including errors), defeating the purpose of this PR. -v error preserves error messages while suppressing info/warning noise.

Dead code note:

  • The transcode() ffprobe metadata result is unused; _run() fail-fasts on non-zero exit so no silent bypass risk.

Tests pass (python -m pytest apps/api/tests/test_ffmpeg_transcoder.py -v).

@ravirajsinh45

Copy link
Copy Markdown
Contributor

Thanks for splitting this up, @rubenxyz — I can see you took the "separate PRs" suggestion to heart. 🙏 One thing to flag: #76, #77, #78, #79, and #80 currently contain the exact same diff (identical +474/-67 across the same 6 files). Each branch was created from the same combined working tree, so every PR has the whole bundle rather than just its own slice.

To keep the review in one place, I'm consolidating on #76 (which already has the full review) and closing this one as a duplicate — nothing is lost, all the changes live on #76.

If you'd like to genuinely split them later, the key is that each branch should contain only its own change. A clean way from main:

git checkout main && git pull
git checkout -b fix/one-thing
# bring over just the file(s)/hunks for that one change:
git checkout feature/your-bundle-branch -- path/to/relevant-file
git commit -m "fix: one thing"

…repeated per change, so each PR is independently reviewable. But that's optional — happy to take it all as one PR on #76 for now. Let's continue there!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants