blob: 6895baf6a7a0b97af81722fe5b2d542857063717 [file] [log] [blame]
/*
* Copyright 1999-2004 The Apache Software Foundation.
*
* Licensed 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.cocoon.generation;
import org.xml.sax.SAXException;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* @cocoon.sitemap.component.documentation
* An extension of DirectoryGenerators that adds extra attributes for MP3
* files.
*
* @cocoon.sitemap.component.name mp3directory
* @cocoon.sitemap.component.label content
* @cocoon.sitemap.component.logger sitemap.generator.mp3directory
* @cocoon.sitemap.component.documentation.caching
* Uses the last modification date of the directory and the contained files
*
*
* @version $Id$
*/
public class MP3DirectoryGenerator extends DirectoryGenerator
{
// MP3 Constants
private static final int VERSION_MPEG1 = 3;
// private static final int VERSION_MPEG2 = 2;
private static final int MODE_DUAL_CHANNEL = 2;
private static final int MODE_JOINT_STEREO = 1;
private static final int MODE_SINGLE_CHANNEL = 3;
private static final int MODE_STEREO = 0;
private static final int VBR_FRAMES_FLAG = 1;
// private static final int VBR_BYTES_FLAG = 2;
// private static final int VBR_TOC_FLAG = 4;
// private static final int VBR_SCALE_FLAG = 8;
// Attributes
protected static String MP3_FREQUENCY_ATTR_NAME = "frequency";
protected static String MP3_BITRATE_ATTR_NAME = "bitrate";
protected static String MP3_MODE_ATTR_NAME = "mode";
protected static String MP3_VBR_ATTR_NAME = "variable-rate";
protected static String MP3_TITLE_ATTR_NAME = "title";
protected static String MP3_ARTIST_ATTR_NAME = "artist";
protected static String MP3_ALBUM_ATTR_NAME = "album";
protected static String MP3_YEAR_ATTR_NAME = "year";
protected static String MP3_COMMENT_ATTR_NAME = "comment";
protected static String MP3_TRACK_ATTR_NAME = "track";
protected static String MP3_GENRE_ATTR_NAME = "genre";
/**
* Extends the <code>setNodeAttributes</code> method from the
* <code>DirectoryGenerator</code> by adding MP3 tag attributes
* if the path is a MP3 file with valid tag.
*/
protected void setNodeAttributes(File path) throws SAXException {
super.setNodeAttributes(path);
if (path.isDirectory()) {
return;
}
RandomAccessFile in = null;
try {
in = new RandomAccessFile(path, "r");
setID3HeaderAttributes(in);
setID3TagAttributes(in);
} catch (IOException e) {
getLogger().debug("Could not set attributes for " + path, e);
} finally {
if(in != null) try{ in.close(); }catch(IOException ignored){}
}
}
/**
* Read ID3 Tag
*/
private void setID3TagAttributes(RandomAccessFile in) throws IOException {
String s;
// TAG takes 128 bytes
if (in.length() < 128) return;
in.seek(in.length() - 128);
byte [] buf = new byte[128];
// Read TAG
if (in.read(buf,0, 128) != 128) return;
// Check TAG presence
if(buf[0] != 'T' || buf[1] != 'A' || buf[2] != 'G') return;
s = getID3TagValue(buf, 3, 30);
if(s.length() > 0)
attributes.addAttribute("", MP3_TITLE_ATTR_NAME, MP3_TITLE_ATTR_NAME, "CDATA", s);
s = getID3TagValue(buf, 33,30);
if(s.length() > 0)
attributes.addAttribute("", MP3_ARTIST_ATTR_NAME, MP3_ARTIST_ATTR_NAME, "CDATA", s);
s = getID3TagValue(buf, 63,30);
if(s.length() > 0)
attributes.addAttribute("", MP3_ALBUM_ATTR_NAME, MP3_ALBUM_ATTR_NAME, "CDATA", s);
s = getID3TagValue(buf, 93, 4);
if(s.length() > 0)
attributes.addAttribute("", MP3_YEAR_ATTR_NAME, MP3_YEAR_ATTR_NAME, "CDATA", s);
s = getID3TagValue(buf, 97,29);
if(s.length() > 0)
attributes.addAttribute("", MP3_COMMENT_ATTR_NAME, MP3_COMMENT_ATTR_NAME, "CDATA", s);
if(buf[126] > 0)
attributes.addAttribute("", MP3_TRACK_ATTR_NAME, MP3_TRACK_ATTR_NAME, "CDATA",
Byte.toString(buf[126]));
if(buf[127] > 0)
attributes.addAttribute("", MP3_GENRE_ATTR_NAME, MP3_GENRE_ATTR_NAME, "CDATA",
Byte.toString(buf[127]));
}
private String getID3TagValue(byte[] buf, int offset, int length) {
String s = new String(buf, offset, length);
int index = s.indexOf(0x00);
if (index != -1) {
s = s.substring(0, index);
}
return s.trim();
}
private void setID3HeaderAttributes(RandomAccessFile in) throws IOException {
byte[] buffer = new byte[4];
// http://floach.pimpin.net/grd/mp3info/frmheader/index.html
if (in.read(buffer, 0, 3) != 3) {
return;
}
int header = ((buffer[0] << 16) & 0x00FF0000) | ((buffer[1] << 8) & 0x0000FF00) | ((buffer[2] << 0) & 0x000000FF);
do {
header <<= 8;
if (in.read(buffer, 3, 1) != 1) {
return;
}
header |= (buffer[3] & 0x000000FF);
} while (!isSyncMark(header));
int version = (header >>> 19) & 3;
int layer = 4 - (header >>> 17) & 3;
// int protection = (header >>> 16) & 1;
int bitrate = (header >>> 12) & 0xF;
int frequency = (header >>> 10) & 3;
// Value 3 is reserved
if (frequency == 3) {
return;
}
// int padding = (header >>> 9) & 1;
int mode = ((header >>> 6) & 3);
attributes.addAttribute("", MP3_FREQUENCY_ATTR_NAME, MP3_FREQUENCY_ATTR_NAME, "CDATA",
frequencyString(version, frequency));
attributes.addAttribute("", MP3_MODE_ATTR_NAME, MP3_MODE_ATTR_NAME, "CDATA",
mode(mode));
int frames = getVBRHeaderFrames(in, version, mode);
if (frames != -1) {
// get average frame size by deviding fileSize by the number of frames
float medFrameSize = (float)in.length() / frames;
// This does not work properly: (version == VERSION_MPEG1? 12000.0:144000.0)
bitrate = (int)(medFrameSize * frequency(version, frequency) / 144000.0);
attributes.addAttribute("", MP3_BITRATE_ATTR_NAME, MP3_BITRATE_ATTR_NAME, "CDATA",
Integer.toString(bitrate));
} else {
attributes.addAttribute("", MP3_BITRATE_ATTR_NAME, MP3_BITRATE_ATTR_NAME, "CDATA",
bitrate(version, layer, bitrate));
}
}
private static boolean isSyncMark(int header) {
boolean sync = ((header & 0xFFF00000) == 0xFFF00000);
// filter out invalid sample rate
if (sync) sync = ((header >>> 10) & 3) != 3;
// filter out invalid layer
if (sync) sync = ((header >>> 17) & 3) != 0;
// filter out invalid version
if (sync) sync = ((header >>> 19) & 3) != 1;
return sync;
}
private int getVBRHeaderFrames(RandomAccessFile in, int version, int mode) throws IOException {
byte[] buffer = new byte[12];
// Try to detect VBR header
int skip;
if (version == VERSION_MPEG1) {
if (mode == MODE_SINGLE_CHANNEL) skip = 17;
else skip = 32;
} else { // mpeg version 2 or 2.5
if (mode == MODE_SINGLE_CHANNEL) skip = 9;
else skip = 17;
}
while (skip > 0) {
if (in.read() == -1) return -1;
skip --;
}
if (in.read(buffer, 0, 12) != 12) {
return -1;
}
if (buffer[0] != 'X' || buffer[1] != 'i' || buffer[2] != 'n' || buffer[3] != 'g'){
return -1;
}
attributes.addAttribute("", MP3_VBR_ATTR_NAME, MP3_VBR_ATTR_NAME, "CDATA",
"yes");
int flags =
((buffer[4] & 0xFF) << 24) |
((buffer[5] & 0xFF) << 16) |
((buffer[6] & 0xFF) << 8) |
(buffer[7] & 0xFF);
if ((flags & VBR_FRAMES_FLAG) == VBR_FRAMES_FLAG){
int frames =
((buffer[ 8] & 0xFF) << 24) |
((buffer[ 9] & 0xFF) << 16) |
((buffer[10] & 0xFF) << 8) |
(buffer[11] & 0xFF);
return frames;
} else {
return -1;
}
}
// version - layer - bitrate index
private static final String bitrates[][][] = {
{
// MPEG2 - layer 1
{"free format", "32", "48", "56", "64", "80", "96", "112", "128", "144", "160", "176", "192", "224", "256", "forbidden"},
// MPEG2 - layer 2
{"free format", "8", "16", "24", "32", "40", "48", "56", "64", "80", "96", "112", "128", "144", "160", "forbidden"},
// MPEG2 - layer 3
{"free format", "8", "16", "24", "32", "40", "48", "56", "64", "80", "96", "112", "128", "144", "160", "forbidden"}
},
{
// MPEG1 - layer 1
{"free format", "32", "64", "96", "128", "160", "192", "224", "256", "288", "320", "352", "384", "416", "448", "forbidden"},
// MPEG1 - layer 2
{"free format", "32", "48", "56", "64", "80", "96", "112", "128", "160", "192", "224", "256", "320", "384", "forbidden"},
// MPEG1 - layer 3
{"free format", "32", "40", "48", "56", "64", "80" , "96", "112", "128", "160", "192", "224", "256", "320", "forbidden"}
}
};
private static String bitrate(int version, int layer, int bitrate_index) {
return bitrates[version & 1][layer - 1][bitrate_index];
}
private static String mode(int mode) {
switch(mode)
{
case MODE_STEREO:
return "Stereo";
case MODE_JOINT_STEREO:
return "Joint stereo";
case MODE_DUAL_CHANNEL:
return "Dual channel";
case MODE_SINGLE_CHANNEL:
return "Single channel";
}
return null;
}
private static final int frequencies[][] = {
{32000, 16000, 8000}, //MPEG 2.5
{ 0, 0, 0}, //reserved
{22050, 24000, 16000}, //MPEG 2
{44100, 48000, 32000} //MPEG 1
};
private static int frequency(int version, int frequency) {
return frequencies[version][frequency];
}
private static String frequencyString(int version, int frequency) {
return String.valueOf((float)frequency(version, frequency)/1000);
}
}