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

id3-tag.ts ➔ getId3TagBody   B

Complexity

Conditions 5

Size

Total Lines 33
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

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