Skip to content

Conversation

canepan
Copy link

@canepan canepan commented Jul 28, 2025

Proposed changes in this pull request:

  • support music download via MusicBrainz

  • I couldn't find working options, including lidarr

  • I know it's a big change, but it's mainly adding files, and not easy to break down

  • PR is based on the DEVELOP branch

  • Don't send big changes all at once. Split up big PRs into multiple smaller PRs that are easier to manage and review

  • Read contribution guide

Tested adding artists, downloading and showing images and background

Summary by CodeRabbit

  • New Features

    • Introduced comprehensive music management, including artist and album tracking, powered by MusicBrainz integration.
    • Added web interface for browsing, searching, and managing music artists and albums.
    • Enabled configurable music root and download directories in settings.
    • Added support for album and artist metadata, cover art, and status management.
    • Implemented background music search and download queue.
  • User Interface

    • New navigation menu for music with pages to list, search, and manage artists and albums.
    • Added UI components for managing music directories and MusicBrainz integration in settings.
    • Enhanced album and artist detail views with metadata and cover images.
  • Bug Fixes

    • None.
  • Tests

    • Added unit tests for music management features to ensure reliability and correctness.
  • Chores

    • Added new dependencies required for MusicBrainz integration.

Copy link
Contributor

coderabbitai bot commented Jul 28, 2025

Walkthrough

This change set introduces a comprehensive music management feature to the application, integrating MusicBrainz support, a new ORM schema for music data, web handlers, UI templates, configuration options, background schedulers, and supporting tests. It adds new endpoints, templates, JavaScript files, and database models to enable searching, adding, viewing, and managing music artists and albums, including metadata fetching and download management.

Changes

Cohort / File(s) Change Summary
Music Management Core & ORM
sickchill/music.py, sickchill/oldbeard/databases/music.py
Adds core music management logic and a new SQLAlchemy ORM schema for artists, albums, genres, images, search results, and history. Integrates with MusicBrainz API for searching and metadata.
MusicBrainz Metadata Provider
sickchill/providers/metadata/musicbrainz.py
Introduces a metadata provider class for artists and albums, handling metadata and image retrieval/saving from MusicBrainz.
Settings and Configuration
sickchill/settings.py, sickchill/start.py, sickchill/oldbeard/config.py, pyproject.toml
Adds music-related settings, MusicBrainz config, new directory settings, scheduler initialization, and dependency on musicbrainzngs.
Web Handlers & Routing
sickchill/views/music.py, sickchill/views/__init__.py, sickchill/views/server_settings.py, sickchill/views/config/general.py
Adds a new web handler for music endpoints, registers new routes, and extends config handling for music directories and MusicBrainz enablement.
UI Templates: Music Pages
sickchill/gui/slick/views/music/index.mako, sickchill/gui/slick/views/music/details.mako, sickchill/gui/slick/views/music/album_details.mako, sickchill/gui/slick/views/music/search.mako, sickchill/gui/slick/views/music/remove.mako
Adds new Mako templates for music index, artist details, album details, search, and removal confirmation pages.
UI Templates: General & Navigation
sickchill/gui/slick/views/config_general.mako, sickchill/gui/slick/views/inc_musicRootDirs.mako, sickchill/gui/slick/views/layouts/main.mako
Adds music root directory management UI, music download directory input, MusicBrainz enable checkbox, and music navigation menu.
UI JavaScript
sickchill/gui/slick/js/music.js, sickchill/gui/slick/js/musicRootDirs.js
Adds JS for music page fanart backgrounds and UI logic for managing music root directories.
Testing
tests/test_music.py
Adds unit tests for the music management logic, covering searching, adding, album retrieval, provider search, and snatching.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant WebUI
    participant MusicHandler
    participant MusicList
    participant MusicBrainz
    participant DB

    User->>WebUI: Search/Add Artist
    WebUI->>MusicHandler: /music/search or /music/add
    MusicHandler->>MusicList: search_musicbrainz(query)
    MusicList->>MusicBrainz: API search
    MusicBrainz-->>MusicList: Artist data
    MusicList-->>MusicHandler: Results
    MusicHandler-->>WebUI: Render artist list/search results

    User->>WebUI: View Artist Details
    WebUI->>MusicHandler: /music/details
    MusicHandler->>MusicList: artist_by_slug(slug)
    MusicList->>DB: Query artist, albums
    DB-->>MusicList: Artist, album data
    MusicList-->>MusicHandler: Data
    MusicHandler-->>WebUI: Render artist details

    User->>WebUI: Snatch Album
    WebUI->>MusicHandler: /music/snatch_album
    MusicHandler->>MusicList: snatch_album(result)
    MusicList->>DB: Update album status/history
    MusicList-->>MusicHandler: Result
    MusicHandler-->>WebUI: Show status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • The implementation introduces multiple new modules, database models, handlers, templates, configuration options, and tests, with several high-complexity and interdependent changes. Review will require careful examination of the new ORM, API integrations, UI, and business logic.

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@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: 19

