subtitleripper-0.3.4/0040750000175000017500000000000007765042043014775 5ustar marillatmarillatsubtitleripper-0.3.4/Makefile0100640000175000017500000000506707765042042016442 0ustar marillatmarillat# # Makefile for subtitle2pgm + srttool # # use always: DEFINES := LIBS := -lm INCLUDES := ### enable ppm support ### DEFINES += -D_HAVE_LIB_PPM_ LIBS += -lppm ### enable PNG support ### DEFINES += -D_HAVE_PNG_ LIBS += -lpng ### enable zlib support ### DEFINES += -D_HAVE_ZLIB_ LIBS += -lz CC = gcc WARN = -Wall -Wstrict-prototypes COPT = -g -O2 CFLAGS = $(COPT) $(WARN) $(DEFINES) $(INCLUDES) # list of all files that are part of the package PACKAGE_FILES = Makefile \ subtitle2pgm.c subtitle2pgm.h spudec.c spudec.h \ srttool.c \ vobsub.c vobsub.h subtitle2vobsub.c \ pgm2txt \ gocrfilter_en.sed gocrfilter_fr.sed gocrfilter_none.sed \ gocrfilter_nl.sed \ README README.gocr README.srttool \ README.subtitle2pgm README.vobsub \ ChangeLog\ subtitleripper.spec\ vobsub2pgm.c # Name of the package PACKAGE = subtitleripper # version number #VERSION = `date +%Y%m%d` VERSION=0.3 RELEASE=4 # Main targets TARGETS = subtitle2pgm srttool subtitle2vobsub vobsub2pgm all: $(TARGETS) # Generic Rules %.o:%.c @echo Compiling $< @$(CC) -c $(CFLAGS) $< # Dependencies subtitle2pgm.o: subtitle2pgm.c spudec.h subtitle2pgm.h spudec.o: spudec.c spudec.h subtitle2vobsub.o: subtitle2vobsub.c vobsub.c vobsub.h vobsub2pgm.o: vobsub2pgm.c vobsub.h spudec.h # Target subtitle2pgm: subtitle2pgm.o spudec.o @echo "Linking $@" @$(CC) $(LIBS) $^ -o $@ subtitle2vobsub: subtitle2vobsub.o vobsub.o @echo "Linking $@" @$(CC) $(LIBS) $^ -o $@ srttool: srttool.o @echo "Linking $@" @$(CC) $(LIBS) -g $^ -o $@ vobsub2pgm: vobsub2pgm.o vobsub.o spudec.o @echo "Linking $@" @$(CC) $(LIBS) -g $^ -o $@ .PHONY: clean dist rpm clean: rm -f $(TARGETS) subtitle2pgm.o spudec.o srttool.o vobsub.o *~ # Build a tgz package used for upload to sourceforge dist: @mkdir $(PACKAGE) @cp $(PACKAGE_FILES) $(PACKAGE) @tar cvzf $(PACKAGE)-$(VERSION)-$(RELEASE).tgz $(PACKAGE) @rm -rf $(PACKAGE) @mv $(PACKAGE)-$(VERSION)-$(RELEASE).tgz $(HOME)/ # Build a rpm package (tested for SuSE 7.3) rpm: @mkdir $(PACKAGE) @cp $(PACKAGE_FILES) $(PACKAGE) @tar cvzf $(PACKAGE)-$(VERSION)-$(RELEASE).tgz $(PACKAGE) @chmod a+rw $(PACKAGE)-$(VERSION)-$(RELEASE).tgz @rm -rf $(PACKAGE) sudo cp $(PACKAGE)-$(VERSION)-$(RELEASE).tgz /usr/src/packages/SOURCES/ @rm $(PACKAGE)-$(VERSION)-$(RELEASE).tgz sudo cp subtitleripper.spec /usr/src/packages/SPECS/ sudo rpm -ba /usr/src/packages/SPECS/subtitleripper.spec subtitleripper-0.3.4/subtitle2pgm.c0100640000175000017500000003171007765042042017561 0ustar marillatmarillat/* Convert a subtitle stream to pgm/ppm images ( Meanwhile it also supports png ) Author: Arne Driescher Copyright: Most of the code is stolen from mplayer (file spudec.c) http://mplayer.dev.hu/homepage/news.html and transcode http://www.theorie.physik.uni-goettingen.de/~ostreich/transcode/ so that the Copyright of the respective owner should be applied. (That means GPL.) Version: 0.02 */ #include "spudec.h" #include #include #include #include #include #include #include "subtitle2pgm.h" #include #include #ifdef _HAVE_LIB_PPM_ #include #endif #define READ_BUF_SIZE (64*1024) // get the major version number from the version code unsigned int major_version(unsigned int version) { // bit 16-31 contain the major version number return version >> 16; } unsigned int minor_version(unsigned int version) { // bit 0-15 contain the minor version number return version & 0xffff; } void usage(void) { fprintf(stderr,"\n\t Convert a subtitle stream to pgm images \n\n"); fprintf(stderr,"\t subtitle2pgm [options]\n"); fprintf(stderr,"\t subtitle2pgm expects a subtitle stream as input.\n"); fprintf(stderr,"\t Use\n\t\t tcextract -x ps1 -t vob -a 0x20 -i file.vob\n"); fprintf(stderr,"\t to make a subtitle stream from a VOB and pipe its output\n"); fprintf(stderr,"\t directly into subtitle2pgm.\n"); fprintf(stderr,"\t Options:\n"); fprintf(stderr,"\t -i Use file_name for input instead of stdin.\n"); fprintf(stderr,"\t -o Use base_name for output files\n"); fprintf(stderr,"\t default: movie_subtitle\n"); fprintf(stderr,"\t -c Override the default grey levels.\n"); fprintf(stderr,"\t default: -c 255,255,0,255\n"); fprintf(stderr,"\t Valid values: 0<=c<=255\n"); fprintf(stderr,"\t -g Set output format to 0=PGM (default),\n"); fprintf(stderr,"\t 1=PPM, 2=PGM.GZ, 3=PNG_GRAY, 4=PNG_GRAY_ALPH, 5=PNG_RGBA \n"); fprintf(stderr,"\t -t Set tag file format 0=srtx (default) 1=dvdxml\n"); fprintf(stderr,"\t -l Add to PTS for every DVD-9 layer skip\n"); fprintf(stderr,"\t (default 0.0)\n"); fprintf(stderr,"\t -C Crop image but keep pixels if possible\n"); fprintf(stderr,"\t (default is < 0 = don't crop at all)\n"); fprintf(stderr,"\t -e extract only n subtitles starting from hh:mm:ss\n"); fprintf(stderr,"\t -v verbose output\n"); fprintf(stderr,"\t -P progress output\n"); fprintf(stderr,"\t Version 0.3 (alpha) for >transcode-0.6.0\n"); exit(0); } // magic string (definition must match the one in transcode/import/extract_ac3.c) static char *subtitle_header_str="SUBTITLE"; static unsigned int verbose=0; int main(int argc, char** argv) { void *spudec_handle; int len; char read_buf[READ_BUF_SIZE]; char output_base_name[FILENAME_MAX]; char input_file_name[FILENAME_MAX]; char filename_extension[FILENAME_MAX]; subtitle_header_v3_t subtitle_header; int ch,n; int colors[4]; output_formats image_format; tag_formats tag_format; int format_arg; int skip_len; double pts_seconds=0.0; unsigned int show_version_mismatch=~0; double layer_skip_adjust=0.0; double layer_skip_offset=0.0; unsigned int discont_ctr=0; int crop = -1; // default cropping = don't crop unsigned int extract_start_hour=0; unsigned int extract_start_min=0; unsigned int extract_start_sec=0; unsigned int extract_number=~0; unsigned int progress_output=0; unsigned int last_subtitle_number=0; #ifdef _HAVE_LIB_PPM_ // initialize libppm ppm_init(&argc, argv); #endif /* initialize default values here that can be overriden by commandline arguments */ // base filename used for output strcpy(output_base_name,"movie_subtitle"); // color table for subtitle colors 0-3 colors[0]=255; colors[1]=255; colors[2]=0; colors[3]=255; // default output format image_format=PGM; // default tag format tag_format=SRTX; // default filename extension strcpy(filename_extension,"pgm"); /* scan command line arguments */ opterr=0; while ((ch = getopt(argc, argv, "e:i:g:t:c:C:o:l:hvP")) != -1) { switch (ch) { // color palett case 'c': n = sscanf(optarg,"%d,%d,%d,%d", &colors[0], &colors[1], &colors[2], &colors[3]); if(n<1 || n>4) { fprintf(stderr,"invalid argument to color\n"); exit(-1); } break; case 'C': n = sscanf(optarg,"%d", &crop); if(n != 1) { fprintf(stderr,"invalid argument to crop\n"); exit(-1); } break; case 'o': n = sscanf(optarg,"%s", output_base_name); if(n!=1) { fprintf(stderr,"no filename specified to option -o\n"); exit(1); } break; case 'i': n = sscanf(optarg,"%s", input_file_name); if(n!=1) { fprintf(stderr,"no filename specified to option -i\n"); exit(1); } // open the specified input file for reading if( !(freopen(input_file_name,"r",stdin)) ){ perror("stdin redirection"); fprintf(stderr,"Tried to open %s for input\n",input_file_name); exit(1); } break; case 'h': usage(); break; case 'v': verbose=~0; break; case 'P': progress_output=~0; break; case 'g': n = sscanf(optarg,"%d", &format_arg); if(n!=1) { fprintf(stderr,"image format omitted for -g option\n"); exit(1); } if( (format_arg<0) || (format_arg >=(int)LAST_FORMAT) ){ fprintf(stderr,"Unknown image format selected for -g.\n"); fprintf(stderr,"Valid are 0, %d\n", (int)LAST_FORMAT); exit(1); } image_format = (output_formats) format_arg; #ifndef _HAVE_ZLIB_ if(image_format == PGMGZ){ fprintf(stderr,"Cannot use compressed format. Hint: Recompile with -D_HAVE_ZLIB_\n"); exit(1); } #endif break; case 't': n = sscanf(optarg,"%d", &format_arg); if(n!=1) { fprintf(stderr,"tag format omitted for -t option\n"); exit(1); } if( (format_arg <0) || (format_arg >= (int) LAST_TAG_FORMAT) ){ fprintf(stderr,"Unknown tag format selected for -t.\n"); fprintf(stderr,"Valid are 0, %d\n", (int)LAST_TAG_FORMAT); exit(1); } tag_format = (tag_formats) format_arg; break; case 'l': n = sscanf(optarg,"%lf", &layer_skip_offset); if(n!=1) { fprintf(stderr,"Invalid time argument for -l option\n"); exit(1); } break; case 'e': n = sscanf(optarg,"%d:%d:%d,%d", &extract_start_hour, &extract_start_min, &extract_start_sec, &extract_number); if(n!=4) { fprintf(stderr,"Invalid number of parameters for option -e\n"); exit(1); } if(verbose){ fprintf(stderr,"Extracting %d subtitles starting from %02d:%02d:%02d\n", extract_number, extract_start_hour, extract_start_min, extract_start_sec); } break; default: fprintf(stderr,"Unknown option. Use -h for list of valid options.\n"); exit(1); } } switch(image_format){ case PGM: strcpy(filename_extension,"pgm"); break; case PPM: strcpy(filename_extension,"ppm"); break; case PGMGZ: strcpy(filename_extension,"pgm.gz"); break; case PNG_GRAY: case PNG_GRAY_ALPHA: case PNG_RGBA: strcpy(filename_extension,"png"); break; default: fprintf(stderr,"Unknown output file format\n"); exit(1); } // allocate the data struct used by the decoder spudec_handle = spudec_new(colors, output_base_name, output_base_name, image_format, tag_format, crop); assert(spudec_handle); // reset to defaults spudec_reset(spudec_handle); // process all packages in the stream // the stream is an "augmented" raw subtitle stream // where two additional headers are used. while(1){ // read the magic "SUBTITLE" identified len=fread(read_buf, strlen(subtitle_header_str),1, stdin); if(feof(stdin)){ break; } if(len != 1){ fprintf(stderr,"Could not read magic header %s\n", subtitle_header_str); perror("Magic header"); exit(1); } if(strncmp(read_buf,subtitle_header_str,strlen(subtitle_header_str))){ fprintf(stderr,"Header %s not found\n",subtitle_header_str); fprintf(stderr,"%s\n",read_buf); exit(1); } // read the real header len=fread(&subtitle_header, sizeof(subtitle_header_v3_t), 1, stdin); if(len != 1){ fprintf(stderr,"Could not read subtitle header\n"); perror("Subtitle header"); exit(1); } // check for version mismatch and warn the user if( (subtitle_header.header_version < MIN_VERSION_CODE) && show_version_mismatch){ fprintf(stderr,"Warning: subtitle2pgm was compiled for header version %u.%u" " and the stream was produced with version %u.%u.\n", major_version(MIN_VERSION_CODE), minor_version(MIN_VERSION_CODE), major_version(subtitle_header.header_version), minor_version(subtitle_header.header_version)); // don't show this message again show_version_mismatch=0; } // we only try to proceed if the major versions match if( major_version(subtitle_header.header_version) != major_version(MIN_VERSION_CODE) ){ fprintf(stderr,"Versions are not compatible. Please extract subtitle stream\n" " with a newer transcode version\n"); exit(1); } // calculate exessive header bytes skip_len = subtitle_header.header_length - sizeof(subtitle_header_v3_t); // handle versions mismatch if(skip_len){ // header size can only grow (unless something nasty happend) assert(skip_len > 0); // put the rest of the header into read buffer len = fread(read_buf, sizeof(char), skip_len, stdin); if(len != skip_len){ perror("Header skip:"); exit(1); } } /* depending on the minor version some additional information might be available. */ // since version 3.1 discont_ctr is available but works only sine 4-Mar-2002. Allow extra // adjustment if requested. if(minor_version(subtitle_header.header_version) > 0){ discont_ctr=*((unsigned int*) read_buf); layer_skip_adjust = discont_ctr*layer_skip_offset; } // debug output #ifdef DEBUG fprintf(stderr,"subtitle_header: length=%d version=%0x lpts=%u (%f), rpts=%f, payload=%d, discont=%d\n", subtitle_header.header_length, subtitle_header.header_version, subtitle_header.lpts, (double)(subtitle_header.lpts/300)/90000.0, subtitle_header.rpts, subtitle_header.payload_length, discont_ctr); #endif // read one byte subtitle stream id (should match the number given to tcextract -a) len=fread(read_buf, sizeof(char), 1, stdin); if(len != 1){ perror("stream id"); exit(1); } // debug output //fprintf(stderr,"stream id: %x \n",(int)*read_buf); // read numer of bytes given in header len = fread(read_buf, subtitle_header.payload_length-1, 1, stdin); if(len >0){ if(subtitle_header.rpts > 0){ // if rpts is something useful take it pts_seconds=subtitle_header.rpts; } else { // calculate the time from lpts if(verbose){ fprintf(stderr, "fallback to lpts!\n"); } pts_seconds = (double)(subtitle_header.lpts/300)/90000.0; } // add offset for layer skip pts_seconds += layer_skip_adjust; if(pts_seconds < extract_start_hour*3600+extract_start_min*60+extract_start_sec){ // ignore all subtitle till we reached the start time continue; } // output progress report if requested by -P option if(last_subtitle_number != spudec_get_title_num(spudec_handle) && progress_output){ last_subtitle_number = spudec_get_title_num(spudec_handle); fprintf(stderr,"Generating image: %s%04d.%s\n",output_base_name, last_subtitle_number, filename_extension); } // do the actual work of assembling and decoding the data package spudec_assemble(spudec_handle,read_buf, subtitle_header.payload_length-1, (int) (pts_seconds*100)); // check if the number of requested subtitles // is already reached. (The internal subtitle number // counter starts with 1.) if(spudec_get_title_num(spudec_handle) == (extract_number+1)){ // exit while loop break; } } else { perror("Input file processing finished"); exit(errno); } } if(verbose){ fprintf(stderr,"Wrote %d subtitles\n", spudec_get_title_num(spudec_handle)-1); fprintf(stderr,"Conversion finished\n"); } spudec_free((void *) spudec_handle); return 0; } subtitleripper-0.3.4/subtitle2pgm.h0100640000175000017500000000064707765042042017573 0ustar marillatmarillat#ifndef _SUBTITLE2PGM_H_ #define _SUBTITLE2PGM_H_ // minimum version code for subtitle stream that // can be handled by the program #define MIN_VERSION_CODE 0x00030000 // common part of the subtitle header struct for major version 3 typedef struct { unsigned int header_length; unsigned int header_version; unsigned int payload_length; unsigned int lpts; double rpts; } subtitle_header_v3_t; #endif subtitleripper-0.3.4/spudec.c0100640000175000017500000007325607765042042016436 0ustar marillatmarillat/* SPUdec.c Skeleton of function spudec_process_controll() is from xine sources. Further works: LGB,... (yeah, try to improve it and insert your name here! ;-) Kim Minh Kaplan implement fragments reassembly, RLE decoding. read brightness from the IFO. Arne Driescher driescher@mpi-magdeburg.mpg.de Strip down to ppm export. For information on SPU format see: Revision: 2002-10-25: Merging with CVS diff version 1.35 to 1.36 of supdec.c (mplayer CVS) because the lost subtitles problem should be fixed. SPUH = Sub-Picture Unit Header */ #include #include #include #include #include "spudec.h" #include #include #ifdef _HAVE_ZLIB_ #include #endif #ifdef _HAVE_LIB_PPM_ #include #endif #ifdef _HAVE_PNG_ #include "png.h" #endif static void spudec_handle_rest(spudec_handle_t *this); // read a big endian 16 bit value static inline unsigned int get_be16(const unsigned char *p) { return (p[0] << 8) + p[1]; } // read a big endian 24 bit value static inline unsigned int get_be24(const unsigned char *p) { return (get_be16(p) << 8) + p[2]; } static void next_line(spudec_handle_t *this) { if (this->current_nibble[this->deinterlace_oddness] % 2) this->current_nibble[this->deinterlace_oddness]++; this->deinterlace_oddness = (this->deinterlace_oddness + 1) % 2; } static inline unsigned char get_nibble(spudec_handle_t *this) { unsigned char nib; int *nibblep = this->current_nibble + this->deinterlace_oddness; if (*nibblep / 2 >= this->control_start) { fprintf(stderr,"SPUdec: ERROR: get_nibble past end of packet\n"); return 0; } nib = this->packet[*nibblep / 2]; if (*nibblep % 2) nib &= 0xf; else nib >>= 4; ++*nibblep; return nib; } static void spudec_process_data(spudec_handle_t *this) { int i, x, y; if (this->image_size < this->stride * this->height) { if (this->image != NULL) { free(this->image); this->image_size = 0; } // allocate memory for image and alpha channel this->image = malloc(2 * this->stride * this->height); if (this->image) { this->image_size = this->stride * this->height; this->aimage = this->image + this->image_size; } } if (this->image == NULL){ fprintf(stderr,"this->image == NULL\n"); return; } this->deinterlace_oddness = 0; i = this->current_nibble[1]; x = 0; y = 0; while (this->current_nibble[0] < i && this->current_nibble[1] / 2 < this->control_start && y < this->height) { int len, color; unsigned int rle = 0; rle = get_nibble(this); if (rle < 0x04) { rle = (rle << 4) | get_nibble(this); if (rle < 0x10) { rle = (rle << 4) | get_nibble(this); if (rle < 0x040) { rle = (rle << 4) | get_nibble(this); if (rle < 0x0004) rle |= ((this->width - x) << 2); } } } color = 3 - (rle & 0x3); len = rle >> 2; if (len > this->width - x || len == 0) len = this->width - x; assert( (color >=0) && (color <4)); memset(this->image + y * this->stride + x, this->cmap[color], len); // assign alpha values. The original pallet contains alpha values // in the range 0x0..0xf that are here transformed to 0x0..0xff. memset(this->aimage + y * this->stride + x, this->alpha[color] << 4, len); #ifdef DEBUG printf("setting color:%d (%d) for len:%d at (%d,%d)\n", color,this->palette[color],len,y,x); #endif x += len; if (x >= this->width) { next_line(this); x = 0; ++y; } } } static void spudec_process_control(spudec_handle_t *this, int pts100) { int a,b; /* Temporary vars */ int date=0, type; int off; int start_off = 0; int next_off; this->control_start = get_be16(this->packet + 2); next_off = this->control_start; while (start_off != next_off) { start_off = next_off; /* get delay to wait till the command should be executed. The units are 90KHz clock divided by 1024. */ date = get_be16(this->packet + start_off); next_off = get_be16(this->packet + start_off + 2); // fprintf(stderr,"date=%d\n", date); off = start_off + 4; for (type = this->packet[off++]; type != 0xff; type = this->packet[off++]) { //fprintf(stderr,"cmd=%d ",type); switch(type) { case 0x00: case 0x01: if( this->end_pts==-1 ) { this->end_pts = pts100 + date*(100*1024/90000.0); spudec_handle_rest(this); } /* Start display, no arguments */ //fprintf(stderr,"Start display:%d--%d!\n",pts100,date); this->is_forced = (type==0); this->start_pts = pts100 + date*(100*1024/90000.0); this->end_pts = -1; break; case 0x02: /* Stop display, no arguments */ //fprintf(stderr,"Stop display:%d--%d!\n",pts100,date); this->end_pts = pts100 + date*(100*1024/90000.0); spudec_handle_rest(this); break; case 0x03: /* Set Color. Provides four indices into the color lookup table. One nibble addresses one color so that 2 bytes set all 4 colors. Format in stream: emph2 emph1 pattern background */ this->palette[0] = this->packet[off] >> 4; // emph2 this->palette[1] = this->packet[off] & 0xf; // emph1 this->palette[2] = this->packet[off + 1] >> 4; // pattern this->palette[3] = this->packet[off + 1] & 0xf; // background /*fprintf(stderr,"Palette %d, %d, %d, %d\n", this->palette[0], this->palette[1], this->palette[2], this->palette[3]); */ off+=2; break; case 0x04: /* Alpha blendig: Directly provides the four contrast (alpha blend) values to associate with the four pixel values. One nibble per pixel value for a total of 2 bytes. 0x0 = transparent, 0xF = opaque. Format in stream : emph2 emph1 pattern background */ /* Alpha */ this->alpha[0] = this->packet[off] >> 4; // emph2 this->alpha[1] = this->packet[off] & 0xf; // emph1 this->alpha[2] = this->packet[off + 1] >> 4; // pattern this->alpha[3] = this->packet[off + 1] & 0xf; // background off+=2; break; case 0x05: /* DISPLAY AREA Defines the display area, each pair (X and Y) of values is 3 bytes wide, for a total of 6 bytes, and has the form sx sx sx ex ex ex sy sy sy ey ey ey sx = starting X coordinate ex = ending X coordinate sy = starting Y coordinate ey = ending Y coordinate */ a = get_be24(this->packet + off); b = get_be24(this->packet + off + 3); this->start_col = a >> 12; this->end_col = a & 0xfff; this->width = this->end_col - this->start_col + 1; this->stride = (this->width + 7) & ~7; /* Kludge: draw_alpha needs width multiple of 8 */ this->start_row = b >> 12; this->end_row = b & 0xfff; this->height = this->end_row - this->start_row /* + 1 */; /*fprintf(stderr,"Coords col: %d - %d row: %d - %d (%dx%d)\n", this->start_col, this->end_col, this->start_row, this->end_row, this->width, this->height); */ off+=6; break; case 0x06: /* Graphic lines. Two 16 bit values that indicate the first and second line of the subtitle image. (The subtitle image is interlaced so that.) Format in stream: line1 line2 */ this->current_nibble[0] = 2 * get_be16(this->packet + off); this->current_nibble[1] = 2 * get_be16(this->packet + off + 2); /*fprintf(stderr,"Graphic offset 1: %d offset 2: %d\n", this->current_nibble[0] / 2, this->current_nibble[1] / 2); */ off+=4; break; case 0xff: /* End command. */ //fprintf(stderr,"Done!\n"); return; break; default: fprintf(stderr,"spudec: Error determining control type 0x%02x. Skipping %d bytes.\n", type, next_off - off); goto next_control; } } next_control: ; } } // write image in png format static void spudec_writeout_png(spudec_handle_t *this) { #ifdef _HAVE_PNG_ char file_name[FILENAME_MAX]; unsigned int width = this->bb_end_col-this->bb_start_col+1; unsigned int height = this->bb_end_row-this->bb_start_row+1; unsigned int stride = this->stride; FILE *fp; png_structp png_ptr; png_infop info_ptr; png_bytep row_pointers[height]; unsigned int k,j; volatile png_bytep output_image=NULL; unsigned int channels = 1; //fprintf(stderr,"Image size %d x %d\n",this->stride, this->height); assert(this->image); assert(this->aimage); // construct the file name for png image sprintf(file_name,"%s%04d.png",this->ppm_base_name, this->title_num); // fprintf(stderr, "Writing to file %s\n",file_name); /* open the file */ fp = fopen(file_name, "wb"); if (fp == NULL){ fprintf(stderr,"Could not open file %s for writing PNG\n",file_name); return; } /* Create and initialize the png_struct with the desired error handler * functions. If you want to use the default stderr and longjump method, * you can supply NULL for the last three parameters. We also check that * the library version is compatible with the one used at compile time, * in case we are using dynamically linked libraries. REQUIRED. */ png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (png_ptr == NULL) { fclose(fp); fprintf(stderr,"png_create_write_struct() failed\n"); return; } /* Allocate/initialize the image information data. REQUIRED */ info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { fclose(fp); png_destroy_write_struct(&png_ptr, (png_infopp)NULL); fprintf(stderr,"png_create_info_struct failed\n"); return ; } /* Set error handling. REQUIRED if you aren't supplying your own * error handling functions in the png_create_write_struct() call. */ if (setjmp(png_jmpbuf(png_ptr))) { /* If we get here, we had a problem writing the file */ fclose(fp); png_destroy_write_struct(&png_ptr, &info_ptr); fprintf(stderr,"PNG error handler called\n"); if(output_image){ free(output_image); } return ; } /* set up the output control for using standard C streams */ png_init_io(png_ptr, fp); /* Set the image information here. Width and height are up to 2^31, * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY, * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB, * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED */ if(this->image_format == PNG_GRAY){ channels = 1; // only gray channel /* Write image in gray scale format without alpha channel */ png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); /* Construct an array with pointers to all rows of the image */ for(k=this->bb_start_row,j=0; k <= this->bb_end_row; ++k,++j){ row_pointers[j] = this->image + k*stride + this->bb_start_col; } } else if (this->image_format == PNG_GRAY_ALPHA) { channels = 2; // gray and alpha channel /* Write image in gray scale format with alpha channel */ png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_GRAY_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); /* Convert the original gray scale image to grayscale + alpha. */ output_image = malloc(width*height*channels*sizeof(png_byte)); /* Copy the gray and alpha values into the new image */ for(k = 0; k < height; ++k){ for(j = 0; j < width; ++j){ const png_byte pixel = this->image[(this->bb_start_row+k)*stride+j+this->bb_start_col]; const png_byte alpha = this->aimage[(this->bb_start_row+k)*stride+j+this->bb_start_col]; output_image[k*width*channels+channels*j] = pixel; output_image[k*width*channels+channels*j+1] = alpha; } } /* Construct an array with pointers to all rows of the image */ for (k = 0; k < height; k++){ row_pointers[k] = output_image + k*width*channels; } } else if (this->image_format == PNG_RGBA){ channels = 4; // RGB+A channels /* Write image in gray scale format with alpha channel */ png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); /* Convert the original gray scale image to RGB-A format. */ output_image = malloc(width*height*channels*sizeof(png_byte)); // 4 channels /* Copy the gray and alpha values into the new image */ for(k = 0; k < height; ++k){ for(j = 0; j < width; ++j){ const png_byte pixel = this->image[(this->bb_start_row+k)*stride+j+this->bb_start_col]; const png_byte alpha = this->aimage[(this->bb_start_row+k)*stride+j+this->bb_start_col]; /* TODO: color lookup table needed */ output_image[k*width*channels+channels*j] = pixel; // red output_image[k*width*channels+channels*j+1] = pixel; // green output_image[k*width*channels+channels*j+2] = pixel; // blue output_image[k*width*channels+channels*j+3] = alpha; } } /* Construct an array with pointers to all rows of the image */ for (k = 0; k < height; k++){ row_pointers[k] = output_image + k*width*channels; } } else { fprintf(stderr,"Unsupported PNG output format\n"); return ; } /* Write the file header information. */ png_write_info(png_ptr, info_ptr); /* Write the full image */ png_write_image(png_ptr, row_pointers); /* It is REQUIRED to call this to finish writing the rest of the file */ png_write_end(png_ptr, info_ptr); /* clean up after the write, and free any memory allocated */ png_destroy_write_struct(&png_ptr, &info_ptr); /* close the file */ fclose(fp); /* Free allocated image if used */ if(output_image){ free(output_image); } #else fprintf(stderr,"PNG not supported. Recompile with _HAVE_PNG_ to activate this feature\n"); #endif } // write image in PGM format (grayscale) static void spudec_writeout_pgm(spudec_handle_t *this) { int i,j; unsigned char c; char file_name[FILENAME_MAX]; FILE* out_file; unsigned int width; unsigned int height; // calculate width and hight of the image width = this->bb_end_col-this->bb_start_col+1, height= this->bb_end_row-this->bb_start_row+1; //fprintf(stderr,"Image size %d x %d\n",this->stride, this->height); assert(this->image); if(this->image_format == PGMGZ){ #ifdef _HAVE_ZLIB_ gzFile gz_out_file; // construct the file name for pgm image with gzip sprintf(file_name,"%s%04d.pgm.gz",this->ppm_base_name, this->title_num); gz_out_file = gzopen(file_name,"wb"); if(!gz_out_file){ perror("Open for compressed file"); exit(1); } gzprintf(gz_out_file, "P5\n# CREATORE: subtitle2pgm\n %d %d\n255\n", width, height); // copy the spu-image to pgm for(j=this->bb_start_row; j <= this->bb_end_row; j++){ for(i=this->bb_start_col ; i <= this->bb_end_col; i++){ c=*(this->image+j*(this->stride)+i); gzputc(gz_out_file,c); } } gzclose(gz_out_file); #endif } else { // construct the file name for pgm image without gzip sprintf(file_name,"%s%04d.pgm",this->ppm_base_name, this->title_num); // fprintf(stderr, "Writing to file %s\n",file_name); out_file=fopen(file_name,"w"); // write PGM header fprintf(out_file,"P5\n# CREATORE: subtitle2pgm\n %d %d\n255\n", width, height); // copy the spu-image to pgm for(j=this->bb_start_row; j <= this->bb_end_row; j++){ for(i=this->bb_start_col ; i <= this->bb_end_col; i++){ c=*(this->image+j*(this->stride)+i); fputc(c,out_file); } } fclose(out_file); } } // write image in PPM format static void spudec_writeout_ppm(spudec_handle_t* this) { #ifdef _HAVE_LIB_PPM_ int i,j,k,l; pixel** ppm_image; pixel p; unsigned char c; char file_name[FILENAME_MAX]; FILE* out_file; unsigned int width; unsigned int height; // calculate width and hight of the image width = this->bb_end_col-this->bb_start_col+1, height= this->bb_end_row-this->bb_start_row+1; /* fprintf(stderr,"### Image number %d ###\n",this->title_num); fprintf(stderr,"Image width=%d\n",this->width); fprintf(stderr,"Image height=%d\n",this->height); fprintf(stderr,"Image stride=%d\n",this->stride); fprintf(stderr,"start_pts=%d\n",this->start_pts); fprintf(stderr,"end_pts=%d\n",this->end_pts); fprintf(stderr,"start_col=%d\n",this->start_col); fprintf(stderr,"start_row=%d\n",this->start_row); fprintf(stderr,"end_col=%d\n",this->end_col); fprintf(stderr,"end_row=%d\n",this->end_row); */ // alloc an new ppm array ppm_image = ppm_allocarray(width, height); //fprintf(stderr,"Image size %d x %d\n",this->stride, this->height); assert(this->image); // copy the spu-image to ppm for(j=this->bb_start_row,k=0; j <= this->bb_end_row; j++,k++){ for(i=this->bb_start_col,l=0 ; i <= this->bb_end_col; i++,l++){ c=*(this->image+j*(this->stride)+i); PPM_ASSIGN(p,c,c,c); ppm_image[k][l]=p; } } // construct the file name for ppm image sprintf(file_name,"%s%04d.ppm",this->ppm_base_name, this->title_num); // fprintf(stderr, "Writing to file %s\n",file_name); out_file=fopen(file_name,"w"); assert(out_file); ppm_writeppm(out_file, ppm_image, width, height, 255, 0 /*forceplane*/); fclose(out_file); ppm_freearray(ppm_image, this->height); #else fprintf(stderr,"PPM not supported. Please recompile with -D_HAVE_LIB_PPM_\n"); exit(1); #endif } static void spudec_writeout_srtx_tag(spudec_handle_t *this) { int start_sec, end_sec; int start_min, end_min; int start_hour, end_hour; double start_pts=this->start_pts/100.0; double end_pts=this->end_pts/100.0; int start_subsec, end_subsec; // writeout current number fprintf(this->tag_file,"%d\n",this->title_num); // writeout start and end time of this title start_hour = start_pts/(3600); start_min = (start_pts-3600*start_hour)/60; start_sec = (start_pts-3600*start_hour-60*start_min); start_subsec = (start_pts-3600*start_hour-60*start_min-start_sec)*1000; end_hour = end_pts/(3600); end_min = (end_pts-3600*end_hour)/60; end_sec = (end_pts-3600*end_hour-60*end_min); end_subsec = (end_pts-3600*end_hour-60*end_min-end_sec)*1000; fprintf(this->tag_file,"%02d:%02d:%02d,%.3d --> %02d:%02d:%02d,%.3d\n", start_hour,start_min,start_sec,start_subsec, end_hour,end_min,end_sec,end_subsec); switch(this->image_format){ case PGM: fprintf(this->tag_file,"%s%04d.pgm.txt\n\n", this->ppm_base_name, this->title_num); break; case PGMGZ: fprintf(this->tag_file,"%s%04d.pgm.gz.txt\n\n", this->ppm_base_name, this->title_num); break; case PPM: fprintf(this->tag_file,"%s%04d.ppm.txt\n\n", this->ppm_base_name, this->title_num); break; case PNG_GRAY: case PNG_GRAY_ALPHA: case PNG_RGBA: fprintf(this->tag_file,"%s%04d.png.txt\n\n", this->ppm_base_name, this->title_num); break; default: fprintf(stderr,"Invalid output format\n"); exit(1); } } static void spudec_writeout_dvdxml_tag(spudec_handle_t *this) { int start_sec, end_sec; int start_min, end_min; int start_hour, end_hour; double start_pts=this->start_pts/100.0; double end_pts=this->end_pts/100.0; int start_subsec, end_subsec; // writeout start and end time of this title start_hour = start_pts/(3600); start_min = (start_pts-3600*start_hour)/60; start_sec = (start_pts-3600*start_hour-60*start_min); start_subsec = (start_pts-3600*start_hour-60*start_min-start_sec)*1000; end_hour = end_pts/(3600); end_min = (end_pts-3600*end_hour)/60; end_sec = (end_pts-3600*end_hour-60*end_min); end_subsec = (end_pts-3600*end_hour-60*end_min-end_sec)*1000; if (end_pts <= start_pts){ fprintf(stderr,"end_pts <= start_pts! (You might have a problem with this file)\n"); fprintf(stderr,"Line:"); fprintf(stderr," start=\"%02d:%02d:%02d.%.3d\" end=\"%02d:%02d:%02d.%.3d\" \n", start_hour,start_min,start_sec,start_subsec, end_hour,end_min,end_sec,end_subsec); } fprintf(this->tag_file," is_forced ) fprintf(this->tag_file,"force=\"1\" "); switch(this->image_format){ case PGM: fprintf(this->tag_file,"image=\"%s%04d.pgm\"", this->ppm_base_name, this->title_num); break; case PGMGZ: fprintf(this->tag_file,"image=\"%s%04d.pgm.gz\"", this->ppm_base_name, this->title_num); break; case PPM: fprintf(this->tag_file,"image=\"%s%04d.ppm\"", this->ppm_base_name, this->title_num); break; case PNG_GRAY: case PNG_GRAY_ALPHA: case PNG_RGBA: fprintf(this->tag_file,"image=\"%s%04d.png\"", this->ppm_base_name, this->title_num); break; default: fprintf(stderr,"Invalid output format\n"); exit(1); } // close the current tag fprintf(this->tag_file,"> \n"); } // find a bounding box for the text and // add an border of "crop_border_size" pixels to the box static void spudec_crop_image(spudec_handle_t * this) { unsigned int i,j; unsigned char pixel,alpha; unsigned int start_row; unsigned int end_row; unsigned int start_col; unsigned int end_col; unsigned char background_color; unsigned char background_alpha; int border = this->crop_border_size; // only crop if border is >= 0 (see -C flag) if(border < 0 ){ // set begin and end to the full image size this->bb_start_row = 0; this->bb_end_row = this->height-1; this->bb_start_col = 0; this->bb_end_col = this->width-1; return; } // start values are at the opposite edge of the image start_row = this->height-1; end_row = 0; start_col = this->width-1; end_col = 0; // assume that the upper left pixel is always the background color background_color = this->image[0]; background_alpha = this->aimage[0]; // Loop over all pixel. Every time we find a // pixel not equal to the background color the bounding box // is enlarged (if necessary) to include this pixel too. for(j=0; j < this->height; j++){ for(i=0;iwidth;i++){ pixel=*(this->image+j*(this->stride)+i); alpha=*(this->aimage+j*(this->stride)+i); if(pixel != background_color || alpha != background_alpha){ // adjust the line start/end if necessary if(iend_col) end_col=i; // adjust the row start/end if necessary if(jend_row) end_row=j; } } } // sanity check for start and end (some subtitles are empty and crop will fail) if( (end_row >=start_row) && (end_col >= start_col) ){ // make sure the requested border around the text is kept // and assigne the appropriate values to the bounding box this->bb_start_row = start_row > border ? start_row - border : 0; this->bb_end_row = end_row + border < this->height ? end_row + border : this->height-1; this->bb_start_col = start_col > border ? start_col - border : 0; this->bb_end_col = end_col + border < this->width ? end_col + border : this->width-1; } else { // set begin and end to the full image size for empty images fprintf(stderr,"warning: Empty subtitle, cannot crop image\n"); this->bb_start_row = 0; this->bb_end_row = this->height-1; this->bb_start_col = 0; this->bb_end_col = this->width-1; } } // processes a complete packet static void spudec_decode(spudec_handle_t *this, int pts100) { spudec_process_control(this, pts100); } static void spudec_handle_rest(spudec_handle_t *this) { spudec_process_data(this); // grap only the text segment from the image spudec_crop_image(this); // write out the image file switch(this->image_format){ case PPM: spudec_writeout_ppm(this); break; case PGM: case PGMGZ: spudec_writeout_pgm(this); break; case PNG_GRAY: case PNG_GRAY_ALPHA: case PNG_RGBA: spudec_writeout_png(this); break; default: fprintf(stderr,"Unknown image format selected\n"); exit(1); } switch(this->tag_format){ case SRTX: spudec_writeout_srtx_tag(this); break; case DVDAUTHOR_XML: spudec_writeout_dvdxml_tag(this); break; default: fprintf(stderr,"Unknown tag file format\n"); } // next tile this->title_num++; } void spudec_assemble(spudec_handle_t *this, unsigned char *packet, int len, int pts100) { if (len < 2) { fprintf(stderr,"SPUasm: packet too short\n"); return; } if ((this->packet_pts + 10000) < pts100) { // [cb] too long since last fragment: force new packet this->packet_offset = 0; } this->packet_pts = pts100; // start of a new packet ? if (this->packet_offset == 0) { // read packet length unsigned int len2 = get_be16(packet); // make sure we have enought memory allocated for this packet if (this->packet_reserve < len2) { // free old allocated memory free(this->packet); // allocate new memory this->packet = malloc(len2); assert(this->packet); // debug code // save available memory size this->packet_reserve = this->packet != NULL ? len2 : 0; } // copy the data to the allocated memory if (this->packet != NULL) { this->packet_size = len2; memcpy(this->packet, packet, len); this->packet_offset = len; } } else { /* continue current fragment */ // the size of the new fragment should be <= what was given in the first fragment header if (this->packet_size < this->packet_offset + len){ fprintf(stderr,"SPUasm: invalid fragment in %d\n", this->title_num); // reset size and offset to recover from the error this->packet_size = 0; this->packet_offset = 0; } else { // a correct fragment arrived, just copy it to the buffer memcpy(this->packet + this->packet_offset, packet, len); this->packet_offset += len; } } assert(this->packet_offset <= this->packet_size); // debug #if 1 /* check if we have a complete packet (unfortunatelly packet_size is bad for some disks) [cb] packet_size is padded to be even -> may be one byte too long */ if ( (this->packet_offset == this->packet_size) || ((this->packet_offset +1) == this->packet_size)){ int x=0,y; while(x>=0 && x+4<=this->packet_offset){ // next control pointer y=get_be16(this->packet+x+2); /* fprintf(stderr,"SPUtest: x=%d y=%d off=%d size=%d\n", x,y,this->packet_offset,this->packet_size); */ // if it points to self - we're done! if(x>=4 && x==y){ // we got it! //fprintf(stderr,"SPUgot: off=%d size=%d \n",this->packet_offset,this->packet_size); spudec_decode(this, pts100); this->packet_offset = 0; break; } if(y<=x || y>=this->packet_size){ // invalid? fprintf(stderr,"SPUtest: broken packet!!!!! y=%d < x=%d in %d\n",y,x, this->title_num); this->packet_size = this->packet_offset = 0; break; } x=y; } // [cb] packet is done; start new packet this->packet_offset = 0; } #else if (this->packet_offset == this->packet_size) { spudec_decode(this, pts100); this->packet_offset = 0; } #endif } // change the current color settings void spudec_set_color(spudec_handle_t* this, int color[4]) { unsigned int i; // initialize colors for(i=0;i<4;++i){ this->cmap[i]=color[i]; } } void spudec_reset(spudec_handle_t *this) // called after seek { this->now_pts = -1; this->packet_size = this->packet_offset = 0; } // constructor // color[4] pixel values to use for subtitle color 0-3 // ppm_base_name base filename for ppm files // tag_base_name base filename for tag file spudec_handle_t * spudec_new(int color[4], char* ppm_base_name, char* tag_base_name, output_formats format, tag_formats tag_format, int crop) { char tag_filename[FILENAME_MAX]; spudec_handle_t *this = calloc(1, sizeof(spudec_handle_t)); if (!this) { perror("FATAL: spudec_init: calloc"); exit(1); } // set how many pixel should be kept around the text this->crop_border_size = crop; // initialize the color settings spudec_set_color(this,color); // save base filenames strncpy(this->ppm_base_name,ppm_base_name,FILENAME_MAX-10); strncpy(this->tag_base_name,tag_base_name,FILENAME_MAX-10); // reset counter this->title_num=1; // open the tag file for writing switch(tag_format){ case DVDAUTHOR_XML: sprintf(tag_filename,"%s.dvdxml",tag_base_name); break; case SRTX: default: sprintf(tag_filename,"%s.srtx",tag_base_name); break; } this->tag_file = fopen(tag_filename,"w"); if(!(this->tag_file)){ perror("Cannot open tagfile"); exit(1); } // set the output format this->image_format = format; // save the tag format this->tag_format = tag_format; // write file header if(DVDAUTHOR_XML == tag_format){ fprintf(this->tag_file,"\n \n"); } return this; } // destructor void spudec_free(spudec_handle_t* this) { if (this) { if (this->packet) free(this->packet); if (this->image) free(this->image); if(this->tag_file){ // write file footer if(DVDAUTHOR_XML == this->tag_format){ fprintf(this->tag_file," \n\n"); } fclose(this->tag_file); } free(this); } } // return the current title number // (equal to the number of written subtitles) unsigned int spudec_get_title_num(spudec_handle_t* this) { return this->title_num; } subtitleripper-0.3.4/spudec.h0100640000175000017500000000454007765042042016431 0ustar marillatmarillat#ifndef _SUBTITLE2PPM_SPUDEC_H #define _SUBTITLE2PPM_SPUDEC_H #include // list of supported output formats typedef enum{PGM, PPM, PGMGZ, PNG_GRAY,PNG_GRAY_ALPHA,PNG_RGBA, LAST_FORMAT} output_formats; typedef enum{SRTX, DVDAUTHOR_XML,LAST_TAG_FORMAT} tag_formats; typedef struct { unsigned char* packet; size_t packet_reserve; /* size of the memory pointed to by packet */ int packet_offset; /* end of the currently assembled fragment */ int packet_size; /* size of the packet once all fragments are assembled */ unsigned int packet_pts; /* PTS for this packet */ int control_start; /* index of start of control data */ int palette[4]; unsigned int alpha[4]; int cmap[4]; int now_pts; int is_forced; int start_pts, end_pts; int start_col; int end_col; int start_row; int end_row; int width, height, stride; int current_nibble[2]; /* next data nibble (4 bits) to be processed (for RLE decoding) for even and odd lines */ int deinterlace_oddness; /* 0 or 1, index into current_nibble */ size_t image_size; /* Size of the image buffer */ unsigned char *image; /* Grayscale values */ unsigned char *aimage; /* alpha channel */ char ppm_base_name[FILENAME_MAX]; /* base filename of ppm images */ char tag_base_name[FILENAME_MAX]; /* base file name fo tag file */ unsigned int title_num; /* counter for current subtitle nummer */ FILE* tag_file; tag_formats tag_format; /* desired output format for tag file */ output_formats image_format; /* write the image in this format */ /* start and end of the detected bounding box of the text */ unsigned int bb_start_row; unsigned int bb_end_row; unsigned int bb_start_col; unsigned int bb_end_col; int crop_border_size; } spudec_handle_t; void spudec_assemble(spudec_handle_t *this, unsigned char *packet, int len, int pts100); spudec_handle_t * spudec_new(int color[4], char* ppm_base_name, char* tag_base_name, output_formats image_format, tag_formats tag_format, int crop); void spudec_free(spudec_handle_t *this); void spudec_reset(spudec_handle_t *this); // called after seek unsigned int spudec_get_title_num(spudec_handle_t* this); #endif subtitleripper-0.3.4/srttool.c0100640000175000017500000003401707765042042016651 0ustar marillatmarillat/* Various manipulations of srtfiles Author: Arne Driescher Copyright: GPL Version: 0.03 */ #include #include #include #include #include #include #include #include #include #include #include #define MAX_TEXT_LEN 4096 typedef struct { unsigned int hour; unsigned int min; unsigned int sec; unsigned int msec; } srt_time_t; typedef struct{ unsigned int number; srt_time_t start_time; srt_time_t end_time; char text[MAX_TEXT_LEN]; } srt_tag_t; // global vars static int verbose = 0; void usage(void) { fprintf(stderr,"\t srttool [-v] [options]\n"); fprintf(stderr,"\t Modify srt subtitle files. \n"); fprintf(stderr,"\t -v Verbose\n"); fprintf(stderr,"\t -r Renumber all entries.\n"); fprintf(stderr,"\t -d