--- /dev/null
+/*
+ * 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));
+ }
+
+}
--- /dev/null
+/*
+ * 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());
+ }
+
+}
*/
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;
}
//
* @return The number of channels of this metadata
*/
public int channels() {
- return channels;
+ return formatMetadata.channels();
}
/**
* @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);
}
/**
* @return The sampling frequency of this metadata
*/
public int frequency() {
- return frequency;
+ return formatMetadata.frequency();
}
/**
* @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);
}
/**
* @return The encoding of this metadata
*/
public String encoding() {
- return encoding;
+ return formatMetadata.encoding();
}
/**
* @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);
}
/**
* @return The artist, or {@link Optional#absent()}
*/
public Optional<String> artist() {
- return artist;
+ return contentMetadata.artist();
}
/**
* @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));
}
/**
* @return The name, or {@link Optional#absent()}
*/
public Optional<String> name() {
- return name;
+ return contentMetadata.name();
}
/**
* @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));
}
//
@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);
}
}
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;
metadata = identifyingInputStream.get().metadata();
} else {
/* fallback. */
- metadata = new Metadata(UNKNOWN_CHANNELS, UNKNOWN_FREQUENCY, UNKNOWN_ENCODING).name(path);
+ metadata = new Metadata().name(path);
}
}
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;
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()) {
/* 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();
close(tagInputStream, true);
}
}
- return Optional.of(metadata);
+ return Optional.of(new Metadata(formatMetadata, contentMetadata));
}
}
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;
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));
}
/**