Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

 
 

Repository files navigation

yt-studio

yt-studio is a script-first command-line client for publishing and managing YouTube videos without clicking through YouTube Studio. It supports operations that are missing from the official YouTube Data API, including native title and thumbnail experiments, Shorts related videos, info cards, and end screens.

The Python package and CLI command are currently named ytstudio-safe for backward compatibility:

ytstudio-safe --help
# or
python -m ytstudio --help

Warning

This is an unofficial tool. It uses private YouTube Studio endpoints that can change without notice. Automated use may violate Google or YouTube terms or trigger account enforcement. Start with a disposable test channel, review every dry run, and keep captured sessions private.

What it can do

  • Upload long-form videos and Shorts with resumable media transfer.
  • Set title, description, tags, audience, thumbnail, and visibility.
  • Publish immediately or schedule a timezone-aware release.
  • Run a native two- or three-arm title-and-thumbnail A/B test.
  • Attach a related long-form video to a Short.
  • Replace info cards and end screens from one JSON file.
  • Publish an entire long-form-plus-Shorts workflow from an idempotent manifest.
  • Save progress atomically so an interrupted workflow can resume safely.

Every publishing command is a dry run by default. A mutation requires both --apply and an exact --confirm value. Subscriber notifications are disabled by the client.

Requirements

  • Python 3.10 or newer
  • Google Chrome for account sign-in and session capture
  • uv or pip

Give this repository to an agent

Send the agent this repository URL and the following instruction:

Install yt-studio and complete a read-only onboarding.

1. Detect my operating system and install missing Python 3.10+, Git, uv,
   Playwright and a supported dedicated browser. Handle package-manager
   commands yourself; stop only for an administrator password or OS approval.
2. Clone the repository and install ytstudio-safe with Playwright.
3. Open a new dedicated ytstudio-safe browser profile. I will only sign in to
   Google/YouTube and select the intended channel.
4. Capture the filtered session, bind it to the exact channel ID and keep every
   credential outside the repository with private file permissions.
5. Verify authentication and show a dry run. Do not upload, schedule, publish,
   comment or change Studio without separate explicit permission.

The user should only need to complete Google sign-in, 2FA/CAPTCHA if requested, and choose the intended YouTube channel. The agent handles installation, session capture and read-only checks.

Install locally with uv:

git clone https://github.com/sstani-bgv/yt-studio.git
cd yt-studio
uv run --with 'playwright>=1.45' --with-editable . \
  python -m playwright install chromium

Or install with pip:

python -m pip install -e '.[playwright]'
playwright install chromium

Authentication

The tool uses a dedicated Chrome profile. It does not read your normal Chrome, Arc, or ChatGPT browser profile. The captured session is a credential: never commit it, share it, or place it inside this repository.

1. Open the dedicated profile

ytstudio-safe auth-open \
  --session-name youtube-sandbox \
  --browser-channel chrome

Sign in, select the intended channel, wait for its Studio dashboard, and fully quit that Chrome window.

2. Capture and lock the channel identity

ytstudio-safe auth-capture \
  --session-name youtube-sandbox \
  --browser-channel chrome \
  --expected-channel-id UC_REPLACE_WITH_CHANNEL_ID

For an explicitly acknowledged production session, add:

--session-role production --acknowledge-private-api-risk

Before every mutation, the client checks that the active Studio channel matches the captured channel ID. A mismatch fails before the request is sent.

3. Capture the short-lived Studio security token

ytstudio-safe security-capture --session-name youtube-sandbox

This briefly opens the dedicated browser, completes Studio's native security flow, saves the token with mode 0600, and closes the browser. The token expires after 90 minutes. The remaining work is sent directly over HTTP; the CLI does not keep clicking through Studio.

Profiles are stored outside the repository under:

~/.local/share/ytstudio-safe/profiles/<session-name>

Safety model

  • Mutations are dry-run-first and require an exact target confirmation.
  • Direct requests verify the active channel before each mutation.
  • Session data is filtered, atomically stored, locked, and written as 0600.
  • Cookie values, authorization hashes, upload URLs, and API keys are redacted.
  • Credential-bearing redirects are disabled and network hosts are allow-listed.
  • No downloaded JavaScript is evaluated in Python.
  • Public visibility requires the additional --allow-public flag.
  • Browser fallback is never automatic; it requires --browser-fallback.

Upload a video or Short

Review the dry run:

ytstudio-safe upload \
  --file /absolute/path/video.mp4 \
  --title 'Video title' \
  --description-file /absolute/path/description.txt \
  --tag ai --tag automation \
  --thumbnail /absolute/path/thumbnail.png \
  --privacy UNLISTED

Apply it using the dedicated session. The confirmation must be UPLOAD:<filename>:

ytstudio-safe upload \
  --session-name youtube-sandbox \
  --file /absolute/path/video.mp4 \
  --title 'Video title' \
  --description-file /absolute/path/description.txt \
  --tag ai --tag automation \
  --thumbnail /absolute/path/thumbnail.png \
  --privacy UNLISTED \
  --apply --confirm 'UPLOAD:video.mp4'

Shorts use the same command; YouTube detects the format from the media. Uploads are transferred through Studio's resumable Scotty protocol and initially registered as private drafts before the requested visibility is applied.

Schedule a release

Schedule during upload:

