Skip to content

Commit

Permalink
Fixed incorrect parsing of tag size field - fixes #1.
Browse files Browse the repository at this point in the history
Added unit tests.
  • Loading branch information
tolo committed Jul 25, 2022
1 parent 8ab3ebd commit 2b07657
Show file tree
Hide file tree
Showing 12 changed files with 99 additions and 50 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
## 0.1.0
#Changes

##Version 0.2.0

### Bug fixes
* Fixed incorrect parsing of tag size field (resolves [issue 1](https://github.com/tolo/id3tag/issues/1), reported by @VirtualAstronaut).

##Version 0.1.0

* Support for parsing ID3 v2.3 and above
* Basic structure in place, with some inspiration from [OutcastID3](https://github.com/CrunchyBagel/OutcastID3)
Expand Down
26 changes: 17 additions & 9 deletions lib/src/extensions/byte_list_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,35 @@ import '../exceptions.dart';

extension ID3ByteList on List<int> {

int parseInt() {
int _parseInt({required bool syncSafe}) {
int value;
if (length == 4) {
value = this[0] << 24;
value += this[1] << 16;
value += this[2] << 8;
value += this[3];
if (syncSafe) {
value = (this[0] & 0x7f) << 21;
value += (this[1] & 0x7f) << 14;
value += (this[2] & 0x7f) << 7;
value += (this[3] & 0x7f);
} else {
value = this[0] << 24;
value += this[1] << 16;
value += this[2] << 8;
value += this[3];
}
} else {
throw ID3ParserException("Unable to parse int value from byte array of length $length");
}

return value;
}

int parseInt(int id3MinorVersion) {
return _parseInt(syncSafe: id3MinorVersion >= 4);
}

int parseID3FileHeaderSize() {
int size;
if (length == 4) {
size = (this[0] & 0x7f) << 21;
size += (this[1] & 0x7f) << 14;
size += (this[2] & 0x7f) << 7;
size += (this[3] & 0x7f);
size = _parseInt(syncSafe: true);
} else if (length == 3) {
size = this[0] << 16;
size += this[1] << 8;
Expand Down
7 changes: 7 additions & 0 deletions lib/src/extensions/iterable_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,11 @@ extension IterableExtensions<E> on Iterable<E> {

E? lastIfAny({minLength = 1}) => length >= minLength ? last : null;

E? firstWhereOrNull(bool Function(E element) test) {
try {
return firstWhere(test);
} catch(e) {
return null;
}
}
}
5 changes: 3 additions & 2 deletions lib/src/frame_body.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import 'exceptions.dart';

class FrameBody {
final List<int> buffer;
final int tagMinorVersion;
int pos = 0;
int lastEncoding = 0x00; // default to latin1

FrameBody(this.buffer);
FrameBody(this.buffer, this.tagMinorVersion);

List<int> readUntilTerminator(List<int> terminator, {bool aligned = false, bool terminatorMandatory = true}) {
if (remainingBytes == 0) {
Expand Down Expand Up @@ -131,7 +132,7 @@ class FrameBody {
}

int readInt() {
return readBytes(4).parseInt();
return readBytes(4).parseInt(tagMinorVersion);
}

List<int> readRemainingBytes() {
Expand Down
32 changes: 18 additions & 14 deletions lib/src/id3_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ class ID3Parser implements ID3TagReader {

@override bool get id3TagFound => id3FileHeader.id3TagSize > 0;

final _ID3FileHeader id3FileHeader;
int get id3CompleteTagSize => id3FileHeaderLength + _id3TagBytes.length;

final ID3FileHeader id3FileHeader;

final List<int> _id3TagBytes;
final int _initialOffset;
Expand All @@ -49,26 +51,26 @@ class ID3Parser implements ID3TagReader {

try {
final length = _reader.lengthSync();
final List<int> fileHeaderBytes = length > 10 ? _reader.readSync(id3FileHeaderLength) : [];
final List<int> fileHeaderBytes = length > id3FileHeaderLength ? _reader.readSync(id3FileHeaderLength) : [];
// Assume ID3 tag is at beginning of file
final List<int>? id3FileTag = fileHeaderBytes.length == 10 ? fileHeaderBytes.sublist(0, 3) : null;
final List<int>? id3FileTag = fileHeaderBytes.length == id3FileHeaderLength ? fileHeaderBytes.sublist(0, 3) : null;

if (id3FileTag != null && latin1.decode(id3FileTag) == 'ID3') {
final headerBytes = fileHeaderBytes.toList();
final header = _ID3FileHeader.fromHeaderBytes(headerBytes);
final header = ID3FileHeader.fromHeaderBytes(headerBytes);

final id3TagBytes = _reader.readSync(header.id3TagSize);

int initialOffset = 0;
if (header.hasExtendedHeader) {
final extendedHeaderSize = id3TagBytes.sublist(0, 4).parseInt();
final extendedHeaderSize = id3TagBytes.sublist(0, 4).parseInt(header.tagMinorVersion);
initialOffset = extendedHeaderSize + 4; // Size doesn't include size itself
}

return ID3Parser._raw(frameParsers: frameParsers ?? ID3Parser._defaultParsers, id3FileHeader: header,
id3TagBytes: id3TagBytes, initialOffset: initialOffset);
} else {
return ID3Parser._raw(frameParsers: {}, id3FileHeader: _ID3FileHeader.empty(),
return ID3Parser._raw(frameParsers: {}, id3FileHeader: ID3FileHeader.empty(),
id3TagBytes: [], initialOffset: 0);
}
} finally {
Expand Down Expand Up @@ -122,8 +124,10 @@ class ID3Parser implements ID3TagReader {
return null;
}

int frameContentSize = frameHeader.sublist(_frameNameLength, _frameNameLength + _frameSizeLength).parseInt();
final List<int> frameContent = buffer.sublist(offset + _frameHeaderLength, offset + _frameHeaderLength + frameContentSize);
int frameContentSize = frameHeader.sublist(_frameNameLength, _frameNameLength + _frameSizeLength)
.parseInt(id3FileHeader.tagMinorVersion);
final List<int> frameContent = buffer
.sublist(offset + _frameHeaderLength, offset + _frameHeaderLength + frameContentSize);

return RawFrame(this, frameName, _frameHeaderLength + frameContentSize, frameContent);
}
Expand All @@ -147,7 +151,7 @@ class ID3Parser implements ID3TagReader {
}


class _ID3FileHeader {
class ID3FileHeader {
final String id3TagVersion;
/// The ID3 minor version, i.e. the X in 2.X.0
final int tagMinorVersion;
Expand All @@ -163,10 +167,10 @@ class _ID3FileHeader {
final bool hasExtendedHeader;


_ID3FileHeader.raw({required this.id3TagVersion, required this.tagMinorVersion, required this.id3TagSize,
ID3FileHeader.raw({required this.id3TagVersion, required this.tagMinorVersion, required this.id3TagSize,
required this.hasExtendedHeader});

factory _ID3FileHeader.fromHeaderBytes(List<int> headerBytes) {
factory ID3FileHeader.fromHeaderBytes(List<int> headerBytes) {
final int tagMinorVersion = headerBytes[3];
final int tagMicroVersion = headerBytes[4];
final int flag = headerBytes[5];
Expand All @@ -177,15 +181,15 @@ class _ID3FileHeader {

final id3TagSize = headerBytes.sublist(6, 10).parseID3FileHeaderSize();

return _ID3FileHeader.raw(
return ID3FileHeader.raw(
id3TagVersion: '2.$tagMinorVersion.$tagMicroVersion',
tagMinorVersion: tagMinorVersion,
id3TagSize: id3TagSize,
hasExtendedHeader: hasExtendedHeader,
);
}

factory _ID3FileHeader.empty() {
return _ID3FileHeader.raw(id3TagVersion: '-', tagMinorVersion: 0, id3TagSize: 0, hasExtendedHeader: false);
factory ID3FileHeader.empty() {
return ID3FileHeader.raw(id3TagVersion: '-', tagMinorVersion: 0, id3TagSize: 0, hasExtendedHeader: false);
}
}
2 changes: 0 additions & 2 deletions lib/src/id3_tag.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,3 @@ class ID3Tag {
return framesWithTypeAndName<T>(name).firstIfAny();
}
}


2 changes: 1 addition & 1 deletion lib/src/raw_frame.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class RawFrame {
final FrameBody frameContent;

RawFrame(this.id3Parser, this.frameName, this.frameSize, List<int> frameContentBytes) :
frameContent = FrameBody(frameContentBytes);
frameContent = FrameBody(frameContentBytes, id3Parser.id3FileHeader.tagMinorVersion);


RawFrame? parseRawSubFrame(List<int> buffer, {int offset = 0}) {
Expand Down
23 changes: 8 additions & 15 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ packages:
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
version: "31.0.0"
version: "40.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.0"
version: "4.1.0"
args:
dependency: transitive
description:
Expand Down Expand Up @@ -43,13 +43,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
cli_util:
dependency: transitive
description:
name: cli_util
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.5"
collection:
dependency: transitive
description:
Expand Down Expand Up @@ -91,7 +84,7 @@ packages:
name: flutter_lints
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
version: "2.0.1"
frontend_server_client:
dependency: transitive
description:
Expand Down Expand Up @@ -140,7 +133,7 @@ packages:
name: lints
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
version: "2.0.0"
logging:
dependency: transitive
description:
Expand Down Expand Up @@ -287,21 +280,21 @@ packages:
name: test
url: "https://pub.dartlang.org"
source: hosted
version: "1.19.5"
version: "1.21.4"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.8"
version: "0.4.12"
test_core:
dependency: transitive
description:
name: test_core
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.9"
version: "0.4.16"
typed_data:
dependency: transitive
description:
Expand Down Expand Up @@ -345,4 +338,4 @@ packages:
source: hosted
version: "3.1.0"
sdks:
dart: ">=2.14.0 <3.0.0"
dart: ">=2.17.0-206.0.dev <3.0.0"
4 changes: 2 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ environment:
sdk: ">=2.13.0 <3.0.0"

dev_dependencies:
flutter_lints: ^1.0.4
test: ^1.17.10
flutter_lints: ^2.0.1
test: ^1.21.4
Binary file added test/apic.mp3
Binary file not shown.
Binary file added test/chap.mp3
Binary file not shown.
39 changes: 35 additions & 4 deletions test/id3frames_test.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,38 @@
//import 'package:id3tag/id3tag.dart';
import 'dart:io';
import 'package:test/test.dart';

import 'package:id3tag/src/id3_parser.dart';
import 'package:id3tag/src/extensions/iterable_extension.dart';


void main() {
// test('TODO', () {
//
// });
/*test('Truncate file to ID3 tag size', () {
final file = File('test/truncate.mp3');
final parser = ID3Parser(file);
final fileID3TagSize = parser.id3CompleteTagSize;
final RandomAccessFile raf = file.openSync(mode: FileMode.append);
raf.truncateSync(fileID3TagSize);
raf.flushSync();
raf.closeSync();
});*/

test('Tag should contain chapters', () {
final parser = ID3Parser(File('test/chap.mp3'));
final tag = parser.readTagSync();
// print('ID3 tag found: ${tag.tagFound} - version: ${tag.tagVersion}');
// print('Title: ${tag.title}');
// print('Duration: ${tag.duration}');
// print('Chapters: ${tag.chapters}');
// print('frameDictionaries: ${tag.frameDictionaries}');
final frame = tag.frames.firstWhereOrNull((f) => f.frameName == 'CHAP');
expect(frame, isNotNull);
});

test('Tag should contain picture', () {
final parser = ID3Parser(File('test/apic.mp3'));
final tag = parser.readTagSync();
final frame = tag.frames.firstWhereOrNull((f) => f.frameName == 'APIC');
expect(frame, isNotNull);
});
}

0 comments on commit 2b07657

Please sign in to comment.