Skip to content

Instantly share code, notes, and snippets.

@mikeyaworski
Last active April 30, 2025 09:24
Show Gist options
  • Save mikeyaworski/97ded060e53b1a9a08fe21ccc9dd788f to your computer and use it in GitHub Desktop.
Save mikeyaworski/97ded060e53b1a9a08fe21ccc9dd788f to your computer and use it in GitHub Desktop.
MPV Launcher

Install dependencies:

pip install inquirer==3.4.0

Or, in general, save the requirements.txt file and run:

pip install -r requirements.txt

TLDR

Install

git clone [email protected]:97ded060e53b1a9a08fe21ccc9dd788f.git mpv-launcher
cd mpv-launcher
pip install -r requirements.txt

Update

cd mpv-launcher
git pull
import subprocess
import re
import os
import shlex
import tkinter
from tkinter import filedialog
import inquirer
from typing import List
def get_multi_selections(message, choices):
questions = [
inquirer.Checkbox(
'choices',
message=message,
choices=choices,
),
]
answers = inquirer.prompt(questions)
return answers["choices"]
def get_single_selection(message, choices):
questions = [
inquirer.List(
'choice',
message=message,
choices=choices,
),
]
answers = inquirer.prompt(questions)
return answers['choice']
def get_format_selections(url):
try:
return_code = subprocess.call(f'yt-dlp -F "{url}"')
if return_code != 0: raise Exception('Proceeding without format selection')
video_format = input('\nVideo format (optional): ')
audio_format = input('Audio format (optional): ')
return (video_format, audio_format)
except Exception as e:
print(e)
return (None, None)
'''
AI-generated
'''
def get_audio_devices() -> List[tuple[str, str]]:
print('Fetching audio devices...')
result = subprocess.run(['mpv', '--audio-device=help'], capture_output=True, text=True)
output = result.stdout.splitlines()
devices = []
for line in output:
match = re.match(r"^\s+'([^']+)'\s+\((.+)\)$", line)
if match:
device_id, label = match.groups()
devices.append((label, device_id))
return devices
def prompt_local_files() -> List[str]:
root = tkinter.Tk()
root.withdraw()
file_names = filedialog.askopenfilenames(
title='Select Media Files',
filetypes=[
('Media Files', '*.m3u8 *.mp4 *.m4v *.mkv *.avi *.mov *.mp3 *.wav'),
('All Files', '*.*')
]
)
if not file_names:
print('No files selected')
exit()
return list(file_names)
try:
urls = input('URLs: ').split()
file_names = []
if not urls:
choice = get_single_selection('What do you want to do?', [
('Open MPV window (idle)', 'IDLE'),
('Play local files', 'LOCAL_FILE'),
('Exit', 'EXIT'),
])
if choice == 'EXIT':
exit()
elif choice == 'LOCAL_FILE':
file_names = prompt_local_files()
choices = [
('Select custom video format?', 'CUSTOM_YT_DLP_VIDEO_FORMAT'),
('Choose preferred video resolution?', 'YT_DLP_RESOLUTION_SELECTION'),
('Audio only?', 'AUDIO_ONLY'),
('Prevent position saving?', 'NO_POSITION_SAVING'),
('Picture-in-picture?', 'PIP'),
('Referer header?', 'REFERER_HEADER'),
('M3U8 playlist?', 'M3U8_PLAYLIST'),
('Choose audio device?', 'CHOOSE_AUDIO_DEVICE'),
('Custom options?', 'CUSTOM_OPTIONS'),
# This option is easy to become a problem because depending on
# how the script is run, the only way to exit the mpv process is
# via Task Mangager.
# ('Hide window?', 'HIDE_WINDOW'),
] if urls else [
('Select custom video format?', 'CUSTOM_YT_DLP_VIDEO_FORMAT'),
('Choose preferred video resolution?', 'YT_DLP_RESOLUTION_SELECTION'),
('Audio only?', 'AUDIO_ONLY'),
('Prevent position saving?', 'NO_POSITION_SAVING'),
('Picture-in-picture?', 'PIP'),
('M3U8 playlist?', 'M3U8_PLAYLIST'),
('Choose audio device?', 'CHOOSE_AUDIO_DEVICE'),
('Custom options?', 'CUSTOM_OPTIONS'),
]
option_choices = get_multi_selections(
message='Options (Space to select option, Enter to continue)',
choices=choices,
)
headers = []
if 'REFERER_HEADER' in option_choices:
referer = input('Referer: ')
headers.append(('Referer', referer))
format_selections = []
if 'CUSTOM_YT_DLP_VIDEO_FORMAT' in option_choices:
print('Fetching available formats...')
(video_format, audio_format) = get_format_selections(urls[0])
if video_format: format_selections.append(video_format)
if audio_format: format_selections.append(audio_format)
args = [
f'--force-window={"no" if "HIDE_WINDOW" in option_choices else "yes"}',
f'--save-position-on-quit={"no" if "NO_POSITION_SAVING" in option_choices else "yes"}',
]
if 'M3U8_PLAYLIST' in option_choices or file_names and any(os.path.splitext(file_name)[1] == '.m3u8' for file_name in file_names):
# https://github.com/mpv-player/mpv/issues/6928
args.extend([
'--demuxer-lavf-format=hls',
'--stream-lavf-o-append=protocol_whitelist=file,http,https,tcp,tls,crypto,hls,applehttp',
])
if "NO_POSITION_SAVING" in option_choices:
args.append('--no-resume-playback')
if 'AUDIO_ONLY' in option_choices:
args.append('--no-video')
if len(format_selections):
args.append(f'--ytdl-format={"+".join(format_selections)}')
if len(headers):
headers_str = ', '.join(f'{name}: {value}' for (name, value) in headers)
args.append(f'--http-header-fields={headers_str}')
if 'PIP' in option_choices:
args.extend(['--ontop', '--no-border', '--snap-window'])
if 'CHOOSE_AUDIO_DEVICE' in option_choices:
device_id = get_single_selection('Select audio device', get_audio_devices())
args.append(f'--audio-device={device_id}')
if 'YT_DLP_RESOLUTION_SELECTION' in option_choices:
format_selection = get_single_selection('What resolution do you want to use?', [
('360p', '360'),
('480p', '480'),
('720p', '720'),
('1080p', '1080'),
('1440p', '1440'),
('Custom', 'CUSTOM'),
])
custom_format = None
if format_selection == 'CUSTOM':
custom_format = input('Custom format (e.g. bestvideo+bestaudio): ')
format_string = custom_format if custom_format else f'bestvideo[height<=?{format_selection}]+bestaudio/best'
args.append(f'--ytdl-format={format_string}')
if 'CUSTOM_OPTIONS' in option_choices:
custom_options = input('Custom options: ')
args.extend(shlex.split(custom_options))
if not urls and not file_names:
args.extend(['--idle', '--force-window'])
cmd = ['mpv'] + args + (urls or file_names)
print(cmd)
subprocess.Popen(cmd)
except Exception as e:
input(e)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment