|
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) |