feat(api): expose audioTrack on adaptiveFormats - #5845
Open
mdbraber wants to merge 1 commit into
Open
Conversation
Videos with multi-language audio carry an `audioTrack` object on each audio entry of `streamingData.adaptiveFormats`, holding the language id (e.g. "en-US.4"), a human-readable `displayName`, and whether the track is the original audio. Invidious already parses and uses all three: the DASH manifest route reads `audioTrack["id"]`, `audioTrack["audioIsDefault"]` and `audioTrack["displayName"]` to label and order its audio AdaptationSets. But `/api/v1/videos` drops it, so an API consumer has no way to tell one audio track from another, and is left scraping it back out of the stream URL query string (`xtags=acont%3Ddubbed%3Alang%3Dfr`) — undocumented, and silent when it changes. Emit the same three fields the manifest route already relies on, each only when present, in the same passthrough style as the neighbouring `colorInfo` and `captionTrack` fields. Additive and optional throughout: a single-audio video carries no `audioTrack` at all and so gains no field, which is correct — there is no second track to distinguish. AI disclosure, per AI_POLICY.md: this patch was written with AI assistance. Exact model: Claude Opus 5, model ID `claude-opus-5[1m]`. Tool used to interact with it: Claude Code, Anthropic's agentic CLI. The change was verified against a live patched instance (see the PR for the measurements) and is running in production on the submitter's instance. Submitted by and the responsibility of @mdbraber.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR extends the /api/v1/videos JSON output to expose YouTube’s multi-language audioTrack metadata on streamingData.adaptiveFormats, aligning the API response with data already consumed by the DASH manifest route.
Changes:
- Emit an
audioTrackobject on each adaptive audio format when present. - Pass through
id,displayName, andaudioIsDefaultfields (only when provided by YouTube) to let clients distinguish dubbed/original tracks.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+157
to
+168
| if audio_track = fmt["audioTrack"]? | ||
| json.field "audioTrack" do | ||
| json.object do | ||
| # Language tag with a track discriminator, e.g. "en-US.4". | ||
| json.field "id", audio_track["id"] if audio_track["id"]? | ||
| # Human-readable label, e.g. "English (original)". | ||
| json.field "displayName", audio_track["displayName"] if audio_track["displayName"]? | ||
| # True for the video's original (undubbed) audio. | ||
| json.field "audioIsDefault", audio_track["audioIsDefault"] if audio_track["audioIsDefault"]? | ||
| end | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Videos with multi-language audio carry an
audioTrackobject on each audio entry ofstreamingData.adaptiveFormats, holding the language id (e.g."en-US.4"), a human-readabledisplayName, and whether the track is the original audio.Invidious already parses and uses all three:
Invidious::Routes::API::ManifestreadsaudioTrack["id"],audioTrack["audioIsDefault"]andaudioTrack["displayName"]to label and order the audioAdaptationSets of the DASH manifest. But/api/v1/videosdrops it, so an API consumer has no way to tell one audio track from another.Clients are left scraping it back out of the stream URL query string —
xtags=acont%3Ddubbed%3Alang%3Dfr— which is undocumented, changes without notice, and fails silently when it does.This emits the same three fields the manifest route already relies on, each only when present, in the same passthrough style as the neighbouring
colorInfoandcaptionTrackfields.Result
On a 24-language video (
0e3GPea1Tyg), the audio entries ofadaptiveFormats[]now carry:{"id": "en-US.4", "displayName": "English (US) original", "audioIsDefault": true} {"id": "fr.3", "displayName": "French", "audioIsDefault": false} {"id": "zh-Hans.3", "displayName": "Chinese (Simplified)", "audioIsDefault": false} {"id": "zh-Hant.3", "displayName": "Chinese (Traditional)", "audioIsDefault": false}24 distinct ids, all named, exactly one flagged as the original.
Single-audio videos carry no
audioTrackat all and so gain no field, which is correct — there is no second track to distinguish.Notes for reviewers
audioTrackmirrors YouTube's own field name, and the nested-object shape mirrors whatmanifest.cralready consumes.en-US.4→en-US) and then key on the primary subtag. That mergeszh-Hanswithzh-Hant, which are different dubs. We hit exactly that bug downstream.crystal tool format --checkclean.AI disclosure (per
AI_POLICY.md)This patch was written with AI assistance.
claude-opus-5[1m]How it was checked: the field shape was taken from
manifest.cr, which already consumes these exact three keys, rather than inferred. The patch was built, deployed to a live instance, and the output above was read off/api/v1/videoson live data; a single-audio video was checked separately to confirm it gains no field. It is running in production on my instance.I am the submitter and, per the policy, solely responsible for this change.