Passed
Pull Request — master (#160)
by
unknown
01:53
created

src/id3-tag.ts   A

Complexity

Total Complexity 23
Complexity/F 3.29

Size

Lines of Code 148
Function Count 7

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 23
eloc 108
mnd 16
bc 16
fnc 7
dl 0
loc 148
rs 10
bpm 2.2857
cpm 3.2857
noi 0
c 0
b 0
f 0

7 Functions

Rating   Name   Duplication   Size   Complexity  
A id3-tag.ts ➔ isValidId3Header 0 12 4
A id3-tag.ts ➔ parseTagHeaderFlags 0 23 4
A id3-tag.ts ➔ isValidEncodedSize 0 9 1
B id3-tag.ts ➔ getId3TagBody 0 32 5
A id3-tag.ts ➔ removeId3Tag 0 28 4
A id3-tag.ts ➔ getId3TagPosition 0 24 4
A id3-tag.ts ➔ createId3Tag 0 10 1
1
import { decodeSize, encodeSize } from "./util-size"
2
3
export function createId3Tag(frames: Buffer) {
4
    const header = Buffer.alloc(10)
5
    header.fill(0)
6
    header.write("ID3", 0)              // File identifier
7
    header.writeUInt16BE(0x0300, 3)     // Version 2.3.0  --  03 00
8
    header.writeUInt16BE(0x0000, 5)     // Flags 00
9
    encodeSize(frames.length).copy(header, 6)
10
11
    return Buffer.concat([header, frames])
12
}
13
14
/**
15
 * Remove already written ID3-Frames from a buffer
16
 */
17
export function removeId3Tag(data: Buffer) {
18
    const tagPosition = getId3TagPosition(data)
19
    if (tagPosition === -1) {
20
        return data
21
    }
22
23
    const tagHeaderSize = 10
24
    const encodedSize = data.subarray(
25
        tagPosition + 6,
26
        tagPosition + tagHeaderSize
27
    )
28
    if (!isValidEncodedSize(encodedSize)) {
29
        return false
30
    }
31
32
    if (data.length >= tagPosition + tagHeaderSize) {
33
        const size = decodeSize(encodedSize)
34
        return Buffer.concat([
35
            data.subarray(0, tagPosition),
36
            data.subarray(tagPosition + size + tagHeaderSize)
37
        ])
38
    }
39
40
    return data
41
}
42
43
export function getId3TagBody(buffer: Buffer) {
44
    const tagPosition = getId3TagPosition(buffer)
45
    if (tagPosition === -1) {
46
        return undefined
47
    }
48
    const tagSize = 10 + decodeSize(
49
        buffer.subarray(tagPosition + 6, tagPosition + 10)
50
    )
51
    const tagData = buffer.subarray(tagPosition, tagPosition + tagSize)
52
    const tagHeader = tagData.subarray(0, 10)
53
54
    // ID3 version e.g. 3 if ID3v2.3.0
55
    const version = tagHeader[3]
56
    const tagFlags = parseTagHeaderFlags(tagHeader)
57
    let extendedHeaderOffset = 0
58
    if (tagFlags.extendedHeader) {
59
        if (version === 3) {
60
            extendedHeaderOffset = 4 + tagData.readUInt32BE(10)
61
        } else if(version === 4) {
62
            extendedHeaderOffset = decodeSize(tagData.subarray(10, 14))
63
        }
64
    }
65
    const bodySize = tagSize - 10 - extendedHeaderOffset
66
67
    // Copy for now, it might not be necessary, but we are not really sure for
68
    // now, will be reassess if we can avoid the copy.
69
    const tagBody = Buffer.alloc(bodySize)
70
    tagData.copy(tagBody, 0, 0)
71
72
    return {
73
        version, tagBody
74
    }
75
}
76
77
function parseTagHeaderFlags(header: Buffer) {
78
    if (header.length < 10) {
79
        return {}
80
    }
81
    const version = header[3]
82
    const flagsByte = header[5]
83
    if (version === 3) {
84
        return {
85
            unsynchronisation: !!(flagsByte & 128),
86
            extendedHeader: !!(flagsByte & 64),
87
            experimentalIndicator: !!(flagsByte & 32)
88
        }
89
    }
90
    if (version === 4) {
91
        return {
92
            unsynchronisation: !!(flagsByte & 128),
93
            extendedHeader: !!(flagsByte & 64),
94
            experimentalIndicator: !!(flagsByte & 32),
95
            footerPresent: !!(flagsByte & 16)
96
        }
97
    }
98
    return {}
99
}
100
101
/**
102
 * Returns -1 if no tag was found.
103
 */
104
function getId3TagPosition(buffer: Buffer) {
105
    // Search Buffer for valid ID3 frame
106
    const tagHeaderSize = 10
107
    let position = -1
108
    let headerValid = false
109
    do {
110
        position = buffer.indexOf("ID3", position + 1)
111
        if (position !== -1) {
112
            // It's possible that there is a "ID3" sequence without being an
113
            // ID3 Frame, so we need to check for validity of the next 10 bytes.
114
            headerValid = isValidId3Header(
115
                buffer.subarray(position, position + tagHeaderSize)
116
            )
117
        }
118
    } while (position !== -1 && !headerValid)
119
120
    if (!headerValid) {
121
        return -1
122
    }
123
    return position
124
}
125
126
function isValidId3Header(buffer: Buffer) {
127
    if (buffer.length < 10) {
128
        return false
129
    }
130
    if (buffer.readUIntBE(0, 3) !== 0x494433) {
131
        return false
132
    }
133
    if ([0x02, 0x03, 0x04].indexOf(buffer[3]) === -1 || buffer[4] !== 0x00) {
134
        return false
135
    }
136
    return isValidEncodedSize(buffer.subarray(6, 10))
137
}
138
139
function isValidEncodedSize(encodedSize: Buffer) {
140
    // The size must not have the bit 7 set
141
    return ((
142
        encodedSize[0] |
143
        encodedSize[1] |
144
        encodedSize[2] |
145
        encodedSize[3]
146
    ) & 128) === 0
147
}
148