mirror of
https://github.com/markuryy/shark.git
synced 2026-07-27 23:41:01 +00:00
216 lines
6.3 KiB
TypeScript
216 lines
6.3 KiB
TypeScript
/**
|
|
* 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<any | null> {
|
|
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<any | null> {
|
|
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<DeezerMatchResult> {
|
|
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 };
|
|
}
|