tvcast 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.
@@ -0,0 +1,5 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .DS_Store
4
+ dist/
5
+ .venv/
tvcast-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pushkar
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.
@@ -0,0 +1,44 @@
1
+ Icons: https://lucide.dev (chevron-down, x, arrow-up, arrow-down)
2
+ ISC License
3
+
4
+ Copyright (c) 2026 Lucide Icons and Contributors
5
+
6
+ Permission to use, copy, modify, and/or distribute this software for any
7
+ purpose with or without fee is hereby granted, provided that the above
8
+ copyright notice and this permission notice appear in all copies.
9
+
10
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
+
18
+ ---
19
+
20
+ The following Lucide icons are derived from the Feather project:
21
+
22
+ airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out
23
+
24
+ The MIT License (MIT) (for the icons listed above)
25
+
26
+ Copyright (c) 2013-present Cole Bemis
27
+
28
+ Permission is hereby granted, free of charge, to any person obtaining a copy
29
+ of this software and associated documentation files (the "Software"), to deal
30
+ in the Software without restriction, including without limitation the rights
31
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
32
+ copies of the Software, and to permit persons to whom the Software is
33
+ furnished to do so, subject to the following conditions:
34
+
35
+ The above copyright notice and this permission notice shall be included in all
36
+ copies or substantial portions of the Software.
37
+
38
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
39
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
40
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
41
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
42
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
43
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
44
+ SOFTWARE.
tvcast-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.5
2
+ Name: tvcast
3
+ Version: 0.1.0
4
+ Summary: Cast videos from your Mac to DLNA TVs, with a web panel, CLI and Samsung remote.
5
+ Project-URL: Homepage, https://github.com/thepushkarp/tvcast
6
+ Project-URL: Documentation, https://github.com/thepushkarp/tvcast#readme
7
+ Project-URL: Issues, https://github.com/thepushkarp/tvcast/issues
8
+ Project-URL: Source, https://github.com/thepushkarp/tvcast
9
+ Author: Pushkar
10
+ License-Expression: MIT AND ISC
11
+ License-File: LICENSE
12
+ License-File: LICENSE-ICONS
13
+ Keywords: casting,dlna,macos,remote,samsung,tv,upnp,video
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Environment :: Console
16
+ Classifier: Intended Audience :: End Users/Desktop
17
+ Classifier: Operating System :: MacOS :: MacOS X
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Topic :: Multimedia :: Video
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+
23
+ # TV Cast
24
+
25
+ Play the videos on your Mac on any TV in your home. No app on the TV, no cloud, no converting.
26
+ Control it from a web page or the `tv` command. On Samsung TVs it also works as a remote.
27
+
28
+ ![TV Cast with playback controls, a queue and removable folder tags](https://raw.githubusercontent.com/thepushkarp/tvcast/main/docs/images/demo-dark.jpg)
29
+
30
+ <details>
31
+ <summary>Light theme</summary>
32
+
33
+ ![TV Cast in light mode](https://raw.githubusercontent.com/thepushkarp/tvcast/main/docs/images/demo-light.jpg)
34
+
35
+ </details>
36
+
37
+ Screenshots use a demo library and simulated TV.
38
+
39
+ ## Quick start
40
+
41
+ Needs macOS with the Command Line Tools (`xcode-select --install`).
42
+
43
+ Install with [uv](https://docs.astral.sh/uv/):
44
+
45
+ ```sh
46
+ uv tool install tvcast
47
+ tv install # starts now and at every login
48
+ tv ui # open the control panel
49
+ ```
50
+
51
+ `pipx install tvcast` also works. The package has no runtime dependencies.
52
+ Optional: `brew install ffmpeg` so TV Cast can flag videos the TV can't play.
53
+
54
+ <details>
55
+ <summary>Run from source</summary>
56
+
57
+ ```sh
58
+ git clone https://github.com/thepushkarp/tvcast.git ~/Projects/tvcast
59
+ cd ~/Projects/tvcast
60
+ ./tv install
61
+ ./tv ui
62
+ ```
63
+
64
+ Keep the checkout outside Documents, Desktop and Downloads; macOS restricts login services
65
+ there. To use `tv` from any folder, symlink it into a directory on your `PATH`.
66
+
67
+ </details>
68
+
69
+ In the panel:
70
+
71
+ 1. Turn the TV on and pick it at the top (**Find TVs** if it isn't listed).
72
+ If macOS asks to find devices on your local network, allow it.
73
+ 2. Samsung only: **Remote → Pair with TV**, then press Allow on the TV.
74
+ 3. Hit **Play** on any movie.
75
+
76
+ The panel shows the TV's volume, mute state and current supported app. Use **Apps** to switch;
77
+ **Open on TV** appears only after the TV confirms it. Search your library or choose
78
+ **Continue watching** to find unfinished movies. Add movies to **Queue** to play them in order.
79
+
80
+ ## Everyday commands
81
+
82
+ | Command | What it does |
83
+ |---|---|
84
+ | `tv ui` | open the control panel |
85
+ | `tv open dune` | play a movie, resuming where you left off |
86
+ | `tv open "dune (2021)" --from-start` | pick by year, start from the beginning |
87
+ | `tv pause` · `tv play` · `tv stop` | |
88
+ | `tv seek 1:05:00` | jump to a time |
89
+ | `tv volume 20` · `tv mute on` | |
90
+ | `tv subs on` | subtitles on/off (Samsung) |
91
+ | `tv key home` · `tv key down down enter` | press remote buttons (Samsung) |
92
+ | `tv status` | what's playing, and where |
93
+ | `tv tvs --find` · `tv use 2` | find TVs, switch to another TV |
94
+
95
+ `tv --help` lists everything. To remove TV Cast: `tv uninstall`.
96
+
97
+ ## Updates
98
+
99
+ Stop any active cast, then run `uv tool upgrade tvcast` (or `pipx upgrade tvcast`) followed by
100
+ `tv install` to restart the login service with the installed version. `tv --version` shows the
101
+ CLI version. Settings, pairing and playback history stay in Application Support.
102
+
103
+ Before removing the package, run `tv uninstall`, then `uv tool uninstall tvcast` or
104
+ `pipx uninstall tvcast`.
105
+
106
+ ## What works on which TV
107
+
108
+ | | Any DLNA TV | Samsung (2016 or newer) |
109
+ |---|---|---|
110
+ | Play, pause, seek, volume, resume | ✓ | ✓ |
111
+ | Subtitles (`.srt`) | if the TV supports them | ✓, with on/off |
112
+ | Remote buttons, apps, typing | – | ✓ |
113
+
114
+ ## More
115
+
116
+ - [Your library](https://github.com/thepushkarp/tvcast/blob/main/docs/library.md): folders, subtitles, resume and queue
117
+ - [Troubleshooting](https://github.com/thepushkarp/tvcast/blob/main/docs/troubleshooting.md)
118
+ - [How it works](https://github.com/thepushkarp/tvcast/blob/main/docs/how-it-works.md): for developers
119
+ - [Releasing](https://github.com/thepushkarp/tvcast/blob/main/docs/releasing.md)
120
+
121
+ MIT licensed. Bundled icons retain their [Lucide and Feather notices](https://github.com/thepushkarp/tvcast/blob/main/LICENSE-ICONS).
tvcast-0.1.0/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # TV Cast
2
+
3
+ Play the videos on your Mac on any TV in your home. No app on the TV, no cloud, no converting.
4
+ Control it from a web page or the `tv` command. On Samsung TVs it also works as a remote.
5
+
6
+ ![TV Cast with playback controls, a queue and removable folder tags](https://raw.githubusercontent.com/thepushkarp/tvcast/main/docs/images/demo-dark.jpg)
7
+
8
+ <details>
9
+ <summary>Light theme</summary>
10
+
11
+ ![TV Cast in light mode](https://raw.githubusercontent.com/thepushkarp/tvcast/main/docs/images/demo-light.jpg)
12
+
13
+ </details>
14
+
15
+ Screenshots use a demo library and simulated TV.
16
+
17
+ ## Quick start
18
+
19
+ Needs macOS with the Command Line Tools (`xcode-select --install`).
20
+
21
+ Install with [uv](https://docs.astral.sh/uv/):
22
+
23
+ ```sh
24
+ uv tool install tvcast
25
+ tv install # starts now and at every login
26
+ tv ui # open the control panel
27
+ ```
28
+
29
+ `pipx install tvcast` also works. The package has no runtime dependencies.
30
+ Optional: `brew install ffmpeg` so TV Cast can flag videos the TV can't play.
31
+
32
+ <details>
33
+ <summary>Run from source</summary>
34
+
35
+ ```sh
36
+ git clone https://github.com/thepushkarp/tvcast.git ~/Projects/tvcast
37
+ cd ~/Projects/tvcast
38
+ ./tv install
39
+ ./tv ui
40
+ ```
41
+
42
+ Keep the checkout outside Documents, Desktop and Downloads; macOS restricts login services
43
+ there. To use `tv` from any folder, symlink it into a directory on your `PATH`.
44
+
45
+ </details>
46
+
47
+ In the panel:
48
+
49
+ 1. Turn the TV on and pick it at the top (**Find TVs** if it isn't listed).
50
+ If macOS asks to find devices on your local network, allow it.
51
+ 2. Samsung only: **Remote → Pair with TV**, then press Allow on the TV.
52
+ 3. Hit **Play** on any movie.
53
+
54
+ The panel shows the TV's volume, mute state and current supported app. Use **Apps** to switch;
55
+ **Open on TV** appears only after the TV confirms it. Search your library or choose
56
+ **Continue watching** to find unfinished movies. Add movies to **Queue** to play them in order.
57
+
58
+ ## Everyday commands
59
+
60
+ | Command | What it does |
61
+ |---|---|
62
+ | `tv ui` | open the control panel |
63
+ | `tv open dune` | play a movie, resuming where you left off |
64
+ | `tv open "dune (2021)" --from-start` | pick by year, start from the beginning |
65
+ | `tv pause` · `tv play` · `tv stop` | |
66
+ | `tv seek 1:05:00` | jump to a time |
67
+ | `tv volume 20` · `tv mute on` | |
68
+ | `tv subs on` | subtitles on/off (Samsung) |
69
+ | `tv key home` · `tv key down down enter` | press remote buttons (Samsung) |
70
+ | `tv status` | what's playing, and where |
71
+ | `tv tvs --find` · `tv use 2` | find TVs, switch to another TV |
72
+
73
+ `tv --help` lists everything. To remove TV Cast: `tv uninstall`.
74
+
75
+ ## Updates
76
+
77
+ Stop any active cast, then run `uv tool upgrade tvcast` (or `pipx upgrade tvcast`) followed by
78
+ `tv install` to restart the login service with the installed version. `tv --version` shows the
79
+ CLI version. Settings, pairing and playback history stay in Application Support.
80
+
81
+ Before removing the package, run `tv uninstall`, then `uv tool uninstall tvcast` or
82
+ `pipx uninstall tvcast`.
83
+
84
+ ## What works on which TV
85
+
86
+ | | Any DLNA TV | Samsung (2016 or newer) |
87
+ |---|---|---|
88
+ | Play, pause, seek, volume, resume | ✓ | ✓ |
89
+ | Subtitles (`.srt`) | if the TV supports them | ✓, with on/off |
90
+ | Remote buttons, apps, typing | – | ✓ |
91
+
92
+ ## More
93
+
94
+ - [Your library](https://github.com/thepushkarp/tvcast/blob/main/docs/library.md): folders, subtitles, resume and queue
95
+ - [Troubleshooting](https://github.com/thepushkarp/tvcast/blob/main/docs/troubleshooting.md)
96
+ - [How it works](https://github.com/thepushkarp/tvcast/blob/main/docs/how-it-works.md): for developers
97
+ - [Releasing](https://github.com/thepushkarp/tvcast/blob/main/docs/releasing.md)
98
+
99
+ MIT licensed. Bundled icons retain their [Lucide and Feather notices](https://github.com/thepushkarp/tvcast/blob/main/LICENSE-ICONS).
@@ -0,0 +1,105 @@
1
+ # How it works
2
+
3
+ For developers. Details live in the module docstrings and comments; this page is the map.
4
+
5
+ ## The big picture
6
+
7
+ ```
8
+ tv (terminal) ─┐
9
+ ├─ HTTP ─▶ daemon on 127.0.0.1:8766 (login service local.tvcast)
10
+ web panel ─────┘ │
11
+ ├─ 1. SSDP search ─────────────▶ any DLNA TV on the network
12
+ ├─ 2. UPnP SOAP: play, seek… ──▶ the selected TV
13
+ ├◀ 3. the TV downloads the video from the stream server (:8765)
14
+ ├─ 4. WebSocket (:8002) ───────▶ Samsung only: keys, typing
15
+ └─ 5. REST (:8001) ───────────▶ Samsung only: app status and launch
16
+ ```
17
+
18
+ One daemon owns all state. The panel and the `tv` command are both thin clients of its HTTP API,
19
+ so they never disagree.
20
+
21
+ ## Code map
22
+
23
+ | File | Job |
24
+ |---|---|
25
+ | `tv` | Entry script. Adds the repo to `sys.path` and calls `cli.main()`. |
26
+ | `tvcast/launcher.py` | Installed `tv` entry point and login-service bootstrap, using Apple's isolated Python. |
27
+ | `tvcast/cli.py` | The `tv` command: API client, plus `daemon`, `install` and `uninstall`. |
28
+ | `tvcast/app.py` | The daemon: TV selection, playback tracking, resume points, API actions. |
29
+ | `tvcast/server.py` | HTTP handlers: the stream server and the panel API. |
30
+ | `tvcast/discovery.py` | Finds TVs (SSDP) and reads their device descriptions. |
31
+ | `tvcast/upnp.py` | SOAP calls to the TV: play, pause, seek, volume, Samsung subtitles. |
32
+ | `tvcast/samsung.py` | Samsung remote: pairing, keys, typing, apps. |
33
+ | `tvcast/library.py` | Folder scanning, titles, subtitle matching, codec checks. |
34
+ | `tvcast/state.py` | The JSON state file, including the ordered playback queue. |
35
+ | `tvcast/panel.html` | One HTML file, plain JavaScript. Status polls every 2 s (10 s in a background tab); app checks every 15 s. |
36
+
37
+ ## Playing a movie
38
+
39
+ ```
40
+ open → SetAVTransportURI (video URL + title + subtitles)
41
+ → Play retried while the TV answers "701: not ready"
42
+ → Seek to the resume point
43
+ → subtitles on Samsung only
44
+ then every 2 s: ask the TV where it is; save the position every 10 s
45
+ when it ends: mark progress, then play the next queued movie if there is one
46
+ ```
47
+
48
+ TVs are stored by their device ID (UDN), not their IP address, so a new address from the router
49
+ doesn't lose the TV or its pairing.
50
+
51
+ ## Things that look odd but are on purpose
52
+
53
+ - **Apple's `/usr/bin/python3` only (3.9, standard library).** The TV connects *to* the Mac to
54
+ download the video, and the macOS firewall lets Apple-signed programs accept that without a
55
+ prompt. A background service can't answer a prompt. The installed CLI re-executes through
56
+ this interpreter; the login service points to the bootstrap inside the installed package.
57
+ - **`-I` and the `sys.path` line in `tv`.** Isolated mode ignores the user's Python environment,
58
+ but it also drops the script's own folder from the import path. The packaged launcher adds
59
+ its installation directory explicitly, so Apple's Python can load it from a uv/pipx environment.
60
+ - **Never restart mid-movie.** The TV gives up when its stream connection drops. `tv install`
61
+ refuses while something plays, and the panel page is read from disk on every load, so UI edits
62
+ need no restart.
63
+ - **Not in Documents, Desktop or Downloads.** macOS silently blocks login services from those
64
+ folders, so `tv install` refuses them.
65
+ - **Retrying Play/Seek on error 701.** TVs load a new video in the background and reject commands
66
+ until ready.
67
+ - **Queue consumption after loading.** Entries leave the queue only after playback loads, so
68
+ failed requests can be retried. A playback lock serializes starts, stops and TV selection with
69
+ the monitor's queue advancement; status and queue edits remain responsive during TV requests.
70
+ - **Samsung extras.** The `CaptionInfo.sec` header and `sec:` tags attach subtitles; other TVs
71
+ ignore them. MKV files go out as `video/x-mkv`. Subtitles need an explicit "enable" after loading.
72
+ - **Remote apps by ID.** Samsung TVs ignore the "list installed apps" request, so known app IDs are
73
+ checked with at most four requests at a time. REST launches use an empty POST, then wait up to
74
+ 10 s for `visible: true`; `running` alone can mean a background app. Unconfirmed launches show
75
+ an error. App titles, the current HDMI input and apps outside the known list are not reported.
76
+
77
+ ## Security
78
+
79
+ - The panel API listens on 127.0.0.1 only and checks the `Host` header. Commands also need an
80
+ `X-TVCast: 1` header, so other websites can't drive your TV.
81
+ - The stream server only answers the selected TV's IP address.
82
+ - Everything from the network is untrusted. Discovery accepts only `http://` URLs on the device's
83
+ own host, reads at most 1 MB, and rejects XML with a DTD (Apple's Python has no defence
84
+ against XML bombs).
85
+ - Samsung pairing pins the TV's certificate. A paired TV only moves to a new address when its old
86
+ address stops answering, exactly one device claims to be it, and that device presents the
87
+ pinned certificate. A device forwarding to the real TV can't pass the first check, because
88
+ forwarding needs the TV still reachable. Any TV claimed by two addresses at once is ignored.
89
+ - App status and launch use Samsung's unauthenticated REST API on the local network.
90
+ - `state.json` holds the pairing token and is readable only by you.
91
+
92
+ Not protected: anything on your network can pretend to be an *unpaired* TV, and anything holding
93
+ the selected TV's IP address can download library videos. Traffic on the LAN is unencrypted.
94
+
95
+ ## Development
96
+
97
+ ```sh
98
+ /usr/bin/python3 -m unittest discover -s tests -t . # no TV or network needed
99
+ ./tv daemon --port 18765 --panel-port 18766 --state /tmp/tvcast-test.json --library ~/some/videos
100
+ ./tv status --panel-port 18766 # talk to that test daemon
101
+ ```
102
+
103
+ A test daemon picks the real TV automatically if it's the only one on the network, so don't play
104
+ anything from it unless you mean to. After changing Python code, run `tv install` to restart the
105
+ service.
@@ -0,0 +1,55 @@
1
+ # Your library
2
+
3
+ Videos come from `~/Movies` by default. Manage folder tags under **Folders**; the cross removes
4
+ a folder from the library, without deleting its files.
5
+ Use **Rescan**, reopen the panel or run `tv open` to find new videos. Search by title or year;
6
+ **Continue watching** shows unfinished movies with saved progress.
7
+
8
+ **Skipped Movies/TV — macOS restricted access** means that subfolder could not be read.
9
+ Other folders are still scanned. Keep ordinary video files in a separate readable folder.
10
+
11
+ ## Folder layout
12
+
13
+ ```
14
+ Movies/
15
+ ├── Some Film (2001)/ one video in a folder: the folder name is the title
16
+ │ ├── some.film.2001.1080p.mkv
17
+ │ └── some.film.2001.1080p.srt subtitles: same name as the video
18
+ ├── Some Saga/ a folder of movie folders is a collection
19
+ │ ├── Part One (1999)/part.one.mp4
20
+ │ └── Part Two (2002)/part.two.mp4
21
+ └── Another.Film.2010.mp4 a loose file: the file name is the title
22
+ ```
23
+
24
+ - **Titles:** dots and `[tags]` are cleaned up, and a year like `(2001)` is pulled out.
25
+ - **Subtitles** (`.srt`): the first match wins:
26
+ 1. a file with the video's name;
27
+ 2. for a video alone in its folder, the only `.srt` beside it;
28
+ 3. for a video alone in its folder, an English one in a `Subs/` folder.
29
+ - **Skipped:** samples, extras folders (Featurettes, Extras, Trailers, …) and hidden files.
30
+ - **File types:** mp4, m4v, mov, mkv, avi, webm, ts, m2ts, mpg, mpeg, wmv.
31
+
32
+ ## Resume and queue
33
+
34
+ - A movie resumes a few seconds before where you stopped. Use **Start over** or
35
+ `tv open … --from-start` to begin again.
36
+ - Past 95% counts as watched.
37
+ - **Queue** adds a movie without starting it. Use **Play queue** to start, **Play next** to skip,
38
+ the arrows to reorder, or the cross to remove an entry. **Clear** leaves the current movie playing.
39
+ - The next queued movie starts when the current cast finishes. Stop, TV changes and disconnections
40
+ leave the remaining queue intact. An empty queue ends playback.
41
+ - The queue survives restarts. Unavailable videos stay listed; reconnect their folder and rescan,
42
+ or remove them. Failed playback keeps the entry for retry.
43
+ - The Mac stays awake while a movie plays. Closing the lid still puts it to sleep.
44
+
45
+ ## Videos the TV can't play
46
+
47
+ With `ffprobe` installed (`brew install ffmpeg`), each video is checked once. A video the TV likely
48
+ can't decode shows **Try anyway** instead of **Play**.
49
+
50
+ The most common case is DTS or TrueHD audio on Samsung TVs, which plays silent. To fix a file,
51
+ convert just its audio:
52
+
53
+ ```sh
54
+ ffmpeg -i in.mkv -map 0:v:0 -map 0:a:0 -c:v copy -c:a ac3 -b:a 640k out.mkv
55
+ ```
@@ -0,0 +1,34 @@
1
+ # Releasing
2
+
3
+ The version lives in `tvcast/__init__.py`. Releases contain a wheel and a source distribution;
4
+ both include the web panel and license notices. Runtime dependencies must remain empty because
5
+ the daemon runs with Apple's isolated Python, outside the installer's Python environment.
6
+
7
+ ## One-time setup
8
+
9
+ In the PyPI account that will own the project, add a
10
+ [pending Trusted Publisher](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/):
11
+
12
+ | Field | Value |
13
+ |---|---|
14
+ | PyPI project | `tvcast` |
15
+ | GitHub owner | `thepushkarp` |
16
+ | Repository | `tvcast` |
17
+ | Workflow | `release.yml` |
18
+ | Environment | `pypi` |
19
+
20
+ Create the matching GitHub environment under repository Settings → Environments.
21
+ The workflow uses PyPI's Trusted Publishing; no API token belongs in the repository.
22
+ A pending publisher does not reserve the package name.
23
+
24
+ ## Each release
25
+
26
+ 1. Update `__version__`, run the tests, and merge the release changes after the Package check passes.
27
+ 2. Build locally with `uv build` and check metadata with `uvx twine check dist/*`.
28
+ 3. Publish a GitHub release from `main` with the matching tag, such as `v0.1.0`.
29
+ 4. The Package workflow tests on macOS, builds from the source distribution, smoke-tests an
30
+ installed wheel, and publishes those artifacts to PyPI. It rejects mismatched tags and versions.
31
+ 5. Confirm the version on PyPI, then try `uvx --from tvcast==0.1.0 tv --version` from outside the checkout.
32
+
33
+ Only the GitHub release event publishes. Pull requests and pushes to `main` build and test
34
+ without uploading to PyPI. A published version cannot be overwritten; corrections need a new version.
@@ -0,0 +1,17 @@
1
+ # Troubleshooting
2
+
3
+ Log: `~/Library/Logs/tvcast.log` · Settings and resume points: `~/Library/Application Support/tvcast/state.json`
4
+
5
+ | Problem | Fix |
6
+ |---|---|
7
+ | "TV Cast isn't running" | `tv install`, then check the log. |
8
+ | No TV found | Turn the TV on and make sure it's on the same Wi-Fi as the Mac (not a guest network). If you declined macOS's local-network prompt, allow it in **System Settings → Privacy & Security → Local Network**. Turn off any VPN that routes all traffic. Then **Find TVs** again. |
9
+ | Worked before, now the TV doesn't respond | The TV probably has a new address. With the TV fully on, **Find TVs** updates it. |
10
+ | "macOS blocks this service from reading it" | Move the videos out of Documents, Desktop, Downloads or external drives, e.g. into `~/Movies`. |
11
+ | Video plays without sound | The audio is DTS or TrueHD. See [Videos the TV can't play](library.md#videos-the-tv-cant-play). |
12
+ | Subtitles don't show (Samsung) | `tv subs on` again; the TV sometimes says they're on without drawing them. |
13
+ | Remote "refused" | On the TV: **Settings → General → External Device Manager → Device Connection Manager → Device List**, allow TV Cast, then pair again. |
14
+ | App did not confirm it opened | Check the TV for a prompt, then try again. **Refresh** under Apps checks the supported installed apps; an app running in the background is not shown as open. |
15
+ | "Different certificate than when paired" | The TV was reset or updated. Pair again. |
16
+ | `tv install` says "Bootstrap failed: 5" | Run it again. |
17
+ | Video never starts | Check that the macOS firewall isn't set to block all incoming connections. |
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27,<2"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "tvcast"
7
+ dynamic = ["version"]
8
+ description = "Cast videos from your Mac to DLNA TVs, with a web panel, CLI and Samsung remote."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT AND ISC"
12
+ license-files = ["LICENSE", "LICENSE-ICONS"]
13
+ authors = [{name = "Pushkar"}]
14
+ keywords = ["macos", "dlna", "upnp", "samsung", "tv", "casting", "video", "remote"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Environment :: Console",
18
+ "Intended Audience :: End Users/Desktop",
19
+ "Operating System :: MacOS :: MacOS X",
20
+ "Programming Language :: Python :: 3",
21
+ "Topic :: Multimedia :: Video",
22
+ ]
23
+ dependencies = []
24
+
25
+ [project.scripts]
26
+ tv = "tvcast.launcher:main"
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/thepushkarp/tvcast"
30
+ Documentation = "https://github.com/thepushkarp/tvcast#readme"
31
+ Issues = "https://github.com/thepushkarp/tvcast/issues"
32
+ Source = "https://github.com/thepushkarp/tvcast"
33
+
34
+ [tool.hatch.version]
35
+ path = "tvcast/__init__.py"
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["tvcast"]
39
+
40
+ [tool.hatch.build.targets.sdist]
41
+ include = ["/tvcast", "/tests", "/docs", "/tv", "/README.md", "/LICENSE", "/LICENSE-ICONS", "/pyproject.toml"]
42
+ exclude = ["/docs/images"]
File without changes
@@ -0,0 +1,38 @@
1
+ <?xml version="1.0"?>
2
+ <root xmlns="urn:schemas-upnp-org:device-1-0">
3
+ <specVersion><major>1</major><minor>0</minor></specVersion>
4
+ <URLBase>http://127.0.0.1:49152/base/</URLBase>
5
+ <device>
6
+ <deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>
7
+ <friendlyName>Media Hub</friendlyName>
8
+ <manufacturer>Example Audio</manufacturer>
9
+ <UDN>uuid:00000000-0000-0000-0000-000000000002</UDN>
10
+ <serviceList>
11
+ <service>
12
+ <serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType>
13
+ <serviceId>urn:upnp-org:serviceId:ContentDirectory</serviceId>
14
+ <controlURL>ctl/cds</controlURL>
15
+ </service>
16
+ </serviceList>
17
+ <deviceList>
18
+ <device>
19
+ <deviceType>urn:schemas-upnp-org:device:MediaRenderer:3</deviceType>
20
+ <friendlyName>Bedroom TV</friendlyName>
21
+ <manufacturer>Example Audio</manufacturer>
22
+ <UDN>uuid:00000000-0000-0000-0000-000000000003</UDN>
23
+ <serviceList>
24
+ <service>
25
+ <serviceType>urn:schemas-upnp-org:service:ConnectionManager:3</serviceType>
26
+ <serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId>
27
+ <controlURL>ctl/cm</controlURL>
28
+ </service>
29
+ <service>
30
+ <serviceType>urn:schemas-upnp-org:service:AVTransport:3</serviceType>
31
+ <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
32
+ <controlURL>ctl/avt</controlURL>
33
+ </service>
34
+ </serviceList>
35
+ </device>
36
+ </deviceList>
37
+ </device>
38
+ </root>
@@ -0,0 +1,31 @@
1
+ <?xml version="1.0"?>
2
+ <root xmlns="urn:schemas-upnp-org:device-1-0">
3
+ <specVersion><major>1</major><minor>0</minor></specVersion>
4
+ <device>
5
+ <deviceType>urn:schemas-upnp-org:device:InternetGatewayDevice:1</deviceType>
6
+ <friendlyName>Home Router</friendlyName>
7
+ <manufacturer>Example Networks</manufacturer>
8
+ <UDN>uuid:00000000-0000-0000-0000-000000000004</UDN>
9
+ <serviceList>
10
+ <service>
11
+ <serviceType>urn:schemas-upnp-org:service:Layer3Forwarding:1</serviceType>
12
+ <serviceId>urn:upnp-org:serviceId:L3Forwarding1</serviceId>
13
+ <controlURL>/ctl/L3F</controlURL>
14
+ </service>
15
+ </serviceList>
16
+ <deviceList>
17
+ <device>
18
+ <deviceType>urn:schemas-upnp-org:device:WANDevice:1</deviceType>
19
+ <friendlyName>WAN Device</friendlyName>
20
+ <UDN>uuid:00000000-0000-0000-0000-000000000005</UDN>
21
+ <serviceList>
22
+ <service>
23
+ <serviceType>urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1</serviceType>
24
+ <serviceId>urn:upnp-org:serviceId:WANCommonIFC1</serviceId>
25
+ <controlURL>/ctl/CmnIfCfg</controlURL>
26
+ </service>
27
+ </serviceList>
28
+ </device>
29
+ </deviceList>
30
+ </device>
31
+ </root>
@@ -0,0 +1,29 @@
1
+ <?xml version="1.0"?>
2
+ <root xmlns="urn:schemas-upnp-org:device-1-0" xmlns:dlna="urn:schemas-dlna-org:device-1-0">
3
+ <specVersion><major>1</major><minor>0</minor></specVersion>
4
+ <device>
5
+ <deviceType>urn:schemas-upnp-org:device:MediaRenderer:1</deviceType>
6
+ <dlna:X_DLNADOC>DMR-1.50</dlna:X_DLNADOC>
7
+ <friendlyName>Living Room TV</friendlyName>
8
+ <manufacturer>Example Electronics</manufacturer>
9
+ <modelName>Example TV 1000</modelName>
10
+ <UDN>uuid:00000000-0000-0000-0000-000000000001</UDN>
11
+ <serviceList>
12
+ <service>
13
+ <serviceType>urn:schemas-upnp-org:service:RenderingControl:1</serviceType>
14
+ <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
15
+ <controlURL>upnp/control/RenderingControl1</controlURL>
16
+ </service>
17
+ <service>
18
+ <serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType>
19
+ <serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId>
20
+ <controlURL>/upnp/control/ConnectionManager1</controlURL>
21
+ </service>
22
+ <service>
23
+ <serviceType>urn:schemas-upnp-org:service:AVTransport:1</serviceType>
24
+ <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
25
+ <controlURL>/upnp/control/AVTransport1</controlURL>
26
+ </service>
27
+ </serviceList>
28
+ </device>
29
+ </root>