View Javadoc

1   package com.melloware.jukes.file.tag;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import java.util.Calendar;
6   import java.util.GregorianCalendar;
7   import java.util.Iterator;
8   import java.util.List;
9   import java.util.Locale;
10  import java.util.Map;
11  
12  import javax.sound.sampled.AudioFileFormat;
13  import javax.sound.sampled.AudioSystem;
14  import javax.sound.sampled.UnsupportedAudioFileException;
15  
16  import org.apache.commons.io.FilenameUtils;
17  import org.apache.commons.lang.StringUtils;
18  import org.apache.commons.lang.builder.CompareToBuilder;
19  import org.apache.commons.lang.builder.EqualsBuilder;
20  import org.apache.commons.lang.builder.HashCodeBuilder;
21  import org.apache.commons.lang.builder.ToStringBuilder;
22  import org.apache.commons.lang.builder.ToStringStyle;
23  import org.apache.commons.lang.math.NumberUtils;
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  import org.jaudiotagger.tag.id3.valuepair.GenreTypes;
27  import org.tritonus.share.sampled.file.TAudioFileFormat;
28  
29  import com.melloware.jukes.exception.MusicTagException;
30  import com.melloware.jukes.file.FileUtil;
31  import com.melloware.jukes.util.TimeSpan;
32  
33  /**
34   * Abstract superclass for all tag types.
35   * <p>
36   * Copyright (c) 2006 Melloware, Inc. <http://www.melloware.com>
37   * @author Emil A. Lefkof III <info@melloware.com>
38   * @version 4.0
39   */
40  @SuppressWarnings("PMD")
41  abstract public class MusicTag implements ITag, Comparable {
42  
43     private static final Log LOG = LogFactory.getLog(MusicTag.class);
44     public static final String NO_TAG = "[notag]";
45     public static final String CURRENT_YEAR = currentYear();
46     public static final String DEFAULT_FILE_FORMAT = "%n - %t";
47  
48     protected File file;
49     protected Long bitRate = null;
50     protected long trackLength = 1;
51     protected Map header;
52     protected String artist = null;
53     protected String comment = null;
54     protected String disc = null;
55     protected String encodedBy = null;
56     protected String genre = null;
57     protected String title = null;
58     protected String track = null;
59     protected String year = null;
60  
61     /**
62      * Constructor that takes the music file.
63      * <p>
64      * @param aFile the music file
65      * @throws MusicTagException if any error occurs opening tag
66      */
67     public MusicTag(File aFile) throws MusicTagException {
68        super();
69        if (aFile == null) {
70           throw new MusicTagException("File is not valid.");
71        }
72        this.file = aFile;
73  
74        try {
75           // set the file to writeable if it is readonly
76           if (!aFile.canWrite()) {
77              aFile.setReadable(true, false);
78           }
79  
80           // get the header info
81           final AudioFileFormat format = AudioSystem.getAudioFileFormat(aFile);
82           if (format instanceof TAudioFileFormat) {
83              // Tritonus SPI compliant audio file format.
84              final Map props = ((TAudioFileFormat) format).properties();
85              // Clone the Map because it is not mutable.
86              this.header = FileUtil.deepCopy(props);
87           } else {
88              this.header = null;
89           }
90        } catch (UnsupportedAudioFileException ex) {
91           LOG.error(ex.getMessage(), ex);
92           throw new MusicTagException("UnsupportedAudioFileException opening Music File Tag.  "
93                    + aFile.getAbsolutePath() + "\n\n" + ex.getMessage());
94        } catch (IOException ex) {
95           LOG.error(ex.getMessage(), ex);
96           throw new MusicTagException("IOException opening Music File Tag.  " + aFile.getAbsolutePath() + "\n\n"
97                    + ex.getMessage());
98        } catch (Exception ex) {
99           LOG.error(ex.getMessage(), ex);
100          throw new MusicTagException("Error opening Music File Tag.  " + aFile.getAbsolutePath() + "\n\n"
101                   + ex.getMessage());
102       }
103    }
104 
105    /**
106     * Gets the artist.
107     * <p>
108     * @return Returns the artist.
109     */
110    abstract public String getArtist();
111 
112    /**
113     * Gets the bitRate.
114     * <p>
115     * @return Returns the bitRate.
116     */
117    abstract public Long getBitRate();
118 
119    /**
120     * Gets the comment.
121     * <p>
122     * @return Returns the comment.
123     */
124    abstract public String getComment();
125 
126    /*
127     * (non-Javadoc)
128     * @see com.melloware.jukes.file.tag.ITag#getCopyrighted()
129     */
130    abstract public String getCopyrighted();
131 
132    /**
133     * Gets the disc.
134     * <p>
135     * @return Returns the disc.
136     */
137    abstract public String getDisc();
138 
139    /*
140     * (non-Javadoc)
141     * @see com.melloware.jukes.file.tag.ITag#getEmphasis()
142     */
143    abstract public String getEmphasis();
144 
145    /**
146     * Gets the encodedBy.
147     * <p>
148     * @return Returns the encodedBy.
149     */
150    abstract public String getEncodedBy();
151 
152    /*
153     * (non-Javadoc)
154     * @see com.melloware.jukes.file.tag.ITag#getFrequency()
155     */
156    abstract public String getFrequency();
157 
158    /**
159     * Gets the genre.
160     * <p>
161     * @return Returns the genre.
162     */
163    abstract public String getGenre();
164 
165    /**
166     * Gets the header.
167     * <p>
168     * @return Returns the header.
169     */
170    abstract public Map getHeader();
171 
172    /*
173     * (non-Javadoc)
174     * @see com.melloware.jukes.file.tag.ITag#getLayer()
175     */
176    abstract public String getLayer();
177 
178    /*
179     * (non-Javadoc)
180     * @see com.melloware.jukes.file.tag.ITag#getMode()
181     */
182    abstract public String getMode();
183 
184    /**
185     * Gets the title.
186     * <p>
187     * @return Returns the title.
188     */
189    abstract public String getTitle();
190 
191    /**
192     * Gets the track.
193     * <p>
194     * @return Returns the track.
195     */
196    abstract public String getTrack();
197 
198    /**
199     * Gets the trackLength.
200     * <p>
201     * @return Returns the trackLength.
202     */
203    abstract public long getTrackLength();
204 
205    /*
206     * (non-Javadoc)
207     * @see com.melloware.jukes.file.tag.ITag#getVersion()
208     */
209    abstract public String getVersion();
210 
211    /**
212     * Gets the year.
213     * <p>
214     * @return Returns the year.
215     */
216    abstract public String getYear();
217 
218    /**
219     * Sets the artist.
220     * <p>
221     * @param aArtist The artist to set.
222     */
223    abstract public void setArtist(String aArtist);
224 
225    /**
226     * Sets the comment.
227     * <p>
228     * @param aComment The comment to set.
229     */
230    abstract public void setComment(String aComment);
231 
232    /**
233     * Sets the disc.
234     * <p>
235     * @param aDisc The disc to set.
236     */
237    abstract public void setDisc(String aDisc);
238 
239    /**
240     * Sets the encodedBy.
241     * <p>
242     * @param aEncodedBy The encodedBy to set.
243     */
244    abstract public void setEncodedBy(String aEncodedBy);
245 
246    /**
247     * Sets the genre.
248     * <p>
249     * @param aGenre The genre to set.
250     */
251    abstract public void setGenre(String aGenre);
252 
253    /**
254     * Sets the title.
255     * <p>
256     * @param aTitle The title to set.
257     */
258    abstract public void setTitle(String aTitle);
259 
260    /**
261     * Sets the track.
262     * <p>
263     * @param aTrack The track to set.
264     */
265    abstract public void setTrack(String aTrack);
266 
267    /**
268     * Sets the track.
269     * <p>
270     * @param aTrack The track to set.
271     * @param padding the number of 0's to pad
272     */
273    abstract public void setTrack(String track, int padding);
274 
275    /**
276     * Sets the trackLength.
277     * <p>
278     * @param aTrackLength The trackLength to set.
279     */
280    abstract public void setTrackLength(long aTrackLength);
281 
282    /**
283     * Sets the year.
284     * <p>
285     * @param aYear The year to set.
286     */
287    abstract public void setYear(String aYear);
288 
289    /*
290     * (non-Javadoc)
291     * @see com.melloware.jukes.file.tag.ITag#isVBR()
292     */
293    abstract public boolean isVBR();
294 
295    /**
296     * Removes tags from the audio file.
297     * <p>
298     * @throws MusicTagException if any error occurs removing the tag
299     */
300    abstract public void removeTags() throws MusicTagException;
301 
302    /**
303     * Renames this Music file based on a format from prefs. The format is in
304     * aFormat and can have values %n for track number, %t for title, %a for
305     * artist, and %d for disc. Replaces any invalid characters (\\, /, :, , *, ?, ", <, >,
306     * or |) with underscores _ to prevent any errors on file systems. Examples:
307     * %n -%t = 01 - Track.mp3 %a - %d - %n - %t = Artist - Album - 01 -
308     * Track.mp3
309     * <p>
310     * @param aFormat the string format like %n -%t to rename 01 - Track.mp3
311     * @return true if renamed, false if failure
312     */
313    abstract public boolean renameFile(String aFormat);
314 
315    /**
316     * Saves the tag back to the file.
317     * <p>
318     * @throws MusicTagException if any error occurs saving the file
319     */
320    abstract public void save() throws MusicTagException;
321 
322    /**
323     * Gets the genre list.
324     * <p>
325     * @return Returns the genre list
326     */
327    public static List getGenreTypes() {
328       return GenreTypes.getInstanceOf().getAlphabeticalValueList();
329    }
330 
331    /**
332     * Gets the absolute file path.
333     * <p>
334     * @return Returns the absolute file path
335     */
336    public String getAbsolutePath() {
337       return this.getFile().getAbsolutePath();
338    }
339 
340    /**
341     * Returns the bit rate as a string.
342     * @return the bitrate value as a string decoded with VBR
343     */
344    public String getBitRateAsString() {
345       String rate = "0 ";
346       if (isVBR()) {
347          rate = "VBR ~" + getBitRate().toString();
348       } else {
349          rate = getBitRate().toString();
350       }
351 
352       return rate + " kbps";
353    }
354 
355    /**
356     * Gets the file.
357     * <p>
358     * @return Returns the file.
359     */
360    public File getFile() {
361       return this.file;
362    }
363 
364    /**
365     * Returns the info about the audio file.
366     * <p>
367     * @return string containing the info about the MP3 file
368     */
369    public String getHeaderInfo() {
370 
371       /*
372        * ogg.version = 0 ogg.channels = 2 ogg.length.bytes = 3654577
373        * ogg.bitrate.nominal.bps = 160003 ogg.serial = 59211796 ogg.frequency.hz =
374        * 44100 duration = 181055000
375        */
376       /*
377        * mp3.frequency.hz = 44100 title = Goodbye You Lizard Scum
378        * mp3.length.bytes = 3706880 comment = mp3.channels = 2 date = 1997
379        * mp3.version.layer = 3 mp3.framesize.bytes = 413 mp3.id3tag.track = 1
380        * mp3.version.mpeg = 1 mp3.bitrate.nominal.bps = 128000 mp3.vbr.scale = 0
381        * mp3.length.frames = 8889 mp3.crc = false album = Arizona Bay mp3.vbr =
382        * false mp3.copyright = false mp3.framerate.fps = 38.28125 mp3.id3tag.v2 =
383        * java.io.ByteArrayInputStream@294f62 mp3.id3tag.v2.version = 4
384        * mp3.version.encoding = MPEG1L3 mp3.id3tag.genre = Comedy mp3.header.pos =
385        * 487 mp3.original = true mp3.mode = 1 mp3.padding = false duration =
386        * 232202000 author = Bill Hicks
387        */
388       final StringBuffer buffer = new StringBuffer();
389       if (header != null) {
390          final Iterator it = header.keySet().iterator();
391          while (it.hasNext()) {
392             final Object key = it.next();
393             final Object value = header.get(key);
394             buffer.append(key);
395             buffer.append(" = ");
396             buffer.append(value);
397          }
398       }
399       return buffer.toString();
400    }
401 
402    /**
403     * Returns the info about the audio file.
404     * <p>
405     * @return string containing the info about the MP3 file
406     */
407    public String getHeaderValue(final String aKey) {
408       return ((String) header.get(aKey));
409    }
410 
411    /**
412     * Gets the track length in seconds.
413     * <p>
414     * @return Returns the track length in seconds.
415     */
416    public String getTrackLengthAsString() {
417       return new TimeSpan(getTrackLength() * 1000).getMusicDuration();
418    }
419 
420    /**
421     * Default Equals method.
422     */
423    public boolean equals(Object obj) {
424       if (!(obj instanceof MusicTag)) {
425          return false;
426       }
427       if (this == obj) {
428          return true;
429       }
430       final MusicTag rhs = (MusicTag) obj;
431       final EqualsBuilder builder = new EqualsBuilder();
432       builder.append(artist, rhs.artist);
433       builder.append(disc, rhs.disc);
434       builder.append(title, rhs.title);
435       builder.append(year, rhs.year);
436       builder.append(genre, rhs.genre);
437 
438       return builder.isEquals();
439    }
440 
441    /**
442     * Tries to extract the title of the song from the filename.
443     * <p>
444     * EXAMPLES: 06 - Opiate.mp3 = Opiate Tool - Aenima - 06 - Opiate.mp3 =
445     * Opiate 02 - Positively 4th Street.mp3 = Positively 4th Street 11 - Nothing
446     * Compares 2 U.mp3 = Nothing Compares 2 U
447     * <p>
448     * @return the title extracted from the filename
449     */
450    public String extractTitleFromFilename() {
451       String title = "";
452 
453       // first strip off the extension .mp3 or .ogg
454       final String filename = FilenameUtils.getBaseName(file.getAbsolutePath());
455 
456       // split into tokens by whitespace
457       final String[] tokens = StringUtils.split(filename);
458 
459       // loop backwards through the tokens to see if they should be used
460       for (int i = tokens.length - 1; i >= 0; i--) {
461          final String token = tokens[i];
462 
463          // throw out any dashes which are used as separators
464          if (StringUtils.equalsIgnoreCase(token, "-")) {
465             continue;
466          }
467 
468          // break if we hit a number which matches the TRACK
469          if ((StringUtils.isNumericSpace(token)) && (NumberUtils.toInt(token) == NumberUtils.toInt(this.getTrack()))) {
470             break;
471          }
472 
473          // append this token onto the title we are building
474          title = token + " " + title;
475       }
476 
477       // if no title was extracted then just use the filename
478       if (StringUtils.isBlank(title)) {
479          title = filename;
480       }
481 
482       return title;
483    }
484 
485    /**
486     * If the file exists or not.
487     * <p>
488     * @return Returns the file exists flag
489     */
490    public boolean fileExists() {
491       return this.file.exists();
492    }
493 
494    /**
495     * Default hashcode method.
496     */
497    public int hashCode() {
498       return HashCodeBuilder.reflectionHashCode(this);
499    }
500 
501    /**
502     * Renames this Music file based on the default format of %n - %t.mp3 so it
503     * looks like 01 - Track.mp3.
504     * <p>
505     * @return true if renamed, false if failure
506     */
507    public boolean renameFile() {
508       return renameFile(DEFAULT_FILE_FORMAT);
509    }
510 
511    /**
512     * Synchronizes the tag to have the same data in the V1 and V2 tag.
513     */
514    public void synchronize() {
515       this.setDisc(this.getDisc());
516       this.setArtist(this.getArtist());
517       this.setComment(this.getComment());
518       this.setGenre(this.getGenre());
519       this.setTitle(this.getTitle());
520       this.setTrack(this.getTrack());
521       this.setYear(this.getYear());
522       this.setTrackLength(this.getTrackLength());
523       this.setEncodedBy(this.getEncodedBy());
524    }
525 
526    /**
527     * CompareTo method for sorting tracks.
528     */
529    @Override
530    public int compareTo(Object o) {
531       MusicTag myClass = (MusicTag) o;
532       return new CompareToBuilder().append(this.track, myClass.track).append(this.title, myClass.title).toComparison();
533    }
534 
535    /**
536     * Default toString() method.
537     */
538    public String toString() {
539       final ToStringBuilder builder = new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE);
540       builder.append("artist", artist);
541       builder.append("disc", disc);
542       builder.append("title", title);
543       builder.append("track", track);
544       builder.append("genre", genre);
545       builder.append("year", year);
546       builder.append("comment", comment);
547       builder.append("length", trackLength);
548 
549       return builder.toString();
550    }
551 
552    /**
553     * Creates a new filename based on the format passed in.
554     * <p>
555     * @param aFormat the file format like %n -%t = 01 - Track.mp3
556     * @return the new file name
557     */
558    protected String createFilenameFromFormat(String aFormat) {
559       final String oldFileName = this.file.getAbsolutePath();
560       final String directory = FilenameUtils.getFullPath(oldFileName);
561       final String extension = FilenameUtils.getExtension(oldFileName.toLowerCase(Locale.US));
562       String newFileName = aFormat;
563       newFileName = StringUtils.replace(newFileName, "%n", getTrack());
564       newFileName = StringUtils.replace(newFileName, "%t", getTitle());
565       newFileName = StringUtils.replace(newFileName, "%a", getArtist());
566       newFileName = StringUtils.replace(newFileName, "%d", getDisc());
567       newFileName = StringUtils.replace(newFileName, "%N", getTrack().toUpperCase());
568       newFileName = StringUtils.replace(newFileName, "%T", getTitle().toUpperCase());
569       newFileName = StringUtils.replace(newFileName, "%A", getArtist().toUpperCase());
570       newFileName = StringUtils.replace(newFileName, "%D", getDisc().toUpperCase());
571       newFileName = FileUtil.correctFileName(newFileName);
572       newFileName = directory + newFileName + "." + extension;
573       if (LOG.isDebugEnabled()) {
574          LOG.debug("Renaming: '" + oldFileName + "' to '" + newFileName + "'");
575       }
576       return newFileName;
577    }
578 
579    /**
580     * Calculates the current year. This is the default if the audio file has no
581     * year listed.
582     * <p>
583     * @return the current year as a string.
584     */
585    private static String currentYear() {
586       final GregorianCalendar now = new GregorianCalendar();
587       final int year = now.get(Calendar.YEAR);
588       return Integer.toString(year);
589    }
590 
591 }