Overview
TagQt provides comprehensive lyrics management, including fetching from online sources, embedding in audio files, and exporting to .lrc files.
Lyrics Sources
TagQt fetches lyrics from lrclib.net , a free lyrics database:
# From tagqt/core/lyric.py - LyricsFetcher
import requests
class LyricsFetcher :
BASE_URL = "https://lrclib.net/api/search"
def search_lyrics ( self , artist , title , album = None ):
params = {
"q" : f " { artist } { title } " ,
}
try :
response = requests.get( self . BASE_URL , params = params, timeout = 10 )
response.raise_for_status()
data = response.json()
Lrclib.net provides both plain text and time-synced (LRC format) lyrics for millions of songs.
TagQt supports two lyrics formats:
Plain Lyrics Simple line-by-line lyrics without timing information
Synced Lyrics (LRC) Time-synced lyrics with timestamp markers for karaoke-style display
Search Results
The lyrics fetcher returns detailed results:
# From tagqt/core/lyric.py - Search results structure
results = []
for item in data:
results.append({
"id" : item.get( "id" ),
"trackName" : item.get( "trackName" ),
"artistName" : item.get( "artistName" ),
"albumName" : item.get( "albumName" ),
"duration" : item.get( "duration" ),
"syncedLyrics" : item.get( "syncedLyrics" ),
"plainLyrics" : item.get( "plainLyrics" ),
"isSynced" : bool (item.get( "syncedLyrics" ))
})
return results
Fetching Lyrics
Single File
For single files, use the interactive search dialog:
Click Lyrics Button
In the sidebar, click the “Get Lyrics” button or use the romanize button dropdown
Search Results
TagQt searches using the current Artist and Title tags
Select Result
Choose from multiple results, preferring synced lyrics when available
Load and Save
Lyrics are loaded into the editor. Don’t forget to save (Ctrl+S)!
# From tagqt/ui/main.py - Single file lyrics fetch
def fetch_lyrics ( self ):
if getattr ( self .sidebar, 'is_global_mode' , False ):
self .fetch_all_lyrics()
return
artist = self .sidebar.artist_edit.text()
title = self .sidebar.title_edit.text()
album = self .sidebar.album_edit.text()
from tagqt.ui.search import UnifiedSearchDialog
def search_callback ( a , t , al ):
return self .lyrics_fetcher.search_lyrics(a, t, al)
dialog = UnifiedSearchDialog(
self ,
mode = "lyrics" ,
initial_artist = artist,
initial_title = title,
initial_album = album,
fetcher_callback = search_callback
)
if dialog.exec() == QDialog.Accepted and dialog.selected_result:
res = dialog.selected_result
lyrics = res.get( "syncedLyrics" ) or res.get( "plainLyrics" )
if lyrics:
self .sidebar.lyrics_edit.setText(lyrics)
self .show_toast( "Lyrics loaded into editor. Don't forget to save!" )
Batch Lyrics Fetch
Fetch lyrics for multiple files:
Menu - Tools → Lyrics → Get Lyrics (All Visible)
Context Menu - Right-click selection → Get Lyrics (Selected)
Command Palette - Ctrl+K → “Get Lyrics (All)”
# From tagqt/ui/main.py - Batch lyrics fetch
def fetch_all_lyrics ( self ):
if getattr ( self .sidebar, 'is_global_mode' , False ):
files = self .get_selected_files()
else :
files = self .get_all_files()
self ._fetch_lyrics_list(files)
def _fetch_lyrics_list ( self , files ):
if not files:
dialogs.show_warning( self , "No Files" , "No files loaded." )
return
if not self ._prepare_batch( "Get Lyrics Status" ):
return
self .progress_bar.setRange( 0 , len (files))
self .progress_bar.setFormat( "Processing... 0%" )
self ._start_batch_worker(
LyricsWorker(files, self .lyrics_fetcher),
connect_log = True
)
Batch operations process files sequentially and display progress. Click the progress bar to view detailed results.
Lyrics Storage
Lyrics are stored in two locations:
Embedded in Audio Files
Lyrics are embedded using format-specific tags:
# From tagqt/core/tags.py - Lyrics getter
@ property
def lyrics ( self ):
"""Returns lyrics from tags."""
if self .audio is None :
return ""
try :
# ID3 (MP3)
if isinstance ( self .audio, ( ID3 , EasyID3)) or hasattr ( self .audio, 'tags' ):
tags = self .audio if isinstance ( self .audio, ID3 ) else self .audio.tags
if tags:
for key in tags.keys():
if key.startswith( 'USLT:' ):
return tags[key].text
# FLAC / Ogg
elif isinstance ( self .audio, ( FLAC , OggVorbis)):
if 'LYRICS' in self .audio:
return self .audio[ 'LYRICS' ][ 0 ]
# MP4
elif isinstance ( self .audio, MP4 ):
if ' \xa9 lyr' in self .audio:
return self .audio[ ' \xa9 lyr' ][ 0 ]
except Exception as e:
print ( f "Error getting lyrics: { e } " )
return ""
Lyrics Setter (Embedding)
# From tagqt/core/tags.py - Lyrics setter
@lyrics.setter
def lyrics ( self , value ):
"""Sets lyrics to tags."""
if self .audio is None :
return
try :
# ID3 (MP3)
if isinstance ( self .audio, ( ID3 , EasyID3)):
tags = self .audio if isinstance ( self .audio, ID3 ) else self .audio.tags
if tags is not None :
# Remove existing
to_del = [k for k in tags.keys() if k.startswith( 'USLT:' )]
for k in to_del:
del tags[k]
if value:
tags.add(USLT(
encoding = 3 ,
lang = 'eng' , # Default to English
desc = '' ,
text = value
))
# FLAC
elif isinstance ( self .audio, ( FLAC , OggVorbis)):
if value:
self .audio[ 'LYRICS' ] = value
elif 'LYRICS' in self .audio:
del self .audio[ 'LYRICS' ]
# MP4
elif isinstance ( self .audio, MP4 ):
if value:
self .audio[ ' \xa9 lyr' ] = value
elif ' \xa9 lyr' in self .audio:
del self .audio[ ' \xa9 lyr' ]
except Exception as e:
print ( f "Error setting lyrics: { e } " )
External .lrc Files
Lyrics are automatically saved to .lrc files:
# From tagqt/core/tags.py - Auto-save to .lrc
def save ( self ):
if self .audio:
self .audio.save()
# Auto-save lyrics to .lrc if present
if self .lyrics:
self .save_lyrics_file()
def save_lyrics_file ( self ):
"""Saves lyrics to a .lrc file with the same name as the audio file."""
if not self .lyrics:
return
try :
base_path = os.path.splitext( self .filepath)[ 0 ]
lrc_path = base_path + ".lrc"
with open (lrc_path, 'w' , encoding = 'utf-8' ) as f:
f.write( self .lyrics)
except Exception as e:
print ( f "Error saving lyrics file: { e } " )
Embedded Lyrics Stored inside the audio file
Portable with the file
Supported by most players
Takes no extra space
.lrc Files Separate text file
Easy to edit manually
Compatible with karaoke apps
Same name as audio file
Format Tag/Frame Type MP3 USLTUnsynchronized Lyrics frame FLAC LYRICSVorbis comment OGG LYRICSVorbis comment M4A/MP4 ©lyriTunes atom
Loading Lyrics from File
Manually load lyrics from local files:
Click Load Lyrics
Use the dropdown next to the lyrics buttons
Select File
Choose a .lrc or .txt file
Lyrics Loaded
Text is loaded into the lyrics editor
Save Changes
Press Ctrl+S to save to the audio file
# From tagqt/ui/main.py - Load from file
def load_lyrics_from_file ( self ):
file_path, _ = QFileDialog.getOpenFileName(
self ,
"Select Lyrics File" ,
"" ,
"Lyrics (*.lrc *.txt)"
)
if file_path:
try :
with open (file_path, 'r' , encoding = 'utf-8' ) as f:
lyrics = f.read()
self .sidebar.lyrics_edit.setText(lyrics)
except Exception as e:
dialogs.show_error( self , "Error" , f "Error loading lyrics: { e } " )
Romanization (Korean)
For Korean lyrics, TagQt supports romanization:
Romanization requires the optional koroman package:
Single File Romanization
# From tagqt/ui/main.py - Single file romanize
def romanize_metadata ( self ):
if getattr ( self .sidebar, 'is_global_mode' , False ):
files = self .get_selected_files()
if files:
self ._romanize_list(files)
return
available, msg = DependencyChecker.check_koroman()
if not available:
dialogs.show_error( self , "Missing Dependency" , msg)
return
self .sidebar.lyrics_edit.setText(
self .romanizer.romanize_text( self .sidebar.lyrics_edit.toPlainText())
)
Batch Romanization
Menu - Tools → Lyrics → Romanize Lyrics (All Visible)
Context Menu - Right-click → Romanize Lyrics (Selected)
Command Palette - Ctrl+K → “Romanize Lyrics”
# From tagqt/ui/main.py - Batch romanize
def _romanize_list ( self , files ):
available, msg = DependencyChecker.check_koroman()
if not available:
dialogs.show_error( self , "Missing Dependency" , msg)
return
if not files:
dialogs.show_warning( self , "No Files" , "No files loaded." )
return
if not self ._prepare_batch( "Romanize Status" ):
return
self .progress_bar.setRange( 0 , len (files))
self .progress_bar.setFormat( "Romanizing... 0%" )
self ._start_batch_worker(RomanizeWorker(files, self .romanizer))
Lyrics Editor
The sidebar provides a multi-line text editor for lyrics:
Editing - Full text editing support
Formatting - Preserves line breaks and LRC timestamps
Undo/Redo - Standard text editing features
Copy/Paste - Import from external sources
Best Practices
Ensure Artist and Title tags are accurate
Try alternative spellings if no results
Prefer synced lyrics for better player compatibility
Check album name if multiple versions exist
Always save after fetching or editing (Ctrl+S)
.lrc files are created automatically when saving
Manually edit lyrics in the sidebar for corrections
Use romanization for Korean content
Use filters to scope operations to specific albums/artists
Review batch results dialog for failures
Failed fetches may indicate missing/incorrect tags
Lyrics → Get Lyrics (All Visible) - Batch fetch
Lyrics → Romanize Lyrics (All Visible) - Batch romanize
Get Lyrics - Fetch for current file
Romanize - Romanize current lyrics
Load Lyrics - Load from file
Get Lyrics (Selected)
Romanize Lyrics (Selected)
Next Steps
Batch Operations Process multiple files efficiently
Cover Art Manage album artwork