1 /*
2  * aTunes
3  * Copyright (C) Alex Aranda, Sylvain Gaudard and contributors
4  *
5  * See http://www.atunes.org/wiki/index.php?title=Contributing for information about contributors
6  *
7  * http://www.atunes.org
8  * http://sourceforge.net/projects/atunes
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  */
20 
21 package net.sourceforge.atunes.kernel.modules.tags;
22 
23 import net.sourceforge.atunes.model.ILocalAudioObject;
24 import net.sourceforge.atunes.model.ITag;
25 import net.sourceforge.atunes.utils.DateUtils;
26 import net.sourceforge.atunes.utils.Logger;
27 import net.sourceforge.atunes.utils.StringUtils;
28 
29 import org.jaudiotagger.audio.AudioFile;
30 import org.jaudiotagger.tag.FieldKey;
31 import org.jaudiotagger.tag.Tag;
32 import org.joda.time.DateMidnight;
33 import org.joda.time.DateTime;
34 
35 /**
36  * Creates tags by reading with JAudiotagger
37  *
38  * @author alex
39  *
40  */
41 public class JAudiotaggerTagCreator {
42 
43 	private Genres genresHelper;
44 
45 	private RatingsToStars ratingsToStars;
46 
47 	private TagFactory tagFactory;
48 
49 	/**
50 	 * @param tagFactory
51 	 */
setTagFactory(final TagFactory tagFactory)52 	public void setTagFactory(final TagFactory tagFactory) {
53 		this.tagFactory = tagFactory;
54 	}
55 
56 	/**
57 	 * @param ratingsToStars
58 	 */
setRatingsToStars(final RatingsToStars ratingsToStars)59 	public void setRatingsToStars(final RatingsToStars ratingsToStars) {
60 		this.ratingsToStars = ratingsToStars;
61 	}
62 
63 	/**
64 	 * @param genresHelper
65 	 */
setGenresHelper(final Genres genresHelper)66 	public void setGenresHelper(final Genres genresHelper) {
67 		this.genresHelper = genresHelper;
68 	}
69 
70 	/**
71 	 * Creates tag for given audio file
72 	 *
73 	 * @param ao
74 	 * @param file
75 	 * @param readRating
76 	 * @return
77 	 */
createTag(final ILocalAudioObject ao, final AudioFile file, boolean readRating)78 	ITag createTag(final ILocalAudioObject ao, final AudioFile file,
79 			boolean readRating) {
80 		ITag iTag = this.tagFactory.getNewTag();
81 		if (file != null) {
82 			Tag tag = file.getTag();
83 			iTag.setAlbum(getFirstTagValue(tag, FieldKey.ALBUM));
84 			iTag.setArtist(getFirstTagValue(tag, FieldKey.ARTIST));
85 			iTag.setComment(getFirstTagValue(tag, FieldKey.COMMENT));
86 			setGenreFromTag(iTag, getFirstTagValue(tag, FieldKey.GENRE),
87 					this.genresHelper);
88 			iTag.setTitle(getFirstTagValue(tag, FieldKey.TITLE));
89 			setTrackNumberFromTag(iTag, getFirstTagValue(tag, FieldKey.TRACK));
90 			setYearFromTag(iTag, getFirstTagValue(tag, FieldKey.YEAR));
91 			iTag.setLyrics(getFirstTagValue(tag, FieldKey.LYRICS));
92 			iTag.setComposer(getFirstTagValue(tag, FieldKey.COMPOSER));
93 			iTag.setAlbumArtist(getFirstTagValue(tag, FieldKey.ALBUM_ARTIST));
94 			setInternalImage(iTag, tag);
95 			setDate(iTag, tag);
96 			setDiscNumber(iTag, tag);
97 			if (readRating) {
98 				iTag.setStars(this.ratingsToStars
99 						.ratingToStars(getFirstTagValue(tag, FieldKey.RATING)));
100 			}
101 		}
102 		return iTag;
103 	}
104 
105 	/**
106 	 * Sets tag in given audio object
107 	 *
108 	 * @param ao
109 	 * @param file
110 	 * @return
111 	 */
setRatingInTag(ILocalAudioObject ao, AudioFile file)112 	public void setRatingInTag(ILocalAudioObject ao, AudioFile file) {
113 		if (ao != null && ao.getTag() != null && file != null) {
114 			Tag tag = file.getTag();
115 			ao.getTag().setStars(
116 					this.ratingsToStars.ratingToStars(getFirstTagValue(tag,
117 							FieldKey.RATING)));
118 		}
119 	}
120 
121 	/**
122 	 * Sets date from tag
123 	 *
124 	 * @param iTag
125 	 * @param tag
126 	 */
setDate(final ITag iTag, final org.jaudiotagger.tag.Tag tag)127 	private void setDate(final ITag iTag, final org.jaudiotagger.tag.Tag tag) {
128 		if (tag instanceof org.jaudiotagger.tag.vorbiscomment.VorbisCommentTag
129 				|| tag instanceof org.jaudiotagger.tag.flac.FlacTag) {
130 			iTag.setDate(DateUtils.parseRFC3339Date(getFirstTagValue(tag,
131 					"DATE")));
132 		} else if (tag instanceof org.jaudiotagger.tag.id3.ID3v24Tag) {
133 			iTag.setDate(DateUtils.parseRFC3339Date(getFirstTagValue(tag,
134 					"TDRC")));
135 		} else if (tag instanceof org.jaudiotagger.tag.id3.ID3v23Tag) {
136 			setDateFromID3v23Tag(iTag, tag);
137 		} else {
138 			iTag.setDate((DateTime) null);
139 		}
140 	}
141 
142 	/**
143 	 * Sets internal image
144 	 *
145 	 * @param iTag
146 	 * @param tag
147 	 */
setInternalImage(final ITag iTag, final org.jaudiotagger.tag.Tag tag)148 	private void setInternalImage(final ITag iTag,
149 			final org.jaudiotagger.tag.Tag tag) {
150 		boolean hasImage = false;
151 		if (tag != null) {
152 			try {
153 				hasImage = tag.hasField(FieldKey.COVER_ART.name());
154 			} catch (UnsupportedOperationException e) {
155 				Logger.info(e.getMessage());
156 				// Sometimes image is not supported (ID3v1). It's not a problem
157 			}
158 		}
159 		iTag.setInternalImage(hasImage);
160 	}
161 
162 	/**
163 	 * Sets year from tag
164 	 *
165 	 * @param tag
166 	 */
setYearFromTag(final ITag iTag, final String year)167 	private void setYearFromTag(final ITag iTag, final String year) {
168 		if (StringUtils.isEmpty(year)) {
169 			iTag.setYear(-1);
170 		} else {
171 			try {
172 				iTag.setYear(Integer.parseInt(year));
173 			} catch (NumberFormatException e) {
174 				iTag.setYear(-1);
175 			}
176 		}
177 	}
178 
179 	/**
180 	 * Sets track number from tag
181 	 *
182 	 * @param iTag
183 	 * @param track
184 	 */
setTrackNumberFromTag(final ITag iTag, final String track)185 	private void setTrackNumberFromTag(final ITag iTag, final String track) {
186 		String result = track;
187 		try {
188 			if (StringUtils.isEmpty(result)) {
189 				result = "-1";
190 			}
191 			// Certain tags are in the form of track number/total number of
192 			// tracks so check for this:
193 			if (result.contains("/")) {
194 				int separatorPosition;
195 				separatorPosition = result.indexOf('/');
196 				iTag.setTrackNumber(Integer.parseInt(result.substring(0,
197 						separatorPosition)));
198 			} else {
199 				iTag.setTrackNumber(Integer.parseInt(result));
200 			}
201 		} catch (NumberFormatException e) {
202 			iTag.setTrackNumber(-1);
203 		}
204 	}
205 
206 	/**
207 	 * Code taken from Jajuk http://jajuk.info - (Copyright (C) 2008) The Jajuk
208 	 * team Detects if Genre is a number and try to map the corresponding genre
209 	 * This should only happen with ID3 tags Sometimes, the style has this form
210 	 * : (nb)
211 	 *
212 	 * @param iTag
213 	 * @param genre
214 	 * @param genres
215 	 */
setGenreFromTag(final ITag iTag, final String genre, final Genres genres)216 	private void setGenreFromTag(final ITag iTag, final String genre,
217 			final Genres genres) {
218 		String result = genre;
219 		if (!StringUtils.isEmpty(result) && result.matches("\\(.*\\).*")) {
220 			result = result.substring(1, result.indexOf(')'));
221 			try {
222 				result = genres.getGenreForCode(Integer.parseInt(result));
223 			} catch (Exception e) {
224 				iTag.setGenre(""); // error, return unknown
225 			}
226 		}
227 		// If genre is a number mapping a known style, use this style
228 		try {
229 			int number = Integer.parseInt(result);
230 			if (number >= 0
231 					&& !StringUtils.isEmpty(genres.getGenreForCode(number))) {
232 				result = genres.getGenreForCode(Integer.parseInt(result));
233 			}
234 		} catch (NumberFormatException e) {
235 			// nothing wrong here
236 		}
237 		iTag.setGenre(result);
238 	}
239 
240 	/**
241 	 * @param iTag
242 	 * @param tag
243 	 */
setDateFromID3v23Tag(final ITag iTag, final org.jaudiotagger.tag.Tag tag)244 	private void setDateFromID3v23Tag(final ITag iTag,
245 			final org.jaudiotagger.tag.Tag tag) {
246 		// Set date from fields tag TYER and date/month tag TDAT
247 		DateMidnight c = null;
248 		String yearPart = getFirstTagValue(tag, "TYER");
249 		if (!StringUtils.isEmpty(yearPart)) {
250 			try {
251 				c = new DateMidnight().withYear(Integer.parseInt(yearPart))
252 						.withMonthOfYear(1).withDayOfMonth(1);
253 				String dateMonthPart = getFirstTagValue(tag, "TDAT");
254 				if (!StringUtils.isEmpty(dateMonthPart)
255 						&& dateMonthPart.length() >= 4) {
256 					c = c.withMonthOfYear(
257 							Integer.parseInt(dateMonthPart.substring(2, 4)))
258 							.withDayOfMonth(
259 									Integer.parseInt(dateMonthPart.substring(0,
260 											2)));
261 				}
262 			} catch (NumberFormatException e) {
263 				// Skip this date
264 			} catch (IllegalArgumentException e) {
265 				// Skip this date
266 			}
267 		}
268 
269 		if (c != null) {
270 			iTag.setDate(c);
271 		}
272 	}
273 
274 	/**
275 	 * Sets disc number
276 	 *
277 	 * @param iTag
278 	 * @param tag
279 	 */
setDiscNumber(final ITag iTag, final org.jaudiotagger.tag.Tag tag)280 	private void setDiscNumber(final ITag iTag,
281 			final org.jaudiotagger.tag.Tag tag) {
282 		// Disc Number
283 		String discNumberStr = getFirstTagValue(tag, FieldKey.DISC_NO);
284 		if (discNumberStr != null && !discNumberStr.trim().equals("")) {
285 			// try to get disc number parsing string
286 			try {
287 				iTag.setDiscNumber(Integer.parseInt(discNumberStr));
288 			} catch (NumberFormatException e) {
289 				// Sometimes disc number appears as relative to overall disc
290 				// count: "1/2"
291 				if (discNumberStr.contains("/")) {
292 					int separatorPosition = discNumberStr.indexOf('/');
293 					try {
294 						iTag.setDiscNumber(Integer.parseInt(discNumberStr
295 								.substring(0, separatorPosition)));
296 					} catch (NumberFormatException e2) {
297 						// Disc number seems not valid
298 					}
299 				}
300 			}
301 		}
302 	}
303 
getFirstTagValue(final Tag tag, final String field)304 	private String getFirstTagValue(final Tag tag, final String field) {
305 		if (tag != null && field != null) {
306 			try {
307 				return tag.getFirst(field);
308 			} catch (UnsupportedOperationException e) {
309 				Logger.info(e.getMessage());
310 			} catch (Exception e) {
311 				// Avoid any error caused by underlying tag reader
312 				Logger.error(e.getMessage());
313 			}
314 		}
315 		return null;
316 	}
317 
getFirstTagValue(final Tag tag, final FieldKey field)318 	private String getFirstTagValue(final Tag tag, final FieldKey field) {
319 		if (tag != null && field != null) {
320 			try {
321 				return tag.getFirst(field);
322 			} catch (UnsupportedOperationException e) {
323 				Logger.info(e.getMessage());
324 			} catch (Exception e) {
325 				// Avoid any error caused by underlying tag reader
326 				Logger.error(e.getMessage());
327 			}
328 		}
329 		return null;
330 	}
331 }
332