1 | const iconv = require('iconv-lite')
|
2 | const ID3Definitions = require('./ID3Definitions')
|
3 |
|
4 | const ENCODINGS = [
|
5 | 'ISO-8859-1', 'UTF-16', 'UTF-16BE', 'UTF-8'
|
6 | ]
|
7 |
|
8 | module.exports.SplitBuffer = class SplitBuffer {
|
9 | constructor(value = null, remainder = null) {
|
10 | this.value = value
|
11 | this.remainder = remainder
|
12 | }
|
13 | }
|
14 |
|
15 | module.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 |
|
40 | module.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 |
|
48 | module.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 |
|
59 | module.exports.stringToEncodedBuffer = function(str, encodingByte) {
|
60 | return iconv.encode(str, this.encodingFromStringOrByte(encodingByte))
|
61 | }
|
62 |
|
63 | module.exports.bufferToDecodedString = function(buffer, encodingByte) {
|
64 | return iconv.decode(buffer, this.encodingFromStringOrByte(encodingByte)).replace(/\0/g, '')
|
65 | }
|
66 |
|
67 | module.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 |
|
80 | module.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 |
|
93 | module.exports.getFramePosition = function(buffer) {
|
94 |
|
95 | let framePosition = -1;
|
96 | let frameHeaderValid = false;
|
97 | do {
|
98 | framePosition = buffer.indexOf("ID3", framePosition + 1);
|
99 | if(framePosition !== -1) {
|
100 | |
101 |
|
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 |
|
116 |
|
117 | module.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 |
|
127 |
|
128 | module.exports.decodeSize = function(hSize) {
|
129 | return (hSize[0] << 21) + (hSize[1] << 14) + (hSize[2] << 7) + hSize[3];
|
130 | };
|
131 |
|
132 | module.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 |
|
146 | module.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 |
|
170 | module.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 |
|
201 | module.exports.processUnsynchronisedBuffer = function(buffer) {
|
202 | const bufStr = buffer.toString('hex')
|
203 | return Buffer.from(bufStr.replace(/ff00/g, 'ff'), 'hex')
|
204 | }
|