Skip to content

Commit

Permalink
Added better documentation. Updated README
Browse files Browse the repository at this point in the history
  • Loading branch information
mwdle committed Aug 18, 2024
1 parent 5a60657 commit a8b78f6
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 18 deletions.
38 changes: 34 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,49 @@

A small collection of basic tools and scripts for management of a local music library.

## Table of Contents

* [Description](#musictools)
* [M3U Playlist Generator](#playlist-generator)
* [Repair File/Folder Name Utility](#name-normalizer)
* [License](#license)
* [Disclaimer](#disclaimer)

## Playlist Generator

This script allows you to quickly generate an m3u playlist containing all music in a given folder and subfolders. Has support for excluding files/folders using regex.
This script allows you to quickly generate a .m3u playlist containing all music in a given folder and subfolders. Has support for excluding files/folders using regex.
To learn more, execute the following from a shell in the same folder as this README:
```python3 generatePlaylist.py --help``` or ```python3 generatePlaylist.py -h```

```shell
python3 generatePlaylist.py --help
```

or

```shell
python3 generatePlaylist.py -h
```

## Name Normalizer

Some song, album, and artist names have characters that don't play nicely with some systems, resulting in poor portability of your music library when syncing across devices.
This script automates the process of removing and replacing such characters from your music library.
To learn more, execute the following from a shell in the same folder as this README:
```python3 normalizeNames.py --help``` or ```python3 normalizeNames.py -h```

### Disclaimer
```shell
python3 normalizeNames.py --help
```

or

```shell
python3 normalizeNames.py -h
```

## License

This project is licensed under the GNU General Public License v3.0 (GPL-3.0). See the [LICENSE](LICENSE.txt) file for details.

## Disclaimer

This repository is provided as-is and is intended for informational and reference purposes only. The author assumes no responsibility for any errors or omissions in the content or for any consequences that may arise from the use of the information provided. Always exercise caution and seek professional advice if necessary.
18 changes: 14 additions & 4 deletions generatePlaylist.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
import os
import argparse
import re
from mutagen.mp3 import MP3
from mutagen.oggvorbis import OggVorbis
from mutagen.mp3 import MP3 # pip install mutagen
from mutagen.oggvorbis import OggVorbis # pip install mutagen

def get_duration(file_path):
def get_song_duration(file_path):
"""
Returns the duration in seconds of an mp3 or ogg file.
"""
audio = None;
if file_path.lower().endswith('.mp3'):
audio = MP3(file_path)
if file_path.lower().endswith('.ogg'):
audio = OggVorbis(file_path)
return audio.info.length

def generate_m3u_playlist(playlist_directory, music_directory, output_file, exclusions):
"""
Generates an m3u playlist with the given filename in the given directory.
The playlist file will contain relative paths and other information for all files with matching extensions in the given music directory, not including any provided exclusions.
"""
with open(f"{music_directory}{os.path.sep}{output_file}", 'w', encoding='utf-8') as playlist:
# Playlist Header
playlist.write("#EXTM3U\n")
# For every file with a matching extension, add a line to the playlist containing the song length, song name (assumes the file name excluding extension is the song name), and the relative path to the file.
for root, _, files in os.walk(playlist_directory):
for file in files:
if file.lower().endswith('.ogg') or file.lower().endswith('.mp3'):
song_path = os.path.join(root, file)
if exclusions != "" and re.search(exclusions, song_path):
continue
song_length = int(get_duration(song_path))
song_length = int(get_song_duration(song_path))
relative_path = os.path.relpath(song_path, music_directory)
playlist.write(f"#EXTINF:{song_length}, {file[:-4]}\n{relative_path}\n");

Expand Down
41 changes: 31 additions & 10 deletions normalizeNames.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
import os
import argparse

illegal_chars = {'?', ':', '"', '*', '!'}
ILLEGAL_CHARS = {'?', ':', '"', '*', '!'}
"""
Characters that must be replaced / removed from file/folder names in the given directory.
"""

def rename_item(root, old_path, new_name):
"""
Renames a file or folder and prints the change to the console.
"""
new_path = os.path.join(root, new_name)
if old_path != new_path:
os.rename(old_path, new_path)
print(f'Renamed item: {old_path} -> {new_path}')

def replace_colon(old_name, illegal_char):
"""
Custom logic for replacing a colon ':' with a dash '-' in a file/folder name.
If the colon is next to a space on only one side, when it is replaced with a dash a space is added to the opposite side for a more natural look.
If there are already spaces on both sides of the colon, no spaces are added or removed.
If there are no spaces on either side of the colon, no spaces are added.
"""
new_name = old_name
preceding_char = old_name.index(illegal_char)-1
succeeding_char = old_name.index(illegal_char)+1
Expand All @@ -22,8 +34,11 @@ def replace_colon(old_name, illegal_char):
else: new_name = new_name.replace(illegal_char, '-')
return new_name

# You can implement custom logic here for handling specific illegal characters.
def repair_illegal_name(old_name, illegal_chars):
"""
Given a name and a list of illegal characters, each character found in the name is replaced or removed according to the logic below.
Custom logic for a specific character can be added below.
"""
new_name = old_name
for illegal_char in illegal_chars:
if illegal_char in old_name:
Expand All @@ -39,24 +54,30 @@ def repair_illegal_name(old_name, illegal_chars):
new_name = new_name[:-1]
return new_name

def rename_folders(root_folder):
def repair_folder_names(root_folder):
"""
Repair all folders with 'illegal' names found in the given folder and subfolders. Replaces names according to the logic in repair_illegal_name().
"""
for root, dirnames, _ in os.walk(root_folder):
for dirname in dirnames:
if any(illegal_char in dirname for illegal_char in illegal_chars) or dirname.endswith('.'):
new_dirname = repair_illegal_name(dirname, illegal_chars)
if any(illegal_char in dirname for illegal_char in ILLEGAL_CHARS) or dirname.endswith('.'):
new_dirname = repair_illegal_name(dirname, ILLEGAL_CHARS)
rename_item(root, os.path.join(root, dirname), new_dirname)

def rename_files(root_folder):
def repair_file_names(root_folder):
"""
Repair all filenames with 'illegal' names found in the given folder and subfolders. Replaces names according to the logic in repair_illegal_name().
"""
for root, _, filenames in os.walk(root_folder):
for filename in filenames:
if any(illegal_char in filename for illegal_char in illegal_chars) or filename.endswith('.'):
new_name = repair_illegal_name(filename, illegal_chars)
if any(illegal_char in filename for illegal_char in ILLEGAL_CHARS) or filename.endswith('.'):
new_name = repair_illegal_name(filename, ILLEGAL_CHARS)
rename_item(root, os.path.join(root, filename), new_name)

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="This script allows you to quickly and easily replace or remove 'illegal' characters from filenames and foldernames in a given folder and subfolders that break sync compatibility between devices using different filesystem conventions. A practical use of this application is bulk renaming stored music folders/files to enable portability and syncing of a music library across devices. This program does *NOT* modify music metadata, just filenames/foldernames. By default, this application will intelligently replace colons ':' with dashes '-' and proper surrounding spacing, replace quotation marks '\"' with single quotes ', remove trailing periods '.', and remove any other characters defined in the illegal characters list at the top of the normalizeNames.py file. All of the aforementioned behavior can be easily overridden/modified in the repair_illegal_name() method and illegal characters list. All changes are printed to the console.")
parser.add_argument('root_folder', type=str, help="Path to folder containing music/subfolders to repair names in.")
args = parser.parse_args()
rename_files(args.root_folder)
rename_folders(args.root_folder)
repair_file_names(args.root_folder)
repair_folder_names(args.root_folder)
print("Finished! Exiting . . .")

0 comments on commit a8b78f6

Please sign in to comment.