/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (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.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.tika.parser.mp3; import java.io.IOException; import java.io.InputStream; import org.apache.tika.exception.TikaException; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import static java.nio.charset.StandardCharsets.US_ASCII; import static java.nio.charset.StandardCharsets.UTF_8; /** * This is used to parse Lyrics3 tag information * from an MP3 file, if available. * Handles lyrics tags of up to 10kb in size. * Will process any ID3v1 tag data if present. * Ignores extended ID3v1 data in the lyrics block * * @see <a href="http://www.id3.org/Lyrics3v2">Lyrics3 v2.0 specification</a> */ public class LyricsHandler { boolean foundLyrics = false; String lyricsText = null; ID3v1Handler id3v1 = null; public LyricsHandler(InputStream stream, ContentHandler handler) throws IOException, SAXException, TikaException { this(getSuffix(stream, 10240+128)); } /** * Looks for the Lyrics data, which will be * just before the ID3v1 data (if present), * and process it. * Also sets things up for the ID3v1 * processing if required. * Creates from the last 128 bytes of a stream. */ protected LyricsHandler(byte[] tagData) throws IOException, SAXException, TikaException { if(tagData.length < 128) { return; } // Is there ID3v1 data? byte[] last128 = new byte[128]; System.arraycopy(tagData, tagData.length-128, last128, 0, 128); id3v1 = new ID3v1Handler(last128); if(tagData.length < 137) { return; } // Are there lyrics? Look for the closing Lyrics tag // at the end to decide if there is any int lookat = tagData.length - 9; if(id3v1.found) { lookat -= 128; } if(tagData[lookat+0] == 'L' && tagData[lookat+1] == 'Y' && tagData[lookat+2] == 'R' && tagData[lookat+3] == 'I' && tagData[lookat+4] == 'C' && tagData[lookat+5] == 'S' && tagData[lookat+6] == '2' && tagData[lookat+7] == '0' && tagData[lookat+8] == '0') { foundLyrics = true; // The length (6 bytes) comes just before LYRICS200, and is the // size including the LYRICSBEGIN but excluding the // length+LYRICS200 at the end. int length = Integer.parseInt( new String(tagData, lookat-6, 6, UTF_8) ); String lyrics = new String( tagData, lookat-length+5, length-11, US_ASCII ); // Tags are a 3 letter code, 5 digit length, then data int pos = 0; while(pos < lyrics.length()-8) { String tagName = lyrics.substring(pos, pos+3); int tagLen = Integer.parseInt( lyrics.substring(pos+3, pos+8) ); int startPos = pos + 8; int endPos = startPos + tagLen; if(tagName.equals("LYR")) { lyricsText = lyrics.substring(startPos, endPos); } pos = endPos; } } } public boolean hasID3v1() { if(id3v1 == null || id3v1.found == false) { return false; } return true; } public boolean hasLyrics() { return lyricsText != null && lyricsText.length() > 0; } /** * Reads and returns the last <code>length</code> bytes from the * given stream. * @param stream input stream * @param length number of bytes from the end to read and return * @return stream the <code>InputStream</code> to read from. * @throws IOException if the stream could not be read from. */ protected static byte[] getSuffix(InputStream stream, int length) throws IOException { byte[] buffer = new byte[2 * length]; int bytesInBuffer = 0; int n = stream.read(buffer); while (n != -1) { bytesInBuffer += n; if (bytesInBuffer == buffer.length) { System.arraycopy(buffer, bytesInBuffer - length, buffer, 0, length); bytesInBuffer = length; } n = stream.read(buffer, bytesInBuffer, buffer.length - bytesInBuffer); } if (bytesInBuffer < length) { length = bytesInBuffer; } byte[] result = new byte[length]; System.arraycopy(buffer, bytesInBuffer - length, result, 0, length); return result; } }