Skip to main content

ytmla

Tooling for putting multi-language audio tracks on YouTube videos — the API half, plus the Studio half the API doesn't cover.

Built out of a real production run: 3 languages × 6 episodes × long-form and Shorts, all uploaded, dubbed and verified.

The honest status of each half

The API half is proven. Upload, localization, duration fitting and verification were used end to end against a live channel.

The Studio half (ytmla/studio.py) is NOT proven. See "The API cannot do this" below.

Command Status
auth proven
upload proven
localize proven
dub proven
check proven
studio unverified selectors — expect to fix them

Install

pip install -r requirements.txt
playwright install chromium        # only for the studio command

The API cannot do this

Multi-language audio has no Data API endpoint. I checked the v3 discovery document directly: there is no audio/localization write part on videos.insert or videos.update. The only audio-related schema is VideoFileDetailsAudioStream, which is read-only metadata.

So the audio track itself has to be attached through the Studio UI. ytmla/studio.py drives that with Playwright. Studio's DOM changes without notice, so treat the SELECTORS map as a starting point. Run --dry-run --headed first; every failure screenshots into studio-shots/.

The metadata for those languages is available via the API — titles and descriptions in any language, which is what ytmla/localize.py does. That part is reliable; lean on it.

Quickstart

# 1. one-time consent (needs force-ssl, not just upload)
python -m ytmla auth --client-secret client_secret.json --creds token.json

# 2. upload and PROVE it landed
python -m ytmla upload ep01.mp4 --title "..." --description-file desc_en.txt --privacy private

# 3. localized titles + descriptions for every language, in one call
python -m ytmla localize VIDEO_ID translations.json      # {lang: {title, description}}

# 4. fit a dub to the video's exact length (this is what prevents the upload error)
python -m ytmla dub narration_es.wav out_es.m4a --video ep01.mp4 --bed music.wav

# 5. preflight BEFORE uploading -- catches the mismatch that costs an upload cycle
python -m ytmla check out_es.m4a ep01.mp4

# 6. attach it (Studio UI; unverified selectors)
python -m ytmla studio --video-id VIDEO_ID --lang es --audio out_es.m4a --headed --dry-run

Three traps this encodes

These each cost a real failed upload. They're the reason this exists rather than a snippet.

1 · Audio tracks must match video length

Processing failed — Audio tracks must match video length.

A track fitted to the wrong video fails by exactly the difference. If you dub Spanish to the English picture (18:02) and attach it to the Spanish video (19:08), it fails — and then fails again with a generic "unknown error" that sends you hunting in the wrong place.

ytmla check before every upload. ytmla dub --video <the video you will attach to> derives the target length from that file, never from a hardcoded number.

2 · The en localization is bound to snippet.description

For a video with defaultLanguage: en, localizations['en'] is the default description.

  • Writing localizations['en'] alone does nothing — the English text must go to snippet.description. (Observed: es and pt stored fine while en silently kept the old text.)
  • Sending localizations without en can make the default description revert.

localize.apply() re-fetches immediately before updating, carries every existing language through byte-for-byte, and always includes the default language. It then sleeps and re-reads, comparing strings — not trusting that the call returned.

3 · A returned video id is not evidence the upload stuck

videos().insert will hand you an id for an upload that later fails processing or is rejected on length. It can even leave a ghost entry in the uploads playlist while videos().list(id=...) returns nothing.

upload.resumable_upload re-fetches and raises if the video isn't retrievable. Also check channels().list(part='status').longUploadsStatus == 'allowed' — eligible caps you at 15 minutes.

Also worth knowing

AAC overshoots peaks. A source peaking at -4 dBFS came out of the encoder at -0.3 dBFS. dub() applies a default -1.5 dB headroom, because a track that clips on decode is worse than one that's slightly quiet.

64000bps is not enough for a 45-minute documentary with music. The default here is 192k.

Publishing is not automatic. Everything defaults to private. Add audio tracks, check playback, then publish.

What this deliberately does not do

  • Nothing that a human isn't permitted to do in their own Studio account. It clicks the same buttons faster.
  • No auto-dubbing. YouTube's Gemini auto-dub has been measured at a 4–5× drop in average view duration versus professional dubbing, and poor retention on a dubbed track drags the whole channel down. Bring real audio.
  • No creation of separate language channels. That's a strategy decision, not a scripting one.

Licence

MIT.

Metadata

Release files for ytmla 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ytmla 0.1.0
File Size Uploaded Provenance
ytmla-0.1.0.tar.gz 18.3 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for ytmla 0.1.0
File Interpreter ABI Platform
ytmla-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 34.7 kB

Release files / ytmla-0.1.0.tar.gz

Download URL ytmla-0.1.0.tar.gz
Size 18.3 kB
Tags Source
SHA-256 checksum
How to use checksums
368d48d4c9b780738ef0dd14036f7f483af5a25e8664d4610eca411fa2502098
BLAKE2b-256 checksum
How to use checksums
629d46f03d78b32f7b5576ae672f7a2d7f006b7a779b7fbd14d9bea1d6b333ad
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release files / ytmla-0.1.0-py3-none-any.whl

Download URL ytmla-0.1.0-py3-none-any.whl
Size 16.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3384c638a3282ba9968664c9ea3bcc43c39e99c1497694575e81a7fcacd854fd
BLAKE2b-256 checksum
How to use checksums
71688a3fbd1defcde2f50ba48a6c71f95bf48a0f906773c6512844f18cb07879
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Security for ytmla 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for ytmla-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page