-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Second? Yeah, so I originally planned for the project to use basic HTML+CSS with Puppeteer in Electron to generate the thumbnails, but that got more complicated than it had to be (bloated too). I decided to change course and use just simple JS in the browser, making use of the Canvas API to generate the thumbnails instead! This significantly slims down the project's stack, and it allows it to run on any device too! The project utilizes JS MediaTags, a JS library that allows you to read the metadata from song files. In this case, I will be using it to extract the artwork, artist, album, and song names from the song's metadata, then use that to programatically generate the YouTube Art Track thumbnail with the Canvas API. Very neat! I'm also using the JS with Types route, which is JSDoc + TSC. I think it works very well for building an app, as their isn't an API to be consumed by another user, unlike a library or package would. For those, I'd tend to lean more towards full TypeScript, as those tend to have more type exports and things like that. For this, I am only using the type checking to build the project itself. If I end up wanting to separate the project's code directly from the app itself (I do plan to do that eventually), then I would probably move the module bits over to TypeScript, since that would be used by others and not only the app. It would be cool to allow this to work away from the browser too, maybe making use of Puppetter and the Canvas API, so you could use Node exclusively. Is OffscreenCanvas available in Node? That would be great! Also planned to come along with this project (not sure if it will be part of this repo or not, yet), I am going to make a script that utilizes ffmpeg to generate the YouTube video itself, using the thumbnail generated here, and with the song audio itself. With those together, I'd also add an API hook to YouTube itself (you'd have to add cridentials to your account to connect with the YouTube API) so that it could also upload that video automatically. I want to add that, as it could handle making the playlist and such, and I would design it so that it would allow you to upload multiple songs at a time (after the previous one finishes processing), and they will appear in the same order as you intend them to be. I have had a lot of trouble uplading multiple videos at once, in a selected order, and keeping them in that same order as they upload. After they are processed, YouTube seems to disregard that original order, and order them at random, no matter how you had them originally. In a similar vein, this also happens when you un-private privated videos. This caught me by suprise, and it was very annoying. I wanted all of my songs to show up in my video feed in the correct order to how the album goes, and it ended up breaking because of that. So, with all of those together, I essentially am making a programmatic way of generating the art, and video for each of your song files (using the song's metadata itself), then proceeding to upload each of those to YouTube, using that same metadata, and ensuring that they upload in the exact order that matches the order of your album. Super happy with this project so far! I thought of it while up in Mammoth a few weeks ago. These files were from last Tuesday, and I'm making a repo for them now. The original Puppeteer in Electron version was just a few days before that. Was gonna commit it sooner, but stepped back into the Gamedata Parser project again! Lots of new developments over there. I was gonna work on that tonight actually, but realized I should probably get this rolling before it falls to the backburner.
- Loading branch information
0 parents
commit b80a218
Showing
13 changed files
with
238 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.DS_Store | ||
._* | ||
Desktop.ini | ||
Thumbs.db | ||
|
||
/node_modules | ||
/dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2022 Brandon Bennett | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Art-Gen | ||
|
||
An app to generate thumbnails for YouTube Art Tracks! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<!DOCTYPE html> | ||
<html lang="en-US"> | ||
|
||
<head> | ||
|
||
<title>Art Gen</title> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1"> | ||
<meta name="color-scheme" content="dark"> | ||
|
||
<link rel="stylesheet" href="./styles.css"> | ||
<link rel="preconnect" href="https://fonts.googleapis.com"> | ||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | ||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans&display=swap"> | ||
|
||
</head> | ||
|
||
<body> | ||
|
||
<canvas width="1920" height="1080"></canvas> | ||
|
||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jsmediatags.min.js"></script> | ||
<script type="module" src="./src/app.mjs"></script> | ||
|
||
</body> | ||
|
||
</html> |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"name": "art-gen", | ||
"devDependencies": { | ||
"@types/jsmediatags": "^3.9.3" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { readTags, fromPicture } from "./jsmediatags.mjs"; | ||
|
||
const demo = await fetch("../test/26.m4a") | ||
.then(response => response.blob()); | ||
|
||
const thumbnail = await generateThumbnail(demo); | ||
|
||
/** | ||
* Generates a video thumbnail for a given song file. | ||
* | ||
* @param { Blob } song | ||
*/ | ||
export async function generateThumbnail(song){ | ||
const canvas = document.querySelector("canvas"); | ||
if (canvas === null){ | ||
throw new ReferenceError("Cannot find canvas"); | ||
} | ||
|
||
const ctx = canvas.getContext("2d"); | ||
if (ctx === null){ | ||
throw new SyntaxError("Cannot create canvas context"); | ||
} | ||
|
||
const tags = await readTags(song); | ||
console.log(tags); | ||
|
||
if (typeof tags.picture === "undefined"){ | ||
throw new TypeError("Cannot load artwork from song"); | ||
} | ||
|
||
const image = await fromPicture(tags.picture); | ||
|
||
const { naturalHeight } = image; | ||
const { width, height } = canvas; | ||
|
||
ctx.translate(0,height / 2); | ||
ctx.translate(0,-naturalHeight / 2); | ||
|
||
ctx.filter = "blur(30px)"; | ||
|
||
for (let i = 0; i < 16; i++){ | ||
ctx.drawImage(image,0,0,width,naturalHeight); | ||
} | ||
|
||
ctx.resetTransform(); | ||
ctx.filter = "none"; | ||
|
||
ctx.fillStyle = "rgb(0 0 0 / 0.7)"; | ||
ctx.fillRect(0,0,width,height); | ||
|
||
ctx.drawImage(image,135,135,810,810); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
declare global { | ||
interface Window { | ||
jsmediatags: typeof import("jsmediatags"); | ||
} | ||
} | ||
|
||
export {}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/** | ||
* Loads an image from a specified URL. | ||
* | ||
* @param { string } src | ||
* @param { HTMLImageElement } image | ||
*/ | ||
export async function loadImage(src,image = new Image()){ | ||
await new Promise((resolve,reject) => { | ||
image.addEventListener("load",resolve,{ once: true }); | ||
image.addEventListener("error",reject,{ once: true }); | ||
image.src = src; | ||
}); | ||
return image; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { loadImage } from "./image.mjs"; | ||
const { jsmediatags } = window; | ||
|
||
/** | ||
* @typedef { import("jsmediatags/types").Tags } Tags | ||
* @typedef { import("jsmediatags/types").PictureType } PictureType | ||
*/ | ||
|
||
/** | ||
* Reads the media tags for a given song file. | ||
* | ||
* @param { string | Blob } data Accepts either a URL string or a Blob object. | ||
* @returns { Promise<Tags> } Resolves to a Tags object. | ||
*/ | ||
export async function readTags(data){ | ||
return new Promise((resolve,reject) => { | ||
jsmediatags.read(data,{ | ||
onSuccess: ({ tags }) => resolve(tags), | ||
onError: reject | ||
}); | ||
}); | ||
} | ||
|
||
/** | ||
* Reads a Picture tag, and returns an equivalent Image object for that data. | ||
* | ||
* @param { PictureType } picture | ||
*/ | ||
export async function fromPicture(picture){ | ||
try { | ||
const { format: type = "", data = [] } = picture; | ||
const media = new Blob([new Uint8Array(data)],{ type }); | ||
const source = window.URL.createObjectURL(media); | ||
|
||
return loadImage(source); | ||
} catch { | ||
throw new TypeError("Could not construct an image from the picture data"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
*, *::before, *::after { | ||
box-sizing: border-box; | ||
} | ||
|
||
html { | ||
width: 100%; | ||
height: 100%; | ||
} | ||
|
||
body { | ||
margin: 0; | ||
width: 100%; | ||
height: 100%; | ||
font-family: system-ui, sans-serif; | ||
} | ||
|
||
canvas { | ||
width: 100%; | ||
height: 100%; | ||
display: block; | ||
object-fit: contain; | ||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"compilerOptions": { | ||
"allowJs": true, | ||
"checkJs": true, | ||
"rootDir": "./src", | ||
"outDir": "./dist", | ||
"module": "ESNext", | ||
"moduleResolution": "Node", | ||
"target": "ESNext", | ||
"noEmit": true, | ||
"strict": true | ||
} | ||
} |