🧹 Nitpick comments (4)
sickchill/gui/slick/views/music/remove.mako (1)

1-36: LGTM! Clean removal confirmation page.

The template provides good user experience with:

  • Clear success confirmation message
  • Auto-redirect functionality for smooth workflow
  • Manual navigation option as fallback
  • Proper internationalization support
  • Bootstrap styling consistency

The JavaScript redirect is simple and secure using reverse_url for the destination.

Consider making the 5-second redirect timeout configurable or using a slightly longer delay (7-10 seconds) to give users more time to read the confirmation message.

sickchill/gui/slick/js/musicRootDirs.js (1)

103-106: Improve robustness of ID parsing logic.

The current implementation assumes the ID format is always 'rd-X' and uses a hardcoded slice position. This could break if the ID format changes.

-        if ($('#whichDefaultMusicRootDir').val().length >= 4) {
-            directoryString = $('#whichDefaultMusicRootDir').val().slice(3);
-        }
+        const defaultId = $('#whichDefaultMusicRootDir').val();
+        const match = defaultId.match(/^rd-(\d+)$/);
+        if (match) {
+            directoryString = match[1];
+        }
sickchill/oldbeard/databases/music.py (1)

103-103: Move import to module level.

Importing modules inside functions/methods is generally discouraged unless necessary for avoiding circular imports.

Add import shutil at the top of the file with other imports, then remove the import from line 103.

sickchill/music.py (1)

600-608: Add error handling for track duration formatting.

The duration formatting could fail if the duration value is not a valid integer.

                                    if track_info["duration"]:
                                        try:
                                            ms = int(track_info["duration"])
                                            seconds = ms // 1000
                                            minutes = seconds // 60
                                            seconds = seconds % 60
                                            track_info["duration"] = f"{minutes}:{seconds:02d}"
                                        except (ValueError, TypeError):
-                                            pass
+                                            logger.debug(f"Invalid duration value: {track_info['duration']}")
+                                            track_info["duration"] = ""
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9731339 and 0c621c6.

