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

src/ID3Helpers.js   A

Complexity

Total Complexity 38
Complexity/F 3.8

Size

Lines of Code 180
Function Count 10

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

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

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