Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add metadata writers #26

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ It's still in development and there's some metadat format that I could implement

## Usage

### Read

```dart
import 'dart:io';

Expand All @@ -28,6 +30,32 @@ void main() {
}
```

### Write

This use case is a bit more complicated. You have to manipulate the raw metadata and update the good field.

```dart
void main() {
final fullMetadata = readAllMetadata(track);

switch (fullMetadata) {
case Mp3Metadata m:
m.songName = "New title";
break;
case Mp4Metadata m:
m.title = "New title";
break;
case VorbisMetadata m:
m.title = ["New title"];
break;
}

writeMetadata(track, fullMetadata);
}
```

Also, all the ID3v2 metadata will be written in the minor version 4.

## Performance

By running the following code on my laptop with a SSD, it ables to get the metadata of 3392 tracks in less than 200ms (if we don't fetch the covers). With the covers, about 400ms.
Expand Down
24 changes: 22 additions & 2 deletions example/main.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,35 @@
import 'dart:io';

import 'package:audio_metadata_reader/audio_metadata_reader.dart';
import 'package:audio_metadata_reader/src/metadata/base.dart';
import 'package:audio_metadata_reader/src/writer.dart';

Future<void> main() async {
void main() {
final track = File("Pieces.mp3");

// Getting the image of a track can be heavy and slow the reading
final metadata = await readMetadata(track, getImage: false);
final metadata = readMetadata(track, getImage: false);

print(metadata.title);
print(metadata.album);
print(metadata.duration);
// etc...

print("Now we are going to rewrite the metadata");

final fullMetadata = readAllMetadata(track);

switch (fullMetadata) {
case Mp3Metadata m:
m.songName = "New title";
break;
case Mp4Metadata m:
m.title = "New title";
break;
case VorbisMetadata m:
m.title = ["New title"];
break;
}

writeMetadata(track, fullMetadata);
}
20 changes: 20 additions & 0 deletions lib/audio_metadata_reader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,26 @@ library audio_metadata_reader;

export "src/parsers/tag_parser.dart" show AudioMetadata, Picture;
export 'src/metadata/base.dart' show PictureType;
export 'src/utils/metadata_parser_exception.dart' show MetadataParserException;
export 'src/parser.dart' show readMetadata, readAllMetadata;

export 'src/parsers/id3v1.dart' show ID3v1Parser;
export 'src/parsers/id3v2.dart' show ID3v2Parser;
export 'src/parsers/flac.dart' show FlacParser;
export 'src/parsers/mp4.dart' show MP4Parser;
export 'src/parsers/ogg.dart' show OGGParser;

export 'src/writers/id3v4_writer.dart' show Id3v4Writer;
export 'src/utils/metadata_parser_exception.dart'
show MetadataParserException, NoMetadataParserException;
export 'src/parser.dart' show readMetadata;
export 'src/utils/metadata_parser_exception.dart' show MetadataParserException;
export 'src/parser.dart' show readMetadata, readAllMetadata;

export 'src/parsers/id3v1.dart' show ID3v1Parser;
export 'src/parsers/id3v2.dart' show ID3v2Parser;
export 'src/parsers/flac.dart' show FlacParser;
export 'src/parsers/mp4.dart' show MP4Parser;
export 'src/parsers/ogg.dart' show OGGParser;

export 'src/writers/id3v4_writer.dart' show Id3v4Writer;
11 changes: 11 additions & 0 deletions lib/src/metadata/base.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
import 'dart:collection';
import 'dart:typed_data';

import 'package:audio_metadata_reader/audio_metadata_reader.dart';

part 'mp3_metadata.dart';
part 'mp4_metadata.dart';
part 'vorbis_metadata.dart';

sealed class ParserTag {}

enum PictureType {
other,
fileIcon32x32,
Expand Down
4 changes: 1 addition & 3 deletions lib/src/metadata/mp3_metadata.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// https://teslabs.com/openplayer/docs/docs/specs/id3v2.3.0%20-%20ID3.org.pdf

import 'dart:typed_data';

import '../parsers/tag_parser.dart';
part of 'base.dart';

class Mp3Metadata extends ParserTag {
String? album; // TALB
Expand Down
2 changes: 1 addition & 1 deletion lib/src/metadata/mp4_metadata.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'package:audio_metadata_reader/src/parsers/tag_parser.dart';
part of 'base.dart';

class Mp4Metadata extends ParserTag {
String? title;
Expand Down
4 changes: 1 addition & 3 deletions lib/src/metadata/vorbis_metadata.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import 'dart:collection';

import 'package:audio_metadata_reader/src/parsers/tag_parser.dart';
part of 'base.dart';

// https://xiph.org/vorbis/doc/v-comment.html
// https://exiftool.org/TagNames/Vorbis.html
Expand Down
49 changes: 40 additions & 9 deletions lib/src/parser.dart
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import 'dart:io';

import 'package:audio_metadata_reader/audio_metadata_reader.dart';
import 'package:audio_metadata_reader/src/metadata/mp3_metadata.dart';
import 'package:audio_metadata_reader/src/metadata/mp4_metadata.dart';
import 'package:audio_metadata_reader/src/metadata/vorbis_metadata.dart';
import 'package:audio_metadata_reader/src/parsers/id3v1.dart';
import 'package:audio_metadata_reader/src/parsers/id3v2.dart';
import 'package:audio_metadata_reader/src/parsers/mp4.dart';
import 'package:audio_metadata_reader/src/parsers/ogg.dart';
import 'package:audio_metadata_reader/src/parsers/flac.dart';
import 'package:audio_metadata_reader/src/metadata/base.dart';

/// Parse the metadata of a file.
///
/// It automatically detects the type of the file (`.mp3`, `.ogg`, `.flac` etc)
/// and select the matching file parser.
///
/// If there's no parser or an error, a `InvalidTag` instance should be returned.
/// If there's no parser or an error, a `MetadataParserException` exception is raised.
///
/// By default, it does not fetch the images of the file. Because some
/// images/covers are sometimes huge (~5MB), it can drastically make the
Expand Down Expand Up @@ -179,3 +172,41 @@ AudioMetadata readMetadata(File track, {bool getImage = false}) {
track: track,
);
}

/// Parse the metadata of a file and return ALL the metadata. It contains more information
/// than `readMetadata(...)` but you need more knowledges about the metadata specifications.
///
/// Use this one if you want to rewrite the metadata later.
///
/// It automatically detects the type of the file (`.mp3`, `.ogg`, `.flac` etc)
/// and select the matching file parser.
///
/// If there's no parser or an error, a `MetadataParserException` exception is raised.
///
/// By default, it fetches the cover images
ParserTag readAllMetadata(File track, {bool getImage = true}) {
final reader = track.openSync();

try {
if (ID3v2Parser.canUserParser(reader)) {
return ID3v2Parser(fetchImage: getImage).parse(reader);
} else if (FlacParser.canUserParser(reader)) {
return FlacParser(fetchImage: getImage).parse(reader);
} else if (MP4Parser.canUserParser(reader)) {
return MP4Parser(fetchImage: getImage).parse(reader);
} else if (OGGParser.canUserParser(reader)) {
return OGGParser(fetchImage: getImage).parse(reader);
} else if (ID3v2Parser.isID3v1(reader)) {
return ID3v1Parser().parse(reader);
}
} catch (e, trace) {
print(trace);
throw MetadataParserException(track: track, message: e.toString());
}

throw MetadataParserException(
track: track,
message:
"No available parser for this file. Please raise an issue in Github",
);
}
1 change: 0 additions & 1 deletion lib/src/parsers/flac.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import 'dart:io';
import 'dart:typed_data';

import 'package:audio_metadata_reader/src/metadata/base.dart';
import 'package:audio_metadata_reader/src/metadata/vorbis_metadata.dart';
import 'package:audio_metadata_reader/src/parsers/tag_parser.dart';
import 'package:audio_metadata_reader/src/utils/buffer.dart';

Expand Down
2 changes: 1 addition & 1 deletion lib/src/parsers/id3v1.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:audio_metadata_reader/src/metadata/mp3_metadata.dart';
import 'package:audio_metadata_reader/src/metadata/base.dart';
import 'package:audio_metadata_reader/src/parsers/tag_parser.dart';
import 'package:audio_metadata_reader/src/utils/bit_manipulator.dart';
import 'package:audio_metadata_reader/src/utils/buffer.dart';
Expand Down
1 change: 0 additions & 1 deletion lib/src/parsers/id3v2.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import 'dart:io';
import 'dart:typed_data';
import 'package:audio_metadata_reader/src/constants/id3_genres.dart';
import 'package:audio_metadata_reader/src/metadata/base.dart';
import 'package:audio_metadata_reader/src/metadata/mp3_metadata.dart';
import 'package:audio_metadata_reader/src/utils/bit_manipulator.dart';
import 'package:audio_metadata_reader/src/utils/buffer.dart';
import 'package:charset/charset.dart';
Expand Down
2 changes: 1 addition & 1 deletion lib/src/parsers/mp4.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:audio_metadata_reader/src/metadata/mp4_metadata.dart';
import 'package:audio_metadata_reader/src/metadata/base.dart';
import 'package:audio_metadata_reader/src/parsers/tag_parser.dart';
import 'package:mime/mime.dart';

Expand Down
2 changes: 1 addition & 1 deletion lib/src/parsers/ogg.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'dart:io';
import 'dart:typed_data';

import 'package:audio_metadata_reader/src/metadata/vorbis_metadata.dart';
import 'package:audio_metadata_reader/src/metadata/base.dart';
import 'package:audio_metadata_reader/src/parsers/tag_parser.dart';
import 'package:audio_metadata_reader/src/parsers/vorbis_comment.dart';
import 'package:audio_metadata_reader/src/utils/bit_manipulator.dart';
Expand Down
3 changes: 0 additions & 3 deletions lib/src/parsers/tag_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ class Picture {
}
}

/// An abstract class to represent the result of a parser
abstract class ParserTag {}

/// A generic class to gather the metadata. To make it universal, some format-specific metadata
/// are dropped.
///
Expand Down
1 change: 0 additions & 1 deletion lib/src/parsers/vorbis_comment.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import 'dart:convert';
import 'dart:typed_data';

import 'package:audio_metadata_reader/src/metadata/base.dart';
import 'package:audio_metadata_reader/src/metadata/vorbis_metadata.dart';
import 'package:audio_metadata_reader/src/parsers/tag_parser.dart';

void parseVorbisComment(
Expand Down
29 changes: 29 additions & 0 deletions lib/src/utils/pad_bit.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
extension A on List<int> {
List<int> padBitLeft(int desiredLength, int paddingBit) {
if (length >= desiredLength) {
return this; // No padding needed
}

if (paddingBit != 0 && paddingBit != 1) {
throw ArgumentError("Padding bit must be 0 or 1.");
}

int paddingCount = desiredLength - length;
List<int> padding = List.filled(paddingCount, paddingBit);
return [...padding, ...this];
}

List<int> padBitRight(int desiredLength, int paddingBit) {
if (length >= desiredLength) {
return this; // No padding needed
}

if (paddingBit != 0 && paddingBit != 1) {
throw ArgumentError("Padding bit must be 0 or 1.");
}

int paddingCount = desiredLength - length;
List<int> padding = List.filled(paddingCount, paddingBit);
return [...this, ...padding];
}
}
12 changes: 12 additions & 0 deletions lib/src/writer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'dart:io';

import 'package:audio_metadata_reader/audio_metadata_reader.dart';
import 'package:audio_metadata_reader/src/metadata/base.dart';

void writeMetadata(File track, ParserTag metadata) {
final reader = track.openSync();

if (ID3v2Parser.canUserParser(reader)) {
Id3v4Writer().write(track, metadata as Mp3Metadata);
}
}
Loading
Loading