aboutsummaryrefslogtreecommitdiff
path: root/subsonic-main/src/main/java/net/sourceforge/subsonic/service/metadata/JaudiotaggerParser.java
diff options
context:
space:
mode:
Diffstat (limited to 'subsonic-main/src/main/java/net/sourceforge/subsonic/service/metadata/JaudiotaggerParser.java')
-rw-r--r--subsonic-main/src/main/java/net/sourceforge/subsonic/service/metadata/JaudiotaggerParser.java296
1 files changed, 296 insertions, 0 deletions
diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/service/metadata/JaudiotaggerParser.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/service/metadata/JaudiotaggerParser.java
new file mode 100644
index 00000000..8fa7659a
--- /dev/null
+++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/service/metadata/JaudiotaggerParser.java
@@ -0,0 +1,296 @@
+/*
+ This file is part of Subsonic.
+
+ Subsonic 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.
+
+ Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2009 (C) Sindre Mehus
+ */
+package net.sourceforge.subsonic.service.metadata;
+
+import net.sourceforge.subsonic.Logger;
+import net.sourceforge.subsonic.domain.MediaFile;
+
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang.StringUtils;
+import org.jaudiotagger.audio.AudioFile;
+import org.jaudiotagger.audio.AudioFileIO;
+import org.jaudiotagger.audio.AudioHeader;
+import org.jaudiotagger.tag.FieldKey;
+import org.jaudiotagger.tag.Tag;
+import org.jaudiotagger.tag.datatype.Artwork;
+import org.jaudiotagger.tag.reference.GenreTypes;
+
+import java.io.File;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.logging.LogManager;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Parses meta data from audio files using the Jaudiotagger library
+ * (http://www.jthink.net/jaudiotagger/)
+ *
+ * @author Sindre Mehus
+ */
+public class JaudiotaggerParser extends MetaDataParser {
+
+ private static final Logger LOG = Logger.getLogger(JaudiotaggerParser.class);
+ private static final Pattern GENRE_PATTERN = Pattern.compile("\\((\\d+)\\).*");
+ private static final Pattern TRACK_NUMBER_PATTERN = Pattern.compile("(\\d+)/\\d+");
+
+ static {
+ try {
+ LogManager.getLogManager().reset();
+ } catch (Throwable x) {
+ LOG.warn("Failed to turn off logging from Jaudiotagger.", x);
+ }
+ }
+
+ /**
+ * Parses meta data for the given music file. No guessing or reformatting is done.
+ *
+ *
+ * @param file The music file to parse.
+ * @return Meta data for the file.
+ */
+ @Override
+ public MetaData getRawMetaData(File file) {
+
+ MetaData metaData = new MetaData();
+
+ try {
+ AudioFile audioFile = AudioFileIO.read(file);
+ Tag tag = audioFile.getTag();
+ if (tag != null) {
+ metaData.setAlbumName(getTagField(tag, FieldKey.ALBUM));
+ metaData.setTitle(getTagField(tag, FieldKey.TITLE));
+ metaData.setYear(parseInteger(getTagField(tag, FieldKey.YEAR)));
+ metaData.setGenre(mapGenre(getTagField(tag, FieldKey.GENRE)));
+ metaData.setDiscNumber(parseInteger(getTagField(tag, FieldKey.DISC_NO)));
+ metaData.setTrackNumber(parseTrackNumber(getTagField(tag, FieldKey.TRACK)));
+
+ String songArtist = getTagField(tag, FieldKey.ARTIST);
+ String albumArtist = getTagField(tag, FieldKey.ALBUM_ARTIST);
+ metaData.setArtist(StringUtils.isBlank(albumArtist) ? songArtist : albumArtist);
+ }
+
+ AudioHeader audioHeader = audioFile.getAudioHeader();
+ if (audioHeader != null) {
+ metaData.setVariableBitRate(audioHeader.isVariableBitRate());
+ metaData.setBitRate((int) audioHeader.getBitRateAsNumber());
+ metaData.setDurationSeconds(audioHeader.getTrackLength());
+ }
+
+
+ } catch (Throwable x) {
+ LOG.warn("Error when parsing tags in " + file, x);
+ }
+
+ return metaData;
+ }
+
+ private String getTagField(Tag tag, FieldKey fieldKey) {
+ try {
+ return StringUtils.trimToNull(tag.getFirst(fieldKey));
+ } catch (Exception x) {
+ // Ignored.
+ return null;
+ }
+ }
+
+ /**
+ * Returns all tags supported by id3v1.
+ */
+ public static SortedSet<String> getID3V1Genres() {
+ return new TreeSet<String>(GenreTypes.getInstanceOf().getAlphabeticalValueList());
+ }
+
+ /**
+ * Sometimes the genre is returned as "(17)" or "(17)Rock", instead of "Rock". This method
+ * maps the genre ID to the corresponding text.
+ */
+ private String mapGenre(String genre) {
+ if (genre == null) {
+ return null;
+ }
+ Matcher matcher = GENRE_PATTERN.matcher(genre);
+ if (matcher.matches()) {
+ int genreId = Integer.parseInt(matcher.group(1));
+ if (genreId >= 0 && genreId < GenreTypes.getInstanceOf().getSize()) {
+ return GenreTypes.getInstanceOf().getValueForId(genreId);
+ }
+ }
+ return genre;
+ }
+
+ /**
+ * Parses the track number from the given string. Also supports
+ * track numbers on the form "4/12".
+ */
+ private Integer parseTrackNumber(String trackNumber) {
+ if (trackNumber == null) {
+ return null;
+ }
+
+ Integer result = null;
+
+ try {
+ result = new Integer(trackNumber);
+ } catch (NumberFormatException x) {
+ Matcher matcher = TRACK_NUMBER_PATTERN.matcher(trackNumber);
+ if (matcher.matches()) {
+ try {
+ result = Integer.valueOf(matcher.group(1));
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+ }
+
+ if (Integer.valueOf(0).equals(result)) {
+ return null;
+ }
+ return result;
+ }
+
+ private Integer parseInteger(String s) {
+ s = StringUtils.trimToNull(s);
+ if (s == null) {
+ return null;
+ }
+ try {
+ Integer result = Integer.valueOf(s);
+ if (Integer.valueOf(0).equals(result)) {
+ return null;
+ }
+ return result;
+ } catch (NumberFormatException x) {
+ return null;
+ }
+ }
+
+ /**
+ * Updates the given file with the given meta data.
+ *
+ * @param file The music file to update.
+ * @param metaData The new meta data.
+ */
+ @Override
+ public void setMetaData(MediaFile file, MetaData metaData) {
+
+ try {
+ AudioFile audioFile = AudioFileIO.read(file.getFile());
+ Tag tag = audioFile.getTagOrCreateAndSetDefault();
+
+ tag.setField(FieldKey.ARTIST, StringUtils.trimToEmpty(metaData.getArtist()));
+ tag.setField(FieldKey.ALBUM_ARTIST, StringUtils.trimToEmpty(metaData.getArtist()));
+ tag.setField(FieldKey.ALBUM, StringUtils.trimToEmpty(metaData.getAlbumName()));
+ tag.setField(FieldKey.TITLE, StringUtils.trimToEmpty(metaData.getTitle()));
+ tag.setField(FieldKey.GENRE, StringUtils.trimToEmpty(metaData.getGenre()));
+
+ Integer track = metaData.getTrackNumber();
+ if (track == null) {
+ tag.deleteField(FieldKey.TRACK);
+ } else {
+ tag.setField(FieldKey.TRACK, String.valueOf(track));
+ }
+
+ Integer year = metaData.getYear();
+ if (year == null) {
+ tag.deleteField(FieldKey.YEAR);
+ } else {
+ tag.setField(FieldKey.YEAR, String.valueOf(year));
+ }
+
+ audioFile.commit();
+
+ } catch (Throwable x) {
+ LOG.warn("Failed to update tags for file " + file, x);
+ throw new RuntimeException("Failed to update tags for file " + file + ". " + x.getMessage(), x);
+ }
+ }
+
+ /**
+ * Returns whether this parser supports tag editing (using the {@link #setMetaData} method).
+ *
+ * @return Always true.
+ */
+ @Override
+ public boolean isEditingSupported() {
+ return true;
+ }
+
+ /**
+ * Returns whether this parser is applicable to the given file.
+ *
+ * @param file The music file in question.
+ * @return Whether this parser is applicable to the given file.
+ */
+ @Override
+ public boolean isApplicable(File file) {
+ if (!file.isFile()) {
+ return false;
+ }
+
+ String format = FilenameUtils.getExtension(file.getName()).toLowerCase();
+
+ return format.equals("mp3") ||
+ format.equals("m4a") ||
+ format.equals("aac") ||
+ format.equals("ogg") ||
+ format.equals("flac") ||
+ format.equals("wav") ||
+ format.equals("mpc") ||
+ format.equals("mp+") ||
+ format.equals("ape") ||
+ format.equals("wma");
+ }
+
+ /**
+ * Returns whether cover art image data is available in the given file.
+ *
+ * @param file The music file.
+ * @return Whether cover art image data is available.
+ */
+ public boolean isImageAvailable(MediaFile file) {
+ try {
+ return getArtwork(file) != null;
+ } catch (Throwable x) {
+ LOG.warn("Failed to find cover art tag in " + file, x);
+ return false;
+ }
+ }
+
+ /**
+ * Returns the cover art image data embedded in the given file.
+ *
+ * @param file The music file.
+ * @return The embedded cover art image data, or <code>null</code> if not available.
+ */
+ public byte[] getImageData(MediaFile file) {
+ try {
+ return getArtwork(file).getBinaryData();
+ } catch (Throwable x) {
+ LOG.warn("Failed to find cover art tag in " + file, x);
+ return null;
+ }
+ }
+
+ private Artwork getArtwork(MediaFile file) throws Exception {
+ AudioFile audioFile = AudioFileIO.read(file.getFile());
+ Tag tag = audioFile.getTag();
+ return tag == null ? null : tag.getFirstArtwork();
+ }
+} \ No newline at end of file