From d631e8b4939425c992bd62bb1a2612a733c8ffcd Mon Sep 17 00:00:00 2001 From: Paulus Fitzek Date: Wed, 15 May 2024 20:38:01 +0200 Subject: [PATCH 1/4] Fix mp4 precision of duration not accurate --- lib/src/parsers/mp4.dart | 2 +- test/mp4/mp4_test.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/parsers/mp4.dart b/lib/src/parsers/mp4.dart index fd151cd..a805469 100644 --- a/lib/src/parsers/mp4.dart +++ b/lib/src/parsers/mp4.dart @@ -125,7 +125,7 @@ class MP4Parser extends TagParser { final timeScale = getUint32(bytes.sublist(12, 16)); final timeUnit = getUint32(bytes.sublist(16, 20)); - tags.duration = Duration(seconds: timeUnit ~/ timeScale); + tags.duration = Duration(microseconds: timeUnit * timeScale); } else if (box.type == "udta") { await parseRecurvise(reader, box); } else if (box.type == "ilst") { diff --git a/test/mp4/mp4_test.dart b/test/mp4/mp4_test.dart index 9db889a..bff2239 100644 --- a/test/mp4/mp4_test.dart +++ b/test/mp4/mp4_test.dart @@ -16,7 +16,7 @@ void main() { // expect(result.bitrate, equals(48000)); expect(result.title, equals("Title")); expect(result.trackNumber, equals(1)); - expect(result.duration, equals(Duration(seconds: 1))); + expect(result.duration, equals(Duration(microseconds: 1021333))); expect(result.totalDisc, equals(1)); expect(result.lyrics, equals("Lyrics")); expect(result.trackTotal, equals(10)); From ba3cc14a8e47baf96fd280f46aa4b111bfed58a7 Mon Sep 17 00:00:00 2001 From: Paulus Fitzek Date: Thu, 16 May 2024 12:00:41 +0200 Subject: [PATCH 2/4] Fix mp4 precision of duration not accurate --- lib/src/parsers/mp4.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/parsers/mp4.dart b/lib/src/parsers/mp4.dart index a805469..3c0b31f 100644 --- a/lib/src/parsers/mp4.dart +++ b/lib/src/parsers/mp4.dart @@ -125,7 +125,8 @@ class MP4Parser extends TagParser { final timeScale = getUint32(bytes.sublist(12, 16)); final timeUnit = getUint32(bytes.sublist(16, 20)); - tags.duration = Duration(microseconds: timeUnit * timeScale); + double microseconds = (timeUnit / timeScale) * 1000000; + tags.duration = Duration(microseconds: microseconds.toInt()); } else if (box.type == "udta") { await parseRecurvise(reader, box); } else if (box.type == "ilst") { From 155f5e0d15bdfcbe321ac01fef6654dc4b931054 Mon Sep 17 00:00:00 2001 From: Paulus Fitzek Date: Wed, 22 May 2024 14:46:16 +0200 Subject: [PATCH 3/4] Fix parsing of ftypmp42 formats --- lib/src/parsers/mp4.dart | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/src/parsers/mp4.dart b/lib/src/parsers/mp4.dart index 3c0b31f..d4fe489 100644 --- a/lib/src/parsers/mp4.dart +++ b/lib/src/parsers/mp4.dart @@ -20,6 +20,9 @@ class BoxHeader { String type; BoxHeader(this.size, this.type); + + @override + toString() => 'size: ${size}, type: ${type}'; } final supportedBox = [ @@ -82,9 +85,13 @@ class MP4Parser extends TagParser { if (supportedBox.contains(box.type)) { await processBox(reader, box); - } else { - // We substract 8 to the box size because we already read the data for + } else if (box.type == "ftyp"){ + // read 4 bytes brand name + reader.readSync(4); + // We substract 4 to the box size because we already read the data for // the box header + reader.setPositionSync(reader.positionSync() + box.size - 4); + } else { reader.setPositionSync(reader.positionSync() + box.size - 8); } } @@ -126,7 +133,7 @@ class MP4Parser extends TagParser { final timeUnit = getUint32(bytes.sublist(16, 20)); double microseconds = (timeUnit / timeScale) * 1000000; - tags.duration = Duration(microseconds: microseconds.toInt()); + tags.duration = Duration(microseconds: microseconds.toInt()); } else if (box.type == "udta") { await parseRecurvise(reader, box); } else if (box.type == "ilst") { @@ -134,8 +141,6 @@ class MP4Parser extends TagParser { } else if (["trak", "mdia", "minf", "stbl", "stsd"].contains(box.type)) { await parseRecurvise(reader, box); } else if (box.type == "meta") { - reader.readSync(4); - await parseRecurvise(reader, box); } else if (box.type[0] == "©" || ["gnre", "trkn", "disk", "tmpo", "cpil", "too", "covr", "pgap", "gen"] @@ -199,8 +204,7 @@ class MP4Parser extends TagParser { final name = await _readBox(reader); - final nameValue = - String.fromCharCodes(reader.readSync(name.size - 8).sublist(4)); + final nameValue = String.fromCharCodes(reader.readSync(name.size - 8).sublist(4)); final dataBox = await _readBox(reader); final data = reader.readSync(dataBox.size - 8); final finalValue = String.fromCharCodes(data.sublist(8)); @@ -234,6 +238,7 @@ class MP4Parser extends TagParser { // the `meta` box has 4 additional bytes that are not useful. We skip them if ("meta" == box.type) { offset += 4; + reader.readSync(4); } else if (box.type == "stsd") { offset += 8; reader.readSync(8); From 12e7adb7a227263947f52b534b0e8e77ea5b2114 Mon Sep 17 00:00:00 2001 From: Paulus Fitzek Date: Wed, 22 May 2024 14:56:03 +0200 Subject: [PATCH 4/4] Fix parsing of ftypmp42 formats --- test/mp4/mp4_test.dart | 6 ++++++ test/mp4/track_ftypmp42.m4a | Bin 0 -> 15439 bytes 2 files changed, 6 insertions(+) create mode 100644 test/mp4/track_ftypmp42.m4a diff --git a/test/mp4/mp4_test.dart b/test/mp4/mp4_test.dart index bff2239..b7a8e7b 100644 --- a/test/mp4/mp4_test.dart +++ b/test/mp4/mp4_test.dart @@ -33,4 +33,10 @@ void main() { expect(result.pictures.first.bytes, File("test/data/cover.png").readAsBytesSync()); }); + test("Parse MP4 file with ftypmp42 container without the cover", () async { + final track = File('./test/mp4/track_ftypmp42.m4a'); + final result = await readMetadata(track, getImage: false); + + expect(result.duration, equals(3392), reason: 'Wrong: Audio duration'); + }); } diff --git a/test/mp4/track_ftypmp42.m4a b/test/mp4/track_ftypmp42.m4a new file mode 100644 index 0000000000000000000000000000000000000000..1633b4dfa7db08fb760428807ba2a2e7ce0c1b68 GIT binary patch literal 15439 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