/* * AudioTag.java * * Created on Jun 8, 2007, 10:30:10 PM * * This class is designed to be an abstraction over various different tagging * types. We only need some simple information, just use the static getTag() * method and hopefully you'll get a tag object back you can use! :P * */ package com.pugh.sockso.music.tag; import com.pugh.sockso.Utils; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.regex.Pattern; import java.util.regex.Matcher; import org.apache.log4j.Logger; public abstract class AudioTag implements Tag { private static final Logger log = Logger.getLogger( AudioTag.class ); protected String artistTitle = ""; protected String albumTitle = ""; protected String albumArtist = ""; protected String trackTitle = ""; protected String albumYear = ""; protected String genre = ""; protected int trackNumber = 0; protected BufferedImage coverArt = null; public String getArtist() { return artistTitle; } public String getAlbum() { return albumTitle; } public String getAlbumArtist() { return albumArtist; } public String getTrack() { return trackTitle; } public String getGenre() { return genre; } public int getTrackNumber() { return trackNumber; } public String getAlbumYear() { return albumYear; } public BufferedImage getCoverArt() { return coverArt; } /** * this method should be used when trying to read the tags from an * audio file. the type of file is checked and the details of * what it actually is should be nicely hidden away. * * @param file the file to fetch tags for * @return this files tags * * @throws IOException * @throws InvalidTagException * */ public static Tag getTag( final File file ) throws IOException, InvalidTagException { final String ext = Utils.getExt( file ); AudioTag tag = null; // determine type of file by extension if ( ext.equals("mp3") ) tag = new Mp3Tag(); else if ( ext.equals("ogg") ) tag = new OggTag(); else if ( ext.equals("wma") || ext.equals("asf") ) tag = new WmaTag(); else if ( ext.equals("flac") ) tag = new FlacTag(); else if ( ext.equals("m4a")) tag = new AACTag(); else throw new InvalidTagException( file ); tag.parse( file ); // please, please no nullness... if ( tag.artistTitle == null ) tag.artistTitle = ""; if ( tag.albumTitle == null ) tag.albumTitle = ""; if ( tag.albumArtist == null ) tag.albumArtist = ""; if ( tag.trackTitle == null ) tag.trackTitle = ""; if ( tag.albumYear == null ) tag.albumYear = ""; if ( tag.genre == null ) tag.genre = ""; // remove leading/trailing space tag.artistTitle = clean(tag.artistTitle.trim()); tag.albumTitle = clean(tag.albumTitle.trim()); tag.albumArtist = clean(tag.albumArtist.trim()); tag.trackTitle = clean(tag.trackTitle.trim()); tag.albumYear = clean(tag.albumYear.trim()); tag.genre = clean(tag.genre.trim()); // set defaults if we have nothing if ( tag.artistTitle.equals("") ) tag.artistTitle = guessArtist( file ); if ( tag.albumTitle.equals("") ) tag.albumTitle = guessAlbum( file ); if ( tag.albumArtist.equals("") ) tag.albumArtist = guessAlbumArtist( file ); if ( tag.trackTitle.equals("") ) tag.trackTitle = guessTrack( file ); if ( tag.trackNumber == 0 ) tag.setTrackNumber( guessTrackNumber(file) ); return tag; } /** * Clean the string by removing invalid characters. It's obviously a much * better idea to have a whitelist rather than a blacklist, but the set of * valid characters is too large in this case, and users are largely in * charge of managing their own content. * * @param dirty * * @return * */ protected static String clean( final String dirty ) { return dirty.replace( '\0', ' ' ); } /** * takes a string that is *possibly* a track number and sets it as the * track number if it really is. * * @param strNumber * */ protected void setTrackNumber( final String strNumber ) { try { trackNumber = Integer.parseInt( checkTrackNumberForTotal( strNumber ) ); } catch ( final NumberFormatException e ) {} // swallow } /** * checks if a track number isn't something like "6/9" (ie. track 6 of 9) * if it is then it'll just return the first part, otherwise it'll return * whatever was passed into the methog * * @param trackNumber * * @return * */ protected static String checkTrackNumberForTotal( final String trackNumber ) { Pattern p = Pattern.compile( "(\\d+)/\\d+" ); Matcher m = p.matcher( trackNumber ); return m.matches() ? m.group( 1 ) : trackNumber; } /** * tries to guess the name of the artist from the file name/path * * @param file filename to guess from * @return artist name * */ protected static String guessArtist( final File file ) { final File parent = file.getParentFile(); if ( parent != null ) { final String parentName = parent.getName(); final String[] splits = { " - ", "_-_" }; // see if there's a parent folder that seems to have // an "artist - album" type name for ( int i = 0; i < splits.length; i++ ) if ( parentName.indexOf(splits[i]) != -1 ) return parentName.substring( 0, parentName.indexOf(splits[i]) ); // otherwise try and get the parent of the parent (artist/album/track) if ( parent.getParent() != null ) return parent.getParentFile().getName(); } return "(Unknown Artist)"; } /** * tries to guess the name of the album from the file name/path * * @param file filename to guess from * @return album name * */ protected static String guessAlbum( final File file ) { final File parent = file.getParentFile(); if ( parent != null ) { final String parentName = parent.getName(); final String[] splits = { " - ", "_-_" }; // see if there's a parent folder that seems to have // an "artist - album" type name for ( int i = 0; i < splits.length; i++ ) if ( parentName.indexOf(splits[i]) != -1 ) return parentName.substring( parentName.indexOf(splits[i]) + splits[i].length(), parentName.length() ); // otherwise use parent folder name return parent.getName(); } return "(Unknown Album)"; } /** * tries to guess the name of the album artist from the file name/path * * @param file filename to guess from * @return album artist name * */ protected static String guessAlbumArtist( final File file ) { // TODO: This could look for directory names like "Various Artists" or something return ""; } /** * tries to guess the name of the track from the file name * * @param file filename to guess from * @return track name * */ protected static String guessTrack( final File file ) { // possible regexps to match final String[] regexs = { "\\d+ - (.*)\\.\\w+", "\\d+_-_(.*)\\.\\w+", "\\d+-(.*)\\.\\w+", "(.*)\\.\\w" }; final String name = file.getName(); return matchRegex( name, regexs, file.getName() ); } /** * tries to match a bunch of regular expressions against * a string, the first match is returned * * @param str the string to match against * @param regexs array of regular expressions * @param defaultValue the default to return if no matches * @return the first match, or the default if nothing * */ private static String matchRegex( final String str, final String[] regexs, final String defaultValue ) { for ( int i=0; i<regexs.length; i++ ) { final String regex = regexs[ i ]; final Pattern p = Pattern.compile( regex ); final Matcher m = p.matcher( str ); if ( m.matches() ) return m.group( 1 ); } return defaultValue; } /** * tries to guess the name of the track number from the file name/path * * @param file filename to guess from * * @return track number * */ protected static String guessTrackNumber( final File file ) { // possible regexps to match final String[] regexs = { "(\\d+).*", }; final String name = file.getName(); return matchRegex( name, regexs, "0" ); } @Override public String toString() { return AudioTag.class.getSimpleName() + "{" + "artistTitle=" + artistTitle + ", albumTitle=" + albumTitle + ", albumArtist=" + albumArtist + ", trackTitle=" + trackTitle + ", albumYear=" + albumYear + ", genre=" + genre + ", trackNumber=" + trackNumber + "}"; } }