Split metadata into format and content metadata.
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Fri, 24 May 2013 21:03:17 +0000 (23:03 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Mon, 27 May 2013 20:54:37 +0000 (22:54 +0200)
src/main/java/net/pterodactylus/sonitus/data/ContentMetadata.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sonitus/data/FormatMetadata.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sonitus/data/Metadata.java
src/main/java/net/pterodactylus/sonitus/data/source/FileSource.java
src/main/java/net/pterodactylus/sonitus/io/Mp3Identifier.java
src/main/java/net/pterodactylus/sonitus/io/OggVorbisIdentifier.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 (file)
index 0000000..cdfc4d2
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+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.
+ * <p/>
+ * 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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class ContentMetadata {
+
+       /** The artist. */
+       private final Optional<String> artist;
+
+       /** The name. */
+       private final Optional<String> 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<String> artist() {
+               return artist;
+       }
+
+       /**
+        * Returns the name of the track, if it has been set.
+        *
+        * @return The name of the track
+        */
+       public Optional<String> 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 (file)
index 0000000..8e0f1f5
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+
+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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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());
+       }
+
+}
index d3a06c9..52cb365 100644 (file)
@@ -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<String> artist;
-
-       /** The name of the content. */
-       private final Optional<String> 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<String> 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<String> 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);
        }
 
 }
index 3d6e871..dfa1665 100644 (file)
@@ -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);
                }
        }
 
index 51aa1a6..5d4f2ff 100644 (file)
@@ -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<Metadata> 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<byte[]> 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));
        }
 
 }
index bbbecd2..9d38452 100644 (file)
@@ -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<String> 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));
        }
 
        /**