Refactor authentication and utility functions for improved handling of audio formats and API responses
This commit is contained in:
parent
d055deec29
commit
5325bbc34a
3 changed files with 27 additions and 23 deletions
17
index.ts
17
index.ts
|
@ -9,7 +9,7 @@ Bun.serve({
|
||||||
"/api/track/:id": async req => {
|
"/api/track/:id": async req => {
|
||||||
const trackId = parseInt(req.params.id)
|
const trackId = parseInt(req.params.id)
|
||||||
|
|
||||||
/*const file = Bun.file(`downloaded/${trackId}.flac`)
|
const file = Bun.file(`downloaded/${trackId}.flac`)
|
||||||
|
|
||||||
if (await file.exists()) {
|
if (await file.exists()) {
|
||||||
return new Response(await file.arrayBuffer(), {
|
return new Response(await file.arrayBuffer(), {
|
||||||
|
@ -20,7 +20,7 @@ Bun.serve({
|
||||||
"ETag": trackId.toString(),
|
"ETag": trackId.toString(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}*/
|
}
|
||||||
|
|
||||||
const { manifestMimeType, manifest } = await utils.fetchTrack(trackId)
|
const { manifestMimeType, manifest } = await utils.fetchTrack(trackId)
|
||||||
|
|
||||||
|
@ -50,9 +50,7 @@ Bun.serve({
|
||||||
},
|
},
|
||||||
development: true
|
development: true
|
||||||
})
|
})
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
const tracks = await utils.fetchTracks();
|
const tracks = await utils.fetchTracks();
|
||||||
|
|
||||||
for await (const track of tracks.items) {
|
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))
|
const trackData = await utils.fetchTrack(parseInt(id))
|
||||||
|
if (trackData?.status === 404 || trackData === null) {
|
||||||
|
console.error(`track ${trackId} not available`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const audio = await utils.tagFlac(trackId, await utils.downloadFlac(trackData.manifestMimeType, trackData.manifest))
|
const audio = await utils.tagFlac(trackId, await utils.downloadFlac(trackData.manifestMimeType, trackData.manifest))
|
||||||
|
|
||||||
await Bun.write(`downloaded/${trackId}.flac`, audio)
|
//await Bun.write(`downloaded/${trackId}.flac`, audio)
|
||||||
await utimes(`downloaded/${trackId}.flac`, createdAt, createdAt)
|
//await utimes(`downloaded/${trackId}.flac`, createdAt, createdAt)
|
||||||
//console.log(`Downloaded ${trackId}.flac`)
|
//console.log(`Downloaded ${trackId}.flac`)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Failed to download ${trackId}.flac`)
|
console.error(`Failed to download ${trackId}.flac`, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
import "./localStorage";
|
import "./localStorage";
|
||||||
|
|
||||||
import { init, initializeDeviceLogin, finalizeDeviceLogin, credentialsProvider } from "@tidal-music/auth";
|
import { init, initializeLogin, finalizeLogin, credentialsProvider } from "@tidal-music/auth";
|
||||||
|
|
||||||
const clientId = "zU4XHVVkc2tDPo4t";
|
const clientId = "mhPVJJEBNRzVjr2p";
|
||||||
const clientSecret = "VJKhDFqJPqvsPVNBV6ukXTJmwlvbttP7wlMlrc72se4=";
|
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
await init({
|
await init({
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
|
||||||
credentialsStorageKey: "tidal-credentials",
|
credentialsStorageKey: "tidal-credentials",
|
||||||
scopes: ["r_usr", "w_usr", "w_sub"],
|
scopes: ["r_usr", "w_usr"],
|
||||||
})
|
})
|
||||||
|
|
||||||
const credentials = await credentialsProvider.getCredentials();
|
const credentials = await credentialsProvider.getCredentials();
|
||||||
|
|
||||||
if (typeof credentials.userId !== "string" || (credentials.expires || 0) < Date.now()) {
|
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 {
|
return {
|
||||||
|
|
|
@ -14,9 +14,9 @@ export default class {
|
||||||
this.authHeaders = authHeaders
|
this.authHeaders = authHeaders
|
||||||
}
|
}
|
||||||
|
|
||||||
async convertAacToFlac(buffer: ArrayBuffer) {
|
async convertAacToFlac(buffer: Buffer<ArrayBuffer> | ArrayBuffer) {
|
||||||
const inputStream = new PassThrough();
|
const inputStream = new PassThrough();
|
||||||
inputStream.end(Buffer.from(buffer));
|
inputStream.end(Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer));
|
||||||
|
|
||||||
const outputChunks: Buffer[] = [];
|
const outputChunks: Buffer[] = [];
|
||||||
|
|
||||||
|
@ -52,10 +52,12 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchTrack(id: number) {
|
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
|
headers: this.authHeaders
|
||||||
})
|
})
|
||||||
|
|
||||||
|
console.log(await audio.text())
|
||||||
|
|
||||||
return await audio.json() as {
|
return await audio.json() as {
|
||||||
trackId: number,
|
trackId: number,
|
||||||
audioPresentation: string,
|
audioPresentation: string,
|
||||||
|
@ -70,6 +72,7 @@ export default class {
|
||||||
trackPeakAmplitude: number,
|
trackPeakAmplitude: number,
|
||||||
bitDepth: number,
|
bitDepth: number,
|
||||||
sampleRate: number,
|
sampleRate: number,
|
||||||
|
status?: number,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +85,6 @@ export default class {
|
||||||
}
|
}
|
||||||
|
|
||||||
async downloadFlac(manifestMimeType: string, manifest: string) {
|
async downloadFlac(manifestMimeType: string, manifest: string) {
|
||||||
const id = Bun.nanoseconds().toString(36);
|
|
||||||
if (manifestMimeType === "application/dash+xml") {
|
if (manifestMimeType === "application/dash+xml") {
|
||||||
const mpd = new DashMPD();
|
const mpd = new DashMPD();
|
||||||
mpd.parse(Buffer.from(manifest, "base64").toString("utf-8"))
|
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
|
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);
|
const fileId = Bun.nanoseconds().toString(36);
|
||||||
|
|
||||||
if (audioBuffer.mimeType === "audio/mp4") {
|
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`, {
|
const trackReq = await fetch(`https://api.tidal.com/v1/tracks/${id}/?countryCode=US`, {
|
||||||
|
@ -156,6 +158,7 @@ export default class {
|
||||||
title: string,
|
title: string,
|
||||||
cover: string,
|
cover: string,
|
||||||
},
|
},
|
||||||
|
status?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
const alubmReq = await fetch(`https://api.tidal.com/v1/albums/${track.album.id}/?countryCode=US`, {
|
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: {
|
tagMap: {
|
||||||
title: track.title,
|
title: track.title,
|
||||||
trackNumber: track.trackNumber.toString(),
|
trackNumber: track.trackNumber.toString(),
|
||||||
|
|
Loading…
Add table
Reference in a new issue