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

src/ID3Helpers.js   A

Complexity

Total Complexity 38
Complexity/F 3.8

Size

Lines of Code 181
Function Count 10

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 105
dl 0
loc 181
rs 9.36
c 0
b 0
f 0
wmc 38
mnd 28
bc 28
fnc 10
bpm 2.8
cpm 3.8
noi 0

7 Functions

Rating   Name   Duplication   Size   Complexity  
C ID3Helpers.js ➔ getTagsFromFrames 0 41 11
A ID3Helpers.js ➔ createBufferFromTags 0 3 1
A ID3Helpers.js ➔ isFrameDiscarded 0 7 2
A ID3Helpers.js ➔ getTagsFromID3Body 0 3 1
B ID3Helpers.js ➔ getTagsFromBuffer 0 26 5
D ID3Helpers.js ➔ createBuffersFromTags 0 52 13
A ID3Helpers.js ➔ getFramesFromTagBody 0 24 5
1
import {
2
    FRAME_IDENTIFIERS,
3
    FRAME_ALIASES
4
} from "./definitions/FrameIdentifiers"
5
import * as ID3Frames from './ID3Frames'
6
const ID3Util = require('./ID3Util')
7
import { Frame } from './Frame'
8
import { getFrameSize } from './FrameHeader'
9
10
/**
11
 * Returns array of buffers created by tags specified in the tags argument
12
 * @param tags - Object containing tags to be written
13
 * @returns {Array}
14
 */
15
function createBuffersFromTags(tags) {
16
    const frames = []
17
    if(!tags) {
18
        return frames
19
    }
20
    const rawObject = Object.keys(tags).reduce((acc, val) => {
21
        if(FRAME_IDENTIFIERS.v3[val] !== undefined) {
22
            acc[FRAME_IDENTIFIERS.v3[val]] = tags[val]
23
        } else if(FRAME_IDENTIFIERS.v4[val] !== undefined) {
24
            // Currently, node-id3 always writes ID3 version 3.
25
            // However, version 3 and 4 are very similar, and node-id3
26
            // can also read version 4 frames.
27
            // Until version 4 is fully supported, as a workaround,
28
            // allow writing version 4 frames into a version 3 tag.
29
            // If a reader does not support a v4 frame, it's (per spec)
30
            // supposed to skip it, so it should not be a problem.
31
            acc[FRAME_IDENTIFIERS.v4[val]] = tags[val]
32
        } else {
33
            acc[val] = tags[val]
34
        }
35
        return acc
36
    }, {})
37
38
    Object.keys(rawObject).forEach((frameIdentifier) => {
39
        let frame
40
        // Check if invalid frameIdentifier
41
        if (frameIdentifier.length !== 4) {
42
            return
43
        }
44
        if (frameIdentifier in ID3Frames.Frames) {
45
            frame = ID3Frames.Frames[frameIdentifier].create(rawObject[frameIdentifier], 3)
46
        } else if (frameIdentifier.startsWith('T')) {
47
            frame = ID3Frames.GENERIC_TEXT.create(frameIdentifier, rawObject[frameIdentifier], 3)
48
        } else if (frameIdentifier.startsWith('W')) {
49
            if(ID3Util.getSpecOptions(frameIdentifier, 3).multiple && rawObject[frameIdentifier] instanceof Array && rawObject[frameIdentifier].length > 0) {
50
                frame = Buffer.alloc(0)
51
                // deduplicate array
52
                for(const url of [...new Set(rawObject[frameIdentifier])]) {
53
                    frame = Buffer.concat([frame, ID3Frames.GENERIC_URL.create(frameIdentifier, url, 3)])
54
                }
55
            } else {
56
                frame = ID3Frames.GENERIC_URL.create(frameIdentifier, rawObject[frameIdentifier], 3)
57
            }
58
        }
59
60
        if (frame && frame instanceof Buffer) {
61
            frames.push(frame)
62
        }
63
    })
64
65
    return frames
66
}
67
68
/**
69
 * Return a buffer with the frames for the specified tags
70
 * @param tags - Object containing tags to be written
71
 * @returns {Buffer}
72
 */
