Repository: akiirui/mpv-handler
Branch: main
Commit: 87da92284796
Files: 21
Total size: 45.2 KB
Directory structure:
gitextract_j2hgwr2j/
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ └── build.yml
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
├── README.zh-Hans.md
├── README.zh-Hant.md
├── share/
│ ├── linux/
│ │ ├── config.toml
│ │ ├── mpv-handler-debug.desktop
│ │ └── mpv-handler.desktop
│ ├── proto.html
│ └── windows/
│ ├── config.toml
│ ├── handler-install.bat
│ └── handler-uninstall.bat
└── src/
├── config.rs
├── error.rs
├── main.rs
├── plugins/
│ ├── mod.rs
│ └── play.rs
└── protocol.rs
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "weekly"
================================================
FILE: .github/workflows/build.yml
================================================
name: Build
on:
workflow_dispatch:
push:
tags:
- "*"
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: "-C strip=symbols"
jobs:
build:
name: Build for ${{ matrix.os}}
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: ubuntu-latest
artifact_name: mpv-handler
asset_name: linux-amd64
target: x86_64-unknown-linux-musl
- os: windows-latest
artifact_name: mpv-handler.exe
artifact_name_debug: mpv-handler-debug.exe
asset_name: windows-amd64
asset_name_debug: windows-amd64-debug
target: x86_64-pc-windows-msvc
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install dependencies
if: matrix.target == 'x86_64-unknown-linux-musl'
run: |
sudo apt-get install -y musl
rustup target add x86_64-unknown-linux-musl
- name: Build mpv-handler
run: |
cargo build --release --locked --target ${{ matrix.target }}
- name: Upload to artifact
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.asset_name }}
path: target/${{ matrix.target }}/release/${{ matrix.artifact_name }}
- name: Build mpv-handler-debug for Windows
if: matrix.os == 'windows-latest'
run: |
cargo build --release --locked --target ${{ matrix.target }} `
--features console
move target/${{ matrix.target }}/release/${{ matrix.artifact_name }} `
target/${{ matrix.target }}/release/${{ matrix.artifact_name_debug }}
- name: Upload mpv-handler-debug to artifact
if: matrix.os == 'windows-latest'
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.asset_name_debug }}
path: target/${{ matrix.target }}/release/${{ matrix.artifact_name_debug }}
publish:
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Download artifacts
uses: actions/download-artifact@v8
- name: Package Linux
run: |
zip -j mpv-handler-linux-amd64.zip \
README*.md \
share/linux/* \
linux-amd64/mpv-handler
- name: Package Windows
run: |
zip -j mpv-handler-windows-amd64.zip \
README*.md \
share/windows/* \
windows-amd64/mpv-handler.exe \
windows-amd64-debug/mpv-handler-debug.exe
- name: SHA512SUM
run: |
sha512sum mpv-handler-*.zip > sha512sum
- name: Publish
uses: ncipollo/release-action@v1
with:
artifacts: "mpv-handler-*.zip,sha512sum"
generateReleaseNotes: true
================================================
FILE: .gitignore
================================================
/target
================================================
FILE: Cargo.toml
================================================
[package]
name = "mpv-handler"
version = "0.4.2"
edition = "2024"
authors = ["Akatsuki Rui <akiirui@outlook.com>"]
description = "Play website videos and songs with mpv & yt-dlp"
license = "MIT"
readme = "README.md"
homepage = "https://github.com/akiirui/mpv-handler"
repository = "https://github.com/akiirui/mpv-handler"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
base64 = "0.22"
dirs = "6.0"
serde = { version = "1.0", features = ["derive"] }
thiserror = "2.0"
toml = "1.1"
[features]
console = []
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 Akatsuki Rui
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
[English][readme-en] | [简体中文][readme-zh-hans] | [繁体中文][readme-zh-hant]
[readme-en]: https://github.com/akiirui/mpv-handler/blob/main/README.md
[readme-zh-hans]: https://github.com/akiirui/mpv-handler/blob/main/README.zh-Hans.md
[readme-zh-hant]: https://github.com/akiirui/mpv-handler/blob/main/README.zh-Hant.md
# mpv handler
A protocol handler for **mpv**, written by Rust.
Use **mpv** and **yt-dlp** to play video and music from the websites.
Please use it with userscript:
[![play-with-mpv][badges-play-with-mpv]][greasyfork-play-with-mpv]
## Breaking changes
### [v0.4.0][v0.4.0]
To avoid conflicts with the `mpv://` protocol provided by mpv.
> mpv://...
>
> mpv protocol. This is used for starting mpv from URL handler. The protocol is stripped and the rest is passed to the player as a normal open argument. Only safe network protocols are allowed to be opened this way.
Scheme `mpv://` and `mpv-debug://` are deprecated, use `mpv-handler://` and `mpv-handler-debug://`.
**Require manual intervention**
#### Windows
Run `handler-uninstall.bat` to uninstall deprecated protocol, and run `handler-install.bat` to install new procotol.
#### Linux
If you installed manually, please repeat the manual installation process.
## Protocol

### Scheme
- `mpv-handler`: Run mpv-handler without console window
- `mpv-handler-debug`: Run mpv-handler with console window to view outputs and errors
### Plugins
- `play`: Use mpv player to play video
### Encoded Data
Use [URL-safe base64][rfc-base64-url] to encode the URL or TITLE.
Replace `/` to `_`, `+` to `-` and remove padding `=`.
Example (JavaScript):
```javascript
let data = btoa("https://www.youtube.com/watch?v=Ggkn2f5e-IU");
let safe = data.replace(/\//g, "_").replace(/\+/g, "-").replace(/\=/g, "");
```
### Parameters (Optional)
```
cookies = [ www.domain.com.txt ]
profile = [ default, low-latency, etc... ]
quality = [ 2160p, 1440p, 1080p, 720p, 480p, 360p ]
v_codec = [ av01, vp9, h265, h264 ]
v_title = [ Encoded Title ]
subfile = [ Encoded URL ]
startat = [ Seconds (float) ]
referrer = [ Encoded URL ]
```
## Installation
### Linux
#### Arch Linux
[![mpv-handler][badges-aur]][download-aur]
[![mpv-handler-git][badges-aur-git]][download-aur-git]
#### Manual installation
1. Download [latest Linux release][download-linux]
2. Unzip the archive
3. Copy `mpv-handler` to `$HOME/.local/bin`
4. Copy `mpv-handler.desktop` to `$HOME/.local/share/applications/`
5. Copy `mpv-handler-debug.desktop` to `$HOME/.local/share/applications/`
6. Set executable permission for binary
- ```
$ chmod +x $HOME/.local/bin/mpv-handler
```
7. Register xdg-mime (thanks for the [linuxuprising][linuxuprising] reminder)
- ```
$ xdg-mime default mpv-handler.desktop x-scheme-handler/mpv-handler
$ xdg-mime default mpv-handler-debug.desktop x-scheme-handler/mpv-handler-debug
```
8. Add `$HOME/.local/bin` to your environment variable `PATH`
9. **Optional**: _Copy `config.toml` to `$HOME/.config/mpv-handler/config.toml` and configure_
### Windows
Windows users need to install manually.
#### Manual installation
1. Download [latest Windows release][download-windows]
2. Unzip the archive to the directory you want
3. Run `handler-install.bat` to register protocol handler
4. Edit `config.toml` and set `mpv` and `ytdl` path
## Configuration
```toml
mpv = "/usr/bin/mpv"
# Optional, Type: String
# The path of mpv executable binary
# Default value:
# - Linux: mpv
# - Windows: mpv.com
ytdl = "/usr/bin/yt-dlp"
# Optional, Type: String
# The path of yt-dlp executable binary
proxy = "http://example.com:8080"
# Optional, Type: String
# HTTP(S) proxy server address
# For Windows users:
# - The path can be "C:\\folder\\some.exe" or "C:/folder/some.exe"
# - The path target is an executable binary file, not a directory
```
[v0.4.0]: https://github.com/akiirui/mpv-handler/releases/tag/v0.4.0
[rfc-base64-url]: https://datatracker.ietf.org/doc/html/rfc4648#section-5
[badges-aur-git]: https://img.shields.io/aur/version/mpv-handler-git?style=for-the-badge&logo=archlinux&label=mpv-handler-git
[badges-aur]: https://img.shields.io/aur/version/mpv-handler?style=for-the-badge&logo=archlinux&label=mpv-handler
[badges-play-with-mpv]: https://img.shields.io/greasyfork/v/416271?style=for-the-badge&logo=greasyfork&label=play-with-mpv
[download-aur-git]: https://aur.archlinux.org/packages/mpv-handler-git/
[download-aur]: https://aur.archlinux.org/packages/mpv-handler/
[download-linux]: https://github.com/akiirui/mpv-handler/releases/latest/download/mpv-handler-linux-amd64.zip
[download-macos]: https://github.com/akiirui/mpv-handler/releases/latest/download/mpv-handler-macos-amd64.zip
[download-windows]: https://github.com/akiirui/mpv-handler/releases/latest/download/mpv-handler-windows-amd64.zip
[greasyfork-play-with-mpv]: https://greasyfork.org/scripts/416271-play-with-mpv
[linuxuprising]: https://www.linuxuprising.com/2021/07/open-youtube-and-more-videos-from-your.html
================================================
FILE: README.zh-Hans.md
================================================
[English][readme-en] | [简体中文][readme-zh-hans] | [繁体中文][readme-zh-hant]
[readme-en]: https://github.com/akiirui/mpv-handler/blob/main/README.md
[readme-zh-hans]: https://github.com/akiirui/mpv-handler/blob/main/README.zh-Hans.md
[readme-zh-hant]: https://github.com/akiirui/mpv-handler/blob/main/README.zh-Hant.md
# mpv handler
一个 **mpv** 的协议处理程序,使用 Rust 编写。
使用 **mpv** 和 **yt-dlp** 播放网站上的视频与音乐。
请配合用户脚本使用:
[![play-with-mpv][badges-play-with-mpv]][greasyfork-play-with-mpv]
## 重大变更
### [v0.4.0][v0.4.0]
为了避免与 mpv 所提供的 `mpv://` 协议冲突。
> mpv://...
>
> mpv protocol. This is used for starting mpv from URL handler. The protocol is stripped and the rest is passed to the player as a normal open argument. Only safe network protocols are allowed to be opened this way.
协议 `mpv://` 和 `mpv-debug://` 已弃用, 请使用 `mpv-handler://` 和 `mpv-handler-debug://`.
**需要手动干预**
#### Windows
运行 `handler-uninstall.bat` 卸载已弃用的协议, 然后运行 `handler-install.bat` 安装新的协议.
#### Linux
如果你是手动安装的,请重新执行一遍手动安装流程。
## 协议

### 协议名
- `mpv-handler`: 在没有命令行窗口的情况下运行 mpv-handler
- `mpv-handler-debug`: 在有命令行窗口的情况下运行 mpv-handler 以便于查看输出和错误
### 插件 / Plugins
- `play`: 使用 mpv 播放视频
### 编码数据 / Encoded Data
使用 [URL 安全的 base64][rfc-base64-url] 编码网址或标题。
替换 `/` 至 `_`, `+` 至 `-` 并且删除填充的 `=`。
示例 (JavaScript):
```javascript
let data = btoa("https://www.youtube.com/watch?v=Ggkn2f5e-IU");
let safe = data.replace(/\//g, "_").replace(/\+/g, "-").replace(/\=/g, "");
```
### 参数 / Parameters (可选)
```
cookies = [ www.domain.com.txt ]
profile = [ default, low-latency, etc... ]
quality = [ 2160p, 1440p, 1080p, 720p, 480p, 360p ]
v_codec = [ av01, vp9, h265, h264 ]
v_title = [ Encoded Title ]
subfile = [ Encoded URL ]
startat = [ Seconds (float) ]
referrer = [ Encoded URL ]
```
## 安装
### Linux
#### Arch Linux
[![mpv-handler][badges-aur]][download-aur]
[![mpv-handler-git][badges-aur-git]][download-aur-git]
#### 手动安装
1. 下载 [最新的 Linux 压缩包][download-linux]
2. 解压缩压缩包
3. 复制 `mpv-handler` 至 `$HOME/.local/bin`
4. 复制 `mpv-handler.desktop` 至 `$HOME/.local/share/applications/`
5. 复制 `mpv-handler-debug.desktop` 至 `$HOME/.local/share/applications/`
6. 为二进制文件设置可执行权限
- ```
$ chmod +x $HOME/.local/bin/mpv-handler
```
7. 注册 xdg-mime(感谢 [linuxuprising][linuxuprising] 的提醒)
- ```
$ xdg-mime default mpv-handler.desktop x-scheme-handler/mpv-handler
$ xdg-mime default mpv-handler-debug.desktop x-scheme-handler/mpv-handler-debug
```
8. 添加 `$HOME/.local/bin` 到环境变量 `PATH`
9. **可选**: _复制 `config.toml` 至 `$HOME/.config/mpv-handler/config.toml` 并配置_
### Windows
Windows 用户目前只能手动安装。
#### 手动安装
1. 下载 [最新的 Windows 压缩包][download-windows]
2. 解压缩档案到你想要的位置
3. 运行 `handler-install.bat` 注册协议处理程序
4. 编辑 `config.toml` 设置 `mpv` 和 `ytdl` 的路径
## 配置
```toml
mpv = "/usr/bin/mpv"
# 可选,类型:字符串
# mpv 可执行文件的路径
# 默认值:
# - Linux: mpv
# - Windows: mpv.com
ytdl = "/usr/bin/yt-dlp"
# 可选,类型:字符串
# yt-dlp 可执行文件的路径
proxy = "http://example.com:8080"
# 可选,类型:字符串
# HTTP(S) 代理服务器的地址
# 对于 Windows 用户:
# - 路径格式可以是 "C:\\folder\\some.exe",也可以是 "C:/folder/some.exe"
# - 路径的目标是可执行二进制文件,而不是目录
```
[v0.4.0]: https://github.com/akiirui/mpv-handler/releases/tag/v0.4.0
[rfc-base64-url]: https://datatracker.ietf.org/doc/html/rfc4648#section-5
[badges-aur-git]: https://img.shields.io/aur/version/mpv-handler-git?style=for-the-badge&logo=archlinux&label=mpv-handler-git
[badges-aur]: https://img.shields.io/aur/version/mpv-handler?style=for-the-badge&logo=archlinux&label=mpv-handler
[badges-play-with-mpv]: https://img.shields.io/greasyfork/v/416271?style=for-the-badge&logo=greasyfork&label=play-with-mpv
[download-aur-git]: https://aur.archlinux.org/packages/mpv-handler-git/
[download-aur]: https://aur.archlinux.org/packages/mpv-handler/
[download-linux]: https://github.com/akiirui/mpv-handler/releases/latest/download/mpv-handler-linux-amd64.zip
[download-macos]: https://github.com/akiirui/mpv-handler/releases/latest/download/mpv-handler-macos-amd64.zip
[download-windows]: https://github.com/akiirui/mpv-handler/releases/latest/download/mpv-handler-windows-amd64.zip
[greasyfork-play-with-mpv]: https://greasyfork.org/scripts/416271-play-with-mpv
[linuxuprising]: https://www.linuxuprising.com/2021/07/open-youtube-and-more-videos-from-your.html
================================================
FILE: README.zh-Hant.md
================================================
[English][readme-en] | [簡體中文][readme-zh-hans] | [繁體中文][readme-zh-hant]
[readme-en]: https://github.com/akiirui/mpv-handler/blob/main/README.md
[readme-zh-hans]: https://github.com/akiirui/mpv-handler/blob/main/README.zh-Hans.md
[readme-zh-hant]: https://github.com/akiirui/mpv-handler/blob/main/README.zh-Hant.md
# mpv handler
一個 **mpv** 的協議處理程序,使用 Rust 編寫。
使用 **mpv** 和 **yt-dlp** 播放網站上的視頻與音樂。
請配合用戶腳本使用:
[![play-with-mpv][badges-play-with-mpv]][greasyfork-play-with-mpv]
## 重大變更
### [v0.4.0][v0.4.0]
爲了避免與 mpv 所提供的 `mpv://` 協議衝突。
> mpv://...
>
> mpv protocol. This is used for starting mpv from URL handler. The protocol is stripped and the rest is passed to the player as a normal open argument. Only safe network protocols are allowed to be opened this way.
協議 `mpv://` 和 `mpv-debug://` 已棄用, 請使用 `mpv-handler://` 和 `mpv-handler-debug://`.
**需要手動干預**
#### Windows
運行 `handler-uninstall.bat` 卸載已棄用的協議, 然後運行 `handler-install.bat` 安裝新的協議.
#### Linux
如果你是手動安裝的,請重新執行一遍手動安裝流程。
## 協議

### 協議名
- `mpv-handler`: 在沒有命令行窗口的情況下運行 mpv-handler
- `mpv-handler-debug`: 在有命令行窗口的情況下運行 mpv-handler 以便於查看輸出和錯誤
### 插件 / Plugins
- `play`: 使用 mpv 播放視頻
### 編碼數據 / Encoded Data
使用 [URL 安全的 base64][rfc-base64-url] 編碼網址或標題。
替換 `/` 至 `_`, `+` 至 `-` 並且刪除填充的 `=`。
示例 (JavaScript):
```javascript
let data = btoa("https://www.youtube.com/watch?v=Ggkn2f5e-IU");
let safe = data.replace(/\//g, "_").replace(/\+/g, "-").replace(/\=/g, "");
```
### 參數 / Parameters (可選)
```
cookies = [ www.domain.com.txt ]
profile = [ default, low-latency, etc... ]
quality = [ 2160p, 1440p, 1080p, 720p, 480p, 360p ]
v_codec = [ av01, vp9, h265, h264 ]
v_title = [ Encoded Title ]
subfile = [ Encoded URL ]
startat = [ Seconds (float) ]
referrer = [ Encoded URL ]
```
## 安裝
### Linux
#### Arch Linux
[![mpv-handler][badges-aur]][download-aur]
[![mpv-handler-git][badges-aur-git]][download-aur-git]
#### 手動安裝
1. 下載 [最新的 Linux 壓縮包][download-linux]
2. 解壓縮壓縮包
3. 複製 `mpv-handler` 至 `$HOME/.local/bin`
4. 複製 `mpv-handler.desktop` 至 `$HOME/.local/share/applications/`
5. 複製 `mpv-handler-debug.desktop` 至 `$HOME/.local/share/applications/`
6. 爲二進制文件設置可執行權限
- ```
$ chmod +x $HOME/.local/bin/mpv-handler
```
7. 註冊 xdg-mime(感謝 [linuxuprising][linuxuprising] 的提醒)
- ```
$ xdg-mime default mpv-handler.desktop x-scheme-handler/mpv-handler
$ xdg-mime default mpv-handler-debug.desktop x-scheme-handler/mpv-handler-debug
```
8. 添加 `$HOME/.local/bin` 到環境變量 `PATH`
9. **可選**: _複製 `config.toml` 至 `$HOME/.config/mpv-handler/config.toml` 並配置_
### Windows
Windows 用戶目前只能手動安裝。
#### 手動安裝
1. 下載 [最新的 Windows 壓縮包][download-windows]
2. 解壓縮檔案到你想要的位置
3. 運行 `handler-install.bat` 註冊協議處理程序
4. 編輯 `config.toml` 設置 `mpv` 和 `ytdl` 的路徑
## 配置
```toml
mpv = "/usr/bin/mpv"
# 可選,類型:字符串
# mpv 可執行文件的路徑
# 默認值:
# - Linux: mpv
# - Windows: mpv.com
ytdl = "/usr/bin/yt-dlp"
# 可選,類型:字符串
# yt-dlp 可執行文件的路徑
proxy = "http://example.com:8080"
# 可選,類型:字符串
# HTTP(S) 代理服務器的地址
# 對於 Windows 用戶:
# - 路徑格式可以是 "C:\\folder\\some.exe",也可以是 "C:/folder/some.exe"
# - 路徑的目標是可執行二進制文件,而不是目錄
```
[v0.4.0]: https://github.com/akiirui/mpv-handler/releases/tag/v0.4.0
[rfc-base64-url]: https://datatracker.ietf.org/doc/html/rfc4648#section-5
[badges-aur-git]: https://img.shields.io/aur/version/mpv-handler-git?style=for-the-badge&logo=archlinux&label=mpv-handler-git
[badges-aur]: https://img.shields.io/aur/version/mpv-handler?style=for-the-badge&logo=archlinux&label=mpv-handler
[badges-play-with-mpv]: https://img.shields.io/greasyfork/v/416271?style=for-the-badge&logo=greasyfork&label=play-with-mpv
[download-aur-git]: https://aur.archlinux.org/packages/mpv-handler-git/
[download-aur]: https://aur.archlinux.org/packages/mpv-handler/
[download-linux]: https://github.com/akiirui/mpv-handler/releases/latest/download/mpv-handler-linux-amd64.zip
[download-macos]: https://github.com/akiirui/mpv-handler/releases/latest/download/mpv-handler-macos-amd64.zip
[download-windows]: https://github.com/akiirui/mpv-handler/releases/latest/download/mpv-handler-windows-amd64.zip
[greasyfork-play-with-mpv]: https://greasyfork.org/scripts/416271-play-with-mpv
[linuxuprising]: https://www.linuxuprising.com/2021/07/open-youtube-and-more-videos-from-your.html
================================================
FILE: share/linux/config.toml
================================================
#mpv = "/path/of/mpv"
# Optional, Type: String
# The path of mpv executable binary
# Default value:
# - Linux: mpv
# - Windows: mpv.com
#ytdl = "/path/of/ytdl"
# Optional, Type: String
# The path of yt-dlp executable binary
#proxy = "http://example.com:8080"
# Optional, Type: String
# HTTP(S) proxy server address
================================================
FILE: share/linux/mpv-handler-debug.desktop
================================================
[Desktop Entry]
Type=Application
Name=mpv-handler-debug
GenericName=Multimedia player
Comment=Play website videos and songs with mpv & yt-dlp (Debug)
NoDisplay=true
Icon=mpv
Exec=mpv-handler %u
Terminal=true
Categories=AudioVideo;Audio;Video;Player;TV;
MimeType=x-scheme-handler/mpv-handler-debug;
X-KDE-Protocols=mpv-handler-debug
================================================
FILE: share/linux/mpv-handler.desktop
================================================
[Desktop Entry]
Type=Application
Name=mpv-handler
GenericName=Multimedia player
Comment=Play website videos and songs with mpv & yt-dlp
NoDisplay=true
Icon=mpv
Exec=mpv-handler %u
Terminal=false
Categories=AudioVideo;Audio;Video;Player;TV;
MimeType=x-scheme-handler/mpv-handler;
X-KDE-Protocols=mpv-handler
================================================
FILE: share/proto.html
================================================
<html>
<head>
<style>
body {
height: fit-content;
width: max-content;
padding: 5px 5px 10px 5px;
}
p {
font-family: "Roboto Condensed";
font-size: 24px;
}
span {
border-style: solid;
border-width: 1px;
border-radius: 5px;
padding: 2px;
position: relative;
}
span::before {
content: "";
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-bottom: 5px solid black;
position: absolute;
bottom: -10px;
left: calc(50% - 5px);
transform: rotate(180deg);
}
span::after {
width: 200%;
font-size: 16px;
text-align: center;
position: absolute;
bottom: -30px;
left: -50%;
}
.scheme {
background: #cfff95;
border-color: #9ccc65;
}
.scheme::after {
content: "Scheme";
}
.plugins {
background: #73e8ff;
border-color: #29b6f6;
}
.plugins::after {
content: "Plugins";
}
.url {
background: #ffd95b;
border-color: #ffa726;
}
.url::after {
content: "Encoded URL";
}
.params {
background: #df78ef;
border-color: #ab47bc;
}
.params::after {
content: "Parameters (Optional)";
}
</style>
</head>
<body>
<p>
<span class="scheme">mpv-handler</span>://<span class="plugins">play</span
>/<span class="url"
>aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g_dj1HZ2tuMmY1ZS1JVQ</span
>/<span class="params">?param1=value1&param2=value2</span>
</p>
</body>
</html>
================================================
FILE: share/windows/config.toml
================================================
#mpv = "C:\\path\\of\\mpv.com"
# Optional, Type: String
# The path of mpv executable binary
# Default value:
# - Linux: mpv
# - Windows: mpv.com
#ytdl = "C:\\path\\of\\yt-dlp.exe"
# Optional, Type: String
# The path of yt-dlp executable binary
#proxy = "http://example.com:8080"
# Optional, Type: String
# HTTP(S) proxy server address
# For Windows users:
# - The path can be "C:\\folder\\some.exe" or "C:/folder/some.exe"
# - The path is an executable binary file, not a directory
================================================
FILE: share/windows/handler-install.bat
================================================
@echo OFF
:: Unattended install flag. When set, the script will not require user input.
set unattended=no
if "%1"=="/u" set unattended=yes
:: Make sure this is Windows Vista or later
call :ensure_vista
:: Make sure the script is running as admin
call :ensure_admin
:: Get mpv.exe location
call :check_binary
:: Add registry
call :add_verbs
:die
if not [%1] == [] echo %~1
if [%unattended%] == [yes] exit 1
pause
exit 1
:ensure_admin
:: 'openfiles' is just a commmand that is present on all supported Windows
:: versions, requires admin privileges and has no side effects, see:
:: https://stackoverflow.com/questions/4051883/batch-script-how-to-check-for-admin-rights
openfiles >nul 2>&1
if errorlevel 1 (
echo This batch script requires administrator privileges.
echo Right-click on handler-install.bat and select "Run as administrator".
call :die
)
goto :EOF
:ensure_vista
ver | find "XP" >nul
if not errorlevel 1 (
echo This batch script only works on Windows Vista and later. To create file
echo associations on Windows XP, right click on a video file and use "Open with...".
call :die
)
goto :EOF
:check_binary
cd /D %~dp0
set mpv_handler_conf=%cd%\config.toml
set mpv_handler_path=%cd%\mpv-handler.exe
set mpv_handler_debug_path=%cd%\mpv-handler-debug.exe
if not exist "%mpv_handler_conf%" call :die "Not found config.toml"
if not exist "%mpv_handler_path%" call :die "Not found mpv-handler.exe"
if not exist "%mpv_handler_debug_path%" call :die "Not found mpv-handler-debug.exe"
goto :EOF
:reg
:: Wrap the reg command to check for errors
>nul reg %*
if errorlevel 1 set error=yes
if [%error%] == [yes] echo Error in command: reg %*
if [%error%] == [yes] call :die
goto :EOF
:add_verbs
:: Add the mpv protocol to the registry
call :reg add "HKCR\mpv-handler" /d "URL:MPV Handler" /f
call :reg add "HKCR\mpv-handler" /v "Content Type" /d "application/x-mpv-handler" /f
call :reg add "HKCR\mpv-handler" /v "URL Protocol" /f
call :reg add "HKCR\mpv-handler\DefaultIcon" /d "\"%mpv_exe_path%\",1" /f
call :reg add "HKCR\mpv-handler\shell\open\command" /d "\"%mpv_handler_path%\" \"%%%%1\"" /f
:: Add the mpv protocol to the registry
call :reg add "HKCR\mpv-handler-debug" /d "URL:MPV Handler Debug" /f
call :reg add "HKCR\mpv-handler-debug" /v "Content Type" /d "application/x-mpv-handler-debug" /f
call :reg add "HKCR\mpv-handler-debug" /v "URL Protocol" /f
call :reg add "HKCR\mpv-handler-debug\DefaultIcon" /d "\"%mpv_exe_path%\",1" /f
call :reg add "HKCR\mpv-handler-debug\shell\open\command" /d "\"%mpv_handler_debug_path%\" \"%%%%1\"" /f
echo Successfully installed mpv-handler
echo Enjoy!
goto :EOF
================================================
FILE: share/windows/handler-uninstall.bat
================================================
@echo OFF
:: Unattended install flag. When set, the script will not require user input.
set unattended=no
if "%1"=="/u" set unattended=yes
:: Make sure this is Windows Vista or later
call :ensure_vista
:: Make sure the script is running as admin
call :ensure_admin
:: Delete registry
call :del_verbs
:die
if not [%1] == [] echo %~1
if [%unattended%] == [yes] exit 1
pause
exit 1
:ensure_admin
:: 'openfiles' is just a commmand that is present on all supported Windows
:: versions, requires admin privileges and has no side effects, see:
:: https://stackoverflow.com/questions/4051883/batch-script-how-to-check-for-admin-rights
openfiles >nul 2>&1
if errorlevel 1 (
echo This batch script requires administrator privileges.
echo Right-click on handler-uninstall.bat and select "Run as administrator".
call :die
)
goto :EOF
:ensure_vista
ver | find "XP" >nul
if not errorlevel 1 (
echo This batch script only works on Windows Vista and later. To create file
echo associations on Windows XP, right click on a video file and use "Open with...".
call :die
)
goto :EOF
:reg
:: Wrap the reg command to check for errors
>nul reg %*
if errorlevel 1 set error=yes
if [%error%] == [yes] echo Error in command: reg %*
if [%error%] == [yes] call :die
goto :EOF
:del_verbs
:: Delete deprecated mpv and mpv-debug protocol
call :reg delete "HKCR\mpv" /f
call :reg delete "HKCR\mpv-debug" /f
:: Delete protocol
call :reg delete "HKCR\mpv-handler" /f
call :reg delete "HKCR\mpv-handler-debug" /f
echo Successfully uninstalled mpv-handler
goto :EOF
================================================
FILE: src/config.rs
================================================
use crate::error::Error;
use serde::Deserialize;
use std::path::PathBuf;
/// Config of mpv-handler
///
/// - `mpv`: mpv binary path
/// - `ytdl`: yt-dlp binary path
/// - `proxy: HTTP(S) proxy server address
#[derive(Debug, Deserialize)]
pub struct Config {
pub mpv: Option<String>,
pub ytdl: Option<String>,
pub proxy: Option<String>,
}
impl Config {
/// Load config file and retruns `Config`
///
/// If config file doesn't exists, returns default value
pub fn load() -> Result<Config, Error> {
if let Some(mut path) = get_config_dir() {
path.push("config.toml");
if path.exists() {
let data: String = std::fs::read_to_string(&path)?;
let mut config: Config = toml::from_str(&data)?;
if let Some(mpv) = config.mpv {
config.mpv = Some(realpath(mpv)?);
}
if let Some(ytdl) = config.ytdl {
config.ytdl = Some(realpath(ytdl)?);
}
return Ok(config);
}
}
Ok(default_config())
}
}
/// Returns config directory path of mpv-handler
pub fn get_config_dir() -> Option<PathBuf> {
// Linux config directory location: $XDG_CONFIG_HOME/mpv-handler/
#[cfg(unix)]
{
if let Some(mut v) = dirs::config_dir() {
v.push("mpv-handler");
return Some(v);
}
}
// Windows config directory location: %WORKING_DIR%\
#[cfg(windows)]
{
if let Ok(mut v) = std::env::current_exe() {
v.pop();
return Some(v);
}
}
eprintln!("Failed to get config directory");
None
}
/// The default value of `Config.mpv`
pub fn default_mpv() -> Result<String, Error> {
#[cfg(unix)]
return realpath("mpv");
#[cfg(windows)]
return realpath("mpv.com");
}
/// The defalut value of `Config`
fn default_config() -> Config {
Config {
mpv: None,
ytdl: None,
proxy: None,
}
}
fn realpath<T: AsRef<std::ffi::OsStr>>(path: T) -> Result<String, Error> {
let path = std::path::PathBuf::from(&path);
if path.is_relative() {
#[cfg(windows)]
{
if let Some(mut p) = crate::config::get_config_dir() {
p.push(&path);
if let Ok(rp) = p.canonicalize() {
return Ok(rp.display().to_string());
};
}
}
if let Some(paths) = std::env::var_os("PATH") {
for mut p in std::env::split_paths(&paths) {
p.push(&path);
if let Ok(rp) = p.canonicalize() {
return Ok(rp.display().to_string());
};
}
}
}
Ok(path.display().to_string())
}
#[test]
fn test_config_parse() {
// Custom values
let config: Config = toml::from_str(
r#"
mpv = "/usr/bin/mpv"
ytdl = "/usr/bin/yt-dlp"
proxy = "http://example.com:8080"
"#,
)
.unwrap();
assert_eq!(config.mpv, Some("/usr/bin/mpv".to_string()));
assert_eq!(config.ytdl, Some("/usr/bin/yt-dlp".to_string()));
assert_eq!(config.proxy, Some("http://example.com:8080".to_string()));
// Unexpected values
let config: Config = toml::from_str(
r#"
key1 = "value1"
key2 = "value2"
key3 = "value3"
"#,
)
.unwrap();
#[cfg(unix)]
assert_eq!(config.mpv, None);
assert_eq!(config.ytdl, None);
assert_eq!(config.proxy, None);
}
================================================
FILE: src/error.rs
================================================
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("Too many arguments")]
TooManyArgs,
#[error("Incorrect protocol \"{0}\"")]
IncorrectProtocol(String),
#[error("Incorrect video URL \"{0}\"")]
IncorrectVideoURL(String),
#[error("Dangerous video protocol \"{0}\"")]
DangerousVideoProtocol(String),
#[error("Player exited by error")]
PlayerExited(u8),
#[error("Failed to run player ({0})")]
PlayerRunFailed(std::io::Error),
#[error("Failed to decode ({0})")]
FromBase64Error(#[from] base64::DecodeError),
#[error("Failed to decode ({0})")]
FromStringError(#[from] std::string::FromUtf8Error),
#[error("Failed to decode ({0})")]
FromTomlError(#[from] toml::de::Error),
#[error("Failed to decode ({0})")]
FromIoError(#[from] std::io::Error),
}
================================================
FILE: src/main.rs
================================================
#![cfg_attr(
all(target_os = "windows", not(feature = "console"), not(debug_assertions)),
windows_subsystem = "windows"
)]
mod config;
mod error;
mod plugins;
mod protocol;
use std::process::ExitCode;
use crate::config::Config;
use crate::error::Error;
use crate::plugins::Plugins;
use crate::protocol::Protocol;
fn main() -> ExitCode {
match run() {
Ok(_) => ExitCode::SUCCESS,
Err(e) => print_error(e),
}
}
/// Run handler
fn run() -> Result<(), Error> {
let args: Vec<String> = std::env::args().collect();
let arg: &str = match args.len() {
2 => &args[1],
1 => return Ok(print_usage()),
_ => return Err(Error::TooManyArgs),
};
let proto = Protocol::parse(arg)?;
let config = Config::load()?;
// Call plugin by scheme
match proto.plugin {
Plugins::Play => crate::plugins::play::exec(&proto, &config),
}
}
/// Print usage
fn print_usage() {
let version: &str = option_env!("MPV_HANDLER_VERSION").unwrap_or(env!("CARGO_PKG_VERSION"));
println!("mpv-handler {}\n", version);
println!("Usage:\n {}\n", "mpv-handler <url>",);
}
/// Print error
fn print_error(e: Error) -> ExitCode {
eprint!("{e}");
std::io::Read::read(&mut std::io::stdin(), &mut []).unwrap();
match e {
Error::PlayerExited(code) => ExitCode::from(code),
_ => ExitCode::FAILURE,
}
}
================================================
FILE: src/plugins/mod.rs
================================================
pub mod play;
#[derive(Debug, PartialEq)]
pub enum Plugins {
Play,
}
================================================
FILE: src/plugins/play.rs
================================================
use crate::config::Config;
use crate::error::Error;
use crate::protocol::Protocol;
const PREFIX_COOKIES: &str = "--ytdl-raw-options-append=cookies=";
const PREFIX_PROFILE: &str = "--profile=";
const PREFIX_FORMATS: &str = "--ytdl-raw-options-append=format-sort=";
const PREFIX_V_TITLE: &str = "--title=";
const PREFIX_SUBFILE: &str = "--sub-file=";
const PREFIX_STARTAT: &str = "--start=";
const PREFIX_REFERRER: &str = "--referrer=";
const PREFIX_YT_PATH: &str = "--script-opts=ytdl_hook-ytdl_path=";
/// Execute player with given options
pub fn exec(proto: &Protocol, config: &Config) -> Result<(), Error> {
let mut options: Vec<&str> = Vec::new();
let option_cookies: String;
let option_profile: String;
let option_formats: String;
let option_v_title: String;
let option_subfile: String;
let option_startat: String;
let option_yt_path: String;
let option_referrer: String;
// Append cookies option
if let Some(v) = proto.cookies {
if let Some(v) = cookies(v) {
option_cookies = v;
options.push(&option_cookies);
}
}
// Append profile option
if let Some(v) = proto.profile {
option_profile = profile(v);
options.push(&option_profile);
}
// Append formats option
if proto.quality.is_some() || proto.v_codec.is_some() {
if let Some(v) = formats(proto.quality, proto.v_codec) {
option_formats = v;
options.push(&option_formats);
}
}
// Append v_title option
if let Some(v) = &proto.v_title {
option_v_title = v_title(v);
options.push(&option_v_title);
}
// Append subfile option
if let Some(v) = &proto.subfile {
option_subfile = subfile(v);
options.push(&option_subfile);
}
// Append startat option
if let Some(v) = &proto.startat {
option_startat = startat(v);
options.push(&option_startat);
}
// Append referrer option
if let Some(v) = &proto.referrer {
option_referrer = referrer(v);
options.push(&option_referrer);
}
// Set custom ytdl execute file path
if let Some(v) = &config.ytdl {
option_yt_path = yt_path(v);
options.push(&option_yt_path);
}
// Print binaries and options list (in debug build)
if &proto.scheme == &crate::protocol::Schemes::MpvHandlerDebug || cfg!(debug_assertions) {
// Print binaries
println!("Binaries:");
if let Some(v) = &config.mpv {
println!(" {}", v);
} else {
println!(" {}", crate::config::default_mpv()?);
}
if let Some(v) = &config.ytdl {
println!(" {}", v);
}
// Print options list
if !options.is_empty() {
println!("Options:");
for option in &options {
println!(" {}", option);
}
}
}
// Print video URL
println!("Playing: {}", proto.url);
// Execute mpv player
let mut command;
if let Some(v) = &config.mpv {
command = std::process::Command::new(v);
} else {
command = std::process::Command::new(crate::config::default_mpv()?);
}
command.args(&options).arg("--").arg(&proto.url);
// Hide console window on Windows if not in debug mode
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
if &proto.scheme == &crate::protocol::Schemes::MpvHandler && !cfg!(debug_assertions) {
command.creation_flags(0x08000000);
}
}
// Set HTTP(S) proxy environment variables
if let Some(proxy) = &config.proxy {
command.env("http_proxy", proxy);
command.env("HTTP_PROXY", proxy);
command.env("https_proxy", proxy);
command.env("HTTPS_PROXY", proxy);
}
// Fix some browsers to overwrite "LD_LIBRARY_PATH" on Linux
// It will be broken mpv player
// mpv: symbol lookup error: mpv: undefined symbol: vkCreateWaylandSurfaceKHR
#[cfg(unix)]
command.env_remove("LD_LIBRARY_PATH");
// Fix Vivaldi to overwrite "LD_PRELOAD" on Linux
// https://github.com/akiirui/mpv-handler/issues/78
#[cfg(unix)]
command.env_remove("LD_PRELOAD");
match command.status() {
Ok(o) => match o.code() {
Some(code) => match code {
0 => Ok(()),
_ => Err(Error::PlayerExited(code as u8)),
},
None => Ok(()),
},
Err(e) => Err(Error::PlayerRunFailed(e)),
}
}
/// Return cookies option
fn cookies(cookies: &str) -> Option<String> {
match crate::config::get_config_dir() {
Some(mut p) => {
p.push("cookies");
p.push(cookies);
if p.exists() {
let cookies = p.display();
return Some(format!("{PREFIX_COOKIES}{cookies}"));
} else {
eprintln!("Cookies file not found \"{}\"", p.display());
return None;
}
}
None => None,
}
}
/// Return profile option
fn profile(profile: &str) -> String {
format!("{PREFIX_PROFILE}{profile}")
}
/// Return formats option
fn formats(quality: Option<&str>, v_codec: Option<&str>) -> Option<String> {
let mut f: Vec<String> = Vec::new();
let formats: String;
if let Some(v) = quality {
let i: String = v.matches(char::is_numeric).collect();
f.push(format!("res:{i}"));
}
if let Some(v) = v_codec {
f.push(format!("+vcodec:{v}"))
}
formats = f.join(",");
Some(format!("{PREFIX_FORMATS}{formats}"))
}
/// Return v_title option
fn v_title(v_title: &str) -> String {
format!("{PREFIX_V_TITLE}{v_title}")
}
/// Return subfile option
fn subfile(subfile: &str) -> String {
format!("{PREFIX_SUBFILE}{subfile}")
}
/// Return startat option
fn startat(startat: &str) -> String {
format!("{PREFIX_STARTAT}{startat}")
}
/// Return referrer option
fn referrer(referrer: &str) -> String {
format!("{PREFIX_REFERRER}{referrer}")
}
/// Return yt_path option
fn yt_path(yt_path: &str) -> String {
format!("{PREFIX_YT_PATH}{yt_path}")
}
#[test]
fn test_profile_option() {
let p = profile("low-latency");
assert_eq!(p, format!("{PREFIX_PROFILE}low-latency"));
}
#[test]
fn test_formats_option() {
// Only quality
let q = formats(Some("720p"), None);
assert_eq!(q.unwrap(), format!("{PREFIX_FORMATS}res:720"));
// Only v_codec
let v = formats(None, Some("vp9"));
assert_eq!(v.unwrap(), format!("{PREFIX_FORMATS}+vcodec:vp9"));
// Both quality and v_codec
let qv = formats(Some("720p"), Some("vp9"));
assert_eq!(qv.unwrap(), format!("{PREFIX_FORMATS}res:720,+vcodec:vp9"));
}
#[test]
fn test_v_title_option() {
let t = v_title("Hello World!");
assert_eq!(t, format!("{PREFIX_V_TITLE}Hello World!"));
}
#[test]
fn test_subfile_option() {
let s = subfile("http://example.com/en.ass");
assert_eq!(s, format!("{PREFIX_SUBFILE}http://example.com/en.ass"));
}
#[test]
fn test_startat_option() {
let s = startat("233");
assert_eq!(s, format!("{PREFIX_STARTAT}233"));
}
#[test]
fn test_referrer_option() {
let r = referrer("http://example.com/");
assert_eq!(r, format!("{PREFIX_REFERRER}http://example.com/"));
}
#[test]
fn test_yt_path_option() {
let y = yt_path("/usr/bin/yt-dlp");
assert_eq!(y, format!("{PREFIX_YT_PATH}/usr/bin/yt-dlp"));
}
================================================
FILE: src/protocol.rs
================================================
use crate::error::Error;
use crate::plugins::Plugins;
#[derive(Debug, PartialEq)]
pub enum Schemes {
MpvHandler,
MpvHandlerDebug,
}
const SAFE_PROTOS: [&str; 11] = [
"http", "https", "ftp", "ftps", "rtmp", "rtmps", "rtmpe", "rtmpt", "rtmpts", "rtmpte", "data",
];
/// Protocol of mpv-handler
///
/// ```
/// mpv-handler://PLUGINS/ENCODED_URL/?PARAMETERS=VALUES
/// mpv-handler-debug://PLUGINS/ENCODED_URL/?PARAMETERS=VALUES
/// ```
///
/// PLUGINS:
/// - play
///
/// ENCODED_URL:
/// - URL-safe base64 encoded URL
///
/// PARAMETERS:
/// - cookies
/// - profile
/// - quality
/// - v_codec
/// - v_title
/// - subfile
/// - startat
/// - referrer
#[derive(Debug, PartialEq)]
pub struct Protocol<'a> {
pub scheme: Schemes,
pub plugin: Plugins,
pub url: String,
pub cookies: Option<&'a str>,
pub profile: Option<&'a str>,
pub quality: Option<&'a str>,
pub v_codec: Option<&'a str>,
pub v_title: Option<String>,
pub subfile: Option<String>,
pub startat: Option<&'a str>,
pub referrer: Option<String>,
}
impl Protocol<'_> {
/// Parse the given argument and returns `Protocol`
pub fn parse(arg: &str) -> Result<Protocol<'_>, Error> {
let scheme;
let plugin;
let url;
let mut cookies: Option<&str> = None;
let mut profile: Option<&str> = None;
let mut quality: Option<&str> = None;
let mut v_codec: Option<&str> = None;
let mut v_title: Option<String> = None;
let mut subfile: Option<String> = None;
let mut startat: Option<&str> = None;
let mut referrer: Option<String> = None;
let mut i: usize;
// Check scheme `mpv-handler://` and `mpv-handler-debug://`
(i, scheme) = if let Some(s) = arg.find("://") {
match &arg[..s] {
"mpv-handler" => (s + "://".len(), Schemes::MpvHandler),
"mpv-handler-debug" => (s + "://".len(), Schemes::MpvHandlerDebug),
_ => return Err(Error::IncorrectProtocol(arg.to_string())),
}
} else {
return Err(Error::IncorrectProtocol(arg.to_string()));
};
// Get plugin
(i, plugin) = if let Some(s) = arg[i..].find('/') {
match &arg[i..i + s] {
"play" => (i + s + 1, Plugins::Play),
_ => return Err(Error::IncorrectProtocol(arg.to_string())),
}
} else {
return Err(Error::IncorrectProtocol(arg.to_string()));
};
// Get url and decode by base64
(i, url) = if let Some(s) = arg[i..].find('/') {
(i + s + 1, decode_url(&arg[i..i + s])?)
} else {
(arg.len(), decode_url(&arg[i..])?)
};
// Get parameters
if let Some(s) = arg[i..].find('?') {
let params: Vec<&str> = arg[i + s + 1..].split('&').collect();
for param in params {
let data: Vec<&str> = param.split_terminator('=').collect();
if data.len() != 2 {
return Err(Error::IncorrectProtocol(arg.to_string()));
}
let k = data[0];
let v = data[1];
match k {
"cookies" => cookies = Some(v),
"profile" => profile = Some(v),
"quality" => quality = Some(v),
"v_codec" => v_codec = Some(v),
"v_title" => v_title = Some(decode_txt(v)?),
"subfile" => subfile = Some(decode_url(v)?),
"startat" => startat = Some(v),
"referrer" => referrer = Some(decode_txt(v)?),
_ => {}
};
}
}
Ok(Protocol {
scheme,
plugin,
url,
cookies,
profile,
quality,
v_codec,
v_title,
subfile,
startat,
referrer,
})
}
}
/// Decode base64 data (URL-safe) and return `String`
fn decode_txt(data: &str) -> Result<String, Error> {
Ok(String::from_utf8(base64::Engine::decode(
&base64::prelude::BASE64_URL_SAFE_NO_PAD,
data,
)?)?)
}
/// Decode base64 data (URL-safe) and check URL protocol
///
/// Allowed protocols:
///
/// ```
/// "http", "https", "ftp", "ftps", "rtmp", "rtmps",
/// "rtmpe", "rtmpt", "rtmpts", "rtmpte", "data"
/// ```
fn decode_url(data: &str) -> Result<String, Error> {
let url = decode_txt(data)?;
match url.find("://") {
Some(s) => {
if !SAFE_PROTOS.contains(&&url[..s]) {
return Err(Error::DangerousVideoProtocol(url[..s].to_string()));
}
}
None => return Err(Error::IncorrectVideoURL(url)),
};
Ok(url)
}
#[test]
fn test_protocol_parse() {
// All parameters
let proto =
Protocol::parse("mpv-handler://play/aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g_dj1HZ2tuMmY1ZS1JVQ/?cookies=www.youtube.com.txt&profile=low-latency&quality=1080p&v_codec=av01&v_title=VGl0bGU&subfile=aHR0cDovL2V4YW1wbGUuY29tL2VuLmFzcw&startat=233&referrer=aHR0cHM6Ly93d3cueW91dHViZS5jb20v").unwrap();
assert_eq!(proto.scheme, Schemes::MpvHandler);
assert_eq!(proto.plugin, Plugins::Play);
assert_eq!(proto.url, "https://www.youtube.com/watch?v=Ggkn2f5e-IU");
assert_eq!(proto.cookies, Some("www.youtube.com.txt"));
assert_eq!(proto.profile, Some("low-latency"));
assert_eq!(proto.quality, Some("1080p"));
assert_eq!(proto.v_codec, Some("av01"));
assert_eq!(proto.v_title, Some("Title".to_string()));
assert_eq!(proto.subfile, Some("http://example.com/en.ass".to_string()));
assert_eq!(proto.startat, Some("233"));
assert_eq!(proto.referrer, Some("https://www.youtube.com/".to_string()));
// No parameter and last slash
let proto = Protocol::parse(
"mpv-handler://play/aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g_dj1HZ2tuMmY1ZS1JVQ",
)
.unwrap();
assert_eq!(proto.scheme, Schemes::MpvHandler);
assert_eq!(proto.plugin, Plugins::Play);
assert_eq!(proto.url, "https://www.youtube.com/watch?v=Ggkn2f5e-IU");
// No parameter and protocol `mpv`
let proto = Protocol::parse(
"mpv-handler://play/aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g_dj1HZ2tuMmY1ZS1JVQ/",
)
.unwrap();
assert_eq!(proto.scheme, Schemes::MpvHandler);
assert_eq!(proto.plugin, Plugins::Play);
assert_eq!(proto.url, "https://www.youtube.com/watch?v=Ggkn2f5e-IU");
// No parameter and protocol `mpv-handler-debug`
let proto = Protocol::parse(
"mpv-handler-debug://play/aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g_dj1HZ2tuMmY1ZS1JVQ",
)
.unwrap();
assert_eq!(proto.scheme, Schemes::MpvHandlerDebug);
assert_eq!(proto.plugin, Plugins::Play);
assert_eq!(proto.url, "https://www.youtube.com/watch?v=Ggkn2f5e-IU");
}
gitextract_j2hgwr2j/
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ └── build.yml
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
├── README.zh-Hans.md
├── README.zh-Hant.md
├── share/
│ ├── linux/
│ │ ├── config.toml
│ │ ├── mpv-handler-debug.desktop
│ │ └── mpv-handler.desktop
│ ├── proto.html
│ └── windows/
│ ├── config.toml
│ ├── handler-install.bat
│ └── handler-uninstall.bat
└── src/
├── config.rs
├── error.rs
├── main.rs
├── plugins/
│ ├── mod.rs
│ └── play.rs
└── protocol.rs
SYMBOL INDEX (44 symbols across 6 files)
FILE: src/config.rs
type Config (line 11) | pub struct Config {
method load (line 21) | pub fn load() -> Result<Config, Error> {
function get_config_dir (line 45) | pub fn get_config_dir() -> Option<PathBuf> {
function default_mpv (line 69) | pub fn default_mpv() -> Result<String, Error> {
function default_config (line 77) | fn default_config() -> Config {
function realpath (line 85) | fn realpath<T: AsRef<std::ffi::OsStr>>(path: T) -> Result<String, Error> {
function test_config_parse (line 113) | fn test_config_parse() {
FILE: src/error.rs
type Error (line 4) | pub enum Error {
FILE: src/main.rs
function main (line 18) | fn main() -> ExitCode {
function run (line 26) | fn run() -> Result<(), Error> {
function print_usage (line 44) | fn print_usage() {
function print_error (line 52) | fn print_error(e: Error) -> ExitCode {
FILE: src/plugins/mod.rs
type Plugins (line 4) | pub enum Plugins {
FILE: src/plugins/play.rs
constant PREFIX_COOKIES (line 5) | const PREFIX_COOKIES: &str = "--ytdl-raw-options-append=cookies=";
constant PREFIX_PROFILE (line 6) | const PREFIX_PROFILE: &str = "--profile=";
constant PREFIX_FORMATS (line 7) | const PREFIX_FORMATS: &str = "--ytdl-raw-options-append=format-sort=";
constant PREFIX_V_TITLE (line 8) | const PREFIX_V_TITLE: &str = "--title=";
constant PREFIX_SUBFILE (line 9) | const PREFIX_SUBFILE: &str = "--sub-file=";
constant PREFIX_STARTAT (line 10) | const PREFIX_STARTAT: &str = "--start=";
constant PREFIX_REFERRER (line 11) | const PREFIX_REFERRER: &str = "--referrer=";
constant PREFIX_YT_PATH (line 12) | const PREFIX_YT_PATH: &str = "--script-opts=ytdl_hook-ytdl_path=";
function exec (line 15) | pub fn exec(proto: &Protocol, config: &Config) -> Result<(), Error> {
function cookies (line 157) | fn cookies(cookies: &str) -> Option<String> {
function profile (line 176) | fn profile(profile: &str) -> String {
function formats (line 181) | fn formats(quality: Option<&str>, v_codec: Option<&str>) -> Option<Strin...
function v_title (line 200) | fn v_title(v_title: &str) -> String {
function subfile (line 205) | fn subfile(subfile: &str) -> String {
function startat (line 210) | fn startat(startat: &str) -> String {
function referrer (line 215) | fn referrer(referrer: &str) -> String {
function yt_path (line 220) | fn yt_path(yt_path: &str) -> String {
function test_profile_option (line 225) | fn test_profile_option() {
function test_formats_option (line 231) | fn test_formats_option() {
function test_v_title_option (line 245) | fn test_v_title_option() {
function test_subfile_option (line 251) | fn test_subfile_option() {
function test_startat_option (line 257) | fn test_startat_option() {
function test_referrer_option (line 263) | fn test_referrer_option() {
function test_yt_path_option (line 269) | fn test_yt_path_option() {
FILE: src/protocol.rs
type Schemes (line 5) | pub enum Schemes {
constant SAFE_PROTOS (line 10) | const SAFE_PROTOS: [&str; 11] = [
type Protocol (line 37) | pub struct Protocol<'a> {
function parse (line 53) | pub fn parse(arg: &str) -> Result<Protocol<'_>, Error> {
function decode_txt (line 141) | fn decode_txt(data: &str) -> Result<String, Error> {
function decode_url (line 156) | fn decode_url(data: &str) -> Result<String, Error> {
function test_protocol_parse (line 172) | fn test_protocol_parse() {
Condensed preview — 21 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (51K chars).
[
{
"path": ".github/dependabot.yml",
"chars": 208,
"preview": "version: 2\nupdates:\n - package-ecosystem: \"github-actions\"\n directory: \"/\"\n schedule:\n interval: \"weekly\"\n\n "
},
{
"path": ".github/workflows/build.yml",
"chars": 2813,
"preview": "name: Build\n\non:\n workflow_dispatch:\n push:\n tags:\n - \"*\"\n\nenv:\n CARGO_TERM_COLOR: always\n RUSTFLAGS: \"-C st"
},
{
"path": ".gitignore",
"chars": 8,
"preview": "/target\n"
},
{
"path": "Cargo.toml",
"chars": 572,
"preview": "[package]\nname = \"mpv-handler\"\nversion = \"0.4.2\"\nedition = \"2024\"\nauthors = [\"Akatsuki Rui <akiirui@outlook.com>\"]\ndescr"
},
{
"path": "LICENSE",
"chars": 1069,
"preview": "MIT License\n\nCopyright (c) 2020 Akatsuki Rui\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
},
{
"path": "README.md",
"chars": 5027,
"preview": "[English][readme-en] | [简体中文][readme-zh-hans] | [繁体中文][readme-zh-hant]\n\n[readme-en]: https://github.com/akiirui/mpv-hand"
},
{
"path": "README.zh-Hans.md",
"chars": 4257,
"preview": "[English][readme-en] | [简体中文][readme-zh-hans] | [繁体中文][readme-zh-hant]\n\n[readme-en]: https://github.com/akiirui/mpv-hand"
},
{
"path": "README.zh-Hant.md",
"chars": 4257,
"preview": "[English][readme-en] | [簡體中文][readme-zh-hans] | [繁體中文][readme-zh-hant]\n\n[readme-en]: https://github.com/akiirui/mpv-hand"
},
{
"path": "share/linux/config.toml",
"chars": 317,
"preview": "#mpv = \"/path/of/mpv\"\n# Optional, Type: String\n# The path of mpv executable binary\n# Default value:\n# - Linux: mpv\n# - W"
},
{
"path": "share/linux/mpv-handler-debug.desktop",
"chars": 332,
"preview": "[Desktop Entry]\nType=Application\nName=mpv-handler-debug\nGenericName=Multimedia player\nComment=Play website videos and so"
},
{
"path": "share/linux/mpv-handler.desktop",
"chars": 307,
"preview": "[Desktop Entry]\nType=Application\nName=mpv-handler\nGenericName=Multimedia player\nComment=Play website videos and songs wi"
},
{
"path": "share/proto.html",
"chars": 1755,
"preview": "<html>\n <head>\n <style>\n body {\n height: fit-content;\n width: max-content;\n padding: 5px 5"
},
{
"path": "share/windows/config.toml",
"chars": 489,
"preview": "#mpv = \"C:\\\\path\\\\of\\\\mpv.com\"\n# Optional, Type: String\n# The path of mpv executable binary\n# Default value:\n# - Linux: "
},
{
"path": "share/windows/handler-install.bat",
"chars": 2845,
"preview": "@echo OFF\n\n:: Unattended install flag. When set, the script will not require user input.\nset unattended=no\nif \"%1\"==\"/u\""
},
{
"path": "share/windows/handler-uninstall.bat",
"chars": 1708,
"preview": "@echo OFF\n\n:: Unattended install flag. When set, the script will not require user input.\nset unattended=no\nif \"%1\"==\"/u\""
},
{
"path": "src/config.rs",
"chars": 3597,
"preview": "use crate::error::Error;\nuse serde::Deserialize;\nuse std::path::PathBuf;\n\n/// Config of mpv-handler\n///\n/// - `mpv`: mpv"
},
{
"path": "src/error.rs",
"chars": 841,
"preview": "use thiserror::Error;\n\n#[derive(Debug, Error)]\npub enum Error {\n #[error(\"Too many arguments\")]\n TooManyArgs,\n "
},
{
"path": "src/main.rs",
"chars": 1399,
"preview": "#![cfg_attr(\n all(target_os = \"windows\", not(feature = \"console\"), not(debug_assertions)),\n windows_subsystem = \"w"
},
{
"path": "src/plugins/mod.rs",
"chars": 74,
"preview": "pub mod play;\n\n#[derive(Debug, PartialEq)]\npub enum Plugins {\n Play,\n}\n"
},
{
"path": "src/plugins/play.rs",
"chars": 7483,
"preview": "use crate::config::Config;\nuse crate::error::Error;\nuse crate::protocol::Protocol;\n\nconst PREFIX_COOKIES: &str = \"--ytdl"
},
{
"path": "src/protocol.rs",
"chars": 6929,
"preview": "use crate::error::Error;\nuse crate::plugins::Plugins;\n\n#[derive(Debug, PartialEq)]\npub enum Schemes {\n MpvHandler,\n "
}
]
About this extraction
This page contains the full source code of the akiirui/mpv-handler GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 21 files (45.2 KB), approximately 14.0k tokens, and a symbol index with 44 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.