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