Skip to content

Commit 9b63a2a

Browse files
author
Benjamin Russell
authored
Fix for ID3v2 Frame Read Failures (Lake Track) (#114)
* Make frame factory a class * Fix issue of broken frame reading. * Forgot to commit this one
1 parent 5a0db48 commit 9b63a2a

File tree

4 files changed

+92
-76
lines changed

4 files changed

+92
-76
lines changed

src/id3v2/frames/frameFactory.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,17 @@ import {Guards, NumberUtils} from "../../utils";
3030
*/
3131
export type FrameCreator = (data: ByteVector, offset: number, header: Id3v2FrameHeader, version: number) => Frame;
3232

33-
let customFrameCreators: FrameCreator[] = [];
34-
3533
/**
3634
* Performs the necessary operations to determine and create the correct child classes of
3735
* {@link Frame} for a given raw ID3v2 frame.
3836
* By default, this will only load frames contained in the library. To add additional frames to the
3937
* process, register a frame creator with {@link addFrameCreator}.
4038
*/
41-
export default {
39+
export class Id3v2FrameFactory {
40+
41+
42+
private static readonly CUSTOM_FRAME_CREATORS: FrameCreator[] = [];
43+
4244
/**
4345
* Adds a custom frame creator to try before using standard frame creation methods.
4446
* Frame creators are used before standard methods so custom checking can be used and new
@@ -50,17 +52,17 @@ export default {
5052
* * version: number ID3v2 version the raw frame data is stored in (should be byte)
5153
* * returns Frame if method was able to match the frame, falsy otherwise
5254
*/
53-
addFrameCreator: (creator: FrameCreator): void => {
55+
public static addFrameCreator(creator: FrameCreator): void {
5456
Guards.truthy(creator, "creator");
55-
customFrameCreators.unshift(creator);
56-
},
57+
this.CUSTOM_FRAME_CREATORS.unshift(creator);
58+
}
5759

5860
/**
5961
* Removes all custom frame creators
6062
*/
61-
clearFrameCreators: (): void => {
62-
customFrameCreators = [];
63-
},
63+
public static clearFrameCreators(): void {
64+
this.CUSTOM_FRAME_CREATORS.length = 0;
65+
}
6466

6567
/**
6668
* Creates a {@link Frame} object by reading it from raw ID3v2 frame data.
@@ -78,13 +80,13 @@ export default {
7880
* * offset: updated offset where the next frame starts
7981
*/
8082
// @TODO: Split into fromFile and fromData
81-
createFrame: (
83+
public static createFrame(
8284
data: ByteVector,
8385
file: File,
8486
offset: number,
8587
version: number,
8688
alreadyUnsynced: boolean
87-
): {frame: Frame, offset: number} => {
89+
): {frame: Frame, offset: number} {
8890
Guards.uint(offset, "offset");
8991
Guards.byte(version, "version");
9092

@@ -132,7 +134,7 @@ export default {
132134

133135
try {
134136
// Try to find a custom creator
135-
for (const creator of customFrameCreators) {
137+
for (const creator of this.CUSTOM_FRAME_CREATORS) {
136138
// @TODO: If we're reading from a file, data will only ever contain the header
137139
const frame = creator(data, position, header, version);
138140
if (frame) {
@@ -243,4 +245,4 @@ export default {
243245
};
244246
}
245247
}
246-
};
248+
}

src/id3v2/id3v2Tag.ts

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import AttachmentFrame from "./frames/attachmentFrame";
22
import CommentsFrame from "./frames/commentsFrame";
3-
import FrameFactory from "./frames/frameFactory";
43
import Id3v2ExtendedHeader from "./id3v2ExtendedHeader";
54
import Id3v2TagFooter from "./id3v2TagFooter";
65
import Id3v2Settings from "./id3v2Settings";
@@ -11,6 +10,7 @@ import {ByteVector, StringType} from "../byteVector";
1110
import {CorruptFileError, NotImplementedError, NotSupportedError} from "../errors";
1211
import {File, ReadStyle} from "../file";
1312
import {Frame, FrameClassType} from "./frames/frame";
13+
import {Id3v2FrameFactory} from "./frames/frameFactory";
1414
import {FrameIdentifier, FrameIdentifiers} from "./frameIdentifiers";
1515
import {Id3v2FrameFlags} from "./frames/frameHeader";
1616
import {Id3v2TagHeader, Id3v2TagHeaderFlags} from "./id3v2TagHeader";
@@ -1394,6 +1394,7 @@ export default class Id3v2Tag extends Tag {
13941394
let frameDataEndPosition = (data ? data.length : this._header.tagSize) + frameDataPosition;
13951395

13961396
// Check for the extended header
1397+
// @TODO: Replace with NumberUtils.hasFlags
13971398
if ((this._header.flags & Id3v2TagHeaderFlags.ExtendedHeader) !== 0) {
13981399
this._extendedHeader = Id3v2ExtendedHeader.fromData(data, this._header.majorVersion);
13991400

@@ -1405,28 +1406,41 @@ export default class Id3v2Tag extends Tag {
14051406

14061407
// Parse the frames
14071408
while (frameDataPosition < frameDataEndPosition) {
1408-
const frameRead = FrameFactory.createFrame(
1409-
data,
1410-
file,
1411-
frameDataPosition,
1412-
this._header.majorVersion,
1413-
fullTagUnsync
1414-
);
1409+
try {
1410+
const frameRead = Id3v2FrameFactory.createFrame(
1411+
data,
1412+
file,
1413+
frameDataPosition,
1414+
this._header.majorVersion,
1415+
fullTagUnsync
1416+
);
1417+
1418+
// If the frame factory returned undefined, that means we've hit the end of frames
1419+
if (!frameRead) {
1420+
break;
1421+
}
14151422

1416-
// If the frame factory returned undefined, that means we've hit the end of frames
1417-
if (!frameRead) {
1418-
break;
1419-
}
1423+
// We found a frame, deconstruct the read result
1424+
const frame = frameRead.frame;
1425+
frameDataPosition = frameRead.offset;
1426+
1427+
// Only add frames that contain data
1428+
if (!frame || frame.size === 0) {
1429+
continue;
1430+
}
1431+
this.addFrame(frame);
1432+
} catch (e: unknown) {
1433+
// If we fail at any point while trying to read the frames of the tag, we will have
1434+
// lost our place in the file and will have to give up reading the tag.
1435+
// Ok, technically we could use some heuristics to try to recover (eg, Foobar can
1436+
// do this), but it is a lot of effort for minimal reward.
14201437

1421-
// We found a frame, deconstruct the read result
1422-
const frame = frameRead.frame;
1423-
frameDataPosition = frameRead.offset;
1438+
// NOTE: It's important to not let this error propagate to the file level, else
1439+
// the user will not be able to recover the file.
14241440

1425-
// Only add frames that contain data
1426-
if (!frame || frame.size === 0) {
1427-
continue;
1441+
// this.markCorrupt(e); @TODO: Add corruption reasons.
1442+
break;
14281443
}
1429-
this.addFrame(frame);
14301444
}
14311445
}
14321446

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ export {
9898
FrameClassType as Id3v2FrameClassType
9999
} from "./id3v2/frames/frame";
100100
export {
101-
default as Id3v2FrameFactory,
102-
FrameCreator as Id3v2FrameCreator
101+
FrameCreator as Id3v2FrameCreator,
102+
Id3v2FrameFactory
103103
} from "./id3v2/frames/frameFactory";
104104
export {Id3v2FrameFlags, Id3v2FrameHeader} from "./id3v2/frames/frameHeader";
105105
export {default as Id3v2MusicCdIdentifierFrame} from "./id3v2/frames/musicCdIdentifierFrame";

0 commit comments

Comments
 (0)