Overview
TagQt provides comprehensive cover art management, including downloading from multiple sources, embedding in audio files, and exporting to image files.
Cover Art Sources
The CoverArtManager fetches artwork from two primary sources:
MusicBrainz/Cover Art Archive High-quality official release artwork from the Cover Art Archive
iTunes API Album artwork from Apple’s iTunes Store database
# From tagqt/core/art.py - CoverArtManager initialization
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
class CoverArtManager :
ITUNES_API_URL = "https://itunes.apple.com/search"
def __init__ ( self ):
self .session = requests.Session()
retries = Retry(
total = 3 ,
backoff_factor = 1 ,
status_forcelist = [ 500 , 502 , 503 , 504 ]
)
self .session.mount( 'https://' , HTTPAdapter( max_retries = retries))
self .session.mount( 'http://' , HTTPAdapter( max_retries = retries))
Downloading Cover Art
Single File Search
For single files, use the interactive search dialog:
Click Cover Button
In the sidebar, click the “Get Cover” button
Search Sources
TagQt searches MusicBrainz and iTunes using Artist and Album tags
Preview Results
Browse multiple cover art candidates with previews
Select and Apply
Choose a cover and it’s automatically embedded in the file
# From tagqt/ui/main.py - Single file cover search
def search_cover ( self ):
if getattr ( self .sidebar, 'is_global_mode' , False ):
self .fetch_all_covers()
return
artist = self .sidebar.artist_edit.text()
album = self .sidebar.album_edit.text()
from tagqt.ui.search import UnifiedSearchDialog
def search_callback ( a , al ):
return self .cover_manager.search_cover_candidates(a, al)
dialog = UnifiedSearchDialog(
self ,
mode = "cover" ,
initial_artist = artist,
initial_album = album,
fetcher_callback = search_callback
)
if dialog.exec() == QDialog.Accepted and dialog.selected_result:
res = dialog.selected_result
url = res.get( "url" )
try :
data = self .cover_manager.download_and_process_cover(url)
if data:
pixmap = QPixmap()
pixmap.loadFromData(data)
self .sidebar.set_cover(pixmap)
if self .metadata:
self .metadata.set_cover(data)
except Exception as e:
dialogs.show_error( self , "Error" , f "Error downloading cover: { e } " )
Batch Cover Download
Download covers for multiple files:
Menu - Tools → Covers → Get Covers (All Visible)
Context Menu - Right-click selection → Get Covers (Selected)
Command Palette - Ctrl+K → “Get Covers (All)“
# From tagqt/ui/main.py - Batch cover fetch
def fetch_all_covers ( self ):
if getattr ( self .sidebar, 'is_global_mode' , False ):
files = self .get_selected_files()
else :
files = self .get_all_files()
self ._fetch_covers_list(files)
def _fetch_covers_list ( self , files ):
if not files:
dialogs.show_warning( self , "No Files" , "No files loaded." )
return
if not self ._prepare_batch( "Get Covers Status" ):
return
self .progress_bar.setRange( 0 , len (files))
self .progress_bar.setFormat( "Processing... 0%" )
self ._start_batch_worker(CoverFetchWorker(files, self .cover_manager))
Source Priority
The cover art manager uses intelligent source prioritization:
# From tagqt/core/art.py - Source priority
def search_cover ( self , artist , album ):
# Try MusicBrainz first, then iTunes
url = self .search_cover_musicbrainz(artist, album)
if url:
return url
return self .search_cover_itunes(artist, album)
MusicBrainz/Cover Art Archive
# From tagqt/core/art.py - MusicBrainz search
def search_cover_musicbrainz ( self , artist , album ):
def do_search ():
query = f 'artist:" { artist } " AND release:" { album } "'
url = "https://musicbrainz.org/ws/2/release-group"
params = {
"query" : query,
"fmt" : "json"
}
headers = { "User-Agent" : "tagqt/1.0 ( [email protected] )" }
response = self .session.get(url, params = params, headers = headers, timeout = 10 )
response.raise_for_status()
return response.json()
data = self ._retry(do_search)
if data and data.get( "release-groups" ):
rg_id = data[ "release-groups" ][ 0 ][ "id" ]
# Get Cover Art from Cover Art Archive
cover_url = f "https://coverartarchive.org/release-group/ { rg_id } /front"
return cover_url
return None
iTunes API
# From tagqt/core/art.py - iTunes search
def search_cover_itunes ( self , artist , album ):
cands = self .search_cover_candidates(artist, album)
if cands:
return cands[ 0 ][ "url" ]
return None
Cover Art Candidates
The search returns multiple candidates for manual selection:
# From tagqt/core/art.py - Getting candidates
def search_cover_candidates ( self , artist , album ):
"""Returns a list of cover candidates."""
candidates = []
# iTunes
try :
params = {
"term" : f " { artist } { album } " ,
"media" : "music" ,
"entity" : "album" ,
"limit" : 5 # Get multiple results
}
response = self .session.get( self . ITUNES_API_URL , params = params, timeout = 10 )
response.raise_for_status()
data = response.json()
for item in data.get( "results" , []):
url = item.get( "artworkUrl100" )
if url:
# Upgrade to high resolution
url = url.replace( "100x100bb" , "1000x1000bb" )
candidates.append({
"album" : item.get( "collectionName" ),
"artist" : item.get( "artistName" ),
"url" : url,
"source" : "iTunes" ,
"size" : "1000x1000"
})
except Exception as e:
print ( f "Error searching iTunes candidates: { e } " )
# MusicBrainz
try :
mb_url = self .search_cover_musicbrainz(artist, album)
if mb_url:
candidates.append({
"album" : album,
"artist" : artist,
"url" : mb_url,
"source" : "MusicBrainz" ,
"size" : "Unknown"
})
except Exception :
pass
return candidates
Image Processing
Cover art is automatically processed when downloaded:
# From tagqt/core/art.py - Download and process
def download_and_process_cover ( self , url ):
def do_download ():
response = self .session.get(url, timeout = 15 )
response.raise_for_status()
return response.content
content = self ._retry(do_download)
if not content:
return None
try :
img = Image.open(BytesIO(content))
img = img.convert( "RGB" )
img = img.resize(( 500 , 500 ), Image.Resampling. LANCZOS )
output = BytesIO()
img.save(output, format = "JPEG" , quality = 90 )
return output.getvalue()
except Exception as e:
print ( f "Error processing cover: { e } " )
return None
Covers are automatically resized to 500x500 pixels and converted to JPEG format for optimal file size and compatibility.
Embedding Cover Art
Covers are embedded using format-specific methods:
# From tagqt/core/tags.py - Setting cover art
def set_cover ( self , data , max_size = 500 ):
"""Sets the cover art, optionally resizing it."""
if self .audio is None or not data:
return
# Resize if needed
if max_size and max_size > 0 :
try :
img = Image.open(io.BytesIO(data))
if img.width > max_size or img.height > max_size:
img.thumbnail((max_size, max_size), Image.Resampling. LANCZOS )
output = io.BytesIO()
img = img.convert( 'RGB' )
img.save(output, format = 'JPEG' , quality = 85 )
data = output.getvalue()
except Exception as e:
print ( f "Error resizing cover: { e } " )
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( 'APIC:' )]
for k in to_del:
del tags[k]
tags.add(APIC(
encoding = 3 ,
mime = 'image/jpeg' ,
type = 3 ,
desc = 'Cover' ,
data = data
))
# FLAC
elif isinstance ( self .audio, ( FLAC , OggVorbis)):
self .audio.clear_pictures()
p = Picture()
p.data = data
p.type = 3
p.mime = "image/jpeg"
self .audio.add_picture(p)
# MP4
elif isinstance ( self .audio, MP4 ):
self .audio[ 'covr' ] = [MP4Cover(data, imageformat = MP4Cover. FORMAT_JPEG )]
except Exception as e:
print ( f "Error setting cover: { e } " )
Read embedded cover art from files:
# From tagqt/core/tags.py - Getting cover art
def get_cover ( self ):
if self .audio is None :
return None
try :
# ID3 (MP3)
if isinstance ( self .audio, ( ID3 , EasyID3)):
tags = self .audio
if hasattr (tags, '_ID3__id3' ): # EasyID3 internal
tags = tags. _ID3__id3
for key in tags.keys():
if key.startswith( 'APIC:' ):
return tags[key].data
# FLAC / Ogg
if isinstance ( self .audio, ( FLAC , OggVorbis)):
if hasattr ( self .audio, 'pictures' ) and self .audio.pictures:
return self .audio.pictures[ 0 ].data
# MP4
elif isinstance ( self .audio, MP4 ):
if 'covr' in self .audio and self .audio[ 'covr' ]:
return bytes ( self .audio[ 'covr' ][ 0 ])
except Exception as e:
print ( f "Error getting cover: { e } " )
return None
Exporting to Files
Covers are automatically exported to cover.jpg when saving:
# From tagqt/core/tags.py - Saving cover to file
def save_cover_file ( self , data = None , overwrite = True ):
"""
Saves cover art to cover.jpg in the same directory.
If data is provided, uses it. Otherwise tries to get from tags.
If overwrite is False, checks if file exists first.
"""
try :
if data is None :
data = self .get_cover()
if not data:
return
dir_path = os.path.dirname( self .filepath)
cover_path = os.path.join(dir_path, "cover.jpg" )
if not overwrite and os.path.exists(cover_path):
return
with open (cover_path, 'wb' ) as f:
f.write(data)
except Exception as e:
print ( f "Error saving cover file: { e } " )
Embedded Covers Stored inside audio files
Portable with the file
Displayed in music players
No external dependencies
cover.jpg Files External image files
Easy to view/edit
Used by media servers
Shared across album
Format Tag/Frame Type MP3 APICAttached Picture frame FLAC picturesPicture block OGG picturesPicture block M4A/MP4 covrCover art atom
Resizing Covers
Resize embedded cover art to reduce file size:
Single File
Automatic when downloading (500x500, quality 90)
Automatic when setting from file (500x500, quality 85)
Batch Resize
Menu - Tools → Covers → Resize Covers (Selected/All Visible)
Context Menu - Right-click → Resize Covers (Selected)
Command Palette - Ctrl+K → “Resize Covers”
# From tagqt/ui/main.py - Batch resize
def _resize_covers ( self , files ):
if not self ._prepare_batch( "Resize Covers Status" ):
return
self .progress_bar.setRange( 0 , len (files))
self .progress_bar.setFormat( "Resizing... 0%" )
self ._start_batch_worker(CoverResizeWorker(files))
Resizing is destructive - original cover art is replaced with the resized version. Ensure you have backups if needed.
Loading from File
Manually load cover art from local image files:
Click Load Cover
Use the dropdown next to the cover buttons
Select Image
Choose a PNG, JPG, JPEG, or BMP file
Cover Loaded
Image is displayed in the sidebar
Save Changes
Press Ctrl+S to embed in the audio file
# From tagqt/ui/main.py - Load cover from file
def load_cover_from_file ( self ):
file_path, _ = QFileDialog.getOpenFileName(
self ,
"Select Cover Image" ,
"" ,
"Images (*.png *.jpg *.jpeg *.bmp)"
)
if file_path:
try :
with open (file_path, 'rb' ) as f:
data = f.read()
# Validate image
pixmap = QPixmap()
if pixmap.loadFromData(data):
self .sidebar.set_cover(pixmap)
if self .metadata:
self .metadata.set_cover(data)
else :
dialogs.show_warning( self , "Invalid Image" ,
"The selected file is not a valid image." )
except Exception as e:
dialogs.show_error( self , "Error" , f "Error loading cover: { e } " )
Error Handling
Robust retry logic handles network issues:
# From tagqt/core/art.py - Retry mechanism
def _retry ( self , func , max_retries = 3 ):
for attempt in range (max_retries):
try :
return func()
except (requests.exceptions.RequestException, OSError ) as e:
if attempt < max_retries - 1 :
time.sleep( 2 ** attempt) # Exponential backoff
continue
print ( f "Network error after { max_retries } retries: { e } " )
return None
return None
Best Practices
Ensure Artist and Album tags are accurate
iTunes typically has better availability
MusicBrainz/CAA has higher quality official artwork
Try alternative spellings if no results
Always save after setting cover art (Ctrl+S)
Use 500x500 or smaller for optimal file size
JPEG format provides best size/quality balance
cover.jpg files are created automatically
Use filters to scope operations
Review batch results for failures
Failed downloads may indicate missing tags
Consider resizing to reduce collection size
Covers → Get Covers (All Visible) - Batch download
Covers → Resize Covers (Selected) - Resize selected
Covers → Resize Covers (All Visible) - Resize all
Get Cover - Search and download for current file
Load Cover - Load from local file
Get Covers (Selected)
Resize Covers (Selected)
Next Steps
Batch Operations Process multiple files efficiently