diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-24 15:57:41 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-24 15:57:41 +0200 |
| commit | c5585beab3d6adefaa2ef9444946dd3816960a7c (patch) | |
| tree | a321e540c2db0458716d526e4a045fca123937b2 /packages/meshbay-node/src/meshbay_node/media_probe.py | |
| parent | 317f09328ed8bf20148b707470c9b0fe82e59575 (diff) | |
| download | meshbay-c5585beab3d6adefaa2ef9444946dd3816960a7c.tar.gz | |
fix(node,hub): HEVC transcode fallback, live-add progress, per-group TMDB toggle
Three bugs found live testing the Videos app against a real HEVC/EAC3 show,
plus a design change requested afterward:
- Streaming always did "-c:v copy", which faithfully reports a source's real
hev1 codec string but is unplayable in a browser with no HEVC decoder
(most Chrome/Linux builds). The node now transcodes to H264 whenever the
probed codec is browser-incompatible (media_probe.py's new
BROWSER_INCOMPATIBLE_VIDEO_CODECS), with a `transcode_incompatible_video`
node.toml opt-out for operators who know their viewers already decode it.
- Dropping a whole season into an already-watched folder gave no scanning
indicator and no progress bar: IndexProgress was only ever updated by the
two bulk scan paths, never by the real-time per-file watchdog path
(_schedule_update/_debounce/_update_entry). That path now accounts a
"burst" the same way, without double-counting a file rewritten mid-debounce.
- A stray literal "0" rendered in the video detail modal when there was no
TMDB match (`meta.confidence` is 0, and `0 && x` renders "0" in JSX/htm,
not nothing) — `confident` is now a real boolean.
- Whether TMDB is used at all moves from a node-wide setting to per-group
(OP_TMDB_ENABLED/tmdb_enabled/tmdb_enabled_ack, scoped like OP_VIDEO_ROOT):
an operator running a real media-library group alongside test/demo groups
on one node wants outbound TMDB traffic for the one that needs it, not all
of them. The custom API token and query language stay node-wide, one
shared credential/cache (tmdb_config/OP_TMDB_CONFIG, unchanged reasoning).
MNP_VERSION 0.6 -> 0.7, additive.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAmyXtc6dAADsH23ydXQpY
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/media_probe.py')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/media_probe.py | 30 |
1 files changed, 24 insertions, 6 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/media_probe.py b/packages/meshbay-node/src/meshbay_node/media_probe.py index 8ad883d..6dd3baa 100644 --- a/packages/meshbay-node/src/meshbay_node/media_probe.py +++ b/packages/meshbay-node/src/meshbay_node/media_probe.py @@ -11,11 +11,22 @@ import json _H264_PROFILES = {"Baseline": "42", "Main": "4d", "High": "64", "High 10": "6e"} +# Source video codecs whose MSE codec string is real but which no mainstream +# browser can actually decode via MediaSource on most desktop platforms — HEVC +# has no royalty-free decoder in Chrome/Firefox on Linux (and is spotty even +# on platforms with one). Found live: a real HEVC/EAC3 WEB-DL reported +# "Codec not supported for streaming: hev1.1.6.L93.B0,mp4a.40.2" from +# MediaSource.isTypeSupported, even though ffprobe/VLC play it fine. VP9/AV1 +# are not in this set — those decode natively in every mainstream browser. +BROWSER_INCOMPATIBLE_VIDEO_CODECS = frozenset({"hevc"}) -async def probe_video(path: str) -> tuple[str | None, float, bool, int | None, int | None]: + +async def probe_video( + path: str, +) -> tuple[str | None, float, bool, int | None, int | None, str | None]: """ Probe video file with ffprobe, return (MSE codec string, duration, - has_audio, width, height). + has_audio, width, height, raw video codec name). The audio half of the codec string is always "mp4a.40.2" (AAC-LC) or absent — never the source's real audio codec — because the streaming @@ -24,8 +35,13 @@ async def probe_video(path: str) -> tuple[str | None, float, bool, int | None, i that (AC-3, E-AC-3, DTS, ...) is at best silently unplayable and at worst, for E-AC-3 at least, makes ffmpeg itself refuse to write the fragmented MP4 header ("Cannot write moov atom before EAC3 packets - parsed" — reproduced against a real 5.1 E-AC-3 WEB-DL). Video stays - whatever it actually is: it is always copied, never transcoded. + parsed" — reproduced against a real 5.1 E-AC-3 WEB-DL). Video is copied + whenever the browser can decode it directly; the raw codec name is + returned alongside the MSE string so the caller can decide whether this + source needs a real re-encode instead (BROWSER_INCOMPATIBLE_VIDEO_CODECS + above) — the MSE string alone can't drive that decision, since it still + faithfully reports "hev1..." for a source this pipeline cannot actually + deliver copied. width/height come from the same ffprobe call (one extra `-show_entries` field, no second process spawn) — resolution is deliberately never @@ -43,12 +59,14 @@ async def probe_video(path: str) -> tuple[str | None, float, bool, int | None, i duration = float(info.get("format", {}).get("duration", 0)) v_codec = "" + raw_codec_name: str | None = None has_audio = False width: int | None = None height: int | None = None for s in info.get("streams", []): if s.get("codec_type") == "video" and not v_codec: cn = s.get("codec_name", "") + raw_codec_name = cn or None if cn == "h264": p = _H264_PROFILES.get(s.get("profile", "High"), "64") lvl = int(s.get("level", 40)) @@ -65,6 +83,6 @@ async def probe_video(path: str) -> tuple[str | None, float, bool, int | None, i has_audio = True if not v_codec: - return None, duration, has_audio, width, height + return None, duration, has_audio, width, height, raw_codec_name codec = f"{v_codec},mp4a.40.2" if has_audio else v_codec - return codec, duration, has_audio, width, height + return codec, duration, has_audio, width, height, raw_codec_name |