[READ-ONLY] Mirror of https://github.com/probablykasper/ferrum. Music library app for Mac, Linux and Windows ferrum.kasper.space
electron linux macos music music-library music-player napi windows
0

Configure Feed

Select the types of activity you want to include in your feed.

Exclusively use lofty

Kasper (Sep 18, 2024, 1:17 AM +0200) b3e25811 5452103d

+200 -861
+27 -42
Cargo.lock
··· 4 4 5 5 [[package]] 6 6 name = "addr2line" 7 - version = "0.22.0" 7 + version = "0.24.1" 8 8 source = "registry+https://github.com/rust-lang/crates.io-index" 9 - checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" 9 + checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" 10 10 dependencies = [ 11 11 "gimli", 12 12 ] 13 - 14 - [[package]] 15 - name = "adler" 16 - version = "1.0.2" 17 - source = "registry+https://github.com/rust-lang/crates.io-index" 18 - checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 13 20 14 [[package]] 21 15 name = "adler2" ··· 72 66 73 67 [[package]] 74 68 name = "backtrace" 75 - version = "0.3.73" 69 + version = "0.3.74" 76 70 source = "registry+https://github.com/rust-lang/crates.io-index" 77 - checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" 71 + checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 78 72 dependencies = [ 79 73 "addr2line", 80 - "cc", 81 74 "cfg-if", 82 75 "libc", 83 - "miniz_oxide 0.7.4", 76 + "miniz_oxide", 84 77 "object", 85 78 "rustc-demangle", 79 + "windows-targets 0.52.6", 86 80 ] 87 81 88 82 [[package]] ··· 120 114 121 115 [[package]] 122 116 name = "cc" 123 - version = "1.1.15" 117 + version = "1.1.20" 124 118 source = "registry+https://github.com/rust-lang/crates.io-index" 125 - checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" 119 + checksum = "45bcde016d64c21da4be18b655631e5ab6d3107607e71a73a9f53eb48aae23fb" 126 120 dependencies = [ 127 121 "shlex", 128 122 ] ··· 303 297 checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" 304 298 dependencies = [ 305 299 "crc32fast", 306 - "miniz_oxide 0.8.0", 300 + "miniz_oxide", 307 301 ] 308 302 309 303 [[package]] ··· 328 322 329 323 [[package]] 330 324 name = "gimli" 331 - version = "0.29.0" 325 + version = "0.31.0" 332 326 source = "registry+https://github.com/rust-lang/crates.io-index" 333 - checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" 327 + checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" 334 328 335 329 [[package]] 336 330 name = "hashbrown" ··· 340 334 341 335 [[package]] 342 336 name = "iana-time-zone" 343 - version = "0.1.60" 337 + version = "0.1.61" 344 338 source = "registry+https://github.com/rust-lang/crates.io-index" 345 - checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" 339 + checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" 346 340 dependencies = [ 347 341 "android_system_properties", 348 342 "core-foundation-sys", ··· 494 488 495 489 [[package]] 496 490 name = "miniz_oxide" 497 - version = "0.7.4" 498 - source = "registry+https://github.com/rust-lang/crates.io-index" 499 - checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 500 - dependencies = [ 501 - "adler", 502 - ] 503 - 504 - [[package]] 505 - name = "miniz_oxide" 506 491 version = "0.8.0" 507 492 source = "registry+https://github.com/rust-lang/crates.io-index" 508 493 checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" ··· 543 528 544 529 [[package]] 545 530 name = "napi" 546 - version = "2.16.9" 531 + version = "2.16.10" 547 532 source = "registry+https://github.com/rust-lang/crates.io-index" 548 - checksum = "1277600d452e570cc83cf5f4e8efb389cc21e5cbefadcfba7239f4551e2e3e99" 533 + checksum = "04409e8c2d61995696e44d2181b79b68c1dd41f7e24a17cde60bbd9f54ddddef" 549 534 dependencies = [ 550 535 "bitflags", 551 536 "ctor", ··· 565 550 566 551 [[package]] 567 552 name = "napi-derive" 568 - version = "2.16.11" 553 + version = "2.16.12" 569 554 source = "registry+https://github.com/rust-lang/crates.io-index" 570 - checksum = "150d87c4440b9f4815cb454918db498b5aae9a57aa743d20783fe75381181d01" 555 + checksum = "17435f7a00bfdab20b0c27d9c56f58f6499e418252253081bfff448099da31d1" 571 556 dependencies = [ 572 557 "cfg-if", 573 558 "convert_case", ··· 579 564 580 565 [[package]] 581 566 name = "napi-derive-backend" 582 - version = "1.0.73" 567 + version = "1.0.74" 583 568 source = "registry+https://github.com/rust-lang/crates.io-index" 584 - checksum = "0cd81b794fc1d6051acf8c4f3cb4f82833b0621272a232b4ff0cf3df1dbddb61" 569 + checksum = "967c485e00f0bf3b1bdbe510a38a4606919cf1d34d9a37ad41f25a81aa077abe" 585 570 dependencies = [ 586 571 "convert_case", 587 572 "once_cell", ··· 851 836 852 837 [[package]] 853 838 name = "rustix" 854 - version = "0.38.35" 839 + version = "0.38.37" 855 840 source = "registry+https://github.com/rust-lang/crates.io-index" 856 - checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" 841 + checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" 857 842 dependencies = [ 858 843 "bitflags", 859 844 "errno", ··· 1052 1037 1053 1038 [[package]] 1054 1039 name = "unicode-ident" 1055 - version = "1.0.12" 1040 + version = "1.0.13" 1056 1041 source = "registry+https://github.com/rust-lang/crates.io-index" 1057 - checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1042 + checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 1058 1043 1059 1044 [[package]] 1060 1045 name = "unicode-normalization" 1061 - version = "0.1.23" 1046 + version = "0.1.24" 1062 1047 source = "registry+https://github.com/rust-lang/crates.io-index" 1063 - checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" 1048 + checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" 1064 1049 dependencies = [ 1065 1050 "tinyvec", 1066 1051 ] 1067 1052 1068 1053 [[package]] 1069 1054 name = "unicode-segmentation" 1070 - version = "1.11.0" 1055 + version = "1.12.0" 1071 1056 source = "registry+https://github.com/rust-lang/crates.io-index" 1072 - checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" 1057 + checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 1073 1058 1074 1059 [[package]] 1075 1060 name = "url"
+1 -1
ferrum-addon/addon.d.ts
··· 184 184 } 185 185 export declare function get_image(index: number): JsImage | null 186 186 export declare function set_image(index: number, pathStr: string): void 187 - export declare function set_image_data(index: number, bytes: ArrayBuffer, mimeType: string): void 187 + export declare function set_image_data(index: number, bytes: ArrayBuffer): void 188 188 export declare function remove_image(index: number): void 189 189 export declare function update_track_info(trackId: string, info: TrackMd): void 190 190 export declare class ItunesImport {
+60 -320
src-native/tracks/import.rs
··· 1 1 use crate::data::Data; 2 2 use crate::library_types::Track; 3 - use crate::tracks::{generate_filename, tag}; 3 + use crate::tracks::generate_filename; 4 4 use crate::{sys_time_to_timestamp, UniResult}; 5 - use id3::TagLike; 6 - use lofty::tag::{Accessor, TagExt}; 7 - use lofty::{config::ParseOptions, file::AudioFile, ogg::OpusFile}; 8 - use mp3_metadata; 9 - use std::fs::{self, File}; 5 + use lofty::file::{AudioFile, TaggedFileExt}; 6 + use lofty::tag::{Accessor, ItemKey, TagExt}; 7 + use std::fs; 10 8 use std::path::Path; 11 9 12 10 #[derive(PartialEq)] ··· 46 44 write!(f, "{}", self.file_extension()) 47 45 } 48 46 } 49 - pub fn import_auto(data: &Data, path: &Path, now: i64) -> UniResult<Track> { 50 - let track = match FileType::from_path(path)? { 51 - FileType::Mp3 => import_mp3(data, path, now)?, 52 - FileType::M4a => import_m4a(data, path, now)?, 53 - FileType::Opus => import_opus(data, path, now)?, 54 - }; 55 - Ok(track) 56 - } 57 - 58 - fn get_and_fix_id3_year(tag: &mut id3::Tag) -> Option<i64> { 59 - match tag.date_recorded() { 60 - Some(tdrc) => Some(i64::from(tdrc.year)), 61 - None => match tag.year() { 62 - Some(tyer) => { 63 - let x = tag::id3_timestamp_from_year(tyer); 64 - tag.set_date_recorded(x); 65 - Some(i64::from(tyer)) 66 - } 67 - None => match tag.date_released() { 68 - Some(tdrl) => { 69 - tag.set_date_recorded(tag::id3_timestamp_from_year(tdrl.year)); 70 - Some(i64::from(tdrl.year)) 71 - } 72 - None => None, 73 - }, 74 - }, 75 - } 76 - } 77 - 78 - fn get_first_text_m4a(tag: &mp4ameta::Tag, id: [u8; 4]) -> Option<String> { 79 - let fourcc = mp4ameta::Fourcc(id); 80 - let mut frames = tag.data_of(&fourcc); 81 - let data = match frames.next() { 82 - Some(data) => data, 83 - None => return None, 84 - }; 85 - match data.string() { 86 - Some(s) => Some(s.to_string()), 87 - None => None, 88 - } 89 - } 90 47 91 48 pub fn read_file_metadata(path: &Path) -> UniResult<fs::Metadata> { 92 49 match std::fs::metadata(path) { ··· 98 55 } 99 56 } 100 57 101 - pub fn import_opus(data: &Data, track_path: &Path, now: i64) -> UniResult<Track> { 58 + pub fn import(data: &Data, track_path: &Path, now: i64) -> UniResult<Track> { 102 59 let file_md = read_file_metadata(track_path)?; 103 60 104 61 let mut date_modified = match file_md.modified() { ··· 106 63 Err(_) => now, 107 64 }; 108 65 109 - let mut file = match File::open(track_path) { 110 - Ok(file) => file, 111 - Err(e) => throw!("Unable to open file: {}", e), 66 + let probe = match lofty::probe::Probe::open(track_path) { 67 + Ok(f) => { 68 + let parse_options = lofty::config::ParseOptions::new() 69 + .read_properties(true) 70 + .parsing_mode(lofty::config::ParsingMode::Strict); 71 + f.options(parse_options) 72 + } 73 + Err(e) => throw!("File does not exist: {}", e), 112 74 }; 113 - let mut opusfile = match OpusFile::read_from(&mut file, ParseOptions::new()) { 114 - Ok(opusfile) => opusfile, 115 - Err(e) => throw!("Unable to read opus tags: {}", e), 75 + let mut tagged_file = match probe.read() { 76 + Ok(f) => f, 77 + Err(e) => throw!("Unable to read file: {}", e), 116 78 }; 117 - let opus_properties = *opusfile.properties(); 79 + let properties = tagged_file.properties().clone(); 118 80 119 81 let mut tag_changed = false; 120 - let tag = opusfile.vorbis_comments_mut(); 82 + let mut default_tag = lofty::tag::Tag::new(tagged_file.primary_tag_type()); 83 + let tag = match tagged_file.primary_tag_mut() { 84 + Some(tag) => tag, 85 + None => &mut default_tag, 86 + }; 121 87 122 88 let title = match tag.title() { 123 89 Some(title) => title.into_owned(), ··· 134 100 let artist = tag.artist().map(|s| s.into_owned()).unwrap_or_default(); 135 101 136 102 let tracks_dir = &data.paths.tracks_dir; 137 - let filename = generate_filename(tracks_dir, &artist, &title, "opus"); 103 + let extension = match FileType::from_path(track_path)? { 104 + FileType::Opus => "opus", 105 + FileType::M4a => "m4a", 106 + FileType::Mp3 => "mp3", 107 + }; 108 + let filename = generate_filename(tracks_dir, &artist, &title, extension); 138 109 let dest_path = tracks_dir.join(&filename); 139 110 140 111 match fs::copy(track_path, &dest_path) { ··· 159 130 } 160 131 161 132 let track = Track { 162 - size: file_md.len() as i64, 163 - duration: opus_properties.duration().as_secs_f64(), 164 - bitrate: (opus_properties.audio_bitrate() * 1000).into(), // kbps to bps 165 - sampleRate: opus_properties.input_sample_rate().into(), 133 + size: file_md.len().try_into().unwrap(), 134 + duration: properties.duration().as_secs_f64(), 135 + bitrate: (properties.audio_bitrate().expect("Missing bitrate") * 1000).into(), // kbps to bps 136 + sampleRate: properties 137 + .sample_rate() 138 + .expect("Missing sample rate") 139 + .into(), 166 140 file: filename, 167 141 dateModified: date_modified, 168 142 dateAdded: now, ··· 170 144 importedFrom: None, 171 145 originalId: None, 172 146 artist, 173 - composer: tag.get("COMPOSER").map(|s| s.to_string()), 174 - sortName: tag.get("TITLESORT").map(|s| s.to_string()), 175 - sortArtist: tag.get("ARTISTSORT").map(|s| s.to_string()), 176 - sortComposer: tag.get("COMPOSERSORT").map(|s| s.to_string()), 147 + composer: tag.get_string(&ItemKey::Composer).map(|s| s.to_string()), 148 + sortName: tag 149 + .get_string(&ItemKey::TrackTitleSortOrder) 150 + .map(|s| s.to_string()), 151 + sortArtist: tag 152 + .get_string(&ItemKey::TrackArtistSortOrder) 153 + .map(|s| s.to_string()), 154 + sortComposer: tag 155 + .get_string(&ItemKey::ComposerSortOrder) 156 + .map(|s| s.to_string()), 177 157 genre: tag.genre().map(|s| s.into_owned()), 178 158 rating: None, 179 159 year: tag.year().map(|y| y.into()), 180 - bpm: match tag.get("BPM") { 160 + bpm: match tag.get_string(&ItemKey::Bpm) { 181 161 Some(n) => n.parse().ok(), 182 162 None => None, 183 163 }, 184 164 comments: tag.comment().map(|s| s.into_owned()), 185 - grouping: tag.get("GROUPING").map(|s| s.to_string()), 165 + grouping: tag 166 + .get_string(&ItemKey::ContentGroup) 167 + .map(|s| s.to_string()) 168 + .or_else(|| { 169 + tag.get_string(&ItemKey::AppleId3v2ContentGroup) 170 + .map(|s| s.to_string()) 171 + }), 186 172 liked: None, 187 173 disliked: None, 188 174 disabled: None, 189 175 compilation: None, 190 176 albumName: tag.album().map(|s| s.to_string()), 191 - albumArtist: tag.get("ALBUMARTIST").map(|s| s.to_string()), 192 - sortAlbumName: tag.get("ALBUMSORT").map(|s| s.to_string()), 193 - sortAlbumArtist: tag.get("ALBUMARTISTSORT").map(|s| s.to_string()), 177 + albumArtist: tag.get_string(&ItemKey::AlbumArtist).map(|s| s.to_string()), 178 + sortAlbumName: tag 179 + .get_string(&ItemKey::AlbumTitleSortOrder) 180 + .map(|s| s.to_string()), 181 + sortAlbumArtist: tag 182 + .get_string(&ItemKey::AlbumArtistSortOrder) 183 + .map(|s| s.to_string()), 194 184 trackNum: tag.track(), 195 185 trackCount: tag.track_total(), 196 186 discNum: tag.disk(), ··· 206 196 }; 207 197 Ok(track) 208 198 } 209 - 210 - pub fn import_m4a(data: &Data, track_path: &Path, now: i64) -> UniResult<Track> { 211 - let file_md = read_file_metadata(track_path)?; 212 - 213 - let mut date_modified = match file_md.modified() { 214 - Ok(sys_time) => sys_time_to_timestamp(&sys_time), 215 - Err(_) => now, 216 - }; 217 - 218 - let mut tag_changed = false; 219 - let mut tag = match mp4ameta::Tag::read_from_path(track_path) { 220 - Ok(tag) => tag, 221 - Err(e) => match e.kind { 222 - mp4ameta::ErrorKind::NoTag => { 223 - throw!("No m4a tags found in file. Auto creating m4a tags is not yet supported") 224 - } 225 - _ => { 226 - throw!("Error reading m4a tags: {}", e) 227 - } 228 - }, 229 - }; 230 - 231 - let audio_info = tag.audio_info(); 232 - let duration = match audio_info.duration { 233 - Some(duration) => duration, 234 - None => throw!("Unable to read duration of m4a file"), 235 - }; 236 - let bitrate = match audio_info.avg_bitrate { 237 - Some(bitrate) => bitrate, 238 - None => throw!("Unable to read bitrate of m4a file"), 239 - }; 240 - let sample_rate = match audio_info.sample_rate { 241 - Some(sample_rate) => sample_rate, 242 - None => throw!("Unable to read sample rate of m4a file"), 243 - }; 244 - 245 - let year = match tag.year() { 246 - Some(year_str) => match year_str.parse() { 247 - Ok(year) => Some(year), 248 - Err(e) => throw!("Unable to read year of m4a file: {}", e), 249 - }, 250 - None => None, 251 - }; 252 - 253 - let title = match tag.title() { 254 - Some(title) => title.to_owned(), 255 - None => { 256 - let file_stem = match track_path.file_stem() { 257 - Some(stem) => stem.to_string_lossy().into_owned(), 258 - None => "".to_string(), 259 - }; 260 - tag.set_title(&file_stem); 261 - tag_changed = true; 262 - file_stem 263 - } 264 - }; 265 - let artist = tag.artist(); 266 - 267 - let tracks_dir = &data.paths.tracks_dir; 268 - let filename = generate_filename(tracks_dir, artist.unwrap_or(""), &title, "m4a"); 269 - let dest_path = tracks_dir.join(&filename); 270 - 271 - match fs::copy(track_path, &dest_path) { 272 - Ok(_) => (), 273 - Err(e) => throw!("Error copying file: {e}"), 274 - }; 275 - println!( 276 - "{} -> {}", 277 - track_path.to_string_lossy(), 278 - dest_path.to_string_lossy() 279 - ); 280 - 281 - if tag_changed { 282 - match tag.write_to_path(&dest_path) { 283 - Ok(_) => (), 284 - Err(e) => throw!("Unable to tag file {}: {e}", dest_path.to_string_lossy()), 285 - }; 286 - // manually set date_modified because the date_modified doens't seem to 287 - // immediately update after tag.write_to_path(). 288 - date_modified = now; 289 - } 290 - println!("Images: {:?}", tag.images().collect::<Vec<_>>().len()); 291 - 292 - let track = Track { 293 - size: file_md.len() as i64, 294 - duration: duration.as_secs_f64(), 295 - bitrate: bitrate.into(), 296 - sampleRate: sample_rate.hz().into(), 297 - file: filename, 298 - dateModified: date_modified, 299 - dateAdded: now, 300 - name: title.to_string(), 301 - importedFrom: None, 302 - originalId: None, 303 - artist: artist.unwrap_or_default().to_string(), 304 - composer: tag.composer().map(|s| s.to_string()), 305 - sortName: get_first_text_m4a(&tag, *b"sonm"), 306 - sortArtist: get_first_text_m4a(&tag, *b"soar"), 307 - sortComposer: get_first_text_m4a(&tag, *b"soco"), 308 - genre: tag.genre().map(|s| s.to_owned()), 309 - rating: None, 310 - year, 311 - bpm: tag.bpm().map(|bpm| bpm.into()), 312 - comments: tag.comment().map(|g| g.to_string()), 313 - grouping: tag.grouping().map(|g| g.to_string()), 314 - liked: None, 315 - disliked: None, 316 - disabled: None, 317 - compilation: None, 318 - albumName: tag.album().map(|s| s.to_owned()), 319 - albumArtist: tag.album_artist().map(|s| s.to_owned()), 320 - sortAlbumName: get_first_text_m4a(&tag, *b"soal"), 321 - sortAlbumArtist: get_first_text_m4a(&tag, *b"soaa"), 322 - trackNum: tag.track_number().map(|n| n.into()), 323 - trackCount: tag.total_tracks().map(|n| n.into()), 324 - discNum: tag.disc_number().map(|n| n.into()), 325 - discCount: tag.total_discs().map(|n| n.into()), 326 - dateImported: None, 327 - playCount: None, 328 - plays: None, 329 - playsImported: None, 330 - skipCount: None, 331 - skips: None, 332 - skipsImported: None, 333 - volume: None, 334 - }; 335 - Ok(track) 336 - } 337 - 338 - fn get_first_text_id3(tag: &id3::Tag, id: &str) -> Option<String> { 339 - let frame = tag.get(id)?; 340 - let text = frame.content().text()?; 341 - Some(text.to_owned()) 342 - } 343 - pub fn import_mp3(data: &Data, track_path: &Path, now: i64) -> UniResult<Track> { 344 - let file_md = read_file_metadata(track_path)?; 345 - 346 - let mut date_modified = match file_md.modified() { 347 - Ok(sys_time) => sys_time_to_timestamp(&sys_time), 348 - Err(_) => now, 349 - }; 350 - 351 - let mp3_md = match mp3_metadata::read_from_file(track_path) { 352 - Ok(md) => md, 353 - Err(e) => throw!("Error reading mp3 metadata: {}", e), 354 - }; 355 - let duration = mp3_md.duration.as_secs_f64(); 356 - let sample_rate = match mp3_md.frames.get(0) { 357 - Some(frame) => frame.sampling_freq as f64, 358 - None => throw!("Unable to read first audio frame"), 359 - }; 360 - let bitrate = { 361 - let mut bits = 0; 362 - for frame in mp3_md.frames { 363 - bits += frame.size; 364 - } 365 - bits *= 8; // kbits to kb 366 - let bitrate = f64::from(bits) / duration; 367 - bitrate.round() 368 - }; 369 - 370 - let mut tag_changed = false; 371 - let mut tag = match id3::Tag::read_from_path(track_path) { 372 - Ok(tag) => tag, 373 - Err(_) => id3::Tag::new(), 374 - }; 375 - 376 - let year = get_and_fix_id3_year(&mut tag); 377 - 378 - let title = match tag.title() { 379 - Some(title) => title.to_owned(), 380 - None => { 381 - let file_stem = match track_path.file_stem() { 382 - Some(stem) => stem.to_string_lossy().into_owned(), 383 - None => "".to_string(), 384 - }; 385 - tag.set_title(&file_stem); 386 - tag_changed = true; 387 - file_stem 388 - } 389 - }; 390 - let artist = tag.artist(); 391 - 392 - let tracks_dir = &data.paths.tracks_dir; 393 - let filename = generate_filename(tracks_dir, artist.unwrap_or(""), &title, "mp3"); 394 - let dest_path = tracks_dir.join(&filename); 395 - 396 - match fs::copy(track_path, &dest_path) { 397 - Ok(_) => (), 398 - Err(e) => throw!("Error copying file: {e}"), 399 - }; 400 - 401 - if tag_changed { 402 - match tag.write_to_path(&dest_path, id3::Version::Id3v24) { 403 - Ok(_) => (), 404 - Err(e) => throw!("Unable to tag file {}: {e}", dest_path.to_string_lossy()), 405 - }; 406 - // manually set date_modified because the date_modified doens't seem to 407 - // immediately update after tag.write_to_path(). 408 - date_modified = now; 409 - } 410 - 411 - let track = Track { 412 - size: file_md.len() as i64, 413 - duration, 414 - bitrate, 415 - sampleRate: sample_rate, 416 - file: filename, 417 - dateModified: date_modified, 418 - dateAdded: now, 419 - name: title.to_string(), 420 - importedFrom: None, 421 - originalId: None, 422 - artist: artist.unwrap_or_default().to_string(), 423 - composer: get_first_text_id3(&tag, "TCOM"), 424 - sortName: get_first_text_id3(&tag, "TSOT"), 425 - sortArtist: get_first_text_id3(&tag, "TSOP"), 426 - sortComposer: get_first_text_id3(&tag, "TSOC"), 427 - genre: tag.genre().map(|s| s.to_owned()), 428 - rating: None, 429 - year, 430 - bpm: match get_first_text_id3(&tag, "TBPM") { 431 - Some(n) => n.parse().ok(), 432 - None => None, 433 - }, 434 - comments: tag.comments().next().map(|c| c.text.clone()), 435 - grouping: get_first_text_id3(&tag, "GRP1"), 436 - liked: None, 437 - disliked: None, 438 - disabled: None, 439 - compilation: None, 440 - albumName: tag.album().map(|s| s.to_owned()), 441 - albumArtist: tag.album_artist().map(|s| s.to_owned()), 442 - sortAlbumName: get_first_text_id3(&tag, "TSOA"), 443 - sortAlbumArtist: get_first_text_id3(&tag, "TSO2"), 444 - trackNum: tag.track(), 445 - trackCount: tag.total_tracks(), 446 - discNum: tag.disc(), 447 - discCount: tag.total_discs(), 448 - dateImported: None, 449 - playCount: None, 450 - plays: None, 451 - playsImported: None, 452 - skipCount: None, 453 - skips: None, 454 - skipsImported: None, 455 - volume: None, 456 - }; 457 - Ok(track) 458 - }
+7 -12
src-native/tracks/mod.rs
··· 166 166 pub fn import_file(path: String, now: MsSinceUnixEpoch, env: Env) -> Result<()> { 167 167 let data: &mut Data = get_data(&env)?; 168 168 let id = data.library.generate_id(); 169 - let track = import::import_auto(&data, Path::new(&path), now)?; 169 + let track = import::import(&data, Path::new(&path), now)?; 170 170 data.library.tracks.insert(id, track); 171 171 Ok(()) 172 172 } ··· 222 222 let path = data.paths.tracks_dir.join(path_str); 223 223 match &mut data.current_tag { 224 224 Some(tag) => { 225 - let ext = path.extension().unwrap_or_default().to_string_lossy(); 226 - let mime_type = match ext.as_ref() { 227 - "jpg" | "jpeg" => "image/jpeg".to_string(), 228 - "png" => "image/png".to_string(), 229 - "bmp" => "image/bmp".to_string(), 230 - ext => throw!("Unsupported file type: {}", ext), 231 - }; 232 225 let new_bytes = match fs::read(&path) { 233 226 Ok(b) => b, 234 227 Err(e) => throw!("Error reading that file: {}", e), 235 228 }; 236 - tag.set_image(index as usize, new_bytes, &mime_type)?; 229 + tag.set_image(index as usize, new_bytes)?; 237 230 } 238 231 None => throw!("No tag loaded"), 239 232 }; ··· 242 235 243 236 #[napi(js_name = "set_image_data")] 244 237 #[allow(dead_code)] 245 - pub fn set_image_data(index: u32, bytes: JsArrayBuffer, mime_type: String, env: Env) -> Result<()> { 238 + pub fn set_image_data(index: u32, bytes: JsArrayBuffer, env: Env) -> Result<()> { 246 239 let bytes: Vec<u8> = bytes.into_value()?.to_vec(); 247 240 let data: &mut Data = get_data(&env)?; 248 241 match &mut data.current_tag { 249 - Some(tag) => tag.set_image(index as usize, bytes, &mime_type)?, 242 + Some(tag) => tag.set_image(index as usize, bytes)?, 250 243 None => throw!("No tag loaded"), 251 244 }; 252 245 Ok(()) ··· 257 250 pub fn remove_image(index: u32, env: Env) -> Result<()> { 258 251 let data: &mut Data = get_data(&env)?; 259 252 match data.current_tag { 260 - Some(ref mut tag) => tag.remove_image(index as usize), 253 + Some(ref mut tag) => { 254 + tag.remove_image(index as usize); 255 + } 261 256 None => {} 262 257 }; 263 258 Ok(())
+100 -481
src-native/tracks/tag.rs
··· 1 1 use crate::{UniError, UniResult}; 2 - use id3::{self, TagLike}; 2 + use lofty::picture::Picture; 3 + use lofty::tag::ItemKey; 3 4 use lofty::{file::TaggedFileExt, tag::Accessor, tag::TagExt}; 4 5 use std::io::Cursor; 5 6 use std::path::{Path, PathBuf}; ··· 27 28 } 28 29 } 29 30 30 - pub fn id3_timestamp_from_year(year: i32) -> id3::Timestamp { 31 - id3::Timestamp { 32 - year, 33 - month: None, 34 - day: None, 35 - hour: None, 36 - minute: None, 37 - second: None, 38 - } 39 - } 40 - 41 31 pub struct Image { 42 32 // i64 because napi doesn't support u64 43 33 pub index: i64, ··· 53 43 pub data: &'a [u8], 54 44 } 55 45 56 - pub enum Tag { 57 - Id3(id3::Tag), 58 - Mp4(mp4ameta::Tag), 59 - Lofty(lofty::tag::Tag), 46 + pub struct Tag { 47 + tag: lofty::tag::Tag, 60 48 } 61 49 impl Tag { 62 50 pub fn read_from_path(path: &PathBuf) -> UniResult<Tag> { ··· 66 54 let ext = path.extension().unwrap_or_default().to_string_lossy(); 67 55 68 56 let tag = match ext.as_ref() { 69 - "mp3" => { 70 - let tag = match id3::Tag::read_from_path(path) { 71 - Ok(tag) => tag, 72 - Err(_) => id3::Tag::new(), 73 - }; 74 - Tag::Id3(tag) 75 - } 76 - "m4a" => { 77 - let tag = match mp4ameta::Tag::read_from_path(path) { 78 - Ok(tag) => tag, 79 - Err(e) => match e.kind { 80 - mp4ameta::ErrorKind::NoTag => { 81 - throw!("No m4a tags found in file. Auto creating m4a tags is not yet supported") 82 - } 83 - _ => { 84 - throw!("Error reading m4a tags: {}", e) 85 - } 86 - }, 87 - }; 88 - Tag::Mp4(tag) 89 - } 90 - "opus" => { 57 + "mp3" | "m4a" | "opus" => { 91 58 let probe = match lofty::probe::Probe::open(path) { 92 59 Ok(f) => { 93 60 let parse_options = lofty::config::ParseOptions::new() ··· 103 70 Err(e) => throw!("Unable to read file: {}", e), 104 71 }; 105 72 106 - let tag = match tagged_file.remove(lofty::tag::TagType::VorbisComments) { 107 - Some(t) => t, 108 - None => lofty::tag::Tag::new(lofty::tag::TagType::VorbisComments), 73 + let tag = match tagged_file.remove(tagged_file.primary_tag_type()) { 74 + Some(t) => t.clone(), 75 + None => lofty::tag::Tag::new(tagged_file.primary_tag_type()), 109 76 }; 110 - Tag::Lofty(tag) 77 + 78 + Tag { tag } 111 79 } 112 80 _ => throw!("Unsupported file extension: {}", ext), 113 81 }; 114 82 Ok(tag) 115 83 } 116 84 pub fn write_to_path(&mut self, path: &Path) -> UniResult<()> { 117 - match self { 118 - Tag::Id3(tag) => { 119 - match tag.write_to_path(path, id3::Version::Id3v24) { 120 - Ok(_) => {} 121 - Err(e) => throw!("Unable to tag file: {}", e.description), 122 - }; 123 - } 124 - Tag::Mp4(tag) => { 125 - match tag.write_to_path(path) { 126 - Ok(_) => {} 127 - Err(e) => throw!("Unable to tag file: {}", e.description), 128 - }; 129 - } 130 - Tag::Lofty(tag) => { 131 - match tag.save_to_path(path, lofty::config::WriteOptions::default()) { 132 - Ok(_) => {} 133 - Err(e) => throw!("Unable to tag file: {}", e), 134 - }; 135 - } 85 + match self 86 + .tag 87 + .save_to_path(path, lofty::config::WriteOptions::default()) 88 + { 89 + Ok(_) => {} 90 + Err(e) => throw!("Unable to tag file: {}", e), 136 91 }; 137 92 Ok(()) 138 93 } 139 94 pub fn remove_title(&mut self) { 140 - match self { 141 - Tag::Id3(tag) => tag.remove_title(), 142 - Tag::Mp4(tag) => tag.remove_title(), 143 - Tag::Lofty(tag) => tag.remove_title(), 144 - } 95 + self.tag.remove_title() 145 96 } 146 97 pub fn set_title(&mut self, value: &str) { 147 - match self { 148 - Tag::Id3(tag) => tag.set_title(value), 149 - Tag::Mp4(tag) => tag.set_title(value), 150 - Tag::Lofty(tag) => tag.set_title(value.to_string()), 151 - } 98 + self.tag.set_title(value.to_string()) 152 99 } 153 100 pub fn remove_artists(&mut self) { 154 - match self { 155 - Tag::Id3(tag) => tag.remove_artist(), 156 - Tag::Mp4(tag) => tag.remove_artists(), 157 - Tag::Lofty(tag) => tag.remove_artist(), 158 - } 101 + self.tag.remove_artist() 159 102 } 160 103 pub fn set_artist(&mut self, value: &str) { 161 - match self { 162 - Tag::Id3(tag) => tag.set_artist(value), 163 - Tag::Mp4(tag) => tag.set_artist(value), 164 - Tag::Lofty(tag) => tag.set_artist(value.to_string()), 165 - } 104 + self.tag.set_artist(value.to_string()) 166 105 } 167 106 pub fn remove_album(&mut self) { 168 - match self { 169 - Tag::Id3(tag) => tag.remove_album(), 170 - Tag::Mp4(tag) => tag.remove_album(), 171 - Tag::Lofty(tag) => tag.remove_album(), 172 - } 107 + self.tag.remove_album() 173 108 } 174 109 pub fn set_album(&mut self, value: &str) { 175 - match self { 176 - Tag::Id3(tag) => tag.set_album(value), 177 - Tag::Mp4(tag) => tag.set_album(value), 178 - Tag::Lofty(tag) => tag.set_album(value.to_string()), 179 - } 110 + self.tag.set_album(value.to_string()) 180 111 } 181 112 pub fn remove_album_artists(&mut self) { 182 - match self { 183 - Tag::Id3(tag) => tag.remove_album_artist(), 184 - Tag::Mp4(tag) => tag.remove_album_artists(), 185 - Tag::Lofty(tag) => { 186 - let _ = tag.remove_key(&lofty::tag::ItemKey::AlbumArtist); 187 - } 188 - } 113 + self.tag.remove_key(&ItemKey::AlbumArtist); 189 114 } 190 115 pub fn set_album_artist(&mut self, value: &str) { 191 - match self { 192 - Tag::Id3(tag) => tag.set_album_artist(value), 193 - Tag::Mp4(tag) => tag.set_album_artist(value), 194 - Tag::Lofty(tag) => { 195 - let inserted = tag.insert_text(lofty::tag::ItemKey::AlbumArtist, value.to_string()); 196 - assert!(inserted, "Failed to set album artist"); 197 - } 198 - } 116 + let inserted = self 117 + .tag 118 + .insert_text(ItemKey::AlbumArtist, value.to_string()); 119 + assert!(inserted, "Failed to set album artist"); 199 120 } 200 121 pub fn remove_composers(&mut self) { 201 - match self { 202 - Tag::Id3(tag) => { 203 - tag.remove("TCOM"); 204 - } 205 - Tag::Mp4(tag) => tag.remove_composers(), 206 - Tag::Lofty(tag) => { 207 - let _ = tag.remove_key(&lofty::tag::ItemKey::Composer); 208 - } 209 - } 122 + self.tag.remove_key(&ItemKey::Composer); 210 123 } 211 124 pub fn set_composer(&mut self, value: &str) { 212 - match self { 213 - Tag::Id3(tag) => tag.set_text("TCOM", value), 214 - Tag::Mp4(tag) => tag.set_composer(value), 215 - Tag::Lofty(tag) => { 216 - let inserted = tag.insert_text(lofty::tag::ItemKey::Composer, value.to_string()); 217 - assert!(inserted, "Failed to set composer"); 218 - } 219 - } 125 + let inserted = self.tag.insert_text(ItemKey::Composer, value.to_string()); 126 + assert!(inserted, "Failed to set composer"); 220 127 } 221 128 pub fn remove_groupings(&mut self) { 222 - match self { 223 - Tag::Id3(tag) => { 224 - tag.remove("GRP1"); 225 - } 226 - Tag::Mp4(tag) => tag.remove_groupings(), 227 - Tag::Lofty(tag) => { 228 - let _ = tag.remove_key(&lofty::tag::ItemKey::ContentGroup); 229 - } 230 - } 129 + self.tag.remove_key(&ItemKey::ContentGroup) 231 130 } 232 131 pub fn set_grouping(&mut self, value: &str) { 233 - match self { 234 - Tag::Id3(tag) => tag.set_text("GRP1", value), 235 - Tag::Mp4(tag) => tag.set_grouping(value), 236 - Tag::Lofty(tag) => { 237 - let inserted = 238 - tag.insert_text(lofty::tag::ItemKey::ContentGroup, value.to_string()); 239 - assert!(inserted, "Failed to set grouping"); 240 - } 241 - } 132 + let inserted = self 133 + .tag 134 + .insert_text(ItemKey::ContentGroup, value.to_string()); 135 + assert!(inserted, "Failed to set grouping"); 242 136 } 243 137 pub fn remove_genres(&mut self) { 244 - match self { 245 - Tag::Id3(tag) => tag.remove_genre(), 246 - Tag::Mp4(tag) => tag.remove_genres(), 247 - Tag::Lofty(tag) => tag.remove_genre(), 248 - } 138 + self.tag.remove_genre() 249 139 } 250 140 pub fn set_genre(&mut self, value: &str) { 251 - match self { 252 - Tag::Id3(tag) => tag.set_genre(value), 253 - Tag::Mp4(tag) => tag.set_genre(value), 254 - Tag::Lofty(tag) => tag.set_genre(value.to_string()), 255 - } 141 + self.tag.set_genre(value.to_string()) 256 142 } 257 143 pub fn remove_year(&mut self) { 258 - match self { 259 - Tag::Id3(tag) => { 260 - tag.remove_year(); 261 - tag.remove_date_recorded(); 262 - } 263 - Tag::Mp4(tag) => tag.remove_year(), 264 - Tag::Lofty(tag) => tag.remove_year(), 265 - } 144 + self.tag.remove_year() 266 145 } 267 146 pub fn set_year(&mut self, value: i32) { 268 - match self { 269 - Tag::Id3(tag) => { 270 - tag.set_year(value); 271 - tag.set_date_recorded(id3_timestamp_from_year(value)); 272 - } 273 - Tag::Mp4(tag) => tag.set_year(value.to_string()), 274 - Tag::Lofty(tag) => { 275 - let u = if value < 0 { 0 } else { value as u32 }; 276 - tag.set_year(u); 277 - } 278 - } 147 + let u = if value < 0 { 0 } else { value as u32 }; 148 + self.tag.set_year(u); 279 149 } 280 150 /// For some tag types, `total` cannot exist without `number`. In those 281 151 /// cases, `total` is assumed to be `None`. ··· 284 154 number: Option<u32>, 285 155 total: Option<u32>, 286 156 ) -> Result<(), SetInfoError> { 287 - match self { 288 - Tag::Id3(tag) => match (number, total) { 289 - (Some(number), Some(total)) => { 290 - tag.set_track(number); 291 - tag.set_total_tracks(total); 292 - } 293 - (Some(number), None) => { 294 - tag.remove_total_tracks(); 295 - tag.set_track(number); 296 - } 297 - (None, Some(_)) => { 298 - return Err(SetInfoError::NumberRequired); 299 - } 300 - (None, None) => { 301 - tag.remove_total_tracks(); 302 - tag.remove_track(); 303 - } 304 - }, 305 - Tag::Mp4(tag) => { 306 - if number.unwrap_or(0) > u16::MAX as u32 { 307 - let msg = "Track number too large for this file".to_string(); 308 - return Err(SetInfoError::Other(msg)); 309 - } 310 - if total.unwrap_or(0) > u16::MAX as u32 { 311 - let msg = "Total tracks too large for this file".to_string(); 312 - return Err(SetInfoError::Other(msg)); 313 - } 314 - match number { 315 - Some(number) => tag.set_track_number(number as u16), 316 - None => tag.remove_track_number(), 317 - } 318 - match total { 319 - Some(total) => tag.set_total_tracks(total as u16), 320 - None => tag.remove_total_tracks(), 321 - } 322 - } 323 - Tag::Lofty(tag) => { 324 - match number { 325 - Some(number) => tag.set_track(number), 326 - None => tag.remove_track(), 327 - } 328 - match total { 329 - Some(total) => tag.set_track_total(total), 330 - None => tag.remove_track_total(), 331 - } 332 - } 157 + match number { 158 + Some(number) => self.tag.set_track(number), 159 + None => self.tag.remove_track(), 160 + } 161 + match total { 162 + Some(total) => self.tag.set_track_total(total), 163 + None => self.tag.remove_track_total(), 333 164 } 165 + 334 166 Ok(()) 335 167 } 336 168 /// For some tag types, `total` cannot exist without `number`. In those ··· 340 172 number: Option<u32>, 341 173 total: Option<u32>, 342 174 ) -> Result<(), SetInfoError> { 343 - match self { 344 - Tag::Id3(tag) => match (number, total) { 345 - (Some(number), Some(total)) => { 346 - tag.set_disc(number); 347 - tag.set_total_discs(total); 348 - } 349 - (Some(number), None) => { 350 - tag.remove_total_discs(); 351 - tag.set_disc(number); 352 - } 353 - (None, Some(_)) => { 354 - return Err(SetInfoError::NumberRequired); 355 - } 356 - (None, None) => { 357 - tag.remove_total_discs(); 358 - tag.remove_disc(); 359 - } 360 - }, 361 - Tag::Mp4(tag) => { 362 - if number.unwrap_or(0) > u16::MAX as u32 { 363 - let msg = "Disc number too large for this file".to_string(); 364 - return Err(SetInfoError::Other(msg)); 365 - } 366 - if total.unwrap_or(0) > u16::MAX as u32 { 367 - let msg = "Total discs too large for this file".to_string(); 368 - return Err(SetInfoError::Other(msg)); 369 - } 370 - match number { 371 - Some(number) => tag.set_disc_number(number as u16), 372 - None => tag.remove_disc_number(), 373 - } 374 - match total { 375 - Some(total) => tag.set_total_discs(total as u16), 376 - None => tag.remove_total_discs(), 377 - } 378 - } 379 - Tag::Lofty(tag) => { 380 - match number { 381 - Some(number) => tag.set_disk(number), 382 - None => tag.remove_disk(), 383 - } 384 - match total { 385 - Some(total) => tag.set_disk_total(total), 386 - None => tag.remove_disk_total(), 387 - } 388 - } 175 + match number { 176 + Some(number) => self.tag.set_disk(number), 177 + None => self.tag.remove_disk(), 178 + } 179 + match total { 180 + Some(total) => self.tag.set_disk_total(total), 181 + None => self.tag.remove_disk_total(), 389 182 } 183 + 390 184 Ok(()) 391 185 } 392 186 pub fn remove_bpm(&mut self) { 393 - match self { 394 - Tag::Id3(tag) => { 395 - tag.remove("TBPM"); 396 - } 397 - Tag::Mp4(tag) => tag.remove_bpm(), 398 - Tag::Lofty(tag) => { 399 - let _ = tag.remove_key(&lofty::tag::ItemKey::Bpm); 400 - } 401 - }; 187 + self.tag.remove_key(&ItemKey::Bpm); 402 188 } 403 189 pub fn set_bpm(&mut self, value: u16) { 404 - match self { 405 - Tag::Id3(tag) => { 406 - tag.set_text("TBPM", value.to_string()); 407 - } 408 - Tag::Mp4(tag) => { 409 - tag.set_bpm(value); 410 - } 411 - Tag::Lofty(tag) => { 412 - let inserted = tag.insert_text(lofty::tag::ItemKey::Bpm, value.to_string()); 413 - assert!(inserted, "Failed to set BPM"); 414 - } 415 - }; 190 + let mut inserted = self.tag.insert_text(ItemKey::Bpm, value.to_string()); 191 + if !inserted { 192 + inserted = self.tag.insert_text(ItemKey::IntegerBpm, value.to_string()); 193 + } 194 + assert!(inserted, "Failed to set BPM"); 416 195 } 417 196 pub fn remove_comments(&mut self) { 418 - match self { 419 - Tag::Id3(tag) => { 420 - tag.remove("COMM"); 421 - } 422 - Tag::Mp4(tag) => tag.remove_comments(), 423 - Tag::Lofty(tag) => tag.remove_comment(), 424 - }; 197 + self.tag.remove_comment() 425 198 } 426 199 pub fn set_comment(&mut self, value: &str) { 427 - match self { 428 - Tag::Id3(tag) => { 429 - tag.remove_comment(None, None); 430 - tag.add_frame(id3::frame::Comment { 431 - lang: "eng".to_string(), 432 - description: "".to_string(), 433 - text: value.to_string(), 434 - }); 435 - } 436 - Tag::Mp4(tag) => tag.set_comment(value), 437 - Tag::Lofty(tag) => tag.set_comment(value.to_string()), 438 - } 200 + self.tag.set_comment(value.to_string()) 439 201 } 440 - pub fn set_image(&mut self, index: usize, data: Vec<u8>, mime_type: &str) -> UniResult<()> { 441 - match self { 442 - Tag::Id3(tag) => { 443 - let mut pic_frames: Vec<_> = tag 444 - .frames() 445 - .filter(|frame| frame.content().picture().is_some()) 446 - .cloned() 447 - .collect(); 448 - let mut new_pic = id3::frame::Picture { 449 - mime_type: match mime_type { 450 - "image/jpeg" | "image/png" => mime_type.to_string(), 451 - ext => throw!("Unsupported file type: {}", ext), 452 - }, 453 - picture_type: id3::frame::PictureType::Other, 454 - description: "".to_string(), 455 - data, 456 - }; 457 - match pic_frames.get_mut(index) { 458 - Some(old_frame) => { 459 - let old_pic = old_frame.content().picture().unwrap(); 460 - new_pic.picture_type = old_pic.picture_type; 461 - new_pic.description = old_pic.description.clone(); 462 - let new_frame = 463 - id3::Frame::with_content("APIC", id3::Content::Picture(new_pic)); 464 - *old_frame = new_frame; 465 - } 466 - None => { 467 - if index == pic_frames.len() { 468 - let new_frame = 469 - id3::Frame::with_content("APIC", id3::Content::Picture(new_pic)); 470 - pic_frames.insert(index, new_frame); 471 - } else { 472 - throw!("Index out of range"); 473 - } 474 - } 475 - } 476 - tag.remove_all_pictures(); 477 - for pic_frame in pic_frames { 478 - tag.add_frame(pic_frame); 479 - } 480 - } 481 - Tag::Mp4(tag) => { 482 - let mut artworks: Vec<_> = tag.take_artworks().collect(); 483 - let new_artwork = mp4ameta::Img { 484 - fmt: match mime_type { 485 - "image/jpeg" => mp4ameta::ImgFmt::Jpeg, 486 - "image/png" => mp4ameta::ImgFmt::Png, 487 - "image/bmp" => mp4ameta::ImgFmt::Bmp, 488 - ext => throw!("Unsupported file type: {}", ext), 489 - }, 490 - data, 491 - }; 492 - match artworks.get_mut(index) { 493 - Some(artwork) => { 494 - *artwork = new_artwork; 495 - } 496 - None => { 497 - if index == artworks.len() { 498 - artworks.push(new_artwork); 499 - } else { 500 - throw!("Index out of range"); 501 - } 502 - } 503 - } 504 - tag.set_artworks(artworks); 505 - } 506 - Tag::Lofty(tag) => { 507 - let mut reader = Cursor::new(data); 508 - let picture = match lofty::picture::Picture::from_reader(&mut reader) { 509 - Ok(picture) => picture, 510 - Err(e) => throw!("Unable to read picture: {}", e), 511 - }; 512 - match picture.mime_type() { 513 - Some(lofty::picture::MimeType::Png | lofty::picture::MimeType::Jpeg) => { 514 - tag.set_picture(index, picture); 515 - } 516 - _ => throw!("Unsupported picture type"), 517 - } 202 + pub fn set_image(&mut self, index: usize, data: Vec<u8>) -> UniResult<()> { 203 + let mut reader = Cursor::new(data); 204 + let picture = match lofty::picture::Picture::from_reader(&mut reader) { 205 + Ok(picture) => picture, 206 + Err(e) => throw!("Unable to read picture: {}", e), 207 + }; 208 + match picture.mime_type() { 209 + Some(lofty::picture::MimeType::Png | lofty::picture::MimeType::Jpeg) => { 210 + self.tag.set_picture(index, picture); 518 211 } 212 + _ => throw!("Unsupported picture type"), 519 213 } 214 + 520 215 Ok(()) 521 216 } 522 217 pub fn get_image_ref(&self, index: usize) -> Option<ImageRef> { 523 - match self { 524 - Tag::Id3(tag) => match tag.pictures().nth(index) { 525 - Some(pic) => Some(ImageRef { 218 + let pictures = self.tag.pictures(); 219 + match pictures.get(index) { 220 + Some(pic) => { 221 + let data = pic.data(); 222 + Some(ImageRef { 526 223 index: index.try_into().expect("usize conv"), 527 - total_images: tag.pictures().count().try_into().expect("usize conv"), 528 - data: &pic.data, 529 - mime_type: pic.mime_type.clone(), 530 - }), 531 - None => None, 532 - }, 533 - Tag::Mp4(tag) => match tag.artworks().nth(index) { 534 - Some(artwork) => Some(ImageRef { 535 - index: index.try_into().expect("usize conv"), 536 - total_images: tag.artworks().count().try_into().expect("usize conv"), 537 - data: artwork.data, 538 - mime_type: match artwork.fmt { 539 - mp4ameta::ImgFmt::Bmp => "image/bmp".to_string(), 540 - mp4ameta::ImgFmt::Jpeg => "image/jpeg".to_string(), 541 - mp4ameta::ImgFmt::Png => "image/png".to_string(), 542 - }, 543 - }), 544 - None => None, 545 - }, 546 - Tag::Lofty(tag) => { 547 - let pictures = tag.pictures(); 548 - match pictures.get(index) { 549 - Some(pic) => { 550 - let data = pic.data(); 551 - Some(ImageRef { 552 - index: index.try_into().expect("usize conv"), 553 - total_images: pictures.len().try_into().expect("usize conv"), 554 - data, 555 - mime_type: match pic.mime_type() { 556 - Some(mime_type) => mime_type.to_string(), 557 - _ => return None, 558 - }, 559 - }) 560 - } 561 - None => None, 562 - } 563 - } 564 - } 565 - } 566 - pub fn get_image_consume(self, index: usize) -> Option<Image> { 567 - match self { 568 - Tag::Id3(tag) => match tag.pictures().nth(index) { 569 - Some(pic) => Some(Image { 570 - index: index.try_into().expect("usize conv"), 571 - total_images: tag.pictures().count().try_into().expect("usize conv"), 572 - data: pic.data.to_vec(), 573 - mime_type: pic.mime_type.clone(), 574 - }), 575 - None => None, 576 - }, 577 - Tag::Mp4(mut tag) => { 578 - let total_images = tag.artworks().count().try_into().expect("usize conv"); 579 - match tag.take_artworks().nth(index) { 580 - Some(artwork) => Some(Image { 581 - index: index.try_into().expect("usize conv"), 582 - total_images, 583 - data: artwork.data, 584 - mime_type: match artwork.fmt { 585 - mp4ameta::ImgFmt::Bmp => "image/bmp".to_string(), 586 - mp4ameta::ImgFmt::Jpeg => "image/jpeg".to_string(), 587 - mp4ameta::ImgFmt::Png => "image/png".to_string(), 588 - }, 589 - }), 590 - None => None, 591 - } 592 - } 593 - Tag::Lofty(mut tag) => { 594 - let pictures_len = tag.pictures().len(); 595 - if tag.picture_count() <= index.try_into().expect("usize conv") { 596 - return None; 597 - } 598 - let pic = tag.remove_picture(index); 599 - Some(Image { 600 - index: index.try_into().expect("usize conv"), 601 - total_images: pictures_len.try_into().expect("usize conv"), 224 + total_images: pictures.len().try_into().expect("usize conv"), 225 + data, 602 226 mime_type: match pic.mime_type() { 603 227 Some(mime_type) => mime_type.to_string(), 604 228 _ => return None, 605 229 }, 606 - data: pic.into_data(), 607 230 }) 608 231 } 232 + None => None, 609 233 } 610 234 } 611 - pub fn remove_image(&mut self, index: usize) { 612 - match self { 613 - Tag::Id3(ref mut tag) => { 614 - let mut pic_frames: Vec<_> = tag 615 - .frames() 616 - .filter(|frame| frame.content().picture().is_some()) 617 - .cloned() 618 - .collect(); 619 - pic_frames.remove(index); 620 - tag.remove_all_pictures(); 621 - for pic_frame in pic_frames { 622 - tag.add_frame(pic_frame); 623 - } 624 - } 625 - Tag::Mp4(ref mut tag) => { 626 - let mut artworks: Vec<_> = tag.take_artworks().collect(); 627 - artworks.remove(index); 628 - tag.set_artworks(artworks); 629 - } 630 - Tag::Lofty(tag) => { 631 - tag.remove_picture(index); 632 - } 235 + pub fn get_image_consume(mut self, index: usize) -> Option<Image> { 236 + let pictures_len = self.tag.pictures().len(); 237 + if self.tag.picture_count() <= index.try_into().expect("usize conv") { 238 + return None; 633 239 } 240 + let pic = self.tag.remove_picture(index); 241 + Some(Image { 242 + index: index.try_into().expect("usize conv"), 243 + total_images: pictures_len.try_into().expect("usize conv"), 244 + mime_type: match pic.mime_type() { 245 + Some(mime_type) => mime_type.to_string(), 246 + _ => return None, 247 + }, 248 + data: pic.into_data(), 249 + }) 250 + } 251 + pub fn remove_image(&mut self, index: usize) -> Picture { 252 + self.tag.remove_picture(index) 634 253 } 635 254 }
+3 -3
src/components/TrackInfo.svelte
··· 292 292 image_edited = true 293 293 load_image(image?.index || 0) 294 294 } 295 - function replace_cover_data(data: ArrayBuffer, mime_type: string) { 296 - methods.setImageData(image?.index || 0, data, mime_type) 295 + function replace_cover_data(data: ArrayBuffer) { 296 + methods.setImageData(image?.index || 0, data) 297 297 image_edited = true 298 298 load_image(image?.index || 0) 299 299 } ··· 306 306 const reader = new FileReader() 307 307 reader.onload = (e) => { 308 308 if (e.target?.result && e.target.result instanceof ArrayBuffer) { 309 - replace_cover_data(e.target.result, file.type) 309 + replace_cover_data(e.target.result) 310 310 } 311 311 } 312 312 reader.readAsArrayBuffer(file)
+2 -2
src/lib/data.ts
··· 246 246 setImage: (index: number, path: string) => { 247 247 return call((data) => data.set_image(index, path)) 248 248 }, 249 - setImageData: (index: number, bytes: ArrayBuffer, mime_type: string) => { 250 - return call((data) => data.set_image_data(index, bytes, mime_type)) 249 + setImageData: (index: number, bytes: ArrayBuffer) => { 250 + return call((data) => data.set_image_data(index, bytes)) 251 251 }, 252 252 removeImage: (index: number) => { 253 253 return call((data) => data.remove_image(index))