Beam local files, URLs, and cached Premiumize magnets from macOS to an Android TV running mpv.
Find a file
2026-05-27 12:43:08 -04:00
src/mpvtv Harden magnet handler shell command generation 2026-05-27 12:43:08 -04:00
tests Harden magnet handler shell command generation 2026-05-27 12:43:08 -04:00
.gitignore mpvtv 1.0.0 2026-05-25 14:57:43 -04:00
pyproject.toml Add macOS magnet URI handler 2026-05-27 12:43:08 -04:00
README.md Harden magnet handler shell command generation 2026-05-27 12:43:08 -04:00

mpvtv

mpvtv beams media from a Mac to an Android TV running mpv for Android.

It plays:

  • local media files on your Mac
  • direct playback URLs
  • cached Premiumize magnet links

The primary interface is a full-screen terminal app. Scripted and unattended playback is available with quiet mode.

mpvtv is Mac-first software. It assumes macOS, Wi-Fi ADB, and an Android TV on the same network.

Contents

Requirements

mpvtv needs:

  • macOS
  • Python 3.11 or newer
  • Android platform-tools, for adb
  • an Android TV with network ADB enabled
  • mpv for Android installed on the TV, or permission for mpvtv to install it
  • a Premiumize API key for magnet playback

Install Android platform-tools with Homebrew:

brew install android-platform-tools

Confirm adb is available:

adb version

The TV must be reachable from the Mac by IP address. If your TV uses Android's wireless debugging workflow, enable it on the TV before using mpvtv.

Installation

The recommended install method is pipx, which gives mpvtv its own Python environment and puts the mpvtv command on your shell path.

From a checkout of this repository:

python3 -m pip install --user pipx
python3 -m pipx ensurepath
pipx install .

Open a new terminal, then run:

mpvtv

To reinstall after pulling changes from the repository:

pipx install --force .

For development:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e .
mpvtv

First Run

Start the app:

mpvtv

If no TV is configured, mpvtv opens setup.

Setup asks for:

  • a friendly TV name
  • the TV's ADB IP address, with an optional port
  • ADB authorization on the TV
  • mpv availability on the TV
  • an optional Premiumize API key

TV IDs are assigned automatically. The first TV becomes the default TV unless you later choose another default.

ADB addresses may be written with or without a port:

10.81.10.128
10.81.10.128:5555

When the TV asks whether to allow ADB debugging, approve the prompt on the TV.

If mpv is not installed, mpvtv shows the detected Android CPU ABI and offers to install the matching mpv APK.

Premiumize setup is optional. Local files and direct URLs do not require Premiumize.

Interactive Playback

Run:

mpvtv

The interactive app is organized around three screens:

  • TV
  • Media
  • Playback

Choose a TV, paste a source, and start playback.

The TV screen shows:

  • configured TV ID
  • friendly name
  • ADB address
  • whether the TV is currently attached to ADB
  • an asterisk next to the configured default TV

The media screen accepts one input. Paste any supported source:

/Users/me/Movies/movie.mkv
https://example.com/video.mkv
rtsp://example.com/live
magnet:?xt=urn:btih:...

On macOS, dragging a local file into the terminal inserts its path into the focused source field.

You can also start the TUI with a source already supplied:

mpvtv "/Users/me/Movies/movie.mkv"
mpvtv -t 1 "https://example.com/video.mkv"
mpvtv -t 1 "magnet:?xt=urn:btih:..."

If -t/--tv is supplied, that TV is selected. If no TV is supplied and a default TV is configured, the default is selected. If no default TV is configured, the TV picker is shown.

Interactive playback starts from the beginning. Use k during playback to skip to a position.

Quiet Mode

Quiet mode is for command-line playback without the full-screen TUI.

Use -q or --quiet:

mpvtv -q -t 1 "https://example.com/video.mkv"

Quiet mode requires:

  • a source
  • either -t/--tv or a configured default TV

Examples:

mpvtv -q -t 1 "https://example.com/video.mkv"
mpvtv -q -t 1 "/Users/me/Movies/movie.mkv"
mpvtv -q -t 1 --seek 12:34 "/Users/me/Movies/movie.mkv"
mpvtv -q -t 1 "magnet:?xt=urn:btih:..."

If a default TV is configured, -t/--tv may be omitted:

