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) } }