import argparse
import os
import subprocess
import sys
from zipfile import ZipFile
from time import sleep
####################################################################
musicbee_exe = r'C:\Programs\MusicBee\MusicBee.exe'
extract_parent_dir = os.path.join(os.path.expanduser("~"), 'Temp')
####################################################################
def show_error_dialog(exc_type, exc_value, exc_traceback):
import tkinter as tk
from tkinter import messagebox
import traceback
filename = exc_traceback.tb_frame.f_code.co_filename
error_msg = f"{exc_type.__name__}: {exc_value}\n\n"
error_msg += "\n".join(traceback.format_tb(exc_traceback))
root = tk.Tk()
root.withdraw()
messagebox.showerror(filename, error_msg)
def extract_archive(archive_file, out_dir, skip_single_parent=True):
if archive_file.lower().endswith('.rar'):
from rarfile import RarFile
ArchiveFile = RarFile
skip_single_parent = False
else: ArchiveFile = ZipFile
with ArchiveFile(archive_file, "r") as archive_file:
names = [n for n in archive_file.namelist() if not n.startswith('__MACOSX')]
subdirs = [os.path.dirname(n) for n in names if '/' in n]
root_dirs = {n.split('/')[0] for n in names if '/' in n}
if skip_single_parent and len(root_dirs) == 1 and len(subdirs) == len(names):
root_dir = list(root_dirs)[0] + '/'
for archive_info in archive_file.infolist():
if archive_info.is_dir() or archive_info.filename.startswith('__MACOSX'):
continue
archive_info.filename = archive_info.filename.replace(root_dir, '', 1)
archive_file.extract(archive_info, out_dir)
else:
for archive_info in archive_file.infolist():
if not archive_info.filename.startswith('__MACOSX'):
archive_file.extract(archive_info, out_dir)
if sys.executable.endswith('pythonw.exe'):
sys.excepthook = show_error_dialog
parser = argparse.ArgumentParser()
parser.add_argument("zip_file", help="Path to the zip file")
parser.add_argument("-t", "--track-number", help="Queue files in order of the track number in their tags (requires pytaglib)", action="store_true")
parser.add_argument("-a", "--auto", help="Enable --track-number automatically if filenames don't start with track number", action="store_true")
parser.add_argument("-i", "--ignore-m3u", help="Do not use any m3u files within the archive", action="store_true")
parser.add_argument("-o", "--option", default='QueueLast', help="MusicBee option, see https://musicbee.fandom.com/wiki/Command_Line_Parameters.", action="store_true")
args = parser.parse_args()
archive_file = os.path.abspath(args.zip_file)
args.option = '/' + args.option.lstrip('/')
archive_name = os.path.splitext(os.path.basename(archive_file))[0]
temp_dir = os.path.join(extract_parent_dir, archive_name)
os.makedirs(temp_dir, exist_ok=True)
extract_archive(archive_file, temp_dir, skip_single_parent=True)
temp_m3u_name = '_archive_temp_playlist.m3u8'
m3u_file = None
if not args.ignore_m3u:
is_m3u = lambda file: (file.endswith(".m3u") or file.endswith(".m3u8")) and file != temp_m3u_name
m3u_file = next((os.path.join(r, f) for r, d, fnames in os.walk(temp_dir) for f in fnames if is_m3u(f)), None)
if m3u_file:
musicbee_args = [musicbee_exe, args.option, m3u_file]
subprocess.Popen(musicbee_args)
sys.exit()
music_exts_tags = (".mp3", ".flac", ".ogg", ".aac", ".wma", ".m4a", ".alac", ".ape", ".opus", ".mpc")
music_exts_notags = (".wav", ".wave", ".mod", ".mid")
music_exts = music_exts_tags + music_exts_notags
if args.auto:
need_track_num = lambda file: file.lower().endswith(music_exts_tags) and not file.lstrip()[0].isdigit()
args.track_number = any(need_track_num(f) for r, d, fnames in os.walk(temp_dir) for f in fnames)
if args.track_number:
import taglib
files = []
for root, dirs, fnames in os.walk(temp_dir):
for file in fnames:
try:
f = taglib.File(os.path.join(root, file))
key = f.tags.get('DISCNUMBER',['999'])[0] + '-' + f.tags.get('TRACKNUMBER',['999'])[0] + '-' + file
files.append((os.path.join(root, file), key))
except:
if file.lower().endswith(music_exts):
files.append((os.path.join(root, file), f'999-999-{file}'))
files.sort(key=lambda f: f[1])
# Need to create m3u as queueing files one-by-one causes MB to automatically sort by name
temp_m3u = os.path.join(temp_dir, temp_m3u_name)
try:
with open(temp_m3u, 'w', encoding="utf-8") as playlist_file:
for f in files:
playlist_file.write(os.path.relpath(f[0], temp_dir) + '\n')
musicbee_args = [musicbee_exe, args.option, temp_m3u]
subprocess.Popen(musicbee_args)
finally:
sleep(10) # waiting before delete in case musicbee hasn't started yet
try: os.remove(temp_m3u)
except: pass
else:
musicbee_args = [musicbee_exe, args.option, temp_dir]
subprocess.Popen(musicbee_args)