/*
* Copyright (c) 2008, 2009, 2010, 2011 Denis Tulskiy
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* version 3 along with this work. If not, see <http://www.gnu.org/licenses/>.
*/
package com.tulskiy.musique.playlist;
import java.io.File;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.Map.Entry;
import org.jaudiotagger.tag.FieldKey;
import com.tulskiy.musique.gui.model.FieldValues;
import com.tulskiy.musique.util.Util;
/**
* Author: Denis Tulskiy
* Date: 11/14/10
*/
public class TrackData implements Cloneable {
// generic jaudiotagger tag field values
private Map<FieldKey, FieldValues> tagFields = new HashMap<FieldKey, FieldValues>(5, 1f);
// common tag fields (to be displayed in TrackInfoDialog even if missed)
private static final FieldKey[] COMMON_TAG_FIELDS = {
FieldKey.ARTIST,
FieldKey.ALBUM_ARTIST,
FieldKey.TITLE,
FieldKey.ALBUM,
FieldKey.YEAR,
FieldKey.GENRE,
FieldKey.TRACK,
FieldKey.TRACK_TOTAL,
FieldKey.DISC_NO,
FieldKey.DISC_TOTAL,
FieldKey.RECORD_LABEL,
FieldKey.CATALOG_NO,
FieldKey.COMMENT,
FieldKey.RATING
};
private static Set<FieldKey> INTERNED_FIELDS = new LinkedHashSet<FieldKey>() {{
add(FieldKey.ARTIST);
add(FieldKey.ALBUM_ARTIST);
add(FieldKey.YEAR);
add(FieldKey.GENRE);
add(FieldKey.TRACK);
add(FieldKey.TRACK_TOTAL);
add(FieldKey.DISC_NO);
add(FieldKey.DISC_TOTAL);
add(FieldKey.RECORD_LABEL);
add(FieldKey.RATING);
}};
// song info
private int sampleRate;
private int channels;
private int bps;
private int bitrate;
private int subsongIndex;
private long startPosition;
private long totalSamples;
private String locationString;
private boolean cueEmbedded;
private String cueLocation;
private String codec;
private String encoder;
// runtime stuff
private String cueSheet;
private String trackNumberFormatted;
private String length;
private String fileName;
private String directory;
private long dateAdded;
private long lastModified;
public TrackData() {
}
public TrackData(URI location, int subsongIndex) {
locationString = location.toString();
setSubsongIndex(subsongIndex);
}
public TrackData copy() {
try {
TrackData copy = (TrackData) this.clone();
copy.tagFields = new EnumMap<FieldKey, FieldValues>(copy.tagFields);
return copy;
} catch (CloneNotSupportedException ignored) {
return null;
}
}
/**
* Merges all fields from newData to current instance
*
* @param newData TrackData to merge from
* @return current instance
*/
public TrackData merge(TrackData newData) {
// merge technical fields
Field[] fields = getClass().getDeclaredFields();
for (Field field : fields) {
try {
Object value = field.get(newData);
if (value != null && !(value instanceof Map<?, ?>)) {
field.set(this, value);
}
}
catch (IllegalAccessException ignored) {
}
}
// merge tag fields
Iterator<Entry<FieldKey, FieldValues>> entries = newData.getAllTagFieldValuesIterator();
while (entries.hasNext()) {
Entry<FieldKey, FieldValues> entry = entries.next();
addTagFieldValues(entry.getKey(), entry.getValue());
}
return this;
}
public void clearTags() {
tagFields.clear();
setCodec("");
}
// ------------------- meta methods ------------------- //
public Iterator<Entry<FieldKey, FieldValues>> getAllTagFieldValuesIterator() {
return tagFields.entrySet().iterator();
}
public FieldValues getTagFieldValues(FieldKey key) {
return tagFields.get(key);
}
public FieldValues getTagFieldValuesSafe(FieldKey key) {
FieldValues result = getTagFieldValues(key);
if (result == null) {
result = new FieldValues();
}
return result;
}
public void setTagFieldValues(FieldKey key, FieldValues values) {
if (values.isEmpty()) {
return;
}
// handle additional business logic
if (FieldKey.TRACK.equals(key)) {
String track = values.get(0);
trackNumberFormatted = (Util.isEmpty(track) ?
"" : new Formatter().format("%02d", Integer.parseInt(track)).toString()).intern();
}
// handle technical tags
if (FieldKey.ENCODER.equals(key)) {
setEncoder(values.get(0));
}
else if (FieldKey.COVER_ART.equals(key)) {
// TODO skipping, should be handled in its own way
}
// handle common cases
else {
if (INTERNED_FIELDS.contains(key)) {
FieldValues valuesOptimized = new FieldValues();
for (int i = 0; i < values.size(); i++) {
String value = values.get(i);
valuesOptimized.add(value == null ? null : value.intern());
}
tagFields.put(key, valuesOptimized);
}
else {
tagFields.put(key, values);
}
}
}
public void setTagFieldValues(FieldKey key, String value) {
setTagFieldValues(key, new FieldValues(value));
}
public void addTagFieldValues(FieldKey key, FieldValues values) {
FieldValues existingValues = getTagFieldValuesSafe(key);
existingValues.add(values);
setTagFieldValues(key, existingValues);
}
public void addTagFieldValues(FieldKey key, String value) {
FieldValues existingValues = getTagFieldValuesSafe(key);
existingValues.add(value);
setTagFieldValues(key, existingValues);
}
public String getFirstTagFieldValue(FieldKey key) {
FieldValues values = getTagFieldValues(key);
if (!FieldValues.isEmptyEx(values)) {
return values.get(0);
}
return null;
}
public void removeTagField(FieldKey key) {
tagFields.remove(key);
}
// ------------------- common methods ------------------- //
public String getArtist() {
return firstNotEmpty(FieldKey.ARTIST,
FieldKey.ALBUM_ARTIST,
FieldKey.BAND,
FieldKey.COMPOSER
);
}
private String firstNotEmpty(FieldKey... keys) {
for (FieldKey key : keys) {
String value = getFirstTagFieldValue(key);
if (!Util.isEmpty(value))
return value;
}
return null;
}
public void addArtist(String value) {
addTagFieldValues(FieldKey.ARTIST, value);
}
public String getAlbum() {
return getFirstTagFieldValue(FieldKey.ALBUM);
}
public void addAlbum(String value) {
addTagFieldValues(FieldKey.ALBUM, value);
}
public String getTitle() {
String title = getFirstTagFieldValue(FieldKey.TITLE);
if (Util.isEmpty(title))
if (isFile())
return getFileName();
else
return locationString;
else
return title;
}
public void addTitle(String value) {
addTagFieldValues(FieldKey.TITLE, value);
}
public String getAlbumArtist() {
return firstNotEmpty(FieldKey.ALBUM_ARTIST,
FieldKey.BAND, // foobar2000 uses BAND field as album artist in id3v2
FieldKey.ARTIST,
FieldKey.COMPOSER
);
}
public void addAlbumArtist(String value) {
addTagFieldValues(FieldKey.ALBUM_ARTIST, value);
}
public String getYear() {
return getFirstTagFieldValue(FieldKey.YEAR);
}
public void addYear(String value) {
addTagFieldValues(FieldKey.YEAR, value);
}
public String getGenre() {
return getFirstTagFieldValue(FieldKey.GENRE);
}
public FieldValues getGenres() {
return getTagFieldValuesSafe(FieldKey.GENRE);
}
public void addGenre(String value) {
addTagFieldValues(FieldKey.GENRE, value);
}
public String getComment() {
return getFirstTagFieldValue(FieldKey.COMMENT);
}
public void addComment(String value) {
addTagFieldValues(FieldKey.COMMENT, value);
}
public String getTrack() {
return getFirstTagFieldValue(FieldKey.TRACK);
}
public void addTrack(String value) {
addTagFieldValues(FieldKey.TRACK, value);
}
public void addTrack(Integer value) {
if (value != null) {
addTrack(value.toString());
}
}
public void setTrack(String value) {
setTagFieldValues(FieldKey.TRACK, value);
}
public void setTrack(Integer value) {
if (value != null) {
setTrack(value.toString());
}
}
/**
* @return track number formatted to two digits
*/
public String getTrackNumber() {
return trackNumberFormatted;
}
public String getTrackTotal() {
return getFirstTagFieldValue(FieldKey.TRACK_TOTAL);
}
public void addTrackTotal(String value) {
addTagFieldValues(FieldKey.TRACK_TOTAL, value);
}
public void addTrackTotal(Integer value) {
if (value != null) {
addTrackTotal(value.toString());
}
}
public void setTrackTotal(String value) {
setTagFieldValues(FieldKey.TRACK_TOTAL, value);
}
public void setTrackTotal(Integer value) {
if (value != null) {
setTrackTotal(value.toString());
}
}
public String getDisc() {
return getFirstTagFieldValue(FieldKey.DISC_NO);
}
public void addDisc(String value) {
addTagFieldValues(FieldKey.DISC_NO, value);
}
public void addDisc(Integer value) {
if (value != null) {
addDisc(value.toString());
}
}
public void setDisc(String value) {
setTagFieldValues(FieldKey.DISC_NO, value);
}
public void setDisc(Integer value) {
if (value != null) {
setDisc(value.toString());
}
}
public String getDiscTotal() {
return getFirstTagFieldValue(FieldKey.DISC_TOTAL);
}
public void addDiscTotal(String value) {
addTagFieldValues(FieldKey.DISC_TOTAL, value);
}
public void addDiscTotal(Integer value) {
if (value != null) {
addDiscTotal(value.toString());
}
}
public void setDiscTotal(String value) {
setTagFieldValues(FieldKey.DISC_TOTAL, value);
}
public void setDiscTotal(Integer value) {
if (value != null) {
setDiscTotal(value.toString());
}
}
public String getRecordLabel() {
return getFirstTagFieldValue(FieldKey.RECORD_LABEL);
}
public FieldValues getRecordLabels() {
return getTagFieldValuesSafe(FieldKey.RECORD_LABEL);
}
public void addRecordLabel(String value) {
addTagFieldValues(FieldKey.RECORD_LABEL, value);
}
public String getCatalogNo() {
return getFirstTagFieldValue(FieldKey.CATALOG_NO);
}
public FieldValues getCatalogNos() {
return getTagFieldValuesSafe(FieldKey.CATALOG_NO);
}
public void addCatalogNo(String value) {
addTagFieldValues(FieldKey.CATALOG_NO, value);
}
public String getRating() {
return getFirstTagFieldValue(FieldKey.RATING);
}
public void addRating(String value) {
addTagFieldValues(FieldKey.RATING, value);
}
// ------------------- cuesheet methods ------------------- //
public String getCueSheet() {
return cueSheet;
}
public void setCueSheet(String cueSheet) {
this.cueSheet = cueSheet;
}
public boolean isCueEmbedded() {
return cueEmbedded;
}
public void setCueEmbedded(boolean cueEmbedded) {
this.cueEmbedded = cueEmbedded;
}
public String getCueLocation() {
return cueLocation;
}
public void setCueLocation(String cueLocation) {
this.cueLocation = cueLocation;
}
public boolean isCue() {
return subsongIndex > 0;
}
// ------------------- technical methods ------------------- //
public String getLength() {
if (length == null)
length = Util.samplesToTime(totalSamples, sampleRate, 0);
return length;
}
public String getFileName() {
if (fileName == null) {
fileName = Util.removeExt(getFile().getName());
}
return fileName;
}
public int getSampleRate() {
return sampleRate;
}
public void setSampleRate(int sampleRate) {
this.sampleRate = sampleRate;
}
public int getChannels() {
return channels;
}
public String getChannelsAsString() {
switch (getChannels()) {
case 1:
return "Mono";
case 2:
return "Stereo";
default:
return getChannels() + " ch";
}
}
public void setChannels(int channels) {
this.channels = channels;
}
public int getBps() {
return bps;
}
public void setBps(int bps) {
this.bps = bps;
}
public int getBitrate() {
return bitrate;
}
public void setBitrate(int bitrate) {
this.bitrate = bitrate;
}
public int getSubsongIndex() {
return subsongIndex;
}
public void setSubsongIndex(int subsongIndex) {
this.subsongIndex = subsongIndex;
}
public long getStartPosition() {
return startPosition;
}
public void setStartPosition(long startPosition) {
this.startPosition = startPosition;
}
public long getTotalSamples() {
return totalSamples;
}
public void setTotalSamples(long totalSamples) {
this.totalSamples = totalSamples;
length = null;
}
public long getDateAdded() {
return dateAdded;
}
public void setDateAdded(long dateAdded) {
this.dateAdded = dateAdded;
}
public long getLastModified() {
return lastModified;
}
public void setLastModified(long lastModified) {
this.lastModified = lastModified;
}
public URI getLocation() {
if (locationString != null) {
try {
return new URI(locationString);
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
return null;
}
public void setLocation(String location) {
locationString = location;
}
public File getFile() {
return new File(getLocation());
}
public boolean isFile() {
return getLocation() != null && !isStream();
}
public boolean isStream() {
return getLocation() != null && "http".equals(getLocation().getScheme());
}
public String getCodec() {
return codec;
}
public void setCodec(String codec) {
this.codec = codec.intern();
}
public String getEncoder() {
return encoder;
}
public void setEncoder(String encoder) {
this.encoder= encoder.intern();
}
public String getDirectory() {
if (directory == null) {
directory = getFile().getParentFile().getName();
}
return directory;
}
public void setDirectory(String directory) {
this.directory = directory;
}
// ------------------- business logic methods ------------------- //
public void populateWithEmptyCommonTagFields() {
for (FieldKey key : COMMON_TAG_FIELDS) {
if (getTagFieldValuesSafe(key).isEmpty()) {
setTagFieldValues(key, "");
}
}
}
public void removeEmptyCommonTagFields() {
for (FieldKey key : COMMON_TAG_FIELDS) {
removeEmptyTagField(key);
}
}
public void removeEmptyTagField(FieldKey key) {
removeEmptyTagFieldValues(key);
if (Util.isEmpty(getFirstTagFieldValue(key))) {
removeTagField(key);
}
}
public void removeEmptyTagFields() {
for (FieldKey key : FieldKey.values()) {
removeEmptyTagField(key);
}
}
public void removeEmptyTagFieldValues(FieldKey key) {
FieldValues values = getTagFieldValues(key);
if (values != null) {
for (int i = 0; i < values.size(); i++) {
String value = values.get(i);
if (Util.isEmpty(value)) {
values.remove(i);
}
}
}
}
// ------------------- java object methods ------------------- //
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TrackData trackData = (TrackData) o;
return locationString.equals(trackData.locationString)
&& subsongIndex == trackData.subsongIndex;
}
@Override
public int hashCode() {
int result = subsongIndex;
result = 31 * result + (locationString != null ? locationString.hashCode() : 0);
return result;
}
}