Passed
Push — master ( 62bfad...2b6a7f )
by Jan
03:19
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
    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
    /* 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
 */
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
 * @return {Buffer}
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 View Code Duplication
    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 View Code Duplication
    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 newDataArr = []
203
    if(buffer.length > 0) {
204
        newDataArr.push(buffer[0])
205
    }
206
    for(let i = 1; i < buffer.length; i++) {
207
        if(buffer[i - 1] === 0xFF && buffer[i] === 0x00)
208
            continue
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
209
        newDataArr.push(buffer[i])
210
    }
211
    return Buffer.from(newDataArr)
212
}
213