View Javadoc

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