1 | const fs = require('fs')
|
2 | const ID3FrameBuilder = require("./ID3FrameBuilder")
|
3 | const ID3FrameReader = require("./ID3FrameReader")
|
4 | const ID3Definitions = require("./ID3Definitions")
|
5 |
|
6 | module.exports.GENERIC_TEXT = {
|
7 | create: (specName, data) => {
|
8 | if(!specName || !data) {
|
9 | return null
|
10 | }
|
11 |
|
12 | return new ID3FrameBuilder(specName)
|
13 | .appendStaticNumber(0x01, 0x01)
|
14 | .appendStaticValue(data, null, 0x01)
|
15 | .getBuffer()
|
16 | },
|
17 | read: (buffer) => {
|
18 | const reader = new ID3FrameReader(buffer, 0)
|
19 |
|
20 | return reader.consumeStaticValue('string')
|
21 | }
|
22 | }
|
23 |
|
24 | module.exports.GENERIC_URL = {
|
25 | create: (specName, data) => {
|
26 | if(!specName || !data) {
|
27 | return null
|
28 | }
|
29 |
|
30 | return new ID3FrameBuilder(specName)
|
31 | .appendStaticValue(data)
|
32 | .getBuffer()
|
33 | },
|
34 | read: (buffer) => {
|
35 | const reader = new ID3FrameReader(buffer)
|
36 |
|
37 | return reader.consumeStaticValue('string')
|
38 | }
|
39 | }
|
40 |
|
41 | module.exports.APIC = {
|
42 | create: (data) => {
|
43 | try {
|
44 | if (data instanceof Buffer) {
|
45 | data = {
|
46 | imageBuffer: Buffer.from(data)
|
47 | }
|
48 | } else if (typeof data === 'string' || data instanceof String) {
|
49 | data = {
|
50 | imageBuffer: Buffer.from(fs.readFileSync(data, 'binary'), 'binary')
|
51 | }
|
52 | } else if (!data.imageBuffer) {
|
53 | return Buffer.alloc(0)
|
54 | }
|
55 |
|
56 | let mime_type = data.mime
|
57 |
|
58 | if(!data.mime) {
|
59 | if (data.imageBuffer[0] === 0xff && data.imageBuffer[1] === 0xd8 && data.imageBuffer[2] === 0xff) {
|
60 | mime_type = "image/jpeg"
|
61 | } else {
|
62 | mime_type = "image/png"
|
63 | }
|
64 | }
|
65 |
|
66 | return new ID3FrameBuilder("APIC")
|
67 | .appendStaticNumber(0x01, 1)
|
68 | .appendNullTerminatedValue(mime_type)
|
69 | .appendStaticNumber(0x03, 1)
|
70 | .appendNullTerminatedValue(data.description, 0x01)
|
71 | .appendStaticValue(data.imageBuffer)
|
72 | .getBuffer()
|
73 | } catch(e) {
|
74 | return e
|
75 | }
|
76 | },
|
77 | read: (buffer, version) => {
|
78 | const reader = new ID3FrameReader(buffer, 0)
|
79 | let mime
|
80 | if(version === 2) {
|
81 | mime = reader.consumeStaticValue('string', 3, 0x00)
|
82 | } else {
|
83 | mime = reader.consumeNullTerminatedValue('string', 0x00)
|
84 | }
|
85 | if(mime === "image/jpeg") {
|
86 | mime = "jpeg"
|
87 | } else if(mime === "image/png") {
|
88 | mime = "png"
|
89 | }
|
90 |
|
91 | const typeId = reader.consumeStaticValue('number', 1)
|
92 | const description = reader.consumeNullTerminatedValue('string')
|
93 | const imageBuffer = reader.consumeStaticValue()
|
94 |
|
95 | return {
|
96 | mime: mime,
|
97 | type: {
|
98 | id: typeId,
|
99 | name: ID3Definitions.APIC_TYPES[typeId]
|
100 | },
|
101 | description: description,
|
102 | imageBuffer: imageBuffer
|
103 | }
|
104 | }
|
105 | }
|
106 |
|
107 | module.exports.COMM = {
|
108 | create: (data) => {
|
109 | data = data || {}
|
110 | if(!data.text) {
|
111 | return null
|
112 | }
|
113 |
|
114 | return new ID3FrameBuilder("COMM")
|
115 | .appendStaticNumber(0x01, 1)
|
116 | .appendStaticValue(data.language)
|
117 | .appendNullTerminatedValue(data.shortText, 0x01)
|
118 | .appendStaticValue(data.text, null, 0x01)
|
119 | .getBuffer()
|
120 | },
|
121 | read: (buffer) => {
|
122 | const reader = new ID3FrameReader(buffer, 0)
|
123 |
|
124 | return {
|
125 | language: reader.consumeStaticValue('string', 3, 0x00),
|
126 | shortText: reader.consumeNullTerminatedValue('string'),
|
127 | text: reader.consumeStaticValue('string', null)
|
128 | }
|
129 | }
|
130 | }
|
131 |
|
132 | module.exports.USLT = {
|
133 | create: (data) => {
|
134 | data = data || {}
|
135 | if(typeof data === 'string' || data instanceof String) {
|
136 | data = {
|
137 | text: data
|
138 | }
|
139 | }
|
140 | if(!data.text) {
|
141 | return null
|
142 | }
|
143 |
|
144 | return new ID3FrameBuilder("USLT")
|
145 | .appendStaticNumber(0x01, 1)
|
146 | .appendStaticValue(data.language)
|
147 | .appendNullTerminatedValue(data.shortText, 0x01)
|
148 | .appendStaticValue(data.text, null, 0x01)
|
149 | .getBuffer()
|
150 | },
|
151 | read: (buffer) => {
|
152 | const reader = new ID3FrameReader(buffer, 0)
|
153 |
|
154 | return {
|
155 | language: reader.consumeStaticValue('string', 3, 0x00),
|
156 | shortText: reader.consumeNullTerminatedValue('string'),
|
157 | text: reader.consumeStaticValue('string', null)
|
158 | }
|
159 | }
|
160 | }
|
161 |
|
162 | module.exports.TXXX = {
|
163 | create: (data) => {
|
164 | if(!(data instanceof Array)) {
|
165 | data = [data]
|
166 | }
|
167 |
|
168 | return Buffer.concat(data.map(udt => new ID3FrameBuilder("TXXX")
|
169 | .appendStaticNumber(0x01, 1)
|
170 | .appendNullTerminatedValue(udt.description, 0x01)
|
171 | .appendStaticValue(udt.value, null, 0x01)
|
172 | .getBuffer()))
|
173 | },
|
174 | read: (buffer) => {
|
175 | const reader = new ID3FrameReader(buffer, 0)
|
176 |
|
177 | return {
|
178 | description: reader.consumeNullTerminatedValue('string'),
|
179 | value: reader.consumeStaticValue('string')
|
180 | }
|
181 | }
|
182 | }
|
183 |
|
184 | module.exports.POPM = {
|
185 | create: (data) => {
|
186 | const email = data.email
|
187 | let rating = Math.trunc(data.rating)
|
188 | let counter = Math.trunc(data.counter)
|
189 | if(!email) {
|
190 | return null
|
191 | }
|
192 | if(isNaN(rating) || rating < 0 || rating > 255) {
|
193 | rating = 0
|
194 | }
|
195 | if(isNaN(counter) || counter < 0) {
|
196 | counter = 0
|
197 | }
|
198 |
|
199 | return new ID3FrameBuilder("POPM")
|
200 | .appendNullTerminatedValue(email)
|
201 | .appendStaticNumber(rating, 1)
|
202 | .appendStaticNumber(counter, 4)
|
203 | .getBuffer()
|
204 | },
|
205 | read: (buffer) => {
|
206 | const reader = new ID3FrameReader(buffer)
|
207 | return {
|
208 | email: reader.consumeNullTerminatedValue('string'),
|
209 | rating: reader.consumeStaticValue('number', 1),
|
210 | counter: reader.consumeStaticValue('number')
|
211 | }
|
212 | }
|
213 | }
|
214 |
|
215 | module.exports.PRIV = {
|
216 | create: (data) => {
|
217 | if(!(data instanceof Array)) {
|
218 | data = [data]
|
219 | }
|
220 |
|
221 | return Buffer.concat(data.map(priv => new ID3FrameBuilder("PRIV")
|
222 | .appendNullTerminatedValue(priv.ownerIdentifier)
|
223 | .appendStaticValue(priv.data instanceof Buffer ? priv.data : Buffer.from(priv.data, "utf8"))
|
224 | .getBuffer()))
|
225 | },
|
226 | read: (buffer) => {
|
227 | const reader = new ID3FrameReader(buffer)
|
228 | return {
|
229 | ownerIdentifier: reader.consumeNullTerminatedValue('string'),
|
230 | data: reader.consumeStaticValue()
|
231 | }
|
232 | }
|
233 | }
|
234 |
|
235 | module.exports.CHAP = {
|
236 | create: (data, version, nodeId3) => {
|
237 | if (!(data instanceof Array)) {
|
238 | data = [data]
|
239 | }
|
240 |
|
241 | return Buffer.concat(data.map(chap => {
|
242 | if (!chap || !chap.elementID || typeof chap.startTimeMs === "undefined" || !chap.endTimeMs) {
|
243 | return null
|
244 | }
|
245 | return new ID3FrameBuilder("CHAP")
|
246 | .appendNullTerminatedValue(chap.elementID)
|
247 | .appendStaticNumber(chap.startTimeMs, 4)
|
248 | .appendStaticNumber(chap.endTimeMs, 4)
|
249 | .appendStaticNumber(chap.startOffsetBytes ? chap.startOffsetBytes : 0xFFFFFFFF, 4)
|
250 | .appendStaticNumber(chap.endOffsetBytes ? chap.endOffsetBytes : 0xFFFFFFFF, 4)
|
251 | .appendStaticValue(nodeId3.create(chap.tags).slice(10))
|
252 | .getBuffer()
|
253 | }).filter(chap => chap instanceof Buffer))
|
254 | },
|
255 | read: (buffer, version, nodeId3) => {
|
256 | const reader = new ID3FrameReader(buffer)
|
257 | let chap = {
|
258 | elementID: reader.consumeNullTerminatedValue('string'),
|
259 | startTimeMs: reader.consumeStaticValue('number', 4),
|
260 | endTimeMs: reader.consumeStaticValue('number', 4),
|
261 | startOffsetBytes: reader.consumeStaticValue('number', 4),
|
262 | endOffsetBytes: reader.consumeStaticValue('number', 4),
|
263 | tags: nodeId3.getTagsFromFrames(nodeId3.getFramesFromID3Body(reader.consumeStaticValue(), 3, 4, 10), 3)
|
264 | }
|
265 | if(chap.startOffsetBytes === 0xFFFFFFFF) delete chap.startOffsetBytes
|
266 | if(chap.endOffsetBytes === 0xFFFFFFFF) delete chap.endOffsetBytes
|
267 | return chap
|
268 | }
|
269 | }
|
270 |
|
271 | module.exports.CTOC = {
|
272 | create: (data, version, nodeId3) => {
|
273 | if(!(data instanceof Array)) {
|
274 | data = [data]
|
275 | }
|
276 |
|
277 | return Buffer.concat(data.map((toc, index) => {
|
278 | if(!toc || !toc.elementID) {
|
279 | return null
|
280 | }
|
281 | if(!(toc.elements instanceof Array)) {
|
282 | toc.elements = []
|
283 | }
|
284 |
|
285 | let ctocFlags = Buffer.alloc(1, 0)
|
286 | if(index === 0) {
|
287 | ctocFlags[0] += 2
|
288 | }
|
289 | if(toc.isOrdered) {
|
290 | ctocFlags[0] += 1
|
291 | }
|
292 |
|
293 | const builder = new ID3FrameBuilder("CTOC")
|
294 | .appendNullTerminatedValue(toc.elementID)
|
295 | .appendStaticValue(ctocFlags, 1)
|
296 | .appendStaticNumber(toc.elements.length, 1)
|
297 | toc.elements.forEach((el) => {
|
298 | builder.appendNullTerminatedValue(el)
|
299 | })
|
300 | if(toc.tags) {
|
301 | builder.appendStaticValue(nodeId3.create(toc.tags).slice(10))
|
302 | }
|
303 | return builder.getBuffer()
|
304 | }).filter((toc) => toc instanceof Buffer))
|
305 | },
|
306 | read: (buffer, version, nodeId3) => {
|
307 | const reader = new ID3FrameReader(buffer)
|
308 | const elementID = reader.consumeNullTerminatedValue('string')
|
309 | const flags = reader.consumeStaticValue('number', 1)
|
310 | const entries = reader.consumeStaticValue('number', 1)
|
311 | const elements = []
|
312 | for(let i = 0; i < entries; i++) {
|
313 | elements.push(reader.consumeNullTerminatedValue('string'))
|
314 | }
|
315 | const tags = nodeId3.getTagsFromFrames(nodeId3.getFramesFromID3Body(reader.consumeStaticValue(), 3, 4, 10), 3)
|
316 |
|
317 | return {
|
318 | elementID,
|
319 | isOrdered: !!(flags & 0x01 === 0x01),
|
320 | elements,
|
321 | tags
|
322 | }
|
323 | }
|
324 | }
|
325 |
|
326 | module.exports.WXXX = {
|
327 | create: (data) => {
|
328 | if(!(data instanceof Array)) {
|
329 | data = [data]
|
330 | }
|
331 |
|
332 | return Buffer.concat(data.map((udu) => {
|
333 | return new ID3FrameBuilder("WXXX")
|
334 | .appendStaticNumber(0x01, 1)
|
335 | .appendNullTerminatedValue(udu.description, 0x01)
|
336 | .appendStaticValue(udu.url, null)
|
337 | .getBuffer()
|
338 | }))
|
339 | },
|
340 | read: (buffer) => {
|
341 | const reader = new ID3FrameReader(buffer, 0)
|
342 |
|
343 | return {
|
344 | description: reader.consumeNullTerminatedValue('string'),
|
345 | url: reader.consumeStaticValue('string', null, 0x00)
|
346 | }
|
347 | }
|
348 | }
|