/** * Spotify to Deezer track conversion utilities * Matches Spotify tracks to Deezer tracks using ISRC codes (primary) or metadata search (fallback) */ import { fetch } from '@tauri-apps/plugin-http'; import { deezerAPI } from '../deezer'; export interface SpotifyTrackInput { id: string; name: string; artists: string[]; album: string; duration_ms: number; isrc?: string | null; } export interface DeezerMatchResult { success: boolean; deezerTrack?: any; matchMethod?: 'isrc' | 'metadata' | 'none'; error?: string; } /** * Search Deezer for a track by ISRC code * This is the primary and most reliable matching method */ export async function searchDeezerByISRC(isrc: string): Promise { if (!isrc || isrc.trim().length === 0) { return null; } try { console.log(`[Converter] Searching Deezer by ISRC: ${isrc}`); const url = `https://api.deezer.com/2.0/track/isrc:${encodeURIComponent(isrc)}`; const response = await fetch(url, { method: 'GET', headers: { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36', 'Accept': 'application/json' }, connectTimeout: 30000 }); if (!response.ok) { console.warn(`[Converter] ISRC search failed with status: ${response.status}`); return null; } const result = await response.json(); // Check if we got an error response if (result.error) { console.warn(`[Converter] ISRC search returned error:`, result.error); return null; } // Valid track found if (result.id) { console.log(`[Converter] Found Deezer track by ISRC: ${result.id} - ${result.title}`); return result; } return null; } catch (error) { console.error('[Converter] Error searching by ISRC:', error); return null; } } /** * Search Deezer for a track by metadata (title + artist) * Used as fallback when ISRC is not available or doesn't match */ export async function searchDeezerByMetadata( title: string, artist: string, durationMs?: number ): Promise { try { // Build search query: "artist title" const query = `${artist} ${title}`.trim(); console.log(`[Converter] Searching Deezer by metadata: "${query}"`); const searchResults = await deezerAPI.searchTracks(query, 10); if (!searchResults.data || searchResults.data.length === 0) { console.warn(`[Converter] No results found for: "${query}"`); return null; } // Try to find best match // Priority: exact title match, then duration match (±2 seconds) const durationSec = durationMs ? Math.floor(durationMs / 1000) : undefined; for (const track of searchResults.data) { // Check title similarity (case-insensitive) const titleMatch = track.title.toLowerCase() === title.toLowerCase(); // Check duration if available (within 2 seconds tolerance) const durationMatch = !durationSec || Math.abs(track.duration - durationSec) <= 2; if (titleMatch && durationMatch) { console.log(`[Converter] Found exact match by metadata: ${track.id} - ${track.title}`); return track; } } // If no exact match, return first result as best guess const firstResult = searchResults.data[0]; console.log(`[Converter] Using first result as best match: ${firstResult.id} - ${firstResult.title}`); return firstResult; } catch (error) { console.error('[Converter] Error searching by metadata:', error); return null; } } /** * Convert a Spotify track to Deezer track ID * Uses ISRC matching first, falls back to metadata search */ export async function convertSpotifyTrackToDeezer( spotifyTrack: SpotifyTrackInput ): Promise { console.log(`[Converter] Converting Spotify track: ${spotifyTrack.name} by ${spotifyTrack.artists.join(', ')}`); // Try ISRC matching first (most reliable) if (spotifyTrack.isrc) { const deezerTrack = await searchDeezerByISRC(spotifyTrack.isrc); if (deezerTrack) { return { success: true, deezerTrack, matchMethod: 'isrc' }; } console.log(`[Converter] ISRC match failed for: ${spotifyTrack.isrc}`); } // Fallback to metadata search const artist = spotifyTrack.artists[0] || 'Unknown'; const deezerTrack = await searchDeezerByMetadata( spotifyTrack.name, artist, spotifyTrack.duration_ms ); if (deezerTrack) { return { success: true, deezerTrack, matchMethod: 'metadata' }; } // No match found console.warn(`[Converter] Could not find Deezer match for: ${spotifyTrack.name} by ${artist}`); return { success: false, matchMethod: 'none', error: 'No match found on Deezer' }; } /** * Convert multiple Spotify tracks to Deezer track IDs * Returns both successful conversions and failed tracks */ export async function convertSpotifyTracksBatch( spotifyTracks: SpotifyTrackInput[] ): Promise<{ conversions: Array<{ spotifyId: string; deezerId: string; matchMethod: string }>; failures: Array<{ spotifyId: string; name: string; artist: string; error: string }>; }> { const conversions: Array<{ spotifyId: string; deezerId: string; matchMethod: string }> = []; const failures: Array<{ spotifyId: string; name: string; artist: string; error: string }> = []; console.log(`[Converter] Converting ${spotifyTracks.length} Spotify tracks to Deezer...`); for (const track of spotifyTracks) { try { const result = await convertSpotifyTrackToDeezer(track); if (result.success && result.deezerTrack) { conversions.push({ spotifyId: track.id, deezerId: result.deezerTrack.id.toString(), matchMethod: result.matchMethod || 'unknown' }); } else { failures.push({ spotifyId: track.id, name: track.name, artist: track.artists[0] || 'Unknown', error: result.error || 'Unknown error' }); } } catch (error) { console.error(`[Converter] Error converting track ${track.name}:`, error); failures.push({ spotifyId: track.id, name: track.name, artist: track.artists[0] || 'Unknown', error: error instanceof Error ? error.message : 'Conversion error' }); } } console.log(`[Converter] Conversion complete: ${conversions.length} successful, ${failures.length} failed`); return { conversions, failures }; }