/* 2004.02.01 first released source code for IOMP */ /* * Copyright (C) 2000-2003 the xine project * * This file is part of xine, a free video player. * * xine 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 of the License, or * (at your option) any later version. * * xine 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA * * Rip Input Plugin for catching streams * * It saves raw data into file as go from input plugins. * * Usage: * * - activation: * xine stream_mrl#save:file.raw * * - it's possible speeder saving streams in the xine without playing: * xine stream_mrl#save:file.raw\;noaudio\;novideo * * $Id: input_rip.c,v 1.2 2003/11/25 04:21:48 georgedon Exp $ */ extern char *XINE_SEG_ERROR_FILE; //#define LOG_SEG_ERROR sprintf(XINE_SEG_ERROR_FILE,"%s (%d)",__FILE__,__LINE__); #define LOG_SEG_ERROR #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include /* logging */ /* #define LOG 1 */ #define LOG_MODULE "input_rip" #define CLR_FAIL "\e[1;31m" #define CLR_RST "\e[0;39m" #include "xine_internal.h" #define SCRATCH_SIZE 1024 typedef struct { input_plugin_t input_plugin; /* inherited structure */ input_plugin_t *main_input_plugin; /* original input plugin */ xine_stream_t *stream; FILE *file; /* destination file */ int regular; /* permit reading from the file */ char *preview; /* preview data */ off_t preview_size; /* size of read preview data */ off_t curpos; /* current position */ off_t savepos; /* amount of already saved data */ } rip_input_plugin_t; static off_t min_off(off_t a, off_t b) { return a <= b ? a : b; } /* * read data from input plugin and write it into file */ static off_t rip_plugin_read(input_plugin_t *this_gen, char *buf, off_t len) { rip_input_plugin_t *this = (rip_input_plugin_t *)this_gen; off_t retlen, npreview, nread, nwrite, nread_orig, nread_file; lprintf("reading %lld bytes (curpos = %lld, savepos = %lld)\n", len, this->curpos, this->savepos); /* compute sizes and copy data from preview */ if (this->curpos < this->preview_size && this->preview) { npreview = this->preview_size - this->curpos; if (npreview > len) { npreview = len; nread = 0; } else { nread = min_off(this->savepos - this->preview_size, len - npreview); } lprintf(" => get %lld bytes from preview (%lld bytes)\n", npreview, this->preview_size); memcpy(buf, &this->preview[this->curpos], npreview); } else { npreview = 0; nread = min_off(this->savepos - this->curpos, len); } /* size to write into file */ nwrite = len - npreview - nread; /* size to read from file */ nread_file = this->regular ? nread : 0; /* size to read from original input plugin */ nread_orig = this->regular ? 0 : nread; /* re-reading from file */ if (nread_file) { lprintf(" => read %lld bytes from file\n", nread_file); if (fread(&buf[npreview], nread_file, 1, this->file) != 1) { xine_log(this->stream->xine, XINE_LOG_MSG, _("input_rip: reading of saved data failed: %s\n"), strerror(errno)); return -1; } } /* really to read/catch */ if (nread_orig + nwrite) { lprintf(" => read %lld bytes from input plugin\n", nread_orig + nwrite); /* read from main input plugin */ retlen = this->main_input_plugin->read(this->main_input_plugin, &buf[npreview + nread_file], nread_orig + nwrite); lprintf("%s => returned %lld" CLR_RST "\n", retlen == nread_orig + nwrite ? "" : CLR_FAIL, retlen); if (retlen < 0) { xine_log(this->stream->xine, XINE_LOG_MSG, _("input_rip: reading by input plugin failed\n")); return -1; } /* write to file (only successfully read data) */ if (retlen > nread_orig) { nwrite = retlen - nread_orig; if (fwrite(buf + this->savepos - this->curpos, nwrite, 1, this->file) != 1) { xine_log(this->stream->xine, XINE_LOG_MSG, _("input_rip: error writing to file %lld bytes: %s\n"), retlen - nread_orig, strerror(errno)); return -1; } this->savepos += nwrite; lprintf(" => saved %lld bytes\n", nwrite); } else nwrite = 0; } this->curpos += (npreview + nread + nwrite); return npreview + nread + nwrite; } /* * open should never be called */ static int rip_plugin_open(input_plugin_t *this_gen) { rip_input_plugin_t *this = (rip_input_plugin_t *)this_gen; xine_log(this->stream->xine, XINE_LOG_MSG, _("input_rip: open() function should never be called\n")); return 0; } /* * set preview and/or seek capability when it's implemented by RIP */ static uint32_t rip_plugin_get_capabilities(input_plugin_t *this_gen) { rip_input_plugin_t *this = (rip_input_plugin_t *)this_gen; uint32_t caps; caps = this->main_input_plugin->get_capabilities(this->main_input_plugin); if (this->regular) caps |= INPUT_CAP_SEEKABLE; if (this->preview) caps |= INPUT_CAP_PREVIEW; return caps; } /* * read a block of data from input plugin and write it into file * * This rip plugin returns block unchanged from main input plugin. But special * cases are reading over preview or reading already saved data - it returns * own allocated block. */ static buf_element_t *rip_plugin_read_block(input_plugin_t *this_gen, fifo_buffer_t *fifo, off_t todo) { rip_input_plugin_t *this = (rip_input_plugin_t *)this_gen; buf_element_t *buf = NULL; off_t retlen, npreview, nread, nwrite, nread_orig, nread_file; LOG_SEG_ERROR lprintf("reading %lld bytes (curpos = %lld, savepos = %lld) (block)\n", todo, this->curpos, this->savepos); if (!todo) return NULL; LOG_SEG_ERROR /* compute sizes and copy data from preview */ if (this->curpos < this->preview_size && this->preview) { npreview = this->preview_size - this->curpos; if (npreview > todo) { npreview = todo; nread = 0; } else { nread = min_off(this->savepos - this->preview_size, todo - npreview); } LOG_SEG_ERROR lprintf(" => get %lld bytes from preview (%lld bytes) (block)\n", npreview, this->preview_size); } else { LOG_SEG_ERROR npreview = 0; nread = min_off(this->savepos - this->curpos, todo); } /* size to write into file */ nwrite = todo - npreview - nread; /* size to read from file */ nread_file = this->regular ? nread : 0; /* size to read from original input plugin */ nread_orig = this->regular ? 0 : nread; LOG_SEG_ERROR /* create own block by RIP if needed */ if (npreview + nread_file) { LOG_SEG_ERROR buf = fifo->buffer_pool_alloc(fifo); buf->content = buf->mem; buf->type = BUF_DEMUX_BLOCK; /* get data from preview */ if (npreview) { lprintf(" => get %lld bytes from the preview (block)\n", npreview); memcpy(buf->content, &this->preview[this->curpos], npreview); } LOG_SEG_ERROR /* re-reading from the file */ if (nread_file) { lprintf(" => read %lld bytes from the file (block)\n", nread_file); if (fread(&buf->content[npreview], nread_file, 1, this->file) != 1) { LOG_SEG_ERROR xine_log(this->stream->xine, XINE_LOG_MSG, _("input_rip: reading of saved data failed: %s\n"), strerror(errno)); return NULL; } } } /* really to read/catch */ if (nread_orig + nwrite) { LOG_SEG_ERROR /* read from main input plugin */ if (buf) { LOG_SEG_ERROR lprintf(" => read %lld bytes from input plugin (block)\n", nread_orig + nwrite); retlen = this->main_input_plugin->read(this->main_input_plugin, &buf->content[npreview + nread_file], nread_orig + nwrite); } else { LOG_SEG_ERROR lprintf(" => read block of %lld bytes from input plugin (block)\n", nread_orig + nwrite); buf = this->main_input_plugin->read_block(this->main_input_plugin, fifo, nread_orig + nwrite); if (buf) retlen = buf->size; else { LOG_SEG_ERROR lprintf(CLR_FAIL " => returned NULL" CLR_RST "\n"); return NULL; } } if (retlen != nread_orig + nwrite) { LOG_SEG_ERROR lprintf(CLR_FAIL " => returned %lld" CLR_RST "\n", retlen); return NULL; } /* write to file (only successfully read data) */ if (retlen > nread_orig) { LOG_SEG_ERROR nwrite = retlen - nread_orig; if (fwrite(buf->content + this->savepos - this->curpos, nwrite, 1, this->file) != 1) { LOG_SEG_ERROR xine_log(this->stream->xine, XINE_LOG_MSG, _("input_rip: error writing to file %lld bytes: %s\n"), retlen - nread_orig, strerror(errno)); return NULL; } this->savepos += nwrite; lprintf(" => saved %lld bytes\n", nwrite); } else LOG_SEG_ERROR nwrite = 0; } LOG_SEG_ERROR this->curpos += (npreview + nread + nwrite); buf->size = npreview + nread + nwrite; LOG_SEG_ERROR return buf; } static off_t rip_seek_original(rip_input_plugin_t *this, off_t reqpos) { off_t pos; lprintf(" => seeking original input plugin to %lld\n", reqpos); pos = this->main_input_plugin->seek(this->main_input_plugin, reqpos, SEEK_SET); if (pos == -1) { xine_log(this->stream->xine, XINE_LOG_MSG, _("input_rip: seeking failed\n")); return -1; } #ifdef LOG if (pos != reqpos) { lprintf(CLR_FAIL " => reqested position %lld differs from result position %lld" CLR_RST "\n", reqpos, pos); } #endif this->curpos = pos; return pos; } /* * seek in RIP * * If we are seeking back and we can read from saved file, * position of original input plugin isn't changed. */ static off_t rip_plugin_seek(input_plugin_t *this_gen, off_t offset, int origin) { char buffer[SCRATCH_SIZE]; rip_input_plugin_t *this = (rip_input_plugin_t *)this_gen; uint32_t blocksize; off_t newpos, toread, reqpos, pos; lprintf("seek, offset %lld, origin %d (curpos %lld, savepos %lld)\n", offset, origin, this->curpos, this->savepos); switch (origin) { case SEEK_SET: newpos = offset; break; case SEEK_CUR: newpos = this->curpos + offset; break; default: newpos = this->curpos; } /* align the new position down to block sizes */ if( this_gen->get_capabilities(this_gen) & INPUT_CAP_BLOCK ) { blocksize = this_gen->get_blocksize(this_gen); newpos = (newpos / blocksize) * blocksize; } else blocksize = 0; if (newpos < this->savepos) { lprintf(" => virtual seeking from %lld to %lld\n", this->curpos, newpos); /* don't seek into preview area */ if (this->preview && newpos < this->preview_size) { reqpos = this->preview_size; } else { reqpos = newpos; } if (this->regular) { if (reqpos != this->savepos) { lprintf(" => seeking file to %lld\n", reqpos); if (fseek(this->file, reqpos, SEEK_SET) != 0) { xine_log(this->stream->xine, XINE_LOG_MSG, _("input_rip: seeking failed: %s\n"), strerror(errno)); return -1; } } this->curpos = newpos; } else { if ((pos = rip_seek_original(this, reqpos)) == -1) return -1; if (pos == reqpos) this->curpos = newpos; } return this->curpos; } if (this->curpos < this->savepos) { lprintf(" => seeking to end: %lld\n", this->savepos); if (this->regular) { lprintf(" => seeking file to end: %lld\n", this->savepos); if (fseek(this->file, this->savepos, SEEK_SET) != 0) { xine_log(this->stream->xine, XINE_LOG_MSG, _("input_rip: seeking failed: %s\n"), strerror(errno)); return -1; } this->curpos = this->savepos; } else { if ((pos = rip_seek_original(this, this->savepos)) == -1) return -1; if (pos > this->savepos) xine_log(this->stream->xine, XINE_LOG_MSG, _("input_rip: %lld bytes dropped\n"), pos - this->savepos); } } /* read and catch remaining data after this->savepos */ while (this->curpos < newpos) { if( blocksize ) { buf_element_t *buf; buf = rip_plugin_read_block(this_gen, this->stream->video_fifo, blocksize); if (buf) buf->free_buffer(buf); else break; } else { toread = newpos - this->curpos; if( toread > sizeof(buffer) ) toread = sizeof(buffer); if( rip_plugin_read(this_gen, buffer, toread) <= 0 ) { xine_log(this->stream->xine, XINE_LOG_MSG, _("input_rip: seeking failed\n")); break; } } } lprintf(" => new position %lld\n", this->curpos); return this->curpos; } /* * return current position, * check values for debug build */ static off_t rip_plugin_get_current_pos(input_plugin_t *this_gen) { rip_input_plugin_t *this = (rip_input_plugin_t *)this_gen; #ifdef DEBUG off_t pos; pos = this->main_input_plugin->get_current_pos(this->main_input_plugin); if (pos != this->curpos) { lprintf(CLR_FAIL "position: computed = %lld, input plugin = %lld" CLR_RST "\n", this->curpos, pos); } #endif return this->curpos; } static off_t rip_plugin_get_length (input_plugin_t *this_gen) { rip_input_plugin_t *this = (rip_input_plugin_t *)this_gen; off_t length; length = this->main_input_plugin->get_length(this->main_input_plugin); if(length <= 0) length = this->savepos; return length; } static uint32_t rip_plugin_get_blocksize(input_plugin_t *this_gen) { rip_input_plugin_t *this = (rip_input_plugin_t *)this_gen; return this->main_input_plugin->get_blocksize(this->main_input_plugin); } static char* rip_plugin_get_mrl (input_plugin_t *this_gen) { rip_input_plugin_t *this = (rip_input_plugin_t *)this_gen; return this->main_input_plugin->get_mrl(this->main_input_plugin); } static int rip_plugin_get_optional_data (input_plugin_t *this_gen, void *data, int data_type) { rip_input_plugin_t *this = (rip_input_plugin_t *)this_gen; lprintf("get optional data\n"); if (this->preview && data_type == INPUT_OPTIONAL_DATA_PREVIEW) { memcpy(data, this->preview, this->preview_size); return this->preview_size; } else return this->main_input_plugin->get_optional_data( this->main_input_plugin, data, data_type); } /* * dispose main input plugin and self */ static void rip_plugin_dispose(input_plugin_t *this_gen) { rip_input_plugin_t *this = (rip_input_plugin_t *)this_gen; lprintf("rip_plugin_dispose\n"); this->main_input_plugin->dispose(this->main_input_plugin); fclose(this->file); if (this->preview) free(this->preview); free(this); } /* * create self instance, * target file for writing stream is specified in 'data' */ input_plugin_t *rip_plugin_get_instance (xine_stream_t *stream, const char *filename) { rip_input_plugin_t *this; input_plugin_t *main_plugin = stream->input_plugin; struct stat pstat; const char *mode; lprintf("rip_plugin_get_instance(catch file = %s)\n", filename ? filename : "(null)"); /* check given input plugin */ if (!stream->input_plugin) { xine_log(stream->xine, XINE_LOG_MSG, _("input_rip: input plugin not defined!\n")); return NULL; } if ( main_plugin->get_capabilities(main_plugin) & INPUT_CAP_RIP_FORBIDDEN ) { xine_log(stream->xine, XINE_LOG_MSG, _("input_rip: ripping/caching is not permitted!\n")); return NULL; } if (!filename || !filename[0]) { xine_log(stream->xine, XINE_LOG_MSG, _("input_rip: file name not given!\n")); return NULL; } this = (rip_input_plugin_t *)xine_xmalloc(sizeof(rip_input_plugin_t)); this->main_input_plugin = main_plugin; this->stream = stream; this->curpos = 0; this->savepos = 0; /* find out type of file */ if (stat(filename, &pstat) < 0 && errno != ENOENT) { xine_log(this->stream->xine, XINE_LOG_MSG, _("input_rip: stat on the file %s failed: %s\n"), filename, strerror(errno)); } if (errno != ENOENT && S_ISFIFO(pstat.st_mode)) { this->regular = 0; mode = "wb"; } else { this->regular = 1; mode = "wb+"; } if ((this->file = fopen(filename, mode)) == NULL) { xine_log(this->stream->xine, XINE_LOG_MSG, _("input_rip: error opening file %s: %s\n"), filename, strerror(errno)); free(this); return NULL; } /* fill preview memory */ if ( (main_plugin->get_capabilities(main_plugin) & INPUT_CAP_SEEKABLE) == 0) { if ( main_plugin->get_capabilities(main_plugin) & INPUT_CAP_BLOCK ) { buf_element_t *buf; uint32_t blocksize; blocksize = main_plugin->get_blocksize(main_plugin); buf = main_plugin->read_block(main_plugin, stream->video_fifo, blocksize); this->preview_size = buf->size; this->preview = malloc(this->preview_size); memcpy(this->preview, buf->content, this->preview_size); buf->free_buffer(buf); } else { this->preview = malloc(MAX_PREVIEW_SIZE); this->preview_size = main_plugin->read(main_plugin, this->preview, MAX_PREVIEW_SIZE); } } else { this->preview = NULL; } if (this->preview && this->preview_size) { if (fwrite(this->preview, this->preview_size, 1, this->file) != 1) { xine_log(this->stream->xine, XINE_LOG_MSG, _("input_rip: error writing to file %lld bytes: %s\n"), this->preview_size, strerror(errno)); fclose(this->file); free(this); return NULL; } lprintf(" => saved %lld bytes (preview)\n", this->preview_size); this->savepos = this->preview_size; } this->input_plugin.open = rip_plugin_open; this->input_plugin.get_capabilities = rip_plugin_get_capabilities; this->input_plugin.read = rip_plugin_read; this->input_plugin.read_block = rip_plugin_read_block; this->input_plugin.seek = rip_plugin_seek; this->input_plugin.get_current_pos = rip_plugin_get_current_pos; this->input_plugin.get_length = rip_plugin_get_length; this->input_plugin.get_blocksize = rip_plugin_get_blocksize; this->input_plugin.get_mrl = rip_plugin_get_mrl; this->input_plugin.get_optional_data = rip_plugin_get_optional_data; this->input_plugin.dispose = rip_plugin_dispose; this->input_plugin.input_class = main_plugin->input_class; return &this->input_plugin; }