From 09fe7557d65c450947d910050be785eda5e90713 Mon Sep 17 00:00:00 2001 From: Maarten den Braber Date: Thu, 30 Jul 2026 15:09:46 +0200 Subject: [PATCH 1/2] feat(api): expose audioTrack on adaptiveFormats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/invidious/jsonify/api_v1/video_json.cr | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/invidious/jsonify/api_v1/video_json.cr b/src/invidious/jsonify/api_v1/video_json.cr index e02e06173b..b76581653d 100644 --- a/src/invidious/jsonify/api_v1/video_json.cr +++ b/src/invidious/jsonify/api_v1/video_json.cr @@ -150,6 +150,23 @@ module Invidious::JSONify::APIv1 json.field "audioSampleRate", fmt["audioSampleRate"].as_s.to_i if fmt.has_key?("audioSampleRate") json.field "audioChannels", fmt["audioChannels"] if fmt.has_key?("audioChannels") + # Multi-language audio. Only present when a video carries more than + # one audio track. The same data already drives the DASH manifest + # (see `Invidious::Routes::API::Manifest`), but was never exposed on + # the API, leaving clients to scrape `xtags` out of the stream URL. + 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 + # Extra misc stuff json.field "colorInfo", fmt["colorInfo"] if fmt.has_key?("colorInfo") json.field "captionTrack", fmt["captionTrack"] if fmt.has_key?("captionTrack") From 9752768d586a4435ef385c0bb083115c05308ffc Mon Sep 17 00:00:00 2001 From: Maarten den Braber Date: Fri, 31 Jul 2026 11:12:35 +0200 Subject: [PATCH 2/2] Potential fix for pull request finding Hardening via `.try &.as_h?` makes sense - not sure if YT would ever return null Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/invidious/jsonify/api_v1/video_json.cr | 27 ++++++++++++---------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/invidious/jsonify/api_v1/video_json.cr b/src/invidious/jsonify/api_v1/video_json.cr index b76581653d..60e684b133 100644 --- a/src/invidious/jsonify/api_v1/video_json.cr +++ b/src/invidious/jsonify/api_v1/video_json.cr @@ -154,18 +154,21 @@ module Invidious::JSONify::APIv1 # one audio track. The same data already drives the DASH manifest # (see `Invidious::Routes::API::Manifest`), but was never exposed on # the API, leaving clients to scrape `xtags` out of the stream URL. - 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 +if audio_track = fmt["audioTrack"]?.try &.as_h? + json.field "audioTrack" do + json.object do + if id = audio_track["id"]? + json.field "id", id + end + if display_name = audio_track["displayName"]? + json.field "displayName", display_name + end + if audio_is_default = audio_track["audioIsDefault"]? + json.field "audioIsDefault", audio_is_default + end + end + end +end # Extra misc stuff json.field "colorInfo", fmt["colorInfo"] if fmt.has_key?("colorInfo")