From 0f20c60dd0509d91b488c2c10edf7498feeed560 Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Fri, 24 May 2013 23:03:17 +0200 Subject: [PATCH] Split metadata into format and content metadata. --- .../sonitus/data/ContentMetadata.java | 208 +++++++++++++++++++++ .../pterodactylus/sonitus/data/FormatMetadata.java | 162 ++++++++++++++++ .../net/pterodactylus/sonitus/data/Metadata.java | 122 +++++------- .../sonitus/data/source/FileSource.java | 5 +- .../pterodactylus/sonitus/io/Mp3Identifier.java | 9 +- .../sonitus/io/OggVorbisIdentifier.java | 11 +- 6 files changed, 433 insertions(+), 84 deletions(-) create mode 100644 src/main/java/net/pterodactylus/sonitus/data/ContentMetadata.java create mode 100644 src/main/java/net/pterodactylus/sonitus/data/FormatMetadata.java diff --git a/src/main/java/net/pterodactylus/sonitus/data/ContentMetadata.java b/src/main/java/net/pterodactylus/sonitus/data/ContentMetadata.java new file mode 100644 index 0000000..cdfc4d2 --- /dev/null +++ b/src/main/java/net/pterodactylus/sonitus/data/ContentMetadata.java @@ -0,0 +1,208 @@ +/* + * Sonitus - ContentMetadata.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sonitus.data; + +import java.util.Arrays; + +import com.google.common.base.Joiner; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; + +/** + * The part of the {@link Metadata} that contains information about the content + * of a {@link Source}, such as the name of the track, the artist, or other + * information. + *

+ * Content metadata also contains a “title” which is an amalgamation of all + * information in the content metadata. If not given, it will be automatically + * constructed from all other information. If can also be specified manually to + * override the default. + * + * @author David ‘Bombe’ Roden + */ +public class ContentMetadata { + + /** The artist. */ + private final Optional artist; + + /** The name. */ + private final Optional name; + + /** The all-in-one title. */ + private final String title; + + /** Creates empty content metadata. */ + public ContentMetadata() { + this(""); + } + + /** + * Creates content metadata containing the given title. + * + * @param title + * The title of the metadata + * @throws NullPointerException + * if {@code title} is {@code null} + */ + public ContentMetadata(String title) throws NullPointerException { + this(null, null, title); + } + + /** + * Creates content metadata. + * + * @param artist + * The artist of the track + * @param name + * The name of the track + */ + public ContentMetadata(String artist, String name) { + this(artist, name, joinStrings(artist, name)); + } + + /** + * Creates content metadata. + * + * @param artist + * The artist of the track (may be null) + * @param name + * The name of the track (may be null) + * @param title + * The title of the track + * @throws NullPointerException + * if {@code title} is {@code null} + */ + private ContentMetadata(String artist, String name, String title) throws NullPointerException { + this.artist = Optional.fromNullable(artist); + this.name = Optional.fromNullable(name); + this.title = Preconditions.checkNotNull(title, "title must not be null"); + } + + // + // ACCESSORS + // + + /** + * Returns the artist of the track, if it has been set. + * + * @return The artist of the track + */ + public Optional artist() { + return artist; + } + + /** + * Returns the name of the track, if it has been set. + * + * @return The name of the track + */ + public Optional name() { + return name; + } + + /** + * Returns the title of the track. + * + * @return The title of the track + */ + public String title() { + return title; + } + + // + // ACTIONS + // + + /** + * Creates new content metadata that is a copy of this content metadata but + * with the artist changed. The title will be reconstructed from the new artist + * and the existing name. + * + * @param artist + * The new artist + * @return The new content metadata + */ + public ContentMetadata artist(String artist) { + return new ContentMetadata(artist, name().orNull(), joinStrings(artist, name().orNull())); + } + + /** + * Creates new content metadata that is a copy of this content metadata but + * with the name changed. The title will be reconstructed from the existing + * artist and the new name. + * + * @param name + * The new name + * @return The new content metadata + */ + public ContentMetadata name(String name) { + return new ContentMetadata(artist().orNull(), name, joinStrings(artist().orNull(), name)); + } + + /** + * Creates new content metadata that is a copy of this content metadata but + * with the title changed. + * + * @param title + * The new title + * @return The new content metadata + */ + public ContentMetadata title(String title) { + return new ContentMetadata(artist().orNull(), name().orNull(), title); + } + + // + // OBJECT METHODS + // + + @Override + public int hashCode() { + return artist().hashCode() ^ name().hashCode() ^ title().hashCode(); + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof ContentMetadata)) { + return false; + } + ContentMetadata contentMetadata = (ContentMetadata) object; + return artist().equals(contentMetadata.artist()) && name().equals(contentMetadata.name()) && title().equals(contentMetadata.title()); + } + + @Override + public String toString() { + return title; + } + + // + // STATIC METHODS + // + + /** + * Joins the given strings, concatenating them with “ - ” and ignoring {@code + * null} values. + * + * @param strings + * The strings to join + * @return The joined strings + */ + private static String joinStrings(String... strings) { + return Joiner.on(" - ").skipNulls().join(Arrays.asList(strings)); + } + +} diff --git a/src/main/java/net/pterodactylus/sonitus/data/FormatMetadata.java b/src/main/java/net/pterodactylus/sonitus/data/FormatMetadata.java new file mode 100644 index 0000000..8e0f1f5 --- /dev/null +++ b/src/main/java/net/pterodactylus/sonitus/data/FormatMetadata.java @@ -0,0 +1,162 @@ +/* + * Sonitus - ContentMetadata.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sonitus.data; + +/** + * The part of the {@link Metadata} that contains information about the format + * of a track. It specifies the number of channels, the samplerate, and the + * encoding of a track. + * + * @author David ‘Bombe’ Roden + */ +public class FormatMetadata { + + /** Constant for an unknown number of channels. */ + public static final int UNKNOWN_CHANNELS = -1; + + /** Constant for an unknown frequency. */ + public static final int UNKNOWN_FREQUENCY = -1; + + /** Constant for an unknown metadata. */ + public static final String UNKNOWN_ENCODING = "UNKNOWN"; + + /** The number of channels of this metadata. */ + private final int channels; + + /** The sampling frequency of this metadata. */ + private final int frequency; + + /** The encoding of this metadata. */ + private final String encoding; + + /** Creates new format metadata whose parameters are all unknown. */ + public FormatMetadata() { + this(UNKNOWN_CHANNELS, UNKNOWN_FREQUENCY, UNKNOWN_ENCODING); + } + + /** + * Creates new format metadata with the given parameters. + * + * @param channels + * The number of channels + * @param frequency + * The sampling frequency (in Hertz) + * @param encoding + * The encoding (e.g. “PCM” or “MP3”) + */ + public FormatMetadata(int channels, int frequency, String encoding) { + this.channels = channels; + this.frequency = frequency; + this.encoding = encoding; + } + + // + // ACCESSORS + // + + /** + * Returns the number of channels of this metadata. + * + * @return The number of channels of this metadata + */ + public int channels() { + return channels; + } + + /** + * Returns the sampling frequency of this metadata. + * + * @return The sampling frequency of this metadata + */ + public int frequency() { + return frequency; + } + + /** + * Returns the encoding of this metadata + * + * @return The encoding of this metadata + */ + public String encoding() { + return encoding; + } + + // + // ACTIONS + // + + /** + * Creates new format metadata that is a copy of this format metadata but with + * the number of channels changed to the given number of channels. + * + * @param channels + * The new number of channels + * @return The new format metadata + */ + public FormatMetadata channels(int channels) { + return new FormatMetadata(channels, frequency(), encoding()); + } + + /** + * Creates new format metadata that is a copy of this format metadata but with + * the sampling frequency changed to the given sampling frequency. + * + * @param frequency + * The new sampling frequency + * @return The new format metadata + */ + public FormatMetadata frequency(int frequency) { + return new FormatMetadata(channels(), frequency, encoding()); + } + + /** + * Creates new format metadata that is a copy of this format metadata but with + * the encoding changed to the given encoding. + * + * @param encoding + * The new encoding + * @return The new format metadata + */ + public FormatMetadata encoding(String encoding) { + return new FormatMetadata(channels(), frequency(), encoding); + } + + // + // OBJECT METHODS + // + + @Override + public int hashCode() { + return (channels() << 16) ^ frequency() ^ encoding().toUpperCase().hashCode(); + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof FormatMetadata)) { + return false; + } + FormatMetadata formatMetadata = (FormatMetadata) object; + return (channels() == formatMetadata.channels()) && (frequency() == formatMetadata.frequency()) && (encoding().equalsIgnoreCase(formatMetadata.encoding())); + } + + @Override + public String toString() { + return String.format("%d Channel%s, %d Hz, %s", channels(), channels() != 1 ? "s" : "", frequency(), encoding()); + } + +} diff --git a/src/main/java/net/pterodactylus/sonitus/data/Metadata.java b/src/main/java/net/pterodactylus/sonitus/data/Metadata.java index d3a06c9..52cb365 100644 --- a/src/main/java/net/pterodactylus/sonitus/data/Metadata.java +++ b/src/main/java/net/pterodactylus/sonitus/data/Metadata.java @@ -30,49 +30,28 @@ import com.google.common.base.Optional; */ public class Metadata { - /** Constant for an unknown number of channels. */ - public static final int UNKNOWN_CHANNELS = -1; + /** The format metadata. */ + private final FormatMetadata formatMetadata; - /** Constant for an unknown frequency. */ - public static final int UNKNOWN_FREQUENCY = -1; - - /** Constant for an unknown metadata. */ - public static final String UNKNOWN_ENCODING = "UNKNOWN"; - - /** The number of channels of this metadata. */ - private final int channels; - - /** The sampling frequency of this metadata. */ - private final int frequency; - - /** The encoding of this metadata. */ - private final String encoding; - - /** The artist performing the content. */ - private final Optional artist; - - /** The name of the content. */ - private final Optional name; + /** The content metadata. */ + private final ContentMetadata contentMetadata; /** Creates empty metadata. */ - public Metadata(int channels, int frequency, String encoding) { - this(channels, frequency, encoding, null, null); + public Metadata() { + this(new FormatMetadata(), new ContentMetadata()); } /** - * Creates metadata with the given attributes. + * Creates metadata from the given format and content metadata. * - * @param artist - * The artist performing the content (may be {@code null}) - * @param name - * The name of the content (may be {@code null}) + * @param formatMetadata + * The format metadata + * @param contentMetadata + * The content metadata */ - private Metadata(int channels, int frequency, String encoding, String artist, String name) { - this.channels = channels; - this.frequency = frequency; - this.encoding = encoding; - this.artist = Optional.fromNullable(artist); - this.name = Optional.fromNullable(name); + public Metadata(FormatMetadata formatMetadata, ContentMetadata contentMetadata) { + this.formatMetadata = formatMetadata; + this.contentMetadata = contentMetadata; } // @@ -85,7 +64,7 @@ public class Metadata { * @return The number of channels of this metadata */ public int channels() { - return channels; + return formatMetadata.channels(); } /** @@ -97,7 +76,7 @@ public class Metadata { * @return A new metadata with the given number of channels */ public Metadata channels(int channels) { - return new Metadata(channels, frequency, encoding, artist.orNull(), name.orNull()); + return new Metadata(formatMetadata.channels(channels), contentMetadata); } /** @@ -106,7 +85,7 @@ public class Metadata { * @return The sampling frequency of this metadata */ public int frequency() { - return frequency; + return formatMetadata.frequency(); } /** @@ -118,7 +97,7 @@ public class Metadata { * @return A new metadata with the given frequency */ public Metadata frequency(int frequency) { - return new Metadata(channels, frequency, encoding, artist.orNull(), name.orNull()); + return new Metadata(formatMetadata.frequency(frequency), contentMetadata); } /** @@ -127,7 +106,7 @@ public class Metadata { * @return The encoding of this metadata */ public String encoding() { - return encoding; + return formatMetadata.encoding(); } /** @@ -139,7 +118,7 @@ public class Metadata { * @return A new metadata with the given encoding */ public Metadata encoding(String encoding) { - return new Metadata(channels, frequency, encoding, artist.orNull(), name.orNull()); + return new Metadata(formatMetadata.encoding(encoding), contentMetadata); } /** @@ -148,7 +127,7 @@ public class Metadata { * @return The artist, or {@link Optional#absent()} */ public Optional artist() { - return artist; + return contentMetadata.artist(); } /** @@ -160,7 +139,7 @@ public class Metadata { * @return New metadata with a changed artist */ public Metadata artist(String artist) { - return new Metadata(channels, frequency, encoding, (artist != null) ? artist.trim() : artist, name.orNull()); + return new Metadata(formatMetadata, contentMetadata.artist(artist)); } /** @@ -169,7 +148,7 @@ public class Metadata { * @return The name, or {@link Optional#absent()} */ public Optional name() { - return name; + return contentMetadata.name(); } /** @@ -181,7 +160,28 @@ public class Metadata { * @return New metadata with a changed name */ public Metadata name(String name) { - return new Metadata(channels, frequency, encoding, artist.orNull(), (name != null) ? name.trim() : name); + return new Metadata(formatMetadata, contentMetadata.name(name)); + } + + /** + * Returns the title of the content. + * + * @return The title of the content + */ + public String title() { + return contentMetadata.title(); + } + + /** + * Returns new metadata with the same attributes as this metadata but with the + * title changed to the given title. + * + * @param title + * The new title + * @return The new metadata + */ + public Metadata title(String title) { + return new Metadata(formatMetadata, contentMetadata.title(title)); } // @@ -190,45 +190,21 @@ public class Metadata { @Override public int hashCode() { - int hashCode = (channels << 16) ^ frequency ^ encoding.toUpperCase().hashCode(); - if (artist.isPresent()) { - hashCode ^= artist.get().hashCode(); - } - if (name.isPresent()) { - hashCode ^= name.get().hashCode(); - } - return hashCode; + return formatMetadata.hashCode() ^ contentMetadata.hashCode(); } @Override public boolean equals(Object object) { - if ((object == null) || (getClass() != object.getClass())) { + if (!(object instanceof Metadata)) { return false; } Metadata metadata = (Metadata) object; - if ((metadata.channels != channels) || (metadata.frequency != frequency) || !metadata.encoding.equalsIgnoreCase(encoding)) { - return false; - } - if (!artist.equals(metadata.artist)) { - return false; - } - if (!name.equals(metadata.name)) { - return false; - } - return true; + return formatMetadata.equals(metadata.formatMetadata) && contentMetadata.equals(metadata.contentMetadata); } @Override public String toString() { - StringBuilder string = new StringBuilder(); - string.append(String.format("%d Channel%s, %d Hz, %s:", channels, channels != 1 ? "s" : "", frequency, encoding)); - if (artist.isPresent()) { - string.append(" Artist(").append(artist.get()).append(")"); - } - if (name.isPresent()) { - string.append(" Name(").append(name.get()).append(")"); - } - return string.toString(); + return String.format("%s: %s", formatMetadata, contentMetadata); } } diff --git a/src/main/java/net/pterodactylus/sonitus/data/source/FileSource.java b/src/main/java/net/pterodactylus/sonitus/data/source/FileSource.java index 3d6e871..dfa1665 100644 --- a/src/main/java/net/pterodactylus/sonitus/data/source/FileSource.java +++ b/src/main/java/net/pterodactylus/sonitus/data/source/FileSource.java @@ -18,9 +18,6 @@ package net.pterodactylus.sonitus.data.source; import static com.google.common.base.Preconditions.checkNotNull; -import static net.pterodactylus.sonitus.data.Metadata.UNKNOWN_CHANNELS; -import static net.pterodactylus.sonitus.data.Metadata.UNKNOWN_ENCODING; -import static net.pterodactylus.sonitus.data.Metadata.UNKNOWN_FREQUENCY; import java.io.EOFException; import java.io.FileInputStream; @@ -69,7 +66,7 @@ public class FileSource implements Source { metadata = identifyingInputStream.get().metadata(); } else { /* fallback. */ - metadata = new Metadata(UNKNOWN_CHANNELS, UNKNOWN_FREQUENCY, UNKNOWN_ENCODING).name(path); + metadata = new Metadata().name(path); } } diff --git a/src/main/java/net/pterodactylus/sonitus/io/Mp3Identifier.java b/src/main/java/net/pterodactylus/sonitus/io/Mp3Identifier.java index 51aa1a6..5d4f2ff 100644 --- a/src/main/java/net/pterodactylus/sonitus/io/Mp3Identifier.java +++ b/src/main/java/net/pterodactylus/sonitus/io/Mp3Identifier.java @@ -25,6 +25,8 @@ import java.io.IOException; import java.io.InputStream; import java.util.Arrays; +import net.pterodactylus.sonitus.data.ContentMetadata; +import net.pterodactylus.sonitus.data.FormatMetadata; import net.pterodactylus.sonitus.data.Metadata; import net.pterodactylus.sonitus.io.mp3.Frame; import net.pterodactylus.sonitus.io.mp3.Parser; @@ -53,7 +55,8 @@ public class Mp3Identifier { public static Optional identify(InputStream inputStream) throws IOException { Parser mp3Parser = new Parser(inputStream); Frame frame = mp3Parser.nextFrame(); - Metadata metadata = new Metadata((frame.channelMode() == SINGLE_CHANNEL) ? 1 : 2, frame.samplingRate(), "MP3"); + FormatMetadata formatMetadata = new FormatMetadata((frame.channelMode() == SINGLE_CHANNEL) ? 1 : 2, frame.samplingRate(), "MP3"); + ContentMetadata contentMetadata = new ContentMetadata(""); /* check for ID3v2 tag. */ Optional id3v2TagBuffer = mp3Parser.getId3Tag(); if (id3v2TagBuffer.isPresent()) { @@ -63,7 +66,7 @@ public class Mp3Identifier { /* skip “ID3” header tag. */ ID3V2Tag id3v2Tag = ID3V2Tag.read(tagInputStream); if (id3v2Tag != null) { - metadata = metadata.artist(id3v2Tag.getArtist()).name(id3v2Tag.getTitle()); + contentMetadata = contentMetadata.artist(id3v2Tag.getArtist()).name(id3v2Tag.getTitle()); } } catch (ID3Exception id3e1) { id3e1.printStackTrace(); @@ -71,7 +74,7 @@ public class Mp3Identifier { close(tagInputStream, true); } } - return Optional.of(metadata); + return Optional.of(new Metadata(formatMetadata, contentMetadata)); } } diff --git a/src/main/java/net/pterodactylus/sonitus/io/OggVorbisIdentifier.java b/src/main/java/net/pterodactylus/sonitus/io/OggVorbisIdentifier.java index bbbecd2..9d38452 100644 --- a/src/main/java/net/pterodactylus/sonitus/io/OggVorbisIdentifier.java +++ b/src/main/java/net/pterodactylus/sonitus/io/OggVorbisIdentifier.java @@ -20,6 +20,8 @@ package net.pterodactylus.sonitus.io; import java.io.IOException; import java.io.InputStream; +import net.pterodactylus.sonitus.data.ContentMetadata; +import net.pterodactylus.sonitus.data.FormatMetadata; import net.pterodactylus.sonitus.data.Metadata; import com.google.common.base.Optional; @@ -115,21 +117,22 @@ public class OggVorbisIdentifier { buffer = syncState.data; } - Metadata metadata = new Metadata(info.channels, info.rate, "Vorbis"); + FormatMetadata formatMetadata = new FormatMetadata(info.channels, info.rate, "Vorbis"); + ContentMetadata contentMetadata = new ContentMetadata(""); for (int c = 0; c < comment.comments; ++c) { String field = comment.getComment(c); Optional extractedField = extractField(field, "ARTIST"); if (extractedField.isPresent()) { - metadata = metadata.artist(extractedField.get()); + contentMetadata = contentMetadata.artist(extractedField.get()); continue; } extractedField = extractField(field, "TITLE"); if (extractedField.isPresent()) { - metadata = metadata.name(extractedField.get()); + contentMetadata = contentMetadata.name(extractedField.get()); continue; } } - return Optional.of(metadata); + return Optional.of(new Metadata(formatMetadata, contentMetadata)); } /** -- 2.7.4