v7.0.9
This commit is contained in:
@@ -7,7 +7,7 @@ export function useAvailability() {
|
||||
const [checkingTrackId, setCheckingTrackId] = useState<string | null>(null);
|
||||
const [availabilityMap, setAvailabilityMap] = useState<Map<string, TrackAvailability>>(new Map());
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const checkAvailability = useCallback(async (spotifyId: string, isrc?: string) => {
|
||||
const checkAvailability = useCallback(async (spotifyId: string) => {
|
||||
if (!spotifyId) {
|
||||
setError("No Spotify ID provided");
|
||||
return null;
|
||||
@@ -20,7 +20,7 @@ export function useAvailability() {
|
||||
setError(null);
|
||||
try {
|
||||
logger.info(`Checking availability for track: ${spotifyId}`);
|
||||
const response = await CheckTrackAvailability(spotifyId, isrc || "");
|
||||
const response = await CheckTrackAvailability(spotifyId);
|
||||
const availability: TrackAvailability = JSON.parse(response);
|
||||
setAvailabilityMap((prev) => {
|
||||
const newMap = new Map(prev);
|
||||
|
||||
@@ -51,7 +51,7 @@ export function useDownload(region: string) {
|
||||
artists: string;
|
||||
} | null>(null);
|
||||
const shouldStopDownloadRef = useRef(false);
|
||||
const downloadWithAutoFallback = async (isrc: string, settings: any, trackName?: string, artistName?: string, albumName?: string, playlistName?: string, position?: number, spotifyId?: string, durationMs?: number, releaseYear?: string, albumArtist?: string, releaseDate?: string, coverUrl?: string, spotifyTrackNumber?: number, spotifyDiscNumber?: number, spotifyTotalTracks?: number, spotifyTotalDiscs?: number, copyright?: string, publisher?: string) => {
|
||||
const downloadWithAutoFallback = async (id: string, settings: any, trackName?: string, artistName?: string, albumName?: string, playlistName?: string, position?: number, spotifyId?: string, durationMs?: number, releaseYear?: string, albumArtist?: string, releaseDate?: string, coverUrl?: string, spotifyTrackNumber?: number, spotifyDiscNumber?: number, spotifyTotalTracks?: number, spotifyTotalDiscs?: number, copyright?: string, publisher?: string) => {
|
||||
const service = settings.downloader;
|
||||
const query = trackName && artistName ? `${trackName} ${artistName} ` : undefined;
|
||||
const os = settings.operatingSystem;
|
||||
@@ -117,7 +117,7 @@ export function useDownload(region: string) {
|
||||
if (trackName && artistName) {
|
||||
try {
|
||||
const checkRequest: CheckFileExistenceRequest = {
|
||||
spotify_id: spotifyId || isrc,
|
||||
spotify_id: spotifyId || id,
|
||||
track_name: trackName,
|
||||
artist_name: displayArtist || "",
|
||||
album_name: albumName,
|
||||
@@ -149,7 +149,7 @@ export function useDownload(region: string) {
|
||||
const { AddToDownloadQueue } = await import("../../wailsjs/go/main/App");
|
||||
let itemID: string | undefined;
|
||||
if (!fileExists) {
|
||||
itemID = await AddToDownloadQueue(isrc, trackName || "", displayArtist || "", albumName || "");
|
||||
itemID = await AddToDownloadQueue(id, trackName || "", displayArtist || "", albumName || "");
|
||||
}
|
||||
if (service === "auto") {
|
||||
let streamingURLs: any = null;
|
||||
@@ -174,13 +174,12 @@ export function useDownload(region: string) {
|
||||
try {
|
||||
logger.debug(`trying tidal for: ${trackName} - ${artistName}`);
|
||||
const response = await downloadTrack({
|
||||
isrc,
|
||||
service: "tidal",
|
||||
query,
|
||||
track_name: trackName,
|
||||
artist_name: artistName,
|
||||
artist_name: displayArtist,
|
||||
album_name: albumName,
|
||||
album_artist: albumArtist,
|
||||
album_artist: displayAlbumArtist,
|
||||
release_date: finalReleaseDate || releaseDate,
|
||||
cover_url: coverUrl,
|
||||
output_dir: outputDir,
|
||||
@@ -201,6 +200,7 @@ export function useDownload(region: string) {
|
||||
spotify_total_discs: spotifyTotalDiscs,
|
||||
copyright: copyright,
|
||||
publisher: publisher,
|
||||
use_first_artist_only: settings.useFirstArtistOnly,
|
||||
});
|
||||
if (response.success) {
|
||||
logger.success(`tidal: ${trackName} - ${artistName}`);
|
||||
@@ -218,13 +218,12 @@ export function useDownload(region: string) {
|
||||
try {
|
||||
logger.debug(`trying amazon for: ${trackName} - ${artistName}`);
|
||||
const response = await downloadTrack({
|
||||
isrc,
|
||||
service: "amazon",
|
||||
query,
|
||||
track_name: trackName,
|
||||
artist_name: artistName,
|
||||
artist_name: displayArtist,
|
||||
album_name: albumName,
|
||||
album_artist: albumArtist,
|
||||
album_artist: displayAlbumArtist,
|
||||
release_date: finalReleaseDate || releaseDate,
|
||||
cover_url: coverUrl,
|
||||
output_dir: outputDir,
|
||||
@@ -260,13 +259,12 @@ export function useDownload(region: string) {
|
||||
try {
|
||||
logger.debug(`trying qobuz for: ${trackName} - ${artistName}`);
|
||||
const response = await downloadTrack({
|
||||
isrc,
|
||||
service: "qobuz",
|
||||
query,
|
||||
track_name: trackName,
|
||||
artist_name: artistName,
|
||||
artist_name: displayArtist,
|
||||
album_name: albumName,
|
||||
album_artist: albumArtist,
|
||||
album_artist: displayAlbumArtist,
|
||||
release_date: finalReleaseDate || releaseDate,
|
||||
cover_url: coverUrl,
|
||||
output_dir: outputDir,
|
||||
@@ -314,13 +312,12 @@ export function useDownload(region: string) {
|
||||
audioFormat = settings.qobuzQuality || "6";
|
||||
}
|
||||
const singleServiceResponse = await downloadTrack({
|
||||
isrc,
|
||||
service: service as "tidal" | "qobuz" | "amazon",
|
||||
query,
|
||||
track_name: trackName,
|
||||
artist_name: artistName,
|
||||
artist_name: displayArtist,
|
||||
album_name: albumName,
|
||||
album_artist: albumArtist,
|
||||
album_artist: displayAlbumArtist,
|
||||
release_date: finalReleaseDate || releaseDate,
|
||||
cover_url: coverUrl,
|
||||
output_dir: outputDir,
|
||||
@@ -347,7 +344,7 @@ export function useDownload(region: string) {
|
||||
}
|
||||
return singleServiceResponse;
|
||||
};
|
||||
const downloadWithItemID = async (isrc: string, settings: any, itemID: string, trackName?: string, artistName?: string, albumName?: string, folderName?: string, position?: number, spotifyId?: string, durationMs?: number, isAlbum?: boolean, releaseYear?: string, albumArtist?: string, releaseDate?: string, coverUrl?: string, spotifyTrackNumber?: number, spotifyDiscNumber?: number, spotifyTotalTracks?: number, spotifyTotalDiscs?: number, copyright?: string, publisher?: string) => {
|
||||
const downloadWithItemID = async (settings: any, itemID: string, trackName?: string, artistName?: string, albumName?: string, folderName?: string, position?: number, spotifyId?: string, durationMs?: number, isAlbum?: boolean, releaseYear?: string, albumArtist?: string, releaseDate?: string, coverUrl?: string, spotifyTrackNumber?: number, spotifyDiscNumber?: number, spotifyTotalTracks?: number, spotifyTotalDiscs?: number, copyright?: string, publisher?: string) => {
|
||||
const service = settings.downloader;
|
||||
const query = trackName && artistName ? `${trackName} ${artistName}` : undefined;
|
||||
const os = settings.operatingSystem;
|
||||
@@ -375,13 +372,16 @@ export function useDownload(region: string) {
|
||||
const yearValue = releaseYear || finalReleaseDate?.substring(0, 4);
|
||||
const hasSubfolder = settings.folderTemplate && settings.folderTemplate.trim() !== "";
|
||||
const trackNumberForTemplate = (hasSubfolder && finalTrackNumber > 0) ? finalTrackNumber : (position || 0);
|
||||
if (hasSubfolder) {
|
||||
useAlbumTrackNumber = true;
|
||||
}
|
||||
const displayArtist = settings.useFirstArtistOnly && artistName
|
||||
? getFirstArtist(artistName)
|
||||
: artistName;
|
||||
const displayAlbumArtist = settings.useFirstArtistOnly && albumArtist
|
||||
? getFirstArtist(albumArtist)
|
||||
: albumArtist;
|
||||
const templateData: TemplateData = {
|
||||
artist: artistName?.replace(/\//g, placeholder),
|
||||
artist: displayArtist?.replace(/\//g, placeholder),
|
||||
album: albumName?.replace(/\//g, placeholder),
|
||||
album_artist: albumArtist?.replace(/\//g, placeholder) || artistName?.replace(/\//g, placeholder),
|
||||
album_artist: displayAlbumArtist?.replace(/\//g, placeholder) || displayArtist?.replace(/\//g, placeholder),
|
||||
title: trackName?.replace(/\//g, placeholder),
|
||||
track: trackNumberForTemplate,
|
||||
year: yearValue,
|
||||
@@ -424,13 +424,12 @@ export function useDownload(region: string) {
|
||||
if (s === "tidal" && streamingURLs?.tidal_url) {
|
||||
try {
|
||||
const response = await downloadTrack({
|
||||
isrc,
|
||||
service: "tidal",
|
||||
query,
|
||||
track_name: trackName,
|
||||
artist_name: artistName,
|
||||
artist_name: displayArtist,
|
||||
album_name: albumName,
|
||||
album_artist: albumArtist,
|
||||
album_artist: displayAlbumArtist,
|
||||
release_date: finalReleaseDate || releaseDate,
|
||||
cover_url: coverUrl,
|
||||
output_dir: outputDir,
|
||||
@@ -451,6 +450,7 @@ export function useDownload(region: string) {
|
||||
spotify_total_discs: spotifyTotalDiscs,
|
||||
copyright: copyright,
|
||||
publisher: publisher,
|
||||
use_first_artist_only: settings.useFirstArtistOnly,
|
||||
});
|
||||
if (response.success) {
|
||||
return response;
|
||||
@@ -465,13 +465,12 @@ export function useDownload(region: string) {
|
||||
else if (s === "amazon" && streamingURLs?.amazon_url) {
|
||||
try {
|
||||
const response = await downloadTrack({
|
||||
isrc,
|
||||
service: "amazon",
|
||||
query,
|
||||
track_name: trackName,
|
||||
artist_name: artistName,
|
||||
artist_name: displayArtist,
|
||||
album_name: albumName,
|
||||
album_artist: albumArtist,
|
||||
album_artist: displayAlbumArtist,
|
||||
release_date: finalReleaseDate || releaseDate,
|
||||
cover_url: coverUrl,
|
||||
output_dir: outputDir,
|
||||
@@ -490,6 +489,7 @@ export function useDownload(region: string) {
|
||||
spotify_total_discs: spotifyTotalDiscs,
|
||||
copyright: copyright,
|
||||
publisher: publisher,
|
||||
use_first_artist_only: settings.useFirstArtistOnly,
|
||||
});
|
||||
if (response.success) {
|
||||
return response;
|
||||
@@ -504,13 +504,12 @@ export function useDownload(region: string) {
|
||||
else if (s === "qobuz") {
|
||||
try {
|
||||
const response = await downloadTrack({
|
||||
isrc,
|
||||
service: "qobuz",
|
||||
query,
|
||||
track_name: trackName,
|
||||
artist_name: artistName,
|
||||
artist_name: displayArtist,
|
||||
album_name: albumName,
|
||||
album_artist: albumArtist,
|
||||
album_artist: displayAlbumArtist,
|
||||
release_date: finalReleaseDate || releaseDate,
|
||||
cover_url: coverUrl,
|
||||
output_dir: outputDir,
|
||||
@@ -530,6 +529,7 @@ export function useDownload(region: string) {
|
||||
spotify_total_discs: spotifyTotalDiscs,
|
||||
copyright: copyright,
|
||||
publisher: publisher,
|
||||
use_first_artist_only: settings.useFirstArtistOnly,
|
||||
});
|
||||
if (response.success) {
|
||||
return response;
|
||||
@@ -557,13 +557,12 @@ export function useDownload(region: string) {
|
||||
audioFormat = settings.qobuzQuality || "6";
|
||||
}
|
||||
const singleServiceResponse = await downloadTrack({
|
||||
isrc,
|
||||
service: service as "tidal" | "qobuz" | "amazon",
|
||||
query,
|
||||
track_name: trackName,
|
||||
artist_name: artistName,
|
||||
artist_name: displayArtist,
|
||||
album_name: albumName,
|
||||
album_artist: albumArtist,
|
||||
album_artist: displayAlbumArtist,
|
||||
release_date: finalReleaseDate || releaseDate,
|
||||
cover_url: coverUrl,
|
||||
output_dir: outputDir,
|
||||
@@ -590,40 +589,41 @@ export function useDownload(region: string) {
|
||||
}
|
||||
return singleServiceResponse;
|
||||
};
|
||||
const handleDownloadTrack = async (isrc: string, trackName?: string, artistName?: string, albumName?: string, spotifyId?: string, playlistName?: string, durationMs?: number, position?: number, albumArtist?: string, releaseDate?: string, coverUrl?: string, spotifyTrackNumber?: number, spotifyDiscNumber?: number, spotifyTotalTracks?: number, spotifyTotalDiscs?: number, copyright?: string, publisher?: string) => {
|
||||
if (!isrc) {
|
||||
toast.error("No ISRC found for this track");
|
||||
const handleDownloadTrack = async (id: string, trackName?: string, artistName?: string, albumName?: string, spotifyId?: string, playlistName?: string, durationMs?: number, position?: number, albumArtist?: string, releaseDate?: string, coverUrl?: string, spotifyTrackNumber?: number, spotifyDiscNumber?: number, spotifyTotalTracks?: number, spotifyTotalDiscs?: number, copyright?: string, publisher?: string) => {
|
||||
if (!id) {
|
||||
toast.error("No ID found for this track");
|
||||
return;
|
||||
}
|
||||
logger.info(`starting download: ${trackName} - ${artistName}`);
|
||||
const settings = getSettings();
|
||||
setDownloadingTrack(isrc);
|
||||
const displayArtist = settings.useFirstArtistOnly && artistName ? getFirstArtist(artistName) : artistName;
|
||||
logger.info(`starting download: ${trackName} - ${displayArtist}`);
|
||||
setDownloadingTrack(id);
|
||||
try {
|
||||
const releaseYear = releaseDate?.substring(0, 4);
|
||||
const response = await downloadWithAutoFallback(isrc, settings, trackName, artistName, albumName, playlistName, position, spotifyId, durationMs, releaseYear, albumArtist || "", releaseDate, coverUrl, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks, spotifyTotalDiscs, copyright, publisher);
|
||||
const response = await downloadWithAutoFallback(id, settings, trackName, artistName, albumName, playlistName, position, spotifyId, durationMs, releaseYear, albumArtist || "", releaseDate, coverUrl, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks, spotifyTotalDiscs, copyright, publisher);
|
||||
if (response.success) {
|
||||
if (response.already_exists) {
|
||||
toast.info(response.message);
|
||||
setSkippedTracks((prev) => new Set(prev).add(isrc));
|
||||
setSkippedTracks((prev) => new Set(prev).add(id));
|
||||
}
|
||||
else {
|
||||
toast.success(response.message);
|
||||
}
|
||||
setDownloadedTracks((prev) => new Set(prev).add(isrc));
|
||||
setDownloadedTracks((prev) => new Set(prev).add(id));
|
||||
setFailedTracks((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(isrc);
|
||||
newSet.delete(id);
|
||||
return newSet;
|
||||
});
|
||||
}
|
||||
else {
|
||||
toast.error(response.error || "Download failed");
|
||||
setFailedTracks((prev) => new Set(prev).add(isrc));
|
||||
setFailedTracks((prev) => new Set(prev).add(id));
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
toast.error(err instanceof Error ? err.message : "Download failed");
|
||||
setFailedTracks((prev) => new Set(prev).add(isrc));
|
||||
setFailedTracks((prev) => new Set(prev).add(id));
|
||||
}
|
||||
finally {
|
||||
setDownloadingTrack(null);
|
||||
@@ -646,18 +646,20 @@ export function useDownload(region: string) {
|
||||
outputDir = joinPath(os, outputDir, sanitizePath(folderName.replace(/\//g, " "), os));
|
||||
}
|
||||
const selectedTrackObjects = selectedTracks
|
||||
.map((isrc) => allTracks.find((t) => t.isrc === isrc))
|
||||
.map((id) => allTracks.find((t) => t.spotify_id === id))
|
||||
.filter((t): t is TrackMetadata => t !== undefined);
|
||||
logger.info(`checking existing files in parallel...`);
|
||||
const useAlbumTrackNumber = settings.folderTemplate?.includes("{album}") || false;
|
||||
const audioFormat = "flac";
|
||||
const existenceChecks = selectedTrackObjects.map((track, index) => {
|
||||
const displayArtist = settings.useFirstArtistOnly && track.artists ? getFirstArtist(track.artists) : track.artists;
|
||||
const displayAlbumArtist = settings.useFirstArtistOnly && track.album_artist ? getFirstArtist(track.album_artist) : track.album_artist;
|
||||
return {
|
||||
spotify_id: track.spotify_id || track.isrc,
|
||||
spotify_id: track.spotify_id || "",
|
||||
track_name: track.name || "",
|
||||
artist_name: track.artists || "",
|
||||
artist_name: displayArtist || "",
|
||||
album_name: track.album_name || "",
|
||||
album_artist: track.album_artist || "",
|
||||
album_artist: displayAlbumArtist || "",
|
||||
release_date: track.release_date || "",
|
||||
track_number: track.track_number || 0,
|
||||
disc_number: track.disc_number || 0,
|
||||
@@ -682,20 +684,23 @@ export function useDownload(region: string) {
|
||||
logger.info(`found ${existingSpotifyIDs.size} existing files`);
|
||||
const { AddToDownloadQueue } = await import("../../wailsjs/go/main/App");
|
||||
const itemIDs: string[] = [];
|
||||
for (const isrc of selectedTracks) {
|
||||
const track = allTracks.find((t) => t.isrc === isrc);
|
||||
const trackID = track?.spotify_id || isrc;
|
||||
const itemID = await AddToDownloadQueue(trackID, track?.name || "", track?.artists || "", track?.album_name || "");
|
||||
for (const id of selectedTracks) {
|
||||
const track = allTracks.find((t) => t.spotify_id === id);
|
||||
if (!track)
|
||||
continue;
|
||||
const trackID = track.spotify_id || id;
|
||||
const displayArtist = settings.useFirstArtistOnly && track.artists ? getFirstArtist(track.artists) : track.artists;
|
||||
const itemID = await AddToDownloadQueue(trackID, track.name || "", displayArtist || "", track.album_name || "");
|
||||
itemIDs.push(itemID);
|
||||
if (existingSpotifyIDs.has(trackID)) {
|
||||
const filePath = existingFilePaths.get(trackID) || "";
|
||||
setTimeout(() => SkipDownloadItem(itemID, filePath), 10);
|
||||
setSkippedTracks((prev) => new Set(prev).add(isrc));
|
||||
setDownloadedTracks((prev) => new Set(prev).add(isrc));
|
||||
setSkippedTracks((prev) => new Set(prev).add(id));
|
||||
setDownloadedTracks((prev) => new Set(prev).add(id));
|
||||
}
|
||||
}
|
||||
const tracksToDownload = selectedTrackObjects.filter((track) => {
|
||||
const trackID = track.spotify_id || track.isrc;
|
||||
const trackID = track.spotify_id || "";
|
||||
return !existingSpotifyIDs.has(trackID);
|
||||
});
|
||||
let successCount = 0;
|
||||
@@ -709,45 +714,46 @@ export function useDownload(region: string) {
|
||||
break;
|
||||
}
|
||||
const track = tracksToDownload[i];
|
||||
const isrc = track.isrc;
|
||||
const originalIndex = selectedTracks.indexOf(isrc);
|
||||
const id = track.spotify_id || "";
|
||||
const originalIndex = selectedTracks.indexOf(id);
|
||||
const itemID = itemIDs[originalIndex];
|
||||
setDownloadingTrack(isrc);
|
||||
setCurrentDownloadInfo({ name: track.name, artists: track.artists });
|
||||
setDownloadingTrack(id);
|
||||
const displayArtist = settings.useFirstArtistOnly && track.artists ? getFirstArtist(track.artists) : track.artists;
|
||||
setCurrentDownloadInfo({ name: track.name, artists: displayArtist || "" });
|
||||
try {
|
||||
const releaseYear = track.release_date?.substring(0, 4);
|
||||
const response = await downloadWithItemID(isrc, settings, itemID, track.name, track.artists, track.album_name, folderName, originalIndex + 1, track.spotify_id, track.duration_ms, isAlbum, releaseYear, track.album_artist || "", track.release_date, track.images, track.track_number, track.disc_number, track.total_tracks, track.total_discs, track.copyright, track.publisher);
|
||||
const response = await downloadWithItemID(settings, itemID, track.name, track.artists, track.album_name, folderName, originalIndex + 1, track.spotify_id, track.duration_ms, isAlbum, releaseYear, track.album_artist || "", track.release_date, track.images, track.track_number, track.disc_number, track.total_tracks, track.total_discs, track.copyright, track.publisher);
|
||||
if (response.success) {
|
||||
if (response.already_exists) {
|
||||
skippedCount++;
|
||||
logger.info(`skipped: ${track.name} - ${track.artists} (already exists)`);
|
||||
setSkippedTracks((prev) => new Set(prev).add(isrc));
|
||||
logger.info(`skipped: ${track.name} - ${displayArtist} (already exists)`);
|
||||
setSkippedTracks((prev) => new Set(prev).add(id));
|
||||
}
|
||||
else {
|
||||
successCount++;
|
||||
logger.success(`downloaded: ${track.name} - ${track.artists}`);
|
||||
logger.success(`downloaded: ${track.name} - ${displayArtist}`);
|
||||
}
|
||||
if (response.file) {
|
||||
finalFilePaths.set(isrc, response.file);
|
||||
finalFilePaths.set(track.spotify_id || isrc, response.file);
|
||||
finalFilePaths.set(id, response.file);
|
||||
finalFilePaths.set(track.spotify_id || id, response.file);
|
||||
}
|
||||
setDownloadedTracks((prev) => new Set(prev).add(isrc));
|
||||
setDownloadedTracks((prev) => new Set(prev).add(id));
|
||||
setFailedTracks((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(isrc);
|
||||
newSet.delete(id);
|
||||
return newSet;
|
||||
});
|
||||
}
|
||||
else {
|
||||
errorCount++;
|
||||
logger.error(`failed: ${track.name} - ${track.artists}`);
|
||||
setFailedTracks((prev) => new Set(prev).add(isrc));
|
||||
logger.error(`failed: ${track.name} - ${displayArtist}`);
|
||||
setFailedTracks((prev) => new Set(prev).add(id));
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
errorCount++;
|
||||
logger.error(`error: ${track.name} - ${err}`);
|
||||
setFailedTracks((prev) => new Set(prev).add(isrc));
|
||||
setFailedTracks((prev) => new Set(prev).add(id));
|
||||
if (itemID) {
|
||||
const { MarkDownloadItemFailed } = await import("../../wailsjs/go/main/App");
|
||||
await MarkDownloadItemFailed(itemID, err instanceof Error ? err.message : String(err));
|
||||
@@ -764,7 +770,7 @@ export function useDownload(region: string) {
|
||||
const { CancelAllQueuedItems } = await import("../../wailsjs/go/main/App");
|
||||
await CancelAllQueuedItems();
|
||||
if (settings.createM3u8File && folderName) {
|
||||
const paths = selectedTrackObjects.map((t) => finalFilePaths.get(t.spotify_id || t.isrc) || "").filter((p) => p !== "");
|
||||
const paths = selectedTrackObjects.map((t) => finalFilePaths.get(t.spotify_id || "") || "").filter((p) => p !== "");
|
||||
if (paths.length > 0) {
|
||||
try {
|
||||
logger.info(`creating m3u8 playlist: ${folderName}`);
|
||||
@@ -798,12 +804,12 @@ export function useDownload(region: string) {
|
||||
}
|
||||
};
|
||||
const handleDownloadAll = async (tracks: TrackMetadata[], folderName?: string, isAlbum?: boolean) => {
|
||||
const tracksWithIsrc = tracks.filter((track) => track.isrc);
|
||||
if (tracksWithIsrc.length === 0) {
|
||||
const tracksWithId = tracks.filter((track) => track.spotify_id);
|
||||
if (tracksWithId.length === 0) {
|
||||
toast.error("No tracks available for download");
|
||||
return;
|
||||
}
|
||||
logger.info(`starting batch download: ${tracksWithIsrc.length} tracks`);
|
||||
logger.info(`starting batch download: ${tracksWithId.length} tracks`);
|
||||
const settings = getSettings();
|
||||
setIsDownloading(true);
|
||||
setBulkDownloadType("all");
|
||||
@@ -817,13 +823,15 @@ export function useDownload(region: string) {
|
||||
logger.info(`checking existing files in parallel...`);
|
||||
const useAlbumTrackNumber = settings.folderTemplate?.includes("{album}") || false;
|
||||
const audioFormat = "flac";
|
||||
const existenceChecks = tracksWithIsrc.map((track, index) => {
|
||||
const existenceChecks = tracksWithId.map((track, index) => {
|
||||
const displayArtist = settings.useFirstArtistOnly && track.artists ? getFirstArtist(track.artists) : track.artists;
|
||||
const displayAlbumArtist = settings.useFirstArtistOnly && track.album_artist ? getFirstArtist(track.album_artist) : track.album_artist;
|
||||
return {
|
||||
spotify_id: track.spotify_id || track.isrc,
|
||||
spotify_id: track.spotify_id || "",
|
||||
track_name: track.name || "",
|
||||
artist_name: track.artists || "",
|
||||
artist_name: displayArtist || "",
|
||||
album_name: track.album_name || "",
|
||||
album_artist: track.album_artist || "",
|
||||
album_artist: displayAlbumArtist || "",
|
||||
release_date: track.release_date || "",
|
||||
track_number: track.track_number || 0,
|
||||
disc_number: track.disc_number || 0,
|
||||
@@ -835,7 +843,7 @@ export function useDownload(region: string) {
|
||||
};
|
||||
});
|
||||
const existenceResults = await CheckFilesExistence(outputDir, settings.downloadPath, existenceChecks);
|
||||
const finalFilePaths: string[] = new Array(tracksWithIsrc.length).fill("");
|
||||
const finalFilePaths: string[] = new Array(tracksWithId.length).fill("");
|
||||
const existingSpotifyIDs = new Set<string>();
|
||||
const existingFilePaths = new Map<string, string>();
|
||||
for (let i = 0; i < existenceResults.length; i++) {
|
||||
@@ -849,25 +857,26 @@ export function useDownload(region: string) {
|
||||
logger.info(`found ${existingSpotifyIDs.size} existing files`);
|
||||
const { AddToDownloadQueue } = await import("../../wailsjs/go/main/App");
|
||||
const itemIDs: string[] = [];
|
||||
for (const track of tracksWithIsrc) {
|
||||
const itemID = await AddToDownloadQueue(track.isrc, track.name, track.artists, track.album_name || "");
|
||||
for (const track of tracksWithId) {
|
||||
const displayArtist = settings.useFirstArtistOnly && track.artists ? getFirstArtist(track.artists) : track.artists;
|
||||
const itemID = await AddToDownloadQueue(track.spotify_id || "", track.name || "", displayArtist || "", track.album_name || "");
|
||||
itemIDs.push(itemID);
|
||||
const trackID = track.spotify_id || track.isrc;
|
||||
const trackID = track.spotify_id || "";
|
||||
if (existingSpotifyIDs.has(trackID)) {
|
||||
const filePath = existingFilePaths.get(trackID) || "";
|
||||
setTimeout(() => SkipDownloadItem(itemID, filePath), 10);
|
||||
setSkippedTracks((prev) => new Set(prev).add(track.isrc));
|
||||
setDownloadedTracks((prev) => new Set(prev).add(track.isrc));
|
||||
setSkippedTracks((prev: Set<string>) => new Set(prev).add(trackID));
|
||||
setDownloadedTracks((prev: Set<string>) => new Set(prev).add(trackID));
|
||||
}
|
||||
}
|
||||
const tracksToDownload = tracksWithIsrc.filter((track) => {
|
||||
const trackID = track.spotify_id || track.isrc;
|
||||
const tracksToDownload = tracksWithId.filter((track) => {
|
||||
const trackID = track.spotify_id || "";
|
||||
return !existingSpotifyIDs.has(trackID);
|
||||
});
|
||||
let successCount = 0;
|
||||
let errorCount = 0;
|
||||
let skippedCount = existingSpotifyIDs.size;
|
||||
const total = tracksWithIsrc.length;
|
||||
const total = tracksWithId.length;
|
||||
setDownloadProgress(Math.round((skippedCount / total) * 100));
|
||||
for (let i = 0; i < tracksToDownload.length; i++) {
|
||||
if (shouldStopDownloadRef.current) {
|
||||
@@ -875,27 +884,29 @@ export function useDownload(region: string) {
|
||||
break;
|
||||
}
|
||||
const track = tracksToDownload[i];
|
||||
const originalIndex = tracksWithIsrc.findIndex((t) => t.isrc === track.isrc);
|
||||
const originalIndex = tracksWithId.findIndex((t) => t.spotify_id === track.spotify_id);
|
||||
const itemID = itemIDs[originalIndex];
|
||||
setDownloadingTrack(track.isrc);
|
||||
setCurrentDownloadInfo({ name: track.name, artists: track.artists });
|
||||
const trackId = track.spotify_id || "";
|
||||
setDownloadingTrack(trackId);
|
||||
const displayArtist = settings.useFirstArtistOnly && track.artists ? getFirstArtist(track.artists) : track.artists;
|
||||
setCurrentDownloadInfo({ name: track.name || "", artists: displayArtist || "" });
|
||||
try {
|
||||
const releaseYear = track.release_date?.substring(0, 4);
|
||||
const response = await downloadWithItemID(track.isrc, settings, itemID, track.name, track.artists, track.album_name, folderName, originalIndex + 1, track.spotify_id, track.duration_ms, isAlbum, releaseYear, track.album_artist || "", track.release_date, track.images, track.track_number, track.disc_number, track.total_tracks, track.total_discs, track.copyright, track.publisher);
|
||||
const response = await downloadWithItemID(settings, itemID, track.name, track.artists, track.album_name, folderName, originalIndex + 1, track.spotify_id, track.duration_ms, isAlbum, releaseYear, track.album_artist || "", track.release_date, track.images, track.track_number, track.disc_number, track.total_tracks, track.total_discs, track.copyright, track.publisher);
|
||||
if (response.success) {
|
||||
if (response.already_exists) {
|
||||
skippedCount++;
|
||||
logger.info(`skipped: ${track.name} - ${track.artists} (already exists)`);
|
||||
setSkippedTracks((prev) => new Set(prev).add(track.isrc));
|
||||
logger.info(`skipped: ${track.name} - ${displayArtist} (already exists)`);
|
||||
setSkippedTracks((prev) => new Set(prev).add(trackId));
|
||||
}
|
||||
else {
|
||||
successCount++;
|
||||
logger.success(`downloaded: ${track.name} - ${track.artists}`);
|
||||
logger.success(`downloaded: ${track.name} - ${displayArtist}`);
|
||||
}
|
||||
setDownloadedTracks((prev) => new Set(prev).add(track.isrc));
|
||||
setDownloadedTracks((prev) => new Set(prev).add(trackId));
|
||||
setFailedTracks((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(track.isrc);
|
||||
newSet.delete(trackId);
|
||||
return newSet;
|
||||
});
|
||||
if (response.file) {
|
||||
@@ -904,14 +915,14 @@ export function useDownload(region: string) {
|
||||
}
|
||||
else {
|
||||
errorCount++;
|
||||
logger.error(`failed: ${track.name} - ${track.artists}`);
|
||||
setFailedTracks((prev) => new Set(prev).add(track.isrc));
|
||||
logger.error(`failed: ${track.name} - ${displayArtist}`);
|
||||
setFailedTracks((prev) => new Set(prev).add(trackId));
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
errorCount++;
|
||||
logger.error(`error: ${track.name} - ${err}`);
|
||||
setFailedTracks((prev) => new Set(prev).add(track.isrc));
|
||||
setFailedTracks((prev) => new Set(prev).add(trackId));
|
||||
const { MarkDownloadItemFailed } = await import("../../wailsjs/go/main/App");
|
||||
await MarkDownloadItemFailed(itemID, err instanceof Error ? err.message : String(err));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { getSettings } from "@/lib/settings";
|
||||
import { fetchSpotifyMetadata } from "@/lib/api";
|
||||
import { toastWithSound as toast } from "@/lib/toast-with-sound";
|
||||
import { logger } from "@/lib/logger";
|
||||
@@ -7,6 +8,7 @@ import type { SpotifyMetadataResponse } from "@/types/api";
|
||||
export function useMetadata() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [metadata, setMetadata] = useState<SpotifyMetadataResponse | null>(null);
|
||||
const [showApiModal, setShowApiModal] = useState(false);
|
||||
const [showAlbumDialog, setShowAlbumDialog] = useState(false);
|
||||
const [selectedAlbum, setSelectedAlbum] = useState<{
|
||||
id: string;
|
||||
@@ -109,7 +111,7 @@ export function useMetadata() {
|
||||
saveToHistory(url, data);
|
||||
if ("track" in data) {
|
||||
logger.success(`fetched track: ${data.track.name} - ${data.track.artists}`);
|
||||
logger.debug(`isrc: ${data.track.isrc}, duration: ${data.track.duration_ms}ms`);
|
||||
logger.debug(`duration: ${data.track.duration_ms}ms`);
|
||||
}
|
||||
else if ("album_info" in data) {
|
||||
logger.success(`fetched album: ${data.album_info.name}`);
|
||||
@@ -129,7 +131,13 @@ export function useMetadata() {
|
||||
catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : "Failed to fetch metadata";
|
||||
logger.error(`fetch failed: ${errorMsg}`);
|
||||
toast.error(errorMsg);
|
||||
const settings = getSettings();
|
||||
if (!settings.useSpotFetchAPI) {
|
||||
setShowApiModal(true);
|
||||
}
|
||||
else {
|
||||
toast.error(errorMsg);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
setLoading(false);
|
||||
@@ -224,7 +232,13 @@ export function useMetadata() {
|
||||
catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : "Failed to fetch album metadata";
|
||||
logger.error(`fetch failed: ${errorMsg}`);
|
||||
toast.error(errorMsg);
|
||||
const settings = getSettings();
|
||||
if (!settings.useSpotFetchAPI) {
|
||||
setShowApiModal(true);
|
||||
}
|
||||
else {
|
||||
toast.error(errorMsg);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
setLoading(false);
|
||||
@@ -243,6 +257,8 @@ export function useMetadata() {
|
||||
handleConfirmAlbumFetch,
|
||||
handleArtistClick,
|
||||
loadFromCache,
|
||||
showApiModal,
|
||||
setShowApiModal,
|
||||
resetMetadata: () => setMetadata(null),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user