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

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