A small https server that listens for requests to open YouTube URLs in mpv.
I wanted an easy way to share videos from NewPipe on my Android phone with mpv+youtube-dl on my Linux PC.
While I could have gone all out and written an Android application that implements the share API, so that I could share a video link with the app from NewPipe, and the app would send it over to the PC, it sounded like an over-engineering to write an app whose entire purpose is to send a single HTTP POST request, a lot of an unnecessary complexity for something so simple.
Instead I decided on using Termux, which I already have installed and use often.
Termux is an Android terminal emulator and Linux environment app.
You can run bash and various command line programs in it, including curl
!
In addition, Termux allows other apps to share URLs with it, which it then passes to a user-provided shell script as an argument.
You probably can already see where I'm going with this.
The way it works, you share a video URL in NewPipe with Termux app, Termux executes our shell script with the URL as an argument, which runs curl
to send a POST request containing the video URL as a payload to the PC.
Everything happens automatically after you select Termux in the list of apps to share the video with, there is no extra input or interaction required and it all happens in a blink of an eye: Termux pops up, runs the curl
command, and immediately disappears, bringing you back to NewPipe.
Writing a single curl
command is sure easier than writing an Android app!
On PC we run a simple https server app that listens for POST requests and opens links in mpv.
Create a home directory for the server, e.g.:
mkdir "${XDG_DATA_HOME:-$HOME/.local/share}/mpv-url-opener"
Since the server would need to spawn mpv processes with our user's privileges, it's easier to do so if it runs under our user to begin with. So we do a user-local install of the server/daemon.
Put mpv-url-opener.py
in there:
cp mpv-url-opener.py "${XDG_DATA_HOME:-$HOME/.local/share}/mpv-url-opener"
Edit the config file for your needs and install it:
cp config.json "${XDG_DATA_HOME:-$HOME/.local/share}/mpv-url-opener"
editor "${XDG_DATA_HOME:-$HOME/.local/share}/mpv-url-opener/config.json"
Change the default device-username
and device-password
placeholders to something else.
The password would get replaced by its hash once you start the server.
Generate an SSL cert with:
sudo apt install openssl
openssl req -x509 -nodes -newkey rsa:4096 -days 3650 \
-subj '/CN=app.localhost' -addext "subjectAltName=DNS:app.localhost" \
-keyout "${XDG_DATA_HOME:-$HOME/.local/share}/mpv-url-opener/key.pem" \
-out "${XDG_DATA_HOME:-$HOME/.local/share}/mpv-url-opener/cert.pem"
This will create a self-signed certificate for app.localhost
expiring in about 10 years and the corresponding private key.
Keep app.localhost
as it is, don't worry that doesn't resolve to anything, we will address that later.
Create a python virtual environment for the server:
sudo apt install python3-virtualenv
python3 -m virtualenv "${XDG_DATA_HOME:-$HOME/.local/share}/mpv-url-opener/env"
"${XDG_DATA_HOME:-$HOME/.local/share}/mpv-url-opener/env/bin/pip3" install -r requirements.txt
Edit the service file for your needs (paths, IP, port, etc.), install it and start the service:
mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
cp mpv-url-opener.service "${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
editor "${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user/mpv-url-opener.service"
systemctl --user daemon-reload
systemctl --user enable mpv-url-opener.service
systemctl --user start mpv-url-opener.service
Note that you can make the server listen on multiple IP addresses, even on addresses that doesn't yet exist (it sets IP_FREEBIND
).
Just specify more ip:port pairs to --ip-port
, e.g. --ip-port 192.168.1.101:8000 192.168.1.102:8000
.
Check to see if the server is running properly:
systemctl --user status mpv-url-opener.service
journalctl --user -u mpv-url-opener.service
Fix any issues if it fails to start.
Print the SSL certificate fingerprint:
openssl x509 -noout -sha256 -fingerprint \
-in "${XDG_DATA_HOME:-$HOME/.local/share}/mpv-url-opener/cert.pem"
Take a note of it, will need it for later.
The instructions are the same as for Linux, just Windows-ified.
Install Python if you don't have it already.
Get the OpenSSL binary from a trusted source or build it yourself.
Create an mpv-url-opener
folder under %LocalAppData%
.
Copy mpv-url-opener.py
and config.json
in there.
Edit the config file for your needs.
Change the default device-username
and device-password
placeholders to something else.
The password would get replaced by its hash once you start the server.
Generate an SSL cert in the mpv-url-opener
folder with:
openssl.exe req -x509 -nodes -newkey rsa:4096 -days 3650 ^
-subj '/CN=app.localhost' -addext "subjectAltName=DNS:app.localhost" ^
-keyout key.pem ^
-out cert.pem
This will create a self-signed certificate for app.localhost
expiring in about 10 years and the corresponding private key.
Keep app.localhost
as it is, don't worry that doesn't resolve to anything, we will address that later.
Create a python virtual environment for the server in the mpv-url-opener
folder:
python.exe -m venv env
env\Scripts\pip.exe install -r requirements.txt
Create a shortcut to C:\path\to\mpv-url-opener\env\Scripts\pythonw.exe -u mpv-url-opener.py --ip-port 192.168.1.101:8000 --ssl-cert cert.pem --ssl-key key.pem --config config.json
with the path to mpv-url-opener
folder as the working directory (the "Start in:" field) and drop it into shell:startup
folder for the server to auto-start on Windows login. Edit the IP, port, etc. in the shortcut for your needs.
Try to run the shortcut's pythonw.exe
command manually in cmd.exe
and fix any issues if it fails to start.
Print the SSL certificate fingerprint:
openssl.exe x509 -noout -sha256 -fingerprint -in cert.pem
Take a note of it, will need it for later.
Install Termux.
Run Termux and install the required packages:
pkg upgrade
pkg install curl openssl-tool
Create ~/bin
and download the server SSL certificate into it:
mkdir -p ~/bin
echo -n | openssl s_client -connect 192.168.1.101:8000 | openssl x509 > ~/bin/mpv-url-opener.pem
Change the IP address and port to those of the server.
Check that the certificate fingerprint matches the one we got on PC earlier:
openssl x509 -noout -sha256 -fingerprint -in ~/bin/mpv-url-opener.pem
If they don't match, you have downloaded the certificate for the wrong server.
Create termux-url-opener
:
echo '#!/data/data/com.termux/files/usr/bin/sh
curl --capath ./intentionally-invalid-path \
--cacert ~/bin/mpv-url-opener.pem \
-u device-username:device-password \
-d "url=$1" \
--resolve app.localhost:8000:192.168.1.101 \
https://app.localhost:8000/mpv-open-url
' > ~/bin/termux-url-opener
editor ~/bin/termux-url-opener
chmod +x ~/bin/termux-url-opener
Change the IP address and port to those of the server and change device-username
and device-password
to the ones you have set on the server.
--capath
, --cacert
and https://
make sure that curl refuses to connect to anything other than our server (SSL certificate authentication).
--resolve
tells curl to resolve app.localhost
for port 8000 as 192.168.1.101
, which is needed for the certificate verification to work as our certificate is for app.localhost
.
If your server might be using a couple of different IPs, e.g. the server is running on your laptop that you take with you somewhere else, as long as you know the IPs beforehand, you could list them in --resolve
with a comma, e.g. --resolve app.localhost:8000:192.168.1.101,192.168.1.102,192.168.1.103
, from the most probable to the least, and also set --connect-timeout
to some small value so that curl doesn't wait too long when trying to use the wrong IP, e.g. --connect-timeout 0.2
.
curl
will try to connect to the IPs in the order they are listed.
While intended to run on a secure home LAN, I have added some security measures in case I run the server on a laptop and would want to use it while on someone ease's LAN.
- The server is HTTPS only, so the communication is SSL encrypted.
- The server relies on Python providing secure SSL defaults, which it does, as long as you keep python up-to-date enough.
- Since it's intended for home LAN usage, the server uses a self-signed SSL certificate.
- The server authenticates the client via username:password Basic HTTP Authentication, preventing unauthorized parties from opening videos on your PC. Failed attempts are rate limited to 5/hour by the sender IP.
- The client authenticates the server against the pre-downloaded SSL certificate file, preventing MITM and sharing the username:password, along with the video URL, with unintended parties in general.
- The server validates that the input it receives from the client is exactly a YouTube URL, no more and no less, avoiding any command injections. Additionally, it executes the mpv binary directly, without any shell interpreter, which also helps protect against command injections.
The server is a Flask app that uses Waitress WSGI server. Waitress was chosen because it's easy to set socket options using it. I don't run a proxy server on my PC, so I wrote the application with the idea that it won't be running behind a proxy and made it handle everything on it's own: SSL, Basic HTTP Authentication, etc.
As such, running behind a proxy server is not supported, though it could be done with some minimal modifications. Some of the modifications would be: removing the SSL support from the app, delegating SSL handling to the proxy instead, and modifying the rate limiting code, as it would see the requests as coming from the IP of the proxy server (e.g. 127.0.0.1), instead of users' IP addresses.
mpv processes spawned by the server will remain as zombies after mpv is closed. There is no real harm in them remaining, they don't use up RAM or other resources.
The zombie processes get removed whenever the server spawns mpv (at least in the CPython implementation) or when the server is restarted (systemctl --user restart mpv-url-opener.service
).
AGPL-3.0-only