1 /*
2  *  filter_extsub.c
3  *
4  *  Copyright (C) Thomas Oestreich - January 2002
5  *
6  *  This file is part of transcode, a video stream processing tool
7  *
8  *  transcode is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2, or (at your option)
11  *  any later version.
12  *
13  *  transcode is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with GNU Make; see the file COPYING.  If not, write to
20  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
21  *
22  */
23 
24 #define MOD_NAME    "filter_extsub.so"
25 #define MOD_VERSION "0.3.5 (2003-10-15)"
26 #define MOD_CAP     "DVD subtitle overlay plugin"
27 #define MOD_AUTHOR  "Thomas Oestreich"
28 
29 #include "transcode.h"
30 #include "encoder.h"
31 #include "filter.h"
32 #include "libtc/optstr.h"
33 #include "libtcvideo/tcvideo.h"
34 
35 #include <stdint.h>
36 
37 #include "dl_loader.h"
38 #include "import/magic.h"
39 
40 #include "subtitle_buffer.h"
41 #include "subproc.h"
42 
43 #define BUFFER_SIZE SIZE_RGB_FRAME
44 #define SUBTITLE_BUFFER 100
45 
46 static transfer_t import_para;
47 
48 static pthread_t thread1;
49 
50 static double f_pts, f_time;
51 
52 //infos on activated subtitle
53 static char *sub_frame;
54 static char *vid_frame;
55 static char *tmp_frame;
56 
57 static double sub_pts1=-1.0f, sub_pts2=-1.0f;
58 static int sub_xpos, sub_ypos;
59 static int sub_xlen, sub_ylen;
60 static int sub_id=0;
61 static int sub_forced=0;
62 static int sub_colour[4];
63 static int sub_alpha[4];
64 
65 static int codec;
66 static int vshift=0, tshift=0, post=0;
67 
68 static unsigned int color1=0, color2=255;
69 static int forced=0;
70 
71 static double aa_weight, aa_bias;
72 
73 static TCVHandle tcvhandle = 0;
74 
75 
76 //-------------------------------------------------------------------
77 //
78 // retrieve a valid subtitle
79 //
80 //-------------------------------------------------------------------
81 
subtitle_retrieve(void)82 static int subtitle_retrieve(void)
83 {
84 
85   sframe_list_t *sptr = NULL;
86   sub_info_t sub;
87   int n;
88 
89   // get a subtitle from buffer
90   pthread_mutex_lock(&sframe_list_lock);
91 
92   if(sframe_fill_level(TC_BUFFER_EMPTY)) {
93       pthread_mutex_unlock(&sframe_list_lock);
94       return(-1);
95   }
96 
97   //not beeing empty does not mean ready to go!!!!!
98 
99   if(sframe_fill_level(TC_BUFFER_READY)) {
100 
101       pthread_mutex_unlock(&sframe_list_lock);
102 
103       if((sptr = sframe_retrieve())==NULL) {
104 	  //this shouldn't happen
105 	  tc_log_error(MOD_NAME, "internal error (S)");
106 	  return(-1);
107       }
108   } else {
109       pthread_mutex_unlock(&sframe_list_lock);
110       return(-1);
111   }
112 
113   //room for title pixels
114   sub.frame = sub_frame;
115 
116   // conversion
117   if(subproc_feedme(sptr->video_buf, sptr->video_size, sptr->id, sptr->pts, &sub)<0) {
118     // problems, drop this subtitle
119     if(verbose & TC_DEBUG) tc_log_warn(MOD_NAME, "subtitle dropped");
120     sframe_remove(sptr);
121     pthread_cond_signal(&sframe_list_full_cv);
122     return(-1);
123   }
124 
125   //save data
126 
127   sub_id   = sptr->id;
128   sub_pts1 = sptr->pts * f_time;
129   sub_pts2 = sub_pts1 + sub.time/100.0;
130 
131   sub_forced = sub.forced;
132   sub_xpos = sub.x;
133   sub_ypos = sub.y;
134   sub_xlen = sub.w;
135   sub_ylen = sub.h;
136 
137   for(n=0; n<4;++n) sub_alpha[n] = sub.alpha[n];
138 
139   //release packet buffer
140   sframe_remove(sptr);
141   pthread_cond_signal(&sframe_list_full_cv);
142 
143   if(verbose & TC_STATS)
144     tc_log_info(MOD_NAME, "got SUBTITLE %d with forced=%d, pts=%.3f dtime=%.3f",
145 	            sub_id, sub_forced, sub_pts1, sub_pts2-sub_pts1);
146   return(0);
147 }
148 
149 //-------------------------------------------------------------------
150 //
151 // assign colors to subtitle colors 0-4
152 //
153 //-------------------------------------------------------------------
154 
155 static int color_set_done=0;
156 static int anti_alias_done=0;
157 static int skip_anti_alias=0;
158 
159 static unsigned int ca=2, cb=3;
160 
get_subtitle_colors(void)161 static void get_subtitle_colors(void) {
162 
163   int y;
164 
165   for(y=0; y<sub_ylen*sub_xlen; ++y) ++sub_colour[(uint8_t) sub_frame[y]];
166 
167   if(sub_colour[0] || sub_colour[1] || sub_colour[2] || sub_colour[3]) {
168 
169     if(sub_colour[1]>sub_colour[2] && sub_colour[1]>sub_colour[3]) {
170       ca = 1;
171       cb = (sub_colour[2]>sub_colour[3]) ? 2:3;
172     }
173 
174     if(sub_colour[2]>sub_colour[1] && sub_colour[2]>sub_colour[3]) {
175       ca = 2;
176       cb = (sub_colour[1]>sub_colour[3]) ? 1:3;
177     }
178 
179     if(sub_colour[3]>sub_colour[1] && sub_colour[3]>sub_colour[2]) {
180       ca = 3;
181       cb = (sub_colour[1]>sub_colour[2]) ? 1:2;
182     }
183   }
184   color_set_done=1;
185 
186   if(verbose & TC_DEBUG) {
187     tc_log_info(MOD_NAME, "color dis: 0=%d, 1=%d, 2=%d, 3=%d, ca=%d, cb=%d",
188                     sub_colour[0], sub_colour[1], sub_colour[2], sub_colour[3],
189                     ca, cb);
190     tc_log_info(MOD_NAME, "alpha dis: 0=%d, 1=%d, 2=%d, 3=%d, ca=%d, cb=%d",
191                     sub_alpha[0], sub_alpha[1], sub_alpha[2], sub_alpha[3],
192                     ca, cb);
193   }
194 
195 }
196 
197 //-------------------------------------------------------------------
198 //
199 // anti-alias subtitle bitmap
200 //
201 //-------------------------------------------------------------------
202 
anti_alias_subtitle(int black)203 static void anti_alias_subtitle(int black) {
204 
205   int back_col=black;
206   int n;
207 
208   if(color1<=black) color1=black+1;
209   if(color2<=black) color2=black+1;
210 
211   for(n=0; n<sub_ylen*sub_xlen; ++n) {
212 
213     if(sub_frame[n] == ca) {
214       sub_frame[n] = color1 & 0xff;
215       back_col=black;
216       goto next_pix;
217     }
218 
219     if(sub_frame[n] == cb) {
220       sub_frame[n] = color2 & 0xff;
221       back_col=255;
222       goto next_pix;
223     }
224 
225     if(back_col==255) {
226       sub_frame[n] = 255 & 0xff;
227     } else sub_frame[n]=black;
228 
229   next_pix:
230     continue;
231   }
232 
233   if(!skip_anti_alias) {
234     tcv_antialias(tcvhandle, sub_frame, tmp_frame, sub_xlen, sub_ylen, 1,
235 		  aa_weight, aa_bias);
236     ac_memcpy(sub_frame, tmp_frame, sub_xlen * sub_ylen);
237   }
238 
239   anti_alias_done=1;
240 
241 }
242 
243 //-------------------------------------------------------------------
244 //
245 // YUV overlay
246 //
247 //-------------------------------------------------------------------
248 
subtitle_overlay_yuv(char * vid_frame,int w,int h)249 static void subtitle_overlay_yuv(char *vid_frame, int w, int h)
250 {
251 
252   int x, y, n, m;
253 
254   int eff_sub_ylen, off;
255 
256   if(verbose & TC_STATS)
257     tc_log_info(MOD_NAME, "SUBTITLE id=%d, x=%d, y=%d, w=%d, h=%d, t=%f",
258                     sub_id, sub_xpos, sub_ypos, sub_xlen, sub_ylen,
259                     sub_pts2-sub_pts1);
260 
261   if(color_set_done==0) get_subtitle_colors();
262 
263   //check:
264   eff_sub_ylen = (sub_ylen+vshift>h) ?  h-vshift:sub_ylen;
265 
266   off = (vshift>0) ? vshift:0;
267 
268   if(eff_sub_ylen<0 || off>eff_sub_ylen) {
269     tc_log_info(MOD_NAME, "invalid subtitle shift parameter");
270     return;
271   }
272 
273   if(!anti_alias_done) anti_alias_subtitle(16);
274 
275   //black the frame
276   if(0) {
277     for(y=0; y<w*h; ++y) vid_frame[y]=255;
278     for(y=0; y<w*h/4; ++y) vid_frame[w*h+y]=80;
279     for(y=0; y<w*h/4; ++y) vid_frame[w*h*5/4+y]=80;
280   }
281 
282   n=0;
283 
284   for(y=0; y<eff_sub_ylen-off; ++y) {
285 
286     m = sub_xpos + (y+h-eff_sub_ylen)*w+vshift*w;
287 
288     for(x=0; x<sub_xlen; ++x) {
289 
290       if(sub_frame[n] != 16) {
291 	//Y
292 	vid_frame[m] = sub_frame[n] & 0xff;
293 	if(0) {
294 	  //U
295 	  if(x%2==0 && y%2==0)
296 	    vid_frame[w*h + x/2+ sub_xpos/2 + (y+h-eff_sub_ylen-vshift)*w/4] = 0x80 & 0xff;
297 	  //V
298 	  if(x%2==0 && y%2==0)
299 	    vid_frame[(w*h*5)/4 + x/2 +  sub_xpos/2 + (y+h-eff_sub_ylen-vshift)*w/4] = 0x80 & 0xff;
300 	}
301       }
302       //16=transparent
303       ++m;
304       ++n;
305     } // progress columns
306   } //progress rows
307 }
308 
309 //-------------------------------------------------------------------
310 //
311 // RGB overlay
312 //
313 //-------------------------------------------------------------------
314 
subtitle_overlay_rgb(char * vid_frame,int w,int h)315 static void subtitle_overlay_rgb(char *vid_frame, int w, int h)
316 {
317 
318   int x, y, n, m;
319 
320   int eff_sub_ylen, off;
321 
322   if(verbose & TC_STATS)
323     tc_log_info(MOD_NAME, "SUBTITLE id=%d, x=%d, y=%d, w=%d, h=%d, t=%f",
324                     sub_id, sub_xpos, sub_ypos, sub_xlen, sub_ylen,
325                     sub_pts2-sub_pts1);
326 
327   if(color_set_done==0) get_subtitle_colors();
328 
329   n=0;
330 
331   //check:
332   eff_sub_ylen = (sub_ylen+vshift>h) ?  h-vshift:sub_ylen;
333   eff_sub_ylen = sub_ylen;
334 
335   off = (vshift<0) ? -vshift:0;
336 
337   if(eff_sub_ylen<0 || off>eff_sub_ylen) {
338     tc_log_warn(MOD_NAME, "invalid subtitle shift parameter");
339     return;
340   }
341 
342   if(!anti_alias_done) anti_alias_subtitle(0);
343 
344   for(y=0; y<eff_sub_ylen-off; ++y) {
345 
346     m = sub_xpos*3 + (eff_sub_ylen-y+vshift+(off?0:vshift))*w*3;
347 
348     for(x=0; x<sub_xlen; ++x) {
349 
350       if(sub_frame[n] !=0) {
351 	vid_frame[m++]= sub_frame[n] & 0xff;
352 	vid_frame[m++]= sub_frame[n] & 0xff;
353 	vid_frame[m++]= sub_frame[n] & 0xff;
354       } else {
355 	m=m+3;
356       }
357       ++n;
358     }
359   }
360 }
361 
362 //-------------------------------------------------------------------
363 //
364 // subtitle overlay wrapper
365 //
366 //-------------------------------------------------------------------
367 
subtitle_overlay(char * vid_frame,int w,int h)368 static void subtitle_overlay(char *vid_frame, int w, int h)
369 {
370   if(codec == CODEC_RGB) subtitle_overlay_rgb(vid_frame, w, h);
371   if(codec == CODEC_YUV) subtitle_overlay_yuv(vid_frame, w, h);
372 }
373 
374 /*-------------------------------------------------
375  *
376  * single function interface
377  *
378  *-------------------------------------------------*/
379 
is_optstr(char * buf)380 static int is_optstr (char *buf) {
381     if (strchr(buf, 'h')) return 1;
382     if (strchr(buf, '='))
383 	return 1;
384     return 0;
385 }
386 
tc_filter(frame_list_t * ptr_,char * options)387 int tc_filter(frame_list_t *ptr_, char *options)
388 {
389   vframe_list_t *ptr = (vframe_list_t *)ptr_;
390   static vob_t *vob=NULL;
391   int pre=0, vid=0;
392   int subtitles=0;
393   int n=0;
394 
395   if (ptr->tag & TC_FILTER_GET_CONFIG) {
396       optstr_filter_desc (options, MOD_NAME, MOD_CAP, MOD_VERSION, MOD_AUTHOR, "VRYOE", "1");
397       optstr_param (options, "track",   "Subtitle track to render",    "%d",    "0", "0", "255");
398       optstr_param (options, "forced", "Render only forced subtitles", "%d", "0", "0", "1" );
399       optstr_param (options, "vertshift", "offset of subtitle with respect to bottom of frame in rows", "%d",  "0", "0", "height");
400       optstr_param (options, "timeshift", "global display start time correction in msec",    "%d",    "0", "0", "-1");
401       optstr_param (options, "antialias", "anti-aliasing the rendered text (0=off,1=on)",    "%d",    "1", "0", "1");
402       optstr_param (options, "pre",   "Run as a pre filter",    "%d",    "1", "0", "1");
403       optstr_param (options, "color1", "Make a subtitle color visible with given intensity", "%d",  "0", "0", "255");
404       optstr_param (options, "color2", "Make a subtitle color visible with given intensity", "%d",  "0", "0", "255");
405       optstr_param (options, "ca",   "Shuffle the color assignment by choosing another subtitle color",    "%d", "0", "0", "3");
406       optstr_param (options, "cb",   "Shuffle the color assignment by choosing another subtitle color",    "%d", "0", "0", "3");
407 
408       return 0;
409   }
410 
411   //----------------------------------
412   //
413   // filter init
414   //
415   //----------------------------------
416 
417   if(ptr->tag & TC_FILTER_INIT) {
418 
419     if((vob = tc_get_vob())==NULL) return(-1);
420 
421     // filter init ok.
422 
423     if(verbose) tc_log_info(MOD_NAME, "%s %s", MOD_VERSION, MOD_CAP);
424 
425     if(verbose) tc_log_info(MOD_NAME, "options=%s", options);
426 
427     //------------------------------------------------------------
428 
429     if(options != NULL) {
430 	if (!is_optstr(options)) {
431 	    n=sscanf(options,"%d:%d:%d:%d:%d:%d:%d:%d:%d", &vob->s_track, &vshift, &tshift, &skip_anti_alias, &post, &color1, &color2, &ca, &cb);
432 	} else { // new options
433 	    optstr_get (options, "track", "%d", &vob->s_track);
434 	    optstr_get (options, "forced", "%d", &forced);
435 	    optstr_get (options, "vertshift", "%d", &vshift);
436 	    optstr_get (options, "timeshift", "%d", &tshift);
437 	if (optstr_get (options, "antialias", "%d", &skip_anti_alias)>=0) skip_anti_alias = !skip_anti_alias;
438 	if (optstr_get (options, "pre", "%d", &post)>=0) post = !post;
439 	    optstr_get (options, "color1", "%d", &color1);
440 	    optstr_get (options, "color2", "%d", &color2);
441 	if (optstr_get (options, "ca", "%d", &ca)>=0) n = 9;
442 	if (optstr_get (options, "cb", "%d", &cb)>=0) n = 9;
443 	if (optstr_lookup (options, "help")) return (-1);
444 	}
445     }
446     if (!skip_anti_alias) {
447         if (!(tcvhandle = tcv_init())) {
448              tc_log_error(MOD_NAME, "antialiasing initialization failed");
449              return TC_EXPORT_ERROR;
450         }
451     }
452 
453     if (vob->im_v_codec == CODEC_YUV)
454 	vshift = -vshift;
455 
456     if(n>8) color_set_done=1;
457 
458     if(verbose) tc_log_info(MOD_NAME, "extracting subtitle 0x%x", vob->s_track+0x20);
459 
460     // start subtitle stream
461     import_para.flag=TC_SUBEX;
462 
463     if(tcv_import(TC_IMPORT_OPEN, &import_para, vob)<0) {
464       tc_log_error(MOD_NAME, "popen subtitle stream");
465     }
466 
467     //------------------------------------------------------------
468 
469     subproc_init(NULL, "title", subtitles, (unsigned short)vob->s_track);
470 
471     // buffer allocation:
472 
473     sframe_alloc(SUBTITLE_BUFFER, import_para.fd);
474 
475     // start thread
476     if(pthread_create(&thread1, NULL, (void *) subtitle_reader, NULL)!=0)
477       tc_log_error(MOD_NAME, "failed to start subtitle import thread");
478 
479     // misc:
480 
481     if (post) {
482 	f_time = 1./vob->ex_fps;
483     } else {
484 	f_time = 1./vob->fps;
485     }
486 
487     codec = vob->im_v_codec;
488 
489     if ((sub_frame = malloc(BUFFER_SIZE))==NULL) {
490       tc_log_perror(MOD_NAME, "out of memory");
491       return(TC_EXPORT_ERROR);
492     } else
493       memset(sub_frame, 0, BUFFER_SIZE);
494 
495     if ((vid_frame = malloc(BUFFER_SIZE))==NULL) {
496       tc_log_perror(MOD_NAME, "out of memory");
497       return(TC_EXPORT_ERROR);
498     } else
499       memset(vid_frame, 0, BUFFER_SIZE);
500 
501     if ((tmp_frame = malloc(BUFFER_SIZE))==NULL) {
502       tc_log_perror(MOD_NAME, "out of memory");
503       return(TC_EXPORT_ERROR);
504     } else
505       memset(tmp_frame, 0, BUFFER_SIZE);
506 
507     aa_weight = vob->aa_weight;
508     aa_bias = vob->aa_bias;
509 
510     return(0);
511   }
512 
513   //----------------------------------
514   //
515   // filter close
516   //
517   //----------------------------------
518 
519   if(ptr->tag & TC_FILTER_CLOSE) {
520 
521     void * status;
522 
523     pthread_cancel(thread1);
524 #ifdef BROKEN_PTHREADS // Used to be MacOSX specific; kernel 2.6 as well?
525     pthread_cond_signal(&sframe_list_full_cv);
526 #endif
527     pthread_join(thread1, &status);
528 
529     import_para.flag=TC_SUBEX;
530 
531     if(import_para.fd!=NULL) pclose(import_para.fd);
532 
533     import_para.fd=NULL;
534 
535     tcv_free(tcvhandle);
536     tcvhandle = 0;
537 
538     //FIXME: module already removed by main process
539 
540     //    if(tca_import(TC_IMPORT_CLOSE, &import_para, vob)<0) {
541     //  tc_log_error(MOD_NAME, "pclose subtitle stream");
542     //}
543 
544     if(vid_frame) free(vid_frame);
545     if(sub_frame) free(sub_frame);
546 
547     return(0);
548   }
549 
550   //----------------------------------
551   //
552   // filter frame routine
553   //
554   //----------------------------------
555 
556   // tag variable indicates, if we are called before
557   // transcodes internal video/audo frame processing routines
558   // or after and determines video/audio context
559 
560   if(verbose & TC_STATS)
561     tc_log_info(MOD_NAME, "%s/%s %s %s",
562                     vob->mod_path, MOD_NAME, MOD_VERSION, MOD_CAP);
563 
564   if(post) {
565     pre = (ptr->tag & TC_POST_S_PROCESS)? 1:0;
566   } else {
567     pre = (ptr->tag & TC_PRE_S_PROCESS)? 1:0;
568   }
569   vid = (ptr->tag & TC_VIDEO)? 1:0;
570 
571   if(pre==0 || vid==0) return(0);
572 
573   //-------------------------------------------------------------------------
574   //
575   // below is a very fuzzy concept of rendering the subtitle on the movie
576   //
577   // (1) check if we have a valid subtitle and render it
578   // (2) if (1) fails try to get a new one
579   // (3) buffer and display the new one if it's showtime, if not return
580   //
581   // repeat steps throughout the movie
582 
583   // calculate current frame video PTS in [s]
584   // adjust for dropped frames so far
585   // adjust for user shift (in milliseconds)
586 
587   f_pts = f_time*(ptr->id-tc_get_frames_dropped()+vob->psu_offset) + tshift/1000.;
588 
589   if(verbose & TC_DEBUG)
590     tc_log_info(MOD_NAME, "frame=%06d pts=%.3f sub1=%.3f sub2=%.3f",
591                     ptr->id, f_pts, sub_pts1, sub_pts2);
592 
593   //overlay now?
594   if(sub_pts1 <= f_pts && f_pts <= sub_pts2) {
595     if(!forced || sub_forced)
596       subtitle_overlay(ptr->video_buf, ptr->v_width, ptr->v_height);
597     return(0);
598   }
599 
600   //get a new subtitle, if the last one has expired:
601 
602   //reset anti-alias info
603   anti_alias_done=0;
604 
605   if(f_pts > sub_pts2) {
606     if(subtitle_retrieve()<0) {
607       if(verbose & TC_STATS)
608         tc_log_info(MOD_NAME, "no subtitle available at this time");
609       return(0);
610     }
611   }
612 
613   //overlay now?
614   if(sub_pts1 < f_pts && f_pts < sub_pts2)
615     if(!forced || sub_forced)
616       subtitle_overlay(ptr->video_buf, ptr->v_width, ptr->v_height);
617   return(0);
618 }
619