📒 Files selected for processing (23)
  • SickChill.py (2 hunks)
  • pyproject.toml (1 hunks)
  • sickchill/gui/slick/js/music.js (1 hunks)
  • sickchill/gui/slick/js/musicRootDirs.js (1 hunks)
  • sickchill/gui/slick/views/config_general.mako (2 hunks)
  • sickchill/gui/slick/views/inc_musicRootDirs.mako (1 hunks)
  • sickchill/gui/slick/views/layouts/main.mako (2 hunks)
  • sickchill/gui/slick/views/music/album_details.mako (1 hunks)
  • sickchill/gui/slick/views/music/details.mako (1 hunks)
  • sickchill/gui/slick/views/music/index.mako (1 hunks)
  • sickchill/gui/slick/views/music/remove.mako (1 hunks)
  • sickchill/gui/slick/views/music/search.mako (1 hunks)
  • sickchill/music.py (1 hunks)
  • sickchill/oldbeard/config.py (1 hunks)
  • sickchill/oldbeard/databases/music.py (1 hunks)
  • sickchill/providers/metadata/musicbrainz.py (1 hunks)
  • sickchill/settings.py (3 hunks)
  • sickchill/start.py (8 hunks)
  • sickchill/views/__init__.py (1 hunks)
  • sickchill/views/config/general.py (4 hunks)
  • sickchill/views/music.py (1 hunks)
  • sickchill/views/server_settings.py (2 hunks)
  • tests/test_music.py (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: destotelhorus
PR: SickChill/sickchill#8740
File: sickchill/oldbeard/clients/__deluge_base.py:32-32
Timestamp: 2024-06-10T19:15:15.947Z
Learning: User destotelhorus agreed to the suggested change of combining `options.update` calls into a single call for better readability and efficiency in the `sickchill/oldbeard/clients/__deluge_base.py` file, but mentioned not having touched that part of the code themselves.
SickChill.py (1)

Learnt from: miigotu
PR: #8812
File: sickchill/helper/common.py:443-447
Timestamp: 2024-11-29T05:51:19.858Z
Learning: In the choose_data_dir function in sickchill/helper/common.py, avoid using try-except blocks to handle PermissionError or OSError when checking for path existence or access. Instead, check if the user has access to the path using os.access() to determine if the user has the necessary permissions, following the preference to avoid the EAFP pattern in modern Python.

sickchill/views/server_settings.py (1)

Learnt from: miigotu
PR: #8812
File: sickchill/helper/common.py:443-447
Timestamp: 2024-11-29T05:51:19.858Z
Learning: In the choose_data_dir function in sickchill/helper/common.py, avoid using try-except blocks to handle PermissionError or OSError when checking for path existence or access. Instead, check if the user has access to the path using os.access() to determine if the user has the necessary permissions, following the preference to avoid the EAFP pattern in modern Python.

sickchill/oldbeard/config.py (1)

Learnt from: destotelhorus
PR: #8740
File: sickchill/oldbeard/clients/__deluge_base.py:32-32
Timestamp: 2024-06-10T19:15:15.947Z
Learning: User destotelhorus agreed to the suggested change of combining options.update calls into a single call for better readability and efficiency in the sickchill/oldbeard/clients/__deluge_base.py file, but mentioned not having touched that part of the code themselves.

🪛 Ruff (0.12.2)
sickchill/views/__init__.py

25-25: sickchill.views.music.MusicHandler imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)

sickchill/views/music.py

5-5: sickchill.oldbeard.config imported but unused

Remove unused import: sickchill.oldbeard.config

(F401)

tests/test_music.py

8-8: musicbrainzngs imported but unused

Remove unused import: musicbrainzngs

(F401)

sickchill/oldbeard/databases/music.py

9-9: sqlalchemy.ext.declarative.declarative_base imported but unused

Remove unused import: sqlalchemy.ext.declarative.declarative_base

(F401)

sickchill/providers/metadata/musicbrainz.py

12-12: sickchill.oldbeard.databases.music imported but unused

Remove unused import: sickchill.oldbeard.databases.music

(F401)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: GitGuardian Security Checks
🔇 Additional comments (36)
pyproject.toml (1)

300-300: Approve addition of musicbrainzngs dependency

Verified that musicbrainzngs = ">=0.7.1" on pyproject.toml (line 300) is correct:

  • 0.7.1 is the latest release as of July 2025.
  • No known security vulnerabilities reported for version 0.7.1 or later.
  • Version constraint >=0.7.1 balances flexibility and feature requirements.

No further action required.

sickchill/settings.py (4)

13-13: LGTM!

The import of MusicList type for type checking is correctly placed and follows the existing pattern with MovieList.


81-81: Clarify the purpose of BRANCH setting.

The addition of BRANCH = "master" appears unrelated to the music integration feature. Please clarify if this change is intentional and necessary for this PR.


588-588: LGTM!

The music_list variable declaration follows the same pattern as movie_list and uses proper type annotation.


590-596: Well-structured MusicBrainz configuration settings.

The MusicBrainz configuration variables are comprehensive and follow good naming conventions. The settings cover all necessary aspects: feature toggle, API credentials, user agent info, and directory management.

sickchill/views/__init__.py (1)

25-25: LGTM!

The import of MusicHandler follows the established pattern with other handlers like MoviesHandler. The static analysis warning about unused import can be safely ignored as this is a standard pattern for exposing imports in package __init__.py files.

SickChill.py (2)

30-30: LGTM!

The import of MusicList is clean and follows the existing pattern with MovieList.


220-221: Well-implemented conditional initialization.

The conditional initialization of settings.music_list follows the same pattern as movie_list and uses an appropriate feature flag (USE_MUSICBRAINZ). The placement in the startup sequence is logical.

sickchill/gui/slick/views/layouts/main.mako (2)

183-194: LGTM! Clean integration of music navigation menu.

The music menu follows the established pattern of other dropdown menus and is properly conditionally displayed based on the USE_MUSICBRAINZ setting. The use of reverse_url for routing ensures consistency with the URL structure.


418-418: LGTM! Appropriate script inclusion.

The musicRootDirs.js script is properly included alongside other core scripts in the authenticated user section, following the same pattern as existing functionality.

sickchill/gui/slick/views/inc_musicRootDirs.mako (1)

1-37: LGTM! Well-structured music root directories management component.

This template follows the established patterns from existing root directory management:

  • Proper parsing of the MUSIC_ROOT_DIRS setting using pipe delimiter
  • Consistent UI structure with select box and control buttons
  • Good use of Bootstrap responsive classes
  • Proper internationalization with _() function
  • Hidden inputs for state management following existing patterns

The implementation is clean and maintainable.

sickchill/views/server_settings.py (2)

15-15: LGTM! Proper import addition.

The MusicHandler import is correctly added to support the new music functionality.


168-179: LGTM! Comprehensive and well-structured music routing.

The music routes are well-designed and consistent:

  • Complete coverage of music operations (index, details, add, remove, search, etc.)
  • URL patterns follow established conventions from movies routes
  • Proper use of named parameters (slug, pk) for resource identification
  • Route names follow consistent naming pattern (music-{action})
  • No apparent routing conflicts or security concerns

The routing structure provides a solid foundation for the music management feature.

sickchill/gui/slick/views/music/search.mako (1)

18-32: LGTM! Well-structured search form.

The search form follows good practices:

  • Proper form structure with Bootstrap classes
  • Meaningful labels and placeholders for accessibility
  • Uses POST method and reverse_url for consistent routing
  • Good internationalization support
sickchill/views/config/general.py (3)

33-34: LGTM - Consistent pattern implementation.

The method follows the same pattern as saveRootDirs above, maintaining consistency in the codebase.


69-69: LGTM - Consistent error handling pattern.

The music download directory handling follows the same validation and error handling pattern as the log directory above it, ensuring consistent user experience and proper directory management.

Also applies to: 92-93


120-121: LGTM - Standard checkbox setting implementation.

The USE_MUSICBRAINZ setting follows the established pattern used by other checkbox settings in this method, using the standard config.checkbox_to_value helper.

sickchill/gui/slick/views/config_general.mako (2)

262-279: LGTM - Well-structured directory input field.

The music download directory section follows the established pattern and correctly uses the field name that matches the backend expectation.


1175-1187: LGTM - Standard checkbox implementation.

The MusicBrainz checkbox follows the established pattern and is appropriately placed in the advanced settings section.

sickchill/gui/slick/views/music/album_details.mako (4)

1-13: LGTM - Well-structured template setup.

The imports are appropriate and the background image metadata is a nice UI enhancement. The use of static_url() provides proper URL generation and XSS protection.


27-94: LGTM - Comprehensive album information display.

The album information section is well-structured with proper handling of missing data (e.g., year fallback to "Unknown") and comprehensive status display covering all AlbumStatus enum values with appropriate styling.


96-133: LGTM - Robust track listing with defensive programming.

The track list implementation properly handles missing data scenarios and uses defensive programming with .get() method calls to prevent template errors when MusicBrainz data is incomplete.


136-138: LGTM - Standard JavaScript inclusion.

The JavaScript file inclusion follows the established pattern with proper static URL generation.

sickchill/gui/slick/views/music/index.mako (4)

1-20: LGTM - Good UX pattern for feature availability.

The conditional display based on USE_MUSICBRAINZ setting provides clear user guidance when the feature is disabled, with instructions on how to enable it.


32-50: LGTM - Useful filtering feature with minor consideration.

The root directory filtering is well-implemented. The .split('|')[1:] assumes a specific format for MUSIC_ROOT_DIRS - ensure this parsing logic aligns with how the directories are stored and managed in the backend.


51-96: LGTM - Comprehensive artist table with good UX.

The artist table implementation handles edge cases well (empty lists, missing country data) and provides appropriate user actions. The responsive design and proper link generation ensure good usability.


98-107: LGTM - Clean and defensive JavaScript implementation.

The JavaScript provides smooth UX with automatic form submission on filter changes. The null check and proper event handling demonstrate good defensive programming practices.

sickchill/oldbeard/config.py (1)

302-302: Inconsistent None check compared to similar functions.

The condition includes a check for settings.MUSIC_DOWNLOAD_DIR is None, which differs from the pattern used in similar functions like change_tv_download_dir (line 281). This inconsistency could lead to unexpected behavior if the setting is already initialized.

Consider whether this None check is necessary. If MUSIC_DOWNLOAD_DIR can legitimately be None initially, document why this function differs from others. Otherwise, align with the existing pattern:

-    if settings.MUSIC_DOWNLOAD_DIR is None or os.path.normpath(settings.MUSIC_DOWNLOAD_DIR) != os.path.normpath(music_download_dir):
+    if os.path.normpath(settings.MUSIC_DOWNLOAD_DIR) != os.path.normpath(music_download_dir):
sickchill/start.py (8)

13-13: LGTM! Clean import addition.

The music module import is properly integrated with the existing sickchill imports and supports the new MusicBrainz functionality.


186-187: LGTM! Proper conditional feature enablement.

The USE_MUSICBRAINZ setting is appropriately restricted to developer mode, which is a sensible approach for a new experimental feature. The conditional logic and formatting are consistent with the existing codebase.


344-345: LGTM! Consistent configuration additions.

The music directory settings follow established patterns and naming conventions. The placement after TV_DOWNLOAD_DIR is logical and the empty string defaults are appropriate for optional settings.


970-978: LGTM! Well-configured scheduler setup.

The music scheduler is properly integrated with the existing scheduler framework. The timing parameters (10s initial delay, 5s cycle) are appropriate for a queue scheduler, and the silent mode logic correctly ties to the USE_MUSICBRAINZ setting.


1035-1039: LGTM! Consistent scheduler startup integration.

The music scheduler startup follows the established pattern used by other feature-based schedulers like Trakt and subtitles. The conditional enablement based on USE_MUSICBRAINZ is properly implemented.


1061-1061: LGTM! Proper shutdown integration.

The music scheduler is correctly added to the threads list for proper shutdown handling, ensuring clean termination of the music queue processing.


1263-1264: LGTM! Consistent configuration persistence.

The music directory settings are properly persisted to the configuration file with appropriate empty string fallbacks, following the same pattern as other directory settings in the General section.


1302-1302: LGTM! Proper boolean configuration persistence.

The USE_MUSICBRAINZ setting is correctly saved to the configuration file with appropriate integer conversion, consistent with other boolean settings in the General section.

Comment on lines +1 to +11
// Music page specific JavaScript
$(document).ready(function() {
// Apply background image if FANART_BACKGROUND is enabled
if (metaToBool('settings.FANART_BACKGROUND')) {
const backgroundPath = getMeta('showBackgroundImage');
if (backgroundPath) {
$.backstretch(backgroundPath);
$('.backstretch').css('opacity', getMeta('settings.FANART_BACKGROUND_OPACITY')).fadeIn('500');
}
}
}); No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Clean implementation with good defensive programming.

The JavaScript code is well-structured and follows good practices:

  • Uses feature detection with metaToBool() before applying functionality
  • Defensive programming by checking backgroundPath exists
  • Reasonable animation timing (500ms fade-in)
  • Clean jQuery document ready pattern

Please verify that the required dependencies are available:


🏁 Script executed:

#!/bin/bash
# Description: Verify that required JavaScript functions and plugins are available
# Expected: metaToBool, getMeta functions and backstretch plugin should be defined

echo "Checking for required JavaScript dependencies..."

# Search for metaToBool function definition
echo "=== Searching for metaToBool function ==="
rg -A 3 "function metaToBool|metaToBool\s*=" --type js

# Search for getMeta function definition  
echo "=== Searching for getMeta function ==="
rg -A 3 "function getMeta|getMeta\s*=" --type js

# Search for backstretch plugin
echo "=== Searching for backstretch plugin ==="
rg -A 3 "backstretch|jquery.*backstretch" --type js

# Search for backstretch script inclusion
echo "=== Searching for backstretch script tags ==="
rg "backstretch" --type html

Length of output: 273830


Ensure Backstretch plugin is loaded before using it

The helper functions are available and the code is correct, but I didn’t find where the Backstretch plugin itself is included:

• metaToBool/getMeta are defined in sickchill/gui/slick/js/core.js
$.backstretch(...) is invoked in both core.js and music.js
• No <script> tag or JS file defining the Backstretch plugin was found

Action required:

  • Add or verify inclusion of the Backstretch plugin (e.g. jquery.backstretch.js) in your layout or main.mako before music.js is loaded so that $.backstretch is defined.
🤖 Prompt for AI Agents
In sickchill/gui/slick/js/music.js lines 1 to 11, the code uses the Backstretch
plugin via $.backstretch but the plugin script is not included anywhere. To fix
this, ensure the Backstretch plugin JavaScript file (e.g.,
jquery.backstretch.js) is added as a <script> tag in the main layout file such
as main.mako, and that it is loaded before music.js so that $.backstretch is
defined when used.

method = methods[length];

// Only stub undefined methods.
console[method] ||= noop;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use fallback syntax for broader browser compatibility.

The logical nullish assignment operator (||=) requires ES2021 support, which may not be available in older browsers.

-        console[method] ||= noop;
+        console[method] = console[method] || noop;
📝 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
console[method] ||= noop;
console[method] = console[method] || noop;
🤖 Prompt for AI Agents
In sickchill/gui/slick/js/musicRootDirs.js at line 38, replace the logical
nullish assignment operator (||=) with a more widely supported fallback syntax.
Instead of using console[method] ||= noop;, use a conditional check to assign
noop only if console[method] is falsy, ensuring compatibility with older
browsers that do not support ES2021 features.

Comment on lines +122 to +125
function postRootDirectories() {
refreshRootDirectories();
$.post(scRoot + '/config/general/saveMusicRootDirs', {musicRootDirString: $('#musicRootDirText').val()});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for the AJAX request.

The POST request lacks error handling, which could leave users unaware of save failures.

 function postRootDirectories() {
     refreshRootDirectories();
-    $.post(scRoot + '/config/general/saveMusicRootDirs', {musicRootDirString: $('#musicRootDirText').val()});
+    $.post(scRoot + '/config/general/saveMusicRootDirs', {musicRootDirString: $('#musicRootDirText').val()})
+        .fail(function(xhr, status, error) {
+            console.error('Failed to save music root directories:', error);
+            alert('Failed to save music root directories. Please try again.');
+        });
 }
📝 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
function postRootDirectories() {
refreshRootDirectories();
$.post(scRoot + '/config/general/saveMusicRootDirs', {musicRootDirString: $('#musicRootDirText').val()});
}
function postRootDirectories() {
refreshRootDirectories();
$.post(scRoot + '/config/general/saveMusicRootDirs', {musicRootDirString: $('#musicRootDirText').val()})
.fail(function(xhr, status, error) {
console.error('Failed to save music root directories:', error);
alert('Failed to save music root directories. Please try again.');
});
}
🤖 Prompt for AI Agents
In sickchill/gui/slick/js/musicRootDirs.js around lines 122 to 125, the $.post
AJAX call to saveMusicRootDirs lacks error handling, which can leave users
uninformed if the save operation fails. Modify the $.post call to include
.done() and .fail() handlers or use the .ajax() method with success and error
callbacks to handle the response properly. In the error handler, provide user
feedback such as an alert or UI message indicating the failure to save the music
root directories.

isDefault = true;
}

$('#musicRootDirs').append('<option value="' + path + '">' + path + '</option>');
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Escape user input to prevent XSS vulnerabilities.

The path is directly inserted into HTML without escaping, which could lead to XSS attacks if the path contains malicious content.

-        $('#musicRootDirs').append('<option value="' + path + '">' + path + '</option>');
+        const option = $('<option></option>').attr('value', path).text(path);
+        $('#musicRootDirs').append(option);
📝 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
$('#musicRootDirs').append('<option value="' + path + '">' + path + '</option>');
- $('#musicRootDirs').append('<option value="' + path + '">' + path + '</option>');
+ const option = $('<option></option>')
+ .attr('value', path)
+ .text(path);
+ $('#musicRootDirs').append(option);
🤖 Prompt for AI Agents
In sickchill/gui/slick/js/musicRootDirs.js at line 138, the user input variable
'path' is directly inserted into the HTML option element without escaping, which
can cause XSS vulnerabilities. To fix this, escape the 'path' string properly
before inserting it into the HTML, or use a safer method such as creating the
option element with jQuery and setting its text and value properties to avoid
direct HTML injection.

Comment on lines +205 to +209
refreshRootDirectories();
$.post(scRoot + '/config/general/saveMusicRootDirs', {
musicRootDirString: $('#musicRootDirText').val(),
});
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor duplicate AJAX calls and add error handling.

The same POST request is duplicated in multiple event handlers without error handling.

Instead of duplicating the AJAX call, reuse the postRootDirectories function which already calls refreshRootDirectories:

-        refreshRootDirectories();
-        $.post(scRoot + '/config/general/saveMusicRootDirs', {
-            musicRootDirString: $('#musicRootDirText').val(),
-        });
+        postRootDirectories();

Apply this change to both the delete handler (lines 205-209) and default handler (lines 216-220).

Also applies to: 216-220

🤖 Prompt for AI Agents
In sickchill/gui/slick/js/musicRootDirs.js around lines 205 to 209 and 216 to
220, the POST request to save music root directories is duplicated without error
handling. Replace the direct $.post calls in both event handlers with calls to
the existing postRootDirectories function, which already handles refreshing and
includes error handling. This will remove duplication and improve robustness.

Comment on lines +351 to +374

if front_images:
# Use the first front image
image_url = front_images[0]["image"]
logger.debug(f"Found front image for release group: {image_url}")

# Download the image
response = requests.get(image_url)
if response.status_code == 200:
image_data = response.content
else:
logger.warning(f"Failed to download image from {image_url}")
else:
# If no front images, try to use any image
if image_list["images"]:
image_url = image_list["images"][0]["image"]
logger.debug(f"No front image found, using first available: {image_url}")

# Download the image
response = requests.get(image_url)
if response.status_code == 200:
image_data = response.content
else:
logger.warning(f"Failed to download image from {image_url}")
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Extract duplicated image download logic.

The image download logic is duplicated multiple times in this method, making it harder to maintain.

Extract the image download logic into a helper method:

def _download_image(self, image_list, logger_prefix=""):
    """Download the first available image from an image list."""
    image_data = None
    
    # Check for front images first
    front_images = [img for img in image_list["images"] if img.get("front", False)]
    images_to_try = front_images if front_images else image_list["images"]
    
    if images_to_try:
        image_url = images_to_try[0]["image"]
        logger.debug(f"{logger_prefix}Found image: {image_url}")
        
        try:
            response = requests.get(image_url)
            if response.status_code == 200:
                image_data = response.content
            else:
                logger.warning(f"Failed to download image from {image_url}")
        except Exception as e:
            logger.warning(f"Error downloading image: {e}")
    
    return image_data

Then use it in both places to eliminate duplication.

Also applies to: 400-425

🤖 Prompt for AI Agents
In sickchill/providers/metadata/musicbrainz.py around lines 351 to 374, the
image download logic is duplicated when handling front images and fallback
images. Extract this logic into a helper method that accepts the image list and
an optional logger prefix, which filters front images first and attempts to
download the first available image with proper error handling and logging.
Replace the duplicated code blocks with calls to this helper method. Apply the
same refactoring to lines 400 to 425 where similar duplication occurs.

import os

from sickchill import settings
from sickchill.oldbeard import config
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove unused import.

The config module is imported but never used in this file.

-from sickchill.oldbeard import config
📝 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
from sickchill.oldbeard import config
🧰 Tools
🪛 Ruff (0.12.2)

5-5: sickchill.oldbeard.config imported but unused

Remove unused import: sickchill.oldbeard.config

(F401)

🤖 Prompt for AI Agents
In sickchill/views/music.py at line 5, the config module is imported but not
used anywhere in the file. Remove the line importing config from
sickchill.oldbeard to clean up unused imports.

Comment on lines +32 to +35
assert selected_root != "-1"
selected_root_dir = backend_dirs[int(selected_root)]
except (IndexError, ValueError, TypeError, AssertionError):
selected_root_dir = ""
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Replace assert with proper validation.

Using assert for input validation is problematic as assertions can be disabled with Python's -O flag. This could lead to unexpected behavior in production.

-                assert selected_root != "-1"
-                selected_root_dir = backend_dirs[int(selected_root)]
-            except (IndexError, ValueError, TypeError, AssertionError):
+                if selected_root == "-1":
+                    raise ValueError("Invalid root selection")
+                selected_root_dir = backend_dirs[int(selected_root)]
+            except (IndexError, ValueError, TypeError):
📝 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
assert selected_root != "-1"
selected_root_dir = backend_dirs[int(selected_root)]
except (IndexError, ValueError, TypeError, AssertionError):
selected_root_dir = ""
if selected_root == "-1":
raise ValueError("Invalid root selection")
selected_root_dir = backend_dirs[int(selected_root)]
except (IndexError, ValueError, TypeError):
selected_root_dir = ""
🤖 Prompt for AI Agents
In sickchill/views/music.py around lines 32 to 35, replace the assert statement
used for validating selected_root with explicit input validation logic. Instead
of using assert, check if selected_root is not equal to "-1" using a conditional
statement, and handle the invalid case appropriately to avoid relying on
assertions which can be disabled in production. Adjust the code to raise an
exception or set selected_root_dir to an empty string if the validation fails.

result = settings.music_list.add_from_musicbrainz(artist_id=artist_id)

# If result is an Artist object, use it
if hasattr(result, 'slug'):
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use proper type checking instead of attribute checking.

Using hasattr(result, 'slug') to determine if the result is an Artist object is fragile and could break if other objects gain a slug attribute.

-            if hasattr(result, 'slug'):
+            if isinstance(result, music.Artist):
📝 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
if hasattr(result, 'slug'):
if isinstance(result, music.Artist):
🤖 Prompt for AI Agents
In sickchill/views/music.py at line 86, replace the use of hasattr(result,
'slug') with proper type checking to verify if result is an Artist object. Use
an explicit isinstance check against the Artist class instead of relying on the
presence of the 'slug' attribute, ensuring more robust and clear type
validation.

import unittest
from unittest import mock

import musicbrainzngs
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove unused import.

The musicbrainzngs module is imported but never used directly in the tests. All its functionality is mocked.

-import musicbrainzngs
-
📝 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
import musicbrainzngs
🧰 Tools
🪛 Ruff (0.12.2)

8-8: musicbrainzngs imported but unused

Remove unused import: musicbrainzngs

(F401)

🤖 Prompt for AI Agents
In tests/test_music.py at line 8, remove the import statement for the
musicbrainzngs module since it is not used directly in the tests and all its
functionality is mocked, making the import unnecessary.

@Tensai75
Copy link

You coded this because you couldn't get Lidarr to work? Impressive!
But shouldn't this rather be a separate fork just for music? One app for one purpose...

@canepan
Copy link
Author

canepan commented Aug 12, 2025 via email

Copy link

This pull request is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.

@github-actions github-actions bot added the Stale Closing old issues with no activity - Comment to reopen if necessary label Sep 12, 2025
@github-actions github-actions bot closed this Sep 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Stale Closing old issues with no activity - Comment to reopen if necessary
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants