Skip to main content

yttv

Play YouTube videos on a TV from the command line or from Python: Apple TV, Cast TVs (Chromecast, Samsung Tizen) and DIAL devices (Fire TV, WebOS).

yttv https://youtu.be/dQw4w9WgXcQ        # play on the last used screen
yttv -a ID1 ID2                          # append to its queue, one call for all
yttv -d bedroom ID                       # pick a screen by name or address
yttv -l                                  # list known screens

Install

pip install 'yttv[all]'        # every backend
pip install 'yttv[cast]'       # Cast TVs only
pip install 'yttv[appletv]'    # Apple TV only (needs Python < 3.14, see below)
pip install yttv               # DIAL and screens paired by code; only httpx

Or, in a pixi project: pixi add --pypi 'yttv[all]'.

Setting up a screen

Every TV needs one of these once. yttv remembers the result under ~/.cache/yttv/devices.json and picks the last used screen by default.

TV Once Then
Apple TV yttv --appletv 192.168.1.5 and type the PIN it shows yttv URL opens the app and plays
Chromecast, Samsung Tizen, other Cast TVs yttv --cast 192.168.1.6 yttv URL; the first cast makes a Samsung ask you to accept Cast terms, once
Fire TV, WebOS, other DIAL devices yttv -s finds them yttv URL wakes the TV and starts the app
Anything with a YouTube app yttv --pair 123456789 with the code from Settings › Link with TV code yttv URL, with the app already open

Videos are ids or URLs in any of the usual forms (watch?v=, youtu.be, shorts, live, embed, ...). A t= parameter becomes the start position. Several videos are always sent together in one call: sending them one after another scrambles the TV's queue.

From Python

import yttv

yttv.devices()                        # known screens, from the cache, no network
yttv.cast(["dQw4w9WgXcQ"])            # play now on the last used screen
yttv.cast([url1, url2], queue=True)   # append to its queue
yttv.cast([url], device="bedroom")    # pick a screen
yttv.pair("123 456 789")              # link a screen by TV code
yttv.add_device("cast", "192.168.1.6", cast_uuid="...")
yttv.discover()                       # DIAL search

Every failure is a subclass of yttv.YttvError with a message meant for people, so a caller can show it as is. cast() takes up to timeout seconds (default 90) for waking the TV and starting the app; that is how long a sleeping Fire TV needs.

Screens paired with ytcast are taken over from ~/.cache/ytcast/ytcast.json on the first run.

How it works

Three ways to reach a TV, one protocol to drive it:

Device Finds and starts the app via Plays via
Apple TV pyatv, Companion protocol Lounge
Cast TVs pychromecast, the YouTube receiver's mdx channel Lounge
DIAL devices SSDP and the DIAL REST interface, built in Lounge

The Lounge API is the unofficial protocol behind the "Play on TV" button in the phone app: pair with a screen, play a video, append to the queue. Each backend's only job is to bring the YouTube app up and obtain the screen's id; from there everything goes through ytlounge, a separate package by the same author that knows nothing about devices, caches or files.

The Lounge API is not documented and can change at any time. If it does, expect this to break the same way for every tool built on it.

Python versions

The core runs on Python 3.11 and newer, 3.14 included. The Apple TV backend depends on pyatv, which does not run on 3.14 yet; its extra is skipped there and yttv says so when an Apple TV is used.

When the search finds nothing

The DIAL search is a multicast packet; every device answers with a unicast reply from its own address. A stateful firewall on your machine (ufw, firewalld) does not connect that reply to the packet you sent and drops it silently. Discovery then reports "no device found" although the packets are on the wire.

yttv --doctor

sends the searches, counts the replies and, when nothing comes back, prints the rule to check and the ufw line that fixes it. Cast TVs and Apple TV never answer DIAL searches; that is expected, use --cast and --appletv for those. --doctor --host <ip> probes a device directly and sidesteps multicast.

Origins

The Lounge protocol was reverse-engineered independently by several people; nothing here is derived from their code, but their write-ups made the protocol knowable. yttv and ytlounge learned it from Marco Lucidi's ytcast (Go), whose behaviour served as the reference for verifying requests on the wire, and, through it, from the sources ytcast itself credits:

The list of YouTube URL forms used in the tests comes from this gist.

Development

pixi run test              # unit and fixture tests, Python 3.13 with all backends
pixi run -e core314 test   # the same on Python 3.14 without any backend extra
pixi run test-device       # needs a real TV on the network
pixi run check             # build wheel and sdist, validate the metadata

The pixi environments take ytlounge from a checkout in ../ytlounge, so both can be changed together; a released yttv gets it from PyPI.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

yttv-0.1.0.tar.gz (41.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

yttv-0.1.0-py3-none-any.whl (28.8 kB view details)

Uploaded Python 3

File details

Details for the file yttv-0.1.0.tar.gz.

File metadata

  • Download URL: yttv-0.1.0.tar.gz
  • Upload date:
  • Size: 41.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.15

File hashes

Hashes for yttv-0.1.0.tar.gz
Algorithm Hash digest
SHA256 60ce193f3f0f3961211a9dab0942b71d4586d7501ad188d78a16b87352f46deb
MD5 91eea4da71fd4ae9243aa0a079f5cc72
BLAKE2b-256 6b75f18a952e7140e6031875db9a051342e0bdc88f544045da322906c27f23cd

See more details on using hashes here.

File details

Details for the file yttv-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: yttv-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 28.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.15

File hashes

Hashes for yttv-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5b36118694ca9170b14e894a4ee0e28f71f91bd7b0e535f0946cae1ae0a85e54
MD5 e852db4127811224ab967d6f486bc18f
BLAKE2b-256 157b1320e27bb93c6d67db0fd71a1dbaef550987b670664ec1f8638502001c1b

See more details on using hashes here.

Release history Release notifications | RSS feed

0.1.1

2 files

This release

0.1.0 This release

2 files

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