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 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 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], } } }