This is a discussion on Re: [PATCH] cdio(1) to grab tracks from CD within the mailing.openbsd.tech forums, part of the OpenBSD category; --> Hi! I'm glad to release 3rd version of cdio patch for track ripping. What it does: 1. Ripping all, ...
| |||||||
| Register | FAQ | Members List | Calendar | Search | Today's Posts | Mark Forums Read |
| ||||
| Hi! I'm glad to release 3rd version of cdio patch for track ripping. What it does: 1. Ripping all, individual tracks or track ranges. 2. Playing all, individual tracks or track ranges. Playing is implemented just like ripping but all ripped data goes to /dev/audio. Rip command: rip. Play command: play. Old play command i renamed into cdplay. I understand that someone uses ``play'' in scripts... and i'm open to ideas and proposals. Rip syntax: cdio rip N-M Y Z A- -Z Before ripping all tracks are sorted. Rip never rips a track twice. For example, ``cdio rip 1-6 5-3 7'' will rip 1-7 Play syntax: cdio play 7-4 11 12- 1 2 ``Play'' plays tracks in order they are given in command line. If we specify track range in descent order it will play tracks in descent order. Any comments are welcome! (this patch is available in http://www.bsdua.org/patches/cdio3.diff) -- Alexey Vatchenko http://www.bsdua.org E-mail: avv@mail.zp.ua JID: alex.vatchenko@gmail.com Index: Makefile ================================================== ================= RCS file: /cvs/src/usr.bin/cdio/Makefile,v retrieving revision 1.4 diff -u -u -r1.4 Makefile --- Makefile 31 May 2006 01:14:41 -0000 1.4 +++ Makefile 26 Apr 2007 10:02:53 -0000 @@ -3,7 +3,7 @@ PROG= cdio DPADD= ${LIBUTIL} ${LIBEDIT} ${LIBTERMCAP} LDADD= -lutil -ledit -ltermcap -SRCS= cdio.c cddb.c mmc.c +SRCS= cdio.c cddb.c mmc.c rip.c CDIAGFLAGS=-Wall -W -Wmissing-prototypes -pedantic .include <bsd.prog.mk> Index: cdio.1 ================================================== ================= RCS file: /cvs/src/usr.bin/cdio/cdio.1,v retrieving revision 1.41 diff -u -u -r1.41 cdio.1 --- cdio.1 26 Mar 2007 11:46:07 -0000 1.41 +++ cdio.1 26 Apr 2007 10:02:53 -0000 @@ -81,7 +81,7 @@ Only as many characters as are required to uniquely identify a command need be specified. The word -.Ic play +.Ic cdplay may be omitted. .Bl -tag -width Ds .It Ic blank @@ -118,13 +118,13 @@ .It Ic pause Stop playing. Do not stop the disc. -.It Ic play Ar first_track Op Ar last_track +.It Ic cdplay Ar first_track Op Ar last_track Play from track .Ar first_track to track .Ar last_track . The first track has number 1 and may be omitted in all cases. -.It Xo Ic play +.It Xo Ic cdplay .Op Ar tr1 .Ar m1 : Ns Ar s1 Ns Op .f1 .Op Ar tr2 @@ -153,12 +153,22 @@ Minutes are in the range 0\(en99. Seconds are in the range 0\(en59. Frame numbers are in the range 0\(en74. -.It Ic play Op Ar #start_block Op Ar length +.It Ic cdplay Op Ar #start_block Op Ar length Play starting from the logical block .Ar start_block using .Ar length logical blocks. +.It Ic play +.Op Ar trackM-TrackN +.Ar ... +Play specified tracks from disk. Unlike +.Ic cdplay +it rips tracks from disk and outputs audio data to +.Pa /dev/audio +file. Allowed to specify both individual tracks and track ranges. If range is +specified in descent order tracks will be played in descent order. +If the first value in range is omitted, tracks from first track on disk to the specified one will be played. If the last value in range is omitted, tracks from the specified track to the last track on disk will be played. .It Ic previous Play the previous track. If we're at the first track, restart. @@ -172,6 +182,13 @@ Resume playing. Used after the .Ic pause +command. +.It Ic rip +.Op Ar trackM-TrackN +.Ar ... +Rip specified tracks from disk. Audio tracks are saved as WAVE sound files. +All tracks will be saved in current directory. If parameters are omitted, it means rip all tracks. Allowed to specify both individual tracks and track ranges just like in +.Ic play command. .It Ic set Ar lba | msf Set LBA Index: cdio.c ================================================== ================= RCS file: /cvs/src/usr.bin/cdio/cdio.c,v retrieving revision 1.56 diff -u -u -r1.56 cdio.c --- cdio.c 25 Aug 2006 04:38:32 -0000 1.56 +++ cdio.c 26 Apr 2007 10:02:57 -0000 @@ -90,7 +90,7 @@ #define CMD_HELP 4 #define CMD_INFO 5 #define CMD_PAUSE 6 -#define CMD_PLAY 7 +#define CMD_CDPLAY 7 #define CMD_QUIT 8 #define CMD_RESUME 9 #define CMD_STOP 10 @@ -105,6 +105,8 @@ #define CMD_CDDB 19 #define CMD_CDID 20 #define CMD_BLANK 21 +#define CMD_RIP 22 +#define CMD_PLAY 23 struct cmdtab { int command; @@ -121,9 +123,9 @@ { CMD_INFO, "info", 1, "" }, { CMD_NEXT, "next", 1, "" }, { CMD_PAUSE, "pause", 2, "" }, -{ CMD_PLAY, "play", 1, "track1[.index1] [track2[.index2]]" }, -{ CMD_PLAY, "play", 1, "[tr1] m1:s1[.f1] [tr2] [m2:s2[.f2]]" }, -{ CMD_PLAY, "play", 1, "[#block [len]]" }, +{ CMD_CDPLAY, "cdplay", 1, "track1[.index1] [track2[.index2]]" }, +{ CMD_CDPLAY, "cdplay", 1, "[tr1] m1:s1[.f1] [tr2] [m2:s2[.f2]]" }, +{ CMD_CDPLAY, "cdplay", 1, "[#block [len]]" }, { CMD_PREV, "previous", 2, "" }, { CMD_QUIT, "quit", 1, "" }, { CMD_RESET, "reset", 4, "" }, @@ -137,6 +139,8 @@ { CMD_CDID, "cdid", 3, "" }, { CMD_QUIT, "exit", 2, "" }, { CMD_BLANK, "blank", 1, "" }, +{ CMD_RIP, "rip", 1, "[[track1-trackN] ...]" }, +{ CMD_PLAY, "play", 1, "[[track1-trackN] ...]" }, { 0, 0, 0, 0} }; @@ -161,7 +165,7 @@ int play_track(int, int, int, int); int get_vol(int *, int *); int status(int *, int *, int *, int *); -int play(char *arg); +int cdplay(char *arg); int info(char *arg); int cddbinfo(char *arg); int pstatus(char *arg); @@ -206,8 +210,8 @@ printf(" %s", c->args); printf("\n"); } - printf("\n\tThe word \"play\" is not required for the play commands.\n"); - printf("\tThe plain target address is taken as a synonym for play.\n"); + printf("\n\tThe word \"cdplay\" is not required for the cdplay commands.\n"); + printf("\tThe plain target address is taken as a synonym for cdplay.\n"); } void @@ -506,14 +510,14 @@ return (0); #endif - case CMD_PLAY: + case CMD_CDPLAY: if (fd < 0 && ! open_cd(cdname, 0)) return (0); while (isspace(*arg)) arg++; - return play(arg); + return cdplay(arg); case CMD_SET: if (!strcasecmp(arg, "msf")) @@ -572,6 +576,22 @@ return 0; return blank(); + case CMD_RIP: + if (fd < 0 && ! open_cd(cdname, 0)) + return (0); + + while (isspace(*arg)) + arg++; + + return rip(arg); + case CMD_PLAY: + if (fd < 0 && ! open_cd(cdname, 0)) + return (0); + + while (isspace(*arg)) + arg++; + + return play(arg); default: case CMD_HELP: help(); @@ -581,7 +601,7 @@ } int -play(char *arg) +cdplay(char *arg) { struct ioc_toc_header h; unsigned char tm, ts, tf; @@ -1424,7 +1444,7 @@ continue; if (isdigit(*p) || (p[0] == '#' && isdigit(p[1]))) { - *cmd = CMD_PLAY; + *cmd = CMD_CDPLAY; return (p); } Index: rip.c ================================================== ================= RCS file: rip.c diff -N rip.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ rip.c 26 Apr 2007 10:02:57 -0000 @@ -0,0 +1,590 @@ +/* + * Copyright (c) 2007 Alexey Vatchenko <av@bsdua.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include <sys/types.h> + +#include <sys/audioio.h> +#include <sys/cdio.h> +#include <sys/ioctl.h> +#include <sys/scsiio.h> +#include <sys/stat.h> + +#include <scsi/scsi_all.h> +#include <scsi/scsi_disk.h> +#include <scsi/scsiconf.h> +#include <scsi/cd.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +extern int fd; +extern int msf; +extern struct cd_toc_entry *toc_buffer; + +extern u_int msf2lba(u_char m, u_char s, u_char f); +extern int read_toc_entrys(int size); + +/* + * Arguments parser + */ +TAILQ_HEAD(track_pair_head, track_pair); + +static int _parse_val(char *start, char *nxt, int *val); +static int _parse_pair(char *start, char *nxt, int *val1, int *val2); +static int _add_pair(struct track_pair_head *head, int val1, int val2, + int issorted); + +struct track_pair { + u_char start; + u_char end; + TAILQ_ENTRY(track_pair) list; +}; + +void parse_tracks_init(struct track_pair_head *head); +void parse_tracks_final(struct track_pair_head *head); +int parse_tracks(struct track_pair_head *head, u_char first, u_char last, + const char *arg, int issorted); +int parse_tracks_add(struct track_pair_head *head, u_char first, + u_char last, int issorted); + +/* + * Tracks ripping + */ +/* Header of the canonical WAVE file */ +static u_char wavehdr[44] = { + 'R', 'I', 'F', 'F', 0x0, 0x0, 0x0, 0x0, 'W', 'A', 'V', 'E', + 'f', 'm', 't', ' ', 0x10, 0x0, 0x0, 0x0, 0x1, 0x0, 0x2, 0x0, + 0x44, 0xac, 0x0, 0x0, 0x10, 0xb1, 0x2, 0x0, 0x4, 0x0, 0x10, 0x0, + 'd', 'a', 't', 'a', 0x0, 0x0, 0x0, 0x0 +}; + +static int write_sector(int fd, u_char *sec, u_int32_t secsize); + +int read_data_sector(int fd, u_int32_t lba, u_char *sec, u_int32_t secsize); + +struct track_info { + int fd; /* descriptor of output file */ + u_int track; /* track number */ + char name[12]; /* output file name, i.e. trackXX.wav/trackXX.dat */ + u_char isaudio; /* true if audio track, otherwise it's data track */ + u_int32_t start_lba; /* starting address of this track */ + u_int32_t end_lba; /* starting address of the next track */ +}; + +int read_track(int fd, struct track_info *ti); + +int rip_next_track(struct track_info *info); +int play_next_track(struct track_info *info); + +static int rip_tracks_loop(struct track_pair *tp, u_int n_tracks, + int (*next_track)(struct track_info *)); + +int rip_tracks(char *arg, int (*next_track)(struct track_info *), + int issorted); + +/* Next-Track function exit codes */ +#define NXTRACK_OK 0 +#define NXTRACK_FAIL 1 +#define NXTRACK_SKIP 2 + +static int +_parse_val(char *start, char *nxt, int *val) +{ + char *p; + int i, base, n; + + n = nxt - start; + + if (n > 3 || n < 1) + return (-1); + for (p = start; p < nxt; p++) { + if (!isdigit(*p)) + return (-1); + } + + *val = 0; + base = 1; + for (i = 0; i < n; i++) { + *val += base * (start[n - i - 1] - '0'); + base *= 10; + } + return (0); +} + +static int +_parse_pair(char *start, char *nxt, int *val1, int *val2) +{ + char *p, *delim; + int error; + + delim = NULL; + p = start; + while (p < nxt) { + if (*p == '-') + delim = p; + p++; + } + + if (delim != NULL) { + error = 0; + if (delim - start < 1) + *val1 = -1; + else + error = _parse_val(start, delim, val1); + + if (error == 0) { + if ((nxt - delim - 1) < 1) + *val2 = -1; + else + error = _parse_val(delim + 1, nxt, val2); + } + } else { + error = _parse_val(start, nxt, val1); + *val2 = *val1; + } + + if (error == 0) { + if (*val1 > 99 || *val2 > 99) + error = -1; + } + + return (error); +} + +static int +_add_pair(struct track_pair_head *head, int val1, int val2, int issorted) +{ + u_char v1, v2, v3; + struct track_pair *tp, *entry; + int fix; + + v1 = (u_char)val1; + v2 = (u_char)val2; + + if (issorted) { + /* 1. Fix order */ + if (v1 > v2) { + v3 = v1; + v1 = v2; + v2 = v3; + } + + /* 2. Find closest range and fix it */ + fix = 0; + TAILQ_FOREACH(entry, head, list) { + if (v1 + 1 == entry->start || v1 == entry->start) + fix = 1; + else if (v1 > entry->start && v1 <= entry->end + 1) + fix = 1; + else if (v2 + 1 == entry->start || v2 == entry->start) + fix = 1; + else if (v2 > entry->start && v2 <= entry->end + 1) + fix = 1; + if (fix) + break; + } + + if (fix) { + if (v1 < entry->start) + entry->start = v1; + if (v2 > entry->end) + entry->end = v2; + + return (0); + } + } + + tp = (struct track_pair *)malloc(sizeof(*tp)); + if (tp == NULL) + return (-1); + + tp->start = v1; + tp->end = v2; + TAILQ_INSERT_TAIL(head, tp, list); + + return (0); +} + +void +parse_tracks_init(struct track_pair_head *head) +{ + + memset(head, 0, sizeof(*head)); + TAILQ_INIT(head); +} + +void +parse_tracks_final(struct track_pair_head *head) +{ + struct track_pair *tp; + + while ((tp = TAILQ_FIRST(head)) != TAILQ_END(head)) { + TAILQ_REMOVE(head, tp, list); + free(tp); + } +} + +int +parse_tracks(struct track_pair_head *head, u_char first, u_char last, + const char *arg, int issorted) +{ + char *p, *nxt; + int error, val1, val2; + + p = (char *)arg; + for (; + /* Skip trailing spaces */ + while (*p != '\0' && isspace(*p)) + ++p; + if (*p == '\0') + break; + + /* Search for the next space symbol */ + nxt = p; + while (*nxt != '\0' && !isspace(*nxt)) + ++nxt; + /* ``nxt'' can't be equal to ``p'' here */ + error = _parse_pair(p, nxt, &val1, &val2); + if (error != 0) + break; /* parse error */ + + if (val1 == -1) + val1 = first; + if (val2 == -1) + val2 = last; + + error = _add_pair(head, val1, val2, issorted); + if (error != 0) + break; /* allocation error */ + + p = nxt; + } + + return (0); +} + +int +parse_tracks_add(struct track_pair_head *head, u_char first, u_char last, + int issorted) +{ + + return _add_pair(head, first, last, issorted); +} + +static int +write_sector(int fd, u_char *sec, u_int32_t secsize) +{ + ssize_t res; + + while (secsize > 0) { + res = write(fd, sec, secsize); + if (res < 0) + return (-1); + + sec += res; + secsize -= res; + } + + return (0); +} + +/* + * ERRORS + * The function can return + * [EBUSY] Device is busy. + * [ETIMEDOUT] Operation timeout. + * [EIO] Any other errors. + * [EAGAIN] The operation must be made again. XXX - not implemented + */ +int +read_data_sector(int fd, u_int32_t lba, u_char *sec, u_int32_t secsize) +{ + scsireq_t scr; + u_char *cmd; + int error; + + memset(&scr, 0, sizeof(scr)); + + cmd = (u_char *)scr.cmd; + cmd[0] = 0xbe; /* READ CD */ + _lto4b(lba, cmd + 2); /* Starting Logical Block Address */ + _lto3b(1, cmd + 6); /* Transfer Length in Blocks */ + cmd[9] = 0x10; /* User Data field */ + + scr.flags = SCCMD_ESCAPE | SCCMD_READ; + scr.databuf = sec; + scr.datalen = secsize; + scr.cmdlen = 12; + scr.timeout = 120000; + scr.senselen = SENSEBUFLEN; + + /* XXX - what's wrong with DVD? */ + + error = ioctl(fd, SCIOCCOMMAND, &scr); + if (error == -1) + return (EIO); + else if (scr.retsts == SCCMD_BUSY) + return (EBUSY); + else if (scr.retsts == SCCMD_TIMEOUT) + return (ETIMEDOUT); + else if (scr.retsts != SCCMD_OK) + return (EIO); + + return (0); +} + +int +read_track(int fd, struct track_info *ti) +{ + u_int32_t i, blksize, n_sec; + u_char *sec; + int error; + + n_sec = ti->end_lba - ti->start_lba; + blksize = (ti->isaudio) ? 2352 : 2048; + sec = (u_char *)malloc(blksize); + if (sec == NULL) + return (-1); + + for (i = 0; i < n_sec; ) { + fprintf(stderr, "track %u '%c' %08u/%08u %3u%%\r", ti->track, + (ti->isaudio) ? 'a' : 'd', i, n_sec, 100 * i / n_sec); + + error = read_data_sector(fd, i + ti->start_lba, sec, blksize); + if (error == 0) { + if (write_sector(ti->fd, sec, blksize) != 0) { + free(sec); + warnx("\nerror while writing to the %s file", + ti->name); + return (-1); + } + + i++; + } else if (error != EAGAIN) { + free(sec); + warnx("\nerror while reading from device"); + return (-1); + } + } + + free(sec); + fprintf(stderr, "track %u '%c' %08u/%08u 100%%\n", ti->track, + (ti->isaudio) ? 'a' : 'd', i, n_sec); + return (0); +} + +int +rip_next_track(struct track_info *info) +{ + int error; + u_int32_t size; + + info->fd = open(info->name, O_CREAT | O_TRUNC | O_RDWR, + S_IRUSR | S_IWUSR); + if (info->fd == -1) { + warnx("can't open %s file", info->name); + return (NXTRACK_FAIL); + } + + if (info->isaudio) { + /* + * Prepend audio track with Wave header + */ + size = 2352 * (info->end_lba - info->start_lba); + *(u_int32_t *)(wavehdr + 4) = htole32(size + 36); + *(u_int32_t *)(wavehdr + 40) = htole32(size); + error = write_sector(info->fd, wavehdr, sizeof(wavehdr)); + if (error == -1) { + warnx("can't write WAVE header for %s file", + info->name); + return (NXTRACK_FAIL); + } + } + + return (NXTRACK_OK); +} + +int +play_next_track(struct track_info *info) +{ + int fd, error; + audio_info_t ai; + + if (!info->isaudio) + return (NXTRACK_SKIP); + + info->fd = open("/dev/audio", O_CREAT | O_TRUNC | O_RDWR, + S_IRUSR | S_IWUSR); + if (info->fd == -1) { + warnx("can't open /dev/audio"); + return (NXTRACK_SKIP); /* just skip this track */ + } + + fd = open("/dev/audioctl", O_RDWR); + if (fd != -1) { + AUDIO_INITINFO(&ai); + ai.play.sample_rate = 44100; + ai.play.channels = 2; + ai.play.precision = 16; + ai.play.encoding = AUDIO_ENCODING_SLINEAR_LE; + error = ioctl(fd, AUDIO_SETINFO, &ai); + close(fd); + } else + error = -1; + + if (error == -1) { + warnx("can't configure audio device"); + close(info->fd); + info->fd = -1; + return (NXTRACK_FAIL); + } + + return (NXTRACK_OK); +} + +static int +rip_tracks_loop(struct track_pair *tp, u_int n_tracks, + int (*next_track)(struct track_info *)) +{ + struct track_info info; + u_char trk; + u_int i; + char order; + int error; + + order = (tp->start > tp->end) ? -1 : 1; + trk = tp->start; + for (; + error = 0; + for (i = 0; i < n_tracks; i++) { + if (trk == toc_buffer[i].track) + break; + } + + if (i != n_tracks) { + /* Track is found */ + info.track = toc_buffer[i].track; + info.isaudio = (toc_buffer[i].control & 4) == 0; + snprintf(info.name, sizeof(info.name), "track%02u.%s", + toc_buffer[i].track, + (info.isaudio) ? "wav" : "dat"); + + error = next_track(&info); + if (error == NXTRACK_SKIP) + continue; + else if (error == NXTRACK_FAIL) { + error = -1; + break; + } + + if (msf) { + info.start_lba = msf2lba( + toc_buffer[i].addr.msf.minute, + toc_buffer[i].addr.msf.second, + toc_buffer[i].addr.msf.frame); + info.end_lba = msf2lba( + toc_buffer[i + 1].addr.msf.minute, + toc_buffer[i + 1].addr.msf.second, + toc_buffer[i + 1].addr.msf.frame); + } else { + info.start_lba = toc_buffer[i].addr.lba; + info.end_lba = toc_buffer[i + 1].addr.lba; + } + + error = read_track(fd, &info); + close(info.fd); + + if (error != 0) { + warnx("can't rip %u track", + toc_buffer[i].track); + break; + } + } + + if (trk == tp->end) + break; + trk += order; + } + + return (error); +} + +int +rip_tracks(char *arg, int (*next_track)(struct track_info *), int issorted) +{ + struct track_pair_head list; + struct track_pair *tp; + struct ioc_toc_header h; + u_int n; + int rc; + + rc = ioctl(fd, CDIOREADTOCHEADER, &h); + if (rc < 0) + return (rc); + + if (h.starting_track > h.ending_track) { + warnx("TOC starting_track > TOC ending_track"); + return (0); + } + + n = h.ending_track - h.starting_track + 1; + rc = read_toc_entrys((n + 1) * sizeof(struct cd_toc_entry)); + if (rc < 0) + return (rc); + + parse_tracks_init(&list); + /* We assume that all spaces are skipped in ``arg''. */ + if (arg == NULL || *arg == '\0') { + rc = parse_tracks_add(&list, h.starting_track, h.ending_track, + 0); + } else { + rc = parse_tracks(&list, h.starting_track, h.ending_track, arg, + issorted); + } + if (rc < 0) { + warnx("can't create track list"); + parse_tracks_final(&list); + return (rc); + } + + TAILQ_FOREACH(tp, &list, list) { + rc = rip_tracks_loop(tp, n, next_track); + if (rc < 0) + break; + } + + parse_tracks_final(&list); + return (0); +} + +int +rip(char *arg) +{ + + return rip_tracks(arg, rip_next_track, 1); +} + +int +play(char *arg) +{ + + return rip_tracks(arg, play_next_track, 0); +} |