Initial revision of ripping support. (Reads track info, doesn't rip tracks

yet.)


git-svn-id: https://samskivert.googlecode.com/svn/trunk@7 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
mdb
2000-10-30 21:08:53 +00:00
parent b1f97cc2b2
commit a8785c1b4c
6 changed files with 332 additions and 0 deletions
@@ -0,0 +1,107 @@
//
// $Id: CDParanoiaRipper.java,v 1.1 2000/10/30 21:08:53 mdb Exp $
package robodj.rip;
import java.io.*;
import java.util.ArrayList;
import gnu.regexp.*;
/**
* A ripper implementation that uses cdparanoia to do it's job.
*/
public class CDParanoiaRipper implements Ripper
{
public TrackInfo[] getTrackInfo () throws RipException
{
try {
// fork off a cdparanoia process to read the TOC
Runtime rt = Runtime.getRuntime();
Process tocproc = rt.exec("cdparanoia -Q");
InputStream in = tocproc.getErrorStream();
BufferedInputStream bin = new BufferedInputStream(in);
DataInputStream din = new DataInputStream(bin);
String inline;
StringBuffer input = new StringBuffer();
ArrayList flist = new ArrayList();
// an input line that we're interested in looks something like
// this:
//
// 1. 17980 [03:59.55] 0 [00:00.00] no no 2
RE regex =
new RE("^\\s*\\d+\\.\\s+(\\d+)\\s\\[\\S*\\]\\s+(\\d+)");
while ((inline = din.readLine()) != null) {
// skip blank lines and lines that are in the header
if (inline.trim().length() == 0 ||
inline.indexOf("mit.edu") != -1 ||
inline.indexOf("cdparanoia") == 0) {
continue;
}
// System.out.println("Matching: " + inline);
// keep track of all of the input in case we need to
// report an error later
input.append(inline).append("\n");
// see if we match our regular expression
REMatch match = regex.getMatch(inline);
if (match != null) {
flist.add(match.toString(1));
flist.add(match.toString(2));
}
}
// check the return value of the process
try {
int retval = tocproc.waitFor();
if (retval != 0) {
// ship off the error output from cdparanoia
throw new RipException(input.toString());
}
} catch (InterruptedException ie) {
// why we were interrupted I can only speculate, but we'll
// go ahead and freak out anyway
throw new RipException("Interrupted while waiting for " +
"cdparanoia process to exit.");
}
// parse the frame offsets and stick them in an array
TrackInfo[] frames = new TrackInfo[flist.size()/2];
for (int i = 0; i < frames.length; i++) {
try {
frames[i] = new TrackInfo();
frames[i].length =
Integer.parseInt((String)flist.get(2*i));
frames[i].offset =
Integer.parseInt((String)flist.get(2*i+1));
// for some reason, cdparanoia reports track offsets
// starting from zero but CDDB assumes they start at 2
// seconds, so we have to adjust... sigh.
frames[i].offset += 2 * RipUtil.FRAMES_PER_SECOND;
} catch (NumberFormatException nfe) {
throw new RipException("Bogus frame value for track " +
(i+1) + ": " + nfe.getMessage() +
"\n\n" + input);
}
}
return frames;
} catch (REException ree) {
throw new RipException("Can't compile regular expression?! " +
ree);
} catch (IOException ioe) {
throw new RipException("Error talking to rip process: " + ioe);
}
}
}
@@ -0,0 +1,13 @@
#
# $Id: Makefile,v 1.1 2000/10/30 21:08:53 mdb Exp $
ROOT = ../..
SRCS = \
CDParanoiaRipper.java \
RipException.java \
RipTest.java \
RipUtil.java \
Ripper.java \
include $(ROOT)/build/Makefile.java
@@ -0,0 +1,16 @@
//
// $Id: RipException.java,v 1.1 2000/10/30 21:08:53 mdb Exp $
package robodj.rip;
/**
* A rip exception can be thrown to indicate that some problem was
* encountered during some part of the ripping process.
*/
public class RipException extends Exception
{
public RipException (String message)
{
super(message);
}
}
@@ -0,0 +1,90 @@
//
// $Id: RipTest.java,v 1.1 2000/10/30 21:08:53 mdb Exp $
package robodj.rip;
import com.samskivert.net.cddb.*;
public class RipTest
{
public static void lookupCDDB (String hostname, Ripper.TrackInfo[] info)
throws Exception
{
CDDB cddb = new CDDB();
long discid = RipUtil.computeDiscId(info);
String cdid = Long.toString(discid, 0x10);
// create an array with the track offsets
int[] offsets = new int[info.length];
for (int i = 0; i < info.length; i++) {
offsets[i] = info[i].offset;
}
int length = RipUtil.computeDiscLength(info);
try {
String rsp = cddb.connect(hostname);
// set the timeout to 30 seconds
cddb.setTimeout(30*1000);
// try a test query
CDDB.Entry[] entries = cddb.query(cdid, offsets, length);
if (entries == null || entries.length == 0) {
System.out.println("No match for " + cdid + ".");
} else {
for (int i = 0; i < entries.length; i++) {
System.out.println("Match " + entries[i].category + "/" +
entries[i].cdid + "/" +
entries[i].title);
}
CDDB.Detail detail = cddb.read(entries[0].category,
entries[0].cdid);
System.out.println("Title: " + detail.title);
for (int i = 0; i < detail.trackNames.length; i++) {
System.out.println(pad(i) + ": " + detail.trackNames[i]);
}
System.out.println("Extended data: " + detail.extendedData);
for (int i = 0; i < detail.extendedTrackData.length; i++) {
System.out.println(pad(i) + ": " +
detail.extendedTrackData[i]);
}
}
} finally {
cddb.close();
}
}
protected static String pad (int value)
{
return ((value > 9) ? "" : " ") + value;
}
public static void main (String[] args)
{
try {
Ripper ripper = new CDParanoiaRipper();
Ripper.TrackInfo[] info = ripper.getTrackInfo();
for (int i = 0; i < info.length; i++) {
System.out.println((i+1) + ": " + info[i].offset + ", " +
info[i].length);
}
if (info.length == 0) {
System.out.println("Matched no tracks?!");
} else {
// finally try the CDDB lookup
lookupCDDB("us.cddb.com", info);
}
} catch (Exception e) {
System.err.println("Error: " + e);
}
}
}
@@ -0,0 +1,65 @@
//
// $Id: RipUtil.java,v 1.1 2000/10/30 21:08:53 mdb Exp $
package robodj.rip;
/**
* This class contains utility functions related to ripping.
*/
public class RipUtil
{
/**
* Compact discs contain this many frames per second.
*/
public static int FRAMES_PER_SECOND = 75;
/**
* Computes the CDDB disc id for the specified CD using the supplied
* track info.
*/
public static long computeDiscId (Ripper.TrackInfo[] info)
{
// first we sum all the digits in the numbers that represent the
// number of seconds for each track
long digits = 0;
for (int i = 0; i < info.length; i++) {
int secs = info[i].offset/FRAMES_PER_SECOND;
digits += addDigits(secs);
}
// determine the total number of seconds of all the tracks on the
// CD (doing so by subtracting the offset of the end of the disc
// from the offset of the beginning which is how the CDDB
// algorithm does it)
long totsecs = (long)computeDiscLength(info);
// finally combine these two values with the number of tracks on
// the disc into the actual disc id
return ((digits & 0xFF) << 24) | (totsecs << 8) | info.length;
}
/**
* @return the length of the CD (in seconds) as computed from the
* supplied frame information.
*/
public static int computeDiscLength (Ripper.TrackInfo[] info)
{
return (info[info.length-1].offset +
info[info.length-1].length -
info[0].offset)/FRAMES_PER_SECOND;
}
/**
* @return the sum of the digits in the supplied number (eg. 6 for 15,
* 8 for 251, etc.).
*/
protected static int addDigits (int value)
{
int rv = 0;
while (value > 0) {
rv += value%10;
value /= 10;
}
return rv;
}
}
@@ -0,0 +1,41 @@
//
// $Id: Ripper.java,v 1.1 2000/10/30 21:08:53 mdb Exp $
package robodj.rip;
/**
* The ripper interface is used to manipulate a particular piece of CD
* ripping software in the ways needed by the RoboDJ system.
*/
public interface Ripper
{
/**
* Instances of this object are used to communicate the track
* information of a CD back to the caller.
*/
public class TrackInfo
{
/**
* The offset of the start of this track (in frames).
*/
public int offset;
/**
* The length of this track (in frames).
*/
public int length;
}
/**
* This function should return an array with an entry for each track
* on the CD (in order) containing the frame offset and length of that
* track. This function will be called in another thread and has 30
* seconds to do its job or it will be declared delinquent and
* ignored.
*
* @exception RipException can be thrown if some problem occurs trying
* to read the CD table of contents (like lack of access to the CDROM
* device or any other errors).
*/
public TrackInfo[] getTrackInfo () throws RipException;
}