Completed
Push — master ( ff6124...8caa6f )
by Jan
23s queued 12s
created

src/ID3Util.ts   A

Complexity

Total Complexity 29
Complexity/F 2.42

Size

Lines of Code 170
Function Count 12

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 29
eloc 121
mnd 17
bc 17
fnc 12
dl 0
loc 170
rs 10
bpm 1.4166
cpm 2.4166
noi 0
c 0
b 0
f 0

12 Functions

Rating   Name   Duplication   Size   Complexity  
A ID3Util.ts ➔ isValidEncodedSize 0 9 1
A ID3Util.ts ➔ getTagPosition 0 24 4
A ID3Util.ts ➔ getFrameSize 0 9 3
A ID3Util.ts ➔ getSpecOptions 0 7 2
A ID3Util.ts ➔ stringToEncodedBuffer 0 8 1
A ID3Util.ts ➔ decodeSize 0 10 1
A ID3Util.ts ➔ parseTagHeaderFlags 0 23 4
A ID3Util.ts ➔ bufferToDecodedString 0 9 1
A ID3Util.ts ➔ isValidID3Header 0 12 4
A ID3Util.ts ➔ encodeSize 0 11 1
A ID3Util.ts ➔ processUnsynchronisedBuffer 0 13 4
A ID3Util.ts ➔ encodingFromStringOrByte 0 16 3
1
import iconv = require('iconv-lite')
2
import { FrameOptions, FRAME_OPTIONS } from './definitions/FrameOptions'
3
import { isKeyOf, isString } from './util'
4
5
export function encodingFromStringOrByte(encoding: string | number) {
6
    const ENCODINGS = [
7
        'ISO-8859-1', 'UTF-16', 'UTF-16BE', 'UTF-8'
8
    ]
9
10
    if (isString(encoding) && ENCODINGS.includes(encoding)) {
11
        return encoding
12
    }
13
    if (
14
        typeof encoding === "number" &&
15
        encoding >= 0 && encoding < ENCODINGS.length
16
    ) {
17
        return ENCODINGS[encoding]
18
    }
19
    return ENCODINGS[0]
20
}
21
22
export function stringToEncodedBuffer(
23
    value: string,
24
    encodingByte: string | number
25
) {
26
    return iconv.encode(
27
        value,
28
        encodingFromStringOrByte(encodingByte)
29
    )
30
}
31
32
export function bufferToDecodedString(
33
    buffer: Buffer,
34
    encodingByte: string | number
35
) {
36
    return iconv.decode(
37
        buffer,
38
        encodingFromStringOrByte(encodingByte)
39
    ).replace(/\0/g, '')
40
}
41
42
export function getSpecOptions(frameIdentifier: string): FrameOptions {
43
    if (isKeyOf(frameIdentifier, FRAME_OPTIONS)) {
44
        return FRAME_OPTIONS[frameIdentifier]
45
    }
46
    return {
47
        multiple: false
48
    }
49
}
50
51
export function isValidID3Header(buffer: Buffer) {
52
    if (buffer.length < 10) {
53
        return false
54
    }
55
    if (buffer.readUIntBE(0, 3) !== 0x494433) {
56
        return false
57
    }
58
    if ([0x02, 0x03, 0x04].indexOf(buffer[3]) === -1 || buffer[4] !== 0x00) {
59
        return false
60
    }
61
    return isValidEncodedSize(buffer.subarray(6, 10))
62
}
63
64
/**
65
 * Returns -1 if no tag was found.
66
 */
67
export function getTagPosition(buffer: Buffer) {
68
    // Search Buffer for valid ID3 frame
69
    const tagHeaderSize = 10
70
    let position = -1
71
    let headerValid = false
72
    do {
73
        position = buffer.indexOf("ID3", position + 1)
74
        if (position !== -1) {
75
            // It's possible that there is a "ID3" sequence without being an
76
            // ID3 Frame, so we need to check for validity of the next 10 bytes.
77
            headerValid = isValidID3Header(
78
                buffer.subarray(position, position + tagHeaderSize)
79
            )
80
        }
81
    } while (position !== -1 && !headerValid)
82
83
    if (!headerValid) {
84
        return -1
85
    }
86
    return position
87
}
88
89
 export function isValidEncodedSize(encodedSize: Buffer) {
90
    // The size must not have the bit 7 set
91
    return ((
92
        encodedSize[0] |
93
        encodedSize[1] |
94
        encodedSize[2] |
95
        encodedSize[3]
96
    ) & 128) === 0
97
}
98
99
/**
100
 * ID3 header size uses only 7 bits of a byte, bit shift is needed.
101
 * @returns Return a Buffer of 4 bytes with the encoded size
102
 */
103
 export function encodeSize(size: number) {
104
    const byte_3 = size & 0x7F
105
    const byte_2 = (size >> 7) & 0x7F
106
    const byte_1 = (size >> 14) & 0x7F
107
    const byte_0 = (size >> 21) & 0x7F
108
    return Buffer.from([byte_0, byte_1, byte_2, byte_3])
109
}
110
111
/**
112
 * Decode the encoded size from an ID3 header.
113
 */
114
 export function decodeSize(encodedSize: Buffer) {
115
    return (
116
        (encodedSize[0] << 21) +
117
        (encodedSize[1] << 14) +
118
        (encodedSize[2] << 7) +
119
        encodedSize[3]
120
    )
121
}
122
123
export function getFrameSize(buffer: Buffer, decode: boolean, version: number) {
124
    const decodeBytes = version > 2 ?
125
        [buffer[4], buffer[5], buffer[6], buffer[7]] :
126
        [buffer[3], buffer[4], buffer[5]]
127
    if (decode) {
128
        return decodeSize(Buffer.from(decodeBytes))
129
    }
130
    return Buffer.from(decodeBytes).readUIntBE(0, decodeBytes.length)
131
}
132
133
export function parseTagHeaderFlags(header: Buffer) {
134
    if (!(header instanceof Buffer && header.length >= 10)) {
135
        return {}
136
    }
137
    const version = header[3]
138
    const flagsByte = header[5]
139
    if (version === 3) {
140
        return {
141
            unsynchronisation: !!(flagsByte & 128),
142
            extendedHeader: !!(flagsByte & 64),
143
            experimentalIndicator: !!(flagsByte & 32)
144
        }
145
    }
146
    if (version === 4) {
147
        return {
148
            unsynchronisation: !!(flagsByte & 128),
149
            extendedHeader: !!(flagsByte & 64),
150
            experimentalIndicator: !!(flagsByte & 32),
151
            footerPresent: !!(flagsByte & 16)
152
        }
153
    }
154
    return {}
155
}
156
157
export function processUnsynchronisedBuffer(buffer: Buffer) {
158
    const newDataArr = []
159
    if (buffer.length > 0) {
160
        newDataArr.push(buffer[0])
161
    }
162
    for(let i = 1; i < buffer.length; i++) {
163
        if (buffer[i - 1] === 0xFF && buffer[i] === 0x00) {
164
            continue
165
        }
166
        newDataArr.push(buffer[i])
167
    }
168
    return Buffer.from(newDataArr)
169
}
170