Passed
Pull Request — master (#133)
by Jan
01:46
created

src/ID3Util.js (1 issue)

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
    const 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
    if(buffer.length <= termination.start + termination.length) {
34
        return new this.SplitBuffer(buffer.slice(0, termination.start), null)
35
    }
36
    return new this.SplitBuffer(buffer.slice(0, termination.start), buffer.slice(termination.start + termination.size))
37
}
38
39
module.exports.terminationBuffer = function(encodingByte = 0x00) {
40
    if(encodingByte === 0x01 || encodingByte === 0x02) {
41
        return Buffer.alloc(2, 0x00)
42
    }
43
    return Buffer.alloc(1, 0x00)
44
}
45
46
module.exports.encodingFromStringOrByte = function(encoding) {
47
    if(ENCODINGS.indexOf(encoding) !== -1) {
48
        return encoding
49
    } else if(encoding > -1 && encoding < ENCODINGS.length) {
50
        encoding = ENCODINGS[encoding]
51
    } else {
52
        encoding = ENCODINGS[0]
53
    }
54
    return encoding
55
}
56
57
module.exports.stringToEncodedBuffer = function(str, encodingByte) {
58
    return iconv.encode(str, this.encodingFromStringOrByte(encodingByte))
59
}
60
61
module.exports.bufferToDecodedString = function(buffer, encodingByte) {
62
    return iconv.decode(buffer, this.encodingFromStringOrByte(encodingByte)).replace(/\0/g, '')
63
}
64
65
module.exports.getSpecOptions = function(frameIdentifier) {
66
    if(ID3Definitions.ID3_FRAME_OPTIONS[frameIdentifier]) {
67
        return ID3Definitions.ID3_FRAME_OPTIONS[frameIdentifier]
68
    }
69
70
    return {}
71
}
72
73
module.exports.isValidID3Header = function(buffer) {
74
    if(buffer.length < 10) {
75
        return false
76
    }
77
    if(buffer.readUIntBE(0, 3) !== 0x494433) {
78
        return false
79
    }
80
    if([0x02, 0x03, 0x04].indexOf(buffer[3]) === -1 || buffer[4] !== 0x00) {
81
        return false
82
    }
83
    return this.isValidEncodedSize(buffer.slice(6, 10))
84
}
85
86
module.exports.getFramePosition = function(buffer) {
87
    /* Search Buffer for valid ID3 frame */
88
    let framePosition = -1
89
    let frameHeaderValid = false
90
    do {
91
        framePosition = buffer.indexOf("ID3", framePosition + 1)
92
        if(framePosition !== -1) {
93
            /* It's possible that there is a "ID3" sequence without being an ID3 Frame,
94
             * so we need to check for validity of the next 10 bytes
95
             */
96
            frameHeaderValid = this.isValidID3Header(buffer.slice(framePosition, framePosition + 10))
97
        }
98
    } while (framePosition !== -1 && !frameHeaderValid)
99
100
    if(!frameHeaderValid) {
101
        return -1
102
    }
103
    return framePosition
104
}
105
106
/**
107
 * @param {Buffer} encodedSize
108
 * @return {boolean} Return if the header contains a valid encoded size
109
 */
110
module.exports.isValidEncodedSize = function(encodedSize) {
111
    // The size must not have the bit 7 set
112
    return ((
113
        encodedSize[0] |
114
        encodedSize[1] |
115
        encodedSize[2] |
116
        encodedSize[3]
117
    ) & 128) === 0
118
}
119
120
/**
121
 * ID3 header size uses only 7 bits of a byte, bit shift is needed
122
 * @param {number} size
123
 * @return {Buffer} Return a Buffer of 4 bytes with the encoded size
124
 */
125
module.exports.encodeSize = function(size) {
126
    const byte_3 = size & 0x7F
127
    const byte_2 = (size >> 7) & 0x7F
128
    const byte_1 = (size >> 14) & 0x7F
129
    const byte_0 = (size >> 21) & 0x7F
130
    return Buffer.from([byte_0, byte_1, byte_2, byte_3])
131
}
132
133
/**
134
 * Decode the size encoded in the ID3 header
135
 * @param {Buffer} encodedSize
136
 * @return {number} Return the decoded size
137
 */
138
module.exports.decodeSize = function(encodedSize) {
139
    return (
140
        (encodedSize[0] << 21) +
141
        (encodedSize[1] << 14) +
142
        (encodedSize[2] << 7) +
143
        encodedSize[3]
144
    )
145
}
146
147
module.exports.getFrameSize = function(buffer, decode, ID3Version) {
148
    let decodeBytes
149
    if(ID3Version > 2) {
150
        decodeBytes = [buffer[4], buffer[5], buffer[6], buffer[7]]
151
    } else {
152
        decodeBytes = [buffer[3], buffer[4], buffer[5]]
153
    }
154
    if(decode) {
155
        return this.decodeSize(Buffer.from(decodeBytes))
156
    } else {
157
        return Buffer.from(decodeBytes).readUIntBE(0, decodeBytes.length)
158
    }
159
}
160
161
module.exports.parseTagHeaderFlags = function(header) {
162
    if(!(header instanceof Buffer && header.length >= 10)) {
163
        return {}
164
    }
165
    const version = header[3]
166
    const flagsByte = header[5]
167
    if(version === 3) {
168
        return {
169
            unsynchronisation: !!(flagsByte & 128),
170
            extendedHeader: !!(flagsByte & 64),
171
            experimentalIndicator: !!(flagsByte & 32)
172
        }
173
    }
174 View Code Duplication
    if(version === 4) {
175
        return {
176
            unsynchronisation: !!(flagsByte & 128),
177
            extendedHeader: !!(flagsByte & 64),
178
            experimentalIndicator: !!(flagsByte & 32),
179
            footerPresent: !!(flagsByte & 16)
180
        }
181
    }
182
    return {}
183
}
184
185
module.exports.parseFrameHeaderFlags = function(header, ID3Version) {
186
    if(!(header instanceof Buffer && header.length === 10)) {
187
        return {}
188
    }
189
    const flagsFirstByte = header[8]
190
    const flagsSecondByte = header[9]
191 View Code Duplication
    if(ID3Version === 3) {
192
        return {
193
            tagAlterPreservation: !!(flagsFirstByte & 128),
194
            fileAlterPreservation: !!(flagsFirstByte & 64),
195
            readOnly: !!(flagsFirstByte & 32),
196
            compression: !!(flagsSecondByte & 128),
197
            encryption: !!(flagsSecondByte & 64),
198
            groupingIdentity: !!(flagsSecondByte & 32),
199
            dataLengthIndicator: !!(flagsSecondByte & 128)
200
        }
201
    }
202 View Code Duplication
    if(ID3Version === 4) {
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
203
        return {
204
            tagAlterPreservation: !!(flagsFirstByte & 64),
205
            fileAlterPreservation: !!(flagsFirstByte & 32),
206
            readOnly: !!(flagsFirstByte & 16),
207
            groupingIdentity: !!(flagsSecondByte & 64),
208
            compression: !!(flagsSecondByte & 8),
209
            encryption: !!(flagsSecondByte & 4),
210
            unsynchronisation: !!(flagsSecondByte & 2),
211
            dataLengthIndicator: !!(flagsSecondByte & 1)
212
        }
213
    }
214
    return {}
215
}
216
217
module.exports.processUnsynchronisedBuffer = function(buffer) {
218
    const newDataArr = []
219
    if(buffer.length > 0) {
220
        newDataArr.push(buffer[0])
221
    }
222
    for(let i = 1; i < buffer.length; i++) {
223
        if(buffer[i - 1] === 0xFF && buffer[i] === 0x00)
224
            continue
225
        newDataArr.push(buffer[i])
226
    }
227
    return Buffer.from(newDataArr)
228
}
229
230
module.exports.getPictureMimeTypeFromBuffer = function(pictureBuffer) {
231
    if (pictureBuffer.length > 3 && pictureBuffer.compare(Buffer.from([0xff, 0xd8, 0xff]), 0, 3, 0, 3) === 0) {
232
        return "image/jpeg"
233
    } else if (pictureBuffer > 8 && pictureBuffer.compare(Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]), 0, 8, 0, 8) === 0) {
234
        return "image/png"
235
    } else {
236
        return null
237
    }
238
}
239