Skip to content

Conversation

@RetricSu
Copy link
Owner

No description provided.

@gemini-code-assist
Copy link

Summary of Changes

Hello @RetricSu, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the application's lyrics management by introducing a robust caching mechanism that utilizes ID3 tags within audio files. Upon successful retrieval, lyrics are now automatically embedded into the track's metadata. Consequently, the application will first attempt to load lyrics from these embedded tags, thereby minimizing reliance on external API services and providing a more responsive and offline-capable user experience.

Highlights

  • Lyrics Caching: Fetched lyrics are now stored directly in the ID3 tags of audio files, enabling offline access and faster retrieval.
  • ID3 Tag Integration: New functions read_lyrics_from_file and write_lyrics_to_file have been added to the LyricsService to manage lyrics persistence within ID3 tags.
  • Optimized Lyrics Fetching: The application now prioritizes checking for cached lyrics in ID3 tags before initiating external API calls, reducing network requests and improving performance.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a great feature for caching lyrics in ID3 tags to avoid repeated API calls. The implementation is solid, but I've identified a couple of issues and areas for improvement. The main concern is a bug where empty lyrics could be written to and read from the cache, which would prevent fetching actual lyrics and result in a poor user experience. I've provided suggestions to fix this by adding checks for empty lyrics. Additionally, I've included a few recommendations to refactor the code for better readability and to adhere to common Rust idioms.

Comment on lines +268 to +289
if let Some(lyrics_frame) = tag.lyrics().next() {
let text = lyrics_frame.text.clone();
tracing::debug!("📖 Found lyrics in ID3 tag ({} characters)", text.len());

// Create a basic Lyrics struct from the ID3 lyrics
// We don't have all the metadata, so we'll create a minimal structure
Some(Lyrics {
id: 0, // Not applicable for cached lyrics
name: "Cached Lyrics".to_string(),
track_name: title.to_string(),
artist_name: artist.to_string(),
album_name: None,
duration: None,
instrumental: false,
plain_lyrics: Some(text),
synced_lyrics: None,
lines: Vec::new(), // We could potentially store synced lyrics differently
})
} else {
tracing::debug!("📖 No lyrics found in ID3 tag");
None
}

Choose a reason for hiding this comment

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

high

There's a potential issue here where lyrics text from an ID3 tag could be empty or only contain whitespace. If this happens, the function will return a Lyrics object with empty plain_lyrics. This can lead to showing an empty lyrics panel to the user and preventing a fetch for actual lyrics from the API.

To fix this, you should check if the lyrics text is empty after reading it from the tag. If it is, you should return None, treating it the same as if no lyrics were found.

                if let Some(lyrics_frame) = tag.lyrics().next() {
                    let text = lyrics_frame.text.clone();
                    if text.trim().is_empty() {
                        tracing::debug!("📖 Found empty lyrics in ID3 tag, ignoring.");
                        None
                    } else {
                        tracing::debug!("📖 Found lyrics in ID3 tag ({} characters)", text.len());

                        // Create a basic Lyrics struct from the ID3 lyrics
                        // We don't have all the metadata, so we'll create a minimal structure
                        Some(Lyrics {
                            id: 0, // Not applicable for cached lyrics
                            name: "Cached Lyrics".to_string(),
                            track_name: title.to_string(),
                            artist_name: artist.to_string(),
                            album_name: None,
                            duration: None,
                            instrumental: false,
                            plain_lyrics: Some(text),
                            synced_lyrics: None,
                            lines: Vec::new(), // We could potentially store synced lyrics differently
                        })
                    }
                } else {
                    tracing::debug!("📖 No lyrics found in ID3 tag");
                    None
                }

Comment on lines +320 to +329
let lyrics_text = if let Some(plain) = &lyrics.plain_lyrics {
plain.clone()
} else if let Some(synced) = &lyrics.synced_lyrics {
// For synced lyrics, we could store them as-is, but for now just store the plain version
// In a more advanced implementation, we could store synced lyrics in a custom frame
synced.clone()
} else {
tracing::warn!("📝 No lyrics content to write to file");
return Ok(()); // Nothing to write
};

Choose a reason for hiding this comment

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

high

This logic for selecting lyrics to write to the file can be simplified. More importantly, it doesn't prevent writing empty or whitespace-only lyrics to the file, which can cause issues when reading them back from the cache.

I suggest refactoring this to be more concise and to add a check to ensure we don't write empty lyrics.

Suggested change
let lyrics_text = if let Some(plain) = &lyrics.plain_lyrics {
plain.clone()
} else if let Some(synced) = &lyrics.synced_lyrics {
// For synced lyrics, we could store them as-is, but for now just store the plain version
// In a more advanced implementation, we could store synced lyrics in a custom frame
synced.clone()
} else {
tracing::warn!("📝 No lyrics content to write to file");
return Ok(()); // Nothing to write
};
let lyrics_text = match lyrics.plain_lyrics.as_ref().or(lyrics.synced_lyrics.as_ref()) {
Some(text) if !text.trim().is_empty() => text.clone(),
_ => {
tracing::warn!("📝 No lyrics content or empty lyrics, not writing to file");
return Ok(()); // Nothing to write
}
};

Comment on lines +61 to +76
if let Some(lyrics_data) = &self.current_lyrics {
if let Some(player) = &self.player {
if let Some(track) = &player.selected_track {
if let Err(e) =
crate::app::lyrics::LyricsService::write_lyrics_to_file(
track.path(),
lyrics_data,
)
{
tracing::warn!("⚠️ Failed to cache lyrics in ID3 tag: {}", e);
} else {
tracing::info!("📝 Successfully cached lyrics in ID3 tag");
}
}
}
}

Choose a reason for hiding this comment

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

medium

The nested if let statements here can be flattened to improve readability. You can use Option::and_then to chain the checks for player and selected_track, which makes the code more concise and less nested.

                    if let Some(lyrics_data) = &self.current_lyrics {
                        if let Some(track) = self.player.as_ref().and_then(|p| p.selected_track.as_ref()) {
                            if let Err(e) =
                                crate::app::lyrics::LyricsService::write_lyrics_to_file(
                                    track.path(),
                                    lyrics_data,
                                )
                            {
                                tracing::warn!("⚠️  Failed to cache lyrics in ID3 tag: {}", e);
                            } else {
                                tracing::info!("📝 Successfully cached lyrics in ID3 tag");
                            }
                        }
                    }

Comment on lines +332 to +337
use id3::frame::Lyrics;
tag.add_frame(Lyrics {
lang: "eng".to_string(),
description: "Lyrics".to_string(),
text: lyrics_text,
});

Choose a reason for hiding this comment

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

medium

Using a use statement inside a function is generally discouraged in Rust as it can make it harder to see where a type is coming from. It's better to qualify the type directly at the point of use.

In this case, since Lyrics is also the name of a struct in this module and a parameter to the function, qualifying it with id3::frame::Lyrics makes the code clearer.

        tag.add_frame(id3::frame::Lyrics {
            lang: "eng".to_string(),
            description: "Lyrics".to_string(),
            text: lyrics_text,
        });

@RetricSu RetricSu merged commit d06df12 into develop Oct 20, 2025
4 checks passed
@RetricSu RetricSu deleted the embed-lyrics branch October 20, 2025 12:39
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