mpvtv -q "https://example.com/video.mkv"

Quiet mode prints timestamped status lines. It exits with a non-zero status when playback cannot be started.

For URL and Premiumize playback, quiet mode exits after handing the source to mpv. For local files, quiet mode stays running because it is serving the media file to the TV. Press Ctrl-C to stop quiet local-file playback; quiet cleanup force-stops mpv on the TV.

The mpv install prompt may still appear in quiet mode if mpv is missing from the TV.

Sources

mpvtv detects the source type automatically.

Local Files

Anything that is not a supported URL or magnet link is treated as a local path.

Supported forms include:

/Users/me/Movies/movie.mkv
~/Movies/movie.mkv
/Users/me/Movies/movie\ with\ spaces.mkv
file:///Users/me/Movies/movie.mkv

The file must exist, be a regular file, and be readable.

URLs

Supported URL schemes:

http
https
rtmp
rtmps
rtp
rtsp
mms
mmst
mmsh
tcp
udp

http:// and https:// URLs must include a host. The other supported streaming schemes are accepted by scheme prefix.

Examples:

https://example.com/video.mkv
rtsp://camera.local/live
udp://239.0.0.1:1234
udp:239.0.0.1:1234

URL playback is handed directly to mpv on the TV.

Magnets

Magnet links must contain a valid btih info hash:

magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567

mpvtv supports 40-character hex btih hashes and 32-character base32 btih hashes.

Premiumize magnet playback requires:

  • a configured Premiumize API key
  • a cache hit from Premiumize
  • at least one supported media file in the direct download result

If a magnet contains multiple playable files, mpvtv naturally sorts the file list before displaying choices or auto-selecting the first file in quiet mode.

Subtitles

Subtitle attachment is automatic for:

  • local files
  • Premiumize magnets

Direct URL playback is left unchanged.

For a local file, mpvtv looks in the same directory for a readable subtitle file with the same base name:

movie.mp4
movie.srt

For Premiumize, mpvtv filters returned direct-download content for subtitle files and attaches the same-base-name match for the selected media file.

Supported subtitle extensions:

aqt
gsub
jss
sub
ttxt
pjs
psb
rt
smi
slt
ssf
srt
ssa
ass
usf
idx
vtt

Disable subtitle attachment with:

mpvtv -ns "/Users/me/Movies/movie.mkv"
mpvtv --nosubs "magnet:?xt=urn:btih:..."

Playback Controls

Interactive playback keys:

m        change media by reopening the media screen
k        skip to a position
f        change file, when a Premiumize magnet has multiple playable files
s        stop mpv on the TV and exit
q        quit mpvtv without force-stopping mpv
Ctrl-R   replay the last mpv launch command
Ctrl-C   open the stop prompt

If the stop prompt is open, pressing Ctrl-C again exits immediately and leaves playback running on the TV.

Changing media keeps the current TV selected and reopens the source input so you can beam another local file, URL, or magnet link.

Other interactive keys:

Ctrl-T   add a TV from the TV screen
Ctrl-P   open Premiumize settings from the TV or Media screen
d        make the highlighted TV the default from the TV screen
q        quit from non-playback screens

Seek positions accept:

90
12:34
1:02:03

These mean seconds, mm:ss, and hh:mm:ss. The Android intent receives the value in milliseconds as --ei position.

For local playback, quitting mpvtv closes the local server. Direct URL and Premiumize playback may continue independently on the TV.

TV Management

TVs are stored in the mpvtv config file.

List TVs:

mpvtv config tv list

Add a TV:

mpvtv config tv add --name "Living Room TV" --ip 10.81.10.128

Add a TV and make it the default:

mpvtv config tv add --name "Bedroom TV" --ip 10.81.10.129 --default

Save a TV without validating ADB and mpv:

mpvtv config tv add --name "Bedroom TV" --ip 10.81.10.129 --skip

Set the default TV:

mpvtv config tv set-default --id 2

Remove a TV:

mpvtv config tv remove --id 2

mpvtv config tv add always assigns the next numeric ID automatically.

When adding a TV without --skip, mpvtv saves the TV first, then validates ADB connectivity and mpv availability. This allows the config to remain available for later use even when validation needs attention.

If the default TV is removed, another configured TV is selected as the default when one is available.

Premiumize

Premiumize is used only for magnet playback.

Set or replace the API key:

mpvtv config premiumize set

Set the key directly:

mpvtv config premiumize set --api-key YOUR_API_KEY

Save without checking account info:

mpvtv config premiumize set --api-key YOUR_API_KEY --skip

Show whether a key is configured:

mpvtv config premiumize status

Remove the key:

mpvtv config premiumize clear

The status command does not call Premiumize. It only reports whether a key is saved.

The set command saves the key before checking account info. If the check fails, the key remains saved so it can be replaced or tested again later.

When account checking succeeds, mpvtv reports whether premium access is active and shows the subscription expiry time.

macOS Magnet Handler

Install a macOS magnet: URL handler:

mpvtv macos magnet install

This creates ~/Applications/mpvtv Magnet Handler.app, registers it with Launch Services, and makes it the default handler for magnet links. Opening a magnet link creates a temporary .command file and asks macOS to open it, so your configured app for shell command files can run regular mpvtv "magnet:...". The full TUI, TV picker, setup flow, and prompts remain available.

The temporary .command file deletes itself as soon as it starts. The handler shell-quotes both the configured mpvtv executable path and the incoming magnet URI before writing the command, and the URI is not interpolated raw into shell code. A stale file should only be left behind if macOS creates the file but never launches it.

If mpvtv is installed somewhere unusual, pass the executable path:

mpvtv macos magnet install --mpvtv /Users/me/.local/bin/mpvtv

Show the current macOS magnet handler:

mpvtv macos magnet status

If you reinstall mpvtv to a different executable path, rerun the install command.

Configuration

Default config path:

~/.config/mpvtv/mpvtv.ini

Override the config path with MPVTV_CONFIG:

MPVTV_CONFIG=/tmp/mpvtv.ini mpvtv
MPVTV_CONFIG=/tmp/mpvtv.ini mpvtv config tv list

Example config:

[tvs]
default = 1

[tv:1]
name = Living Room TV
ip = 10.81.10.128

[premiumize]
api_key = YOUR_API_KEY

TV sections are named tv:ID. The [tvs] default value points at the configured TV ID used by quiet mode when -t/--tv is not supplied.

How Playback Works

ADB and mpv

Playback uses Android intents sent through ADB.

The selected TV is addressed with adb -s TV_IP:PORT, so multiple attached ADB devices are supported.

mpv package:

is.xyz.mpv

mpv activity:

is.xyz.mpv/.MPVActivity

After connecting to a TV and before checking mpv, mpvtv wakes the TV and opens the home screen:

adb -s TV_IP:5555 shell input keyevent KEYCODE_WAKEUP
adb -s TV_IP:5555 shell input keyevent KEYCODE_HOME

Before launching playback, mpvtv force-stops mpv:

adb -s TV_IP:5555 shell am force-stop is.xyz.mpv

Playback launch shape:

adb -s TV_IP:5555 shell am start --user 0 \
  -a android.intent.action.VIEW \
  -n is.xyz.mpv/.MPVActivity \
  -d "MEDIA_URL" \
  -t "video/any"

Seek uses milliseconds:

--ei position 83590000

Subtitles use a string extra:

--es subs "SUBTITLE_URL"

mpv Installation

mpv availability is checked with:

adb -s TV_IP:5555 shell pm list packages is.xyz.mpv

If mpv is missing, the TV CPU ABI is read with:

adb -s TV_IP:5555 shell getprop ro.product.cpu.abi

Supported ABIs:

arm64-v8a
armeabi-v7a

Configured APK URLs:

https://github.com/mpv-android/mpv-android/releases/download/2026-04-25/app-default-arm64-v8a-release.apk
https://github.com/mpv-android/mpv-android/releases/download/2026-04-25/app-default-armeabi-v7a-release.apk

The APK is downloaded to a temporary directory, installed with adb install -r, and removed when the temporary directory is cleaned up.

Local File Streaming

Local files are served from the Mac by a temporary aiohttp server.

The local server:

  • binds a random local port
  • serves only the selected media file
  • also serves the matching subtitle file, when one is found
  • rejects clients whose IP is not the selected TV IP
  • supports HTTP byte-range requests for seeking
  • shuts down when mpvtv exits

