1 /*
2 
3   Convert a transcode subtitle stream to vobsub format
4 
5   Author: Arne Driescher
6 
7   Copyright:
8 
9   Most of the code is stolen from
10   mplayer (file vobsub.c) http://mplayer.dev.hu/homepage/news.html
11   and transcode http://www.theorie.physik.uni-goettingen.de/~ostreich/transcode/
12   so that the Copyright of the respective owner should be applied.
13   (That means GPL.)
14 */
15 
16 #include <stdio.h>
17 #include <assert.h>
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <fcntl.h>
21 #include <errno.h>
22 #include "subtitle2pgm.h"
23 #include <string.h>
24 #include <unistd.h>
25 #include <stdlib.h>
26 #include "vobsub.h"
27 
28 #ifndef MAXFLOAT
29 #  define MAXFLOAT      3.40282347e+38F
30 #endif
31 
32 #define READ_BUF_SIZE (64*1024)
33 
34 
35 int vobsub_id=-1;
36 int verbose=0;
37 static int append=0;
38 static double time_scale_factor=1.0;
39 
40 // magic string (definition must match the one in transcode/import/extract_ac3.c)
41 static char *subtitle_header_str="SUBTITLE";
42 
43 // get the major version number from the version code
major_version(unsigned int version)44 static unsigned int major_version(unsigned int version)
45 {
46     // bit 16-31 contain the major version number
47     return version >> 16;
48 }
49 
minor_version(unsigned int version)50 static unsigned int minor_version(unsigned int version)
51 {
52     // bit 0-15 contain the minor version number
53     return version & 0xffff;
54 }
55 
56 #if 0
57 /*
58    Read the header that transcode adds to the DVD ps1 stream.
59    Arguments:
60    stream_id      :  file handle to read from. (file must be already open)
61    subtitle_header:  struct to return the result
62    Return: >0 success, 0 = EOF, -1 on failure
63 */
64 static int read_transcode_header(subtitle_header_v3_t* subtitle_header)
65 {
66   int len;
67   int skip_len;
68   unsigned int discont_ctr=0;
69   char buf[4096];             // tmp buffer for header
70   double layer_skip_adjust=0.0;
71   double layer_skip_offset=0.0;
72   static int show_version_mismatch=1;
73 
74   // read the magic "SUBTITLE" identified
75   len=fread(buf, strlen(subtitle_header_str),1, stdin);
76 
77   if(feof(stdin)){
78     return 0;
79   }
80 
81   if(len != 1){
82 
83     fprintf(stderr,"Could not read magic header %s\n", subtitle_header_str);
84     perror("Magic header");
85     exit(1);
86   }
87   if(strncmp(buf,subtitle_header_str,strlen(subtitle_header_str))){
88     fprintf(stderr,"Header %s not found\n",subtitle_header_str);
89     fprintf(stderr,"%s\n",buf);
90     exit(1);
91   }
92 
93   // read the real header
94   len=fread(subtitle_header, sizeof(subtitle_header_v3_t), 1, stdin);
95 
96   if(len != 1){
97     fprintf(stderr,"Could not read subtitle header\n");
98     perror("Subtitle header");
99     exit(1);
100   }
101 
102 
103   // check for version mismatch and warn the user
104   if( (subtitle_header->header_version < MIN_VERSION_CODE) && show_version_mismatch){
105     fprintf(stderr,"Warning: subtitle2pgm was compiled for header version %u.%u"
106 	    " and the stream was produced with version %u.%u.\n",
107 	    major_version(MIN_VERSION_CODE),
108 	    minor_version(MIN_VERSION_CODE),
109 	    major_version(subtitle_header->header_version),
110 	    minor_version(subtitle_header->header_version));
111     // don't show this message again
112     show_version_mismatch=0;
113   }
114 
115   // we only try to proceed if the major versions match
116   if( major_version(subtitle_header->header_version) != major_version(MIN_VERSION_CODE) ){
117     fprintf(stderr,"Versions are not compatible. Please extract subtitle stream\n"
118 	    " with a newer transcode version\n");
119     exit(1);
120   }
121 
122 
123   // calculate excessive header bytes
124   skip_len = subtitle_header->header_length - sizeof(subtitle_header_v3_t);
125 
126   // handle versions mismatch
127   if(skip_len){
128 
129     // header size can only grow (unless something nasty happened)
130     assert(skip_len > 0);
131 
132     // put the rest of the header into read buffer
133     len = fread(buf, sizeof(char), skip_len, stdin);
134 
135     if(len != skip_len){
136       perror("Header skip:");
137       exit(1);
138     }
139   }
140 
141   /* depending on the minor version some additional information might
142      be available. */
143 
144   // since version 3.1 discont_ctr is available but works only sine 4-Mar-2002. Allow extra
145   // adjustment if requested.
146   if(minor_version(subtitle_header->header_version) > 1){
147     discont_ctr=*((unsigned int*) buf);
148     layer_skip_adjust = discont_ctr*layer_skip_offset;
149   }
150 
151 
152 
153   // debug output
154 #ifdef DEBUG
155   fprintf(stderr,"subtitle_header: length=%d version=%0x lpts=%u (%f), rpts=%f, payload=%d, discont=%d\n",
156 	  subtitle_header->header_length,
157 	  subtitle_header->header_version,
158 	  subtitle_header->lpts,
159 	  (double)(subtitle_header->lpts/300)/90000.0,
160 	  subtitle_header->rpts,
161 	  subtitle_header->payload_length,
162 	  discont_ctr);
163 #endif
164 
165 
166   return len;
167 }
168 #endif
169 
remove_old_sub_idx_files(const char * basename)170 void remove_old_sub_idx_files(const char* basename)
171 {
172     char sub_file_name[FILENAME_MAX];
173     char idx_file_name[FILENAME_MAX];
174     struct stat stat_buf;
175     int err;
176 
177     if(strlen(basename)<1){
178 	fprintf(stderr,"cannot remove sub/idx files for empty basename\n");
179 	return;
180     }
181 
182     // build filename for sub-file
183     strcpy(sub_file_name,basename);
184     strcat(sub_file_name,".sub");
185 
186     // build filename for idx-file
187     strcpy(idx_file_name,basename);
188     strcat(idx_file_name,".idx");
189 
190 
191 
192     err = stat(sub_file_name,&stat_buf);
193 
194     if(err){
195 	switch(errno){
196 	    case  ENOENT:  // file does not exist, no need to remove it
197 		break;
198 	    default:
199 		perror("stat sub file");
200 	}
201     } else {
202 	// try to remove the existing sub file
203 	if( -1 == remove(sub_file_name) ){
204 	    perror("remove sub file");
205 	} else {
206 	    if(verbose){
207 		fprintf(stderr,"sub file %s removed\n",sub_file_name);
208 	    }
209 	}
210     }
211 
212     err = stat(idx_file_name,&stat_buf);
213 
214     if(err){
215 	switch(errno){
216 	    case  ENOENT:  // file does not exist, no need to remove it
217 		break;
218 	    default:
219 		perror("stat idx file");
220 	}
221     } else {
222 	// try to remove the existing sub file
223 	if( -1 == remove(idx_file_name) ){
224 	    perror("remove idx file");
225 	} else {
226 	    if(verbose){
227 		fprintf(stderr,"idx file %s removed\n",idx_file_name);
228 	    }
229 	}
230     }
231 }
232 
usage(void)233 void usage(void)
234 {
235     fprintf(stderr,"\n\t Convert a transcode subtitle stream to vobsub format \n\n");
236     fprintf(stderr,"\t subtitle2vobsub [options]\n");
237     fprintf(stderr,"\t -p <input file name> of transcode ps1 file\n");
238     fprintf(stderr,"\t -i <input file name> of ifo file\n");
239     fprintf(stderr,"\t -o <output file base> name\n");
240     fprintf(stderr,"\t -s <width,height> set the default movie size if ifo-file is missing\n");
241     fprintf(stderr,"\t -c <up to 16 hex values> set color palette if ifo-file is missing\n");
242     fprintf(stderr,"\t -e <start,end,new_start> extract only part of file (parameters in seconds)\n");
243     fprintf(stderr,"\t -a n,[xx] append additionally language to existing sub and idx files\n");
244     fprintf(stderr,"\t    n = language index, xx = optional two letter language code (e.g. fr for French)\n");
245     fprintf(stderr,"\t -t <factor> time scale factor\n");
246     fprintf(stderr,"\t -v verbose output\n");
247     fprintf(stderr,"\n");
248     fprintf(stderr,"\t Version 0.3-1 (alpha) for transcode version better than 0.6.0\n");
249     fprintf(stderr,"\t See http://subtitleripper.sourceforge.net for further information\n");
250     exit(0);
251 }
252 
253 
254 
main(int argc,char ** argv)255 int main(int argc, char** argv)
256 {
257 
258     int len;
259     char read_buf[READ_BUF_SIZE];
260     char output_base_name[FILENAME_MAX];
261     char input_file_name[FILENAME_MAX];
262     char ifo_file_name[FILENAME_MAX];
263 
264     subtitle_header_v3_t subtitle_header;
265     int ch,n;
266     int skip_len;
267     double pts_seconds=0.0;
268     unsigned int show_version_mismatch=~0;
269     double layer_skip_adjust=0.0;
270     double layer_skip_offset=0.0;
271     unsigned int discont_ctr=0;
272 
273     // default color palette
274     unsigned int palette[16]={0x101010, 0x6e6e6e, 0xcbcbcb, 0x202020, 0x808080, 0x808080,
275 			      0x808080, 0x808080, 0x808080, 0x808080, 0x808080, 0x808080,
276 			      0xb4b4b4, 0x101010, 0xe4e4e4, 0x808080};
277     // assume PAL DVD size: 720x576 if no IFO was found
278     unsigned int width=720;
279     unsigned int height=576;
280     unsigned char lang_abrv[3] = { 'e', 'n', 0 };
281     unsigned int vobsub_out_index=0;
282     int dvdsub_id=0; //FIXME
283     void *vobsub_writer=NULL;
284     double extract_start_pts=0.0;
285     double extract_end_pts= MAXFLOAT;
286     double extract_output_start_pts=0.0;
287     double output_pts=0.0;
288 
289     /* initialize default values here that can be overridden by command line arguments */
290 
291     // default filenames used for input/output
292     strcpy(output_base_name,"movie_subtitle");
293     strcpy(ifo_file_name,"movie.ifo");
294     strcpy(input_file_name,"movie.ps1");
295 
296     /* scan command line arguments */
297     opterr=0;
298     while ((ch = getopt(argc, argv, "vt:s:p:i:c:e:o:a:h")) != -1) {
299 
300 	switch (ch) {
301 
302 	    // color palette
303 	    case 'c':
304 		n = sscanf(optarg,"%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x",
305 			   &palette[0], &palette[1], &palette[2], &palette[3],
306 			   &palette[4], &palette[5], &palette[6], &palette[7],
307 			   &palette[8], &palette[9], &palette[10], &palette[11],
308 			   &palette[12], &palette[13], &palette[14], &palette[15]);
309 		if(n<1 || n>16) {
310 		    fprintf(stderr,"invalid argument to color palette option\n");
311 		    exit(-1);
312 		}
313 		break;
314 
315 
316 	    case 'e':  // extract only a part
317 		n = sscanf(optarg,"%lf,%lf,%lf", &extract_start_pts, &extract_end_pts,&extract_output_start_pts);
318 		if(n==0) {
319 		    fprintf(stderr,"Invalid parameter for -e option\n");
320 		    exit(1);
321 		}
322 		if(extract_start_pts<0){
323 		    fprintf(stderr,"negative start pts not allowed for -e option\n");
324 		    exit(-1);
325 		}
326 
327 		if(extract_end_pts<0){
328 		    fprintf(stderr,"negative end pts not allowed for option -e\n");
329 		    exit(-1);
330 		}
331 		if(extract_output_start_pts<0){
332 		    fprintf(stderr,"warning: Negative pts for output given to -e option. Check your results!\n");
333 		}
334 
335 		if(verbose)
336 		    fprintf(stderr,"Extracting from %f. to %f and setting initial pts to %f \n",
337 			    extract_start_pts,
338 			    extract_end_pts,
339 			    extract_output_start_pts);
340 		break;
341 
342 	    case 's':  // set default size to -s width,height if no ifo file is available
343 		n = sscanf(optarg,"%d,%d", &width, &height);
344 		if( (n<1) || (n>2) ) {
345 		    fprintf(stderr,"Invalid parameter for -s width,height option\n");
346 		    exit(1);
347 		}
348 		break;
349 
350 	    case 'o':
351 		n = sscanf(optarg,"%s", output_base_name);
352 
353 		if(n!=1) {
354 		    fprintf(stderr,"no filename specified to option -o\n");
355 		    exit(1);
356 		}
357 		break;
358 
359 	    case 'i':
360 		n = sscanf(optarg,"%s",ifo_file_name);
361 
362 		if(n!=1) {
363 		    fprintf(stderr,"no filename specified to option -i\n");
364 		    exit(1);
365 		}
366 		break;
367 
368 	    case 'p':
369 		n = sscanf(optarg,"%s", input_file_name);
370 
371 		if(n!=1) {
372 		    fprintf(stderr,"no filename specified to option -p\n");
373 		    exit(1);
374 		}
375 		// open the specified input file for reading
376 		if( !(freopen(input_file_name,"r",stdin)) ){
377 		    perror("stdin redirection");
378 		    fprintf(stderr,"Tried to open %s for input\n",input_file_name);
379 		    exit(1);
380 		}
381 		break;
382 
383 	    case 't':
384 		n = sscanf(optarg,"%lf", &time_scale_factor);
385 		if(n<1){
386 		    fprintf(stderr,"Argument to -t option not given\n");
387 		    exit(1);
388 		}
389 		if(verbose){
390 		    fprintf(stderr,"Scaling time by %f\n",
391 			    time_scale_factor);
392 		}
393 		break;
394 	    case 'a':
395 		n = sscanf(optarg,"%d,%c%c",
396 			   &vobsub_out_index,
397 			   &lang_abrv[0],
398 			   &lang_abrv[1]);
399 		if(n<1){
400 		    fprintf(stderr,"Invalid arguments to -a option\n");
401 		    exit(1);
402 		}
403 		dvdsub_id = vobsub_out_index;
404 		append=1; // do not delete the old files
405 		break;
406 
407 	    case 'h':
408 		usage();
409 		break;
410 
411 	    case 'v':
412 		verbose=1;
413 		break;
414 
415 	    default:
416 		fprintf(stderr,"Unknown option. Use -h for list of valid options.\n");
417 		exit(1);
418 	}
419     }
420 
421     // parse the ifo file to get subtitle picture size and color palette
422     if(vobsub_parse_ifo(NULL,ifo_file_name, palette, &width, &height, 1, dvdsub_id, lang_abrv)>=0){
423 	if(verbose){
424 	    fprintf(stderr,"reading IFO file was successful\n");
425 	}
426     } else {
427 	fprintf(stderr,"Opening ifo file failed. I tried to open %s but got an error.\n",
428 		ifo_file_name);
429 	fprintf(stderr,"Using default or command line arguments "
430 		"for palette, width and hight instead\n");
431     }
432 
433     // delete existing sub/idx files if append mode is not requested
434     if(append){
435 	if(verbose){
436 	    fprintf(stderr,"Append mode for existing sub/idx files assumed\n");
437 	}
438     } else {
439 	remove_old_sub_idx_files(output_base_name);
440     }
441 
442     // open the vobsub output file
443     vobsub_writer = vobsub_out_open(output_base_name, palette, width, height,
444 				    lang_abrv, vobsub_out_index);
445 
446     if(!vobsub_writer){
447 	fprintf(stderr,"vobsub_writer instance not created.\n");
448 	exit(-1);
449     }
450 
451     if(verbose){
452 	fprintf(stderr,"Using width:%d height:%d language:%s vobsub index:%d\n",
453 		width,
454 		height,
455 		lang_abrv,
456 		vobsub_out_index);
457     }
458 
459     // process all packages in the stream
460     // The stream is an "augmented" raw subtitle stream
461     // where two additional headers are used.
462     while(1){
463 
464 	// read the magic "SUBTITLE" identified
465 	len=fread(read_buf, strlen(subtitle_header_str),1, stdin);
466 
467 	if(feof(stdin)){
468 	    break;
469 	}
470 
471 	if(len != 1){
472 
473 	    fprintf(stderr,"Could not read magic header %s\n", subtitle_header_str);
474 	    perror("Magic header");
475 	    exit(1);
476 	}
477 	if(strncmp(read_buf,subtitle_header_str,strlen(subtitle_header_str))){
478 	    fprintf(stderr,"Header %s not found\n",subtitle_header_str);
479 	    fprintf(stderr,"%s\n",read_buf);
480 	    exit(1);
481 	}
482 
483 	// read the real header
484 	len=fread(&subtitle_header, sizeof(subtitle_header_v3_t), 1, stdin);
485 
486 	if(len != 1){
487 	    fprintf(stderr,"Could not read subtitle header\n");
488 	    perror("Subtitle header");
489 	    exit(1);
490 	}
491 
492 
493 	// check for version mismatch and warn the user
494 	if( (subtitle_header.header_version < MIN_VERSION_CODE) && show_version_mismatch){
495 	    fprintf(stderr,"Warning: subtitle2pgm was compiled for header version %u.%u"
496 		    " and the stream was produced with version %u.%u.\n",
497 		    major_version(MIN_VERSION_CODE),
498 		    minor_version(MIN_VERSION_CODE),
499 		    major_version(subtitle_header.header_version),
500 		    minor_version(subtitle_header.header_version));
501 	    // don't show this message again
502 	    show_version_mismatch=0;
503 	}
504 
505 	// we only try to proceed if the major versions match
506 	if( major_version(subtitle_header.header_version) != major_version(MIN_VERSION_CODE) ){
507 	    fprintf(stderr,"Versions are not compatible. Please extract subtitle stream\n"
508 		    " with a newer transcode version\n");
509 	    exit(1);
510 	}
511 
512 
513 	// calculate excessive header bytes
514 	skip_len = subtitle_header.header_length - sizeof(subtitle_header_v3_t);
515 
516 	// handle versions mismatch
517 	if(skip_len){
518 
519 	    // header size can only grow (unless something nasty happened)
520 	    assert(skip_len > 0);
521 
522 	    // put the rest of the header into read buffer
523 	    len = fread(read_buf, sizeof(char), skip_len, stdin);
524 
525 	    if(len != skip_len){
526 		perror("Header skip:");
527 		exit(1);
528 	    }
529 	}
530 
531 	/* depending on the minor version some additional information might
532 	   be available. */
533 
534 	// since version 3.1 discont_ctr is available but works only sine 4-Mar-2002. Allow extra
535 	// adjustment if requested.
536 	if(minor_version(subtitle_header.header_version) > 1){
537 	    discont_ctr=*((unsigned int*) read_buf);
538 	    layer_skip_adjust = discont_ctr*layer_skip_offset;
539 	}
540 
541 
542 
543 	// debug output
544 #ifdef DEBUG
545         fprintf(stderr,"subtitle_header: length=%d version=%0x lpts=%u (%f), rpts=%f, payload=%d, discont=%d\n",
546 		subtitle_header.header_length,
547 		subtitle_header.header_version,
548 		subtitle_header.lpts,
549 		(double)(subtitle_header.lpts/300)/90000.0,
550 		subtitle_header.rpts,
551 		subtitle_header.payload_length,
552 		discont_ctr);
553 #endif
554 
555 	// read one byte subtitle stream id (should match the number given to tcextract -a)
556 	len=fread(read_buf, sizeof(char), 1, stdin);
557 	if(len != 1){
558 	    perror("stream id");
559 	    exit(1);
560 	}
561 	// debug output
562 	//fprintf(stderr,"stream id: %x \n",(int)*read_buf);
563 
564 
565 	// read number of bytes given in header
566 	len = fread(read_buf, subtitle_header.payload_length-1, 1, stdin);
567 
568 
569 	if(len >0){
570 	    if(subtitle_header.rpts > 0){
571 		// if rpts is something useful take it
572 		pts_seconds=subtitle_header.rpts;
573 	    } else {
574 		// calculate the time from lpts
575 		fprintf(stderr, "fallback to lpts!\n");
576 		pts_seconds = (double)(subtitle_header.lpts/300)/90000.0;
577 	    }
578 
579 	    // add offset for layer skip
580 	    pts_seconds += layer_skip_adjust;
581 
582 
583 	    // output only in the requested range (-e option)
584 	    if( (extract_start_pts <= pts_seconds) && (extract_end_pts >= pts_seconds) ){
585 		output_pts = pts_seconds - extract_start_pts + extract_output_start_pts;
586 		// scale time if requested (-t option)
587 		output_pts *= time_scale_factor;
588 		vobsub_out_output(vobsub_writer,read_buf, subtitle_header.payload_length-1,output_pts);
589 	    }
590 	} else {
591 	    perror("Input file processing finished");
592 	    exit(errno);
593 	}
594     }
595 
596     if(verbose){
597 	fprintf(stderr,"Conversion finished\n");
598     }
599 
600     vobsub_out_close(vobsub_writer);
601 
602     return 0;
603 }
604 
605 
606 
607 
608 
609 
610 
611 
612 
613 
614 
615 
616 
617 
618 
619 
620 
621 
622 
623 
624 
625 
626 
627 
628 
629 
630