Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions src/api/remove.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice further simplification due my recent changes. 👍

Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export type RemoveCallback =
*
* @public
*/
export function removeTags(filepath: string): boolean | Error
export function removeTags(filepath: string): true | Error

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The remove callback type should also be updated.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove on callback never returns a boolean, only an error or null (we wanted to change that in a major version)


/**
* Removes asynchronously any written ID3-Frames from the specified file.
Expand All @@ -51,9 +51,6 @@ function removeTagsSync(filepath: string) {
}

const newData = removeTagsFromBuffer(data)
if(!newData) {
return false
}

try {
fs.writeFileSync(filepath, newData, 'binary')
Expand All @@ -72,10 +69,6 @@ function removeTagsAsync(filepath: string, callback: RemoveCallback) {
}

const newData = removeTagsFromBuffer(data)
if(!newData) {
callback(error)
return
}

fs.writeFile(filepath, newData, 'binary', (error) => {
if(error) {
Expand Down
22 changes: 11 additions & 11 deletions src/api/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ import { read } from "./read"
import { updateTags } from '../updateTags'
import { write, WriteCallback } from "./write"

/**

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why to have moved it?
The order before was: Buffer, sync, async, i.e. file op grouped, I don't understand the logic now?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no logic, but TypeScript is unable to infer the callback parameter types otherwise.
Before, it was not possible to call NodeID3.update({}, buffer, (err, data) => {}) because err/data would be any.

I don't know why changing the order fixes it :/ Do you have any idea?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a test and I do not observe this behaviour this is strange. Let's experiment offline.

* Updates ID3-Tags asynchronously in the specified file.
*
* @public
*/
export function update(
tags: WriteTags,
filebuffer: string | Buffer,
callback: WriteCallback
): void

/**
* Updates ID3-Tags from the given buffer.
*
Expand All @@ -27,17 +38,6 @@ import { write, WriteCallback } from "./write"
options?: Options
): true | Error

/**
* Updates ID3-Tags asynchronously in the specified file.
*
* @public
*/
export function update(
tags: WriteTags,
filebuffer: string | Buffer,
callback: WriteCallback
): void

/**
* Updates ID3-Tags asynchronously from the given buffer or specified file.
*
Expand Down
4 changes: 0 additions & 4 deletions src/id3-tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,6 @@ export function removeId3Tag(data: Buffer) {
}
const encodedSize = subarray(data, tagPosition + Header.offset.size, 4)

if (!isValidEncodedSize(encodedSize)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

return false
}

if (data.length >= tagPosition + Header.size) {
const size = decodeSize(encodedSize)
return Buffer.concat([
Expand Down
70 changes: 70 additions & 0 deletions test/api/remove.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import * as NodeID3 from '../../index'
import assert = require('assert')
import chai = require('chai')
import * as fs from 'fs'

describe('NodeID3 API', function () {
describe('#removeTags()', function() {
const nonExistingFilepath = './hopefully-does-not-exist.mp3'
it('sync not existing filepath', function() {
chai.assert.isFalse(fs.existsSync(nonExistingFilepath))
chai.assert.instanceOf(
NodeID3.removeTags(nonExistingFilepath), Error
)
})
it('async not existing filepath', function() {
chai.assert.isFalse(fs.existsSync(nonExistingFilepath))
NodeID3.removeTags(nonExistingFilepath, function(err) {
if(!(err instanceof Error)) {
assert.fail("No error thrown on non-existing filepath")
}
})
})

const titleTag = {
title: "abc"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small: title: "title".

} satisfies NodeID3.WriteTags
const filepath = './testfile.mp3'

describe('valid buffer', function() {
const prefixBuffer = Buffer.from([0x01, 0x02, 0x03])
const postfixBuffer = Buffer.from([0x04, 0x05, 0x06])
const buffer = Buffer.concat([
prefixBuffer,
NodeID3.create(titleTag),
postfixBuffer
])
const bufferAfterRemove = Buffer.concat([
prefixBuffer,
postfixBuffer
])

beforeEach(function() {
fs.writeFileSync(filepath, buffer)
})

afterEach(function() {
fs.unlinkSync(filepath)
})

it('sync remove tags from file', function() {
NodeID3.removeTags(filepath)
assert.deepStrictEqual(
fs.readFileSync(filepath),
bufferAfterRemove
)
})

it('async remove tags from file', function(done) {
NodeID3.removeTags(filepath, (err) => {
assert.equal(err, null)
assert.deepStrictEqual(
fs.readFileSync(filepath),
bufferAfterRemove
)
done()
})
})
})
})
})
79 changes: 79 additions & 0 deletions test/api/update.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as NodeID3 from '../../index'
import assert = require('assert')
import chai = require('chai')
import * as fs from 'fs'
import { WriteCallback } from '../../index'

describe('NodeID3 API', function () {
describe('#update()', function() {
const titleTag = {
title: "abc"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion title: "title".

} satisfies NodeID3.WriteTags
const albumTag = {
album: "def"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: album: "album".

}
const tags = {...titleTag, ...albumTag}
const filepath = './testfile.mp3'

beforeEach(function() {
fs.writeFileSync(filepath, NodeID3.create(titleTag))
})

it('sync add tag to existing', function() {
chai.assert.isTrue(NodeID3.update(albumTag, filepath))
assert.deepStrictEqual(
NodeID3.read(filepath, {noRaw: true}),
tags
)
})

it('async add tag to existing', function(done) {
NodeID3.update(albumTag, filepath, (error, data) => {
chai.assert.isNull(error)
assert.deepStrictEqual(
NodeID3.read(filepath, {noRaw: true}),
tags
)
done()
})
})

it('compare key', function() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somehow, let's try to explain a bit more what is happening here:

  • userDefinedText frame with same description should update the value
  • private frame with same ownerIdentifier should update the data

TODO:
By the way this behaviour is ad-hoc,, we need to discuss more about it.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed that we will remove this behavior in a new major release, I'm aware that this test is gonna be obsolete soon, just added it until then.
I changed the it text and added a little comment explaining the test until then.

const beforeTags = {
userDefinedText: [{
description: 'description',
value: 'some value'
}],
private: [{
ownerIdentifier: 'ownerIdentifier',
data: Buffer.from('data')
}]
} satisfies NodeID3.Tags
const addTags = {
userDefinedText: [{
description: 'description',
value: 'some other value'
}],
private: {
ownerIdentifier: 'ownerIdentifier',
data: Buffer.from('data2')
}
} satisfies NodeID3.Tags
const afterTags = {
userDefinedText: addTags.userDefinedText,
private: [...beforeTags.private, addTags.private]
} satisfies NodeID3.Tags
const beforeBuffer = NodeID3.create(beforeTags)
const afterBuffer = NodeID3.update(addTags, beforeBuffer)

assert.deepStrictEqual(
NodeID3.read(afterBuffer, {noRaw: true}),
afterTags
)
})

afterEach(function() {
fs.unlinkSync(filepath)
})
})
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing EOL,
TODO: add a lint error for this.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the rule 👍

122 changes: 121 additions & 1 deletion test/frames.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import * as NodeID3 from '../index'
import assert = require('assert')
import { expect } from 'chai'

/**
* Some characters to test unicode encoding.
*/
const unicodeTestCharacters = "-äé"
const TagConstants = NodeID3.TagConstants

