From 9fee0ec4ca0c5b7a334cc29dbb58e76c7a4c736e Mon Sep 17 00:00:00 2001 From: Michiel Van Der Kolk Date: Mon, 11 Jul 2005 15:42:37 +0000 Subject: Songdb java version, source. only 1.5 compatible git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7101 a1c6a512-1295-4272-9138-f99709370657 --- .../shredzone/ifish/ltr/FormatDecodeException.java | 66 +++ songdbj/net/shredzone/ifish/ltr/LTR.java | 251 ++++++++++++ songdbj/net/shredzone/ifish/ltr/LTRmp3.java | 165 ++++++++ .../net/shredzone/ifish/ltr/OggFastFileStream.java | 249 ++++++++++++ songdbj/net/shredzone/ifish/ltr/TagAsf.java | 170 ++++++++ songdbj/net/shredzone/ifish/ltr/TagMp3v1.java | 184 +++++++++ songdbj/net/shredzone/ifish/ltr/TagMp3v2.java | 441 +++++++++++++++++++++ songdbj/net/shredzone/ifish/ltr/TagMp3v200.java | 373 +++++++++++++++++ songdbj/net/shredzone/ifish/ltr/TagOggVorbis.java | 207 ++++++++++ 9 files changed, 2106 insertions(+) create mode 100644 songdbj/net/shredzone/ifish/ltr/FormatDecodeException.java create mode 100644 songdbj/net/shredzone/ifish/ltr/LTR.java create mode 100644 songdbj/net/shredzone/ifish/ltr/LTRmp3.java create mode 100644 songdbj/net/shredzone/ifish/ltr/OggFastFileStream.java create mode 100644 songdbj/net/shredzone/ifish/ltr/TagAsf.java create mode 100644 songdbj/net/shredzone/ifish/ltr/TagMp3v1.java create mode 100644 songdbj/net/shredzone/ifish/ltr/TagMp3v2.java create mode 100644 songdbj/net/shredzone/ifish/ltr/TagMp3v200.java create mode 100644 songdbj/net/shredzone/ifish/ltr/TagOggVorbis.java (limited to 'songdbj/net/shredzone/ifish/ltr') diff --git a/songdbj/net/shredzone/ifish/ltr/FormatDecodeException.java b/songdbj/net/shredzone/ifish/ltr/FormatDecodeException.java new file mode 100644 index 0000000000..72522eaa4d --- /dev/null +++ b/songdbj/net/shredzone/ifish/ltr/FormatDecodeException.java @@ -0,0 +1,66 @@ +/* + * iFish -- An iRiver iHP jukebox database creation tool + * + * Copyright (c) 2004 Richard "Shred" Körber + * http://www.shredzone.net/go/ifish + * + *----------------------------------------------------------------------- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is IFISH. + * + * The Initial Developer of the Original Code is + * Richard "Shred" Körber. + * Portions created by the Initial Developer are Copyright (C) 2004 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** + */ + +package net.shredzone.ifish.ltr; + +/** + * This exception signals that the Tag could not be decoded for various + * reasons. + * + * @author Richard Körber <dev@shredzone.de> + * @version $Id$ + */ +public class FormatDecodeException extends Exception { + private static final long serialVersionUID = 3690758397339187507L; + + /** + * Constructor for the FormatDecodeException object + * + * @param msg A message + */ + public FormatDecodeException( String msg ) { + super( msg ); + } + +} diff --git a/songdbj/net/shredzone/ifish/ltr/LTR.java b/songdbj/net/shredzone/ifish/ltr/LTR.java new file mode 100644 index 0000000000..8a38676583 --- /dev/null +++ b/songdbj/net/shredzone/ifish/ltr/LTR.java @@ -0,0 +1,251 @@ +/* + * iFish -- An iRiver iHP jukebox database creation tool + * + * Copyright (c) 2004 Richard "Shred" Körber + * http://www.shredzone.net/go/ifish + * + *----------------------------------------------------------------------- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is IFISH. + * + * The Initial Developer of the Original Code is + * Richard "Shred" Körber. + * Portions created by the Initial Developer are Copyright (C) 2004 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** + */ + +package net.shredzone.ifish.ltr; + +import java.io.*; + +/** + * The Base Class for the Lightweight Tag Reader. LTR was made to read + * the basic tag information of MP3 (IDv1, IDv2), Ogg Vorbis and ASF/WMA + * files. It is lightweight because it is optimized for speed, and is + * only able to read the tag information, but unable to edit them. + * + * @author Richard Körber <dev@shredzone.de> + * @version $Id$ + */ +public abstract class LTR { + protected RandomAccessFile in; // The file to be checked + + /** + * Create a new LTR object. It is connected to the file to be decoded, + * by a RandomAccessFile. The cursor position will be changed during + * recognizing and decoding. + * + * @param in RandomAccessFile to be used + * @throws FormatDecodeException Description of the Exception + */ + public LTR( RandomAccessFile in ) + throws FormatDecodeException { + this.in = in; + try { + in.seek( 0 ); // To the beginning of file + } catch( IOException e ) { + throw new FormatDecodeException( "couldn't seek: " + e.toString() ); + } + } + + /** + * Create an LTR object for a file. If the file given, was not + * recognized or did not contain any tags, null will be returned. + * + * @param file File to open + * @return LTR to this file, or null + * @exception IOException Description of the Exception + */ + public static LTR create( File file ) { + RandomAccessFile in = null; + LTR result = null; + + try { + in = new RandomAccessFile( file, "r" ); + + try { + result = new TagOggVorbis( in ); + return result; + } catch( FormatDecodeException e ) {} + + try { + result = new TagMp3v2( in ); + return result; + } catch( FormatDecodeException e ) {} + + try { + result = new TagMp3v200( in ); + return result; + } catch( FormatDecodeException e ) {} + + try { + result = new TagAsf( in, file ); + return result; + }catch( FormatDecodeException e ) {} + + try { + // Always check ID3v1 *after* ID3v2, because a lot of ID3v2 + // files also contain ID3v1 tags with limited content. + result = new TagMp3v1( in ); + return result; + } catch( FormatDecodeException e ) {} + }catch(IOException e) { + return null; + } finally { + try { + if( in!=null ) in.close(); + } catch(IOException e) { + System.out.println("Failed to close file."); + } + } + + return null; + } + + /** + * Get the type of this file. This is usually the compression format + * itself (e.g. "OGG" or "MP3"). If there are different taggings for + * this format, the used tag format is appended after a slash (e.g. + * "MP3/id3v2"). + * + * @return The type + */ + public abstract String getType(); + + /** + * Get the artist. + * + * @return The artist (never null) + */ + public abstract String getArtist(); + + /** + * Get the album. + * + * @return The album (never null) + */ + public abstract String getAlbum(); + + /** + * Get the title. + * + * @return The title (never null) + */ + public abstract String getTitle(); + + /** + * Get the genre. + * + * @return The genre (never null) + */ + public abstract String getGenre(); + + /** + * Get the year. + * + * @return The year (never null) + */ + public abstract String getYear(); + + /** + * Get the comment. + * + * @return The comment (never null) + */ + public abstract String getComment(); + + /** + * Get the track. + * + * @return The track (never null) + */ + public abstract String getTrack(); + + /** + * Read a String of a certain length from the file. It will always use + * "ISO-8859-1" encoding! + * + * @param length Maximum number of bytes to read + * @return String that was read + * @throws IOException If EOF was already reached + */ + protected String readStringLen( int length ) + throws IOException { + return readStringLen( length, "ISO-8859-1" ); + } + + /** + * Read a String of a certain length from the file. The length will + * not be exceeded. No null termination is required. Anyhow an + * IOException will be thrown if the EOF was reached before invocation. + * + * @param length Maximum number of bytes to read + * @param charset Charset to be used + * @return String that was read + * @throws IOException If EOF was already reached + */ + protected String readStringLen( int length, String charset ) + throws IOException { + byte[] buf = new byte[length]; + int readlength = in.read( buf ); + if( readlength < 0 ) { + throw new IOException( "Unexpected EOF" ); + } + return new String( buf, 0, readlength, charset ); + } + + /** + * Return a string representation of the LTR content. + * + * @return String representation + */ + public String toString() { + StringBuffer buff = new StringBuffer(); + buff.append( getType() ); + buff.append( "[ART='" ); + buff.append( getArtist() ); + buff.append( "' ALB='" ); + buff.append( getAlbum() ); + buff.append( "' TIT='" ); + buff.append( getTitle() ); + buff.append( "' TRK='" ); + buff.append( getTrack() ); + buff.append( "' GEN='" ); + buff.append( getGenre() ); + buff.append( "' YR='" ); + buff.append( getYear() ); + buff.append( "' CMT='" ); + buff.append( getComment() ); + buff.append( "']" ); + return buff.toString(); + } + +} diff --git a/songdbj/net/shredzone/ifish/ltr/LTRmp3.java b/songdbj/net/shredzone/ifish/ltr/LTRmp3.java new file mode 100644 index 0000000000..1b31c405ba --- /dev/null +++ b/songdbj/net/shredzone/ifish/ltr/LTRmp3.java @@ -0,0 +1,165 @@ +/* + * iFish -- An iRiver iHP jukebox database creation tool + * + * Copyright (c) 2004 Richard "Shred" Körber + * http://www.shredzone.net/go/ifish + * + *----------------------------------------------------------------------- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is IFISH. + * + * The Initial Developer of the Original Code is + * Richard "Shred" Körber. + * Portions created by the Initial Developer are Copyright (C) 2004 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** + */ + +package net.shredzone.ifish.ltr; + +import java.io.*; + +/** + * The Base Class for mp3 decoding of the Lightweight Tag Reader. + * + * @author Richard Körber <dev@shredzone.de> + * @version $Id$ + */ +public abstract class LTRmp3 extends LTR { + + private final static String[] genres = { + //--- Genres as specified in ID3v1 --- + "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", + "Grunge", "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", + "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno", "Industrial", + "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack", + "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", + "Fusion", "Trance", "Classical", "Instrumental", "Acid", "House", + "Game", "Sound Clip", "Gospel", "Noise", "AlternRock", "Bass", + "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", + "Instrumental Rock", "Ethnic", "Gothic", "Darkwave", + "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream", + "Southern Rock", "Comedy", "Cult", "Gangsta", "Top 40", + "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret", + "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", + "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", + "Rock & Roll", "Hard Rock", + + //--- This are WinAmp extensions --- + "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebop", + "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", + "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", + "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", + "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", + "Primus", "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", + "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", + "Duet", "Punk Rock", "Drum Solo", "A capella", "Euro-House", "Dance Hall", + }; + + protected final String charsetV1 = "ISO-8859-1"; + protected final String charsetV2 = "ISO-8859-1"; + + /** + * Constructor for the LTRmp3 object + * + * @param in File to be used + * @throws FormatDecodeException Could not decode file + */ + public LTRmp3( RandomAccessFile in ) + throws FormatDecodeException { + super( in ); + } + + /** + * Decode the mp3 numerical Genre code and convert it to a human + * readable string. The genre is decoded according to the + * specifications found at www.id3.org, + * as well as the WinAmp extensions. + * + * @param id Genre ID + * @return ID String, null if the genre ID was unknown + */ + protected String decodeGenre( int id ) { + if( id>=genres.length ) return null; + return genres[id]; + } + + /** + * Read an ID3v2 integer. This is a 4 byte big endian value, which is + * always not syncsafe. + * + * @return The integer read. + * @throws IOException If there were not enough bytes in the file. + */ + protected int readInt() + throws IOException { + int val = 0; + for( int cnt = 4; cnt > 0; cnt-- ) { + val <<= 8; + val |= ( in.readByte() & 0xFF ); + } + return val; + } + + /** + * Read an ID3v2 syncsafe integer. This is a 4 byte big endian value + * with the bit 7 of each byte always being 0. + * + * @return The syncsafe integer read. + * @throws IOException If there were not enough bytes in the file. + */ + protected int readSyncsafeInt() + throws IOException { + int val = 0; + for( int cnt = 4; cnt > 0; cnt-- ) { + val <<= 7; + val |= ( readSyncsafe() & 0x7F ); + } + return val; + } + + /** + * Read a syncsafe byte. It is made sure that a byte is available in + * the file, and that bit 7 is 0. An IOException is thrown otherwise. + * + * @return The byte read. + * @throws IOException If premature EOF was reached or byte was not + * syncsafe. + */ + protected byte readSyncsafe() + throws IOException { + byte read = in.readByte(); + if(( read & 0x80 ) != 0 ) { + throw new IOException( "not syncsafe" ); + } + return read; + } + +} diff --git a/songdbj/net/shredzone/ifish/ltr/OggFastFileStream.java b/songdbj/net/shredzone/ifish/ltr/OggFastFileStream.java new file mode 100644 index 0000000000..f86699b0f1 --- /dev/null +++ b/songdbj/net/shredzone/ifish/ltr/OggFastFileStream.java @@ -0,0 +1,249 @@ +/* + * iFish -- An iRiver iHP jukebox database creation tool + * + * Copyright (c) 2004 Richard "Shred" Körber + * http://www.shredzone.net/go/ifish + * + *----------------------------------------------------------------------- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is IFISH. + * + * The Initial Developer of the Original Code is + * Richard "Shred" Körber. + * Portions created by the Initial Developer are Copyright (C) 2004 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** + */ + +package net.shredzone.ifish.ltr; + +import java.io.*; +import java.util.*; + +import de.jarnbjo.ogg.*; + +/** + * Replacement file reader class. The original J-Ogg FileStream has the + * major disadvantage that it reads the entire file, even though we just + * need a few byte from it. This FastFileStream will only read as little + * information as possible. + * + * @author Richard Körber <dev@shredzone.de> + * @version $Id$ + */ +public class OggFastFileStream implements PhysicalOggStream { + private InputStream sourceStream; + private boolean closed = false; + private int contentLength = 0; + private int position = 0; + private HashMap logicalStreams = new HashMap(); + private OggPage firstPage; + + /** + * Constructor for the OggFastFileStream object + * + * @param in RandomAccessFile to be read + * @throws OggFormatException Bad format + * @throws IOException IO error + */ + public OggFastFileStream( RandomAccessFile in ) + throws OggFormatException, IOException { + this.sourceStream = new RandomAdapterInputStream( in ); + contentLength = (int) in.length(); + firstPage = OggPage.create( sourceStream ); + position += firstPage.getTotalLength(); + LogicalOggStreamImpl los = new LogicalOggStreamImpl( this, firstPage.getStreamSerialNumber() ); + logicalStreams.put( new Integer( firstPage.getStreamSerialNumber() ), los ); + los.checkFormat( firstPage ); + } + + /** + * Get a collection of the logical streams. + * + * @return Collection + */ + public Collection getLogicalStreams() { + return logicalStreams.values(); + } + + /** + * Checks if the file is open. + * + * @return true: open, false: closed + */ + public boolean isOpen() { + return !closed; + } + + /** + * Closes the stream + * + * @throws IOException IO error + */ + public void close() + throws IOException { + closed = true; + sourceStream.close(); + } + + /** + * Get the content length + * + * @return The content length + */ + public int getContentLength() { + return contentLength; + } + + /** + * Get the current position + * + * @return Position + */ + public int getPosition() { + return position; + } + + /** + * Get an OggPage. + * + * @param index Index to be fetched + * @return The oggPage value + * @throws IOException IO Error + */ + public OggPage getOggPage( int index ) + throws IOException { + if( firstPage != null ) { + OggPage tmp = firstPage; + firstPage = null; + return tmp; + } else { + OggPage page = OggPage.create( sourceStream ); + position += page.getTotalLength(); + return page; + } + } + + /** + * Move the stream to a certain time position. + * + * @param granulePosition The new position + * @throws IOException + */ + public void setTime( long granulePosition ) + throws IOException { + throw new UnsupportedOperationException( "not supported" ); + } + + /** + * Is this FileStream seekable? We pretend we are not, so J-Ogg + * will not get some stupid thoughts... ;) + * + * @return false + */ + public boolean isSeekable() { + return false; + } + +/*--------------------------------------------------------------------*/ + + /** + * This class repairs a design flaw in JDK1.0. A RandomAccessFile + * is not derived from InputStream, though it provides the same API. + * This Adapter gives an InputStream view of a Random Access File. + *

+ * For a detailed method description, see InputStream. + */ + private static class RandomAdapterInputStream extends InputStream { + private RandomAccessFile rf; + + /** + * Create a new Adapter. + * + * @param rf RandomAccessFile to be used + */ + public RandomAdapterInputStream( RandomAccessFile rf ) { + this.rf = rf; + } + + /** + * Read a byte. + * + * @return Read byte or -1. + */ + public int read() throws IOException { + return rf.read(); + } + + /** + * Read a byte array. + * + * @param b Byte array to be read + * @return Number of bytes read or -1 + */ + public int read( byte[] b) throws IOException { + return rf.read(b); + } + + /** + * Read into a byte array. + * + * @param b Byte array to be read + * @param off Starting offset + * @param len Length + * @return Number of bytes read or -1 + */ + public int read( byte[] b, int off, int len) throws IOException { + return rf.read( b, off, len ); + } + + /** + * Skip a number of bytes in forward direction. + * + * @param n Number of bytes to skip + * @return Number of bytes skipped, or -1 + */ + public long skip( long n ) throws IOException { + return rf.skipBytes( (int) n ); + } + + /** + * Return the number of available bytes. Here it is the number of + * bytes remaining until EOF. + * + * @return Number of bytes available. + */ + public int available() throws IOException { + return (int) (rf.length() - rf.getFilePointer()); + } + + } + +} diff --git a/songdbj/net/shredzone/ifish/ltr/TagAsf.java b/songdbj/net/shredzone/ifish/ltr/TagAsf.java new file mode 100644 index 0000000000..fc68789345 --- /dev/null +++ b/songdbj/net/shredzone/ifish/ltr/TagAsf.java @@ -0,0 +1,170 @@ +/* + * iFish -- An iRiver iHP jukebox database creation tool + * + * Copyright (c) 2004 Richard "Shred" Körber + * http://www.shredzone.net/go/ifish + * + *----------------------------------------------------------------------- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is IFISH. + * + * The Initial Developer of the Original Code is + * Richard "Shred" Körber. + * Portions created by the Initial Developer are Copyright (C) 2004 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** + */ + +package net.shredzone.ifish.ltr; + +import java.io.*; +import java.util.List; + +import de.jarnbjo.ogg.*; +import de.jarnbjo.vorbis.*; +import entagged.audioformats.AudioFile; +import entagged.audioformats.Tag; +import entagged.audioformats.asf.AsfFileReader; +import entagged.audioformats.exceptions.CannotReadException; + +/** + * Decodes an ASF/WMA stream. It uses parts of the + * Entagged software, which is + * copyrighted by the Entagged Development Team, and published under + * GPL. + *

+ * NOTE that due to the fact that Entagged is GPL, you MUST + * remove all entagged sources and this class file if you decide to use + * the LGPL or MPL part of the iFish licence! + * + * @author Richard Körber <dev@shredzone.de> + * @version $Id$ + */ +public class TagAsf extends LTR { + private final Tag tag; + + /** + * Create a new TagAsf object. + * + * @param in File to read + * @param file Reference to the file itself + * @throws FormatDecodeException Couldn't decode this file + */ + public TagAsf( RandomAccessFile in, File file ) + throws FormatDecodeException { + super( in ); + + try { + final AsfFileReader afr = new AsfFileReader(); + final AudioFile af = afr.read( file, in ); + tag = af.getTag(); + }catch( CannotReadException e ) { + throw new FormatDecodeException( "could not decode file: " + e.toString() ); + }catch( RuntimeException e ) { + throw new FormatDecodeException( "error decoding file: " + e.toString() ); + } + } + + /** + * Get the type of this file. This is usually the compression format + * itself (e.g. "OGG" or "MP3"). If there are different taggings for + * this format, the used tag format is appended after a slash (e.g. + * "MP3/id3v2"). + * + * @return The type + */ + public String getType() { + return "ASF"; + } + + /** + * Get the artist. + * + * @return The artist (never null) + */ + public String getArtist() { + return tag.getFirstArtist().trim(); + } + + /** + * Get the album. + * + * @return The album (never null) + */ + public String getAlbum() { + return tag.getFirstAlbum().trim(); + } + + /** + * Get the title. + * + * @return The title (never null) + */ + public String getTitle() { + return tag.getFirstTitle().trim(); + } + + /** + * Get the genre. + * + * @return The genre (never null) + */ + public String getGenre() { + return tag.getFirstGenre().trim(); + } + + /** + * Get the year. + * + * @return The year (never null) + */ + public String getYear() { + return tag.getFirstYear().trim(); + } + + /** + * Get the comment. + * + * @return The comment (never null) + */ + public String getComment() { + return tag.getFirstComment().trim(); + } + + /** + * Get the track. + * + * @return The track (never null) + */ + public String getTrack() { + return tag.getFirstTrack().trim(); + } + +} diff --git a/songdbj/net/shredzone/ifish/ltr/TagMp3v1.java b/songdbj/net/shredzone/ifish/ltr/TagMp3v1.java new file mode 100644 index 0000000000..9d3182260a --- /dev/null +++ b/songdbj/net/shredzone/ifish/ltr/TagMp3v1.java @@ -0,0 +1,184 @@ +/* + * iFish -- An iRiver iHP jukebox database creation tool + * + * Copyright (c) 2004 Richard "Shred" Körber + * http://www.shredzone.net/go/ifish + * + *----------------------------------------------------------------------- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is IFISH. + * + * The Initial Developer of the Original Code is + * Richard "Shred" Körber. + * Portions created by the Initial Developer are Copyright (C) 2004 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** + */ + +package net.shredzone.ifish.ltr; + +import java.io.*; + +/** + * Decodes an MP3 file with ID3v1 tag. The file is compliant to the + * specifications found at www.id3.org. + * + * @author Richard Körber <dev@shredzone.de> + * @version $Id$ + */ +public class TagMp3v1 extends LTRmp3 { + private String artist = ""; + private String comment = ""; + private String title = ""; + private String album = ""; + private String year = ""; + private String track = ""; + private String genre = ""; + + /** + * Create a new TagMp3v1 object. + * + * @param in File to be read + * @throws FormatDecodeException Description of the Exception + */ + public TagMp3v1( RandomAccessFile in ) + throws FormatDecodeException { + super( in ); + + try { + //--- Decode header --- + in.seek( in.length() - 128 ); // To the place where the tag lives + if( !readStringLen( 3 ).equals( "TAG" ) ) { + throw new FormatDecodeException( "not an id3v1 tag" ); + } + + title = readStringLen( 30, charsetV1 ).trim(); + artist = readStringLen( 30, charsetV1 ).trim(); + album = readStringLen( 30, charsetV1 ).trim(); + year = readStringLen( 4, charsetV1 ).trim(); + comment = readStringLen( 28, charsetV1 ); + + byte[] sto = new byte[2]; + in.readFully( sto ); + if( sto[0] == 0x00 ) { + // ID3v1.1 + track = ( sto[1]>0 ? String.valueOf(sto[1]) : "" ); + } else { + // ID3v1.0 + comment += new String( sto, charsetV1 ); + } + comment = comment.trim(); + + genre = decodeGenre( in.readUnsignedByte() ); + if( genre==null ) genre=""; + + } catch( IOException e ) { + throw new FormatDecodeException( "could not decode file: " + e.toString() ); + }catch( RuntimeException e ) { + throw new FormatDecodeException( "error decoding file: " + e.toString() ); + } + } + + /** + * Get the type of this file. This is usually the compression format + * itself (e.g. "OGG" or "MP3"). If there are different taggings for + * this format, the used tag format is appended after a slash (e.g. + * "MP3/id3v2"). + * + * @return The type + */ + public String getType() { + return "MP3/id3v1"; + } + + /** + * Get the artist. + * + * @return The artist (never null) + */ + public String getArtist() { + return artist; + } + + /** + * Get the album. + * + * @return The album (never null) + */ + public String getAlbum() { + return album; + } + + /** + * Get the title. + * + * @return The title (never null) + */ + public String getTitle() { + return title; + } + + /** + * Get the genre. + * + * @return The genre (never null) + */ + public String getGenre() { + return genre; + } + + /** + * Get the year. + * + * @return The year (never null) + */ + public String getYear() { + return year; + } + + /** + * Get the comment. + * + * @return The comment (never null) + */ + public String getComment() { + return comment; + } + + /** + * Get the track. + * + * @return The track (never null) + */ + public String getTrack() { + return track; + } + +} diff --git a/songdbj/net/shredzone/ifish/ltr/TagMp3v2.java b/songdbj/net/shredzone/ifish/ltr/TagMp3v2.java new file mode 100644 index 0000000000..689dd21618 --- /dev/null +++ b/songdbj/net/shredzone/ifish/ltr/TagMp3v2.java @@ -0,0 +1,441 @@ +/* + * iFish -- An iRiver iHP jukebox database creation tool + * + * Copyright (c) 2004 Richard "Shred" Körber + * http://www.shredzone.net/go/ifish + * + *----------------------------------------------------------------------- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is IFISH. + * + * The Initial Developer of the Original Code is + * Richard "Shred" Körber. + * Portions created by the Initial Developer are Copyright (C) 2004 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** + */ + +package net.shredzone.ifish.ltr; + +import java.io.*; +import java.util.regex.*; + +/** + * Decodes an MP3 file with ID3v2 tag. The file is compliant to the + * specifications found at www.id3.org, + * V2.4.0 and V2.3.0. ID3 V2.2.0 is handled in a separate tag handler. + * Anyhow it has certain limitations regarding tag frames which could + * not be used or entirely used to iFish, e.g. multiple genres. + * + * @author Richard Körber <dev@shredzone.de> + * @version $Id$ + */ +public class TagMp3v2 extends LTRmp3 { + private int globalSize; + private Pattern patGenre; + private boolean v230 = false; // V2.3.0 detected + + private String artist = ""; + private String comment = ""; + private String title = ""; + private String album = ""; + private String year = ""; + private String track = ""; + private String genre = ""; + + /** + * Create a new TagMp3v2 instance. + * + * @param in File to be read + * @throws FormatDecodeException Couldn't decode this file + */ + public TagMp3v2( RandomAccessFile in ) + throws FormatDecodeException { + super( in ); + + patGenre = Pattern.compile( "\\((\\d{1,3})\\).*" ); + + try { + //--- Decode header --- + if( !readStringLen( 3 ).equals( "ID3" ) ) { + throw new FormatDecodeException( "not an id3v2 tag" ); + } + + byte version = in.readByte(); + byte revision = in.readByte(); + + if( version==0xFF || revision==0xFF ) { + throw new FormatDecodeException( "not an id3v2 tag" ); + } + + if( version<=0x02 || version>0x04 ) { + throw new FormatDecodeException( "unable to decode ID3v2."+version+"."+revision ); + } + + byte flags = in.readByte(); + v230 = (version==0x03); + globalSize = readSyncsafeInt() + 10; + + //--- Skip extended header --- + if( ( flags & 0x40 ) != 0 ) { + int ehsize = readAutoInt(); + if( ehsize < 6 ) { + throw new FormatDecodeException( "extended header too small" ); + } + in.skipBytes( ehsize - 4 ); + } + + //--- Read all frames --- + Frame frm; + while( ( frm = readFrame() ) != null ) { + String type = frm.getType(); + String[] answer; + + if( type.equals( "TOPE" ) || type.equals( "TPE1" ) ) { + + answer = frm.getAsStringEnc(); + artist = (answer.length>0 ? answer[0].trim() : ""); + + } else if( type.equals( "COMM" ) ) { + + answer = frm.getAsStringEnc(); + comment = (answer.length>1 ? answer[1].trim() : ""); + + } else if( type.equals( "TIT2" ) ) { + + answer = frm.getAsStringEnc(); + title =(answer.length>0 ? answer[0].trim() : ""); + + } else if( type.equals( "TALB" ) ) { + + answer = frm.getAsStringEnc(); + album = (answer.length>0 ? answer[0].trim() : ""); + + } else if( type.equals( "TYER" ) ) { + + answer = frm.getAsStringEnc(); + year = (answer.length>0 ? answer[0].trim() : ""); + + } else if( type.equals( "TRCK" ) ) { + + answer = frm.getAsStringEnc(); + track = (answer.length>0 ? answer[0].trim() : ""); + + } else if( type.equals( "TCON" ) ) { + + answer = frm.getAsStringEnc(); + if( answer.length>0 ) { + genre = answer[0].trim(); + Matcher mat = patGenre.matcher( genre ); + if( mat.matches() ) { + genre = decodeGenre( Integer.parseInt( mat.group( 1 ) ) ); + if( genre==null ) genre=""; + } + } + } + } + + //--- Footer frame? --- + if( ( flags & 0x10 ) != 0 ) { + in.skipBytes( 10 ); // Then skip it + } + // Position is now the start of the MP3 data + + } catch( IOException e ) { + throw new FormatDecodeException( "could not decode file: " + e.toString() ); + }catch( RuntimeException e ) { + throw new FormatDecodeException( "error decoding file: " + e.toString() ); + } + } + + /** + * Get the type of this file. This is usually the compression format + * itself (e.g. "OGG" or "MP3"). If there are different taggings for + * this format, the used tag format is appended after a slash (e.g. + * "MP3/id3v2"). + * + * @return The type + */ + public String getType() { + return ( v230 ? "MP3/id3v2.3.0" : "MP3/id3v2.4.0" ); + } + + /** + * Get the artist. + * + * @return The artist (never null) + */ + public String getArtist() { + return artist; + } + + /** + * Get the album. + * + * @return The album (never null) + */ + public String getAlbum() { + return album; + } + + /** + * Get the title. + * + * @return The title (never null) + */ + public String getTitle() { + return title; + } + + /** + * Get the genre. + * + * @return The genre (never null) + */ + public String getGenre() { + return genre; + } + + /** + * Get the year. + * + * @return The year (never null) + */ + public String getYear() { + return year; + } + + /** + * Get the comment. + * + * @return The comment (never null) + */ + public String getComment() { + return comment; + } + + /** + * Get the track. + * + * @return The track (never null) + */ + public String getTrack() { + return track; + } + + /** + * Read an ID3v2 integer. This is a 4 byte big endian value, which is + * only syncsafe for ID3v2.4 or higher, but not for ID3v2.3. + * + * @return The integer read. + * @throws IOException If there were not enough bytes in the file. + */ + protected int readAutoInt() + throws IOException { + return( v230 ? readInt() : readSyncsafeInt() ); + } + + /** + * Read a tag frame. A Frame object will be returned, or null if no + * more frames were available. + * + * @return The next frame, or null + * @throws IOException If premature EOF was reached. + */ + protected Frame readFrame() + throws IOException { + if( in.getFilePointer() >= globalSize ) { + return null; + } + + //--- Get the type --- + String type = readStringLen( 4 ); + if( type.charAt(0)==0 ) { // Optional padding frame + in.skipBytes( (int) ( globalSize - in.getFilePointer() ) );// Skip it... + return null; // Return null + } + + //--- Read the frame --- + int size = readAutoInt(); + byte flag1 = in.readByte(); + byte flag2 = in.readByte(); + + //--- Read the content --- + // Stay within reasonable boundaries. If the data part is bigger than + // 16K, it's not really useful for us, so we will keep the frame empty. + byte[] data = null; + if( size<=16384 ) { + data = new byte[size]; + int rlen = in.read( data ); + if( rlen != size ) { + throw new IOException( "unexpected EOF" ); + } + }else { + in.skipBytes( size ); + } + + //--- Return the frame --- + return new Frame( type, size, flag1, flag2, data ); + } + +/*--------------------------------------------------------------------*/ + + /** + * This class contains a ID3v2 frame. + */ + private static class Frame { + private final String charset; + private final String type; +// private final int size; +// private final byte flag1; + private final byte flag2; + private byte[] data; + private boolean decoded = false; + + /** + * Constructor for the Frame object + * + * @param type Frame type + * @param size Frame size + * @param flag1 Flag 1 + * @param flag2 Flag 2 + * @param data Frame content, may be null + */ + public Frame( String type, int size, byte flag1, byte flag2, byte[] data ) { + charset = "ISO-8859-1"; + this.type = type; +// Currently unused... +// this.size = size; +// this.flag1 = flag1; + this.flag2 = flag2; + this.data = data; + } + + /** + * Get the type. + * + * @return The type of this Frame + */ + public String getType() { + return type; + } + + /** + * Return the Frame content as String. This method is to be used for + * strings without a leading encoding byte. This machine's default + * encoding will be used instead. + * + * @return Encoded string + * @throws FormatDecodeException Could not read or decode frame + */ + public String getAsString() + throws FormatDecodeException { + if(data==null) return null; + decode(); + return new String( data ); + } + + /** + * Return the Frame content as encoded String. The first byte will + * contain the encoding type, following the string itself. Multiple + * strings are null-terminated. An array of all strings found, will + * be returned. + * + * @return Array of all strings. Might be an empty array! + * @throws FormatDecodeException Could not read or decode frame + */ + public String[] getAsStringEnc() + throws FormatDecodeException { + if( data==null ) return new String[0]; + decode(); + if( data.length==0 ) return new String[0]; + int len = data.length - 1; + String result = ""; + try { + switch ( data[0] ) { + case 0x00: + result = new String( data, 1, len, charset ); + break; + case 0x01: + result = new String( data, 1, len, "UTF-16" ); + break; + case 0x02: + result = new String( data, 1, len, "UTF-16BE" ); + break; + case 0x03: + result = new String( data, 1, len, "UTF-8" ); + break; + default: + throw new FormatDecodeException( "unknown encoding of frame " + type ); + } + }catch( UnsupportedEncodingException e ) { + throw new FormatDecodeException( "Java misses a basic encoding!?" ); + } + return result.split( "\0" ); + } + + /** + * Decode a frame, unless already decoded. If the frame was + * unsynchronized, it will be synchronized here. Compression and + * encryption is not supported yet. You can invoke this method + * several times without any effect. + * + * @throws FormatDecodeException Description of the Exception + */ + private void decode() + throws FormatDecodeException { + if( decoded ) return; + decoded = true; + + if( ( flag2 & 0x02 ) != 0 ) { // Unsynchronize + byte decoded[] = new byte[data.length]; + int pos = 0; + for( int ix = 0; ix < data.length - 1; ix++ ) { + decoded[pos++] = data[ix]; + if( data[ix] == 0xFF && data[ix + 1] == 0x00 ) { + ix++; + } + } + data = new byte[pos]; + System.arraycopy( decoded, 0, data, 0, pos ); + } + + if( ( flag2 & 0x08 ) != 0 ) { // Compression + throw new FormatDecodeException( "sorry, compression is not yet supported" ); + } + + if( ( flag2 & 0x04 ) != 0 ) { // Encryption + throw new FormatDecodeException( "sorry, encryption is not yet supported" ); + } + } + + } + +} diff --git a/songdbj/net/shredzone/ifish/ltr/TagMp3v200.java b/songdbj/net/shredzone/ifish/ltr/TagMp3v200.java new file mode 100644 index 0000000000..28e623f962 --- /dev/null +++ b/songdbj/net/shredzone/ifish/ltr/TagMp3v200.java @@ -0,0 +1,373 @@ +/* + * iFish -- An iRiver iHP jukebox database creation tool + * + * Copyright (c) 2004 Richard "Shred" Körber + * http://www.shredzone.net/go/ifish + * + *----------------------------------------------------------------------- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is IFISH. + * + * The Initial Developer of the Original Code is + * Richard "Shred" Körber. + * Portions created by the Initial Developer are Copyright (C) 2004 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** + */ + +package net.shredzone.ifish.ltr; + +import java.io.*; +import java.util.regex.*; + +/** + * Decodes an MP3 file with old ID3v2.00 tag. The file is compliant to the + * specifications found at www.id3.org. + * Only ID3 V2.00 up to v2.2.0 is handled here. Newer versions are separately + * handled in TagMp3v2. + * + * @author Richard Körber <dev@shredzone.de> + * @version $Id$ + */ +public class TagMp3v200 extends LTRmp3 { + private int globalSize; + private Pattern patGenre; + + private String artist = ""; + private String comment = ""; + private String title = ""; + private String album = ""; + private String year = ""; + private String track = ""; + private String genre = ""; + + /** + * Create a new TagMp3v200 instance. + * + * @param in File to be read + * @throws FormatDecodeException Couldn't decode this file + */ + public TagMp3v200( RandomAccessFile in ) + throws FormatDecodeException { + super( in ); + + patGenre = Pattern.compile( "\\((\\d{1,3})\\).*" ); + + try { + //--- Decode header --- + if( !readStringLen( 3 ).equals( "ID3" ) ) { + throw new FormatDecodeException( "not an id3v2 tag" ); + } + + byte version = in.readByte(); + byte revision = in.readByte(); + + if( version!=0x02 || revision==0xFF ) { + throw new FormatDecodeException( "not an id3v2.2.0 tag" ); + } + + byte flags = in.readByte(); + globalSize = readSyncsafeInt() + 10; + + //--- Read all frames --- + Frame frm; + while( ( frm = readFrame() ) != null ) { + String type = frm.getType(); + String[] answer; + + if( type.equals( "TOA" ) || type.equals( "TP1" ) ) { + + answer = frm.getAsStringEnc(); + artist = (answer.length>0 ? answer[0].trim() : ""); + + } else if( type.equals( "COM" ) ) { + + answer = frm.getAsStringEnc(); + comment = (answer.length>1 ? answer[1].trim() : ""); + + } else if( type.equals( "TT2" ) ) { + + answer = frm.getAsStringEnc(); + title =(answer.length>0 ? answer[0].trim() : ""); + + } else if( type.equals( "TAL" ) ) { + + answer = frm.getAsStringEnc(); + album = (answer.length>0 ? answer[0].trim() : ""); + + } else if( type.equals( "TYE" ) ) { + + answer = frm.getAsStringEnc(); + year = (answer.length>0 ? answer[0].trim() : ""); + + } else if( type.equals( "TRK" ) ) { + + answer = frm.getAsStringEnc(); + track = (answer.length>0 ? answer[0].trim() : ""); + + } else if( type.equals( "TCO" ) ) { + + answer = frm.getAsStringEnc(); + if( answer.length>0 ) { + genre = answer[0].trim(); + Matcher mat = patGenre.matcher( genre ); + if( mat.matches() ) { + genre = decodeGenre( Integer.parseInt( mat.group( 1 ) ) ); + if( genre==null ) genre=""; + } + } + } + } + + // Position is now the start of the MP3 data + + } catch( IOException e ) { + throw new FormatDecodeException( "could not decode file: " + e.toString() ); + }catch( RuntimeException e ) { + throw new FormatDecodeException( "error decoding file: " + e.toString() ); + } + } + + /** + * Get the type of this file. This is usually the compression format + * itself (e.g. "OGG" or "MP3"). If there are different taggings for + * this format, the used tag format is appended after a slash (e.g. + * "MP3/id3v2"). + * + * @return The type + */ + public String getType() { + return "MP3/id3v2.2.0"; + } + + /** + * Get the artist. + * + * @return The artist (never null) + */ + public String getArtist() { + return artist; + } + + /** + * Get the album. + * + * @return The album (never null) + */ + public String getAlbum() { + return album; + } + + /** + * Get the title. + * + * @return The title (never null) + */ + public String getTitle() { + return title; + } + + /** + * Get the genre. + * + * @return The genre (never null) + */ + public String getGenre() { + return genre; + } + + /** + * Get the year. + * + * @return The year (never null) + */ + public String getYear() { + return year; + } + + /** + * Get the comment. + * + * @return The comment (never null) + */ + public String getComment() { + return comment; + } + + /** + * Get the track. + * + * @return The track (never null) + */ + public String getTrack() { + return track; + } + + /** + * Read a tag frame. A Frame object will be returned, or null if no + * more frames were available. + * + * @return The next frame, or null + * @throws IOException If premature EOF was reached. + */ + protected Frame readFrame() + throws IOException { + if( in.getFilePointer() >= globalSize ) { + return null; + } + + //--- Get the type --- + String type = readStringLen( 3 ); + if( type.charAt(0)==0 ) { // Optional padding frame + in.skipBytes( (int) ( globalSize - in.getFilePointer() ) );// Skip it... + return null; // Return null + } + + //--- Read the frame --- + int size = read3Int(); + + //--- Read the content --- + // Stay within reasonable boundaries. If the data part is bigger than + // 16K, it's not really useful for us, so we will keep the frame empty. + byte[] data = null; + if( size<=16384 ) { + data = new byte[size]; + int rlen = in.read( data ); + if( rlen != size ) { + throw new IOException( "unexpected EOF" ); + } + }else { + in.skipBytes( size ); + } + + //--- Return the frame --- + return new Frame( type, size, data ); + } + + /** + * Read an ID3v2 3 byte integer. This is a 3 byte big endian value, which is + * always not syncsafe. + * + * @return The integer read. + * @throws IOException If there were not enough bytes in the file. + */ + protected int read3Int() + throws IOException { + int val = 0; + for( int cnt = 3; cnt > 0; cnt-- ) { + val <<= 8; + val |= ( in.readByte() & 0xFF ); + } + return val; + } + +/*--------------------------------------------------------------------*/ + + /** + * This class contains a ID3v2.2.0 frame. + */ + private static class Frame { + private final String charset; + private final String type; +// private final int size; + private byte[] data; + + /** + * Constructor for the Frame object + * + * @param type Frame type + * @param size Frame size + * @param data Frame content, may be null + */ + public Frame( String type, int size, byte[] data ) { + this.charset = "ISO-8859-1"; + this.type = type; +// Currently unused... +// this.size = size; + this.data = data; + } + + /** + * Get the type. + * + * @return The type of this Frame + */ + public String getType() { + return type; + } + + /** + * Return the Frame content as String. This method is to be used for + * strings without a leading encoding byte. This machine's default + * encoding will be used instead. + * + * @return Encoded string + * @throws FormatDecodeException Could not read or decode frame + */ + public String getAsString() + throws FormatDecodeException { + if(data==null) return null; + return new String( data ); + } + + /** + * Return the Frame content as encoded String. The first byte will + * contain the encoding type, following the string itself. Multiple + * strings are null-terminated. An array of all strings found, will + * be returned. + * + * @return Array of all strings. Might be an empty array! + * @throws FormatDecodeException Could not read or decode frame + */ + public String[] getAsStringEnc() + throws FormatDecodeException { + if(data==null) return new String[0]; + int len = data.length - 1; + String result = ""; + try { + switch ( data[0] ) { + case 0x00: + result = new String( data, 1, len, charset ); + break; + case 0x01: + result = new String( data, 1, len, "UTF-16" ); + break; + default: + throw new FormatDecodeException( "unknown encoding of frame " + type ); + } + }catch( UnsupportedEncodingException e ) { + throw new FormatDecodeException( "Java misses a basic encoding!?" ); + } + return result.split( "\0" ); + } + + } + +} diff --git a/songdbj/net/shredzone/ifish/ltr/TagOggVorbis.java b/songdbj/net/shredzone/ifish/ltr/TagOggVorbis.java new file mode 100644 index 0000000000..45b7401437 --- /dev/null +++ b/songdbj/net/shredzone/ifish/ltr/TagOggVorbis.java @@ -0,0 +1,207 @@ +/* + * iFish -- An iRiver iHP jukebox database creation tool + * + * Copyright (c) 2004 Richard "Shred" Körber + * http://www.shredzone.net/go/ifish + * + *----------------------------------------------------------------------- + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is IFISH. + * + * The Initial Developer of the Original Code is + * Richard "Shred" Körber. + * Portions created by the Initial Developer are Copyright (C) 2004 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** + */ + +package net.shredzone.ifish.ltr; + +import java.io.*; +import de.jarnbjo.ogg.*; +import de.jarnbjo.vorbis.*; + +/** + * Decodes an Ogg Vorbis stream. It uses the + * J-Ogg library, which is + * copyrighted by Tor-Einar Jarnbjo. His licence says that may use and + * modify it as will (even commercial), as long as a reference to his + * library is stated in the software. + *

+ * NOTE: Due to a bug, there is a patch required in the J-Ogg + * library. In class de.jarnbjo.vorbis.CommentHeader, private method + * addComment(), add a line + *

+ *   key = key.toUpperCase();
+ * 
+ * after the method declaration header. + * + * @author Richard Körber <dev@shredzone.de> + * @version $Id$ + */ +public class TagOggVorbis extends LTR { + private CommentHeader cmt; + + /** + * Create a new TagOggVorbis object. + * + * @param in File to read + * @throws FormatDecodeException Couldn't decode this file + */ + public TagOggVorbis( RandomAccessFile in ) + throws FormatDecodeException { + super( in ); + + try { + //--- Check for Ogg Signature --- + // I expected J-Ogg to do this check, but it has been commented + // out because very old ogg files do not seem to have this header. + // We will ignore those files. + if( !readStringLen(4).equals("OggS") ) + throw new FormatDecodeException( "not an Ogg file" ); + in.seek(0); + + //--- Get the Comment Header --- + OggFastFileStream fs = new OggFastFileStream(in); + LogicalOggStream los = (LogicalOggStream) fs.getLogicalStreams().iterator().next(); + if( los.getFormat() != LogicalOggStream.FORMAT_VORBIS ) + throw new FormatDecodeException( "not a plain Ogg Vorbis file" ); + VorbisStream vos = new VorbisStream( los ); + cmt = vos.getCommentHeader(); + + }catch( OggFormatException e ) { + throw new FormatDecodeException( "not an Ogg file" ); + }catch( VorbisFormatException e ) { + throw new FormatDecodeException( "not an Ogg Vorbis file" ); + }catch( IOException e ) { + throw new FormatDecodeException( "could not decode file: " + e.toString() ); + }catch( RuntimeException e ) { + throw new FormatDecodeException( "error decoding file: " + e.toString() ); + } + } + + /** + * Get the type of this file. This is usually the compression format + * itself (e.g. "OGG" or "MP3"). If there are different taggings for + * this format, the used tag format is appended after a slash (e.g. + * "MP3/id3v2"). + * + * @return The type + */ + public String getType() { + return "OGG"; + } + + /** + * Get the artist. + * + * @return The artist (never null) + */ + public String getArtist() { + String val = cmt.getArtist(); + if( val==null ) val=""; + return val.trim(); + } + + /** + * Get the album. + * + * @return The album (never null) + */ + public String getAlbum() { + String val = cmt.getAlbum(); + if( val==null ) val=""; + return val.trim(); + } + + /** + * Get the title. + * + * @return The title (never null) + */ + public String getTitle() { + String val = cmt.getTitle(); + if( val==null ) val=""; + return val.trim(); + } + + /** + * Get the genre. + * + * @return The genre (never null) + */ + public String getGenre() { + String val = cmt.getGenre(); + if( val==null ) val=""; + return val.trim(); + } + + /** + * Get the year. + * + * @return The year (never null) + */ + public String getYear() { + String val = cmt.getDate(); + if( val==null ) val=""; + return val.trim(); + } + + /** + * Get the comment. + * + * @return The comment (never null) + */ + public String getComment() { + String val = cmt.getDescription(); + if( val==null ) { + // *sigh* The Ogg Vorbis documentation does not explicitely + // state the comment types. So there are some ogg writers + // around that use COMMENT instead of DESCRIPTION. We will + // use COMMENT if DESCRIPTION was empty. + val = cmt.getComment("COMMENT"); + } + if( val==null ) + val=""; + return val.trim(); + } + + /** + * Get the track. + * + * @return The track (never null) + */ + public String getTrack() { + String val = cmt.getTrackNumber(); + if( val==null ) val=""; + return val.trim(); + } + +} -- cgit v1.2.3