UNPKG

6.94 kBJavaScriptView Raw
1const iconv = require('iconv-lite')
2const ID3Definitions = require('./ID3Definitions')
3
4const ENCODINGS = [
5 'ISO-8859-1', 'UTF-16', 'UTF-16BE', 'UTF-8'
6]
7
8module.exports.SplitBuffer = class SplitBuffer {
9 constructor(value = null, remainder = null) {
10 this.value = value
11 this.remainder = remainder
12 }
13}
14
15module.exports.splitNullTerminatedBuffer = function(buffer, encodingByte = 0x00) {
16 let termination = { start: -1, size: 0 }
17 if(encodingByte === 0x01 || encodingByte === 0x02) {
18 termination.start = buffer.indexOf(Buffer.from([0x00, 0x00]))
19 termination.size = 2
20 if(termination.start !== -1 && buffer.length > (termination.start + termination.size)) {
21 if(buffer[termination.start + termination.size] === 0x00) {
22 termination.start += 1
23 }
24 }
25 } else {
26 termination.start = buffer.indexOf(0x00)
27 termination.size = 1
28 }
29
30 if(termination.start === -1) {
31 return new this.SplitBuffer(null, buffer.slice(0))
32 }
33 else if(buffer.length <= termination.start + termination.length) {
34 return new this.SplitBuffer(buffer.slice(0, termination.start), null)
35 } else {
36 return new this.SplitBuffer(buffer.slice(0, termination.start), buffer.slice(termination.start + termination.size))
37 }
38}
39
40module.exports.terminationBuffer = function(encodingByte = 0x00) {
41 if(encodingByte === 0x01 || encodingByte === 0x02) {
42 return Buffer.alloc(2, 0x00)
43 } else {
44 return Buffer.alloc(1, 0x00)
45 }
46}
47
48module.exports.encodingFromStringOrByte = function(encoding) {
49 if(ENCODINGS.indexOf(encoding) !== -1) {
50 return encoding
51 } else if(encoding > -1 && encoding < ENCODINGS.length) {
52 encoding = ENCODINGS[encoding]
53 } else {
54 encoding = ENCODINGS[0]
55 }
56 return encoding
57}
58
59module.exports.stringToEncodedBuffer = function(str, encodingByte) {
60 return iconv.encode(str, this.encodingFromStringOrByte(encodingByte))
61}
62
63module.exports.bufferToDecodedString = function(buffer, encodingByte) {
64 return iconv.decode(buffer, this.encodingFromStringOrByte(encodingByte)).replace(/\0/g, '')
65}
66
67module.exports.getSpecOptions = function(specName, version) {
68 if(version === 2) {
69 if(ID3Definitions.ID3_FRAME_OPTIONS.v2[specName] && ID3Definitions.ID3_FRAME_OPTIONS.v2[specName]) {
70 return ID3Definitions.ID3_FRAME_OPTIONS.v2[specName]
71 }
72 } else if (version === 3 || version === 4) {
73 if(ID3Definitions.ID3_FRAME_OPTIONS.v3[specName] && ID3Definitions.ID3_FRAME_OPTIONS.v3[specName]) {
74 return ID3Definitions.ID3_FRAME_OPTIONS.v3[specName]
75 }
76 }
77 return {}
78}
79
80module.exports.isValidID3Header = function(buffer) {
81 if(buffer.length < 10) {
82 return false;
83 } else if(buffer.readUIntBE(0, 3) !== 0x494433) {
84 return false;
85 } else if([0x02, 0x03, 0x04].indexOf(buffer[3]) === -1 || buffer[4] !== 0x00) {
86 return false;
87 } else if(buffer[6] & 128 === 1 || buffer[7] & 128 === 1 || buffer[8] & 128 === 1 || buffer[9] & 128 === 1) {
88 return false;
89 }
90 return true;
91};
92
93module.exports.getFramePosition = function(buffer) {
94 /* Search Buffer for valid ID3 frame */
95 let framePosition = -1;
96 let frameHeaderValid = false;
97 do {
98 framePosition = buffer.indexOf("ID3", framePosition + 1);
99 if(framePosition !== -1) {
100 /* It's possible that there is a "ID3" sequence without being an ID3 Frame,
101 * so we need to check for validity of the next 10 bytes
102 */
103 frameHeaderValid = this.isValidID3Header(buffer.slice(framePosition, framePosition + 10));
104 }
105 } while (framePosition !== -1 && !frameHeaderValid);
106
107 if(!frameHeaderValid) {
108 return -1;
109 } else {
110 return framePosition;
111 }
112}
113
114/**
115 * @return {Buffer}
116 */
117module.exports.encodeSize = function(totalSize) {
118 let byte_3 = totalSize & 0x7F;
119 let byte_2 = (totalSize >> 7) & 0x7F;
120 let byte_1 = (totalSize >> 14) & 0x7F;
121 let byte_0 = (totalSize >> 21) & 0x7F;
122 return Buffer.from([byte_0, byte_1, byte_2, byte_3]);
123};
124
125/**
126 * @return {Buffer}
127 */
128module.exports.decodeSize = function(hSize) {
129 return (hSize[0] << 21) + (hSize[1] << 14) + (hSize[2] << 7) + hSize[3];
130};
131
132module.exports.getFrameSize = function(buffer, decode, ID3Version) {
133 let decodeBytes
134 if(ID3Version > 2) {
135 decodeBytes = [buffer[4], buffer[5], buffer[6], buffer[7]]
136 } else {
137 decodeBytes = [buffer[3], buffer[4], buffer[5]]
138 }
139 if(decode) {
140 return this.decodeSize(Buffer.from(decodeBytes))
141 } else {
142 return Buffer.from(decodeBytes).readUIntBE(0, decodeBytes.length)
143 }
144}
145
146module.exports.parseTagHeaderFlags = function(header) {
147 if(!(header instanceof Buffer && header.length >= 10)) {
148 return {}
149 }
150 const version = header[3]
151 const flagsByte = header[5]
152 if(version === 3) {
153 return {
154 unsynchronisation: !!(flagsByte & 128),
155 extendedHeader: !!(flagsByte & 64),
156 experimentalIndicator: !!(flagsByte & 32)
157 }
158 }
159 if(version === 4) {
160 return {
161 unsynchronisation: !!(flagsByte & 128),
162 extendedHeader: !!(flagsByte & 64),
163 experimentalIndicator: !!(flagsByte & 32),
164 footerPresent: !!(flagsByte & 16)
165 }
166 }
167 return {}
168}
169
170module.exports.parseFrameHeaderFlags = function(header, ID3Version) {
171 if(!(header instanceof Buffer && header.length === 10)) {
172 return {}
173 }
174 const flagsFirstByte = header[8]
175 const flagsSecondByte = header[9]
176 if(ID3Version === 3) {
177 return {
178 tagAlterPreservation: !!(flagsFirstByte & 128),
179 fileAlterPreservation: !!(flagsFirstByte & 64),
180 readOnly: !!(flagsFirstByte & 32),
181 compression: !!(flagsSecondByte & 128),
182 encryption: !!(flagsSecondByte & 64),
183 groupingIdentity: !!(flagsSecondByte & 32)
184 }
185 }
186 if(ID3Version === 4) {
187 return {
188 tagAlterPreservation: !!(flagsFirstByte & 64),
189 fileAlterPreservation: !!(flagsFirstByte & 32),
190 readOnly: !!(flagsFirstByte & 16),
191 groupingIdentity: !!(flagsSecondByte & 64),
192 compression: !!(flagsSecondByte & 8),
193 encryption: !!(flagsSecondByte & 4),
194 unsynchronisation: !!(flagsSecondByte & 2),
195 dataLengthIndicator: !!(flagsSecondByte & 1)
196 }
197 }
198 return {}
199}
200
201module.exports.processUnsynchronisedBuffer = function(buffer) {
202 const bufStr = buffer.toString('hex')
203 return Buffer.from(bufStr.replace(/ff00/g, 'ff'), 'hex')
204}