#include #include #include #include #include #include #include #define VERSION "0.0.1" #ifndef PATH_MAX # define PATH_MAX 1024 #endif /* This should be big enough to hold any CD sector */ #define BUF_SIZE 4096 const char *self; char *imagefile = NULL; char *outfilebase = "track"; char *outfmt = "wav"; int want_track = 0; /* -1 = no tracks, 0 = all, 1-99 = just that one */ double quality = 0.7l; int swap_bytes = 0; int autoname = 0; int want_data = 1; int want_audio = 1; MirageContext *mirage = NULL; MirageDisc *disc = NULL; MirageSession *session = NULL; int output_track_number = 0; int total_bytes = 0; void die(char *msg) { fprintf(stderr, "%s: %s\n", self, msg); exit(1); } const guint8 *apply_s_opt(const guint8 *buf, gint len) { int i; static guint8 newbuf[BUF_SIZE]; if(!swap_bytes) return buf; for(i = 0; i < len; i += 2) { newbuf[i] = buf[i + 1]; newbuf[i + 1] = buf[i]; } return (const guint8 *)newbuf; } void print_progress(void) { static char spinner[] = "-/|\\"; static int count = 0; putchar(spinner[count++]); putchar('\b'); fflush(stdout); if(count == 4) count = 0; } char *human_mb(int bytes) { static char buf[10]; sprintf(buf, "%2.1f", (float)bytes / 1048576.0); return buf; } void extract_track(int t, int extract) { MirageTrack *track; MirageSector *sector; int sector_type, sec = 0; gint len; const guint8 *buf; char *ext; char outfile[PATH_MAX + 1]; FILE *out = NULL; SNDFILE *sfout = NULL; struct SF_INFO sfi; int bytes = 0; printf(" Track %d (%d): ", t + 1, output_track_number); if(!(track = mirage_session_get_track_by_index(session, t, NULL))) die("can't read track"); sector_type = mirage_track_get_sector_type(track); switch(sector_type) { case MIRAGE_SECTOR_MODE1: case MIRAGE_SECTOR_MODE2_FORM1: printf("data, "); ext = "iso"; if(!want_data) extract = 0; break; case MIRAGE_SECTOR_AUDIO: printf("audio, "); if(!want_audio) extract = 0; ext = outfmt; break; default: printf(", "); ext = "raw"; if(!want_data) extract = 0; break; } sector = mirage_track_get_sector(track, 0, 0, NULL); sprintf(outfile, "%s%02d.%s", outfilebase, output_track_number, ext); if(extract) { if((sector_type == MIRAGE_SECTOR_AUDIO) && (outfmt[0] != 'c')) { sfi.samplerate = 44100; sfi.channels = 2; switch(outfmt[0]) { case 'o': sfi.format = SF_FORMAT_OGG | SF_FORMAT_VORBIS; break; case 'f': sfi.format = SF_FORMAT_FLAC | SF_FORMAT_PCM_16; break; case 'w': sfi.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; break; } if(!(sfout = sf_open(outfile, SFM_WRITE, &sfi))) die("can't open output audio file"); sf_command(sfout, SFC_SET_VBR_ENCODING_QUALITY, &quality, sizeof(double)); } else { if(!(out = fopen(outfile, "wb"))) { die("can't open output file"); } } } sec = mirage_track_get_track_start(track); while((sector = mirage_track_get_sector(track, sec++, 0, NULL))) { mirage_sector_get_data(sector, &buf, &len, NULL); bytes += len; if(extract) { if(sfout) { sf_write_short(sfout, (const short *)apply_s_opt(buf, len), len / 2); } else { fwrite(buf, len, 1, out); } if(isatty(1) && !(sec % 100)) print_progress(); } g_object_unref(sector); } if(sfout) sf_close(sfout); if(out) fclose(out); printf("%d bytes (%sMB, %d sectors)\n", bytes, human_mb(bytes), sec); if(extract) printf(" Extracted to %s\n", outfile); total_bytes += bytes; return; } void set_auto_name(void) { static char autoname[PATH_MAX + 1]; char *p; strcpy(autoname, imagefile); if((p = strrchr(autoname, '.'))) { *p = '\0'; } outfilebase = autoname; } void usage(int exit_code) { fprintf(exit_code ? stderr : stdout, "miragextract v" VERSION " by B. Watson, WTFPL\n" "Usage: %s [-l] [-s] [-n] [-a] [-d] [-t track] [-b base]\n" " [-f fmt ] [-q quality ] image-file\n" "See man page for details\n", self); exit(exit_code); } void parse_args(int argc, char **argv) { if(argc == 1) usage(0); if(argc > 1 && strcmp(argv[1], "--help") == 0) usage(0); while(++argv, --argc) { if(argv[0][0] == '-') { char *nextarg = argv[1]; switch(argv[0][1]) { case 'l': want_track = -1; break; case 'a': want_audio = 1; want_data = 0; break; case 'd': want_audio = 0; want_data = 1; break; case 's': swap_bytes = 1; break; case 't': if(nextarg) { argv++, argc--; want_track = atoi(nextarg); if(want_track < 1 || want_track > 99) die("invalid track number for -t (must be 1-99)"); } else die("-t option requires track argument"); break; case 'b': if(nextarg) { argv++, argc--; outfilebase = nextarg; } else die("-b option requires base filename argument"); break; case 'f': if(nextarg) { argv++, argc--; outfmt = nextarg; } else die("-f option requires format argument"); break; case 'q': if(nextarg) { argv++, argc--; quality = (double)atoi(nextarg) / 10.0l; if(quality < 0.0l || quality > 1.0l) die("invalid quality for -q (must be 1-10)"); } else die("-t option requires track argument"); break; case 'n': autoname = 1; break; default: die("unrecognized option"); break; } } else if(imagefile) { die("too many arguments on command line"); } else { imagefile = *argv; } } if(!imagefile) die("missing image file argument"); if(autoname) set_auto_name(); } int main(int argc, char **argv) { const char *p; gchar *fn[2]; int s, t; /* get rid of any path elements in our executable name */ self = argv[0]; for(p = self; *p; p++) if(p[0] == '/' && p[1]) self = p + 1; parse_args(argc, argv); if(!((mirage = g_object_new(MIRAGE_TYPE_CONTEXT, NULL)))) die("couldn't get mirage context"); if(!mirage_initialize(NULL)) die("couldn't initialize libmirage"); /* TODO: find out what image formats require multiple filenames, if any */ fn[0] = imagefile; fn[1] = NULL; if(!(disc = mirage_context_load_image(mirage, fn, NULL))) die("couldn't load image"); for(s = 0; s < mirage_disc_get_number_of_sessions(disc); s++) { if(!(session = mirage_disc_get_session_by_index(disc, s, NULL))) die("couldn't read session"); printf("Session %d\n", s + 1); for(t = 0; t < mirage_session_get_number_of_tracks(session); t++) { int extract = 0; output_track_number++; if(want_track == 0 || want_track == output_track_number) extract = 1; extract_track(t, extract); } } mirage_shutdown(NULL); printf("Total size: %d bytes (%sMB)\n", total_bytes, human_mb(total_bytes)); return 0; }