Passed
Push — convert-to-typescript ( 6cf209 )
by Jan
03:57
created

index.ts ➔ update   B

Complexity

Conditions 7

Size

Total Lines 9
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 18
dl 0
loc 9
c 0
b 0
f 0
rs 8
cc 7
1
import fs = require('fs')
2
const ID3Util = require('./src/ID3Util')
3
const ID3Helpers = require('./src/ID3Helpers')
4
import { isFunction, isString } from './src/util'
5
import { Tags, WriteTags } from './src/Tags'
6
import { Options } from './src/Options'
7
import { TagConstants } from './src/TagConstants'
8
import { updateTags } from './src/update'
9
10
export { TagConstants } from './src/TagConstants'
11
12
/*
13
**  Used specification: http://id3.org/id3v2.3.0
14
*/
15
16
type WriteCallback = {
17
    (error: null, data: Buffer): void
18
    (error: NodeJS.ErrnoException | Error, data: null): void
19
}
20
21
type ReadCallback = {
22
    (error: NodeJS.ErrnoException | Error, tags: null): void
23
    (error: null, tags: Tags): void
24
}
25
26
type RemoveCallback =
27
    (error: NodeJS.ErrnoException | Error | null) => void
28
29
type CreateCallback =
30
    (data: Buffer) => void
31
32
/**
33
 * Check and remove already written ID3-Frames from a buffer
34
 */
35
export function removeTagsFromBuffer(data: Buffer) {
36
    const framePosition = ID3Util.getFramePosition(data)
37
38
    if (framePosition === -1) {
39
        return data
40
    }
41
42
    const encodedSize = data.subarray(framePosition + 6, framePosition + 10)
43
    if (!ID3Util.isValidEncodedSize(encodedSize)) {
44
        return false
45
    }
46
47
    if (data.length >= framePosition + 10) {
48
        const size = ID3Util.decodeSize(encodedSize)
49
        return Buffer.concat([
50
            data.subarray(0, framePosition),
51
            data.subarray(framePosition + size + 10)
52
        ])
53
    }
54
55
    return data
56
}
57
58
function writeInBuffer(tags: Buffer, buffer: Buffer) {
59
    buffer = removeTagsFromBuffer(buffer) || buffer
60
    return Buffer.concat([tags, buffer])
61
}
62
63
function writeAsync(tags: Buffer, filepath: string, callback: WriteCallback) {
64
    fs.readFile(filepath, (error, data) => {
65
        if(error) {
66
            callback(error, null)
67
            return
68
        }
69
        const newData = writeInBuffer(tags, data)
70
        fs.writeFile(filepath, newData, 'binary', (error) => {
71
            if (error) {
72
                callback(error, null)
73
            } else {
74
                callback(null, newData)
75
            }
76
        })
77
    })
78
}
79
80
function writeSync(tags: Buffer, filepath: string) {
81
    try {
82
        const data = fs.readFileSync(filepath)
83
        const newData = writeInBuffer(tags, data)
84
        fs.writeFileSync(filepath, newData, 'binary')
85
        return true
86
    } catch(error) {
87
        return error as Error
88
    }
89
}
90
91
/**
92
 * Write passed tags to a file/buffer
93
 */
94
export function write(tags: WriteTags, buffer: Buffer): Buffer
95
export function write(tags: WriteTags, filepath: string): true | Error
96
export function write(
97
    tags: WriteTags, filebuffer: string | Buffer, callback: WriteCallback
98
): void
99
export function write(
100
    tags: WriteTags,
101
    filebuffer: string | Buffer,
102
    callback?: WriteCallback
103
): Buffer | true | Error | void {
104
    const tagsBuffer = create(tags)
105
106
    if(isFunction(callback)) {
107
        if (isString(filebuffer)) {
108
            return writeAsync(tagsBuffer, filebuffer, callback)
109
        }
110
        return callback(null, writeInBuffer(tagsBuffer, filebuffer))
111
    }
112
    if(isString(filebuffer)) {
113
        return writeSync(tagsBuffer, filebuffer)
114
    }
115
    return writeInBuffer(tagsBuffer, filebuffer)
116
}
117
118
/**
119
 * Creates a buffer containing the ID3 Tag
120
 */
121
export function create(tags: WriteTags): Buffer
122
export function create(tags: WriteTags, callback: CreateCallback): void
123
export function create(tags: WriteTags, callback?: CreateCallback) {
124
    const frames = ID3Helpers.createBufferFromTags(tags)
125
126
    //  Create ID3 header
127
    const header = Buffer.alloc(10)
128
    header.fill(0)
129
    header.write("ID3", 0)              //File identifier
130
    header.writeUInt16BE(0x0300, 3)     //Version 2.3.0  --  03 00
131
    header.writeUInt16BE(0x0000, 5)     //Flags 00
132
    ID3Util.encodeSize(frames.length).copy(header, 6)
133
134
    const id3Data = Buffer.concat([header, frames])
135
136
    if(isFunction(callback)) {
137
        return callback(id3Data)
138
    }
139
    return id3Data
140
}
141
142
function readSync(filebuffer: string | Buffer, options: Options): Tags {
143
    if(isString(filebuffer)) {
144
        filebuffer = fs.readFileSync(filebuffer)
145
    }
146
    return ID3Helpers.getTagsFromBuffer(filebuffer, options)
147
}
148
149
function readAsync(
150
    filebuffer: string | Buffer,
151
    options: Options,
152
    callback: ReadCallback
153
) {
154
    if(isString(filebuffer)) {
155
        fs.readFile(filebuffer, (error, data) => {
156
            if(error) {
157
                callback(error, null)
158
            } else {
159
                callback(null, ID3Helpers.getTagsFromBuffer(data, options))
160
            }
161
        })
162
    } else {
163
        callback(null, ID3Helpers.getTagsFromBuffer(filebuffer, options))
164
    }
165
}
166
167
/**
168
 * Read ID3-Tags from passed buffer/filepath
169
 */
