Skip to content

Instantly share code, notes, and snippets.

@cskaryd
Forked from lempamo/mbl_reader.py
Last active May 28, 2023 09:13
Show Gist options
  • Save cskaryd/51496f5c6edecba360800c855f32668f to your computer and use it in GitHub Desktop.
Save cskaryd/51496f5c6edecba360800c855f32668f to your computer and use it in GitHub Desktop.
Fix issues with .NET DateTime Ticks parsing. It appears that all DateTime's expect the 8th byte to be hardcoded to \x08; Making this adjustment allows the byte array to translate into an integer creating a valid .NET Ticks number that correctly converts to a DateTime value which matches the value displayed in MusicBee.
import json
import datetime
global library
def decode_from_7bit(data):
"""
Decode 7-bit encoded int from str data
"""
result = 0
for index, char in enumerate(data):
#byte_value = ord(char)
result |= (char & 0x7f) << (7 * index)
if char & 0x80 == 0:
break
return result
def read_int(bytes_):
return int.from_bytes(bytes_, byteorder="little", signed=True)
def read_uint(bytes_):
return int.from_bytes(bytes_, byteorder="little")
def read_str(file):
len_1 = file.read(1)
if read_uint(len_1) > 0x7F:
len_2 = file.read(1)
if read_uint(len_2) > 0x7F:
length = decode_from_7bit([read_uint(len_1), read_uint(len_2), read_uint(file.read(1))])
else:
length = decode_from_7bit([read_uint(len_1), read_uint(len_2)])
else:
length = read_uint(len_1)
if length == 0:
return ""
return file.read(length).decode("utf-8")
def format_date(date_bytes):
if date_bytes == b'\x00\x00\x00\x00\x00\x00\x00\x00':
return ''
else:
date_ticks = read_int(date_bytes[0:7] + b'\x08')
epoch_ticks = 621355968000000000
return datetime.datetime.fromtimestamp((date_ticks - epoch_ticks)/10000000).strftime('%Y-%m-%d %H:%M:%S')
with open("F:\\MusicBee\\MusicBeeLibrary.mbl", "rb") as mbl:
count = read_int(mbl.read(4))
if not count & 0xFF:
raise
count = count >> 8
library = {"file_count": count, "files": []}
while True:
media = {"file_designation": read_uint(mbl.read(1))}
if media["file_designation"] == 1:
break
if 10 > media["file_designation"] > 1:
media["status"] = read_uint(mbl.read(1))
if media["status"] > 6:
raise
media["unknown_1"] = read_uint(mbl.read(1))
media["play_count"] = read_uint(mbl.read(2))
media["date_last_played"] = format_date(mbl.read(8))
media["skip_count"] = read_uint(mbl.read(2))
media["file_path"] = read_str(mbl)
print(media["file_path"])
if media["file_path"] == "":
raise
media["file_size"] = read_int(mbl.read(4))
media["sample_rate"] = read_int(mbl.read(4))
media["channel_mode"] = read_uint(mbl.read(1))
media["bitrate_type"] = read_uint(mbl.read(1))
media["bitrate"] = read_int(mbl.read(2))
media["track_length"] = read_int(mbl.read(4))
media["date_added"] = format_date(mbl.read(8))
media["date_modified"] = format_date(mbl.read(8))
media["artwork"] = []
while True:
art = {"type": read_uint(mbl.read(1))}
if art["type"] > 253:
break
art["string_1"] = read_str(mbl)
art["store_mode"] = read_uint(mbl.read(1))
art["string_2"] = read_str(mbl)
media["artwork"].append(art)
media["tags_type"] = read_uint(mbl.read(1))
media["tags"] = {}
while True:
tag_code = read_uint(mbl.read(1))
if tag_code == 0:
break
if tag_code == 255:
c = read_int(mbl.read(2))
i = 0
media["cue"] = []
while i < c:
cue = {}
cue["a"] = read_uint(mbl.read(1))
cue["b"] = read_uint(mbl.read(2))
cue["c"] = read_int(mbl.read(8))
cue["d"] = read_uint(mbl.read(2))
media["cue"].append(cue)
i += 1
break
media["tags"][str(tag_code)] = read_str(mbl)
library["files"].append(media)
else:
raise
print(count, len(library["files"]))
with open("F:\\MusicBee\\MBL.json", "w") as jfile:
json.dump(library, jfile, indent=2)
@cskaryd
Copy link
Author

cskaryd commented Nov 26, 2022

See also a quick and dirty MBL reader written in C# based on lempano's original gist which iterates through the mbl file and displays select values including correct datetime values

https://github.com/cskaryd/musicbee-library-parser

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment