Skip to content

feat(api): expose audioTrack on adaptiveFormats - #5845

Open
mdbraber wants to merge 1 commit into
iv-org:masterfrom
mdbraber:feat/api-audio-track
Open

feat(api): expose audioTrack on adaptiveFormats#5845
mdbraber wants to merge 1 commit into
iv-org:masterfrom
mdbraber:feat/api-audio-track

Conversation

@mdbraber

Copy link
Copy Markdown

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: Invidious::Routes::API::Manifest reads audioTrack["id"], audioTrack["audioIsDefault"] and audioTrack["displayName"] to label and order the audio AdaptationSets of the DASH manifest. But /api/v1/videos drops 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 colorInfo and captionTrack fields.

Result

On a 24-language video (0e3GPea1Tyg), the audio entries of adaptiveFormats[] 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 audioTrack at all and so gain no field, which is correct — there is no second track to distinguish.

Notes for reviewers

  • Additive and optional throughout. Each field is emitted only when YouTube supplies it, so no existing response changes shape unless the video actually has multi-language audio.
  • audioTrack mirrors YouTube's own field name, and the nested-object shape mirrors what manifest.cr already consumes.
  • Happy to add an API-documentation entry — tell me where it should go.
  • One caveat worth flagging for consumers: do not fold the id's track discriminator (en-US.4en-US) and then key on the primary subtag. That merges zh-Hans with zh-Hant, which are different dubs. We hit exactly that bug downstream.
  • crystal tool format --check clean.

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

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/videos on 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.

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.

Copilot AI 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.

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 audioTrack object on each adaptive audio format when present.
  • Pass through id, displayName, and audioIsDefault fields (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
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