/*
* 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.audio.formats.mp3;
import java.io.IOException;
import java.util.List;
import com.tulskiy.musique.gui.model.FieldValues;
import com.tulskiy.musique.util.Util;
import org.jaudiotagger.audio.mp3.LameFrame;
import org.jaudiotagger.audio.mp3.MP3AudioHeader;
import org.jaudiotagger.audio.mp3.MP3File;
import org.jaudiotagger.audio.mp3.XingFrame;
import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.tag.KeyNotFoundException;
import org.jaudiotagger.tag.Tag;
import org.jaudiotagger.tag.TagField;
import org.jaudiotagger.tag.id3.*;
import org.jaudiotagger.tag.id3.framebody.AbstractFrameBodyTextInfo;
import org.jaudiotagger.tag.id3.framebody.FrameBodyCOMM;
import org.jaudiotagger.tag.id3.framebody.FrameBodyPOPM;
import org.jaudiotagger.tag.id3.framebody.FrameBodyTPOS;
import org.jaudiotagger.tag.id3.framebody.FrameBodyTRCK;
import org.jaudiotagger.tag.id3.valuepair.TextEncoding;
import com.tulskiy.musique.audio.AudioFileReader;
import com.tulskiy.musique.audio.formats.ape.APETagProcessor;
import com.tulskiy.musique.playlist.Track;
import com.tulskiy.musique.playlist.TrackData;
import davaguine.jmac.info.ID3Tag;
/**
* @Author: Denis Tulskiy
* @Date: 26.06.2009
*/
public class MP3FileReader extends AudioFileReader {
private static final int GAPLESS_DELAY = 529;
private APETagProcessor apeTagProcessor = new APETagProcessor();
public Track readSingle(Track track) {
TrackData trackData = track.getTrackData();
TextEncoding.getInstanceOf().setDefaultNonUnicode(defaultCharset.name());
ID3Tag.setDefaultEncoding(defaultCharset.name());
MP3File mp3File = null;
try {
mp3File = new MP3File(trackData.getFile(), MP3File.LOAD_ALL, true);
} catch (Exception ignored) {
System.out.println("Couldn't read file: " + trackData.getFile());
}
ID3v24Tag v24Tag = null;
if (mp3File != null) {
try {
v24Tag = mp3File.getID3v2TagAsv24();
if (v24Tag != null) {
copyCommonTagFields(v24Tag, track);
copySpecificTagFields(v24Tag, track);
}
ID3v1Tag id3v1Tag = mp3File.getID3v1Tag();
if (id3v1Tag != null) {
copyCommonTagFields(id3v1Tag, track);
}
} catch (IOException e) {
e.printStackTrace();
}
MP3AudioHeader mp3AudioHeader = mp3File.getMP3AudioHeader();
copyHeaderFields(mp3AudioHeader, track);
long totalSamples = trackData.getTotalSamples();
int enc_delay = GAPLESS_DELAY;
XingFrame xingFrame = mp3AudioHeader.getXingFrame();
if (xingFrame != null) {
LameFrame lameFrame = xingFrame.getLameFrame();
if (lameFrame != null) {
long length = totalSamples;
enc_delay += lameFrame.getEncDelay();
int enc_padding = lameFrame.getEncPadding() - GAPLESS_DELAY;
if (enc_padding < length)
length -= enc_padding;
if (totalSamples > length)
totalSamples = length;
} else {
totalSamples += GAPLESS_DELAY;
}
}
totalSamples -= enc_delay;
trackData.setTotalSamples(totalSamples);
}
// TODO review correctness of reading APETag only in case ID3 is missed
// for example, maybe useful to read and set those fields
// that are missed in ID3 but presented in APE
if (v24Tag == null) {
try {
apeTagProcessor.readAPEv2Tag(track);
}
catch (Exception ignored) {
}
}
return track;
}
public boolean isFileSupported(String ext) {
return ext.equalsIgnoreCase("mp3");
}
@Override
protected void copyCommonTagFields(Tag tag, Track track) throws IOException {
if (tag instanceof ID3v24Tag) {
ID3v24Tag v24Tag = (ID3v24Tag) tag;
for (FieldKey key : FieldKey.values()) {
setMusiqueTagFieldValues(track, key, v24Tag);
}
} else if (tag instanceof ID3v1Tag) {
ID3v1Tag id3v1Tag = (ID3v1Tag) tag;
TrackData trackData = track.getTrackData();
for (FieldKey key : FieldKey.values()) {
String val = id3v1Tag.getFirst(key);
if (!Util.isEmpty(val)) {
FieldValues tagFieldValues = trackData.getTagFieldValues(key);
if (tagFieldValues == null || tagFieldValues.isEmpty())
trackData.setTagFieldValues(key, val);
}
}
}
}
// @Override
// protected void copySpecificTagFields(Tag tag, Track track) {
// ID3v24Tag v24Tag = (ID3v24Tag) tag;
// }
// TODO review (T?? [but not TXXX] are only supported at the moment)
private void setMusiqueTagFieldValues(Track track, FieldKey key, ID3v24Tag tag) {
List<TagField> fields;
try {
fields = tag.getFields(key);
}
catch (KeyNotFoundException ignored) {
return;
}
for (TagField field : fields) {
ID3v24Frame frame = (ID3v24Frame) field;
if (frame.getBody() instanceof FrameBodyTRCK) {
FrameBodyTRCK body = (FrameBodyTRCK) frame.getBody();
if (FieldKey.TRACK.equals(key)) {
track.getTrackData().addTrack(body.getTrackNo());
}
else if (FieldKey.TRACK_TOTAL.equals(key)) {
track.getTrackData().addTrackTotal(body.getTrackTotal());
}
}
else if (frame.getBody() instanceof FrameBodyTPOS) {
FrameBodyTPOS body = (FrameBodyTPOS) frame.getBody();
if (FieldKey.DISC_NO.equals(key)) {
track.getTrackData().addDisc(body.getDiscNo());
}
else if (FieldKey.DISC_TOTAL.equals(key)) {
track.getTrackData().addDiscTotal(body.getDiscTotal());
}
}
else if (frame.getBody() instanceof FrameBodyCOMM) {
FrameBodyCOMM body = (FrameBodyCOMM) frame.getBody();
track.getTrackData().addComment(body.getText());
}
else if (frame.getBody() instanceof FrameBodyPOPM) {
FrameBodyPOPM body = (FrameBodyPOPM) frame.getBody();
track.getTrackData().addRating(String.valueOf(body.getRating()));
}
else if (frame.getBody() instanceof AbstractFrameBodyTextInfo) {
AbstractFrameBodyTextInfo body = (AbstractFrameBodyTextInfo) frame.getBody();
for (int i = 0; i < body.getNumberOfValues(); i++) {
track.getTrackData().addTagFieldValues(key, body.getValueAtIndex(i));
}
}
}
}
}