update installation instructions and add mpv handler setup script

This commit is contained in:
2026-04-18 18:12:17 -07:00
parent 0bd3bff92d
commit 3bd335cfb5
9 changed files with 982 additions and 413 deletions
+462 -145
View File
@@ -1,155 +1,472 @@
[English][readme-en] | [简体中文][readme-zh-hans] | [繁体中文][readme-zh-hant]
# mpv-handler setup
[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
This folder contains the desktop playback helper assets used by API Media Player.
# mpv handler
## What is in this folder
A protocol handler for **mpv**, written by Rust.
- `setup-mpv-handler.mjs`: cross-platform helper used by `npm run setup:mpv-handler`
- `install-mpv-handler.ps1`: Windows protocol registration script
- `uninstall-mpv-handler.ps1`: Windows protocol cleanup script
- `handler-install.bat` and `handler-uninstall.bat`: thin Windows wrappers for the PowerShell scripts
- `config.toml`: template used when the helper needs to create or refresh an `mpv-handler` config
- `mpv-handler.exe` and `mpv-handler-debug.exe`: bundled Windows handler binaries
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_
## Recommended commands
### Windows
Windows users need to install manually.
Run from an elevated PowerShell or Windows Terminal:
#### 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
```bash
npm run setup:mpv-handler
```
[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
Remove the registration later with:
```bash
npm run uninstall:mpv-handler
```
### Linux
1. Download and extract the upstream Linux release:
```text
https://github.com/akiirui/mpv-handler/releases/latest/download/mpv-handler-linux-amd64.zip
```
2. Point the helper at that extracted folder:
```bash
npm run setup:mpv-handler -- --root /path/to/extracted/mpv-handler-linux-amd64
```
The helper copies files into `~/.local/bin` and `~/.local/share/applications`, updates `config.toml`, and runs `xdg-mime` for both protocol handlers.
## Useful flags
```bash
npm run setup:mpv-handler -- --help
npm run setup:mpv-handler -- --root /path/to/mpv-handler
npm run setup:mpv-handler -- --mpv /path/to/mpv
npm run setup:mpv-handler -- --ytdl /path/to/yt-dlp
npm run setup:mpv-handler -- --skip-config
npm run setup:mpv-handler -- --dry-run
```
`--root` points at the extracted upstream `mpv-handler` folder. On Windows it is optional because this repo already ships the handler files under `scripts/`. On Linux it is normally required.
## What the helper does
### Windows
- finds the bundled handler files in this folder unless you override `--root`
- creates or updates `config.toml`
- tries to detect `mpv` and `yt-dlp` from `PATH`
- registers `mpv-handler://` and `mpv-handler-debug://` through the PowerShell installer
### Linux
- validates the extracted upstream release layout
- creates or updates `config.toml`
- copies the handler binary to `~/.local/bin/mpv-handler`
- copies the desktop entries to `~/.local/share/applications`
- rewrites `Exec=` entries to the installed absolute binary path
- runs `xdg-mime default ...` for both schemes
## Manual fallback
If you would rather install without the helper:
### Windows
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\install-mpv-handler.ps1 -InstallRoot .\scripts
```
### Linux
```bash
cp /path/to/mpv-handler/mpv-handler ~/.local/bin/mpv-handler
cp /path/to/mpv-handler/mpv-handler.desktop ~/.local/share/applications/
cp /path/to/mpv-handler/mpv-handler-debug.desktop ~/.local/share/applications/
chmod +x ~/.local/bin/mpv-handler
xdg-mime default mpv-handler.desktop x-scheme-handler/mpv-handler
xdg-mime default mpv-handler-debug.desktop x-scheme-handler/mpv-handler-debug
```
## Upstream project
Upstream `mpv-handler` releases and source:
```text
https://github.com/akiirui/mpv-handler
https://github.com/akiirui/mpv-handler/releases
```
This repo uses the upstream protocol scheme and binaries; the docs here only describe the setup flow for API Media Player.
*** Add File: c:\Forgejo\API-MediaPlayer\scripts\uninstall-mpv-handler.ps1
#Requires -Version 5.1
#Requires -RunAsAdministrator
[CmdletBinding()]
param()
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Remove-ProtocolKey {
param(
[Parameter(Mandatory = $true)]
[string]$SchemeName
)
$classesRoot = [Microsoft.Win32.Registry]::ClassesRoot
try {
$classesRoot.DeleteSubKeyTree($SchemeName, $false)
} catch {
}
}
if ([System.Environment]::OSVersion.Platform -ne [System.PlatformID]::Win32NT) {
throw 'This uninstaller is only for Windows.'
}
Remove-ProtocolKey -SchemeName 'mpv'
Remove-ProtocolKey -SchemeName 'mpv-debug'
Remove-ProtocolKey -SchemeName 'mpv-handler'
Remove-ProtocolKey -SchemeName 'mpv-handler-debug'
Write-Host 'Successfully removed mpv-handler protocol registration.' -ForegroundColor Green
*** Add File: c:\Forgejo\API-MediaPlayer\scripts\setup-mpv-handler.mjs
#!/usr/bin/env node
import { spawnSync } from 'node:child_process'
import { copyFileSync, existsSync, mkdirSync, readFileSync, statSync, chmodSync, writeFileSync } from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const scriptPath = fileURLToPath(import.meta.url)
const scriptDir = path.dirname(scriptPath)
const templateConfigPath = path.join(scriptDir, 'config.toml')
const usage = `Usage:
npm run setup:mpv-handler
npm run setup:mpv-handler -- --root /path/to/mpv-handler
npm run setup:mpv-handler -- --mpv /path/to/mpv --ytdl /path/to/yt-dlp
Options:
--root <path> Path to the extracted mpv-handler folder.
--mpv <path> Override the mpv executable path written to config.toml.
--ytdl <path> Override the yt-dlp executable path written to config.toml.
--skip-config Do not create or update config.toml.
--keep-existing Windows only. Keep existing protocol keys instead of replacing them.
--dry-run Print the actions without changing files or running installers.
--help Show this help text.
`
function fail(message) {
console.error(`Error: ${message}`)
process.exit(1)
}
function parseArgs(argv) {
const options = {
root: '',
mpv: '',
ytdl: '',
skipConfig: false,
keepExisting: false,
dryRun: false,
help: false,
}
for (let index = 0; index < argv.length; index += 1) {
const value = argv[index]
if (value === '--help' || value === '-h') {
options.help = true
continue
}
if (value === '--skip-config') {
options.skipConfig = true
continue
}
if (value === '--keep-existing') {
options.keepExisting = true
continue
}
if (value === '--dry-run') {
options.dryRun = true
continue
}
if (value === '--root' || value === '--mpv' || value === '--ytdl') {
const nextValue = argv[index + 1]
if (!nextValue || nextValue.startsWith('--')) {
fail(`Missing value for ${value}`)
}
if (value === '--root') options.root = nextValue
if (value === '--mpv') options.mpv = nextValue
if (value === '--ytdl') options.ytdl = nextValue
index += 1
continue
}
fail(`Unknown argument: ${value}`)
}
return options
}
function escapeTomlString(value) {
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
}
function upsertTomlValue(content, key, value) {
const line = `${key} = "${escapeTomlString(value)}"`
const pattern = new RegExp(`^\\s*#?\\s*${key}\\s*=.*$`, 'm')
if (pattern.test(content)) {
return content.replace(pattern, line)
}
const trimmed = content.trimEnd()
return trimmed ? `${trimmed}\n${line}\n` : `${line}\n`
}
function resolveOnPath(commandNames) {
const locator = process.platform === 'win32' ? 'where.exe' : 'which'
for (const commandName of commandNames) {
const result = spawnSync(locator, [commandName], { encoding: 'utf8' })
if (result.status !== 0) continue
const match = result.stdout
.split(/\r?\n/)
.map((entry) => entry.trim())
.find((entry) => entry)
if (match && existsSync(match)) {
return match
}
}
return ''
}
function isFile(candidatePath) {
try {
return statSync(candidatePath).isFile()
} catch {
return false
}
}
function looksLikeRoot(rootPath) {
const requiredFiles = process.platform === 'win32'
? ['mpv-handler.exe', 'mpv-handler-debug.exe']
: process.platform === 'linux'
? ['mpv-handler', 'mpv-handler.desktop', 'mpv-handler-debug.desktop']
: ['mpv-handler']
return requiredFiles.every((fileName) => isFile(path.join(rootPath, fileName)))
}
function resolveRoot(options) {
const candidates = []
if (options.root) candidates.push(path.resolve(options.root))
candidates.push(scriptDir)
candidates.push(process.cwd())
const seen = new Set()
for (const candidate of candidates) {
if (!candidate || seen.has(candidate)) continue
seen.add(candidate)
if (looksLikeRoot(candidate)) {
return candidate
}
}
if (process.platform === 'win32') {
fail('Could not find mpv-handler files. Re-run with --root pointing at the extracted mpv-handler folder.')
}
if (process.platform === 'linux') {
fail('Could not find a Linux mpv-handler release. Download and extract the upstream archive, then pass --root /path/to/extracted/mpv-handler-linux-amd64.')
}
fail('Automatic setup is not available for this platform yet.')
}
function ensureConfig(rootPath, options) {
const configPath = path.join(rootPath, 'config.toml')
if (options.skipConfig) {
return { configPath, changed: false, mpvPath: '', ytdlPath: '' }
}
let content = existsSync(configPath)
? readFileSync(configPath, 'utf8')
: readFileSync(templateConfigPath, 'utf8')
const detectedMpv = options.mpv || resolveOnPath(process.platform === 'win32' ? ['mpv.com', 'mpv.exe', 'mpv'] : ['mpv'])
const detectedYtdl = options.ytdl || resolveOnPath(process.platform === 'win32' ? ['yt-dlp.exe', 'yt-dlp'] : ['yt-dlp'])
let changed = !existsSync(configPath)
let nextContent = content
if (detectedMpv) {
const updated = upsertTomlValue(nextContent, 'mpv', detectedMpv)
changed ||= updated !== nextContent
nextContent = updated
}
if (detectedYtdl) {
const updated = upsertTomlValue(nextContent, 'ytdl', detectedYtdl)
changed ||= updated !== nextContent
nextContent = updated
}
if (changed && !options.dryRun) {
writeFileSync(configPath, nextContent, 'utf8')
}
return {
configPath,
changed,
mpvPath: detectedMpv,
ytdlPath: detectedYtdl,
}
}
function runWindowsSetup(rootPath, options) {
const powershell = resolveOnPath(['powershell.exe', 'pwsh.exe']) || 'powershell.exe'
const installScript = path.join(scriptDir, 'install-mpv-handler.ps1')
const args = ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', installScript, '-InstallRoot', rootPath]
if (options.keepExisting) {
args.push('-KeepExistingProtocolKeys')
}
if (options.dryRun) {
console.log('Dry run: would execute Windows installer')
console.log(`${powershell} ${args.map((value) => JSON.stringify(value)).join(' ')}`)
return
}
const result = spawnSync(powershell, args, { stdio: 'inherit' })
if (result.status !== 0) {
fail('Windows protocol registration failed. Re-run the command from an elevated PowerShell or Windows Terminal.')
}
}
function rewriteDesktopExec(content, targetBinary) {
return content.replace(/^Exec=.*$/m, (line) => {
const prefix = 'Exec='
const rest = line.slice(prefix.length).trim()
const firstSpaceIndex = rest.indexOf(' ')
const suffix = firstSpaceIndex === -1 ? '' : rest.slice(firstSpaceIndex)
return `${prefix}${targetBinary}${suffix}`
})
}
function runLinuxSetup(rootPath, options) {
const localBin = path.join(os.homedir(), '.local', 'bin')
const applicationsDir = path.join(os.homedir(), '.local', 'share', 'applications')
const targetBinary = path.join(localBin, 'mpv-handler')
const copies = [
{
source: path.join(rootPath, 'mpv-handler'),
target: targetBinary,
executable: true,
},
{
source: path.join(rootPath, 'mpv-handler.desktop'),
target: path.join(applicationsDir, 'mpv-handler.desktop'),
patchExec: true,
},
{
source: path.join(rootPath, 'mpv-handler-debug.desktop'),
target: path.join(applicationsDir, 'mpv-handler-debug.desktop'),
patchExec: true,
},
]
if (options.dryRun) {
console.log('Dry run: would install Linux desktop files to ~/.local')
for (const item of copies) {
console.log(`copy ${item.source} -> ${item.target}`)
}
console.log('xdg-mime default mpv-handler.desktop x-scheme-handler/mpv-handler')
console.log('xdg-mime default mpv-handler-debug.desktop x-scheme-handler/mpv-handler-debug')
return
}
mkdirSync(localBin, { recursive: true })
mkdirSync(applicationsDir, { recursive: true })
for (const item of copies) {
if (item.patchExec) {
const content = readFileSync(item.source, 'utf8')
writeFileSync(item.target, rewriteDesktopExec(content, targetBinary), 'utf8')
continue
}
copyFileSync(item.source, item.target)
if (item.executable) {
chmodSync(item.target, 0o755)
}
}
for (const args of [
['default', 'mpv-handler.desktop', 'x-scheme-handler/mpv-handler'],
['default', 'mpv-handler-debug.desktop', 'x-scheme-handler/mpv-handler-debug'],
]) {
const result = spawnSync('xdg-mime', args, { stdio: 'inherit' })
if (result.status !== 0) {
fail(`xdg-mime failed for ${args[2]}. Run the command manually after fixing your desktop environment registration.`)
}
}
}
function main() {
const options = parseArgs(process.argv.slice(2))
if (options.help) {
console.log(usage)
return
}
if (!['win32', 'linux', 'darwin'].includes(process.platform)) {
fail(`Unsupported platform: ${process.platform}`)
}
if (process.platform === 'darwin') {
console.log('Automatic macOS protocol registration is not available in this repo yet.')
console.log('The app can still browse media on macOS, but desktop playback setup must be handled manually.')
return
}
const rootPath = resolveRoot(options)
const configResult = ensureConfig(rootPath, options)
console.log(`Platform: ${process.platform}`)
console.log(`mpv-handler root: ${rootPath}`)
console.log(`config.toml: ${configResult.configPath}${configResult.changed ? ' (updated)' : ' (unchanged)'}`)
console.log(`mpv: ${configResult.mpvPath || 'not detected'}`)
console.log(`yt-dlp: ${configResult.ytdlPath || 'not detected'}`)
if (process.platform === 'win32') {
runWindowsSetup(rootPath, options)
} else if (process.platform === 'linux') {
runLinuxSetup(rootPath, options)
}
console.log('mpv-handler setup complete.')
}
main()
+15 -78
View File
@@ -1,82 +1,19 @@
@echo OFF
setlocal
:: Unattended install flag. When set, the script will not require user input.
set unattended=no
if "%1"=="/u" set unattended=yes
set interactive=yes
if /I "%1"=="/u" (
set interactive=no
shift
)
:: Make sure this is Windows Vista or later
call :ensure_vista
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0install-mpv-handler.ps1" -InstallRoot "%~dp0" %*
if errorlevel 1 (
echo.
echo Install failed. Re-run from an elevated PowerShell or Command Prompt.
if /I "%interactive%"=="yes" pause
exit /b 1
)
:: 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
if /I "%interactive%"=="yes" pause
exit /b 0
+15 -60
View File
@@ -1,64 +1,19 @@
@echo OFF
setlocal
:: Unattended install flag. When set, the script will not require user input.
set unattended=no
if "%1"=="/u" set unattended=yes
set interactive=yes
if /I "%1"=="/u" (
set interactive=no
shift
)
:: Make sure this is Windows Vista or later
call :ensure_vista
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0uninstall-mpv-handler.ps1" %*
if errorlevel 1 (
echo.
echo Uninstall failed. Re-run from an elevated PowerShell or Command Prompt.
if /I "%interactive%"=="yes" pause
exit /b 1
)
:: 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
if /I "%interactive%"=="yes" pause
exit /b 0
+29 -1
View File
@@ -129,6 +129,29 @@ function Get-MpvPathFromConfig {
}
}
function Resolve-CommandPath {
param(
[Parameter(Mandatory = $true)]
[string[]]$Names
)
foreach ($name in $Names) {
try {
$command = Get-Command -Name $name -ErrorAction Stop
if ($command.Path) {
return $command.Path
}
if ($command.Source) {
return $command.Source
}
} catch {
}
}
return $null
}
function Remove-ProtocolKey {
param(
[Parameter(Mandatory = $true)]
@@ -204,7 +227,12 @@ $effectiveIconPath = if ($IconPath) {
}
(Resolve-Path -LiteralPath $IconPath).Path
} else {
Get-MpvPathFromConfig -ConfigPath $configPath
$configuredPath = Get-MpvPathFromConfig -ConfigPath $configPath
if ($configuredPath) {
$configuredPath
} else {
Resolve-CommandPath -Names @('mpv.com', 'mpv.exe', 'mpv')
}
}
if (-not $effectiveIconPath) {
+332
View File
@@ -0,0 +1,332 @@
#!/usr/bin/env node
import { spawnSync } from 'node:child_process'
import { copyFileSync, existsSync, mkdirSync, readFileSync, statSync, chmodSync, writeFileSync } from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const scriptPath = fileURLToPath(import.meta.url)
const scriptDir = path.dirname(scriptPath)
const templateConfigPath = path.join(scriptDir, 'config.toml')
const usage = `Usage:
npm run setup:mpv-handler
npm run setup:mpv-handler -- --root /path/to/mpv-handler
npm run setup:mpv-handler -- --mpv /path/to/mpv --ytdl /path/to/yt-dlp
Options:
--root <path> Path to the extracted mpv-handler folder.
--mpv <path> Override the mpv executable path written to config.toml.
--ytdl <path> Override the yt-dlp executable path written to config.toml.
--skip-config Do not create or update config.toml.
--keep-existing Windows only. Keep existing protocol keys instead of replacing them.
--dry-run Print the actions without changing files or running installers.
--help Show this help text.
`
function fail(message) {
console.error(`Error: ${message}`)
process.exit(1)
}
function parseArgs(argv) {
const options = {
root: '',
mpv: '',
ytdl: '',
skipConfig: false,
keepExisting: false,
dryRun: false,
help: false,
}
for (let index = 0; index < argv.length; index += 1) {
const value = argv[index]
if (value === '--help' || value === '-h') {
options.help = true
continue
}
if (value === '--skip-config') {
options.skipConfig = true
continue
}
if (value === '--keep-existing') {
options.keepExisting = true
continue
}
if (value === '--dry-run') {
options.dryRun = true
continue
}
if (value === '--root' || value === '--mpv' || value === '--ytdl') {
const nextValue = argv[index + 1]
if (!nextValue || nextValue.startsWith('--')) {
fail(`Missing value for ${value}`)
}
if (value === '--root') options.root = nextValue
if (value === '--mpv') options.mpv = nextValue
if (value === '--ytdl') options.ytdl = nextValue
index += 1
continue
}
fail(`Unknown argument: ${value}`)
}
return options
}
function escapeTomlString(value) {
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
}
function upsertTomlValue(content, key, value) {
const line = `${key} = "${escapeTomlString(value)}"`
const pattern = new RegExp(`^\\s*#?\\s*${key}\\s*=.*$`, 'm')
if (pattern.test(content)) {
return content.replace(pattern, line)
}
const trimmed = content.trimEnd()
return trimmed ? `${trimmed}\n${line}\n` : `${line}\n`
}
function resolveOnPath(commandNames) {
const locator = process.platform === 'win32' ? 'where.exe' : 'which'
for (const commandName of commandNames) {
const result = spawnSync(locator, [commandName], { encoding: 'utf8' })
if (result.status !== 0) continue
const match = result.stdout
.split(/\r?\n/)
.map((entry) => entry.trim())
.find((entry) => entry)
if (match && existsSync(match)) {
return match
}
}
return ''
}
function isFile(candidatePath) {
try {
return statSync(candidatePath).isFile()
} catch {
return false
}
}
function looksLikeRoot(rootPath) {
const requiredFiles = process.platform === 'win32'
? ['mpv-handler.exe', 'mpv-handler-debug.exe']
: process.platform === 'linux'
? ['mpv-handler', 'mpv-handler.desktop', 'mpv-handler-debug.desktop']
: ['mpv-handler']
return requiredFiles.every((fileName) => isFile(path.join(rootPath, fileName)))
}
function resolveRoot(options) {
const candidates = []
if (options.root) candidates.push(path.resolve(options.root))
candidates.push(scriptDir)
candidates.push(process.cwd())
const seen = new Set()
for (const candidate of candidates) {
if (!candidate || seen.has(candidate)) continue
seen.add(candidate)
if (looksLikeRoot(candidate)) {
return candidate
}
}
if (process.platform === 'win32') {
fail('Could not find mpv-handler files. Re-run with --root pointing at the extracted mpv-handler folder.')
}
if (process.platform === 'linux') {
fail('Could not find a Linux mpv-handler release. Download and extract the upstream archive, then pass --root /path/to/extracted/mpv-handler-linux-amd64.')
}
fail('Automatic setup is not available for this platform yet.')
}
function ensureConfig(rootPath, options) {
const configPath = path.join(rootPath, 'config.toml')
if (options.skipConfig) {
return { configPath, changed: false, mpvPath: '', ytdlPath: '' }
}
const configExists = existsSync(configPath)
const content = configExists
? readFileSync(configPath, 'utf8')
: readFileSync(templateConfigPath, 'utf8')
const detectedMpv = options.mpv || resolveOnPath(process.platform === 'win32' ? ['mpv.com', 'mpv.exe', 'mpv'] : ['mpv'])
const detectedYtdl = options.ytdl || resolveOnPath(process.platform === 'win32' ? ['yt-dlp.exe', 'yt-dlp'] : ['yt-dlp'])
let changed = !configExists
let nextContent = content
if (detectedMpv) {
const updated = upsertTomlValue(nextContent, 'mpv', detectedMpv)
changed ||= updated !== nextContent
nextContent = updated
}
if (detectedYtdl) {
const updated = upsertTomlValue(nextContent, 'ytdl', detectedYtdl)
changed ||= updated !== nextContent
nextContent = updated
}
if (changed && !options.dryRun) {
writeFileSync(configPath, nextContent, 'utf8')
}
return {
configPath,
changed,
mpvPath: detectedMpv,
ytdlPath: detectedYtdl,
}
}
function runWindowsSetup(rootPath, options) {
const powershell = resolveOnPath(['powershell.exe', 'pwsh.exe']) || 'powershell.exe'
const installScript = path.join(scriptDir, 'install-mpv-handler.ps1')
const args = ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', installScript, '-InstallRoot', rootPath]
if (options.keepExisting) {
args.push('-KeepExistingProtocolKeys')
}
if (options.dryRun) {
console.log('Dry run: would execute Windows installer')
console.log(`${powershell} ${args.map((value) => JSON.stringify(value)).join(' ')}`)
return
}
const result = spawnSync(powershell, args, { stdio: 'inherit' })
if (result.status !== 0) {
fail('Windows protocol registration failed. Re-run the command from an elevated PowerShell or Windows Terminal.')
}
}
function rewriteDesktopExec(content, targetBinary) {
return content.replace(/^Exec=.*$/m, (line) => {
const prefix = 'Exec='
const rest = line.slice(prefix.length).trim()
const firstSpaceIndex = rest.indexOf(' ')
const suffix = firstSpaceIndex === -1 ? '' : rest.slice(firstSpaceIndex)
return `${prefix}${targetBinary}${suffix}`
})
}
function runLinuxSetup(rootPath, options) {
const localBin = path.join(os.homedir(), '.local', 'bin')
const applicationsDir = path.join(os.homedir(), '.local', 'share', 'applications')
const targetBinary = path.join(localBin, 'mpv-handler')
const copies = [
{
source: path.join(rootPath, 'mpv-handler'),
target: targetBinary,
executable: true,
},
{
source: path.join(rootPath, 'mpv-handler.desktop'),
target: path.join(applicationsDir, 'mpv-handler.desktop'),
patchExec: true,
},
{
source: path.join(rootPath, 'mpv-handler-debug.desktop'),
target: path.join(applicationsDir, 'mpv-handler-debug.desktop'),
patchExec: true,
},
]
if (options.dryRun) {
console.log('Dry run: would install Linux desktop files to ~/.local')
for (const item of copies) {
console.log(`copy ${item.source} -> ${item.target}`)
}
console.log('xdg-mime default mpv-handler.desktop x-scheme-handler/mpv-handler')
console.log('xdg-mime default mpv-handler-debug.desktop x-scheme-handler/mpv-handler-debug')
return
}
mkdirSync(localBin, { recursive: true })
mkdirSync(applicationsDir, { recursive: true })
for (const item of copies) {
if (item.patchExec) {
const content = readFileSync(item.source, 'utf8')
writeFileSync(item.target, rewriteDesktopExec(content, targetBinary), 'utf8')
continue
}
copyFileSync(item.source, item.target)
if (item.executable) {
chmodSync(item.target, 0o755)
}
}
for (const args of [
['default', 'mpv-handler.desktop', 'x-scheme-handler/mpv-handler'],
['default', 'mpv-handler-debug.desktop', 'x-scheme-handler/mpv-handler-debug'],
]) {
const result = spawnSync('xdg-mime', args, { stdio: 'inherit' })
if (result.status !== 0) {
fail(`xdg-mime failed for ${args[2]}. Run the command manually after fixing your desktop environment registration.`)
}
}
}
function main() {
const options = parseArgs(process.argv.slice(2))
if (options.help) {
console.log(usage)
return
}
if (!['win32', 'linux', 'darwin'].includes(process.platform)) {
fail(`Unsupported platform: ${process.platform}`)
}
if (process.platform === 'darwin') {
console.log('Automatic macOS protocol registration is not available in this repo yet.')
console.log('The app can still browse media on macOS, but desktop playback setup must be handled manually.')
return
}
const rootPath = resolveRoot(options)
const configResult = ensureConfig(rootPath, options)
console.log(`Platform: ${process.platform}`)
console.log(`mpv-handler root: ${rootPath}`)
console.log(`config.toml: ${configResult.configPath}${configResult.changed ? ' (updated)' : ' (unchanged)'}`)
console.log(`mpv: ${configResult.mpvPath || 'not detected'}`)
console.log(`yt-dlp: ${configResult.ytdlPath || 'not detected'}`)
if (process.platform === 'win32') {
runWindowsSetup(rootPath, options)
} else if (process.platform === 'linux') {
runLinuxSetup(rootPath, options)
}
console.log('mpv-handler setup complete.')
}
main()
+32
View File
@@ -0,0 +1,32 @@
#Requires -Version 5.1
#Requires -RunAsAdministrator
[CmdletBinding()]
param()
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Remove-ProtocolKey {
param(
[Parameter(Mandatory = $true)]
[string]$SchemeName
)
$classesRoot = [Microsoft.Win32.Registry]::ClassesRoot
try {
$classesRoot.DeleteSubKeyTree($SchemeName, $false)
} catch {
}
}
if ([System.Environment]::OSVersion.Platform -ne [System.PlatformID]::Win32NT) {
throw 'This uninstaller is only for Windows.'
}
Remove-ProtocolKey -SchemeName 'mpv'
Remove-ProtocolKey -SchemeName 'mpv-debug'
Remove-ProtocolKey -SchemeName 'mpv-handler'
Remove-ProtocolKey -SchemeName 'mpv-handler-debug'
Write-Host 'Successfully removed mpv-handler protocol registration.' -ForegroundColor Green