chore: update public repo

This commit is contained in:
desu-bot 2025-09-14 21:52:13 +00:00
parent 728699b3ec
commit 96ca247fcb
No known key found for this signature in database
6 changed files with 261 additions and 133 deletions

View file

@ -12,7 +12,8 @@ import { FileCookieStore } from 'tough-cookie-file-store'
import { z } from 'zod'
import { $, question } from 'zx'
import { ffetch as ffetchBase } from '../../utils/fetch.ts'
import { sanitizeFilename } from '../../utils/fs.ts'
import { sanitizeFilename, writeWebStreamToFile } from '../../utils/fs.ts'
import { generateFfmpegMetadataFlags, pipeIntoProc, runMetaflac } from '../../utils/media-metadata.ts'
import { getEnv } from '../../utils/misc.ts'
const jar = new CookieJar(new FileCookieStore('./assets/deezer-cookies.json'))
@ -151,7 +152,7 @@ const GwTrack = z.object({
SNG_ID: z.string(),
SNG_TITLE: z.string(),
TRACK_NUMBER: z.string(),
VERSION: z.string(),
VERSION: z.string().optional(),
MD5_ORIGIN: z.string(),
FILESIZE_AAC_64: z.coerce.number(),
FILESIZE_MP3_64: z.coerce.number(),
@ -182,6 +183,8 @@ const GwAlbum = z.object({
ART_ID: z.string(),
ART_NAME: z.string(),
})),
TYPE: z.string().optional(), // "0" = single, "1" = normal album, "3" = ep
ROLE_ID: z.number().optional(), // 0 = own album, 5 = "featured on" album
COPYRIGHT: z.string(),
PRODUCER_LINE: z.string(),
DIGITAL_RELEASE_DATE: z.string(),
@ -471,112 +474,48 @@ async function downloadTrack(track: GwTrack, opts: {
'0:a',
'-c',
'copy',
'-metadata',
`title=${getTrackName(track)}`,
'-metadata',
`album=${track.ALB_TITLE}`,
'-metadata',
`year=${track.DIGITAL_RELEASE_DATE}`,
'-metadata',
`comment=ripped from deezer (id: ${track.SNG_ID})`,
'-metadata',
`track=${track.TRACK_NUMBER}`,
'-metadata',
`disc=${track.DISK_NUMBER}`,
...generateFfmpegMetadataFlags({
title: getTrackName(track),
album: opts.album?.ALB_TITLE ?? track.ALB_TITLE,
year: track.DIGITAL_RELEASE_DATE,
comment: `ripped from deezer (id: ${track.SNG_ID})`,
track: track.TRACK_NUMBER,
disc: track.DISK_NUMBER,
composer: track.SNG_CONTRIBUTORS?.composer,
artist: track.ARTISTS?.map(it => it.ART_NAME) ?? track.ART_NAME,
}),
filename,
]
if (opts.album) {
params.push('-metadata', `album=${opts.album.ALB_TITLE}`)
}
if (track.SNG_CONTRIBUTORS?.composer) {
for (const composer of track.SNG_CONTRIBUTORS.composer) {
params.push('-metadata', `composer=${composer}`)
}
}
if (track.ARTISTS?.length) {
for (const artist of track.ARTISTS) {
params.push('-metadata', `artist=${artist.ART_NAME}`)
}
} else {
params.push('-metadata', `artist=${track.ART_NAME}`)
}
if (lyricsLrc) {
await writeFile(`${opts.destination}.lrc`, lyricsLrc)
}
const proc = $`ffmpeg ${params}`
const pipe = Readable.fromWeb(decStream as any).pipe(proc.stdin)
await new Promise<void>((resolve, reject) => {
pipe.on('error', reject)
pipe.on('finish', resolve)
})
await pipeIntoProc(proc, decStream)
await proc
} else {
const fd = await open(filename, 'w+')
const writer = fd.createWriteStream()
await writeWebStreamToFile(decStream, filename)
for await (const chunk of decStream as any) {
writer.write(chunk)
}
writer.end()
await new Promise<void>((resolve, reject) => {
writer.on('error', reject)
writer.on('finish', resolve)
await runMetaflac({
path: filename,
tags: {
TITLE: getTrackName(track),
ALBUM: track.ALB_TITLE,
DATE: track.DIGITAL_RELEASE_DATE ?? asNonNull(opts.album?.DIGITAL_RELEASE_DATE),
DISCNUMBER: track.DISK_NUMBER,
TRACKNUMBER: track.TRACK_NUMBER,
COMMENT: `ripped from deezer (id: ${track.SNG_ID})`,
ARTIST: track.ARTISTS?.map(it => it.ART_NAME) ?? track.ART_NAME,
COMPOSER: track.SNG_CONTRIBUTORS?.composer,
MAIN_ARTIST: track.SNG_CONTRIBUTORS?.main_artist,
ISRC: track.ISRC,
PRODUCER: opts.album?.PRODUCER_LINE,
COPYRIGHT: opts.album?.COPYRIGHT,
LYRICS: lyricsLrc,
},
coverPath: albumCoverPath,
})
const params: string[] = [
'--remove-all-tags',
`--set-tag=TITLE=${getTrackName(track)}`,
`--set-tag=ALBUM=${track.ALB_TITLE}`,
`--set-tag=DATE=${track.DIGITAL_RELEASE_DATE ?? asNonNull(opts.album?.DIGITAL_RELEASE_DATE)}`,
`--set-tag=DISCNUMBER=${track.DISK_NUMBER}`,
`--set-tag=TRACKNUMBER=${track.TRACK_NUMBER}`,
`--set-tag=COMMENT=ripped from deezer (id: ${track.SNG_ID})`,
`--import-picture-from=${albumCoverPath}`,
]
if (track.ARTISTS) {
for (const artist of track.ARTISTS) {
params.push(`--set-tag=ARTIST=${artist.ART_NAME}`)
}
} else {
params.push(`--set-tag=ARTIST=${track.ART_NAME}`)
}
if (track.SNG_CONTRIBUTORS?.composer) {
for (const composer of track.SNG_CONTRIBUTORS.composer) {
params.push(`--set-tag=COMPOSER=${composer}`)
}
}
if (track.SNG_CONTRIBUTORS?.main_artist) {
for (const mainArtist of track.SNG_CONTRIBUTORS.main_artist) {
params.push(`--set-tag=MAIN_ARTIST=${mainArtist}`)
}
}
if (track.ISRC) {
params.push(`--set-tag=ISRC=${track.ISRC}`)
}
if (opts.album) {
params.push(`--set-tag=PRODUCER=${opts.album.PRODUCER_LINE}`)
params.push(`--set-tag=COPYRIGHT=${opts.album.COPYRIGHT}`)
}
if (lyricsLrc) {
params.push(`--set-tag=LYRICS=${lyricsLrc}`)
}
params.push(filename)
await $`metaflac ${params}`
}
await rm(albumCoverPath, { force: true })
@ -595,7 +534,7 @@ async function downloadTrackList(tracks: GwTrack[], opts: {
const isMultiDisc = tracks.some(it => it.DISK_NUMBER !== '1')
const firstTrackArtistString = getTrackArtistString(tracks[0])
const isVariousArtists = tracks.some(it => getTrackArtistString(it) !== firstTrackArtistString)
const isDifferentArtists = tracks.some(it => getTrackArtistString(it) !== firstTrackArtistString)
await asyncPool(tracks, async (track) => {
let filename = ''
@ -605,7 +544,7 @@ async function downloadTrackList(tracks: GwTrack[], opts: {
}
filename = `${track.TRACK_NUMBER.padStart(2, '0')}. `
}
if (isVariousArtists) {
if (isDifferentArtists) {
filename += `${getTrackArtistString(track)} - `
}
filename += `${getTrackName(track)}`
@ -636,7 +575,11 @@ const GwPageArtist = z.object({
}),
})
async function downloadArtist(artistId: string) {
async function downloadArtist(options: {
artistId: string
includeFeaturedAlbums?: boolean
}) {
const { artistId, includeFeaturedAlbums = false } = options
const artistInfo = await gwLightApi({
method: 'deezer.pageArtist',
token: userData.checkForm,
@ -681,6 +624,8 @@ async function downloadArtist(artistId: string) {
})
for (const alb of res.data) {
if (!includeFeaturedAlbums && alb.ROLE_ID === 5) continue
albums.push(alb)
trackCount += asNonNull(alb.SONGS).total
}
@ -692,7 +637,6 @@ async function downloadArtist(artistId: string) {
spinnies.succeed('collect', { text: `collected ${albums.length} albums with a total of ${trackCount} tracks` })
}
// fixme: "featured" albums/tracks (i.e. when main artist of the album is not the one we're dling) should have album artist name in its dirname
// fixme: singles should be saved in artist root dir
// todo: automatic musicbrainz matching
// todo: automatic genius/musixmatch matching for lyrics if unavailable directly from deezer
@ -713,11 +657,16 @@ async function downloadArtist(artistId: string) {
assert(tracks.total === asNonNull(alb.SONGS).total)
assert(tracks.data.length === asNonNull(alb.SONGS).total)
let folderName = alb.ALB_TITLE
if (alb.ROLE_ID === 5) {
folderName = `${artistInfo.DATA.ART_NAME} - ${folderName}`
}
await downloadTrackList(tracks.data, {
destination: join(
'assets/deezer-dl',
sanitizeFilename(artistInfo.DATA.ART_NAME),
sanitizeFilename(alb.ALB_TITLE),
sanitizeFilename(folderName),
),
album: alb,
poolLimit: 4,
@ -827,7 +776,11 @@ async function downloadByUri(uri: string) {
}
if (type === 'artist') {
await downloadArtist(id)
const includeFeaturedAlbums = await question('include featured albums? (y/N) > ')
await downloadArtist({
artistId: id,
includeFeaturedAlbums: includeFeaturedAlbums.toLowerCase() === 'y',
})
}
}

View file

@ -10,7 +10,7 @@ import { $, ProcessOutput, question } from 'zx'
import { downloadFile, ffetch as ffetchBase } from '../../utils/fetch.ts'
import { sanitizeFilename } from '../../utils/fs.ts'
import { chunks, getEnv } from '../../utils/misc.ts'
import { generateOpusImageBlob } from '../../utils/opus.ts'
import { generateOpusImageBlob } from '../../utils/media-metadata.ts'
const ffetchApi = ffetchBase.extend({
baseUrl: 'https://api-v2.soundcloud.com',