Add Discogs artwork selection UI in album edit modal

Backend:
- Add DiscogsImageOption struct for artwork options
- Add discogs_search_artwork command to search multiple images
- Add discogs_download_artwork command to download selected image
- Extend DiscogsClient with search_artwork_options() method
- Extend DiscogsClient with download_artwork_from_url() method
- Register new commands in lib.rs

Frontend:
- Add Discogs image selection to album edit modal
- Add 'Fetch from Discogs' button with Discogs logo
- Display grid of artwork options from Discogs search results
- Allow user to select preferred artwork option
- Download and save selected artwork on Save
- Add comprehensive CSS styling for Discogs UI elements
- Adjust Discogs logo color filter for proper contrast

User can now click 'Fetch from Discogs', browse multiple artwork
options, select one, and save it as the album's cover art.
This commit is contained in:
vicrodh committed 2026-01-17 00:12:14 -06:00
1 parent dd70f0a8c4
commit dab5c28727
5 files changed
+315 -9

No files matched your search

+99
View File
@@ -32,6 +32,16 @@ pub struct SearchResult {
pub result_type: String,
}
/// Image option for artwork selection
#[derive(Debug, Deserialize, serde::Serialize, Clone)]
pub struct DiscogsImageOption {
pub url: String,
pub width: u32,
pub height: u32,
#[serde(rename = "type")]
pub image_type: String,
}
impl DiscogsClient {
/// Create a new Discogs client (proxy handles credentials)
pub fn new() -> Self {
@@ -135,6 +145,95 @@ impl DiscogsClient {
None
}
/// Search for album artwork options
/// Returns up to 10 image options from the first matching release
pub async fn search_artwork_options(
&self,
artist: &str,
album: &str,
) -> Result<Vec<DiscogsImageOption>, String> {
// Build search query
let query = format!("{} {}", artist, album);
let url = format!(
"{}/search?q={}&type=release",
DISCOGS_PROXY_URL,
urlencoding::encode(&query)
);
log::debug!("Searching Discogs artwork options for: {} - {}", artist, album);
let response = self.client.get(&url).send().await
.map_err(|e| format!("Failed to search Discogs: {}", e))?;
if !response.status().is_success() {
return Err(format!("Discogs search failed with status: {}", response.status()));
}
let search: SearchResponse = response.json().await
.map_err(|e| format!("Failed to parse Discogs response: {}", e))?;
// Collect image options from search results
let mut images = Vec::new();
for result in search.results.iter().take(10) {
if result.result_type == "release" || result.result_type == "master" {
// Add cover image if available
if let Some(cover) = &result.cover_image {
if !cover.is_empty() && !cover.contains("spacer.gif") {
images.push(DiscogsImageOption {
url: cover.clone(),
width: 600, // Discogs standard size
height: 600,
image_type: "primary".to_string(),
});
}
}
// Add thumbnail as alternative
if let Some(thumb) = &result.thumb {
if !thumb.is_empty() && !thumb.contains("spacer.gif") {
images.push(DiscogsImageOption {
url: thumb.clone(),
width: 150, // Discogs thumb size
height: 150,
image_type: "secondary".to_string(),
});
}
}
}
}
if images.is_empty() {
return Err("No artwork found on Discogs".to_string());
}
// Return up to 10 unique images
images.truncate(10);
Ok(images)
}
/// Download image from URL and return local path
pub async fn download_artwork_from_url(
&self,
image_url: &str,
cache_dir: &Path,
artist: &str,
album: &str,
) -> Result<String, String> {
// Generate cache filename
let filename = format!(
"discogs_{:x}.jpg",
Self::simple_hash(&format!("{}_{}", artist, album))
);
let cache_path = cache_dir.join(&filename);
// Download the image
self.download_image(image_url, &cache_path).await
.ok_or_else(|| "Failed to download image".to_string())?;
Ok(cache_path.to_string_lossy().to_string())
}
/// Search for artists and return search results
pub async fn search_artist(&self, query: &str) -> Result<SearchResponse, String> {
let url = format!(
+2
View File
@@ -417,6 +417,8 @@ pub fn run() {
// Discogs artwork commands
library::commands::discogs_has_credentials,
library::commands::discogs_search_artist,
library::commands::discogs_search_artwork,
library::commands::discogs_download_artwork,
library::commands::library_fetch_missing_artwork,
library::commands::library_fetch_album_artwork,
library::commands::library_set_album_artwork,
+29
View File
@@ -1712,3 +1712,32 @@ pub async fn playlist_get_offline_available(
Ok(playlists.iter().map(|p| p.qobuz_playlist_id).collect())
}
// === Discogs Artwork ===
/// Search Discogs for artwork options
#[tauri::command]
pub async fn discogs_search_artwork(
artist: String,
album: String,
) -> Result<Vec<crate::discogs::DiscogsImageOption>, String> {
log::info!("Command: discogs_search_artwork {} - {}", artist, album);
let client = DiscogsClient::new();
client.search_artwork_options(&artist, &album).await
}
/// Download and save Discogs artwork
#[tauri::command]
pub async fn discogs_download_artwork(
image_url: String,
artist: String,
album: String,
) -> Result<String, String> {
log::info!("Command: discogs_download_artwork from {}", image_url);
let cache_dir = get_artwork_cache_dir();
let client = DiscogsClient::new();
client.download_artwork_from_url(&image_url, &cache_dir, &artist, &album).await
}