yttv 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
yttv-0.1.0/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ # pixi
2
+ .pixi/
3
+ # Python
4
+ __pycache__/
5
+ *.py[cod]
6
+ *.egg-info/
7
+ dist/
8
+ build/
9
+ .pytest_cache/
10
+ .hypothesis/
yttv-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 desvaters
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
yttv-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,167 @@
1
+ Metadata-Version: 2.5
2
+ Name: yttv
3
+ Version: 0.1.0
4
+ Summary: Play YouTube videos on a TV: Cast, Apple TV and DIAL devices, all driven through the YouTube Lounge API
5
+ Project-URL: Homepage, https://github.com/desvaters/yttv
6
+ Project-URL: Issues, https://github.com/desvaters/yttv/issues
7
+ Author-email: desvaters <pypi@desvate.rs>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: apple-tv,cast,chromecast,dial,lounge,tv,youtube
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Multimedia :: Video
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: httpx>=0.27
22
+ Requires-Dist: ytlounge<0.2,>=0.1
23
+ Provides-Extra: all
24
+ Requires-Dist: pyatv>=0.18; (python_version < '3.14') and extra == 'all'
25
+ Requires-Dist: pychromecast>=14; extra == 'all'
26
+ Provides-Extra: appletv
27
+ Requires-Dist: pyatv>=0.18; (python_version < '3.14') and extra == 'appletv'
28
+ Provides-Extra: cast
29
+ Requires-Dist: pychromecast>=14; extra == 'cast'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # yttv
33
+
34
+ Play YouTube videos on a TV from the command line or from Python: Apple TV,
35
+ Cast TVs (Chromecast, Samsung Tizen) and DIAL devices (Fire TV, WebOS).
36
+
37
+ ```bash
38
+ yttv https://youtu.be/dQw4w9WgXcQ # play on the last used screen
39
+ yttv -a ID1 ID2 # append to its queue, one call for all
40
+ yttv -d bedroom ID # pick a screen by name or address
41
+ yttv -l # list known screens
42
+ ```
43
+
44
+ ## Install
45
+
46
+ ```bash
47
+ pip install 'yttv[all]' # every backend
48
+ pip install 'yttv[cast]' # Cast TVs only
49
+ pip install 'yttv[appletv]' # Apple TV only (needs Python < 3.14, see below)
50
+ pip install yttv # DIAL and screens paired by code; only httpx
51
+ ```
52
+
53
+ Or, in a [pixi](https://pixi.sh) project: `pixi add --pypi 'yttv[all]'`.
54
+
55
+ ## Setting up a screen
56
+
57
+ Every TV needs one of these once. yttv remembers the result under
58
+ `~/.cache/yttv/devices.json` and picks the last used screen by default.
59
+
60
+ | TV | Once | Then |
61
+ |---|---|---|
62
+ | Apple TV | `yttv --appletv 192.168.1.5` and type the PIN it shows | `yttv URL` opens the app and plays |
63
+ | 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 |
64
+ | Fire TV, WebOS, other DIAL devices | `yttv -s` finds them | `yttv URL` wakes the TV and starts the app |
65
+ | Anything with a YouTube app | `yttv --pair 123456789` with the code from *Settings › Link with TV code* | `yttv URL`, with the app already open |
66
+
67
+ Videos are ids or URLs in any of the usual forms (`watch?v=`, `youtu.be`,
68
+ `shorts`, `live`, `embed`, ...). A `t=` parameter becomes the start
69
+ position. Several videos are always sent together in one call: sending
70
+ them one after another scrambles the TV's queue.
71
+
72
+ ## From Python
73
+
74
+ ```python
75
+ import yttv
76
+
77
+ yttv.devices() # known screens, from the cache, no network
78
+ yttv.cast(["dQw4w9WgXcQ"]) # play now on the last used screen
79
+ yttv.cast([url1, url2], queue=True) # append to its queue
80
+ yttv.cast([url], device="bedroom") # pick a screen
81
+ yttv.pair("123 456 789") # link a screen by TV code
82
+ yttv.add_device("cast", "192.168.1.6", cast_uuid="...")
83
+ yttv.discover() # DIAL search
84
+ ```
85
+
86
+ Every failure is a subclass of `yttv.YttvError` with a message meant for
87
+ people, so a caller can show it as is. `cast()` takes up to `timeout`
88
+ seconds (default 90) for waking the TV and starting the app; that is how
89
+ long a sleeping Fire TV needs.
90
+
91
+ Screens paired with [ytcast](https://github.com/MarcoLucidi01/ytcast) are
92
+ taken over from `~/.cache/ytcast/ytcast.json` on the first run.
93
+
94
+ ## How it works
95
+
96
+ Three ways to reach a TV, one protocol to drive it:
97
+
98
+ | Device | Finds and starts the app via | Plays via |
99
+ |---|---|---|
100
+ | Apple TV | [pyatv](https://github.com/postlund/pyatv), Companion protocol | Lounge |
101
+ | Cast TVs | [pychromecast](https://github.com/home-assistant-libs/pychromecast), the YouTube receiver's `mdx` channel | Lounge |
102
+ | DIAL devices | SSDP and the DIAL REST interface, built in | Lounge |
103
+
104
+ The Lounge API is the unofficial protocol behind the "Play on TV" button in
105
+ the phone app: pair with a screen, play a video, append to the queue. Each
106
+ backend's only job is to bring the YouTube app up and obtain the screen's
107
+ id; from there everything goes through
108
+ [ytlounge](https://github.com/desvaters/ytlounge), a separate package by
109
+ the same author that knows nothing about devices, caches or files.
110
+
111
+ The Lounge API is not documented and can change at any time. If it does,
112
+ expect this to break the same way for every tool built on it.
113
+
114
+ ### Python versions
115
+
116
+ The core runs on Python 3.11 and newer, 3.14 included. The Apple TV backend
117
+ depends on pyatv, which does not run on 3.14 yet; its extra is skipped
118
+ there and `yttv` says so when an Apple TV is used.
119
+
120
+ ## When the search finds nothing
121
+
122
+ The DIAL search is a multicast packet; every device answers with a unicast
123
+ reply from its own address. A stateful firewall on your machine (ufw,
124
+ firewalld) does not connect that reply to the packet you sent and drops it
125
+ silently. Discovery then reports "no device found" although the packets are
126
+ on the wire.
127
+
128
+ ```bash
129
+ yttv --doctor
130
+ ```
131
+
132
+ sends the searches, counts the replies and, when nothing comes back, prints
133
+ the rule to check and the ufw line that fixes it. Cast TVs and Apple TV
134
+ never answer DIAL searches; that is expected, use `--cast` and `--appletv`
135
+ for those. `--doctor --host <ip>` probes a device directly and sidesteps
136
+ multicast.
137
+
138
+ ## Origins
139
+
140
+ The Lounge protocol was reverse-engineered independently by several
141
+ people; nothing here is derived from their code, but their write-ups made
142
+ the protocol knowable. yttv and ytlounge learned it from Marco Lucidi's
143
+ [ytcast](https://github.com/MarcoLucidi01/ytcast) (Go), whose behaviour
144
+ served as the reference for verifying requests on the wire, and, through
145
+ it, from the sources ytcast itself credits:
146
+
147
+ - https://0x41.cf/automation/2021/03/02/google-assistant-youtube-smart-tvs.html
148
+ - https://github.com/thedroidgeek/youtube-cast-automation-api
149
+ - https://github.com/mutantmonkey/youtube-remote
150
+ - https://bugs.xdavidhu.me/google/2021/04/05/i-built-a-tv-that-plays-all-of-your-private-youtube-videos
151
+ - https://github.com/aykevl/plaincast
152
+ - https://github.com/ur1katz/casttube
153
+
154
+ The list of YouTube URL forms used in the tests comes from
155
+ [this gist](https://gist.github.com/rodrigoborgesdeoliveira/987683cfbfcc8d800192da1e73adc486).
156
+
157
+ ## Development
158
+
159
+ ```bash
160
+ pixi run test # unit and fixture tests, Python 3.13 with all backends
161
+ pixi run -e core314 test # the same on Python 3.14 without any backend extra
162
+ pixi run test-device # needs a real TV on the network
163
+ pixi run check # build wheel and sdist, validate the metadata
164
+ ```
165
+
166
+ The pixi environments take `ytlounge` from a checkout in `../ytlounge`, so
167
+ both can be changed together; a released `yttv` gets it from PyPI.
yttv-0.1.0/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # yttv
2
+
3
+ Play YouTube videos on a TV from the command line or from Python: Apple TV,
4
+ Cast TVs (Chromecast, Samsung Tizen) and DIAL devices (Fire TV, WebOS).
5
+
6
+ ```bash
7
+ yttv https://youtu.be/dQw4w9WgXcQ # play on the last used screen
8
+ yttv -a ID1 ID2 # append to its queue, one call for all
9
+ yttv -d bedroom ID # pick a screen by name or address
10
+ yttv -l # list known screens
11
+ ```
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pip install 'yttv[all]' # every backend
17
+ pip install 'yttv[cast]' # Cast TVs only
18
+ pip install 'yttv[appletv]' # Apple TV only (needs Python < 3.14, see below)
19
+ pip install yttv # DIAL and screens paired by code; only httpx
20
+ ```
21
+
22
+ Or, in a [pixi](https://pixi.sh) project: `pixi add --pypi 'yttv[all]'`.
23
+
24
+ ## Setting up a screen
25
+
26
+ Every TV needs one of these once. yttv remembers the result under
27
+ `~/.cache/yttv/devices.json` and picks the last used screen by default.
28
+
29
+ | TV | Once | Then |
30
+ |---|---|---|
31
+ | Apple TV | `yttv --appletv 192.168.1.5` and type the PIN it shows | `yttv URL` opens the app and plays |
32
+ | 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 |
33
+ | Fire TV, WebOS, other DIAL devices | `yttv -s` finds them | `yttv URL` wakes the TV and starts the app |
34
+ | Anything with a YouTube app | `yttv --pair 123456789` with the code from *Settings › Link with TV code* | `yttv URL`, with the app already open |
35
+
36
+ Videos are ids or URLs in any of the usual forms (`watch?v=`, `youtu.be`,
37
+ `shorts`, `live`, `embed`, ...). A `t=` parameter becomes the start
38
+ position. Several videos are always sent together in one call: sending
39
+ them one after another scrambles the TV's queue.
40
+
41
+ ## From Python
42
+
43
+ ```python
44
+ import yttv
45
+
46
+ yttv.devices() # known screens, from the cache, no network
47
+ yttv.cast(["dQw4w9WgXcQ"]) # play now on the last used screen
48
+ yttv.cast([url1, url2], queue=True) # append to its queue
49
+ yttv.cast([url], device="bedroom") # pick a screen
50
+ yttv.pair("123 456 789") # link a screen by TV code
51
+ yttv.add_device("cast", "192.168.1.6", cast_uuid="...")
52
+ yttv.discover() # DIAL search
53
+ ```
54
+
55
+ Every failure is a subclass of `yttv.YttvError` with a message meant for
56
+ people, so a caller can show it as is. `cast()` takes up to `timeout`
57
+ seconds (default 90) for waking the TV and starting the app; that is how
58
+ long a sleeping Fire TV needs.
59
+
60
+ Screens paired with [ytcast](https://github.com/MarcoLucidi01/ytcast) are
61
+ taken over from `~/.cache/ytcast/ytcast.json` on the first run.
62
+
63
+ ## How it works
64
+
65
+ Three ways to reach a TV, one protocol to drive it:
66
+
67
+ | Device | Finds and starts the app via | Plays via |
68
+ |---|---|---|
69
+ | Apple TV | [pyatv](https://github.com/postlund/pyatv), Companion protocol | Lounge |
70
+ | Cast TVs | [pychromecast](https://github.com/home-assistant-libs/pychromecast), the YouTube receiver's `mdx` channel | Lounge |
71
+ | DIAL devices | SSDP and the DIAL REST interface, built in | Lounge |
72
+
73
+ The Lounge API is the unofficial protocol behind the "Play on TV" button in
74
+ the phone app: pair with a screen, play a video, append to the queue. Each
75
+ backend's only job is to bring the YouTube app up and obtain the screen's
76
+ id; from there everything goes through
77
+ [ytlounge](https://github.com/desvaters/ytlounge), a separate package by
78
+ the same author that knows nothing about devices, caches or files.
79
+
80
+ The Lounge API is not documented and can change at any time. If it does,
81
+ expect this to break the same way for every tool built on it.
82
+
83
+ ### Python versions
84
+
85
+ The core runs on Python 3.11 and newer, 3.14 included. The Apple TV backend
86
+ depends on pyatv, which does not run on 3.14 yet; its extra is skipped
87
+ there and `yttv` says so when an Apple TV is used.
88
+
89
+ ## When the search finds nothing
90
+
91
+ The DIAL search is a multicast packet; every device answers with a unicast
92
+ reply from its own address. A stateful firewall on your machine (ufw,
93
+ firewalld) does not connect that reply to the packet you sent and drops it
94
+ silently. Discovery then reports "no device found" although the packets are
95
+ on the wire.
96
+
97
+ ```bash
98
+ yttv --doctor
99
+ ```
100
+
101
+ sends the searches, counts the replies and, when nothing comes back, prints
102
+ the rule to check and the ufw line that fixes it. Cast TVs and Apple TV
103
+ never answer DIAL searches; that is expected, use `--cast` and `--appletv`
104
+ for those. `--doctor --host <ip>` probes a device directly and sidesteps
105
+ multicast.
106
+
107
+ ## Origins
108
+
109
+ The Lounge protocol was reverse-engineered independently by several
110
+ people; nothing here is derived from their code, but their write-ups made
111
+ the protocol knowable. yttv and ytlounge learned it from Marco Lucidi's
112
+ [ytcast](https://github.com/MarcoLucidi01/ytcast) (Go), whose behaviour
113
+ served as the reference for verifying requests on the wire, and, through
114
+ it, from the sources ytcast itself credits:
115
+
116
+ - https://0x41.cf/automation/2021/03/02/google-assistant-youtube-smart-tvs.html
117
+ - https://github.com/thedroidgeek/youtube-cast-automation-api
118
+ - https://github.com/mutantmonkey/youtube-remote
119
+ - https://bugs.xdavidhu.me/google/2021/04/05/i-built-a-tv-that-plays-all-of-your-private-youtube-videos
120
+ - https://github.com/aykevl/plaincast
121
+ - https://github.com/ur1katz/casttube
122
+
123
+ The list of YouTube URL forms used in the tests comes from
124
+ [this gist](https://gist.github.com/rodrigoborgesdeoliveira/987683cfbfcc8d800192da1e73adc486).
125
+
126
+ ## Development
127
+
128
+ ```bash
129
+ pixi run test # unit and fixture tests, Python 3.13 with all backends
130
+ pixi run -e core314 test # the same on Python 3.14 without any backend extra
131
+ pixi run test-device # needs a real TV on the network
132
+ pixi run check # build wheel and sdist, validate the metadata
133
+ ```
134
+
135
+ The pixi environments take `ytlounge` from a checkout in `../ytlounge`, so
136
+ both can be changed together; a released `yttv` gets it from PyPI.
yttv-0.1.0/pixi.toml ADDED
@@ -0,0 +1,64 @@
1
+ [workspace]
2
+ name = "yttv"
3
+ channels = ["conda-forge"]
4
+ platforms = ["linux-64"]
5
+
6
+ # ---------------------------------------------------------------------------
7
+ # Shared by every environment: the core's runtime dependency, the test
8
+ # runner and the tasks. The Python version and the project install live in
9
+ # the features below, because they differ between environments.
10
+ # ---------------------------------------------------------------------------
11
+ [dependencies]
12
+ # The only runtime dependency of the core. Without it: ModuleNotFoundError
13
+ # on `import ytlounge`.
14
+ httpx = ">=0.27"
15
+ # Test runner. Without it `pixi run test` fails with "pytest: command not found".
16
+ pytest = ">=8"
17
+ # Builds wheel and sdist for `pixi run build`. Without it: "No module named build".
18
+ python-build = ">=1.2"
19
+ # `pixi run check` validates the built metadata the way PyPI will; also the
20
+ # eventual upload. Without it: "twine: command not found".
21
+ twine = ">=6"
22
+
23
+ [tasks]
24
+ # Unit and fixture tests only. Device tests are opt-in via `pixi run test-device`
25
+ # because they need a TV on the network and would fail in any other setup.
26
+ test = "pytest -m 'not device'"
27
+ test-device = "pytest -m device"
28
+ test-all = "pytest"
29
+ # Release: build both artifacts into dist/, then let twine validate them.
30
+ build = "python -m build"
31
+ check = { cmd = "twine check dist/*", depends-on = ["build"] }
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # backends: Python 3.13 with every backend extra. 3.13 and not 3.14 because
35
+ # pyatv (Apple TV backend) breaks on 3.14, see NOTES.md §5. With 3.14 the
36
+ # appletv extra would silently resolve to nothing (its marker excludes it)
37
+ # and every Apple TV test would error with ModuleNotFoundError: pyatv.
38
+ # ---------------------------------------------------------------------------
39
+ [feature.backends.dependencies]
40
+ python = "3.13.*"
41
+
42
+ [feature.backends.pypi-dependencies]
43
+ # The project itself, editable, with all backend extras. Editable so that
44
+ # changes under src/ are visible without reinstalling; without this line
45
+ # `import yttv` fails inside the environment.
46
+ yttv = { path = ".", editable = true, extras = ["all"] }
47
+ # The protocol core from its own checkout next door, editable, so both can
48
+ # be developed together. Released installs get it from PyPI instead.
49
+ ytlounge = { path = "../ytlounge", editable = true }
50
+
51
+ # ---------------------------------------------------------------------------
52
+ # py314: the newest Python without any backend extra. Proves that ytlounge
53
+ # and the yttv core do not depend on anything that caps the Python version.
54
+ # ---------------------------------------------------------------------------
55
+ [feature.py314.dependencies]
56
+ python = "3.14.*"
57
+
58
+ [feature.py314.pypi-dependencies]
59
+ yttv = { path = ".", editable = true }
60
+ ytlounge = { path = "../ytlounge", editable = true }
61
+
62
+ [environments]
63
+ default = { features = ["backends"] }
64
+ core314 = { features = ["py314"] }
@@ -0,0 +1,62 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "yttv"
7
+ version = "0.1.0"
8
+ description = "Play YouTube videos on a TV: Cast, Apple TV and DIAL devices, all driven through the YouTube Lounge API"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.11"
13
+ authors = [{ name = "desvaters", email = "pypi@desvate.rs" }]
14
+ keywords = ["youtube", "tv", "cast", "chromecast", "apple-tv", "dial", "lounge"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Environment :: Console",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: 3.14",
24
+ "Topic :: Multimedia :: Video",
25
+ ]
26
+ # ytlounge is the protocol core (same author, separate distribution) and
27
+ # brings httpx, the only other runtime dependency, with it.
28
+ dependencies = ["ytlounge>=0.1,<0.2", "httpx>=0.27"]
29
+
30
+ [project.optional-dependencies]
31
+ # Cast TVs (e.g. Samsung Tizen): discovery via mDNS and app launch through
32
+ # pychromecast. Only the launch and screenId retrieval come from here; play and
33
+ # queue always go through ytlounge.
34
+ cast = ["pychromecast>=14"]
35
+ # Apple TV: app launch through the Companion protocol. pyatv does not run on
36
+ # Python 3.14 (asyncio.get_event_loop() raises), which is why this is an extra
37
+ # and not a hard dependency: it must not cap the Python version of the core.
38
+ appletv = ["pyatv>=0.18; python_version < '3.14'"]
39
+ all = ["yttv[cast,appletv]"]
40
+
41
+ [project.scripts]
42
+ yttv = "yttv.cli:main"
43
+
44
+ [project.urls]
45
+ Homepage = "https://github.com/desvaters/yttv"
46
+ Issues = "https://github.com/desvaters/yttv/issues"
47
+
48
+ [tool.hatch.build.targets.wheel]
49
+ packages = ["src/yttv"]
50
+
51
+ # The sdist is what people read; keep the reference implementation and the
52
+ # private working notes out of it (both are git-excluded locally, which
53
+ # hatch does not see).
54
+ [tool.hatch.build.targets.sdist]
55
+ exclude = ["/upstream", "/NOTES.md", "/HANDOFF.md", "/.pixi", "/pixi.lock", "/.claude"]
56
+
57
+ [tool.pytest.ini_options]
58
+ testpaths = ["tests"]
59
+ addopts = "-ra"
60
+ markers = [
61
+ "device: needs a real TV on the network (deselected by default in pixi tasks)",
62
+ ]
@@ -0,0 +1,45 @@
1
+ """yttv: play YouTube videos on a TV.
2
+
3
+ Device backends (Cast, Apple TV, DIAL) bring the YouTube app to the front and
4
+ obtain the screen's ``screen_id``; the ``ytlounge`` package does the actual
5
+ playing.
6
+
7
+ The public API is three functions::
8
+
9
+ import yttv
10
+ yttv.devices() # paired screens, from the cache
11
+ yttv.cast(["dQw4w9WgXcQ"]) # play now on the last used screen
12
+ yttv.cast([url1, url2], queue=True) # append to the queue
13
+ yttv.pair("123 456 789") # link a screen by TV code
14
+ """
15
+
16
+ from .api import (
17
+ CastError,
18
+ InvalidVideoError,
19
+ NoDeviceError,
20
+ PairError,
21
+ YttvError,
22
+ add_device,
23
+ attach,
24
+ cast,
25
+ devices,
26
+ discover,
27
+ pair,
28
+ )
29
+ from .cache import Cache, Device
30
+
31
+ __all__ = [
32
+ "Cache",
33
+ "CastError",
34
+ "Device",
35
+ "InvalidVideoError",
36
+ "NoDeviceError",
37
+ "PairError",
38
+ "YttvError",
39
+ "add_device",
40
+ "attach",
41
+ "cast",
42
+ "devices",
43
+ "discover",
44
+ "pair",
45
+ ]