feat: add image retrieval functionality using coverartarchive.org API

This commit is contained in:
xtrullor73
2024-05-12 20:00:57 -07:00
parent caf9d554ef
commit bcdc66ca18
7 changed files with 104 additions and 34 deletions

1
app.js
View File

@@ -24,6 +24,7 @@ async function main() {
const recordingIds = await recognizeAudioFiles(audioFiles);
// Fetch the audio metadata from Spotify using the recognized track IDs
const audioMetadata = await retrieveMetadata(recordingIds);
console.log(audioMetadata);
// Write the fetched metadata into the audio file
// const processedAudioFiles = await fileController.writeMetadata(audioMetadata, audioFiles);
} catch (e) {

33
src/api/imageRetrieval.js Normal file
View File

@@ -0,0 +1,33 @@
import axios from 'axios';
/**
* Retrieves the album art image URL for a given album ID.
* It attempts to get the image from the `coverartarchive.org` service.
*
* @param {string} albumId - The unique identifier for the album whose art is being retrieved.
* @returns {Promise<string|null>} - A promise that resolves to the image URL or null if not found or in case of an error.
*/
export const getAlbumArt = (albumId) => {
const endpoint = `http://coverartarchive.org/release/${albumId}/front`;
return axios.get(endpoint, { maxRedirects: 0 })
.then((response) => {
// If the status code is 200, the image URL should be in the responseURL
if (response.status === 200) {
return response.request.responseURL;
} if (response.status === 307) {
// If it's a 307 Temporary Redirect, the image URL will be in the headers 'location'
return response.headers.location;
}
// If the response status is not 200 or 307, which we don't handle, resolve as null
return null;
})
.catch((error) => {
if (error.response && error.response.status === 307) {
// In case of a 307 redirect status received in the error, return the 'location' header.
return error.response.headers.location;
}
console.error('Error retrieving cover art:', error);
return null;
});
};

26
src/api/lyricRetrieval.js Normal file
View File

@@ -0,0 +1,26 @@
import axiosRetry from '../services/retryAxios.js';
import { handleError } from '../errors/generalApiErrorHandler.js';
/**
* Fetches lyrics for a specific song using the provided artist name and title.
* Makes a call to the `lyrics.ovh` API and returns the lyrics found, if any.
*
* @param {string} artist - The name of the artist.
* @param {string} title - The title of the song.
* @returns {Promise<string|null>} Promise object representing the lyrics for the song or null if not found or in case of an error.
*/
export default function fetchLyrics(artist, title) {
const endpoint = `https://api.lyrics.ovh/v1/${encodeURIComponent(artist)}/${encodeURIComponent(title)}`;
return axiosRetry.get(endpoint)
.then((response) =>
// Access the lyrics from the response data object
response.data.lyrics || null, // If no lyrics are found, return null
)
.catch((error) => {
// Use the general API error handler to handle the error
const errorMessage = handleError(error, `${artist} - ${title}`);
console.error(errorMessage);
return null; // Return null to indicate failure to retrieve lyrics
});
}

View File

@@ -1,13 +0,0 @@
import axios from 'axios';
export default async function (artist, title) {
const endpoint = `https://api.lyrics.ovh/v1/${artist}/${title}`;
try {
const response = await axios.get(endpoint);
return response.data.lyrics;
} catch (error) {
console.error('Error fetching lyrics: ', error);
return null;
}
}

View File

@@ -1,7 +1,5 @@
import axios from 'axios';
import axiosRetry from '../services/retryAxios.js';
import { handleError } from '../errors/musicBrainzApiErrorHandler.js';
// import { handleError } from '../errors/musicBrainzApiErrorHandler.js';
import { handleError } from '../errors/generalApiErrorHandler.js';
/**
* Retrieves the metadata for a recording from MusicBrainz.
@@ -30,6 +28,7 @@ export default async function getAudioMetadata(recordingId) {
return null;
}
console.log('Metadata retrieval successful for:', recordingId);
return data;
})
.catch((error) => {

View File

@@ -1,4 +1,6 @@
import getTrackMetadata from '../api/metadataRetrieval.js';
import getLyrics from '../api/lyricRetrieval.js';
import {getAlbumArt} from "../api/imageRetrieval.js";
/**
* Controller to handle retrieval of metadata for an array of MusicBrainz recording IDs.
@@ -11,10 +13,29 @@ export async function retrieveMetadata(recordingIds) {
if (!Array.isArray(recordingIds)) {
throw new Error('MetadataController expects an array of recordingIds');
}
const metadata = recordingIds.map((recordingId) => getTrackMetadata(recordingId));
return metadata;
// Prepare an array to hold the metadata results initialized as an array of Promises
const metadataPromises = recordingIds.map(async (recordingId) => {
const trackMetadata = await getTrackMetadata(recordingId);
const artist = trackMetadata['artist-credit']?.[0]?.name;
const title = trackMetadata.title;
const albumId = trackMetadata.releases[0].id
// Assume getLyrics requires artist name and track name, which are to be obtained from the trackMetadata
const lyrics = await getLyrics(artist, title);
const albumArt = await getAlbumArt(albumId);
// Combine the track metadata with the lyrics
return {
...trackMetadata,
albumArt,
lyrics
};
});
return await Promise.all(metadataPromises);
} catch (e) {
console.error('Error retrieving metadata from MusicBrainz:', e);
console.error('Error retrieving metadata from MusicBrainz or lyrics:', e);
throw e;
}
}

View File

@@ -1,13 +1,14 @@
export function handleError(error, recordingId) {
export function handleError(error, identifier) {
let message = '';
// Handler when the API responds with an error status.
if (error.response) {
const { status } = error.response;
console.error(`Error with recording ID: ${recordingId}`);
console.error(`Request failed with status: ${status}`);
console.error('Headers:', error.response.headers);
console.error('Data:', error.response.data);
let message = `API responded with an error: ${error.response.data.message || status}`;
message = `API responded with an error for ${identifier}: ${error.response.data.message || status}`;
switch (status) {
case 400:
@@ -20,25 +21,27 @@ export function handleError(error, recordingId) {
message += ' - Forbidden: You do not have access to this resource.';
break;
case 404:
message += ' - Not Found: The requested resource was not found on the server.';
message += ' - Not Found: The requested resource or endpoint was not found on the server.';
break;
case 503:
message += ' - Service Unavailable: The server is not ready to handle the request. Common causes include server overload or server taken down for maintenance.';
message += ' - Service Unavailable: The server is not ready to handle the request.';
break;
default:
message += ' - An unexpected error occurred.';
}
return message;
} else if (error.request) {
// Case when the request was made but no response was received.
} if (error.request) {
console.error(`No response received for recording ID: ${recordingId}`);
console.error(`No response received for ${identifier}`);
console.error('Error request:', error.request);
return `No response from API for recording ID: ${recordingId}`;
// Errors that occur before the request is made, during setup.
message = `No response from API for ${identifier}`;
} else {
// Errors that occur before the request is made, during setup.
console.error(`Error setting up the request for ${identifier}`);
console.error('Error message:', error.message);
message = `Problem with setting up the request for ${identifier} - ${error.message}`;
}
console.error(`Error setting up the request for recording ID: ${recordingId}`);
console.error('Error message:', error.message);
return `Problem with setting up the request for recording ID: ${recordingId} - ${error.message}`;
// Log the message and return it
console.error(message);
return message;
}