describe('NodeID3 frames', function () {
it('read() matches create()', function () {
const TagConstants = NodeID3.TagConstants
const tags = {
/**
* COMM
Expand Down Expand Up @@ -162,4 +163,123 @@ describe('NodeID3 frames', function () {
const readTags = NodeID3.read(createdBuffer, { noRaw: true})
assert.deepStrictEqual(tags, readTags)
})

describe('read() does not match create()', function() {
it('create throws', function() {
const throwingTags = [
{ POPM: {} },
{ POPM: {
email: 'test'
}},
{ POPM: {
email: 'test',
rating: 1
}},
{ CTOC: {} },
{ USLT: {} },
{ CHAP: {} },
{ COMM: {} },
{ TALB: null },
{ WCOM: null },
{ APIC: {
mime: "a",
type: {
id: TagConstants.AttachedPicture.PictureType.FRONT_COVER
},
description: "d",
imageBuffer: ""
}},
{ COMM: {
language: 'asdf',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

invalid-language-code ?

text: 'text'
}},
{ COMR: {
prices: {
EURO: 13
}
}}
]

for(const throwingTag of throwingTags) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be for throwingTags.forEach to be a bit more functional.

expect(() => NodeID3.create(throwingTag as never)).to.throw()
}
})

it('frame builder changes data', function() {
const tags = {
unsynchronisedLyrics: 'just a string',
commercialFrame: {
validUntil: { year: 2023, month: 9, day: 'a'},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

day is not valid shouldn't it throw?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be more in line with the other throws? Right now, it's just ignored

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, I remember, there is no validation right now, we should probably add one after this PR.

receivedAs: TagConstants.CommercialFrame.ReceivedAs.OTHER,
},
tableOfContents: {
elementID: "1"
},
synchronisedLyrics: {
language: "eng",
timeStampFormat: TagConstants.TimeStampFormat.MILLISECONDS,
contentType: TagConstants.SynchronisedLyrics.ContentType.LYRICS,
synchronisedText: []
},
private: {
data: 'string'
},
uniqueFileIdentifier: {
ownerIdentifier: 'a',
identifier: 'b'
},
image: Buffer.from([0xff, 0xd8, 0xff, 0x00])
}
const expectedTags = {
unsynchronisedLyrics: {
language: 'eng',
shortText: '',
text: tags.unsynchronisedLyrics
},
commercialFrame: [{
...tags.commercialFrame,
validUntil: {
year: 0, month: 0, day: 0
},
prices: {},
contactUrl: '',
nameOfSeller: '',
description: '',
}],
tableOfContents: [{
...tags.tableOfContents,
isOrdered: false,
elements: [],
tags: { raw: {} }
}],
synchronisedLyrics: [{
...tags.synchronisedLyrics,
shortText: ''
}],
private: [{
ownerIdentifier: '',
data: Buffer.from(tags.private.data, 'utf8')
}],
uniqueFileIdentifier: [{
...tags.uniqueFileIdentifier,
identifier: Buffer.from(
tags.uniqueFileIdentifier.identifier
, 'utf8')
}],
image: {
mime: "image/jpeg",
type: {
id: TagConstants.AttachedPicture.PictureType.FRONT_COVER,
name: "front cover"
},
description: '',
imageBuffer: tags.image
}
} satisfies NodeID3.Tags
assert.deepStrictEqual(
NodeID3.read(NodeID3.create(tags as never), {noRaw: true}),
expectedTags
)
})
})
})