diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-17 15:06:57 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-17 15:06:57 +0200 |
| commit | d472725167c1d335996059d24b7b74f3730768fb (patch) | |
| tree | 1ffca323c9dfe682c8fff2a20da1cfae0106f830 /packages/meshbay-node/src/meshbay_node | |
| parent | 30980ba0994d3e99d41c5252555320d6cbb1f5a9 (diff) | |
| download | meshbay-d472725167c1d335996059d24b7b74f3730768fb.tar.gz | |
feat: tell a forced subtitle track from a full one
Reported as "I click a subtitle and nothing appears", on three films. Nothing
was broken. The track selected was the container's forced track, which carries
signage and foreign dialogue only: measured on the film in question, 30 cues
and 77 seconds of text across 2h32 — 0.8% of the running time, against 1559
cues and 41.8% for the full track sitting beside it under the same language
tag. At all three positions tested there was genuinely no cue to show; the
full track would have shown one at two of them.
So the defect is that the menu could not say which was which. The label used
the container's title tag, which said "Forced" on that film and says nothing
at all on most, and no other field was carried. The disposition is the half
that is always there: `probe_video` now reads `forced` and `hearing_impaired`,
`stream_init` carries them, and the label states them in the reader's own
language rather than repeating an English word a muxer happened to type.
The node fixture grows a forced track with no title, because a title would
let the old code pass. The label harness's `t` stub took a parameters object
unconditionally and threw on a key that has none — a fixture narrower than
production, fixed here rather than worked around.
Also removes the activeCues probe that found this. It answered its question:
mode showing, cues 30, active 0, none due at that instant.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UGY17EPph5LsLzePPXhUVc
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/media_probe.py | 14 | ||||
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py | 6 |
2 files changed, 20 insertions, 0 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/media_probe.py b/packages/meshbay-node/src/meshbay_node/media_probe.py index 06b1e28..81174c8 100644 --- a/packages/meshbay-node/src/meshbay_node/media_probe.py +++ b/packages/meshbay-node/src/meshbay_node/media_probe.py @@ -69,6 +69,16 @@ class SubtitleTrack: language: str | None title: str | None codec_name: str | None + # **A forced track is not a shorter version of the full one.** It carries + # only signage and the lines spoken in another language — measured on a + # real film: 30 cues and 77 seconds of text across 2h32, against 1559 cues + # and 41% of the running time for the full track beside it. Picking it and + # seeing nothing for ten minutes is the ordinary outcome, not a fault, and + # nothing in the menu let a viewer tell those two apart. The disposition is + # what says which it is; the title tag that would also say it ("Forced", + # "SDH") is absent as often as it is present. + forced: bool = False + hearing_impaired: bool = False @dataclass @@ -143,6 +153,7 @@ async def probe_video(path: str) -> VideoProbe: "-show_entries", "stream=codec_name,profile,level,codec_type,width,height,channels", "-show_entries", "stream_tags=language,title", + "-show_entries", "stream_disposition=forced,hearing_impaired", "-show_entries", "format=duration", "-of", "json", path, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, @@ -192,11 +203,14 @@ async def probe_video(path: str) -> VideoProbe: if codec_name not in TEXT_SUBTITLE_CODECS: continue tags = s.get("tags") or {} + disp = s.get("disposition") or {} subtitle_tracks.append(SubtitleTrack( ordinal=ordinal, language=(tags.get("language") or "").strip() or None, title=(tags.get("title") or "").strip() or None, codec_name=codec_name, + forced=bool(disp.get("forced")), + hearing_impaired=bool(disp.get("hearing_impaired")), )) has_audio = bool(audio_tracks) diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py index 1e85f29..2863679 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py +++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc_server.py @@ -6267,6 +6267,12 @@ class WebRTCPeerSession: "lang": tr.language, "title": tr.title, "codec": tr.codec_name, + # What tells a full translation from signage-only. Without + # it the two are the same menu entry, and picking the + # forced one shows nothing for minutes at a time — which + # reads as a broken feature and was reported as one. + "forced": tr.forced, + "sdh": tr.hearing_impaired, } for tr in probe.subtitle_tracks ], |