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:
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user