Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/esau/ptarmigan/impl/filter/ID3v1Filter.java


1   /* $Header: /cvsroot/ptarmigan/ptarmigan/src/java/org/esau/ptarmigan/impl/filter/ID3v1Filter.java,v 1.4 2002/09/19 03:42:01 reedesau Exp $ */
2   
3   package org.esau.ptarmigan.impl.filter;
4   
5   import java.io.IOException;
6   import java.io.InputStream;
7   import java.io.File;
8   import java.io.ByteArrayInputStream;
9   import java.io.RandomAccessFile;
10  import java.text.ParseException;
11  
12  import org.xml.sax.SAXException;
13  
14  import org.apache.commons.logging.Log;
15  import org.apache.commons.logging.LogFactory;
16  
17  import org.esau.ptarmigan.impl.RandomAccessFileSource;
18  import org.esau.ptarmigan.util.HelperDate;
19  import org.esau.ptarmigan.util.HelperID3;
20  import org.esau.ptarmigan.util.HelperIO;
21  import org.esau.ptarmigan.util.HelperMisc;
22  import org.esau.ptarmigan.util.HelperURL;
23  
24  /**
25   * Extract metadata from an ID3v1 tag
26   * <p>
27   * It's a 128-byte block at the end of a file.
28   * <p>
29   * May appear on MP3, Ogg, Flac, and other files.
30   * <p>
31   * Note that it is unsupported with streams, at least for now.  This
32   * is because of the overhead required in scanning to the end of the file.
33   *
34   * @author Reed Esau
35   * @version $Revision: 1.4 $ $Date: 2002/09/19 03:42:01 $
36   */
37  public final class ID3v1Filter extends BinaryFilter implements RandomAccessFileSource {
38  
39      public ID3v1Filter() throws SAXException  {
40      }
41  
42      String getNamespaceURI() {
43          return NS_URI;
44      }
45  
46      String getNamespacePrefix() {
47          return NS_PREFIX;
48      }
49  
50      /**
51       * SAX-Invoked parse of a 'document' from an input stream.
52       * <p>
53       * Note that the caller must already have called this.getByteStream()
54       * to obtain the raw data for the tag.
55       * <p>
56       * This module creates the document from the parsed ID3V1 metadata.
57       */
58      public void doParse() throws SAXException, IOException, ParseException {
59          log.debug("doParse");
60  
61          writeID3v1();
62  
63          m_media_properties.getRange().reduceLength(ID3V1_TAG_LENGTH);
64      }
65  
66  
67      /** build the xml content */
68      void writeID3v1()
69      throws IOException, SAXException, ParseException {
70  
71          final String local_name = "id3v1";
72          final String q_name = NS_PREFIX + ":" + local_name;
73  
74          startElement(NS_URI, local_name, q_name, EMPTY_ATTRS);
75  
76          byte[] buf = new byte[128];
77          read(buf, 0, 3);
78  
79  //         StringBuffer buf2 = new StringBuffer();
80  //         for (int i = 0; i < 128; i++) {
81  //             buf2.append(Integer.toHexString(buf[i])).append(" ");
82  //         }
83  //         log.debug("buf2=" + buf2.toString());
84  
85          if (buf[0] != 'T' || buf[1] != 'A' || buf[2] != 'G')
86              throw new ParseException("missing 'TAG' at start of tag", 0);   //TODO: supply the position
87  
88          read(buf, 0, ITEM_TITLE_LENGTH);
89          maybeWrite(buf, ITEM_TITLE_LENGTH, "title");
90  
91          read(buf, 0, ITEM_ARTIST_LENGTH);
92          maybeWrite(buf, ITEM_ARTIST_LENGTH, "artist");
93  
94          read(buf, 0, ITEM_ALBUM_LENGTH);
95          maybeWrite(buf, ITEM_ALBUM_LENGTH, "album");
96  
97          read(buf, 0, ITEM_YEAR_LENGTH);
98          String year = HelperDate.extractYear( new String(buf, 0, ITEM_YEAR_LENGTH) );
99          if (year != null)
100             write("year", year);
101 
102         read(buf, 0, ITEM_COMMENT_LENGTH-1);
103         maybeWrite(buf, ITEM_COMMENT_LENGTH-1, "comment");
104         read();      // ignore last byte of comment (used to establish version)
105 
106         int track = read();
107         if (track == -1)
108             throw new IOException("underflow on reading track no");
109         write("track", track);
110 
111         int code = read();
112         if (code == -1)
113             throw new IOException("underflow on reading genre code");
114         String genre_str = HelperID3.getNullsoftGenre(code);
115         if (genre_str != null)
116             write("genre", genre_str);
117 
118         endElement(NS_URI, local_name, q_name);
119     }
120 
121     /**
122      * obtain the id3 tag as a stream of bytes, for handy parsing
123      *
124      * Many different filetypes may have an ID3v1 tag (128 bytes)
125      * attached to their end.
126      */
127     InputStream getByteStream(String system_id)
128     throws IOException, SAXException {
129 
130         File file = HelperURL.systemIdToFile(system_id);
131         if (file == null) {
132             log.warn("could not resolve the system id to a local file");
133             return null;
134         }
135 
136         if (log.isDebugEnabled ())
137             log.debug("getByteStream: file=" + file);
138 
139         RandomAccessFile raf = null;
140         try {
141             raf = new RandomAccessFile(file, "r");
142 
143             long length = raf.length();
144 
145             if (length < ID3V1_TAG_LENGTH) {
146                 log.debug("file too short to contain ID3v1 tag");
147                 return null;
148             }
149 
150             byte[] buf = new byte[128];
151 
152             raf.seek( raf.length() - buf.length );
153             raf.read(buf);
154 
155             InputStream is = null;
156 
157             if (HelperMisc.startsWith(buf, 0, buf.length, ID3_MARKER)) {
158                 log.debug("found ID3 tag");
159                 is = new ByteArrayInputStream(buf);
160             }
161             return is;
162         }
163         finally {
164             HelperIO.safeClose(raf);
165         }
166     }
167 
168 
169     /** */
170     void maybeWrite(byte[] buf, int length, String xml_name)
171     throws IOException, SAXException {
172 
173         if (buf[0] == '\0')  // don't even try
174             return;
175 
176         //TODO: figure out which encoding to use
177         String ENCODING = "ASCII"; // replaces extended chars with question mark
178         String str = new String(buf, ENCODING).trim();
179 
180         str = HelperMisc.clean(str);       // there might still be nul & ctrl garbage
181 
182         if (str.length() > 0)
183             write(xml_name, str);
184     }
185 
186     static final int ID3V1_TAG_LENGTH = 128;
187 
188     static final int ITEM_TAG_LENGTH     = 3;
189     static final int ITEM_TITLE_LENGTH   = 30;
190     static final int ITEM_ARTIST_LENGTH  = 30;
191     static final int ITEM_ALBUM_LENGTH   = 30;
192     static final int ITEM_YEAR_LENGTH    = 4;
193     static final int ITEM_COMMENT_LENGTH = 29;
194 
195 //
196 // data members
197 //
198 
199     File m_file;
200 
201 //
202 // constants
203 //
204 
205     static final byte[] ID3_MARKER = { 'T', 'A', 'G'};
206 
207     static final String NS_URI = "http://esau.org/ns/ptarmigan/id3v1";
208     static final String NS_PREFIX = "v1";
209 
210     /**
211      * logging object
212      */
213     static Log log = LogFactory.getLog(ID3v1Filter.class);
214 }
215 
216 /*
217 PTARMIGAN MODIFIED BSD LICENSE
218 
219 Copyright (c) 2002, Reed Esau (reed.esau@pobox.com) All rights reserved.
220 
221 Redistribution and use in source and binary forms, with or without
222 modification, are permitted provided that the following conditions are
223 met:
224 
225 Redistributions of source code must retain the above copyright notice,
226 this list of conditions and the following disclaimer.
227 
228 Redistributions in binary form must reproduce the above copyright notice,
229 this list of conditions and the following disclaimer in the documentation
230 and/or other materials provided with the distribution.
231 
232 Neither the name of the Ptarmigan Project
233 (http://ptarmigan.sourceforge.net) nor the names of its contributors may
234 be used to endorse or promote products derived from this software without
235 specific prior written permission.
236 
237 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
238 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
239 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
240 PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
241 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
242 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
243 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
244 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
245 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
246 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
247 POSSIBILITY OF SUCH DAMAGE.
248 */