1272 lines
32 KiB
C
1272 lines
32 KiB
C
/*
|
|
* Copyright (C) 2004, 2005 Rocky Bernstein <rocky@panix.com>
|
|
* (C) 1998 Monty <xiphmont@mit.edu>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*
|
|
*
|
|
* See ChangeLog for recent changes.
|
|
*
|
|
* last changes:
|
|
* 22.01.98 - first version
|
|
* 15.02.98 - alpha 2: juggled two includes from interface/low_interface.h
|
|
* that move contents in Linux 2.1
|
|
* Linked status bar to isatty to avoid it appearing
|
|
* in a redirected file.
|
|
* Played with making TOC less verbose.
|
|
* 04.04.98 - alpha 3: zillions of bugfixes, also added MMC and IDE_SCSI
|
|
* emulation support
|
|
* 05.04.98 - alpha 4: Segfault fix, cosmetic repairs
|
|
* 05.04.98 - alpha 5: another segfault fix, cosmetic repairs,
|
|
* Gadi Oxman provided code to identify/fix nonstandard
|
|
* ATAPI CDROMs
|
|
* 07.04.98 - alpha 6: Bugfixes to autodetection
|
|
* 18.06.98 - alpha 7: Additional SCSI error handling code
|
|
* cosmetic fixes
|
|
* segfault fixes
|
|
* new sync/silence code, smaller fft
|
|
* 15.07.98 - alpha 8: More new SCSI code, better error recovery
|
|
* more segfault fixes (two linux bugs, one my fault)
|
|
* Fixup reporting fixes, smilie fixes.
|
|
* AIFF support (in addition to AIFC)
|
|
* Path parsing fixes
|
|
* Changes are becoming TNTC. Will resume the log at beta.
|
|
*
|
|
* 09.04.04 - conversion to use libcdio. See top-level Changelog for
|
|
* libcdio history.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_STDIO_H
|
|
# include <stdio.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_STDARG_H
|
|
# include <stdarg.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_STDLIB_H
|
|
# include <stdlib.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_STRING_H
|
|
# include <string.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_FCNTL_H
|
|
#include <fcntl.h>
|
|
#endif
|
|
|
|
#include <getopt.h>
|
|
|
|
#ifdef HAVE_ERRNO_H
|
|
#include <errno.h>
|
|
#endif
|
|
|
|
#include <math.h>
|
|
#include <sys/time.h>
|
|
|
|
#ifdef HAVE_SYS_STAT_H
|
|
# include <sys/stat.h>
|
|
#endif
|
|
|
|
#include <cdio/cdio.h>
|
|
#include <cdio/cd_types.h>
|
|
#include <cdio/cdda.h>
|
|
#include <cdio/paranoia.h>
|
|
#include <cdio/bytesex.h>
|
|
#include "utils.h"
|
|
#include "report.h"
|
|
#include "version.h"
|
|
#include "header.h"
|
|
#include "buffering_write.h"
|
|
|
|
extern int verbose;
|
|
extern int quiet;
|
|
|
|
/* I wonder how many alignment issues this is gonna trip in the
|
|
future... it shouldn't trip any... I guess we'll find out :) */
|
|
|
|
static int
|
|
bigendianp(void)
|
|
{
|
|
int test=1;
|
|
char *hack=(char *)(&test);
|
|
if(hack[0])return(0);
|
|
return(1);
|
|
}
|
|
|
|
static long
|
|
parse_offset(cdrom_drive_t *d, char *offset, int begin)
|
|
{
|
|
track_t i_track= CDIO_INVALID_TRACK;
|
|
long hours = -1;
|
|
long minutes = -1;
|
|
long seconds = -1;
|
|
long sectors = -1;
|
|
char *time = NULL;
|
|
char *temp = NULL;
|
|
long ret;
|
|
|
|
if (!offset) return -1;
|
|
|
|
/* seperate track from time offset */
|
|
temp=strchr(offset,']');
|
|
if(temp){
|
|
*temp='\0';
|
|
temp=strchr(offset,'[');
|
|
if(temp==NULL){
|
|
report("Error parsing span argument");
|
|
exit(1);
|
|
}
|
|
*temp='\0';
|
|
time=temp+1;
|
|
}
|
|
|
|
/* parse track */
|
|
{
|
|
int chars=strspn(offset,"0123456789");
|
|
if(chars>0){
|
|
offset[chars]='\0';
|
|
i_track=atoi(offset);
|
|
if ( i_track > d->tracks ) {
|
|
/*take track i_first_track-1 as pre-gap of 1st track*/
|
|
char buffer[256];
|
|
snprintf(buffer, sizeof(buffer),
|
|
"Track #%d does not exist.", i_track);
|
|
report(buffer);
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
while(time){
|
|
long val,chars;
|
|
char *sec=strrchr(time,'.');
|
|
if(!sec)sec=strrchr(time,':');
|
|
if(!sec)sec=time-1;
|
|
|
|
chars=strspn(sec+1,"0123456789");
|
|
if(chars)
|
|
val=atoi(sec+1);
|
|
else
|
|
val=0;
|
|
|
|
switch(*sec){
|
|
case '.':
|
|
if(sectors!=-1){
|
|
report("Error parsing span argument");
|
|
exit(1);
|
|
}
|
|
sectors=val;
|
|
break;
|
|
default:
|
|
if(seconds==-1)
|
|
seconds=val;
|
|
else
|
|
if(minutes==-1)
|
|
minutes=val;
|
|
else
|
|
if(hours==-1)
|
|
hours=val;
|
|
else{
|
|
report("Error parsing span argument");
|
|
exit(1);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (sec<=time) break;
|
|
*sec='\0';
|
|
}
|
|
|
|
if (i_track == CDIO_INVALID_TRACK) {
|
|
if (seconds==-1 && sectors==-1) return -1;
|
|
if (begin==-1) {
|
|
ret=cdda_disc_firstsector(d);
|
|
} else
|
|
ret = begin;
|
|
} else {
|
|
if ( seconds==-1 && sectors==-1 ) {
|
|
if (begin==-1){
|
|
/* first half of a span */
|
|
return(cdda_track_firstsector(d, i_track));
|
|
}else{
|
|
return(cdda_track_lastsector(d, i_track));
|
|
}
|
|
} else {
|
|
/* relative offset into a track */
|
|
ret=cdda_track_firstsector(d, i_track);
|
|
}
|
|
}
|
|
|
|
/* OK, we had some sort of offset into a track */
|
|
|
|
if (sectors != -1) ret += sectors;
|
|
if (seconds != -1) ret += seconds*CDIO_CD_FRAMES_PER_SEC;
|
|
if (minutes != -1) ret += minutes*CDIO_CD_FRAMES_PER_MIN;
|
|
if (hours != -1) ret += hours *60*CDIO_CD_FRAMES_PER_MIN;
|
|
|
|
/* We don't want to outside of the track; if it's relative, that's OK... */
|
|
if( i_track != CDIO_INVALID_TRACK ){
|
|
if (cdda_sector_gettrack(d,ret) != i_track) {
|
|
report("Time/sector offset goes beyond end of specified track.");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/* Don't pass up end of session */
|
|
|
|
if( ret>cdda_disc_lastsector(d) ) {
|
|
report("Time/sector offset goes beyond end of disc.");
|
|
exit(1);
|
|
}
|
|
|
|
return(ret);
|
|
}
|
|
|
|
static void
|
|
display_toc(cdrom_drive_t *d)
|
|
{
|
|
long audiolen=0;
|
|
track_t i;
|
|
|
|
report("\nTable of contents (audio tracks only):\n"
|
|
"track length begin copy pre ch\n"
|
|
"===========================================================");
|
|
|
|
for( i=1; i<=d->tracks; i++)
|
|
if ( cdda_track_audiop(d,i) ) {
|
|
char buffer[256];
|
|
|
|
lsn_t sec=cdda_track_firstsector(d,i);
|
|
lsn_t off=cdda_track_lastsector(d,i)-sec+1;
|
|
|
|
sprintf(buffer,
|
|
"%3d. %7ld [%02d:%02d.%02d] %7ld [%02d:%02d.%02d] %s %s %s",
|
|
i,
|
|
(long int) off,
|
|
(int) (off/(CDIO_CD_FRAMES_PER_MIN)),
|
|
(int) ((off/CDIO_CD_FRAMES_PER_SEC) % CDIO_CD_SECS_PER_MIN),
|
|
(int) (off % CDIO_CD_FRAMES_PER_SEC),
|
|
(long int) sec,
|
|
(int) (sec/(CDIO_CD_FRAMES_PER_MIN)),
|
|
(int) ((sec/CDIO_CD_FRAMES_PER_SEC) % CDIO_CD_SECS_PER_MIN),
|
|
(int) (sec % CDIO_CD_FRAMES_PER_SEC),
|
|
cdda_track_copyp(d,i)?" OK":" no",
|
|
cdda_track_preemp(d,i)?" yes":" no",
|
|
cdda_track_channels(d,i)==2?" 2":" 4");
|
|
report(buffer);
|
|
audiolen+=off;
|
|
}
|
|
{
|
|
char buffer[256];
|
|
sprintf(buffer, "TOTAL %7ld [%02d:%02d.%02d] (audio only)",
|
|
audiolen,
|
|
(int) (audiolen/(CDIO_CD_FRAMES_PER_MIN)),
|
|
(int) ((audiolen/CDIO_CD_FRAMES_PER_SEC) % CDIO_CD_SECS_PER_MIN),
|
|
(int) (audiolen % CDIO_CD_FRAMES_PER_SEC));
|
|
report(buffer);
|
|
}
|
|
report("");
|
|
}
|
|
|
|
#include "usage.h"
|
|
static void usage(FILE *f)
|
|
{
|
|
fprintf( f, usage_help);
|
|
}
|
|
|
|
long callbegin;
|
|
long callend;
|
|
long callscript=0;
|
|
|
|
static const char *callback_strings[15]={
|
|
"wrote",
|
|
"finished",
|
|
"read",
|
|
"verify",
|
|
"jitter",
|
|
"correction",
|
|
"scratch",
|
|
"scratch repair",
|
|
"skip",
|
|
"drift",
|
|
"backoff",
|
|
"overlap",
|
|
"dropped",
|
|
"duped",
|
|
"transport error"};
|
|
|
|
static int skipped_flag=0;
|
|
static int abort_on_skip=0;
|
|
static void
|
|
callback(long int inpos, paranoia_cb_mode_t function)
|
|
{
|
|
/*
|
|
|
|
(== PROGRESS == [--+!---x--------------> | 007218 01 ] == :-) . ==)
|
|
|
|
*/
|
|
|
|
int graph=30;
|
|
char buffer[256];
|
|
static long c_sector=0,v_sector=0;
|
|
static char dispcache[30]=" ";
|
|
static int last=0;
|
|
static long lasttime=0;
|
|
long sector,osector=0;
|
|
struct timeval thistime;
|
|
static char heartbeat=' ';
|
|
int position=0,aheadposition=0;
|
|
static int overlap=0;
|
|
static int printit=-1;
|
|
|
|
static int slevel=0;
|
|
static int slast=0;
|
|
static int stimeout=0;
|
|
const char *smilie="= :-)";
|
|
|
|
if(callscript)
|
|
fprintf(stderr,"##: %d [%s] @ %ld\n",
|
|
function,(function>=-2&&function<=13?callback_strings[function+2]:
|
|
""),inpos);
|
|
|
|
if(!quiet){
|
|
long test;
|
|
osector=inpos;
|
|
sector=inpos/CD_FRAMEWORDS;
|
|
|
|
if(printit==-1){
|
|
if(isatty(STDERR_FILENO)){
|
|
printit=1;
|
|
}else{
|
|
printit=0;
|
|
}
|
|
}
|
|
|
|
if(printit==1){ /* else don't bother; it's probably being
|
|
redirected */
|
|
position=((float)(sector-callbegin)/
|
|
(callend-callbegin))*graph;
|
|
|
|
aheadposition=((float)(c_sector-callbegin)/
|
|
(callend-callbegin))*graph;
|
|
|
|
if(function==-2){
|
|
v_sector=sector;
|
|
return;
|
|
}
|
|
if(function==-1){
|
|
last=8;
|
|
heartbeat='*';
|
|
slevel=0;
|
|
v_sector=sector;
|
|
}else
|
|
if(position<graph && position>=0)
|
|
switch(function){
|
|
case PARANOIA_CB_VERIFY:
|
|
if(stimeout>=30){
|
|
if(overlap>CD_FRAMEWORDS)
|
|
slevel=2;
|
|
else
|
|
slevel=1;
|
|
}
|
|
break;
|
|
case PARANOIA_CB_READ:
|
|
if(sector>c_sector)c_sector=sector;
|
|
break;
|
|
|
|
case PARANOIA_CB_FIXUP_EDGE:
|
|
if(stimeout>=5){
|
|
if(overlap>CD_FRAMEWORDS)
|
|
slevel=2;
|
|
else
|
|
slevel=1;
|
|
}
|
|
if(dispcache[position]==' ')
|
|
dispcache[position]='-';
|
|
break;
|
|
case PARANOIA_CB_FIXUP_ATOM:
|
|
if(slevel<3 || stimeout>5)slevel=3;
|
|
if(dispcache[position]==' ' ||
|
|
dispcache[position]=='-')
|
|
dispcache[position]='+';
|
|
break;
|
|
case PARANOIA_CB_READERR:
|
|
slevel=6;
|
|
if(dispcache[position]!='V')
|
|
dispcache[position]='e';
|
|
break;
|
|
case PARANOIA_CB_SKIP:
|
|
slevel=8;
|
|
dispcache[position]='V';
|
|
break;
|
|
case PARANOIA_CB_OVERLAP:
|
|
overlap=osector;
|
|
break;
|
|
case PARANOIA_CB_SCRATCH:
|
|
slevel=7;
|
|
break;
|
|
case PARANOIA_CB_DRIFT:
|
|
if(slevel<4 || stimeout>5)slevel=4;
|
|
break;
|
|
case PARANOIA_CB_FIXUP_DROPPED:
|
|
case PARANOIA_CB_FIXUP_DUPED:
|
|
slevel=5;
|
|
if(dispcache[position]==' ' ||
|
|
dispcache[position]=='-' ||
|
|
dispcache[position]=='+')
|
|
dispcache[position]='!';
|
|
break;
|
|
case PARANOIA_CB_REPAIR:
|
|
case PARANOIA_CB_BACKOFF:
|
|
break;
|
|
}
|
|
|
|
switch(slevel){
|
|
case 0: /* finished, or no jitter */
|
|
if(skipped_flag)
|
|
smilie=" 8-X";
|
|
else
|
|
smilie=" :^D";
|
|
break;
|
|
case 1: /* normal. no atom, low jitter */
|
|
smilie=" :-)";
|
|
break;
|
|
case 2: /* normal, overlap > 1 */
|
|
smilie=" :-|";
|
|
break;
|
|
case 4: /* drift */
|
|
smilie=" :-/";
|
|
break;
|
|
case 3: /* unreported loss of streaming */
|
|
smilie=" :-P";
|
|
break;
|
|
case 5: /* dropped/duped bytes */
|
|
smilie=" 8-|";
|
|
break;
|
|
case 6: /* scsi error */
|
|
smilie=" :-0";
|
|
break;
|
|
case 7: /* scratch */
|
|
smilie=" :-(";
|
|
break;
|
|
case 8: /* skip */
|
|
smilie=" ;-(";
|
|
skipped_flag=1;
|
|
break;
|
|
|
|
}
|
|
|
|
gettimeofday(&thistime,NULL);
|
|
test=thistime.tv_sec*10+thistime.tv_usec/100000;
|
|
|
|
if(lasttime!=test || function==-1 || slast!=slevel){
|
|
if(lasttime!=test || function==-1){
|
|
last++;
|
|
lasttime=test;
|
|
if(last>7)last=0;
|
|
stimeout++;
|
|
switch(last){
|
|
case 0:
|
|
heartbeat=' ';
|
|
break;
|
|
case 1:case 7:
|
|
heartbeat='.';
|
|
break;
|
|
case 2:case 6:
|
|
heartbeat='o';
|
|
break;
|
|
case 3:case 5:
|
|
heartbeat='0';
|
|
break;
|
|
case 4:
|
|
heartbeat='O';
|
|
break;
|
|
}
|
|
if(function==-1)
|
|
heartbeat='*';
|
|
|
|
}
|
|
if(slast!=slevel){
|
|
stimeout=0;
|
|
}
|
|
slast=slevel;
|
|
|
|
if(abort_on_skip && skipped_flag && function !=-1){
|
|
sprintf(buffer,
|
|
"\r (== PROGRESS == [%s| %06ld %02d ] ==%s %c ==) ",
|
|
" ...aborting; please wait... ",
|
|
v_sector,overlap/CD_FRAMEWORDS,smilie,heartbeat);
|
|
}else{
|
|
if(v_sector==0)
|
|
sprintf(buffer,
|
|
"\r (== PROGRESS == [%s| ...... %02d ] ==%s %c ==) ",
|
|
dispcache,overlap/CD_FRAMEWORDS,smilie,heartbeat);
|
|
|
|
else
|
|
sprintf(buffer,
|
|
"\r (== PROGRESS == [%s| %06ld %02d ] ==%s %c ==) ",
|
|
dispcache,v_sector,overlap/CD_FRAMEWORDS,smilie,heartbeat);
|
|
|
|
if(aheadposition>=0 && aheadposition<graph && !(function==-1))
|
|
buffer[aheadposition+19]='>';
|
|
}
|
|
|
|
fprintf(stderr,buffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* clear the indicator for next batch */
|
|
if(function==-1)
|
|
memset(dispcache,' ',graph);
|
|
}
|
|
|
|
const char *optstring = "escCn:o:O:d:g:S:prRwafvqVQhZz::YXWBi:Tt:x:";
|
|
|
|
struct option options [] = {
|
|
{"stderr-progress", no_argument, NULL, 'e'},
|
|
{"search-for-drive", no_argument, NULL, 's'},
|
|
{"force-cdrom-little-endian", no_argument, NULL, 'c'},
|
|
{"force-cdrom-big-endian", no_argument, NULL, 'C'},
|
|
{"force-default-sectors", required_argument, NULL, 'n'},
|
|
{"force-search-overlap", required_argument, NULL, 'o'},
|
|
{"force-cdrom-device", required_argument, NULL, 'd'},
|
|
{"force-generic-device", required_argument, NULL, 'g'},
|
|
{"force-read-speed", required_argument, NULL, 'S'},
|
|
{"sample-offset", required_argument, NULL, 'O'},
|
|
{"toc-offset", required_argument, NULL, 't'},
|
|
{"toc-bias", no_argument, NULL, 'T'},
|
|
{"output-raw", no_argument, NULL, 'p'},
|
|
{"output-raw-little-endian", no_argument, NULL, 'r'},
|
|
{"output-raw-big-endian", no_argument, NULL, 'R'},
|
|
{"output-wav", no_argument, NULL, 'w'},
|
|
{"output-aiff", no_argument, NULL, 'f'},
|
|
{"output-aifc", no_argument, NULL, 'a'},
|
|
{"batch", no_argument, NULL, 'B'},
|
|
{"verbose", no_argument, NULL, 'v'},
|
|
{"quiet", no_argument, NULL, 'q'},
|
|
{"version", no_argument, NULL, 'V'},
|
|
{"query", no_argument, NULL, 'Q'},
|
|
{"help", no_argument, NULL, 'h'},
|
|
{"disable-paranoia", no_argument, NULL, 'Z'},
|
|
{"disable-extra-paranoia", no_argument, NULL, 'Y'},
|
|
{"abort-on-skip", no_argument, NULL, 'X'},
|
|
{"test-mode", required_argument, NULL, 'x'},
|
|
{"disable-fragmentation", no_argument, NULL, 'F'},
|
|
{"output-info", required_argument, NULL, 'i'},
|
|
{"never-skip", optional_argument, NULL, 'z'},
|
|
|
|
{NULL,0,NULL,0}
|
|
};
|
|
|
|
static cdrom_drive_t *d = NULL;
|
|
static cdrom_paranoia_t *p = NULL;
|
|
static char *span = NULL;
|
|
static char *force_cdrom_device = NULL;
|
|
static char *info_file = NULL;
|
|
|
|
#define free_and_null(p) \
|
|
if (p) free(p); \
|
|
p=NULL;
|
|
|
|
static void
|
|
cleanup (void)
|
|
{
|
|
if (p) paranoia_free(p);
|
|
if (d) cdda_close(d);
|
|
free_and_null(force_cdrom_device);
|
|
free_and_null(span);
|
|
free_and_null(info_file);
|
|
}
|
|
|
|
int
|
|
main(int argc,char *argv[])
|
|
{
|
|
int toc_bias = 0;
|
|
int toc_offset = 0;
|
|
int sample_offset = 0;
|
|
int force_cdrom_endian = -1;
|
|
int force_cdrom_sectors = -1;
|
|
int force_cdrom_overlap = -1;
|
|
int force_cdrom_speed = -1;
|
|
int test_flags = 0;
|
|
int max_retries = 20;
|
|
int output_type = 1; /* 0=raw, 1=wav, 2=aifc */
|
|
int output_endian = 0; /* -1=host, 0=little, 1=big */
|
|
int query_only = 0;
|
|
int batch = 0;
|
|
|
|
/* full paranoia, but allow skipping */
|
|
int paranoia_mode=PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP;
|
|
|
|
int out;
|
|
|
|
int search=0;
|
|
int c,long_option_index;
|
|
|
|
atexit(cleanup);
|
|
|
|
while((c=getopt_long(argc,argv,optstring,options,&long_option_index))!=EOF){
|
|
switch(c){
|
|
case 'B':
|
|
batch=1;
|
|
break;
|
|
case 'c':
|
|
force_cdrom_endian=0;
|
|
break;
|
|
case 'C':
|
|
force_cdrom_endian=1;
|
|
break;
|
|
case 'n':
|
|
force_cdrom_sectors=atoi(optarg);
|
|
break;
|
|
case 'o':
|
|
force_cdrom_overlap=atoi(optarg);
|
|
break;
|
|
case 'g':
|
|
case 'd':
|
|
if (force_cdrom_device) {
|
|
fprintf(stderr,
|
|
"Multiple cdrom devices given. Previous device %s ignored\n",
|
|
force_cdrom_device);
|
|
free(force_cdrom_device);
|
|
}
|
|
force_cdrom_device=strdup(optarg);
|
|
break;
|
|
case 'S':
|
|
force_cdrom_speed=atoi(optarg);
|
|
break;
|
|
case 'p':
|
|
output_type=0;
|
|
output_endian=-1;
|
|
break;
|
|
case 'r':
|
|
output_type=0;
|
|
output_endian=0;
|
|
break;
|
|
case 'R':
|
|
output_type=0;
|
|
output_endian=1;
|
|
break;
|
|
case 'w':
|
|
output_type=1;
|
|
output_endian=0;
|
|
break;
|
|
case 'a':
|
|
output_type=2;
|
|
output_endian=1;
|
|
break;
|
|
case 'f':
|
|
output_type=3;
|
|
output_endian=1;
|
|
break;
|
|
case 'v':
|
|
verbose=CDDA_MESSAGE_PRINTIT;
|
|
quiet=0;
|
|
break;
|
|
case 's':
|
|
search=1;
|
|
break;
|
|
case 'q':
|
|
verbose=CDDA_MESSAGE_FORGETIT;
|
|
quiet=1;
|
|
break;
|
|
case 'e':
|
|
callscript=1;
|
|
fprintf(stderr,
|
|
"Sending all callback output to stderr for wrapper script\n");
|
|
break;
|
|
case 'V':
|
|
fprintf(stderr,PARANOIA_VERSION);
|
|
fprintf(stderr,"\n");
|
|
exit(0);
|
|
break;
|
|
case 'Q':
|
|
query_only=1;
|
|
break;
|
|
case 'h':
|
|
usage(stdout);
|
|
exit(0);
|
|
case 'Z':
|
|
paranoia_mode=PARANOIA_MODE_DISABLE;
|
|
break;
|
|
case 'z':
|
|
if (optarg) {
|
|
max_retries = atoi (optarg);
|
|
paranoia_mode&=~PARANOIA_MODE_NEVERSKIP;
|
|
} else {
|
|
paranoia_mode|=PARANOIA_MODE_NEVERSKIP;
|
|
}
|
|
break;
|
|
case 'Y':
|
|
paranoia_mode|=PARANOIA_MODE_OVERLAP; /* cdda2wav style overlap
|
|
check only */
|
|
paranoia_mode&=~PARANOIA_MODE_VERIFY;
|
|
break;
|
|
case 'X':
|
|
/*paranoia_mode&=~(PARANOIA_MODE_SCRATCH|PARANOIA_MODE_REPAIR);*/
|
|
abort_on_skip=1;
|
|
break;
|
|
case 'W':
|
|
paranoia_mode&=~PARANOIA_MODE_REPAIR;
|
|
break;
|
|
case 'F':
|
|
paranoia_mode&=~(PARANOIA_MODE_FRAGMENT);
|
|
break;
|
|
case 'i':
|
|
if(info_file)free(info_file);
|
|
info_file=strdup(info_file);
|
|
break;
|
|
case 'T':
|
|
toc_bias=-1;
|
|
break;
|
|
case 't':
|
|
toc_offset=atoi(optarg);
|
|
break;
|
|
case 'O':
|
|
sample_offset=atoi(optarg);
|
|
break;
|
|
case 'x':
|
|
test_flags=atoi(optarg);
|
|
break;
|
|
default:
|
|
usage(stderr);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if(optind>=argc && !query_only){
|
|
if(batch)
|
|
span=NULL;
|
|
else{
|
|
/* D'oh. No span. Fetch me a brain, Igor. */
|
|
usage(stderr);
|
|
exit(1);
|
|
}
|
|
}else
|
|
if (argv[optind]) span=strdup(argv[optind]);
|
|
|
|
report(PARANOIA_VERSION);
|
|
|
|
/* Query the cdrom/disc; we may need to override some settings */
|
|
|
|
if(force_cdrom_device)
|
|
d=cdda_identify(force_cdrom_device,verbose,NULL);
|
|
else {
|
|
driver_id_t driver_id;
|
|
char **ppsz_cd_drives = cdio_get_devices_with_cap_ret(NULL,
|
|
CDIO_FS_AUDIO,
|
|
false,
|
|
&driver_id);
|
|
if (ppsz_cd_drives && *ppsz_cd_drives) {
|
|
d=cdda_identify(*ppsz_cd_drives,verbose, NULL);
|
|
} else {
|
|
report("\nUnable find or access a CD-ROM drive with an audio CD"
|
|
" in it.");
|
|
report("\nYou might try specifying the drive, especially if it has"
|
|
" mixed-mode (and non-audio) format tracks");
|
|
exit(1);
|
|
}
|
|
|
|
cdio_free_device_list(ppsz_cd_drives);
|
|
free(ppsz_cd_drives);
|
|
}
|
|
|
|
if(!d){
|
|
if(!verbose)
|
|
report("\nUnable to open cdrom drive; -v might give more information.");
|
|
exit(1);
|
|
}
|
|
|
|
if(verbose)
|
|
cdda_verbose_set(d,CDDA_MESSAGE_PRINTIT,CDDA_MESSAGE_PRINTIT);
|
|
else
|
|
cdda_verbose_set(d,CDDA_MESSAGE_PRINTIT,CDDA_MESSAGE_FORGETIT);
|
|
|
|
/* possibly force hand on endianness of drive, sector request size */
|
|
if(force_cdrom_endian!=-1){
|
|
d->bigendianp=force_cdrom_endian;
|
|
switch(force_cdrom_endian){
|
|
case 0:
|
|
report("Forcing CDROM sense to little-endian; ignoring preset and autosense");
|
|
break;
|
|
case 1:
|
|
report("Forcing CDROM sense to big-endian; ignoring preset and autosense");
|
|
break;
|
|
}
|
|
}
|
|
if (force_cdrom_sectors!=-1) {
|
|
if(force_cdrom_sectors<0 || force_cdrom_sectors>100){
|
|
report("Default sector read size must be 1<= n <= 100\n");
|
|
cdda_close(d);
|
|
d=NULL;
|
|
exit(1);
|
|
}
|
|
{
|
|
char buffer[256];
|
|
sprintf(buffer,"Forcing default to read %d sectors; "
|
|
"ignoring preset and autosense",force_cdrom_sectors);
|
|
report(buffer);
|
|
d->nsectors=force_cdrom_sectors;
|
|
}
|
|
}
|
|
if (force_cdrom_overlap!=-1) {
|
|
if (force_cdrom_overlap<0 || force_cdrom_overlap>CDIO_CD_FRAMES_PER_SEC) {
|
|
report("Search overlap sectors must be 0<= n <=75\n");
|
|
cdda_close(d);
|
|
d=NULL;
|
|
exit(1);
|
|
}
|
|
{
|
|
char buffer[256];
|
|
sprintf(buffer,"Forcing search overlap to %d sectors; "
|
|
"ignoring autosense",force_cdrom_overlap);
|
|
report(buffer);
|
|
}
|
|
}
|
|
|
|
switch( cdda_open(d) ) {
|
|
case -2:case -3:case -4:case -5:
|
|
report("\nUnable to open disc. Is there an audio CD in the drive?");
|
|
exit(1);
|
|
case -6:
|
|
report("\nCdparanoia could not find a way to read audio from this drive.");
|
|
exit(1);
|
|
case 0:
|
|
break;
|
|
default:
|
|
report("\nUnable to open disc.");
|
|
exit(1);
|
|
}
|
|
|
|
d->i_test_flags = test_flags;
|
|
|
|
/* Dump the TOC */
|
|
if (query_only || verbose ) display_toc(d);
|
|
|
|
if (query_only) exit(0);
|
|
|
|
/* bias the disc. A hack. Of course. this is never the default. */
|
|
/*
|
|
Some CD-ROM/CD-R drives will add an offset to the position on
|
|
reading audio data. This is usually around 500-700 audio samples
|
|
(ca. 1/75 second) on reading. So when this program queries a
|
|
specific sector, it might not receive exactly that sector, but
|
|
shifted by some amount.
|
|
|
|
Note that if ripping includes the end of the CD, this will this
|
|
cause this program to attempt to read partial sectors before or
|
|
past the known user data area of the disc, probably causing read
|
|
errors on most drives and possibly even hard lockups on some
|
|
buggy hardware.
|
|
|
|
[Note to libcdio driver hackers: make sure all CD-drivers don't
|
|
try to read outside of the stated disc boundaries.]
|
|
*/
|
|
if(sample_offset){
|
|
toc_offset+=sample_offset/588;
|
|
sample_offset%=588;
|
|
if(sample_offset<0){
|
|
sample_offset+=588;
|
|
toc_offset--;
|
|
}
|
|
}
|
|
|
|
if (toc_bias) {
|
|
toc_offset = -cdda_track_firstsector(d,1);
|
|
}
|
|
|
|
{
|
|
int i;
|
|
for( i=0; i < d->tracks+1; i++ )
|
|
d->disc_toc[i].dwStartSector+=toc_offset;
|
|
}
|
|
|
|
if (force_cdrom_speed != -1) {
|
|
cdda_speed_set(d,force_cdrom_speed);
|
|
}
|
|
|
|
if (d->nsectors==1) {
|
|
report("WARNING: The autosensed/selected sectors per read value is\n"
|
|
" one sector, making it very unlikely Paranoia can \n"
|
|
" work.\n\n"
|
|
" Attempting to continue...\n\n");
|
|
}
|
|
|
|
/* parse the span, set up begin and end sectors */
|
|
|
|
{
|
|
long i_first_lsn;
|
|
long i_last_lsn;
|
|
long batch_first;
|
|
long batch_last;
|
|
int batch_track;
|
|
|
|
if (span) {
|
|
/* look for the hyphen */
|
|
char *span2=strchr(span,'-');
|
|
if(strrchr(span,'-')!=span2){
|
|
report("Error parsing span argument");
|
|
exit(1);
|
|
}
|
|
|
|
if (span2!=NULL) {
|
|
*span2='\0';
|
|
span2++;
|
|
}
|
|
|
|
i_first_lsn=parse_offset(d, span, -1);
|
|
|
|
if(i_first_lsn==-1)
|
|
i_last_lsn=parse_offset(d, span2, cdda_disc_firstsector(d));
|
|
|
|
else
|
|
i_last_lsn=parse_offset(d, span2, i_first_lsn);
|
|
|
|
if (i_first_lsn == -1) {
|
|
if (i_last_lsn == -1) {
|
|
report("Error parsing span argument");
|
|
exit(1);
|
|
} else {
|
|
i_first_lsn=cdda_disc_firstsector(d);
|
|
}
|
|
} else {
|
|
if (i_last_lsn==-1) {
|
|
if (span2) { /* There was a hyphen */
|
|
i_last_lsn=cdda_disc_lastsector(d);
|
|
} else {
|
|
i_last_lsn=
|
|
cdda_track_lastsector(d,cdda_sector_gettrack(d, i_first_lsn));
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
i_first_lsn = cdda_disc_firstsector(d);
|
|
i_last_lsn = cdda_disc_lastsector(d);
|
|
}
|
|
|
|
{
|
|
char buffer[250];
|
|
int track1 = cdda_sector_gettrack(d, i_first_lsn);
|
|
int track2 = cdda_sector_gettrack(d, i_last_lsn);
|
|
long off1 = i_first_lsn - cdda_track_firstsector(d, track1);
|
|
long off2 = i_last_lsn - cdda_track_firstsector(d, track2);
|
|
int i;
|
|
|
|
for( i=track1; i<=track2; i++ )
|
|
if(!cdda_track_audiop(d,i)){
|
|
report("Selected span contains non audio tracks. Aborting.\n\n");
|
|
exit(1);
|
|
}
|
|
|
|
sprintf(buffer, "Ripping from sector %7ld (track %2d [%d:%02d.%02d])\n"
|
|
"\t to sector %7ld (track %2d [%d:%02d.%02d])\n",
|
|
i_first_lsn,
|
|
track1,
|
|
(int) (off1/(CDIO_CD_FRAMES_PER_MIN)),
|
|
(int) ((off1/CDIO_CD_FRAMES_PER_SEC) % CDIO_CD_SECS_PER_MIN),
|
|
(int) (off1 % CDIO_CD_FRAMES_PER_SEC),
|
|
i_last_lsn,
|
|
track2,
|
|
(int) (off2/(CDIO_CD_FRAMES_PER_MIN)),
|
|
(int) ((off2/CDIO_CD_FRAMES_PER_SEC) % CDIO_CD_SECS_PER_MIN),
|
|
(int) (off2 % CDIO_CD_FRAMES_PER_SEC));
|
|
report(buffer);
|
|
|
|
}
|
|
|
|
{
|
|
long cursor;
|
|
int16_t offset_buffer[1176];
|
|
int offset_buffer_used=0;
|
|
int offset_skip=sample_offset*4;
|
|
|
|
p=paranoia_init(d);
|
|
paranoia_modeset(p,paranoia_mode);
|
|
if(force_cdrom_overlap!=-1)paranoia_overlapset(p,force_cdrom_overlap);
|
|
|
|
if(verbose) {
|
|
cdda_verbose_set(d,CDDA_MESSAGE_LOGIT,CDDA_MESSAGE_LOGIT);
|
|
cdio_loglevel_default = CDIO_LOG_INFO;
|
|
} else
|
|
cdda_verbose_set(d,CDDA_MESSAGE_FORGETIT,CDDA_MESSAGE_FORGETIT);
|
|
|
|
paranoia_seek(p,cursor=i_first_lsn,SEEK_SET);
|
|
|
|
/* this is probably a good idea in general */
|
|
seteuid(getuid());
|
|
setegid(getgid());
|
|
|
|
/* we'll need to be able to read one sector past user data if we
|
|
have a sample offset in order to pick up the last bytes. We
|
|
need to set the disc length forward here so that the libs are
|
|
willing to read past, assuming that works on the hardware, of
|
|
course */
|
|
if(sample_offset)
|
|
d->disc_toc[d->tracks].dwStartSector++;
|
|
|
|
while(cursor<=i_last_lsn){
|
|
char outfile_name[256];
|
|
if ( batch ){
|
|
batch_first = cursor;
|
|
batch_track = cdda_sector_gettrack(d,cursor);
|
|
batch_last = cdda_track_lastsector(d, batch_track);
|
|
if (batch_last>i_last_lsn) batch_last=i_last_lsn;
|
|
} else {
|
|
batch_first = i_first_lsn;
|
|
batch_last = i_last_lsn;
|
|
batch_track = -1;
|
|
}
|
|
|
|
callbegin=batch_first;
|
|
callend=batch_last;
|
|
|
|
/* argv[optind] is the span, argv[optind+1] (if exists) is outfile */
|
|
|
|
if (optind+1<argc) {
|
|
if (!strcmp(argv[optind+1],"-") ){
|
|
out = dup(fileno(stdout));
|
|
if(batch)
|
|
report("Are you sure you wanted 'batch' "
|
|
"(-B) output with stdout?");
|
|
report("outputting to stdout\n");
|
|
outfile_name[0]='\0';
|
|
} else {
|
|
char path[256];
|
|
|
|
char *post=strrchr(argv[optind+1],'/');
|
|
int pos=(post?post-argv[optind+1]+1:0);
|
|
char *file=argv[optind+1]+pos;
|
|
|
|
path[0]='\0';
|
|
|
|
if(pos)
|
|
strncat(path,argv[optind+1],pos>256?256:pos);
|
|
|
|
if(batch)
|
|
snprintf(outfile_name, 246, " %strack%02d.%s", path,
|
|
batch_track, file);
|
|
else
|
|
snprintf(outfile_name, 246, "%s%s", path, file);
|
|
|
|
if(file[0]=='\0'){
|
|
switch (output_type) {
|
|
case 0: /* raw */
|
|
strcat(outfile_name, "cdda.raw");
|
|
break;
|
|
case 1:
|
|
strcat(outfile_name, "cdda.wav");
|
|
break;
|
|
case 2:
|
|
strcat(outfile_name, "cdda.aifc");
|
|
break;
|
|
case 3:
|
|
strcat(outfile_name, "cdda.aiff");
|
|
break;
|
|
}
|
|
}
|
|
|
|
out=open(outfile_name,O_RDWR|O_CREAT|O_TRUNC,0666);
|
|
if(out==-1){
|
|
report3("Cannot open specified output file %s: %s",
|
|
outfile_name, strerror(errno));
|
|
exit(1);
|
|
}
|
|
report2("outputting to %s\n", outfile_name);
|
|
}
|
|
} else {
|
|
/* default */
|
|
if (batch)
|
|
sprintf(outfile_name,"track%02d.", batch_track);
|
|
else
|
|
outfile_name[0]='\0';
|
|
|
|
switch(output_type){
|
|
case 0: /* raw */
|
|
strcat(outfile_name,"cdda.raw");
|
|
break;
|
|
case 1:
|
|
strcat(outfile_name,"cdda.wav");
|
|
break;
|
|
case 2:
|
|
strcat(outfile_name,"cdda.aifc");
|
|
break;
|
|
case 3:
|
|
strcat(outfile_name,"cdda.aiff");
|
|
break;
|
|
}
|
|
|
|
out = open(outfile_name, O_RDWR|O_CREAT|O_TRUNC, 0666);
|
|
if(out==-1){
|
|
report3("Cannot open default output file %s: %s", outfile_name,
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
report2("outputting to %s\n", outfile_name);
|
|
}
|
|
|
|
switch(output_type) {
|
|
case 0: /* raw */
|
|
break;
|
|
case 1: /* wav */
|
|
WriteWav(out, (batch_last-batch_first+1)*CDIO_CD_FRAMESIZE_RAW);
|
|
break;
|
|
case 2: /* aifc */
|
|
WriteAifc(out, (batch_last-batch_first+1)*CDIO_CD_FRAMESIZE_RAW);
|
|
break;
|
|
case 3: /* aiff */
|
|
WriteAiff(out, (batch_last-batch_first+1)*CDIO_CD_FRAMESIZE_RAW);
|
|
break;
|
|
}
|
|
|
|
/* Off we go! */
|
|
|
|
if(offset_buffer_used){
|
|
/* partial sector from previous batch read */
|
|
cursor++;
|
|
if (buffering_write(out,
|
|
((char *)offset_buffer)+offset_buffer_used,
|
|
CDIO_CD_FRAMESIZE_RAW-offset_buffer_used)){
|
|
report2("Error writing output: %s", strerror(errno));
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
skipped_flag=0;
|
|
while(cursor<=batch_last){
|
|
/* read a sector */
|
|
int16_t *readbuf=paranoia_read_limited(p, callback, max_retries);
|
|
char *err=cdda_errors(d);
|
|
char *mes=cdda_messages(d);
|
|
|
|
if(mes || err)
|
|
fprintf(stderr,"\r "
|
|
" \r%s%s\n",
|
|
mes?mes:"",err?err:"");
|
|
|
|
if (err) free(err);
|
|
if (mes) free(mes);
|
|
if( readbuf==NULL) {
|
|
skipped_flag=1;
|
|
report("\nparanoia_read: Unrecoverable error, bailing.\n");
|
|
break;
|
|
}
|
|
if(skipped_flag && abort_on_skip){
|
|
cursor=batch_last+1;
|
|
break;
|
|
}
|
|
|
|
skipped_flag=0;
|
|
cursor++;
|
|
|
|
if (output_endian!=bigendianp()) {
|
|
int i;
|
|
for (i=0; i<CDIO_CD_FRAMESIZE_RAW/2; i++)
|
|
readbuf[i]=UINT16_SWAP_LE_BE_C(readbuf[i]);
|
|
}
|
|
|
|
callback(cursor*(CD_FRAMEWORDS)-1,-2);
|
|
|
|
if (buffering_write(out,((char *)readbuf)+offset_skip,
|
|
CDIO_CD_FRAMESIZE_RAW-offset_skip)){
|
|
report2("Error writing output: %s", strerror(errno));
|
|
exit(1);
|
|
}
|
|
offset_skip=0;
|
|
|
|
if (output_endian != bigendianp()){
|
|
int i;
|
|
for (i=0; i<CDIO_CD_FRAMESIZE_RAW/2; i++)
|
|
readbuf[i] = UINT16_SWAP_LE_BE_C(readbuf[i]);
|
|
}
|
|
|
|
/* One last bit of silliness to deal with sample offsets */
|
|
if(sample_offset && cursor>batch_last){
|
|
int i;
|
|
/* read a sector and output the partial offset. Save the
|
|
rest for the next batch iteration */
|
|
readbuf=paranoia_read_limited(p,callback,max_retries);
|
|
err=cdda_errors(d);mes=cdda_messages(d);
|
|
|
|
if(mes || err)
|
|
fprintf(stderr,"\r "
|
|
" \r%s%s\n",
|
|
mes?mes:"",err?err:"");
|
|
|
|
if(err)free(err);if(mes)free(mes);
|
|
if(readbuf==NULL){
|
|
skipped_flag=1;
|
|
report("\nparanoia_read: Unrecoverable error reading through "
|
|
"sample_offset shift\n\tat end of track, bailing.\n");
|
|
break;
|
|
}
|
|
if (skipped_flag && abort_on_skip) break;
|
|
skipped_flag=0;
|
|
/* do not move the cursor */
|
|
|
|
if(output_endian!=bigendianp())
|
|
for(i=0;i<CDIO_CD_FRAMESIZE_RAW/2;i++)
|
|
offset_buffer[i]=UINT16_SWAP_LE_BE_C(readbuf[i]);
|
|
else
|
|
memcpy(offset_buffer,readbuf,CDIO_CD_FRAMESIZE_RAW);
|
|
offset_buffer_used=sample_offset*4;
|
|
|
|
callback(cursor*(CD_FRAMEWORDS),-2);
|
|
|
|
if(buffering_write(out,(char *)offset_buffer,
|
|
offset_buffer_used)){
|
|
report2("Error writing output: %s", strerror(errno));
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
callback(cursor*(CDIO_CD_FRAMESIZE_RAW/2)-1,-1);
|
|
buffering_close(out);
|
|
if(skipped_flag){
|
|
/* remove the file */
|
|
report2("\nRemoving aborted file: %s", outfile_name);
|
|
unlink(outfile_name);
|
|
/* make the cursor correct if we have another track */
|
|
if(batch_track!=-1){
|
|
batch_track++;
|
|
cursor=cdda_track_firstsector(d,batch_track);
|
|
paranoia_seek(p,cursor, SEEK_SET);
|
|
offset_skip=sample_offset*4;
|
|
offset_buffer_used=0;
|
|
}
|
|
}
|
|
report("\n");
|
|
}
|
|
|
|
paranoia_free(p);
|
|
p=NULL;
|
|
}
|
|
}
|
|
|
|
report("Done.\n\n");
|
|
|
|
return 0;
|
|
}
|