1import { parseBlob, parseFromTokenizer, parseWebStream } from "music-metadata";
2import * as URI from "uri-js";
3import { HttpClient } from "@tokenizer/http";
4import { tokenizer as rangeTokenizer } from "@tokenizer/range";
5
6/**
7 * @import { TrackStats, TrackTags } from "@definitions/types.d.ts";
8 * @import { Extraction, Urls } from "./types.d.ts";
9 */
10
11// 🛠️
12
13/**
14 * @param {{ includeArtwork?: boolean; mimeType?: string; stream?: ReadableStream; urls?: Urls; }} _
15 * @returns {Promise<Extraction>}
16 */
17export async function musicMetadataTags({
18 includeArtwork,
19 mimeType,
20 stream,
21 urls,
22}) {
23 const uri = urls ? URI.parse(urls.get) : undefined;
24 const pathParts = uri?.path?.split("/");
25 const filename = pathParts?.[pathParts.length - 1];
26
27 let meta;
28
29 if (urls?.get.startsWith("blob:")) {
30 const blob = await fetch(urls.get).then((r) => r.blob());
31 meta = await parseBlob(blob, { skipCovers: !includeArtwork });
32 } else if (urls) {
33 const httpClient = new HttpClient(urls.head, {
34 resolveUrl: false,
35 });
36 httpClient.resolvedUrl = urls.get;
37 const getHeadInfo = httpClient.getHeadInfo;
38
39 // FUCKAROUND: Not sure of the downsides of this
40 httpClient.getHeadInfo = async () => {
41 const info = await getHeadInfo.call(httpClient);
42 return { ...info, acceptPartialRequests: true };
43 };
44
45 /** @type {any} */
46 const tokenizer = await rangeTokenizer(httpClient);
47 meta = await parseFromTokenizer(tokenizer, { skipCovers: !includeArtwork });
48 } else if (stream) {
49 meta = await parseWebStream(stream, { mimeType }, {
50 skipCovers: !includeArtwork,
51 });
52 } else {
53 throw new Error("Missing args, need either some urls or a stream.");
54 }
55
56 /** @type {TrackStats} */
57 const stats = {
58 albumGain: meta.format.albumGain,
59 bitrate: meta.format.bitrate,
60 bitsPerSample: meta.format.bitsPerSample,
61 codec: meta.format.codec,
62 container: meta.format.container,
63 duration: meta.format.duration,
64 lossless: meta.format.lossless,
65 numberOfChannels: meta.format.numberOfChannels,
66 sampleRate: meta.format.sampleRate,
67 trackGain: meta.format.trackGain,
68 };
69
70 /** @type {TrackTags} */
71 const tags = {
72 album: meta.common.album,
73 albumartist: meta.common.albumartist,
74 albumartists: Array.isArray(meta.common.albumartist)
75 ? meta.common.albumartist
76 : (meta.common.albumartist ? [meta.common.albumartist] : undefined),
77 albumartistsort: meta.common.albumartistsort,
78 albumsort: meta.common.albumsort,
79 arranger: meta.common.arranger,
80 artist: meta.common.artist,
81 artists: meta.common.artists ??
82 (meta.common.artist ? [meta.common.artist] : []),
83 artistsort: meta.common.artistsort,
84 asin: meta.common.asin,
85 averageLevel: meta.common.averageLevel,
86 barcode: meta.common.barcode,
87 bpm: meta.common.bpm,
88 catalognumbers: meta.common.catalognumber,
89 compilation: meta.common.compilation,
90 composers: meta.common.composer,
91 composersort: meta.common.composersort,
92 conductors: meta.common.conductor,
93 date: meta.common.date,
94 disc: {
95 no: meta.common.disk.no || 1,
96 of: meta.common.disk.of ?? undefined,
97 },
98 djmixers: meta.common.djmixer,
99 engineers: meta.common.engineer,
100 gapless: meta.common.gapless,
101 genres: Array.isArray(meta.common.genre)
102 ? meta.common.genre
103 : meta.common.genre
104 ? [meta.common.genre]
105 : undefined,
106 isrc: meta.common.isrc,
107 labels: meta.common.label,
108 lyricists: meta.common.lyricist,
109 media: meta.common.media,
110 mixers: meta.common.mixer,
111 moods: Array.isArray(meta.common.mood)
112 ? meta.common.mood
113 : meta.common.mood
114 ? [meta.common.mood]
115 : undefined,
116 originaldate: meta.common.originaldate,
117 originalyear: meta.common.originalyear,
118 peakLevel: meta.common.peakLevel,
119 producers: meta.common.producer,
120 publishers: meta.common.publisher,
121 releasecountry: meta.common.releasecountry,
122 releasedate: meta.common.releasedate,
123 releasestatus: meta.common.releasestatus,
124 releasetypes: meta.common.releasetype,
125 remixers: meta.common.remixer,
126 technicians: meta.common.technician,
127 title: meta.common.title || filename || urls?.head || "Unknown",
128 titlesort: meta.common.titlesort,
129 track: {
130 no: meta.common.track.no || 1,
131 of: meta.common.track.of ?? undefined,
132 },
133 work: meta.common.work,
134 writers: meta.common.writer,
135 year: meta.common.year,
136 };
137
138 return {
139 artwork: includeArtwork ? meta.common.picture : undefined,
140 stats,
141 tags,
142 };
143}