Skip to main content

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.

Lyrics Formats

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:
1

Click Lyrics Button

In the sidebar, click the “Get Lyrics” button or use the romanize button dropdown
2

Search Results

TagQt searches using the current Artist and Title tags
3

Select Result

Choose from multiple results, preferring synced lyrics when available
4

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 '\xa9lyr' in self.audio:
                return self.audio['\xa9lyr'][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['\xa9lyr'] = value
            elif '\xa9lyr' in self.audio:
                del self.audio['\xa9lyr']
    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-Specific Tags

FormatTag/FrameType
MP3USLTUnsynchronized Lyrics frame
FLACLYRICSVorbis comment
OGGLYRICSVorbis comment
M4A/MP4©lyriTunes atom

Loading Lyrics from File

Manually load lyrics from local files:
1

Click Load Lyrics

Use the dropdown next to the lyrics buttons
2

Select File

Choose a .lrc or .txt file
3

Lyrics Loaded

Text is loaded into the lyrics editor
4

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:
pip install koroman

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

Tools Menu

  • 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

Context Menu

  • Get Lyrics (Selected)
  • Romanize Lyrics (Selected)

Next Steps

Batch Operations

Process multiple files efficiently

Cover Art

Manage album artwork

Build docs developers (and LLMs) love