Refactor authentication and utility functions for improved handling of audio formats and API responses

This commit is contained in:
Seth 2025-05-05 20:58:55 -04:00
parent d055deec29
commit 5325bbc34a
3 changed files with 27 additions and 23 deletions

View file

@ -9,7 +9,7 @@ Bun.serve({
"/api/track/:id": async req => {
const trackId = parseInt(req.params.id)
/*const file = Bun.file(`downloaded/${trackId}.flac`)
const file = Bun.file(`downloaded/${trackId}.flac`)
if (await file.exists()) {
return new Response(await file.arrayBuffer(), {
@ -20,7 +20,7 @@ Bun.serve({
"ETag": trackId.toString(),
}
})
}*/
}
const { manifestMimeType, manifest } = await utils.fetchTrack(trackId)
@ -50,9 +50,7 @@ Bun.serve({
},
development: true
})
/*
const tracks = await utils.fetchTracks();
for await (const track of tracks.items) {
@ -67,15 +65,18 @@ for await (const track of tracks.items) {
}
const trackData = await utils.fetchTrack(parseInt(id))
if (trackData?.status === 404 || trackData === null) {
console.error(`track ${trackId} not available`)
continue
}
try {
const audio = await utils.tagFlac(trackId, await utils.downloadFlac(trackData.manifestMimeType, trackData.manifest))
await Bun.write(`downloaded/${trackId}.flac`, audio)
await utimes(`downloaded/${trackId}.flac`, createdAt, createdAt)
//await Bun.write(`downloaded/${trackId}.flac`, audio)
//await utimes(`downloaded/${trackId}.flac`, createdAt, createdAt)
//console.log(`Downloaded ${trackId}.flac`)
} catch (e) {
console.error(`Failed to download ${trackId}.flac`)
console.error(`Failed to download ${trackId}.flac`, e)
}
}

View file

@ -1,27 +1,27 @@
import "./localStorage";
import { init, initializeDeviceLogin, finalizeDeviceLogin, credentialsProvider } from "@tidal-music/auth";
import { init, initializeLogin, finalizeLogin, credentialsProvider } from "@tidal-music/auth";
const clientId = "zU4XHVVkc2tDPo4t";
const clientSecret = "VJKhDFqJPqvsPVNBV6ukXTJmwlvbttP7wlMlrc72se4=";
const clientId = "mhPVJJEBNRzVjr2p";
export default async () => {
await init({
clientId,
clientSecret,
credentialsStorageKey: "tidal-credentials",
scopes: ["r_usr", "w_usr", "w_sub"],
scopes: ["r_usr", "w_usr"],
})
const credentials = await credentialsProvider.getCredentials();
if (typeof credentials.userId !== "string" || (credentials.expires || 0) < Date.now()) {
const response = await initializeDeviceLogin();
const response = await initializeLogin({
redirectUri: "https://desktop.tidal.com/login/auth"
});
console.log(`Please open https://${response.verificationUriComplete} to login.`)
console.log(`Please open ${response} to login.`)
await finalizeDeviceLogin();
await finalizeLogin(new URL(prompt("Enter the URL you were redirected to: ") || "").search)
}
return {

View file

@ -14,9 +14,9 @@ export default class {
this.authHeaders = authHeaders
}
async convertAacToFlac(buffer: ArrayBuffer) {
async convertAacToFlac(buffer: Buffer<ArrayBuffer> | ArrayBuffer) {
const inputStream = new PassThrough();
inputStream.end(Buffer.from(buffer));
inputStream.end(Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer));
const outputChunks: Buffer[] = [];
@ -52,10 +52,12 @@ export default class {
}
async fetchTrack(id: number) {
const audio = await fetch(`https://api.tidal.com/v1/tracks/${id}/playbackinfopostpaywall/v4?audioquality=HI_RES_LOSSLESS&playbackmode=STREAM&assetpresentation=FULL`, {
const audio = await fetch(`https://desktop.tidal.com/v1/tracks/${id}/playbackinfo?audioquality=HI_RES_LOSSLESS&playbackmode=STREAM&assetpresentation=FULL`, {
headers: this.authHeaders
})
console.log(await audio.text())
return await audio.json() as {
trackId: number,
audioPresentation: string,
@ -70,6 +72,7 @@ export default class {
trackPeakAmplitude: number,
bitDepth: number,
sampleRate: number,
status?: number,
}
}
@ -82,7 +85,6 @@ export default class {
}
async downloadFlac(manifestMimeType: string, manifest: string) {
const id = Bun.nanoseconds().toString(36);
if (manifestMimeType === "application/dash+xml") {
const mpd = new DashMPD();
mpd.parse(Buffer.from(manifest, "base64").toString("utf-8"))
@ -124,11 +126,11 @@ export default class {
return { buffer: new ArrayBuffer(0), mimeType: "" } // TODO: Handle other mime types
}
async tagFlac(id: number, audioBuffer: { buffer: ArrayBuffer, mimeType: string }) {
async tagFlac(id: number, audioBuffer: { buffer: Buffer<ArrayBuffer> | ArrayBuffer, mimeType: string }) {
const fileId = Bun.nanoseconds().toString(36);
if (audioBuffer.mimeType === "audio/mp4") {
audioBuffer.buffer = await this.convertAacToFlac(audioBuffer.buffer) as ArrayBuffer;
audioBuffer.buffer = await this.convertAacToFlac(audioBuffer.buffer);
}
const trackReq = await fetch(`https://api.tidal.com/v1/tracks/${id}/?countryCode=US`, {
@ -156,6 +158,7 @@ export default class {
title: string,
cover: string,
},
status?: string,
};
const alubmReq = await fetch(`https://api.tidal.com/v1/albums/${track.album.id}/?countryCode=US`, {
@ -175,7 +178,7 @@ export default class {
],
}
return FlacStreamTagger.fromBuffer(Buffer.from(audioBuffer.buffer), {
return FlacStreamTagger.fromBuffer(Buffer.from(Buffer.isBuffer(audioBuffer.buffer) ? audioBuffer.buffer : Buffer.from(audioBuffer.buffer)), {
tagMap: {
title: track.title,
trackNumber: track.trackNumber.toString(),