-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
build_music_metadata.js
191 lines (158 loc) · 5.3 KB
/
build_music_metadata.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
const path = require('path')
const fs = require('fs')
const { readdir } = require('fs').promises
const sharp = require('sharp')
const empty_transparent_png = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='
async function* getFilesRecursive(dir, root = dir) {
// source: https://stackoverflow.com/a/45130990/2387277
// source: https://stackoverflow.com/questions/5827612/node-js-fs-readdir-recursive-directory-search
// I added the part with the root_path variable, because I wanted to get the relative path of the files.
const root_path = path.resolve(root) + '/'
const dirents = await readdir(dir, { withFileTypes: true });
for (const dirent of dirents) {
const res = path.resolve(dir, dirent.name);
if (dirent.isDirectory()) {
yield* getFilesRecursive(res, dir);
} else {
yield res.replace(root_path, '');
}
}
}
function get_genres(playlist) {
let genres = playlist.Songs
// sort the genres by count with reduce
.reduce((acc, song) => {
const genre = song.Genre
if (!acc.hasOwnProperty(genre)) {
acc[genre] = 0
}
acc[genre] += 1
return acc
}, {})
genres = Object.entries(genres)
.sort((a, b) => b[1] - a[1])
.map(([genre, count]) => genre)
.slice(0, 3) // only keep the top 3 genres
return genres
}
function get_artists(playlist) {
let artists = playlist.Songs
// sort the artists by count with reduce
.reduce((acc, song) => {
const artist = song.Artist
if (!acc.hasOwnProperty(artist)) {
acc[artist] = 0
}
acc[artist] += 1
return acc
}, {})
artists = Object.entries(artists)
.sort((a, b) => b[1] - a[1])
.map(([artist, count]) => artist)
.slice(0, 3) // only keep the top 3 artists
return artists
}
// Function to combine images into a 2x2 grid
async function combineImages(imageURIs, options = {}) {
const {
canvasWidth = 128, // Adjust as needed
canvasHeight = 128, // Adjust as needed
} = options
const images = []
// Loop through the image URIs and composite them onto the canvas
for (let i = 0; i < imageURIs.length; i++) {
const x = i % 2 * (canvasWidth / 2); // Calculate x position based on column
const y = Math.floor(i / 2) * (canvasHeight / 2); // Calculate y position based on row
// Composite the image onto the canvas
images.push({
input: Buffer.from(imageURIs[i].split(',')[1], 'base64'), // Extract image data from data URI
top: y,
left: x
})
}
// Create a new canvas with the specified width and height
const canvas = sharp({
create: {
width: canvasWidth,
height: canvasHeight,
channels: 4,
background: { r: 0, g: 0, b: 0, alpha: 0 } // Transparent background
}
})
.composite(images)
.png({ encoding: "base64" })
const dataUrl = await canvas.toBuffer();
return `data:image/png;base64,${dataUrl.toString('base64')}`;
}
async function get_playlist_coverphoto(playlist) {
let images = []
for (const song of playlist.Songs) {
const image = song['Album Artwork']
if (image && image !== '') {
if (!images.find(img => img === image)) {
images.push(image)
if (images.length >= 4) {
break
}
}
}
}
if (images.length === 0) {
return empty_transparent_png
}
if (images.length === 1) {
images.push(empty_transparent_png)
images.push(empty_transparent_png)
images.push(empty_transparent_png)
}
if (images.length === 2) {
images.push(empty_transparent_png)
images.push(empty_transparent_png)
}
if (images.length === 3) {
images.push(empty_transparent_png)
}
// Call the function to combine images
const finished_image = await combineImages(images, {
canvasWidth: 128,
canvasHeight: 128,
})
return finished_image
}
async function build_music_metadata() {
const playlists = []
const playlist_dir = './data_about_thomasrosen/music/playlists/'
for await (const relative_filepath of getFilesRecursive(playlist_dir)) {
// get the playlist_date from the filename (2018 12 DEZ.json) with regexp
const playlist_date = relative_filepath.match(/(\d{4}) (\d{2}) ([A-Z]{3})\.json/)
if (!playlist_date) {
continue
}
const year = playlist_date[1]
const month = playlist_date[2]
const playlist_date_string = `${year}-${month}-01T00:00:00.000Z`
// get playlist
const playlist = JSON.parse(fs.readFileSync(playlist_dir + relative_filepath))
if (playlist.Songs.hasOwnProperty('Title')) {
// if there is only one song, ios saves this as an object, not as an array
playlist.Songs = [playlist.Songs]
}
// push playlist metadata to playlists
playlists.push({
name: playlist.Name,
filename: relative_filepath,
date_month: playlist_date_string,
date_updated: playlist.Date,
count: playlist.Songs.length,
genres: get_genres(playlist),
// artists: get_artists(playlist),
coverphoto: await get_playlist_coverphoto(playlist),
})
}
const playlists_metadata = {
playlists,
}
fs.writeFileSync('./public/music/playlists.json', JSON.stringify(playlists_metadata))
// fs.writeFileSync('./build/music/playlists.json', JSON.stringify(playlists_metadata))
}
build_music_metadata()