Skip to content

Replace musicbrainz with eyed3 for ID tag reading#322

Open
filipsworks wants to merge 5 commits into
rakuri255:mainfrom
filipsworks:patch-2
Open

Replace musicbrainz with eyed3 for ID tag reading#322
filipsworks wants to merge 5 commits into
rakuri255:mainfrom
filipsworks:patch-2

Conversation

@filipsworks
Copy link
Copy Markdown

@filipsworks filipsworks commented May 15, 2026

Read song info + cover from id3 tags instead of using musicbrainz, which makes mismatches

Summary by CodeRabbit

  • New Features

    • Metadata extraction now reads ID3 tags from audio/video files to populate artist, title, year, and genre; if tags are missing or unreadable, filename-derived values are used as fallbacks.
    • Embedded cover images are extracted and saved when present.
    • Resolved metadata is used to construct sensible output filenames.
  • Chores

    • Added a dependency to enable ID3 tag reading.

Review Change Stack

read id tags via eyed3 instead of using musicbrainz which is faultprone
Replaced 'musicbrainzngs' with 'eyed3' in dependencies.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

📝 Walkthrough

Walkthrough

The PR migrates audio metadata extraction from MusicBrainz API lookups to local ID3 tag reading. The eyed3 library is added as a dependency, MusicBrainz imports are removed, and infos_from_audio_video_input_file() now extracts artist, title, year, genres, and cover image from ID3 tags with filename fallbacks.

Changes

ID3 Metadata Extraction via eyed3

Layer / File(s) Summary
Dependency and import setup
pyproject.toml, src/UltraSinger.py
eyed3 added to project dependencies; MusicBrainz import removed; eyed3 imported at module level and inside the metadata extraction function.
ID3 metadata extraction logic
src/UltraSinger.py
ID3 tags are read via eyed3 to extract artist, title, year, genres, and cover image data; filename-derived values are used as fallbacks when tags are missing or parsing fails; output basename is rebuilt from ID3 artist/title.
MediaInfo population from ID3 fields
src/UltraSinger.py
Cover image from ID3 tags is conditionally saved; MediaInfo is constructed using extracted artist, title, year, and genres, with cover_url set to None, replacing the prior MusicBrainz-based metadata approach.

Sequence Diagram

sequenceDiagram
  participant CLI_or_Caller
  participant infos_from_audio_video_input_file
  participant eyed3
  participant Filesystem
  CLI_or_Caller->>infos_from_audio_video_input_file: provide input file path
  infos_from_audio_video_input_file->>Filesystem: access file bytes
  infos_from_audio_video_input_file->>eyed3: eyed3.load(file_path)
  eyed3-->>infos_from_audio_video_input_file: ID3 tags (artist, title, year, genre, images)
  infos_from_audio_video_input_file->>Filesystem: save cover image (if present)
  infos_from_audio_video_input_file-->>CLI_or_Caller: return MediaInfo (artist, title, year, genres, cover_url=None)
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

A rabbit hops through ID3 tags so bright, 🐰
No more chasing MusicBrainz in flight,
Local metadata, quick and clean,
The finest extraction I've seen! 🎵

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: replacing MusicBrainz with eyed3 for metadata extraction, which is the primary objective reflected in both file changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
pyproject.toml (1)

25-25: ⚡ Quick win

Consider pinning the eyed3 version for reproducible builds.

