diff --git a/app.js b/app.js index 4e80304..f0fc03b 100644 --- a/app.js +++ b/app.js @@ -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) { diff --git a/src/api/imageRetrieval.js b/src/api/imageRetrieval.js new file mode 100644 index 0000000..8e1dabf --- /dev/null +++ b/src/api/imageRetrieval.js @@ -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} - 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; + }); +}; diff --git a/src/api/lyricRetrieval.js b/src/api/lyricRetrieval.js new file mode 100644 index 0000000..09e2ce6 --- /dev/null +++ b/src/api/lyricRetrieval.js @@ -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} 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 + }); +} diff --git a/src/api/lyricsRetrieval.js b/src/api/lyricsRetrieval.js deleted file mode 100644 index 792583f..0000000 --- a/src/api/lyricsRetrieval.js +++ /dev/null @@ -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; - } -} diff --git a/src/api/metadataRetrieval.js b/src/api/metadataRetrieval.js index 7d8b99c..eabfc56 100644 --- a/src/api/metadataRetrieval.js +++ b/src/api/metadataRetrieval.js @@ -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) => { diff --git a/src/controllers/metadataController.js b/src/controllers/metadataController.js index 771cdbe..e02b08c 100644 --- a/src/controllers/metadataController.js +++ b/src/controllers/metadataController.js @@ -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; } } diff --git a/src/errors/musicBrainzApiErrorHandler.js b/src/errors/generalApiErrorHandler.js similarity index 53% rename from src/errors/musicBrainzApiErrorHandler.js rename to src/errors/generalApiErrorHandler.js index c572018..09028b9 100644 --- a/src/errors/musicBrainzApiErrorHandler.js +++ b/src/errors/generalApiErrorHandler.js @@ -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; }