Index: dvd_reader.c =================================================================== --- dvd_reader.c (revision 1153) +++ dvd_reader.c (working copy) @@ -259,3 +269,44 @@ return dvd; } +/* + * Open a image inside a rar. Takes extra filepath argument to the rar archive + */ +dvd_reader_t *DVDOpenRARImageFile( const char *unrar_cmd, + const char *rarfile, + const char *location ) +{ + dvd_reader_t *dvd; + dvd_input_t dev; + + dvdinput_setupRAR(unrar_cmd, rarfile); + + dev = dvdinput_open( location ); + if( !dev ) { + fprintf( stderr, "libdvdread: Can't openRAR %s:%s for reading\n", + rarfile, location); + return NULL; + } + + dvd = (dvd_reader_t *) malloc( sizeof( dvd_reader_t ) ); + if( !dvd ) { + dvdinput_close(dev); + return NULL; + } + dvd->isImageFile = 1; + dvd->dev = dev; + dvd->path_root = NULL; + + dvd->udfcache_level = DEFAULT_UDF_CACHE_LEVEL; + dvd->udfcache = NULL; + + dvd->css_title = 0; + + return dvd; +} + + + + + + Index: dvd_input.h =================================================================== --- dvd_input.h (revision 1153) +++ dvd_input.h (working copy) @@ -46,5 +46,7 @@ * Setup function accessed by dvd_reader.c. Returns 1 if there is CSS support. */ int dvdinput_setup(void); +int dvdinput_setupRAR(const char *, const char *); + #endif /* LIBDVDREAD_DVD_INPUT_H */ Index: dvd_reader.h =================================================================== --- dvd_reader.h (revision 1153) +++ dvd_reader.h (working copy) @@ -104,3 +104,14 @@ */ void DVDClose( dvd_reader_t * ); +/* + * Open an ImageFile contained in a RAR archive volume. This API call should + * probably be cleaned up. + * @lundman: 20080817 + */ +dvd_reader_t *DVDOpenRARImageFile( const char *unrar_cmd, + const char *rarfile, + const char *location ); + + + Index: dvd_input.c =================================================================== --- dvd_input.c (revision 1153) +++ dvd_input.c (working copy) @@ -25,6 +25,7 @@ #include #include #include +#include #include "dvd_reader.h" #include "dvd_input.h" @@ -37,6 +38,8 @@ int (*dvdinput_title) (dvd_input_t, int); int (*dvdinput_read) (dvd_input_t, void *, int, int); char * (*dvdinput_error) (dvd_input_t); +char * dvdinput_unrar_cmd = NULL; +char * dvdinput_unrar_file = NULL; #ifdef HAVE_DVDCSS_DVDCSS_H /* linking to libdvdcss */ @@ -66,6 +69,10 @@ static char * (*DVDcss_error) (dvdcss_handle); #endif +/* When is it faster to respawn unrar, rather than seek by eating bytes? */ +#define MAXIMUM_SEEK_SIZE 1048576 + + /* The DVDinput handle, add stuff here for new input methods. */ struct dvd_input_s { /* libdvdcss handle */ @@ -73,6 +80,14 @@ /* dummy file input */ int fd; + + /* unrar support */ + char *unrar_archive; + char *unrar_file; + FILE *unrar_stream; + off_t seek_pos; + off_t current_pos; + }; @@ -362,3 +377,264 @@ return 0; } } + + +/** + * initialize and open a DVD device or file. + */ +static dvd_input_t rarfile_open(const char *target) +{ + dvd_input_t dev; + + /* Allocate the library structure */ + dev = (dvd_input_t) malloc(sizeof(*dev)); + if(dev == NULL) { + fprintf(stderr, "libdvdread: Could not allocate memory.\n"); + return NULL; + } + + dev->unrar_stream = NULL; + dev->unrar_file = NULL; + dev->seek_pos = 0; /* Assume start of file */ + dev->current_pos = 0; + + /* This is kind of sad, to use setupRAR to pass the rarfile name, then + * hope that dvdinput_open is called after. We could also do some sort + * of filename separator parsing perhaps. */ + + /* Allocated in setupRAR, move it over. */ + dev->unrar_archive = dvdinput_unrar_file; + dvdinput_unrar_file = NULL; + + /* The filename inside the RAR archive to operate on */ + dev->unrar_file = strdup(target); + + /* Lets call unrar, to list the release, as a way to quickly check that + * all the volumes are present, and the unrar_cmd works? */ + + return dev; +} + + +/** + * return the last error message + */ +static char *rarfile_error(dvd_input_t dev) +{ + /* use strerror(errno)? */ + return (char *)"unknown error"; +} + +/** + * seek into the device. + */ +static int rarfile_seek(dvd_input_t dev, int blocks) +{ + + dev->seek_pos = (off_t)blocks * (off_t)DVD_VIDEO_LB_LEN; + + /* assert pos % DVD_VIDEO_LB_LEN == 0 */ + return (int) (dev->seek_pos / DVD_VIDEO_LB_LEN); +} + +/** + * set the block for the begining of a new title (key). + */ +static int rarfile_title(dvd_input_t dev, int block) +{ + return -1; +} + +/** + * read data from the device. + */ +static int rarfile_read(dvd_input_t dev, void *buffer, int blocks, int flags) +{ + size_t len, read_size; + char ibuffer[DVD_VIDEO_LB_LEN]; + int ret; + + /* First, position ourselves where the API wants us. This may mean + * spawning new unrar, or possibly eating data until the correct + * position. */ + + /* We already have a stream, here, we can be exactly at the right place, + * or, need to eat data until the correct position, or + * if the seek is too far, close this unrar, and spawn a new. + */ + if (dev->unrar_stream) { + + /* eat data? */ + if (dev->seek_pos > dev->current_pos) { + + /* Seek too far? Better to spawn new unrar? */ + if ((dev->seek_pos - dev->current_pos) >= MAXIMUM_SEEK_SIZE) { +#ifdef DEBUG + fprintf(stderr, "libdvdread: seek too large, re-spawning unrar\r\n"); +#endif + pclose(dev->unrar_stream); + dev->unrar_stream = NULL; + + } else { + /* Not too far, read and eat bytes. */ + while (dev->seek_pos > dev->current_pos) { + + /* Work out how much we need to read, but no larger than + * the size of our buffer.*/ + + read_size = dev->seek_pos - dev->current_pos; + if (read_size > sizeof(ibuffer)) + read_size = sizeof(ibuffer); + + if ((fread(ibuffer, read_size, 1, dev->unrar_stream)) != 1) { + /* Something failed, lets close, and let's try respawn */ + pclose(dev->unrar_stream); + dev->unrar_stream = NULL; + break; + } + dev->current_pos += read_size; + } /* while seek > current */ + } /* NOT >= max seek */ + } /* NOT seek > current */ + + /* Also check if seek < current, then we must restart unrar */ + if (dev->seek_pos < dev->current_pos) { + pclose(dev->unrar_stream); + dev->unrar_stream = NULL; + } + + } /* we have active stream */ + + /* Spawn new unrar? */ + if (!dev->unrar_stream) { + snprintf(ibuffer, sizeof(ibuffer), + "%s p -inul -c- -p- -y -cfg- -sk%"PRIu64" -- \"%s\" \"%s\"", + dvdinput_unrar_cmd, + dev->seek_pos, + dev->unrar_archive, + dev->unrar_file ); + +#ifdef DEBUG + fprintf(stderr, "libdvdvread: spawning '%s'\r\n", ibuffer); +#endif + + dev->unrar_stream = popen(ibuffer, +#ifdef WIN32 + "rb" +#else + "r" /* It is an error to send "b" in Unix :( */ +#endif + ); + + if (!dev->unrar_stream) { + return -1; + } + + /* Update ptr */ + dev->current_pos = dev->seek_pos; + + } + + /* Assert current == seek ? */ +#ifdef DEBUG + if (dev->current_pos != dev->seek_pos) + fprintf(stderr, "libdvdread: somehow, current_pos != seek_pos!?\r\n"); +#endif + + len = (size_t)blocks * DVD_VIDEO_LB_LEN; + + while(len > 0) { + + /* ret = read(dev->fd, buffer, len); */ + ret = fread(buffer, 1, len, dev->unrar_stream); + + if((ret != len) && ferror(dev->unrar_stream)) { + /* One of the reads failed, too bad. We won't even bother + * returning the reads that went ok, and as in the posix spec + * the file postition is left unspecified after a failure. */ + return -1; + } + + if (ret < 0) + return -1; + + if ((ret != len) || feof(dev->unrar_stream)) { + /* Nothing more to read. Return the whole blocks, if any, + * that we got. and adjust the file position back to the + * previous block boundary. */ + size_t bytes = (size_t)blocks * DVD_VIDEO_LB_LEN - len; /* 'ret'? */ + off_t over_read = -(bytes % DVD_VIDEO_LB_LEN); + /*off_t pos =*/ /*lseek(dev->fd, over_read, SEEK_CUR);*/ + dev->current_pos += (off_t)len; + dev->seek_pos = dev->current_pos + (off_t)len + over_read; + /* minus over_read, I did not touch the code above, but I wonder + * if it is correct. It does not even use "ret" in the math. */ + + /* should have pos % 2048 == 0 */ + return (int) (bytes / DVD_VIDEO_LB_LEN); + } + + dev->current_pos += (off_t) ret; + dev->seek_pos += (off_t) ret; + len -= ret; + } + + return blocks; +} + + +/** + * close the DVD device and clean up. + */ +static int rarfile_close(dvd_input_t dev) +{ + + if (dev->unrar_stream) + pclose(dev->unrar_stream); + dev->unrar_stream = NULL; + + if (dev->unrar_archive) + free(dev->unrar_archive); + dev->unrar_archive = NULL; + + if (dev->unrar_file) + free(dev->unrar_file); + dev->unrar_file = NULL; + + free(dev); + + return 0; +} + + + +int dvdinput_setupRAR(const char *unrar_path, const char *rarfile) +{ + +#ifdef DEBUG + fprintf(stderr, "libdvdread: Configuring for unrar.\n"); +#endif + /* Search for unrar in $PATH, common places etc, as well as "unrar_path" + * + * But we really need unrar with special SEEK -sk option for this to work + * + * If you want this to work on Windows, you have to use internal popen() + * functions, as Windows popen() does not work correctly. + * Please see: http://www.lundman.net/win32_popen.c + */ + + dvdinput_unrar_cmd = strdup(unrar_path); + dvdinput_unrar_file = strdup(rarfile); + + /* Check it is present, and executable? */ + + /* libdvdcss replacement functions */ + dvdinput_open = rarfile_open; + dvdinput_close = rarfile_close; + dvdinput_seek = rarfile_seek; + dvdinput_title = rarfile_title; + dvdinput_read = rarfile_read; + dvdinput_error = rarfile_error; + + return 0; +}