406 lines
10 KiB
Rust
406 lines
10 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
|
|
pub enum ChunkType {
|
|
Database,
|
|
DataSet,
|
|
AlbumList,
|
|
AlbumItem,
|
|
TrackList,
|
|
TrackItem,
|
|
StringTypes,
|
|
PlaylistList,
|
|
Playlist,
|
|
SongReference,
|
|
Unknown,
|
|
}
|
|
|
|
impl From<[u8; 4]> for ChunkType {
|
|
fn from(value: [u8; 4]) -> Self {
|
|
match value {
|
|
[0x6D, 0x68, 0x62, 0x64] => ChunkType::Database,
|
|
[0x6D, 0x68, 0x73, 0x64] => ChunkType::DataSet,
|
|
[0x6D, 0x68, 0x69, 0x61] => ChunkType::AlbumItem,
|
|
[0x6D, 0x68, 0x6C, 0x61] => ChunkType::AlbumList,
|
|
[0x6D, 0x68, 0x6C, 0x74] => ChunkType::TrackList,
|
|
[0x6D, 0x68, 0x69, 0x74] => ChunkType::TrackItem,
|
|
[0x6D, 0x68, 0x6F, 0x64] => ChunkType::StringTypes,
|
|
[0x6D, 0x68, 0x69, 0x70] => ChunkType::SongReference,
|
|
[0x6D, 0x68, 0x6C, 0x70] => ChunkType::PlaylistList,
|
|
[0x6D, 0x68, 0x79, 0x70] => ChunkType::Playlist,
|
|
_ => ChunkType::Unknown,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<ChunkType> for [u8; 4] {
|
|
fn from(value: ChunkType) -> Self {
|
|
match value {
|
|
ChunkType::Database => [0x6D, 0x68, 0x62, 0x64],
|
|
ChunkType::DataSet => [0x6D, 0x68, 0x73, 0x64],
|
|
ChunkType::AlbumItem => [0x6D, 0x68, 0x69, 0x61],
|
|
ChunkType::AlbumList => [0x6D, 0x68, 0x6C, 0x61],
|
|
ChunkType::TrackList => [0x6D, 0x68, 0x6C, 0x74],
|
|
ChunkType::TrackItem => [0x6D, 0x68, 0x69, 0x74],
|
|
ChunkType::SongReference => [0x6D, 0x68, 0x69, 0x70],
|
|
ChunkType::StringTypes => [0x6D, 0x68, 0x6F, 0x64],
|
|
ChunkType::PlaylistList => [0x6D, 0x68, 0x6C, 0x70],
|
|
ChunkType::Playlist => [0x6D, 0x68, 0x79, 0x70],
|
|
ChunkType::Unknown => [0x00, 0x00, 0x00, 0x00],
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Copy)]
|
|
pub struct ChunkHeader {
|
|
pub chunk_type: [u8; 4],
|
|
pub end_of_chunk: u32,
|
|
pub children_count: u32,
|
|
}
|
|
|
|
impl ChunkHeader {
|
|
pub(crate) fn empty() -> Self {
|
|
Self {
|
|
chunk_type: [0,0,0,0],
|
|
end_of_chunk: 0,
|
|
children_count: 0
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
|
pub struct Database {
|
|
unknown: u32,
|
|
version: u32,
|
|
children_count: u32,
|
|
id: u64,
|
|
unknown1: [u8; 32],
|
|
language: u16,
|
|
persistent_id: u64,
|
|
hash: [u8; 20],
|
|
unk: [u8; 30],
|
|
unk1: [u8; 32],
|
|
unk2: [u8; 20],
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Copy)]
|
|
pub struct DataSet {
|
|
pub data_type: u32,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
|
pub struct AlbumItem {
|
|
number_of_strings: u32,
|
|
unknown: u16,
|
|
album_id_for_track: u16,
|
|
timestamp: u64,
|
|
unknown1: u32,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Copy)]
|
|
pub struct TrackItem {
|
|
pub number_of_strings: u32, // number of mhod's count
|
|
pub unique_id: u32,
|
|
visible: u32,
|
|
pub filetype: u32,
|
|
type1: u8,
|
|
type2: u8,
|
|
compilation_flag: u8,
|
|
pub stars: u8,
|
|
pub last_modified_time: u32,
|
|
pub size: u32,
|
|
pub length: u32,
|
|
track_number: u32,
|
|
total_tracks: u32,
|
|
pub year: u32,
|
|
pub bitrate: u32,
|
|
pub sample_rate: u32,
|
|
volume: u32,
|
|
start_time: u32,
|
|
stop_time: u32,
|
|
soundcheck: u32,
|
|
pub play_count: u32,
|
|
play_count2: u32,
|
|
last_played_time: u32,
|
|
disc_number: u32,
|
|
total_discs: u32,
|
|
userid: u32,
|
|
date_added: u32,
|
|
bookmark_time: u32,
|
|
pub dbid: u64,
|
|
checked: u8,
|
|
application_rating: u8,
|
|
pub bpm: u16,
|
|
artwork_count: u16,
|
|
unk9: u16,
|
|
artwork_size: u32,
|
|
unk11: u32,
|
|
sample_rate2: u32,
|
|
date_released: u32,
|
|
unk14: u32,
|
|
unk15: u32,
|
|
unk16: u32,
|
|
pub skip_count: u32,
|
|
last_skipped: u32,
|
|
pub has_artwork: u8,
|
|
skip_when_shuffling: u8,
|
|
remember_playback_position: u8,
|
|
flag4: u8,
|
|
pub dbid2: u64,
|
|
lyrics_flag: u8,
|
|
movie_file_flag: u8,
|
|
played_mark: u8,
|
|
unk17: u8,
|
|
unk21: u32,
|
|
pregap: u32,
|
|
sample_count: u64,
|
|
unk25: u32,
|
|
postgap: u32,
|
|
unk27: u32,
|
|
pub media_type: u32,
|
|
season_number: u32,
|
|
episode_number: u32,
|
|
unk31: [u8; 28],
|
|
gapless_data: u32,
|
|
unk38: u32,
|
|
gapless_track_flag: u16,
|
|
gapless_album_flag: u16,
|
|
unk39_hash: [u8; 20],
|
|
unk40: [u8; 18],
|
|
album_id: u16,
|
|
mhii_link: u32,
|
|
unk: [u8; 32],
|
|
unk1: [u8; 32],
|
|
unk2: [u8; 32],
|
|
unk3: [u8; 32],
|
|
unk4: [u8; 32],
|
|
unk5: [u8; 32],
|
|
unk6: [u8; 32],
|
|
}
|
|
|
|
impl TrackItem {
|
|
pub(crate) fn new(unique_id: u32, size: u32, length: u32, year: u32, bitrate: u32, sample_rate: u32, dbid: u64, sample_count: u64) -> Self {
|
|
Self {
|
|
number_of_strings: 0,
|
|
unique_id,
|
|
visible: 1,
|
|
filetype: 1297101600,
|
|
type1: 0,
|
|
type2: 1,
|
|
compilation_flag: 0,
|
|
stars: 0,
|
|
last_modified_time: 3786278955,
|
|
size,
|
|
length,
|
|
track_number: 0,
|
|
total_tracks: 0,
|
|
year,
|
|
bitrate,
|
|
sample_rate,
|
|
volume: 0,
|
|
start_time: 0,
|
|
stop_time: 0,
|
|
soundcheck: 0,
|
|
play_count: 0,
|
|
play_count2: 0,
|
|
last_played_time: 0,
|
|
disc_number: 0,
|
|
total_discs: 0,
|
|
userid: 0,
|
|
date_added: 3815861983,
|
|
bookmark_time: 0,
|
|
dbid,
|
|
checked: 0,
|
|
application_rating: 0,
|
|
bpm: 0,
|
|
artwork_count: 0,
|
|
unk9: 65535,
|
|
artwork_size: 0,
|
|
unk11: 0,
|
|
sample_rate2: sample_rate,
|
|
date_released: 0,
|
|
unk14: 12,
|
|
unk15: 0,
|
|
unk16: 0,
|
|
skip_count: 0,
|
|
last_skipped: 0,
|
|
has_artwork: 2,
|
|
skip_when_shuffling: 0,
|
|
remember_playback_position: 0,
|
|
flag4: 0,
|
|
dbid2: dbid,
|
|
lyrics_flag: 0,
|
|
movie_file_flag: 0,
|
|
played_mark: 0,
|
|
unk17: 0,
|
|
unk21: 0,
|
|
pregap: 528,
|
|
sample_count,
|
|
unk25: 0,
|
|
postgap: 566,
|
|
unk27: 33554435,
|
|
media_type: 1,
|
|
season_number: 0,
|
|
episode_number: 0,
|
|
unk31: [0; 28],
|
|
gapless_data: 8154240,
|
|
unk38: 0,
|
|
gapless_track_flag: 1,
|
|
gapless_album_flag: 0,
|
|
unk39_hash: [0; 20],
|
|
unk40: [0; 18],
|
|
album_id: 36682,
|
|
mhii_link: 8161920,
|
|
unk: [0; 32],
|
|
unk1: [0; 32],
|
|
unk2: [0; 32],
|
|
unk3: [0; 32],
|
|
unk4: [0; 32],
|
|
unk5: [0; 32],
|
|
unk6: [0; 32],
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
|
pub struct StringEntry {
|
|
// mhod
|
|
pub entry_type: u32,
|
|
pub unk1: u32,
|
|
pub unk2: u32,
|
|
pub position: u32,
|
|
pub length: u32,
|
|
pub unknown: u32,
|
|
pub unk4: u32,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
|
pub struct PlaylistIndexEntry {
|
|
// mhod
|
|
entry_type: u32,
|
|
unk1: u32,
|
|
unk2: u32,
|
|
index_type: u32,
|
|
pub count: u32,
|
|
null_padding: u64,
|
|
null_padding1: u64,
|
|
null_padding2: u64,
|
|
null_padding3: u64,
|
|
null_padding4: u64,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
|
pub struct LetterJumpEntry {
|
|
entry_type: u32,
|
|
unk1: u32,
|
|
unk2: u32,
|
|
index_type: u32,
|
|
pub count: u32,
|
|
null_padding: u64,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
|
pub struct JumpTable {
|
|
letter: u32, // UTF-16 LE Uppercase with two padding null bytes
|
|
entry_num: u32, // the number of the first entry in the corresponding MHOD52 index starting with this letter. Zero-based and incremented by one for each entry, not 4.
|
|
count: u32, // the count of entries starting with this letter in the corresponding MHOD52.
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
|
pub struct Playlist {
|
|
pub data_object_child_count: u32,
|
|
pub playlist_item_count: u32,
|
|
pub is_master_playlist_flag: u8,
|
|
unk: [u8; 3],
|
|
pub timestamp: u32,
|
|
pub persistent_playlist_id: u64,
|
|
unk3: u32,
|
|
pub string_mhod_count: u16,
|
|
podcast_flag: u16,
|
|
list_sort_order: u32,
|
|
unk1: [u8; 22],
|
|
unk2: [u8; 22],
|
|
}
|
|
|
|
impl Playlist {
|
|
pub fn new(persistent_playlist_id: u64, sort_order: ListSortOrder) -> Self {
|
|
Self {
|
|
data_object_child_count: 0,
|
|
playlist_item_count: 0,
|
|
is_master_playlist_flag: 0,
|
|
unk: [0; 3],
|
|
timestamp: 0,
|
|
persistent_playlist_id,
|
|
unk3: 0,
|
|
string_mhod_count: 0,
|
|
podcast_flag: 0,
|
|
list_sort_order: sort_order.into(),
|
|
unk1: [0; 22],
|
|
unk2: [0; 22],
|
|
}
|
|
}
|
|
}
|
|
|
|
pub enum ListSortOrder {
|
|
Manual,
|
|
SongTitle,
|
|
Album,
|
|
Artist,
|
|
Bitrate,
|
|
Genre,
|
|
Size,
|
|
Year,
|
|
SampleRate,
|
|
PlayCount,
|
|
LastPlayed,
|
|
MyRating,
|
|
BPM
|
|
}
|
|
|
|
impl From<ListSortOrder> for u32 {
|
|
fn from(value: ListSortOrder) -> Self {
|
|
match value {
|
|
ListSortOrder::Manual => 1,
|
|
ListSortOrder::SongTitle => 3,
|
|
ListSortOrder::Album => 4,
|
|
ListSortOrder::Artist => 5,
|
|
ListSortOrder::Bitrate => 6,
|
|
ListSortOrder::Genre => 7,
|
|
ListSortOrder::Size => 11,
|
|
ListSortOrder::Year => 13,
|
|
ListSortOrder::SampleRate => 14,
|
|
ListSortOrder::PlayCount => 20,
|
|
ListSortOrder::LastPlayed => 21,
|
|
ListSortOrder::MyRating => 23,
|
|
ListSortOrder::BPM => 25
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
|
pub struct PlaylistItem {
|
|
pub data_object_child_count: u32,
|
|
podcast_grouping_flag: u16,
|
|
unk4: u16,
|
|
pub group_id: u32,
|
|
pub track_id: u32,
|
|
timestamp: u32,
|
|
podcast_grouping_reference: u32,
|
|
unk: [u8; 30],
|
|
unk1: [u8; 10],
|
|
}
|
|
|
|
impl PlaylistItem {
|
|
pub fn new(track_id: u32, group_id: u32) -> Self {
|
|
Self {
|
|
data_object_child_count: 0,
|
|
podcast_grouping_flag: 0,
|
|
unk4: 0,
|
|
group_id,
|
|
track_id,
|
|
timestamp: 0,
|
|
podcast_grouping_reference: 0,
|
|
unk: [0; 30],
|
|
unk1: [0; 10],
|
|
}
|
|
}
|
|
} |