Skip to content

Commit

Permalink
Implemented metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
FrenzyExists committed Nov 30, 2024
1 parent 7df51eb commit 7f32540
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 60 deletions.
Binary file modified .DS_Store
Binary file not shown.
11 changes: 5 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,20 +182,17 @@ const navigateSpotifyTracks = async (token, playlistId, download_path, track_siz
if (!tracks) {
throw new Error("No tracks found in the playlist.");
}

const choices = tracks
.map((t) => {

return {
name: `${t.track.name} - ${t.track.artists.map((a) => a.name).join(", ")}`, // Visible to user
value: {
name: t.track.name,
name: t.track.name,
duration_ms: t.track.duration_ms,
name: t.track.name,
duration_ms: t.track.duration_ms,
artist: t.track.artists.map((a) => a.name),
album: t.track.album.name,
duration_ms: t.track.duration_ms,
image: t.track.album.images?.[0]?.url || "",
album_url: t.track.album.href,
external_url: t.track.external_urls.spotify,
Expand All @@ -204,6 +201,8 @@ const navigateSpotifyTracks = async (token, playlistId, download_path, track_siz
track_number: t.track.track_number,
release_date: t.track.album.release_date,
type: t.track.type,
explicit: t.track.explicit,
isrc: t.track.external_ids.isrc
},
};
});
Expand All @@ -214,7 +213,7 @@ const navigateSpotifyTracks = async (token, playlistId, download_path, track_siz
return;
}

searchAndDownloadYTTrack(t.artist[0], t.name, download_path, 1);
searchAndDownloadYTTrack(t, download_path, 1);
}))
} catch (error) {
console.error(`Error: ${error.message}`);
Expand Down
60 changes: 30 additions & 30 deletions kanpilot.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,6 @@ name = "To Do"
priority = ""
linkCommits = [ ]

[[processes.tasks]]
id = "v1lpyqddavsj7twc1m5swhnc"
title = "Get a public playlist"
description = "Returns a public playlist given a spotify playlist url"
tag = "backlog"
linkFiles = [ ]
dueDate = ""
checkList = [ ]
priority = ""
linkCommits = [ ]

[[processes.tasks]]
id = "llv5r9dmvk8pz34ekwnfsup1"
title = "Implement basic user info"
Expand Down Expand Up @@ -55,31 +44,20 @@ id = "process2"
name = "In Progress"

[[processes.tasks]]
id = "lvpws0ingpg1i6akfsp3x69h"
title = "Modify mp3 metadata with spotify data"
description = ""
tag = "cli"
linkFiles = [ ]
dueDate = ""
checkList = [ ]
priority = "High"
linkCommits = [ ]

[[processes.tasks]]
id = "wvxa5wvg9tf5szf9sgwhuiys"
title = "Allow Spotituby plz"
description = '<p>Agree</p><p> Tell puppeteer to look at this button and do something about it</p><p></p><p>Use this</p><p></p><p>&lt;button data-testid="auth-accept" data-encore-id="buttonPrimary" class="Button-sc-qlcn5g-0 hVnPpH"&gt;&lt;span class="ButtonInner-sc-14ud5tc-0 dOeYIb encore-bright-accent-set"&gt;&lt;p data-encore-id="type" class="Type__TypeElement-sc-goli3j-0 bkjCej"&gt;Agree&lt;/p&gt;&lt;/span&gt;&lt;span class="ButtonFocus-sc-2hq6ey-0 iMcXKe"&gt;&lt;/span&gt;&lt;/button&gt;</p>'
tag = "bug"
id = "thkjnadh8qoxuhzcekbfol5q"
title = "implement a jester"
description = "I have no idea what to test lmao"
tag = "backlog"
linkFiles = [ ]
dueDate = ""
checkList = [ ]
priority = "High"
priority = ""
linkCommits = [ ]

[[processes.tasks]]
id = "thkjnadh8qoxuhzcekbfol5q"
title = "implement a jester"
description = "I have no idea what to test lmao"
id = "v1lpyqddavsj7twc1m5swhnc"
title = "Get a public playlist"
description = "Returns a public playlist given a spotify playlist url"
tag = "backlog"
linkFiles = [ ]
dueDate = ""
Expand Down Expand Up @@ -134,3 +112,25 @@ name = "Done"
checkList = [ ]
priority = "Low"
linkCommits = [ ]

[[processes.tasks]]
id = "wvxa5wvg9tf5szf9sgwhuiys"
title = "Allow Spotituby plz"
description = '<p>Agree</p><p> Tell puppeteer to look at this button and do something about it</p><p></p><p>Use this</p><p></p><p>&lt;button data-testid="auth-accept" data-encore-id="buttonPrimary" class="Button-sc-qlcn5g-0 hVnPpH"&gt;&lt;span class="ButtonInner-sc-14ud5tc-0 dOeYIb encore-bright-accent-set"&gt;&lt;p data-encore-id="type" class="Type__TypeElement-sc-goli3j-0 bkjCej"&gt;Agree&lt;/p&gt;&lt;/span&gt;&lt;span class="ButtonFocus-sc-2hq6ey-0 iMcXKe"&gt;&lt;/span&gt;&lt;/button&gt;</p>'
tag = "bug"
linkFiles = [ ]
dueDate = ""
checkList = [ ]
priority = "High"
linkCommits = [ ]

[[processes.tasks]]
id = "lvpws0ingpg1i6akfsp3x69h"
title = "Modify mp3 metadata with spotify data"
description = ""
tag = "cli"
linkFiles = [ ]
dueDate = ""
checkList = [ ]
priority = "High"
linkCommits = [ ]
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
{
"name": "spotituby",
"version": "1.0.0",
"version": "1.0.1",
"main": "index.js",
"type": "module",
"scripts": {
"app": "node index.js"
},
"publishConfig": {
"registry": "https://npm.pkg.github.com"
},
"repository": {
"type": "git",
"url": "git+https://github.com/FrenzyExists/spotituby.git"
Expand Down
140 changes: 117 additions & 23 deletions src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,18 +166,15 @@ const fetchLikedTracks = async (accessToken, pageSize = 50) => {
// Paginate if pageSize exceeds 50
while (offset < pageSize) {
const limit = Math.min(maxPageSize, pageSize - offset); // Fetch only the required amount
const response = await axios.get(
"https://api.spotify.com/v1/me/tracks",
{
headers: {
Authorization: `Bearer ${accessToken}`
},
params: {
limit,
offset
}
const response = await axios.get("https://api.spotify.com/v1/me/tracks", {
headers: {
Authorization: `Bearer ${accessToken}`
},
params: {
limit,
offset
}
);
});

totalTracks = totalTracks.concat(response.data.items);
offset += limit;
Expand All @@ -196,34 +193,65 @@ const fetchLikedTracks = async (accessToken, pageSize = 50) => {
};


const getFilenameFromCommand = (metadataCommand, outputDir) => {
return new Promise((resolve, reject) => {
const titleProcess = exec(metadataCommand);

let filename = "";
titleProcess.stdout.on("data", (data) => {
filename = `${outputDir}/${data.trim()}`; // Construct the filename
});
console.log(filename);

titleProcess.on("exit", (code) => {
if (code === 0) {
resolve(filename);
} else {
reject(new Error("Failed to retrieve filename"));
}
});

titleProcess.on("error", (err) => {
reject(err);
});
});
};


/**
* Searches for a YouTube track based on artist and title, and downloads it in MP3 format.
*
* @param {string|null} artist - The artist's name. Used in search query if provided.
* @param {string|null} title - The track title. Used in search query if provided.
* @param {object|null} metadata - Metadata stuff
* @param {string|null} outputDir - The directory where the downloaded file will be saved.
* @param {number} resultsCount - The number of search results to consider. Defaults to 1.
* @param {boolean} search - Flag to determine if a search should be performed. Defaults to true.
* @param {string|null} url - Direct URL of the track, bypassing search if provided.
*/
const searchAndDownloadYTTrack = async (
artist = null,
title = null,
metadata = null,
outputDir = null,
resultsCount = 1,
search = true,
url = null
) => {

const filterQuery =
search && !url
? `--reject-title "official video|music video"`
: "";
const searchQuery =
search && !url
? `ytsearch${resultsCount}:"${artist} - ${title}"`
: `ytsearch${resultsCount}:"${artist} - ${title}"`;
? `ytsearch${resultsCount}:"${metadata.artist} - ${metadata.name}"`
: "";

const urlQuery = url && !search ? `url:${url}` : "";
// {"downloaded_bytes": 4111336, "total_bytes": 4111336, "filename": "./downloads/Voyage - Dynamic.webm", "status": "finished", "elapsed": 0.9414091110229492, "ctx_id": null, "speed": 4367215.0097767385, "_speed_str": "4.16MiB/s", "_total_bytes_str": " 3.92MiB", "_elapsed_str": "00:00:00", "_percent_str": "100.0%", "_default_template": "100% of 3.92MiB in 00:00:00 at 4.16MiB/s"}

const downloadQuery = `-x --audio-format mp3 -o "${outputDir}/%(title)s.%(ext)s" --quiet --progress --progress-template "%(progress._percent_str)s - %(progress._total_bytes_str)s ETA %(progress._eta_str)s"`;

const command = `yt-dlp ${urlQuery} ${downloadQuery} ${searchQuery}`;
const command = `yt-dlp ${urlQuery} ${downloadQuery} ${searchQuery} ${filterQuery}`;

const metadataCommand = `yt-dlp ${urlQuery} ${searchQuery} ${filterQuery} --print "%(title)s.%(ext)s"`;

const process = exec(command);
process.stdio[1].on("data", data => {
console.log(data.toString());
Expand All @@ -236,9 +264,19 @@ const searchAndDownloadYTTrack = async (
process.kill();
});

process.on("exit", () => {
process.on("exit", async () => {
console.log("------------------------------------------");
console.log(`Saved at ${outputDir}`);

try {
const filename = await getFilenameFromCommand(metadataCommand, outputDir);
console.log(`Title: ${filename}`);

await writeMetadata(metadata, filename);
console.log("Metadata written successfully!");
} catch (error) {
console.error("Error getting filename or writing metadata:", error);
}
});
};

Expand Down Expand Up @@ -271,7 +309,7 @@ const fetchPlaylistTracks = async (accessToken, playlistId, pageSize = 50) => {
break;
}
}

return totalTracks;
} catch (error) {
console.error("Error fetching playlist tracks:", error.message);
Expand Down Expand Up @@ -326,8 +364,64 @@ const trackSelector = async choices => {
return selectedTracks;
};

const writeMetadata = (info, outputDir) => {
console.log("Havent implemented this sry 😂");
const fetchImage = async imageUrl => {
try {
const response = await axios.get(imageUrl, {
responseType: "arraybuffer"
});

// Return the buffer and MIME type
return Buffer.from(response.data);
} catch (error) {
console.error("Error fetching image:", error.message);
throw new Error("Failed to fetch image");
}
};


/**
* Writes metadata tags to the specified music file.
*
* This function takes track information and a file path,
* then writes metadata tags such as title, artist, album,
* year, track number, and more to the music file.
*
* @param {object} info - An object containing metadata information about the track.
* @param {string} info.name - The title of the track.
* @param {string[]} info.artist - An array of artist names.
* @param {string} info.album - The album name of the track.
* @param {string} info.release_date - The release date of the track in YYYY-MM-DD format.
* @param {number} info.track_number - The track number in the album.
* @param {number} info.duration_ms - The duration of the track in milliseconds.
* @param {string} info.isrc - The International Standard Recording Code of the track.
* @param {string} info.image - URL to the image associated with the track.
* @param {boolean} info.explicit - Indicates if the track contains explicit content.
* @param {string} filepath - The path to the music file where metadata will be written.
*/
const writeMetadata = async (info, filepath) => {
// fetch image url
const img = await fetchImage(info.image);

const tags = {
title: info.name,
artist: info.artist.join(", "), // TPE1
album: info.album, // TALB
year: info.release_date.split("-")[0], // TYER
date: info.release_date.replace(/-/g, ""), // TDAT (Format: DDMM)
trackNumber: `${info.track_number}`, // TRCK
disc_number: info.disc_number,
length: `${Math.round(info.duration_ms / 1000)}`, // TLEN in seconds
ISRC: info.isrc, // TSRC
mediaType: "Digital", // TMED
APIC: img,
comment: info.explicit ? "Explicit content" : "Clean content", // COMM
originalTitle: info.name // TOAL
};

let f = filepath.replace(/\.[^/.]+$/, ".mp3");
console.log(f);

NodeID3.update(tags, f, (e, buff) => {});
};

const TOKENFILE = ".token";
Expand Down

0 comments on commit 7f32540

Please sign in to comment.