The eyed3 dependency is compatible with the project's Python 3.12-3.13 range (eyed3 0.9.9 requires Python 3.10+). However, while the project currently leaves most dependencies unpinned, pinning versions for all direct dependencies improves build reproducibility and reduces the risk of unexpected breaking changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyproject.toml` at line 25, The dependency list includes an unpinned "eyed3"
entry which can lead to non-reproducible builds; update the pyproject.toml to
pin a specific compatible version (e.g., a stable 0.9.x release) for the "eyed3"
dependency so installs are deterministic, ensuring the change targets the
"eyed3" entry in the project dependencies section of pyproject.toml and updates
any lockfile if present.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/UltraSinger.py`:
- Around line 674-676: The bare "except Exception:" around the eyed3.load() call
should be replaced with explicit exception types (e.g., IOError, OSError and the
eyed3 library's exception class) and capture the exception into a variable so
you can log it before falling back to defaults; update the except to something
like "except (IOError, OSError, eyed3.Error) as e:" (or the correct eyed3
exception class available in your environment), call your logger with the
exception (e.g., processLogger.error or logging.exception) and then set
file_artist = artist or "Unknown Artist" and file_title = title as the fallback.
- Around line 661-676: The current eyed3-based metadata extraction (eyed3.load)
only handles MP3/ID3 and silently fails for other formats; update the logic in
the metadata block (where eyed3.load is called and file_artist, file_title,
year, genres, cover_image_data are set) to first validate the audio format
(e.g., check file extension or MIME type) and, if not MP3, use a format-agnostic
metadata reader such as mutagen to extract artist/title/year/genre/cover image,
falling back to eyed3 only for MP3; keep the same variables (file_artist,
file_title, year, genres, cover_image_data) and ensure exceptions still fall
back safely to artist/title defaults.

---

Nitpick comments:
In `@pyproject.toml`:
- Line 25: The dependency list includes an unpinned "eyed3" entry which can lead
to non-reproducible builds; update the pyproject.toml to pin a specific
compatible version (e.g., a stable 0.9.x release) for the "eyed3" dependency so
installs are deterministic, ensuring the change targets the "eyed3" entry in the
project dependencies section of pyproject.toml and updates any lockfile if
present.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5c1a38a9-e733-4506-816a-2dcddd557343

📥 Commits

Reviewing files that changed from the base of the PR and between 4155efd and 9af0597.

📒 Files selected for processing (2)
  • pyproject.toml
  • src/UltraSinger.py

Comment thread src/UltraSinger.py
Comment on lines +661 to +676
file_artist, file_title, year, genres, cover_image_data = None, None, None, [], None

try:
audio_file = eyed3.load(settings.input_file_path)
if audio_file is not None and audio_file.tag is not None:
file_artist = audio_file.tag.artist or artist or "Unknown Artist"
file_title = audio_file.tag.title or title
if audio_file.tag.best_release_date:
year = audio_file.tag.best_release_date.year
if audio_file.tag.genre:
genres = [audio_file.tag.genre.name]
if audio_file.tag.images:
cover_image_data = audio_file.tag.images[0].image_data
except Exception:
file_artist = artist or "Unknown Artist"
file_title = title
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚖️ Poor tradeoff

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check which audio formats are referenced in the codebase
echo "=== Searching for audio format references ==="
rg -i --type=py -C2 '(mp3|flac|ogg|m4a|wav|aac)' -g '!test*' | head -30

echo ""
echo "=== Checking eyed3 format support documentation ==="
# Search for format-related checks or conversions
rg -i --type=py 'is_video_file|audio.*format|file.*extension' -C3

Repository: rakuri255/UltraSinger

Length of output: 9299


🏁 Script executed:

# First, let's look at the broader context around lines 661-676 in UltraSinger.py
sed -n '600,700p' src/UltraSinger.py | cat -n

Repository: rakuri255/UltraSinger

Length of output: 5295


🏁 Script executed:

# Search for where input_file_path is processed before the metadata extraction
rg -n 'input_file_path|eyed3' src/UltraSinger.py | head -40

Repository: rakuri255/UltraSinger

Length of output: 1163


🏁 Script executed:

# Check if there's format detection or conversion before metadata extraction
rg -B10 'eyed3.load' src/UltraSinger.py

Repository: rakuri255/UltraSinger

Length of output: 434


🏁 Script executed:

# Check if there's any eyed3 configuration or documentation in the codebase
rg -i 'eyed3|id3' src/ --type=py -B2 -A2

Repository: rakuri255/UltraSinger

Length of output: 1406


🏁 Script executed:

# Check requirements/dependencies to see eyed3 version
fd -i requirements '\.(txt|in|toml|yaml|yml)$' -exec cat {} \;

Repository: rakuri255/UltraSinger

Length of output: 325


🌐 Web query:

eyed3 library supported audio formats

💡 Result:

The eyeD3 library is primarily designed for working with MP3 files and ID3 metadata [1][2]. According to the official documentation, it explicitly supports the following file types: - MP3 audio files (via eyed3.mp3.Mp3AudioFile) [3] - Raw ID3 data files (via eyed3.id3.TagFile) [3] While the library's design is abstract to allow for potential future support of other formats [1][4], its current core functionality is focused on MP3s and ID3 tags. When using the library's loading functions (such as eyed3.load), it will return None if the file type or MIME type is not recognized [3]. Additionally, eyeD3 supports ID3 metadata versions 1.x (1.0/1.1), 2.2 (read-only), 2.3, and 2.4 [1][5].

Citations:


eyed3 only supports MP3 files with ID3 tags—non-MP3 formats will silently fail to extract metadata.

The eyed3 library is designed exclusively for MP3 files with ID3 tags and returns None for other formats (FLAC, OGG Vorbis, WAV, M4A). When eyed3.load() fails on non-MP3 files, the exception handler falls back to filename-only parsing, causing users to lose available metadata (artist, title, year, genre, cover art) for formats that support tags natively.

Consider adding explicit format validation before attempting ID3 extraction, or use a format-agnostic metadata library for broader audio format support.

🧰 Tools
🪛 Ruff (0.15.12)

[warning] 674-674: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/UltraSinger.py` around lines 661 - 676, The current eyed3-based metadata
extraction (eyed3.load) only handles MP3/ID3 and silently fails for other
formats; update the logic in the metadata block (where eyed3.load is called and
file_artist, file_title, year, genres, cover_image_data are set) to first
validate the audio format (e.g., check file extension or MIME type) and, if not
MP3, use a format-agnostic metadata reader such as mutagen to extract
artist/title/year/genre/cover image, falling back to eyed3 only for MP3; keep
the same variables (file_artist, file_title, year, genres, cover_image_data) and
ensure exceptions still fall back safely to artist/title defaults.

