Skip to content

Commit 56706ba

Browse files
authored
Audit MPEG-4 box types (#125)
* Auditing mpeg4 box types * An letter * Backwards compat and more tests * Clear existing boxes for conductor/subtitle * Accidentally a ;
1 parent c477378 commit 56706ba

3 files changed

Lines changed: 123 additions & 44 deletions

File tree

src/mpeg4/appleTag.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,16 @@ export default class AppleTag extends Tag {
5555
public set title(v: string) { this.setQuickTimeString(Mpeg4BoxType.NAM, v); }
5656

5757
/** @inheritDoc */
58-
public get subtitle(): string { return this.getFirstQuickTimeString(Mpeg4BoxType.SUBT); }
58+
public get subtitle(): string {
59+
return this.getFirstQuickTimeString(Mpeg4BoxType.SUBT) // @TODO: Backwards compat for pre-6.0.2 releases
60+
|| this.getFirstQuickTimeString(Mpeg4BoxType.ST3);
61+
}
5962
/** @inheritDoc */
60-
public set subtitle(v: string) { this.setQuickTimeString(Mpeg4BoxType.SUBT, v); }
63+
public set subtitle(v: string) {
64+
// Clear out existing boxes for conductor (@TODO: Backwards compat for pre-6.0.2 releases)
65+
this.setQuickTimeData(Mpeg4BoxType.SUBT, undefined);
66+
this.setQuickTimeString(Mpeg4BoxType.ST3, v);
67+
}
6168

6269
/** @inheritDoc */
6370
public get description(): string { return this.getFirstQuickTimeString(Mpeg4BoxType.DESC); }
@@ -209,9 +216,16 @@ export default class AppleTag extends Tag {
209216
}
210217

211218
/** @inheritDoc */
212-
public get conductor(): string { return this.getFirstQuickTimeString(Mpeg4BoxType.COND); }
219+
public get conductor(): string {
220+
return this.getFirstQuickTimeString(Mpeg4BoxType.COND) // @TODO: Backwards compat for pre-6.0.2 releases
221+
|| this.getFirstQuickTimeString(Mpeg4BoxType.CON);
222+
}
213223
/** @inheritDoc */
214-
public set conductor(v: string) { this.setQuickTimeString(Mpeg4BoxType.COND, v); }
224+
public set conductor(v: string) {
225+
// Clear out existing boxes for conductor (@TODO: Backwards compat for pre-6.0.2 releases)
226+
this.setQuickTimeData(Mpeg4BoxType.COND, undefined);
227+
this.setQuickTimeString(Mpeg4BoxType.CON, v);
228+
}
215229

216230
/** @inheritDoc */
217231
public get copyright(): string { return this.getFirstQuickTimeString(Mpeg4BoxType.CPRT); }

src/mpeg4/mpeg4BoxType.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import {ByteVector, StringType} from "../byteVector";
22

33
/**
4-
* Provides references to different box types used by the library. This class is used to severely reduce the number
5-
* of times these types are created in {@link AppleTag,} greatly improving the speed at which warm files are read.
4+
* Provides references to different box types used by the library. This class is used to severely
5+
* reduce the number of times these types are created in {@link AppleTag,} greatly improving the
6+
* speed at which warm files are read.
7+
*
8+
* These box types were cross-referenced with FFMPEG source and Exiftool database.
69
*/
710
export default class Mpeg4BoxType {
811
/** QuickTime album artist box */
@@ -13,7 +16,15 @@ export default class Mpeg4BoxType {
1316
public static readonly ART = this.getType("©ART");
1417
/** QuickTime comment box */
1518
public static readonly CMT = this.getType("©cmt");
16-
/** QuickTime conductor box? @TODO: Verify this works should not be ©con */
19+
/**
20+
* QuickTime conductor box. This is listed in the FFMPEG source and Exiftool.
21+
*/
22+
public static readonly CON = this.getType("©con");
23+
/**
24+
* Conductor box from original .NET source. This is not listed anywhere in the Exiftool or
25+
* FFMPEG docs.
26+
* @TODO: Remove this when backwards compat time has ended.
27+
*/
1728
public static readonly COND = this.getType("cond");
1829
/** QuickTime cover art box */
1930
public static readonly COVR = this.getType("covr");
@@ -91,19 +102,30 @@ export default class Mpeg4BoxType {
91102
public static readonly STCO = this.getType("stco");
92103
/** ISO sample description box */
93104
public static readonly STSD = this.getType("stsd");
94-
/** Subtitle box? @TODO: There's no record of this one */
105+
/**
106+
* QuickTime subtitle box. This is listed in the FFMPEG source and Exiftool.
107+
*/
108+
public static readonly ST3 = this.getType("©st3");
109+
/**
110+
* Subtitle box from original .NET source. This is not listed anywhere in the Exiftool or
111+
* FFMPEG docs.
112+
* @TODO: Remove this when backwards compat time has ended.
113+
*/
95114
public static readonly SUBT = this.getType("Subt");
96115
/** Alias text box? @TODO: There's no record of this one */
97116
public static readonly TEXT = this.getType("text");
98117
/** QuickTime BPM box */
99118
public static readonly TMPO = this.getType("tmpo");
100119
/** ISO track container box */
101120
public static readonly TRAK = this.getType("trak");
102-
/** QuickTime track number box */
121+
/** QuickTime track number box @TODO: What about ©TRK as per FFMPEG source? */
103122
public static readonly TRKN = this.getType("trkn");
104123
/** ISO User data box */
105124
public static readonly UDTA = this.getType("udta");
106-
/** Alias URL box? @TODO: There's no record of this one */
125+
/**
126+
* Alias URL box?
127+
* @remarks Specified in FFMPEG source but not in Exiftool.
128+
*/
107129
public static readonly URL = this.getType("©url");
108130
/** ISO user extension box */
109131
public static readonly UUID = this.getType("uuid");

test-unit/mpeg4/appleTagTests.ts

Lines changed: 77 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,35 @@ import {TagTypes} from "../../src/tag";
8080
}
8181

8282
@test
83-
public subtitle() {
84-
this.testQuickTimeString((t, v) => t.subtitle = v, (t) => t.subtitle, Mpeg4BoxType.SUBT);
83+
public subtitle_fromEmpty() {
84+
this.testQuickTimeString((t, v) => t.subtitle = v, (t) => t.subtitle, Mpeg4BoxType.ST3);
85+
}
86+
87+
@test
88+
public subtitle_get_existingSubt() {
89+
// Arrange
90+
const box1 = this.getQuickTimeBox(Mpeg4BoxType.SUBT, "foobarbaz");
91+
const testTag = this.getEmptyTag([box1.box]);
92+
93+
// Act / Assert
94+
assert.strictEqual(testTag.tag.subtitle, "foobarbaz");
95+
}
96+
97+
@test
98+
public subtitle_set_existingSubt() {
99+
// Arrange
100+
const box1 = this.getQuickTimeBox(Mpeg4BoxType.SUBT, "foobarbaz");
101+
const testTag = this.getEmptyTag([box1.box]);
102+
103+
// Act
104+
testTag.tag.subtitle = "fuxbuxquxx";
105+
106+
// Assert
107+
const subtBoxes = testTag.ilst.getQuickTimeDataBoxes(Mpeg4BoxType.SUBT);
108+
assert.isEmpty(subtBoxes);
109+
110+
const st3Boxes = testTag.ilst.getQuickTimeDataBoxes(Mpeg4BoxType.ST3);
111+
assert.isNotEmpty(st3Boxes);
85112
}
86113

87114
@test
@@ -169,7 +196,7 @@ import {TagTypes} from "../../src/tag";
169196
ByteVector.fromShort(0),
170197
AppleDataBoxFlagType.ContainsData
171198
);
172-
const box4 = this.getQuickTimeBox(Mpeg4BoxType.GNRE, ByteVector.fromString("foo", StringType.UTF8));
199+
const box4 = this.getQuickTimeBox(Mpeg4BoxType.GNRE, "foo");
173200
const tag = this.getEmptyTag([box3.box, box4.box, box1.box, box2.box]);
174201

175202
// Act
@@ -210,7 +237,7 @@ import {TagTypes} from "../../src/tag";
210237
// Arrange
211238
const box1 = this.getQuickTimeBox(Mpeg4BoxType.GNRE, ByteVector.fromShort(1));
212239
const value2 = "foo; bar; baz";
213-
const box2 = this.getQuickTimeBox(Mpeg4BoxType.GEN, ByteVector.fromString(value2, StringType.UTF8));
240+
const box2 = this.getQuickTimeBox(Mpeg4BoxType.GEN, value2);
214241
const tag = this.getEmptyTag([box1.box, box2.box]);
215242

216243
// Act
@@ -265,9 +292,9 @@ import {TagTypes} from "../../src/tag";
265292
@test
266293
public year_multipleBoxes() {
267294
// Arrange
268-
const box1 = this.getQuickTimeBox(Mpeg4BoxType.DAY, ByteVector.fromString("asdf", StringType.UTF8));
269-
const box2 = this.getQuickTimeBox(Mpeg4BoxType.DAY, ByteVector.fromString("123456", StringType.UTF8));
270-
const box3 = this.getQuickTimeBox(Mpeg4BoxType.DAY, ByteVector.fromString("234", StringType.UTF8));
295+
const box1 = this.getQuickTimeBox(Mpeg4BoxType.DAY, "asdf");
296+
const box2 = this.getQuickTimeBox(Mpeg4BoxType.DAY, "123456");
297+
const box3 = this.getQuickTimeBox(Mpeg4BoxType.DAY, "234");
271298
const testTag = this.getEmptyTag([box1.box, box2.box, box3.box]);
272299

273300
// Act / Assert
@@ -277,7 +304,7 @@ import {TagTypes} from "../../src/tag";
277304
@test
278305
public year_setMultipleBoxes() {
279306
// Arrange
280-
const box1 = this.getQuickTimeBox(Mpeg4BoxType.DAY, ByteVector.fromString("1234", StringType.UTF8));
307+
const box1 = this.getQuickTimeBox(Mpeg4BoxType.DAY, "1234");
281308
const testTag = this.getEmptyTag([box1.box, box1.box, box1.box]);
282309

283310
// Act / Assert
@@ -289,7 +316,7 @@ import {TagTypes} from "../../src/tag";
289316
@test
290317
public year_setZero_clearsMultiple() {
291318
// Arrange
292-
const box1 = this.getQuickTimeBox(Mpeg4BoxType.DAY, ByteVector.fromString("1234", StringType.UTF8));
319+
const box1 = this.getQuickTimeBox(Mpeg4BoxType.DAY, "1234");
293320
const testTag = this.getEmptyTag([box1.box, box1.box, box1.box]);
294321

295322
// Act / Assert
@@ -429,7 +456,34 @@ import {TagTypes} from "../../src/tag";
429456

430457
@test
431458
public conductor() {
432-
this.testQuickTimeString((t, v) => t.conductor = v, (t) => t.conductor, Mpeg4BoxType.COND);
459+
this.testQuickTimeString((t, v) => t.conductor = v, (t) => t.conductor, Mpeg4BoxType.CON);
460+
}
461+
462+
@test
463+
public conductor_get_existingCondBox() {
464+
// Arrange
465+
const box1 = this.getQuickTimeBox(Mpeg4BoxType.COND, "foobarbaz");
466+
const testTag = this.getEmptyTag([box1.box]);
467+
468+
// Act / Assert
469+
assert.strictEqual(testTag.tag.conductor, "foobarbaz");
470+
}
471+
472+
@test
473+
public conductor_set_existingCondBox() {
474+
// Arrange
475+
const box1 = this.getQuickTimeBox(Mpeg4BoxType.COND, "foobarbaz");
476+
const testTag = this.getEmptyTag([box1.box]);
477+
478+
// Act
479+
testTag.tag.conductor = "fuxbuxquxx";
480+
481+
// Assert
482+
const condBoxes = testTag.ilst.getQuickTimeDataBoxes(Mpeg4BoxType.COND);
483+
assert.isEmpty(condBoxes);
484+
485+
const conBoxes = testTag.ilst.getQuickTimeDataBoxes(Mpeg4BoxType.CON);
486+
assert.isNotEmpty(conBoxes);
433487
}
434488

435489
@test
@@ -464,18 +518,9 @@ import {TagTypes} from "../../src/tag";
464518
@test
465519
public dateTagged_multipleBoxes() {
466520
// Arrange
467-
const box1 = this.getQuickTimeBox(
468-
Mpeg4BoxType.DTAG,
469-
ByteVector.fromString("asdf", StringType.UTF8)
470-
);
471-
const box2 = this.getQuickTimeBox(
472-
Mpeg4BoxType.DTAG,
473-
ByteVector.fromString("2023-10-21T10:46:00", StringType.UTF8)
474-
);
475-
const box3 = this.getQuickTimeBox(
476-
Mpeg4BoxType.DTAG,
477-
ByteVector.fromString("2023-10-21", StringType.UTF8)
478-
);
521+
const box1 = this.getQuickTimeBox(Mpeg4BoxType.DTAG, "asdf");
522+
const box2 = this.getQuickTimeBox(Mpeg4BoxType.DTAG, "2023-10-21T10:46:00");
523+
const box3 = this.getQuickTimeBox(Mpeg4BoxType.DTAG, "2023-10-21");
479524
const testTag = this.getEmptyTag([box1.box, box2.box, box3.box]);
480525

481526
// Act / Assert
@@ -485,10 +530,7 @@ import {TagTypes} from "../../src/tag";
485530
@test
486531
public dateTagged_setMultipleBoxes() {
487532
// Arrange
488-
const box1 = this.getQuickTimeBox(
489-
Mpeg4BoxType.DTAG,
490-
ByteVector.fromString("1900-10-21T10:56:00", StringType.UTF8)
491-
);
533+
const box1 = this.getQuickTimeBox(Mpeg4BoxType.DTAG, "1900-10-21T10:56:00");
492534
const testTag = this.getEmptyTag([box1.box, box1.box, box1.box]);
493535
const testValue = new Date("2023-10-21 10:59:00");
494536

@@ -504,10 +546,7 @@ import {TagTypes} from "../../src/tag";
504546
@test
505547
public dateTagged_setZero_clearsMultiple() {
506548
// Arrange
507-
const box1 = this.getQuickTimeBox(
508-
Mpeg4BoxType.DTAG,
509-
ByteVector.fromString("2021-10-21T11:04:00", StringType.UTF8)
510-
);
549+
const box1 = this.getQuickTimeBox(Mpeg4BoxType.DTAG, "2021-10-21T11:04:00");
511550
const testTag = this.getEmptyTag([box1.box, box1.box, box1.box]);
512551

513552
// Act / Assert
@@ -1187,9 +1226,9 @@ import {TagTypes} from "../../src/tag";
11871226

11881227
// TEST CASE 3: Multiple boxes return all valid instances ----------
11891228
// Valid box type and flags
1190-
const box1 = this.getQuickTimeBox(boxType, ByteVector.fromString("foo", StringType.UTF8));
1229+
const box1 = this.getQuickTimeBox(boxType, "foo");
11911230
// Valid box type and flags
1192-
const box2 = this.getQuickTimeBox(boxType, ByteVector.fromString("bar", StringType.UTF8));
1231+
const box2 = this.getQuickTimeBox(boxType, "bar");
11931232
// Valid box type, invalid flags
11941233
const box3 = this.getQuickTimeBox(
11951234
boxType,
@@ -1202,7 +1241,7 @@ import {TagTypes} from "../../src/tag";
12021241
box4.addChild(box2.dataBox);
12031242
box4.addChild(box3.dataBox);
12041243
// Multiple values in single data box
1205-
const box5 = this.getQuickTimeBox(boxType, ByteVector.fromString("fux; bux; quxx", StringType.UTF8));
1244+
const box5 = this.getQuickTimeBox(boxType, "fux; bux; quxx");
12061245

12071246
const testTag2 = this.getEmptyTag([box1.box, box2.box, box3.box, box4, box5.box]);
12081247

@@ -1454,10 +1493,14 @@ import {TagTypes} from "../../src/tag";
14541493

14551494
private getQuickTimeBox(
14561495
boxType: ByteVector,
1457-
value: ByteVector,
1496+
value: ByteVector|string,
14581497
flags: AppleDataBoxFlagType = AppleDataBoxFlagType.ContainsText
14591498
): {box: AppleAnnotationBox, dataBox: AppleDataBox} {
1499+
value = typeof(value) === "string"
1500+
? ByteVector.fromString(value, StringType.UTF8)
1501+
: value;
14601502
const dataBox = AppleDataBox.fromDataAndFlags(value, flags);
1503+
14611504
const box = AppleAnnotationBox.fromType(boxType);
14621505
box.addChild(dataBox);
14631506

0 commit comments

Comments
 (0)