170
export function read(filebuffer: string | Buffer, options?: Options): Tags
171
export function read(filebuffer: string | Buffer, callback: ReadCallback): void
172
export function read(
173
    filebuffer: string | Buffer, options: Options, callback: ReadCallback
174
): void
175
export function read(
176
    filebuffer: string | Buffer,
177
    optionsOrCallback?: Options | ReadCallback,
178
    callback?: ReadCallback
179
): Tags | void {
180
    const options: Options =
181
        (isFunction(optionsOrCallback) ? {} : optionsOrCallback) ?? {}
182
    callback =
183
        isFunction(optionsOrCallback) ? optionsOrCallback : callback
184
185
    if(isFunction(callback)) {
186
        return readAsync(filebuffer, options, callback)
187
    }
188
    return readSync(filebuffer, options)
189
}
190
191
/**
192
 * Update ID3-Tags from passed buffer/filepath
193
 */
194
export function update(
195
    tags: WriteTags,
196
    buffer: Buffer,
197
    options?: Options
198
): Buffer
199
export function update(
200
    tags: WriteTags,
201
    filepath: string,
202
    options?: Options
203
): true | Error
204
export function update(
205
    tags: WriteTags,
206
    filebuffer: string | Buffer,
207
    callback: WriteCallback
208
): void
209
export function update(
210
    tags: WriteTags,
211
    filebuffer: string | Buffer,
212
    options: Options,
213
    callback: WriteCallback
214
): void
215
export function update(
216
    tags: WriteTags,
217
    filebuffer: string | Buffer,
218
    optionsOrCallback?: Options | WriteCallback,
219
    callback?: WriteCallback
220
): Buffer | true | Error | void {
221
    const options: Options =
222
        (isFunction(optionsOrCallback) ? {} : optionsOrCallback) ?? {}
223
    callback =
224
        isFunction(optionsOrCallback) ? optionsOrCallback : callback
225
226
    const currentTags = read(filebuffer, options)
227
    const updatedTags = updateTags(tags, currentTags)
228
    if (isFunction(callback)) {
229
        return write(updatedTags, filebuffer, callback)
230
    }
231
    if (isString(filebuffer)) {
232
        return write(updatedTags, filebuffer)
233
    }
234
    return write(updatedTags, filebuffer)
235
}
236
237
function removeTagsSync(filepath: string) {
238
    let data
239
    try {
240
        data = fs.readFileSync(filepath)
241
    } catch(error) {
242
        return error as Error
243
    }
244
245
    const newData = removeTagsFromBuffer(data)
246
    if(!newData) {
247
        return false
248
    }
249
250
    try {
251
        fs.writeFileSync(filepath, newData, 'binary')
252
    } catch(error) {
253
        return error as Error
254
    }
255
256
    return true
257
}
258
259
function removeTagsAsync(filepath: string, callback: RemoveCallback) {
260
    fs.readFile(filepath, (error, data) => {
261
        if(error) {
262
            callback(error)
263
            return
264
        }
265
266
        const newData = removeTagsFromBuffer(data)
267
        if(!newData) {
268
            callback(error)
269
            return
270
        }
271
272
        fs.writeFile(filepath, newData, 'binary', (error) => {
273
            if(error) {
274
                callback(error)
275
            } else {
276
                callback(null)
277
            }
278
        })
279
    })
280
}
281
282
/**
283
 * Check and remove already written ID3-Frames from a file
284
 */
285
export function removeTags(filepath: string): boolean | Error
286
export function removeTags(filepath: string, callback: RemoveCallback): void
287
export function removeTags(filepath: string, callback?: RemoveCallback) {
288
    if(isFunction(callback)) {
289
        return removeTagsAsync(filepath, callback)
290
    }
291
    return removeTagsSync(filepath)
292
}
293
294
type Settle<T> = {
295
    (error: NodeJS.ErrnoException | Error, result: null): void
296
    (error: null, result: T): void
297
}
298
299
function makePromise<T>(callback: (settle: Settle<T>) => void) {
300
    return new Promise<T>((resolve, reject) => {
301
        callback((error, result) => {
302
            if(error) {
303
                reject(error)
304
            }
305
            if (result) {
306
                resolve(result)
307
            }
308
        })
309
    })
310
}
311
312
export const PromiseExports = {
313
    create: (tags: Tags) =>
314
        makePromise((settle: Settle<Buffer>) =>
315
            create(tags, result => settle(null, result)),
316
    ),
317
    write: (tags: Tags, filebuffer: string | Buffer) =>
318
        makePromise<Buffer>((callback: WriteCallback) =>
319
            write(tags, filebuffer, callback)
320
        ),
321
    update: (tags: Tags, filebuffer: string | Buffer, options?: Options) =>
322
        makePromise<Buffer>((callback: WriteCallback) =>
323
            update(tags, filebuffer, options ?? {}, callback)
324
        ),
325
    read: (file: string, options?: Options) =>
326
        makePromise((callback: ReadCallback) =>
327
            read(file, options ?? {}, callback)
328
        ),
329
    removeTags: (filepath: string) =>
330
        makePromise((settle: Settle<void>) =>
331
            removeTags(
332
                filepath,
333
                (error) => error ? settle(error, null) : settle(null)
334
            )
335
        )
336
} as const
337
338
export default {
339
    create,
340
    read,
341
    write,
342
    TagConstants
343
}
344