Comment thread src/UltraSinger.py
Comment on lines +674 to +676
except Exception:
file_artist = artist or "Unknown Artist"
file_title = title
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Replace bare except Exception: with specific exception types.

The broad exception handler can mask unexpected errors and make debugging difficult. eyed3.load() may raise IOError, OSError, or eyed3-specific exceptions.

🛡️ Proposed fix to catch specific exceptions
-    except Exception:
+    except (OSError, IOError, AttributeError):
         file_artist = artist or "Unknown Artist"
         file_title = title
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
except Exception:
file_artist = artist or "Unknown Artist"
file_title = title
except (OSError, IOError, AttributeError):
file_artist = artist or "Unknown Artist"
file_title = title
🧰 Tools
🪛 Ruff (0.15.12)

[warning] 674-674: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/UltraSinger.py` around lines 674 - 676, The bare "except Exception:"
around the eyed3.load() call should be replaced with explicit exception types
(e.g., IOError, OSError and the eyed3 library's exception class) and capture the
exception into a variable so you can log it before falling back to defaults;
update the except to something like "except (IOError, OSError, eyed3.Error) as
e:" (or the correct eyed3 exception class available in your environment), call
your logger with the exception (e.g., processLogger.error or logging.exception)
and then set file_artist = artist or "Unknown Artist" and file_title = title as
the fallback.

Fix genre assignment to directly use genre name instead of making a list as it breaks parsing in txt converter
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/UltraSinger.py (1)

683-687: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Sanitize ID3-derived artist/title before using them in filesystem paths.

Line 683 builds basename_without_ext directly from tag values. If tags contain separators or absolute-style prefixes, path creation at Lines 685-687 can escape the intended output layout or fail on invalid names.

💡 Suggested fix
+    def _sanitize_path_part(value: str) -> str:
+        value = value.strip()
+        value = value.replace(os.sep, " ")
+        if os.altsep:
+            value = value.replace(os.altsep, " ")
+        return value.strip().strip(".")
+
+    safe_artist = _sanitize_path_part(file_artist or "Unknown Artist")
+    safe_title = _sanitize_path_part(file_title or basename_without_ext)
-    basename_without_ext = f"{file_artist} - {file_title}"
+    basename_without_ext = f"{safe_artist} - {safe_title}"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/UltraSinger.py` around lines 683 - 687, Sanitize the ID3-derived strings
before building basename_without_ext: ensure file_artist and file_title are
cleaned to remove path separators, drive/absolute prefixes, invalid filesystem
characters and trimmed to a safe length (with a sensible fallback like
"unknown_artist" or "unknown_title") before composing basename_without_ext;
update the code that sets basename_without_ext and the call sites
get_unused_song_output_dir and os_helper.create_folder to use the sanitized
values (or add/ reuse a helper function named e.g. sanitize_filename and call it
on file_artist and file_title prior to joining with
settings.output_folder_path).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/UltraSinger.py`:
- Around line 661-672: The variable genres is initialized as a list but later
assigned a string from eyed3 tags, causing type inconsistency; change the
initialization and all assignments so genres is always a scalar (e.g., None or
an empty string) and set genres = audio_file.tag.genre.name when present,
otherwise genres = None (or ""), and apply the same scalar logic in the later
block referenced around lines 722-726 so consumers of genres always receive the
same type.

---

Outside diff comments:
In `@src/UltraSinger.py`:
- Around line 683-687: Sanitize the ID3-derived strings before building
basename_without_ext: ensure file_artist and file_title are cleaned to remove
path separators, drive/absolute prefixes, invalid filesystem characters and
trimmed to a safe length (with a sensible fallback like "unknown_artist" or
"unknown_title") before composing basename_without_ext; update the code that
sets basename_without_ext and the call sites get_unused_song_output_dir and
os_helper.create_folder to use the sanitized values (or add/ reuse a helper
function named e.g. sanitize_filename and call it on file_artist and file_title
prior to joining with settings.output_folder_path).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: af0e253b-1922-48d7-8e44-8cfd68aa1c45

📥 Commits

Reviewing files that changed from the base of the PR and between 9af0597 and 930e2ec.

📒 Files selected for processing (1)
  • src/UltraSinger.py

Comment thread src/UltraSinger.py
Join multiple genres into a single string if available.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (3)
src/UltraSinger.py (3)

661-672: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Genre extraction is currently discarded at Line 671.

genres is initialized as an empty list, then Line 671 joins that same list, so MediaInfo.genre ends up None even when a tag genre exists.

Suggested fix
-    file_artist, file_title, year, genres, cover_image_data = None, None, None, [], None
+    file_artist, file_title, year, genre, cover_image_data = None, None, None, None, None
@@
-            if audio_file.tag.genre:
-                genres = ', '.join(genres) if genres else None
+            if audio_file.tag.genre and audio_file.tag.genre.name:
+                genre = audio_file.tag.genre.name
@@
-            genre=genres,
+            genre=genre,

Also applies to: 725-725

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/UltraSinger.py` around lines 661 - 672, The code initializes genres as an
empty list then immediately sets genres = ', '.join(genres) when
audio_file.tag.genre exists, which joins the empty list and results in None;
instead, read the tag's genre value into the genres list (e.g., append
audio_file.tag.genre.name or str(audio_file.tag.genre)) before joining, and then
set the final MediaInfo.genre to the joined string; apply the same fix to the
other occurrence around the image-handling block (the second genres handling) so
that audio_file.tag.genre is correctly extracted and preserved rather than
discarded.

