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 "] 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 ![](share/proto.png) ### 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 如果你是手动安装的,请重新执行一遍手动安装流程。 ## 协议 ![](share/proto.png) ### 协议名 - `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 如果你是手動安裝的,請重新執行一遍手動安裝流程。 ## 協議 ![](share/proto.png) ### 協議名 - `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 ================================================

mpv-handler://play/aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g_dj1HZ2tuMmY1ZS1JVQ/?param1=value1&param2=value2

================================================ 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, pub ytdl: Option, pub proxy: Option, } impl Config { /// Load config file and retruns `Config` /// /// If config file doesn't exists, returns default value pub fn load() -> Result { 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 { // 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 { #[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>(path: T) -> Result { 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 = 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 ",); } /// 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 { 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 { let mut f: Vec = 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, pub subfile: Option, pub startat: Option<&'a str>, pub referrer: Option, } impl Protocol<'_> { /// Parse the given argument and returns `Protocol` pub fn parse(arg: &str) -> Result, 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 = None; let mut subfile: Option = None; let mut startat: Option<&str> = None; let mut referrer: Option = 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 { 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 { 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"); }