The local IP is resolved in this order:

  1. ipconfig getifaddr en0
  2. parsing ifconfig en0
  3. socket fallback using the selected TV address

Premiumize Magnet Flow

For magnets, mpvtv:

  • validates the magnet link
  • extracts the btih info hash
  • checks /api/cache/check
  • stops if Premiumize reports a cache miss
  • calls /api/transfer/directdl after a cache hit
  • filters returned files to supported video extensions, then falls back to supported audio extensions
  • filters subtitle files separately
  • attaches a matching subtitle when available
  • naturally sorts playable files before display or automatic selection

Supported video extensions:

3g2
3gp
avi
flv
mkv
mk3d
mov
mp2
mp4
m4v
mpe
mpeg
mpg
mpv
webm
wmv
ogm
ts
mts
m2ts

Supported audio fallback extensions:

flac
mp3
ogg

CLI Reference

Playback

mpvtv [SOURCE] [-t ID] [--seek POSITION] [-q] [-ns]
mpvtv -v

Arguments and options:

SOURCE
  Local file path, playback URL, or magnet link.

-t, --tv ID
  Configured TV ID.

--seek, --position POSITION
  Quiet-mode start position. Accepts seconds, mm:ss, or hh:mm:ss.
  In the TUI, playback starts from the beginning and seek is available with k.

-q, --quiet
  Use unattended CLI mode.

-ns, --nosubs
  Disable automatic subtitle attachment.

-v, --version
  Print the mpvtv version and exit.

Config

mpvtv config tv list
mpvtv config tv add --name NAME --ip IP [--default] [--skip]
mpvtv config tv set-default --id ID
mpvtv config tv remove --id ID

mpvtv config premiumize status
mpvtv config premiumize set [--api-key KEY] [--skip]
mpvtv config premiumize clear

mpvtv macos magnet install [--mpvtv PATH]
mpvtv macos magnet status

Detailed command help:

mpvtv --help
mpvtv config --help
mpvtv config tv --help
mpvtv config tv add --help
mpvtv config premiumize --help
mpvtv config premiumize set --help
mpvtv macos magnet --help

Troubleshooting

mpvtv: command not found

Reopen your terminal after pipx ensurepath, or run from the source tree:

PYTHONPATH=src python3 -m mpvtv

adb was not found on PATH

Install Android platform-tools:

brew install android-platform-tools

No TV appears attached

Check the TV IP address, confirm network ADB is enabled, and make sure the TV is awake and on the same network as the Mac.

TV stays unauthorized

Approve the ADB debugging prompt on the TV. If the prompt does not appear, disable and re-enable network debugging on the TV.

TV is offline

Reconnect ADB from the TV settings, wake the TV, and verify the IP address in mpvtv config tv list.

mpv is missing

Accept the install prompt, or install mpv for Android manually.

Magnet unavailable

The magnet is not cached by Premiumize. mpvtv does not start uncached Premiumize transfers.

Premiumize reports "Not logged in"

Replace the saved API key:

mpvtv config premiumize set

Local playback stops when mpvtv exits

Local files are served from the Mac. Keep mpvtv running while watching a local file.

Direct URL or Premiumize playback continues after quitting

Those sources are remote URLs handed to mpv. Use s in the TUI to stop mpv on the TV, or run playback again and stop it from the app.

Development

Run tests:

python3 -m pytest

Run from source:

PYTHONPATH=src python3 -m mpvtv

Run quiet mode from source with a temporary config:

MPVTV_CONFIG=/tmp/mpvtv.ini PYTHONPATH=src python3 -m mpvtv -q -t 1 "https://example.com/video.mkv"

Compile-check the package:

python3 -m compileall src/mpvtv

Primary modules:

src/mpvtv/cli.py          argparse entrypoint and scriptable commands
src/mpvtv/tui/app.py      Textual full-screen app
src/mpvtv/config.py       INI config loading and TV management
src/mpvtv/source.py       source classification
src/mpvtv/seek.py         seek parsing
src/mpvtv/subtitles.py    subtitle matching
src/mpvtv/preflight.py    TV, mpv, and Premiumize checks
src/mpvtv/playback.py     playback orchestration
src/mpvtv/adb.py          ADB and mpv APK operations
src/mpvtv/local_server.py local HTTP file server
src/mpvtv/premiumize.py   Premiumize API client and filtering