674-676: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Narrow broad exception handling at Line 674.

except Exception can hide unrelated failures; catch expected read/parse errors explicitly and log the error context.

#!/bin/bash
# Verify no blind exception handlers remain in Python files.
rg -nP '^\s*except\s+Exception\s*:' --type=py
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/UltraSinger.py` around lines 674 - 676, Replace the broad "except
Exception:" handler around the metadata/read block in UltraSinger.py with
explicit exception catches for expected failure modes (e.g., IOError/OSError for
file access, ValueError/KeyError/AttributeError for parsing/metadata lookup) and
in each except branch set file_artist = artist or "Unknown Artist" and
file_title = title, while logging the caught exception and context (include
artist/title/file path) using the existing logger; this narrows the scope,
preserves fallback assignment for file_artist/file_title, and ensures errors are
recorded for debugging.

663-681: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle the audio_file is None path with filename fallbacks.

Fallback values are only set in except. If metadata loading returns None without throwing, filename-derived artist/title are not preserved, and Line 679 can incorrectly force "Unknown Artist".

Suggested fix
-    file_artist, file_title, year, genres, cover_image_data = None, None, None, [], None
+    file_artist, file_title, year, genres, cover_image_data = (
+        artist or "Unknown Artist",
+        title or basename_without_ext,
+        None,
+        [],
+        None,
+    )
@@
-    except Exception:
-        file_artist = artist or "Unknown Artist"
-        file_title = title
+    except Exception:
+        pass
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/UltraSinger.py` around lines 663 - 681, The try/except currently only
sets fallback file_artist/file_title inside the except, so when
eyed3.load(settings.input_file_path) returns None you lose filename-derived
values; update the logic around eyed3.load and the try block (the block that
assigns file_artist and file_title) to explicitly handle audio_file is None by
assigning file_artist = artist or "Unknown Artist" and file_title = title or
basename_without_ext before falling through, and keep the existing post-checks
that default empty values — reference the eyed3.load call, variables
file_artist, file_title, settings.input_file_path and basename_without_ext to
locate where to add the None-path assignments.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/UltraSinger.py`:
- Around line 661-672: The code initializes genres as an empty list then
immediately sets genres = ', '.join(genres) when audio_file.tag.genre exists,
which joins the empty list and results in None; instead, read the tag's genre
value into the genres list (e.g., append audio_file.tag.genre.name or
str(audio_file.tag.genre)) before joining, and then set the final
MediaInfo.genre to the joined string; apply the same fix to the other occurrence
around the image-handling block (the second genres handling) so that
audio_file.tag.genre is correctly extracted and preserved rather than discarded.
- Around line 674-676: Replace the broad "except Exception:" handler around the
metadata/read block in UltraSinger.py with explicit exception catches for
expected failure modes (e.g., IOError/OSError for file access,
ValueError/KeyError/AttributeError for parsing/metadata lookup) and in each
except branch set file_artist = artist or "Unknown Artist" and file_title =
title, while logging the caught exception and context (include artist/title/file
path) using the existing logger; this narrows the scope, preserves fallback
assignment for file_artist/file_title, and ensures errors are recorded for
debugging.
- Around line 663-681: The try/except currently only sets fallback
file_artist/file_title inside the except, so when
eyed3.load(settings.input_file_path) returns None you lose filename-derived
values; update the logic around eyed3.load and the try block (the block that
assigns file_artist and file_title) to explicitly handle audio_file is None by
assigning file_artist = artist or "Unknown Artist" and file_title = title or
basename_without_ext before falling through, and keep the existing post-checks
that default empty values — reference the eyed3.load call, variables
file_artist, file_title, settings.input_file_path and basename_without_ext to
locate where to add the None-path assignments.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 73a50ad0-cc5d-47dd-a977-5b7c6a391bfd

📥 Commits

Reviewing files that changed from the base of the PR and between 930e2ec and 1f7bd33.

📒 Files selected for processing (1)
  • src/UltraSinger.py

Updated genre assignment to use audio file's genre name instead of a list.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/UltraSinger.py (1)

674-676: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Narrow the catch at Line 674 to expected metadata-read failures only.

except Exception hides unexpected bugs and silently downgrades metadata quality. Catch specific parse/I/O exceptions and keep fallback behavior.

🛠️ Proposed minimal change
-    except Exception:
+    except (OSError, IOError, AttributeError):
         file_artist = artist or "Unknown Artist"
         file_title = title
#!/bin/bash
# Verify broad exception handling still exists in metadata extraction
rg -n -C2 'except Exception' src/UltraSinger.py
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/UltraSinger.py` around lines 674 - 676, Replace the broad "except
Exception" that sets file_artist and file_title with a narrow catch for expected
metadata-read failures: catch specific exceptions such as ValueError,
OSError/IOError (and any library-specific parse error like MutagenError if you
use mutagen) and keep the same fallback assignment to file_artist = artist or
"Unknown Artist" and file_title = title; leave any other unexpected exceptions
to bubble up so real bugs aren't silently ignored.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/UltraSinger.py`:
- Around line 685-688: basename_without_ext is built from raw tag values
(file_artist, file_title) and used to create song_folder_output_path; sanitize
file_artist and file_title before composing basename_without_ext to prevent path
separators or invalid filename characters from creating unexpected directories
or OS errors. Update the code that sets basename_without_ext (and any uses of
file_artist/file_title) to run a sanitizer function that strips or replaces path
separators, control characters and OS-invalid filename characters (e.g., replace
"/" "\" ":" "*" "?" "<" ">" "|" and nulls with underscores), trims whitespace,
and optionally truncates to a safe length, then use the sanitized values when
calling os.path.join(settings.output_folder_path, ...) and
get_unused_song_output_dir. Ensure the sanitizer is applied consistently
wherever tag-derived names are used for filesystem paths.

---

Duplicate comments:
In `@src/UltraSinger.py`:
- Around line 674-676: Replace the broad "except Exception" that sets
file_artist and file_title with a narrow catch for expected metadata-read
failures: catch specific exceptions such as ValueError, OSError/IOError (and any
library-specific parse error like MutagenError if you use mutagen) and keep the
same fallback assignment to file_artist = artist or "Unknown Artist" and
file_title = title; leave any other unexpected exceptions to bubble up so real
bugs aren't silently ignored.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7ae82c34-ea4c-4792-b2ec-5c6ff82cc871

📥 Commits

Reviewing files that changed from the base of the PR and between 1f7bd33 and f8069b4.

📒 Files selected for processing (1)
  • src/UltraSinger.py

Comment thread src/UltraSinger.py
Comment on lines +685 to 688
basename_without_ext = f"{file_artist} - {file_title}"

song_folder_output_path = os.path.join(settings.output_folder_path, basename_without_ext)
song_folder_output_path = get_unused_song_output_dir(song_folder_output_path)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Sanitize tag-derived artist/title before using them in filesystem paths.

Line 685 uses raw ID3 values to build directory/file names. Tag values can contain path separators or invalid filename characters, causing path traversal-like folder creation or OS-specific failures.

🛠️ Proposed minimal change
+    invalid_chars = '\\/:*?"<>|'
+    safe_artist = "".join("_" if c in invalid_chars else c for c in file_artist).replace("..", "_").strip().strip(".")
+    safe_title = "".join("_" if c in invalid_chars else c for c in file_title).replace("..", "_").strip().strip(".")
-    basename_without_ext = f"{file_artist} - {file_title}"
+    basename_without_ext = f"{safe_artist} - {safe_title}"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/UltraSinger.py` around lines 685 - 688, basename_without_ext is built
from raw tag values (file_artist, file_title) and used to create
song_folder_output_path; sanitize file_artist and file_title before composing
basename_without_ext to prevent path separators or invalid filename characters
from creating unexpected directories or OS errors. Update the code that sets
basename_without_ext (and any uses of file_artist/file_title) to run a sanitizer
function that strips or replaces path separators, control characters and
OS-invalid filename characters (e.g., replace "/" "\" ":" "*" "?" "<" ">" "|"
and nulls with underscores), trims whitespace, and optionally truncates to a
safe length, then use the sanitized values when calling
os.path.join(settings.output_folder_path, ...) and get_unused_song_output_dir.
Ensure the sanitizer is applied consistently wherever tag-derived names are used
for filesystem paths.

@rakuri255
Copy link
Copy Markdown
Owner

@filipsworks nice work, but:

  1. Dont remove musicbrainz because not everything has ID3.
  2. ID3 is not supported for every audio container.

Use it as second check or as an higher value.
Also it needs to be in as an config parameter, so that it can be forced/disabled

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants