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 upgrades the application's lyrics functionality, focusing on improving the user experience for displaying, synchronizing, and managing lyrics. Key changes include real-time synced lyrics updates with highlighting and auto-scrolling, a new dedicated lyrics column in the playlist for better visibility and management, and more accurate audio timestamp processing to ensure precise lyric synchronization. The underlying lyrics storage and retrieval mechanisms have also been refined to handle synced lyrics more effectively.

Highlights

  • Enhanced Lyrics Display and Synchronization: The lyrics component now dynamically indicates the type of lyrics (Synced, Plain Text, Instrumental, or No Lyrics). During playback, currently active synced lyric lines are highlighted and automatically scrolled into view for a smoother user experience.
  • Playlist Table Lyrics Column: A new 'Lyrics' column has been added to the playlist table. It displays a musical note icon if lyrics are available for a track and provides a convenient button to remove them directly from the file's metadata.
  • Improved Audio Timestamp Accuracy: The audio engine now sends timestamp updates more frequently (every 100ms instead of 1 second) and accurately converts packet timestamps to milliseconds using the track's timebase, which is crucial for precise synced lyrics.
  • Robust Track Duration Estimation: The application now estimates track duration based on file size, ensuring a more reliable duration display, especially for files where duration metadata might be missing or inaccurate. A minimum duration is also enforced.
  • Lyrics Storage and Removal Logic: The LyricsService has been updated to intelligently detect and prioritize storing synced lyrics in ID3 tags. A new function allows users to remove all lyrics metadata from an audio file.
  • Internationalization Updates: New internationalization keys for the 'Lyrics' column and 'Remove lyrics' action have been added for both English and Chinese languages.
  • Time Format Correction: The player component's time slider now correctly formats time as MM:SS instead of HH:MM.
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 several valuable enhancements to the lyrics feature, such as support for synced lyrics, a lyrics type indicator, and the ability to remove lyrics. The UI responsiveness improvements for synced lyrics are also a great addition. However, I've identified some significant performance issues and a few bugs that need to be addressed. The most critical issue is a performance bottleneck in the playlist view, which performs file I/O for every track on every frame, likely causing UI freezes. There are also bugs related to playlist column configuration and track duration calculation that could impact user experience. I've provided detailed comments and suggestions for resolving these issues.

Comment on lines +663 to +668
let has_lyrics = crate::app::lyrics::LyricsService::read_lyrics_from_file(
track.path(),
&track_artist,
&track_title,
)
.is_some();

Choose a reason for hiding this comment

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

critical

This code calls read_lyrics_from_file for every track in the playlist on every single UI frame. This performs repeated file I/O on the main UI thread, which is a major performance bottleneck. It will cause the UI to freeze or stutter, especially with larger playlists.

The lyrics status should be determined once when the track is added to the library and stored as a property on the LibraryItem. The UI should then just read this boolean flag.

Here's a suggested approach:

  1. Add a has_lyrics: bool field to the LibraryItem struct.
  2. When scanning music files and creating LibraryItems, check for lyrics and set this flag. This can be done by checking for the presence of the lyrics tag without reading the full content.
  3. Store this flag in the database along with other track metadata.
  4. In this component, simply read the track.has_lyrics boolean instead of performing file I/O.

Comment on lines +116 to 117
let column_proportions = [0.05, 0.30, 0.18, 0.20, 0.12, 0.15];
let num_columns = 5; // Changed from 6 to 5 (we don't need empty columns)

Choose a reason for hiding this comment

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

high

The num_columns is hardcoded to 5, but you've added a new "Lyrics" column, making the total 6. This will cause layout issues in the egui::Grid as it's expecting a different number of columns than what is being provided per row. The comment on this line is also now incorrect and misleading.

Suggested change
let column_proportions = [0.05, 0.30, 0.18, 0.20, 0.12, 0.15];
let num_columns = 5; // Changed from 6 to 5 (we don't need empty columns)
let column_proportions = [0.05, 0.30, 0.18, 0.20, 0.12, 0.15];
let num_columns = column_proportions.len();

Comment on lines +674 to +681
if ui.small_button("❌").on_hover_text(t("remove_lyrics")).clicked() {
// Remove lyrics from the file
if let Err(e) = crate::app::lyrics::LyricsService::remove_lyrics_from_file(track.path()) {
tracing::error!("Failed to remove lyrics from file: {}", e);
} else {
tracing::info!("Successfully removed lyrics from file: {:?}", track.path());
}
}

Choose a reason for hiding this comment

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

high

The remove_lyrics_from_file function is called directly within the UI update loop when the button is clicked. This performs a file write operation on the UI thread, which can block it and make the application unresponsive, especially with slow storage or network drives. This operation should be moved to a background thread, similar to how other long-running tasks are handled in the application.

Comment on lines +698 to +699
// Ensure minimum duration for music files (2 minutes = 120,000 ms)
audio_engine_state.duration = estimated_duration_ms.max(120000);

Choose a reason for hiding this comment

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

high

Enforcing a minimum duration of 2 minutes (120000 ms) is incorrect. This will cause any song shorter than 2 minutes to have an inaccurate duration displayed, and the seek bar will behave incorrectly. It's better to use the estimated duration, even if it's short.

Suggested change
// Ensure minimum duration for music files (2 minutes = 120,000 ms)
audio_engine_state.duration = estimated_duration_ms.max(120000);
// Set the duration from the estimation
audio_engine_state.duration = estimated_duration_ms;

Comment on lines +270 to +276
let is_synced = lyrics_frame.description == "SyncedLyrics"
|| text.contains("[00:")
|| text.contains("[01:")
|| text.contains("[02:")
|| text.contains("[03:")
|| text.contains("[04:")
|| text.contains("[05:");

Choose a reason for hiding this comment

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

medium

The current check for synced lyrics is a bit brittle as it only checks for timestamps in the first 6 minutes of a song (e.g., [00: to [05:). This might fail for longer songs or different LRC formats. A more robust heuristic would be to check for the general LRC timestamp pattern on any line.

Suggested change
let is_synced = lyrics_frame.description == "SyncedLyrics"
|| text.contains("[00:")
|| text.contains("[01:")
|| text.contains("[02:")
|| text.contains("[03:")
|| text.contains("[04:")
|| text.contains("[05:");
let is_synced = lyrics_frame.description == "SyncedLyrics"
|| text.lines().any(|l| {
let trimmed = l.trim_start();
trimmed.starts_with('[') && trimmed.contains(':') && trimmed.contains(']')
});

Comment on lines +682 to 707
let file_size_bytes = std::fs::metadata(path).map(|m| m.len()).unwrap_or(0);
let mut estimated_duration_ms = 0;

if file_size_bytes > 100000 {
// Only use file size if it's a reasonable size (>100KB)
// Assume 160 kbps MP3 = 160 * 1024 / 8 = 20480 bytes per second
let bytes_per_second = 160 * 1024 / 8; // 20480
estimated_duration_ms = (file_size_bytes * 1000) / bytes_per_second as u64;
tracing::debug!(
"Estimated duration from file size: {} ms (file size: {} bytes, {} bytes/sec)",
estimated_duration_ms,
file_size_bytes,
bytes_per_second
);
}

// Ensure minimum duration for music files (2 minutes = 120,000 ms)
audio_engine_state.duration = estimated_duration_ms.max(120000);

if estimated_duration_ms < 120000 {
tracing::debug!(
"Using minimum duration: {} ms (estimated was {} ms)",
audio_engine_state.duration,
estimated_duration_ms
);
}

Choose a reason for hiding this comment

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

medium

This block uses several magic numbers for duration estimation (100000, 160, 120000). It's better to define these as named constants to improve readability and make them easier to change in the future.

@RetricSu RetricSu merged commit 4b22af4 into develop Oct 21, 2025
4 checks passed
@RetricSu RetricSu deleted the more-lyrics-feature branch October 21, 2025 23:29
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