73
export function createBufferFromTags(tags) {
74
    return Buffer.concat(createBuffersFromTags(tags))
75
}
76
77
export function getTagsFromBuffer(filebuffer, options) {
78
    const framePosition = ID3Util.getFramePosition(filebuffer)
79
    if(framePosition === -1) {
80
        return getTagsFromFrames([], 3, options)
81
    }
82
    const frameSize = ID3Util.decodeSize(filebuffer.slice(framePosition + 6, framePosition + 10)) + 10
83
    const frame = Buffer.alloc(frameSize + 1)
84
    filebuffer.copy(frame, 0, framePosition)
85
    //ID3 version e.g. 3 if ID3v2.3.0
86
    const version = frame[3]
87
    const tagFlags = ID3Util.parseTagHeaderFlags(frame)
88
    let extendedHeaderOffset = 0
89
    if(tagFlags.extendedHeader) {
90
        if(version === 3) {
91
            extendedHeaderOffset = 4 + filebuffer.readUInt32BE(10)
92
        } else if(version === 4) {
93
            extendedHeaderOffset = ID3Util.decodeSize(filebuffer.slice(10, 14))
94
        }
95
    }
96
    const frameBody = Buffer.alloc(frameSize - 10 - extendedHeaderOffset)
97
    filebuffer.copy(frameBody, 0, framePosition + 10 + extendedHeaderOffset)
98
99
    const frames = getFramesFromTagBody(frameBody, version, options)
100
101
    return getTagsFromFrames(frames, version, options)
102
}
103
104
function isFrameDiscarded(frameIdentifier, options) {
105
    if(options.exclude instanceof Array && options.exclude.includes(frameIdentifier)) {
106
        return true
107
    }
108
109
    return options.include instanceof Array && !options.include.includes(frameIdentifier)
110
}
111
112
function getFramesFromTagBody(tagBody, version, options = {}) {
113
    if(!(tagBody instanceof Buffer)) {
114
        return []
115
    }
116
117
    const frames = []
118
    while(tagBody.length && tagBody[0] !== 0x00) {
119
        const frameSize = getFrameSize(tagBody, version)
120
121
        // Prevent errors due to broken data.
122
        if (frameSize > tagBody.length) {
123
            break
124
        }
125
126
        const frameBuffer = tagBody.subarray(0, frameSize)
127
        const frame = Frame.createFromBuffer(frameBuffer, version)
128
        if(frame && !isFrameDiscarded(frame.identifier, options)) {
129
            frames.push(frame)
130
        }
131
132
        tagBody = tagBody.subarray(frameSize)
133
    }
134
    return frames
135
}
136
137
function getTagsFromFrames(frames, version, options = {}) {
138
    const tags = { }
139
    const raw = { }
140
141
    frames.forEach((frame) => {
142
        const frameValue = frame.getValue()
143
        const frameAlias = FRAME_ALIASES.v34[frame.identifier]
144
145
        if(ID3Util.getSpecOptions(frame.identifier, version).multiple) {
146
            if(!options.onlyRaw) {
147
                if(!tags[frameAlias]) {
148
                    tags[frameAlias] = []
149
                }
150
                tags[frameAlias].push(frameValue)
151
            }
152
            if(!options.noRaw) {
153
                if(!raw[frame.identifier]) {
154
                    raw[frame.identifier] = []
155
                }
156
                raw[frame.identifier].push(frameValue)
157
            }
158
        } else {
159
            if(!options.onlyRaw) {
160
                tags[frameAlias] = frameValue
161
            }
162
            if(!options.noRaw) {
163
                raw[frame.identifier] = frameValue
164
            }
165
        }
166
    })
167
168
    if(options.onlyRaw) {
169
        return raw
170
    }
171
    if(options.noRaw) {
172
        return tags
173
    }
174
175
    tags.raw = raw
176
    return tags
177
}
178
179
export function getTagsFromID3Body(body) {
180
    return getTagsFromFrames(getFramesFromTagBody(body, 3), 3)
181
}
182