Passed
Pull Request — master (#160)
by
unknown
02:21 queued 40s
created

id3-tag.ts ➔ getId3TagBody   B

Complexity

Conditions 5

Size

Total Lines 33
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 26
dl 0
loc 33
rs 8.7893
c 0
b 0
f 0
cc 5
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 totalHeaderSize = 10 + extendedHeaderOffset
66
    const bodySize = tagSize - totalHeaderSize
67
68
    // Copy for now, it might not be necessary, but we are not really sure for
69
    // now, will be re-assessed if we can avoid the copy.
70
    const tagBody = Buffer.alloc(bodySize)
71
    tagData.copy(tagBody, 0, totalHeaderSize)
72
73
    return {
74
        version, tagBody
75
    }
76
}
77
78
function parseTagHeaderFlags(header: Buffer) {
79
    if (header.length < 10) {
80
        return {}
81
    }
82
    const version = header[3]
83
    const flagsByte = header[5]
84
    if (version === 3) {
85
        return {
86
            unsynchronisation: !!(flagsByte & 128),
87
            extendedHeader: !!(flagsByte & 64),
88
            experimentalIndicator: !!(flagsByte & 32)
89
        }
90
    }
91
    if (version === 4) {
92
        return {
93
            unsynchronisation: !!(flagsByte & 128),
94
            extendedHeader: !!(flagsByte & 64),
95
            experimentalIndicator: !!(flagsByte & 32),
96
            footerPresent: !!(flagsByte & 16)
97
        }
98
    }
99
    return {}
100
}
101
102
/**
103
 * Returns -1 if no tag was found.
104
 */
105
function getId3TagPosition(buffer: Buffer) {
106
    // Search Buffer for valid ID3 frame
107
    const tagHeaderSize = 10
108
    let position = -1
109
    let headerValid = false
110
    do {
111
        position = buffer.indexOf("ID3", position + 1)
112
        if (position !== -1) {
113
            // It's possible that there is a "ID3" sequence without being an
114
            // ID3 Frame, so we need to check for validity of the next 10 bytes.
115
            headerValid = isValidId3Header(
116
                buffer.subarray(position, position + tagHeaderSize)
117
            )
118
        }
119
    } while (position !== -1 && !headerValid)
120
121
    if (!headerValid) {
122
        return -1
123
    }
124
    return position
125
}
126
127
function isValidId3Header(buffer: Buffer) {
128
    if (buffer.length < 10) {
129
        return false
130
    }
131
    if (buffer.readUIntBE(0, 3) !== 0x494433) {
132
        return false
133
    }
134
    if ([0x02, 0x03, 0x04].indexOf(buffer[3]) === -1 || buffer[4] !== 0x00) {
135
        return false
136
    }
137
    return isValidEncodedSize(buffer.subarray(6, 10))
138
}
139
140
function isValidEncodedSize(encodedSize: Buffer) {
141
    // The size must not have the bit 7 set
142
    return ((
143
        encodedSize[0] |
144
        encodedSize[1] |
145
        encodedSize[2] |
146
        encodedSize[3]
147
    ) & 128) === 0
148
}
149