ytstudio-safe upload \
  --session-name youtube-sandbox \
  --file /absolute/path/short.mp4 \
  --title 'Short title' \
  --publish-at '2026-08-01T18:00:00+04:00' \
  --apply --confirm 'UPLOAD:short.mp4'

Schedule an existing video:

ytstudio-safe schedule \
  --session-name youtube-sandbox \
  --video-id VIDEO_ID \
  --publish-at '2026-08-03T18:00:00+04:00'

After reviewing the dry run, add --apply --confirm VIDEO_ID. A schedule must be in the future and include a timezone offset.

Batch scheduling accepts this JSON:

{
  "items": [
    {"video_id": "VIDEO_ID", "publish_at": "2026-08-03T18:00:00+04:00"},
    {"video_id": "SHORT_ID", "publish_at": "2026-08-05T18:00:00+04:00"}
  ]
}
ytstudio-safe schedule-batch --input /absolute/path/schedule.json
ytstudio-safe schedule-batch \
  --session-name youtube-sandbox \
  --input /absolute/path/schedule.json \
  --apply --confirm 'BATCH:schedule.json'

Use update-visibility for an existing video. Setting PUBLIC also requires --allow-public.

Title and thumbnail A/B test

Provide two or three title/thumbnail pairs:

ytstudio-safe ab-test \
  --session-name youtube-sandbox \
  --video-id VIDEO_ID \
  --title 'Variant A' --thumbnail /absolute/path/thumb-a.png \
  --title 'Variant B' --thumbnail /absolute/path/thumb-b.png \
  --title 'Variant C' --thumbnail /absolute/path/thumb-c.png

After reviewing the dry run, add --apply --confirm VIDEO_ID. Each thumbnail is uploaded first; the experiment request references its encrypted Studio resource ID instead of embedding image data in metadata.

Related video for a Short

ytstudio-safe related-video \
  --session-name youtube-sandbox \
  --short-id SHORT_ID \
  --related-video-id LONG_FORM_VIDEO_ID

After reviewing the dry run, add --apply --confirm SHORT_ID.

Info cards and end screen

Both operations read one studio-features.json file:

{
  "info_cards": {
    "cards": [
      {
        "type": "video",
        "target_video_id": "TARGET_VIDEO_ID",
        "teaser_start_ms": 120000,
        "custom_message": "Watch next",
        "teaser_text": "Continue"
      }
    ]
  },
  "end_screen": {
    "start_ms": 503500,
    "elements": [
      {
        "type": "video",
        "video_type": "best_for_viewer",
        "left": 0.0228,
        "top": 0.5249,
        "width": 0.3228,
        "duration_ms": 20000
      },
      {
        "type": "subscribe",
        "channel_id": "UC_REPLACE_WITH_CHANNEL_ID",
        "left": 0.8228,
        "top": 0.5491,
        "width": 0.1544,
        "duration_ms": 20000
      }
    ]
  }
}

Apply each validated section using the same file:

ytstudio-safe info-cards \
  --session-name youtube-sandbox \
  --video-id VIDEO_ID \
  --input /absolute/path/studio-features.json

ytstudio-safe end-screen \
  --session-name youtube-sandbox \
  --video-id VIDEO_ID \
  --input /absolute/path/studio-features.json

After each dry run, add --apply --confirm VIDEO_ID. Supported cards are video, playlist, and channel. Supported end-screen elements are video, playlist, subscribe, and channel. Link cards are intentionally unsupported.

Publish from a manifest

publish-project runs a complete, resumable workflow. A manifest can describe:

  • one existing or newly uploaded long-form video;
  • metadata and thumbnail experiment variants;
  • the long-form video's info cards and end screen;
  • multiple Shorts, their schedules, and their related-video link.

Create a plan without changing YouTube:

ytstudio-safe publish-project \
  --manifest /absolute/path/project.manifest.json \
  --output /tmp/project-plan.json

Apply only after reviewing the plan:

ytstudio-safe publish-project \
  --session-name youtube-sandbox \
  --manifest /absolute/path/project.manifest.json \
  --state /absolute/path/project.state.json \
  --apply --confirm 'PROJECT:project.manifest.json'

The state file is replaced atomically after every irreversible step. A rerun reuses completed uploads and skips completed A/B, scheduling, related-video, info-card, and end-screen operations. See examples/publish.example.json for the complete schema; treat it as a dry-run example and replace all paths and IDs before applying it.

Direct transport and browser fallback

Normal apply commands use direct httpx requests after session capture. The transport bootstraps Studio, derives a fresh SAPISIDHASH, checks the active channel, and sends the operation described by a fingerprinted endpoint spec in specs/2026-07-22/.

If a Studio change temporarily breaks direct transport, an individual command can explicitly use the dedicated browser profile with --browser-fallback. There is no automatic fallback, and the same channel guard still runs.

The client does not fabricate or bypass Google attestation. If Studio requires an attestation the client cannot obtain legitimately, the request fails closed.

Development

Run all offline tests and lint checks:

./run_dev.sh
uv run --with ruff --with-editable . ruff check .

The automated test suite must not contact Google or YouTube. Live validation belongs on a disposable test channel and always requires explicit --apply.

License and attribution

Licensed under GPL-3.0. See LICENSE. This code began as a heavily modified derivative of yusufusta/ytstudio. Upstream revisions and other comparative references are documented in THIRD_PARTY_NOTICES.md.

This project is not affiliated with, endorsed by, or supported by YouTube or Google.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages