From f5e343f3863e5ba33bd1194479353df6c5462d58 Mon Sep 17 00:00:00 2001 From: Raleigh Littles Date: Wed, 27 Nov 2024 12:32:56 -0700 Subject: [PATCH] added song renamer tool --- README.md | 10 + docs/Spotify_Primary_Logo_RGB_Black.png | Bin 0 -> 23442 bytes parser/src/parsers/itunesdb_parser.rs | 190 ++++++++---------- song_file_renamer/.gitignore | 166 +++++++++++++++ song_file_renamer/README.md | 62 ++++++ .../rename_songs_from_itunesdb.py | 57 ++++++ 6 files changed, 382 insertions(+), 103 deletions(-) create mode 100644 docs/Spotify_Primary_Logo_RGB_Black.png create mode 100644 song_file_renamer/.gitignore create mode 100644 song_file_renamer/README.md create mode 100644 song_file_renamer/rename_songs_from_itunesdb.py diff --git a/README.md b/README.md index 66133b0..fa70eb9 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,16 @@ If you run it on a Photo Database file, you'll see the list of all images stored Equalizer settings CSV: ![Equalizer settings CSV screenshot](./docs/20241126_equalizer-csv-example.png) +# Extras + +There's 2 extra utilities that may be of use: + +Spotify logo + +* Spotify integration: This creates a Spotify playlist out of the songs that were found on your iPod. See the README in that directory for more. + +* Song renaming functionality: iPods (generally ?) store the song files on their hard drive, however, the filenames are usually just a generic unique ID. I wrote a Python script that lets you rename the songs to have the song title and artist name instead, using the data that is in the iTunesDB file. See the README in that directory for more information. + # Future roadmap This project is a very early work-in-progress. The next major feature to come is [iThumb file decoding](https://github.com/raleighlittles/iTunesDB-Parser/issues/4) diff --git a/docs/Spotify_Primary_Logo_RGB_Black.png b/docs/Spotify_Primary_Logo_RGB_Black.png new file mode 100644 index 0000000000000000000000000000000000000000..1fbb21817d327806239cebaab7181e8a63014477 GIT binary patch literal 23442 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 QhyEFN5dT}ze(#I_2bh({bpQYW literal 0 HcmV?d00001 diff --git a/parser/src/parsers/itunesdb_parser.rs b/parser/src/parsers/itunesdb_parser.rs index d0c7edd..e0e7bd9 100644 --- a/parser/src/parsers/itunesdb_parser.rs +++ b/parser/src/parsers/itunesdb_parser.rs @@ -6,9 +6,7 @@ use crate::itunesdb; use crate::helpers::helpers; use crate::helpers::itunesdb_helpers; - -pub fn parse_itunesdb_file(itunesdb_file_as_bytes : Vec) { - +pub fn parse_itunesdb_file(itunesdb_file_as_bytes: Vec) { let mut music_csv_writer = helpers::init_csv_writer("music.csv"); let mut podcast_csv_writer = helpers::init_csv_writer("podcasts.csv"); @@ -23,7 +21,8 @@ pub fn parse_itunesdb_file(itunesdb_file_as_bytes : Vec) { let mut idx = 0; while idx < (itunesdb_file_as_bytes.len() - itunesdb_constants::DEFAULT_SUBSTRUCTURE_SIZE) { - let potential_section_heading = &itunesdb_file_as_bytes[idx..idx + itunesdb_constants::DEFAULT_SUBSTRUCTURE_SIZE]; + let potential_section_heading = + &itunesdb_file_as_bytes[idx..idx + itunesdb_constants::DEFAULT_SUBSTRUCTURE_SIZE]; // Parse Database Object if potential_section_heading == itunesdb_constants::DATABASE_OBJECT_KEY.as_bytes() { @@ -52,7 +51,6 @@ pub fn parse_itunesdb_file(itunesdb_file_as_bytes : Vec) { } // Parse DataSet else if potential_section_heading == itunesdb_constants::DATASET_KEY.as_bytes() { - let dataset_type_raw = helpers::get_slice_from_offset_with_len( idx, &itunesdb_file_as_bytes, @@ -138,8 +136,7 @@ pub fn parse_itunesdb_file(itunesdb_file_as_bytes : Vec) { if helpers::build_le_u32_from_bytes(track_filetype_raw) == 0 { println!("Track Item file type missing. Is this is a 1st - 4th gen iPod?"); } else { - let track_item_extension = - itunesdb::decode_track_item_filetype(track_filetype_raw); + let track_item_extension = itunesdb::decode_track_item_filetype(track_filetype_raw); write!( track_item_info, "Track extension: '{}' | ", @@ -202,7 +199,6 @@ pub fn parse_itunesdb_file(itunesdb_file_as_bytes : Vec) { track_media_type_enum, itunesdb::HandleableMediaType::SongLike ) { - curr_media_type = track_media_type_enum; let track_advanced_audio_type = helpers::get_slice_as_le_u32( @@ -281,7 +277,6 @@ pub fn parse_itunesdb_file(itunesdb_file_as_bytes : Vec) { curr_song.bitrate_kbps = track_bitrate; curr_song.sample_rate_hz = track_sample_rate_hz; - let track_size_bytes = helpers::get_slice_as_le_u32( idx, &itunesdb_file_as_bytes, @@ -375,7 +370,11 @@ pub fn parse_itunesdb_file(itunesdb_file_as_bytes : Vec) { itunesdb_constants::TRACK_ITEM_TRACK_LAST_SKIPPED_TIMESTAMP_LEN, ); - let track_skip_when_shuffle_setting = &itunesdb_file_as_bytes[idx + itunesdb_constants::TRACK_ITEM_TRACK_SKIP_WHEN_SHUFFLING_SETTING_OFFSET .. idx + itunesdb_constants::TRACK_ITEM_TRACK_SKIP_WHEN_SHUFFLING_SETTING_OFFSET + itunesdb_constants::TRACK_ITEM_TRACK_SKIP_WHEN_SHUFFLING_SETTING_LEN]; + let track_skip_when_shuffle_setting = &itunesdb_file_as_bytes[idx + + itunesdb_constants::TRACK_ITEM_TRACK_SKIP_WHEN_SHUFFLING_SETTING_OFFSET + ..idx + + itunesdb_constants::TRACK_ITEM_TRACK_SKIP_WHEN_SHUFFLING_SETTING_OFFSET + + itunesdb_constants::TRACK_ITEM_TRACK_SKIP_WHEN_SHUFFLING_SETTING_LEN]; write!(track_item_info, "Play/Skip statistics: # of plays: {} , Last played on: {} | # of skips: {}, Last skipped on: {} (Skip when shuffling? {}) ", track_play_count, track_last_played_timestamp, track_skipped_count, track_last_skipped_timestamp, track_skip_when_shuffle_setting[0] ).unwrap(); @@ -432,7 +431,12 @@ pub fn parse_itunesdb_file(itunesdb_file_as_bytes : Vec) { ); if gapless_playback_setting_for_track == 1 { - let num_beginning_silence_samples = helpers::get_slice_as_le_u32(idx, &itunesdb_file_as_bytes, itunesdb_constants::TRACK_ITEM_TRACK_BEGINNING_SILENCE_SAMPLE_COUNT_OFFSET, itunesdb_constants::TRACK_ITEM_TRACK_BEGINNING_SILENCE_SAMPLE_COUNT_LEN); + let num_beginning_silence_samples = helpers::get_slice_as_le_u32( + idx, + &itunesdb_file_as_bytes, + itunesdb_constants::TRACK_ITEM_TRACK_BEGINNING_SILENCE_SAMPLE_COUNT_OFFSET, + itunesdb_constants::TRACK_ITEM_TRACK_BEGINNING_SILENCE_SAMPLE_COUNT_LEN, + ); let num_ending_silence_samples = helpers::get_slice_as_le_u32( idx, @@ -514,13 +518,6 @@ pub fn parse_itunesdb_file(itunesdb_file_as_bytes : Vec) { curr_song.song_year = track_year_released as u16; } - // let track_added_timestamp = helpers::get_slice_as_mac_timestamp( - // idx, - // &itunesdb_file_as_bytes, - // itunesdb_constants::TRACK_ITEM_TRACK_ADDED_TIMESTAMP_OFFSET, - // itunesdb_constants::TRACK_ITEM_TRACK_ADDED_TIMESTAMP_LEN, - // ); - let track_added_epoch = helpers::get_slice_as_le_u32( idx, &itunesdb_file_as_bytes, @@ -542,40 +539,50 @@ pub fn parse_itunesdb_file(itunesdb_file_as_bytes : Vec) { .unwrap(); } - let track_modified_timestamp = helpers::get_slice_as_mac_timestamp( + let track_modified_epoch = helpers::get_slice_as_le_u32( idx, &itunesdb_file_as_bytes, itunesdb_constants::TRACK_ITEM_TRACK_MODIFIED_TIME_OFFSET, itunesdb_constants::TRACK_ITEM_TRACK_MODIFIED_TIME_LEN, ); - let track_published_to_store_timestamp: chrono::DateTime = - helpers::get_slice_as_mac_timestamp( - idx, - &itunesdb_file_as_bytes, - itunesdb_constants::TRACK_ITEM_TRACK_RELEASED_TIMESTAMP_OFFSET, - itunesdb_constants::TRACK_ITEM_TRACK_RELEASED_TIMESTAMP_LEN, - ); - - write!( - track_item_info, - "Last modified: {} Published to iTunes store: {}", - track_modified_timestamp, track_published_to_store_timestamp - ) - .unwrap(); + if track_modified_epoch > 0 { + let track_modified_timestamp = + helpers::get_timestamp_as_mac(track_modified_epoch as u64); + write!( + track_item_info, + "Track last modified: {} | ", + track_modified_timestamp + ).unwrap(); + } - //println!("{} \n", track_item_info); - } + let track_published_to_store_epoch = helpers::get_slice_as_le_u32( + idx, + &itunesdb_file_as_bytes, + itunesdb_constants::TRACK_ITEM_TRACK_RELEASED_TIMESTAMP_OFFSET, + itunesdb_constants::TRACK_ITEM_TRACK_RELEASED_TIMESTAMP_LEN, + ); - else if matches!( - track_media_type_enum, - itunesdb::HandleableMediaType::Podcast) { + if track_published_to_store_epoch > 0 { + let track_published_to_store_timestamp: chrono::DateTime = + helpers::get_timestamp_as_mac(track_published_to_store_epoch as u64); - println!("TrackItem: Podcast found"); + write!( + track_item_info, + "Date published on iTunes: {}", + track_published_to_store_timestamp + ).unwrap(); + } - curr_media_type = track_media_type_enum; + println!("{} \n", track_item_info); + } else if matches!( + track_media_type_enum, + itunesdb::HandleableMediaType::Podcast + ) { + println!("TrackItem: Podcast found"); - } + curr_media_type = track_media_type_enum; + } idx += itunesdb_constants::TRACK_ITEM_LAST_OFFSET; } else if potential_section_heading == itunesdb_constants::PLAYLIST_KEY.as_bytes() { @@ -624,8 +631,7 @@ pub fn parse_itunesdb_file(itunesdb_file_as_bytes : Vec) { //println!("{} ====", playlist_info); idx += itunesdb_constants::PLAYLIST_LAST_OFFSET; - } else if potential_section_heading == itunesdb_constants::PLAYLIST_ITEM_KEY.as_bytes() - { + } else if potential_section_heading == itunesdb_constants::PLAYLIST_ITEM_KEY.as_bytes() { let mut playlist_item_info: String = "-----".to_string(); let playlist_item_added_timestamp = helpers::get_slice_as_mac_timestamp( @@ -711,10 +717,9 @@ pub fn parse_itunesdb_file(itunesdb_file_as_bytes : Vec) { ); // let data_object_str = std::str::from_utf8(&data_object_str_bytes).expect("Can't parse string data object!"); - let data_object_str = String::from_utf16(&helpers::return_utf16_from_utf8( - &data_object_str_bytes, - )) - .expect("Can't decode string to UTF-16"); + let data_object_str = + String::from_utf16(&helpers::return_utf16_from_utf8(&data_object_str_bytes)) + .expect("Can't decode string to UTF-16"); write!( data_object_info, @@ -724,59 +729,40 @@ pub fn parse_itunesdb_file(itunesdb_file_as_bytes : Vec) { .unwrap(); // We've found a title, now, use the TrackItem info to determine if the title is for a song or for a podcast - if data_object_type_raw == itunesdb::HandleableDataObjectType::Title as u32 - { + if data_object_type_raw == itunesdb::HandleableDataObjectType::Title as u32 { if curr_media_type == itunesdb::HandleableMediaType::SongLike { curr_song.song_title = data_object_str; - } - else if curr_media_type == itunesdb::HandleableMediaType::Podcast { + } else if curr_media_type == itunesdb::HandleableMediaType::Podcast { curr_podcast.podcast_title = data_object_str; } - } - else if data_object_type_raw - == itunesdb::HandleableDataObjectType::Album as u32 - { + } else if data_object_type_raw == itunesdb::HandleableDataObjectType::Album as u32 { curr_song.song_album = data_object_str; - - } else if data_object_type_raw - == itunesdb::HandleableDataObjectType::Artist as u32 + } else if data_object_type_raw == itunesdb::HandleableDataObjectType::Artist as u32 { if curr_media_type == itunesdb::HandleableMediaType::SongLike { curr_song.song_artist = data_object_str; - } - else if curr_media_type == itunesdb::HandleableMediaType::Podcast { + } else if curr_media_type == itunesdb::HandleableMediaType::Podcast { curr_podcast.podcast_publisher = data_object_str; } - - } else if data_object_type_raw - == itunesdb::HandleableDataObjectType::Genre as u32 - { + } else if data_object_type_raw == itunesdb::HandleableDataObjectType::Genre as u32 { if curr_media_type == itunesdb::HandleableMediaType::SongLike { curr_song.song_genre = data_object_str; - } - - else if curr_media_type == itunesdb::HandleableMediaType::Podcast { + } else if curr_media_type == itunesdb::HandleableMediaType::Podcast { if curr_podcast.podcast_genre.is_empty() { curr_podcast.podcast_genre = data_object_str; } } - - } else if data_object_type_raw - == itunesdb::HandleableDataObjectType::Comment as u32 + } else if data_object_type_raw == itunesdb::HandleableDataObjectType::Comment as u32 { if curr_media_type == itunesdb::HandleableMediaType::SongLike { - curr_song.song_comment = data_object_str; - } else if curr_media_type == itunesdb::HandleableMediaType::Podcast { curr_podcast.podcast_subtitle = data_object_str; } - } else if data_object_type_raw == itunesdb::HandleableDataObjectType::Composer as u32 { curr_song.song_composer = data_object_str; - } else if data_object_type_raw == itunesdb::HandleableDataObjectType::FileLocation as u32 { @@ -785,26 +771,21 @@ pub fn parse_itunesdb_file(itunesdb_file_as_bytes : Vec) { if curr_song.are_enough_fields_valid() { songs_found.push(curr_song); curr_song = itunesdb::Song::default(); - } - } - else if data_object_type_raw == itunesdb::HandleableDataObjectType::FileType as u32 { - + } else if data_object_type_raw + == itunesdb::HandleableDataObjectType::FileType as u32 + { if curr_media_type == itunesdb::HandleableMediaType::Podcast { - curr_podcast.podcast_file_type = data_object_str; } - - } - else if data_object_type_raw == itunesdb::HandleableDataObjectType::PodcastDescription as u32 { - + } else if data_object_type_raw + == itunesdb::HandleableDataObjectType::PodcastDescription as u32 + { if curr_media_type == itunesdb::HandleableMediaType::Podcast { - curr_podcast.podcast_description = data_object_str; } if !curr_podcast.podcast_title.is_empty() { - podcasts_found.push(curr_podcast); curr_podcast = itunesdb::Podcast::default(); } @@ -840,25 +821,28 @@ pub fn parse_itunesdb_file(itunesdb_file_as_bytes : Vec) { println!("{} songs found", songs_found.len()); - - podcast_csv_writer.write_record(&[ - "Episode Title", - "Publisher", - "Genre", - "Subtitle", - "Description", - "File Type" - ]).expect("Error can't create CSV file headers for podcast file"); + podcast_csv_writer + .write_record(&[ + "Episode Title", + "Publisher", + "Genre", + "Subtitle", + "Description", + "File Type", + ]) + .expect("Error can't create CSV file headers for podcast file"); for episode in podcasts_found.iter() { - podcast_csv_writer.write_record(&[ - episode.podcast_title.to_string(), - episode.podcast_publisher.to_string(), - episode.podcast_genre.to_string(), - episode.podcast_subtitle.to_string(), - episode.podcast_description.to_string().replace("\n", ""), - episode.podcast_file_type.to_string() - ]).expect("Can't write row to podcast CSV file"); + podcast_csv_writer + .write_record(&[ + episode.podcast_title.to_string(), + episode.podcast_publisher.to_string(), + episode.podcast_genre.to_string(), + episode.podcast_subtitle.to_string(), + episode.podcast_description.to_string().replace("\n", ""), + episode.podcast_file_type.to_string(), + ]) + .expect("Can't write row to podcast CSV file"); } music_csv_writer @@ -914,4 +898,4 @@ pub fn parse_itunesdb_file(itunesdb_file_as_bytes : Vec) { ]) .expect("Can't write row to CSV"); } -} \ No newline at end of file +} diff --git a/song_file_renamer/.gitignore b/song_file_renamer/.gitignore new file mode 100644 index 0000000..fbae690 --- /dev/null +++ b/song_file_renamer/.gitignore @@ -0,0 +1,166 @@ +# Don't check in audio files +*.mp3 +*.m4a + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file diff --git a/song_file_renamer/README.md b/song_file_renamer/README.md new file mode 100644 index 0000000..dd4b2fa --- /dev/null +++ b/song_file_renamer/README.md @@ -0,0 +1,62 @@ +# About + +This is a Python script that allows you to automatically rename songs on your iPod according to the info in the iTunesDB file, for recovery purposes. + +To start, you must've run the parser tool on your iTunesDB file (see README in repository root) and had it produce a CSV file. + +You will then copy the song files on your iPod to a separate directory (do NOT override/rename files on your actual iPod itself) on your host PC. + +```bash +$ find -name "*.mp3|*.m4a" -type f -exec cp {} ./ \; +``` + +Then run this script, passing in the path to the CSV file you generated, and the directory where the song files are. + +This script will rename those song files using the song title and song artist in the CSV file. + +ie.. + +``` +./RBCG.mp4 +./REXL.mp3 +./LVXD.mp3 +./NNMT.mp3 +./SEMY.mp3 +./MKIV.mp3 +./QPMR.mp3 +./NEWF.mp3 +./KSMR.mp3 +./TWZE.mp3 +``` + +can become: + +``` +./'Spit It Out - Slipknot.m4a' +./'Storming The Gates Of Hell - Impending Doom.mp3' +./'Strife (Chug Chug) - As Blood Runs Black.mp3' +./'Sulfur - Slipknot.mp3' +./'The Darkest Day Of Man - Whitechapel.mp3' +./'The Day Of Justice - Whitechapel.mp3' +./'There Is No Business To Be Done On A Dead Planet - As Blood Runs Black.mp3' +./'The Takeover - Born of Osiris.mp3' +./'The Tragic Truth - Five Finger Death Punch.mp3' +./'The True Beast - All Shall Perish.mp3' + +``` + +# Usage info + +``` +usage: Apply recovered song data to music files on an iPod [-h] -i SONG_DIRECTORY -c ITUNES_CSV -o OUTPUT_DIRECTORY + +options: + -h, --help show this help message and exit + -i SONG_DIRECTORY, --song-directory SONG_DIRECTORY + Directory to look for songs + -c ITUNES_CSV, --itunes-csv ITUNES_CSV + Path to iTunesDB CSV file + -o OUTPUT_DIRECTORY, --output-directory OUTPUT_DIRECTORY + Directory where to put renamed songs + +``` \ No newline at end of file diff --git a/song_file_renamer/rename_songs_from_itunesdb.py b/song_file_renamer/rename_songs_from_itunesdb.py new file mode 100644 index 0000000..5541fb4 --- /dev/null +++ b/song_file_renamer/rename_songs_from_itunesdb.py @@ -0,0 +1,57 @@ +import argparse +import os +import csv + +if __name__ == "__main__": + + argparse_parser = argparse.ArgumentParser( + "Apply recovered song data to music files on an iPod") + argparse_parser.add_argument( + "-i", "--song-directory", help="Directory to look for songs", required=True, type=str) + argparse_parser.add_argument( + "-c", "--itunes-csv", help="Path to iTunesDB CSV file", required=True) + argparse_parser.add_argument( + "-o", "--output-directory", help="Directory where to put renamed songs", required=True) + + argparse_args = argparse_parser.parse_args() + + if not os.path.exists(argparse_args.song_directory): + raise FileNotFoundError(f"Error! Can't find song directory '{ + argparse_args.song_directory}'") + + if not os.path.exists(argparse_args.itunes_csv): + raise FileNotFoundError(f"Error! Can't find iTunesDB csv file '{ + argparse_args.itunes_csv}', make sure you ran the parser") + + song_filenames_and_titles = dict() + + with open(argparse_args.itunes_csv, mode='r') as csv_file_obj: + csv_reader = csv.DictReader(csv_file_obj) + + for csv_row in csv_reader: + song_title = csv_row["Song Title"] + if song_title is None or song_title == "": + song_title = "UKNOWN_SONG_TITLE" + song_artist = csv_row["Artist"] + if song_artist is None or song_artist == "": + song_artist = "UNKNOWN_ARTIST" + song_filename = csv_row["Filename"] + + # Only care about the filename matching, not the whole directory path + song_filenames_and_titles[song_filename.split( + "/")[-1]] = f"{song_title} - {song_artist}.{song_filename.split(".")[-1]}" + + song_idx = 0 + + for candidate_file in os.listdir(argparse_args.song_directory): + candidate_filename = os.fsdecode(candidate_file) + + if candidate_filename.endswith(".m4a") or candidate_filename.endswith(".mp3"): + if candidate_filename in song_filenames_and_titles: + songs_new_filename = song_filenames_and_titles[candidate_filename] + # print(f"Renaming {candidate_filename} to {songs_new_filename}") + os.rename(os.path.join(argparse_args.song_directory, candidate_filename), os.path.join( + argparse_args.output_directory, songs_new_filename)) + song_idx += 1 + + print(f"[DEBUG] Finished renaming {song_idx} songs")