chore: update public repo

This commit is contained in:
desu-bot 2025-01-14 02:38:00 +00:00
parent e0109980c0
commit e7c9507247
No known key found for this signature in database
25 changed files with 5364 additions and 0 deletions

View file

@ -0,0 +1,105 @@
import type { NavidromeSong } from '../../utils/navidrome.ts'
import { createRequire } from 'node:module'
import { join } from 'node:path'
import kuromoji from 'kuromoji'
import { isKana, toRomaji } from 'wanakana'
import { fetchSongs, navidromeFfetch as ffetch } from '../../utils/navidrome.ts'
const WHITELIST_KEYS = new Set([
// actual different tracks with the same title
'["sorry about my face","untitled track"]',
'["kooeetekumogeemusu","neko bushou sengoku emaki"]',
'["eve","merufuakutorii"]',
// todo
'["arm","legend of zelda"]',
'["arm","tomorrow heart beat ~ ashita anata ni dokkidoki☆ ~"]',
'["dwat","rotladatormarf"]',
'["fujiwara mari sai","zenbuatashinokawaiino"]',
])
const moji = await new Promise<any>((resolve, reject) => {
kuromoji.builder({
dicPath: join(createRequire(import.meta.url).resolve('kuromoji/'), '../../dict'),
}).build((err, tokenizer) => {
if (err) return reject(err)
resolve(tokenizer)
})
})
function clean(s: string) {
const str = s.toLowerCase()
.replace(/\(Explicit\)/i, '')
.replace(/[!@#$%^&*()_+=[\]{}\\|/,.;':"<>`~-]/g, '')
if (str.match(/[\u3000-\u303F\u3040-\u309F\u30A0-\u30FF\uFF00-\uFF9F\u4E00-\u9FAF\u3400-\u4DBF]/)) {
// has japanese
const tokens = moji.tokenize(str)
let res = ''
for (const token of tokens) {
if (token.word_type === 'UNKNOWN') {
res += isKana(token.surface_form) ? toRomaji(token.surface_form) : token.surface_form
} else if (token.word_type === 'KNOWN') {
res += `${toRomaji(token.reading)} `
}
}
return res.trimEnd()
}
return str
}
const CHUNK_SIZE = 1000
function getSongKey(song: NavidromeSong) {
return JSON.stringify([
clean(song.artist),
clean(song.title),
])
}
const seen = new Map<string, NavidromeSong[]>()
for (let offset = 0; ; offset += CHUNK_SIZE) {
const songs = await fetchSongs(offset, CHUNK_SIZE)
if (songs.length === 0) break
for (const song of songs) {
const key = getSongKey(song)
if (WHITELIST_KEYS.has(key)) continue
let arr = seen.get(key)
if (!arr) {
arr = []
seen.set(key, arr)
}
arr.push(song)
}
console.log('⌛ fetched chunk %d (%d items)', Math.floor(offset / CHUNK_SIZE), songs.length)
}
const keysSorted = Array.from(seen.keys()).sort()
let duplicates = 0
for (const key of keysSorted) {
const arr = seen.get(key)!
if (arr.length === 1) continue
duplicates += 1
console.log()
console.log('found duplicates for %s:', key)
for (const song of arr) {
console.log(' %s - %s (from %s - %s) (at %s)', song.artist, song.title, song.albumArtist, song.album, song.path)
}
}
if (duplicates === 0) {
console.log('✅ no duplicates found')
} else {
console.log('🚨 %d duplicates found', duplicates)
}

View file

@ -0,0 +1,66 @@
import { readFile, rm } from 'node:fs/promises'
import { join } from 'node:path'
import { $ } from 'zx'
import { downloadStream } from '../../utils/fetch.ts'
import { getEnv } from '../../utils/misc.ts'
import { fetchSongs } from '../../utils/navidrome.ts'
import { WebdavClient } from '../../utils/webdav.ts'
const webdav = new WebdavClient({
baseUrl: getEnv('NAVIDROME_WEBDAV_ENDPOINT'),
username: getEnv('NAVIDROME_WEBDAV_USERNAME'),
password: getEnv('NAVIDROME_WEBDAV_PASSWORD'),
})
const CHUNK_SIZE = 1000
for (let offset = 0; ; offset += CHUNK_SIZE) {
const songs = await fetchSongs(offset, CHUNK_SIZE)
if (songs.length === 0) break
for (const song of songs) {
const ext = song.path.split('.').pop()!
if (ext !== 'm4a') continue
console.log('❌ song %s is m4a, remuxing...', song.path)
const webdavPath = song.path.replace('/music/s3/', '/')
const res = await webdav.get(webdavPath).catch(() => null)
if (!res) {
console.log(' ❌ failed to get %s', webdavPath)
continue
}
const tmpfile = join('assets', `${song.id}.m4a`)
await downloadStream(res.body!, tmpfile)
console.log(' - downloaded to %s', tmpfile)
const probe = await $`ffprobe -v error -show_entries stream=codec_type,codec_name,index:stream_tags=title,language -of json ${tmpfile}`.json()
const audioStream = probe.streams.find(stream => stream.codec_type === 'audio')
if (!audioStream) {
console.log(' ❌ no audio stream found')
await rm(tmpfile)
continue
}
const codec = audioStream.codec_name
if (codec !== 'flac') {
console.log(` ❌ audio stream is ${codec}, not flac, skipping`)
await rm(tmpfile)
continue
}
console.log(' - audio stream is flac, remuxing')
// remux
const remuxed = join('assets', `${song.id}.flac`)
await rm(remuxed, { force: true })
await $`ffmpeg -i ${tmpfile} -c:a copy ${remuxed}`.quiet(true)
console.log(' - remuxed to %s', remuxed)
await rm(tmpfile)
await webdav.put(webdavPath.replace('.m4a', '.flac'), await readFile(remuxed))
await webdav.delete(webdavPath)
console.log(' - uploaded to %s', webdavPath.replace('.m4a', '.flac'))
await rm(remuxed)
}
}

View file

@ -0,0 +1,39 @@
import { filesize } from 'filesize'
import { z } from 'zod'
import { ffetch } from '../../utils/fetch.ts'
import { getEnv } from '../../utils/misc.ts'
const res = await ffetch('/api/v0/transfers/uploads', {
baseUrl: getEnv('SLSKD_ENDPOINT'),
headers: {
cookie: getEnv('SLSKD_COOKIE'),
},
}).parsedJson(z.array(
z.object({
username: z.string(),
directories: z.array(z.object({
directory: z.string(),
fileCount: z.number(),
files: z.array(z.object({
id: z.string(),
filename: z.string(),
state: z.string(),
bytesTransferred: z.number(),
})),
})),
}),
))
let total = 0
for (const user of res) {
for (const dir of user.directories) {
for (const file of dir.files) {
if (file.state !== 'Completed, Succeeded') continue
total += file.bytesTransferred
}
}
}
console.log(filesize(total))