I wanted zip file support as well, so here's a workaround I use. Maybe somebody will find it useful.
I made a python script that will decompress zips (and rars, if rarfile is installed) and then queue the result in musicbee. For easy access I have also added a context menu entry that can be used when right clicking zips to unzip & queue it.
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)
Configure the `musicbee_exe` and `extract_parent_dir` variables at the start. Then, create a shortcut in "C:\Users\username\AppData\Roaming\Microsoft\Windows\SendTo". Name it something like "MusicBee Queue Archive". In the file properties, set the target to "python.exe {full path to script}". This is assuming your python.exe is added to PATH. You can also use pythonw.exe instead to hide the command window. Also, sometimes zipped songs don't start with the track number (e.g zips from bandcamp), leading to incorrect order of queued files. Add ` -a` add the end of the target path in the properties if that's an issue. This option will sort the tracks by their track number before queueing them, but requires the pytaglib module.
It should now be available in file explorer as a context menu entry under "Send To". Using it on an archive will extract it to "C:\Users\username\temp\{name}" and queue-last the contents in MusicBee.