/src/vlc/modules/codec/libass.c
Line | Count | Source |
1 | | /***************************************************************************** |
2 | | * SSA/ASS subtitle decoder using libass. |
3 | | ***************************************************************************** |
4 | | * Copyright (C) 2008-2009 VLC authors and VideoLAN |
5 | | * |
6 | | * Authors: Laurent Aimar <fenrir@videolan.org> |
7 | | * |
8 | | * This program is free software; you can redistribute it and/or modify it |
9 | | * under the terms of the GNU Lesser General Public License as published by |
10 | | * the Free Software Foundation; either version 2.1 of the License, or |
11 | | * (at your option) any later version. |
12 | | * |
13 | | * This program 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 Lesser General Public License for more details. |
17 | | * |
18 | | * You should have received a copy of the GNU Lesser General Public License |
19 | | * along with this program; if not, write to the Free Software Foundation, |
20 | | * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. |
21 | | *****************************************************************************/ |
22 | | |
23 | | /***************************************************************************** |
24 | | * Preamble |
25 | | *****************************************************************************/ |
26 | | |
27 | | #ifdef HAVE_CONFIG_H |
28 | | # include "config.h" |
29 | | #endif |
30 | | |
31 | | #include <stdarg.h> |
32 | | #include <string.h> |
33 | | #include <limits.h> |
34 | | #include <assert.h> |
35 | | #include <math.h> |
36 | | |
37 | | #include <vlc_common.h> |
38 | | #include <vlc_plugin.h> |
39 | | #include <vlc_codec.h> |
40 | | #include <vlc_input.h> |
41 | | #include <vlc_dialog.h> |
42 | | #include <vlc_stream.h> |
43 | | #include <vlc_messages.h> |
44 | | |
45 | | #include <ass/ass.h> |
46 | | |
47 | | #if LIBASS_VERSION < 0x01300000 |
48 | | # define ASS_FONTPROVIDER_AUTODETECT 1 |
49 | | #endif |
50 | | |
51 | | #if defined(_WIN32) |
52 | | # include <vlc_charset.h> |
53 | | #endif |
54 | | |
55 | | /***************************************************************************** |
56 | | * Module descriptor |
57 | | *****************************************************************************/ |
58 | | static int Create ( vlc_object_t * ); |
59 | | static void Destroy( vlc_object_t * ); |
60 | | |
61 | | #define TEXT_SSA_FONTSDIR N_("Additional fonts directory") |
62 | | |
63 | 174 | vlc_module_begin () |
64 | 87 | set_shortname( N_("Subtitles (advanced)")) |
65 | 87 | set_description( N_("Subtitle renderers using libass") ) |
66 | 87 | set_capability( "spu decoder", 100 ) |
67 | 87 | set_subcategory( SUBCAT_INPUT_SCODEC ) |
68 | 87 | set_callbacks( Create, Destroy ) |
69 | 87 | add_string("ssa-fontsdir", NULL, TEXT_SSA_FONTSDIR, NULL) |
70 | 87 | vlc_module_end () |
71 | | |
72 | | /***************************************************************************** |
73 | | * Local prototypes |
74 | | *****************************************************************************/ |
75 | | static int DecodeBlock( decoder_t *, block_t * ); |
76 | | static void Flush( decoder_t * ); |
77 | | |
78 | | /* */ |
79 | | typedef struct |
80 | | { |
81 | | vlc_tick_t i_last_pts; |
82 | | vlc_tick_t i_max_stop; |
83 | | |
84 | | /* The following fields of decoder_sys_t are shared between decoder and spu units */ |
85 | | vlc_mutex_t lock; |
86 | | int i_refcount; |
87 | | |
88 | | /* */ |
89 | | ASS_Library *p_library; |
90 | | ASS_Renderer *p_renderer; |
91 | | |
92 | | /* */ |
93 | | ASS_Track *p_track; |
94 | | } decoder_sys_t; |
95 | | static void DecSysRelease( decoder_sys_t *p_sys ); |
96 | | static void DecSysHold( decoder_sys_t *p_sys ); |
97 | | |
98 | | /* */ |
99 | | static void SubpictureUpdate( subpicture_t *, const struct vlc_spu_updater_configuration * ); |
100 | | static void SubpictureDestroy( subpicture_t * ); |
101 | | |
102 | | typedef struct |
103 | | { |
104 | | decoder_sys_t *p_dec_sys; |
105 | | vlc_tick_t i_pts; |
106 | | } libass_spu_updater_sys_t; |
107 | | |
108 | | typedef struct |
109 | | { |
110 | | int x0; |
111 | | int y0; |
112 | | int x1; |
113 | | int y1; |
114 | | } rectangle_t; |
115 | | |
116 | | static int BuildRegions( rectangle_t *p_region, int i_max_region, ASS_Image *p_img_list, int i_width, int i_height ); |
117 | | static void RegionDraw( subpicture_region_t *p_region, ASS_Image *p_img ); |
118 | | static void OldEngineClunkyRollInfoPatch( decoder_t *p_dec, ASS_Track * ); |
119 | | |
120 | | //#define DEBUG_REGION |
121 | | |
122 | 134k | static void vlc_ass_msg_handler(int level, const char *fmt, va_list va, void *data) { |
123 | 134k | decoder_t *p_dec = data; |
124 | 134k | msg_GenericVa( p_dec, VLC_MSG_DBG, fmt, va ); |
125 | 134k | } |
126 | | |
127 | | /***************************************************************************** |
128 | | * Create: Open libass decoder. |
129 | | *****************************************************************************/ |
130 | | static int Create( vlc_object_t *p_this ) |
131 | 27.9k | { |
132 | 27.9k | decoder_t *p_dec = (decoder_t *)p_this; |
133 | 27.9k | decoder_sys_t *p_sys; |
134 | | |
135 | 27.9k | if( p_dec->fmt_in->i_codec != VLC_CODEC_SSA ) |
136 | 25.4k | return VLC_EGENERIC; |
137 | | |
138 | 2.53k | p_dec->pf_decode = DecodeBlock; |
139 | 2.53k | p_dec->pf_flush = Flush; |
140 | | |
141 | 2.53k | p_dec->p_sys = p_sys = malloc( sizeof( decoder_sys_t ) ); |
142 | 2.53k | if( !p_sys ) |
143 | 0 | return VLC_ENOMEM; |
144 | | |
145 | | /* */ |
146 | 2.53k | vlc_mutex_init( &p_sys->lock ); |
147 | 2.53k | p_sys->i_refcount = 1; |
148 | 2.53k | p_sys->i_last_pts = VLC_TICK_INVALID; |
149 | 2.53k | p_sys->i_max_stop = VLC_TICK_INVALID; |
150 | 2.53k | p_sys->p_library = NULL; |
151 | 2.53k | p_sys->p_renderer = NULL; |
152 | 2.53k | p_sys->p_track = NULL; |
153 | | |
154 | | /* Create libass library */ |
155 | 2.53k | ASS_Library *p_library = p_sys->p_library = ass_library_init(); |
156 | 2.53k | if( !p_library ) |
157 | 0 | { |
158 | 0 | msg_Warn( p_dec, "Libass library creation failed" ); |
159 | 0 | DecSysRelease( p_sys ); |
160 | 0 | return VLC_EGENERIC; |
161 | 0 | } |
162 | | |
163 | 2.53k | ass_set_message_cb(p_library, vlc_ass_msg_handler, p_dec); |
164 | | |
165 | | /* load attachments */ |
166 | 2.53k | input_attachment_t **pp_attachments; |
167 | 2.53k | int i_attachments; |
168 | 2.53k | if( decoder_GetInputAttachments( p_dec, &pp_attachments, &i_attachments )) |
169 | 2.53k | { |
170 | 2.53k | i_attachments = 0; |
171 | 2.53k | pp_attachments = NULL; |
172 | 2.53k | } |
173 | 2.53k | for( int k = 0; k < i_attachments; k++ ) |
174 | 0 | { |
175 | 0 | input_attachment_t *p_attach = pp_attachments[k]; |
176 | |
|
177 | 0 | bool found = false; |
178 | | |
179 | | /* Check mimetype*/ |
180 | 0 | if( !strcasecmp( p_attach->psz_mime, "application/x-truetype-font" ) ) |
181 | 0 | found = true; |
182 | | /* Then extension */ |
183 | 0 | else if( !found && strnlen( p_attach->psz_name, 4+1 ) > 4 ) |
184 | 0 | { |
185 | 0 | char *ext = p_attach->psz_name + strlen( p_attach->psz_name ) - 4; |
186 | |
|
187 | 0 | if( !strcasecmp( ext, ".ttf" ) || !strcasecmp( ext, ".otf" ) || !strcasecmp( ext, ".ttc" ) ) |
188 | 0 | found = true; |
189 | 0 | } |
190 | |
|
191 | 0 | if( found ) |
192 | 0 | { |
193 | 0 | msg_Dbg( p_dec, "adding embedded font %s", p_attach->psz_name ); |
194 | |
|
195 | 0 | ass_add_font( p_sys->p_library, p_attach->psz_name, p_attach->p_data, p_attach->i_data ); |
196 | 0 | } |
197 | 0 | vlc_input_attachment_Release( p_attach ); |
198 | 0 | } |
199 | 2.53k | free( pp_attachments ); |
200 | | |
201 | 2.53k | char *psz_fontsdir = var_InheritString( p_dec, "ssa-fontsdir" ); |
202 | 2.53k | if( psz_fontsdir ) |
203 | 0 | { |
204 | 0 | ass_set_fonts_dir( p_library, psz_fontsdir ); |
205 | 0 | free( psz_fontsdir ); |
206 | 0 | } |
207 | | |
208 | 2.53k | ass_set_extract_fonts( p_library, true ); |
209 | 2.53k | ass_set_style_overrides( p_library, NULL ); |
210 | | |
211 | | /* Create the renderer */ |
212 | 2.53k | ASS_Renderer *p_renderer = p_sys->p_renderer = ass_renderer_init( p_library ); |
213 | 2.53k | if( !p_renderer ) |
214 | 0 | { |
215 | 0 | msg_Warn( p_dec, "Libass renderer creation failed" ); |
216 | 0 | DecSysRelease( p_sys ); |
217 | 0 | return VLC_EGENERIC; |
218 | 0 | } |
219 | | |
220 | 2.53k | ass_set_use_margins( p_renderer, false); |
221 | | //if( false ) |
222 | | // ass_set_margins( p_renderer, int t, int b, int l, int r); |
223 | 2.53k | ass_set_font_scale( p_renderer, 1.0 ); |
224 | 2.53k | ass_set_line_spacing( p_renderer, 0.0 ); |
225 | | |
226 | | #if defined( __ANDROID__ ) |
227 | | const char *psz_font, *psz_family; |
228 | | const char *psz_font_droid = "/system/fonts/DroidSans-Bold.ttf"; |
229 | | const char *psz_family_droid = "Droid Sans Bold"; |
230 | | const char *psz_font_noto = "/system/fonts/NotoSansCJK-Regular.ttc"; |
231 | | const char *psz_family_noto = "Noto Sans"; |
232 | | |
233 | | // Workaround for Android 5.0+, since libass doesn't parse the XML yet |
234 | | if( access( psz_font_noto, R_OK ) != -1 ) |
235 | | { |
236 | | psz_font = psz_font_noto; |
237 | | psz_family = psz_family_noto; |
238 | | } |
239 | | else |
240 | | { |
241 | | psz_font = psz_font_droid; |
242 | | psz_family = psz_family_droid; |
243 | | } |
244 | | |
245 | | #elif defined( __APPLE__ ) |
246 | | const char *psz_font = NULL; /* We don't ship a default font with VLC */ |
247 | | const char *psz_family = "Helvetica Neue"; /* Use HN if we can't find anything more suitable - Arial is not on all Apple platforms */ |
248 | | #else |
249 | 2.53k | const char *psz_font = NULL; /* We don't ship a default font with VLC */ |
250 | 2.53k | const char *psz_family = "Arial"; /* Use Arial if we can't find anything more suitable */ |
251 | 2.53k | #endif |
252 | | |
253 | 2.53k | #ifdef HAVE_FONTCONFIG |
254 | | #if defined(_WIN32) |
255 | | vlc_dialog_id *p_dialog_id = |
256 | | vlc_dialog_display_progress( p_dec, true, 0.0, NULL, |
257 | | _("Building font cache"), |
258 | | _( "Please wait while your font cache is rebuilt.\n" |
259 | | "This should take less than a minute." ) ); |
260 | | #endif |
261 | 2.53k | ass_set_fonts( p_renderer, psz_font, psz_family, ASS_FONTPROVIDER_AUTODETECT, NULL, 1 ); // setup default font/family |
262 | | #if defined(_WIN32) |
263 | | if( p_dialog_id != 0 ) |
264 | | vlc_dialog_release( p_dec, p_dialog_id ); |
265 | | #endif |
266 | | #else |
267 | | ass_set_fonts( p_renderer, psz_font, psz_family, ASS_FONTPROVIDER_AUTODETECT, NULL, 0 ); |
268 | | #endif |
269 | | |
270 | | /* Anything else than NONE will break smooth img updating. |
271 | | TODO: List and force ASS_HINTING_LIGHT for known problematic fonts */ |
272 | 2.53k | ass_set_hinting( p_renderer, ASS_HINTING_NONE ); |
273 | | |
274 | | /* Add a track */ |
275 | 2.53k | ASS_Track *p_track = p_sys->p_track = ass_new_track( p_sys->p_library ); |
276 | 2.53k | if( !p_track ) |
277 | 0 | { |
278 | 0 | DecSysRelease( p_sys ); |
279 | 0 | return VLC_EGENERIC; |
280 | 0 | } |
281 | 2.53k | ass_process_codec_private( p_track, p_dec->fmt_in->p_extra, p_dec->fmt_in->i_extra ); |
282 | 2.53k | OldEngineClunkyRollInfoPatch( p_dec, p_track ); |
283 | | |
284 | 2.53k | p_dec->fmt_out.i_codec = VLC_CODEC_RGBA; |
285 | | |
286 | 2.53k | return VLC_SUCCESS; |
287 | 2.53k | } |
288 | | |
289 | | /***************************************************************************** |
290 | | * Destroy: finish |
291 | | *****************************************************************************/ |
292 | | static void Destroy( vlc_object_t *p_this ) |
293 | 2.53k | { |
294 | 2.53k | decoder_t *p_dec = (decoder_t *)p_this; |
295 | | |
296 | 2.53k | DecSysRelease( p_dec->p_sys ); |
297 | 2.53k | } |
298 | | |
299 | | static void DecSysHold( decoder_sys_t *p_sys ) |
300 | 1.03k | { |
301 | 1.03k | vlc_mutex_lock( &p_sys->lock ); |
302 | 1.03k | p_sys->i_refcount++; |
303 | 1.03k | vlc_mutex_unlock( &p_sys->lock ); |
304 | 1.03k | } |
305 | | static void DecSysRelease( decoder_sys_t *p_sys ) |
306 | 3.57k | { |
307 | | /* */ |
308 | 3.57k | vlc_mutex_lock( &p_sys->lock ); |
309 | 3.57k | p_sys->i_refcount--; |
310 | 3.57k | if( p_sys->i_refcount > 0 ) |
311 | 1.03k | { |
312 | 1.03k | vlc_mutex_unlock( &p_sys->lock ); |
313 | 1.03k | return; |
314 | 1.03k | } |
315 | 2.53k | vlc_mutex_unlock( &p_sys->lock ); |
316 | | |
317 | 2.53k | if( p_sys->p_track ) |
318 | 2.53k | ass_free_track( p_sys->p_track ); |
319 | 2.53k | if( p_sys->p_renderer ) |
320 | 2.53k | ass_renderer_done( p_sys->p_renderer ); |
321 | 2.53k | if( p_sys->p_library ) |
322 | 2.53k | ass_library_done( p_sys->p_library ); |
323 | | |
324 | 2.53k | free( p_sys ); |
325 | 2.53k | } |
326 | | |
327 | | /***************************************************************************** |
328 | | * Flush: |
329 | | *****************************************************************************/ |
330 | | static void Flush( decoder_t *p_dec ) |
331 | 0 | { |
332 | 0 | decoder_sys_t *p_sys = p_dec->p_sys; |
333 | |
|
334 | 0 | p_sys->i_max_stop = VLC_TICK_INVALID; |
335 | 0 | p_sys->i_last_pts = VLC_TICK_INVALID; |
336 | 0 | } |
337 | | |
338 | | /**************************************************************************** |
339 | | * DecodeBlock: |
340 | | ****************************************************************************/ |
341 | | static int DecodeBlock( decoder_t *p_dec, block_t *p_block ) |
342 | 42.7k | { |
343 | 42.7k | decoder_sys_t *p_sys = p_dec->p_sys; |
344 | | |
345 | 42.7k | subpicture_t *p_spu = NULL; |
346 | | |
347 | 42.7k | if( p_block == NULL ) /* No Drain */ |
348 | 22.6k | return VLCDEC_SUCCESS; |
349 | | |
350 | 20.0k | if( p_block->i_flags & BLOCK_FLAG_CORRUPTED ) |
351 | 0 | { |
352 | 0 | Flush( p_dec ); |
353 | 0 | block_Release( p_block ); |
354 | 0 | return VLCDEC_SUCCESS; |
355 | 0 | } |
356 | | |
357 | 20.0k | if( p_block->i_buffer == 0 || p_block->p_buffer[0] == '\0' ) |
358 | 0 | { |
359 | 0 | block_Release( p_block ); |
360 | 0 | return VLCDEC_SUCCESS; |
361 | 0 | } |
362 | | |
363 | 20.0k | if( p_block->i_pts != p_sys->i_last_pts ) |
364 | 1.03k | { |
365 | 1.03k | libass_spu_updater_sys_t *p_spu_sys = malloc( sizeof(*p_spu_sys) ); |
366 | 1.03k | if( !p_spu_sys ) |
367 | 0 | { |
368 | 0 | block_Release( p_block ); |
369 | 0 | return VLCDEC_SUCCESS; |
370 | 0 | } |
371 | | |
372 | 1.03k | static const struct vlc_spu_updater_ops spu_ops = |
373 | 1.03k | { |
374 | 1.03k | .update = SubpictureUpdate, |
375 | 1.03k | .destroy = SubpictureDestroy, |
376 | 1.03k | }; |
377 | | |
378 | 1.03k | subpicture_updater_t updater = { |
379 | 1.03k | .sys = p_spu_sys, |
380 | 1.03k | .ops = &spu_ops, |
381 | 1.03k | }; |
382 | | |
383 | 1.03k | p_spu = decoder_NewSubpicture( p_dec, &updater ); |
384 | 1.03k | if( !p_spu ) |
385 | 0 | { |
386 | 0 | msg_Warn( p_dec, "can't get spu buffer" ); |
387 | 0 | free( p_spu_sys ); |
388 | 0 | block_Release( p_block ); |
389 | 0 | return VLCDEC_SUCCESS; |
390 | 0 | } |
391 | | |
392 | 1.03k | p_spu_sys->p_dec_sys = p_sys; |
393 | 1.03k | p_spu_sys->i_pts = p_block->i_pts; |
394 | 1.03k | p_spu->i_start = p_block->i_pts; |
395 | 1.03k | p_spu->i_stop = __MAX( p_sys->i_max_stop, p_block->i_pts + p_block->i_length ); |
396 | 1.03k | p_spu->b_ephemer = true; |
397 | | |
398 | 1.03k | p_sys->i_max_stop = p_spu->i_stop; |
399 | | |
400 | 1.03k | DecSysHold( p_sys ); /* Keep a reference for the returned subpicture */ |
401 | 1.03k | } |
402 | | |
403 | 20.0k | p_sys->i_last_pts = p_block->i_pts; |
404 | | |
405 | 20.0k | vlc_mutex_lock( &p_sys->lock ); |
406 | 20.0k | if( p_sys->p_track ) |
407 | 20.0k | { |
408 | 20.0k | ass_process_chunk( p_sys->p_track,(void *) p_block->p_buffer, p_block->i_buffer, |
409 | 20.0k | MS_FROM_VLC_TICK( p_block->i_pts ), MS_FROM_VLC_TICK( p_block->i_length ) ); |
410 | 20.0k | } |
411 | 20.0k | vlc_mutex_unlock( &p_sys->lock ); |
412 | | |
413 | 20.0k | block_Release( p_block ); |
414 | | |
415 | 20.0k | if( p_spu ) |
416 | 1.03k | decoder_QueueSub( p_dec, p_spu ); |
417 | | |
418 | 20.0k | return VLCDEC_SUCCESS; |
419 | 20.0k | } |
420 | | |
421 | | /**************************************************************************** |
422 | | * |
423 | | ****************************************************************************/ |
424 | | static void SubpictureUpdate( subpicture_t *p_subpic, |
425 | | const struct vlc_spu_updater_configuration *cfg ) |
426 | 0 | { |
427 | 0 | libass_spu_updater_sys_t *p_spusys = p_subpic->updater.sys; |
428 | 0 | decoder_sys_t *p_sys = p_spusys->p_dec_sys; |
429 | 0 | const video_format_t *p_fmt_src = cfg->current.video_src; |
430 | 0 | const video_format_t *p_fmt_dst = cfg->current.video_dst; |
431 | |
|
432 | 0 | bool b_fmt_src = p_fmt_src->i_visible_width != cfg->previous.video_src->i_visible_width || |
433 | 0 | p_fmt_src->i_visible_height != cfg->previous.video_src->i_visible_height; |
434 | 0 | bool b_fmt_dst = !video_format_IsSimilar(cfg->previous.video_dst, p_fmt_dst); |
435 | |
|
436 | 0 | vlc_mutex_lock( &p_sys->lock ); |
437 | |
|
438 | 0 | if( b_fmt_src || b_fmt_dst ) |
439 | 0 | { |
440 | 0 | ass_set_frame_size( p_sys->p_renderer, p_fmt_dst->i_visible_width, p_fmt_dst->i_visible_height ); |
441 | 0 | #if LIBASS_VERSION > 0x01010000 |
442 | 0 | ass_set_storage_size( p_sys->p_renderer, p_fmt_src->i_visible_width, p_fmt_src->i_visible_height ); |
443 | 0 | #endif |
444 | 0 | const double src_ratio = (double)p_fmt_src->i_visible_width / p_fmt_src->i_visible_height; |
445 | 0 | const double dst_ratio = (double)p_fmt_dst->i_visible_width / p_fmt_dst->i_visible_height; |
446 | 0 | #if LIBASS_VERSION >= 0x01020000 |
447 | 0 | ass_set_pixel_aspect( p_sys->p_renderer, dst_ratio / src_ratio ); |
448 | | #else |
449 | | ass_set_aspect_ratio( p_sys->p_renderer, dst_ratio / src_ratio, 1 ); |
450 | | #endif |
451 | 0 | } |
452 | | |
453 | | /* */ |
454 | 0 | const vlc_tick_t i_stream_date = p_spusys->i_pts + (cfg->pts - p_subpic->i_start); |
455 | 0 | int i_changed; |
456 | 0 | ASS_Image *p_img = ass_render_frame( p_sys->p_renderer, p_sys->p_track, |
457 | 0 | MS_FROM_VLC_TICK( i_stream_date ), &i_changed ); |
458 | |
|
459 | 0 | if( !i_changed && !b_fmt_src && !b_fmt_dst && |
460 | 0 | (p_img != NULL) == (!vlc_spu_regions_is_empty(&p_subpic->regions)) ) |
461 | 0 | { |
462 | 0 | vlc_mutex_unlock( &p_sys->lock ); |
463 | 0 | return; |
464 | 0 | } |
465 | | |
466 | 0 | vlc_spu_regions_Clear( &p_subpic->regions ); |
467 | | |
468 | | /* */ |
469 | 0 | p_subpic->i_original_picture_height = p_fmt_dst->i_visible_height; |
470 | 0 | p_subpic->i_original_picture_width = p_fmt_dst->i_visible_width; |
471 | | |
472 | | /* XXX to improve efficiency we merge regions that are close minimizing |
473 | | * the lost surface. |
474 | | * libass tends to create a lot of small regions and thus spu engine |
475 | | * reinstanciate a lot the scaler, and as we do not support subpel blending |
476 | | * it looks ugly (text unaligned). |
477 | | */ |
478 | 0 | rectangle_t region[4]; |
479 | 0 | const int i_region = BuildRegions( region, ARRAY_SIZE(region), p_img, p_fmt_dst->i_width, p_fmt_dst->i_height ); |
480 | |
|
481 | 0 | if( i_region <= 0 ) |
482 | 0 | { |
483 | 0 | vlc_mutex_unlock( &p_sys->lock ); |
484 | 0 | return; |
485 | 0 | } |
486 | | |
487 | | /* Allocate the regions and draw them */ |
488 | 0 | video_format_t fmt_region; |
489 | 0 | fmt_region = *p_fmt_dst; |
490 | 0 | fmt_region.i_chroma = VLC_CODEC_RGBA; |
491 | 0 | fmt_region.i_x_offset = 0; |
492 | 0 | fmt_region.i_y_offset = 0; |
493 | 0 | fmt_region.transfer = TRANSFER_FUNC_SRGB; |
494 | 0 | fmt_region.primaries = COLOR_PRIMARIES_SRGB; |
495 | 0 | fmt_region.space = COLOR_SPACE_SRGB; |
496 | 0 | fmt_region.color_range = COLOR_RANGE_FULL; |
497 | 0 | for( int i = 0; i < i_region; i++ ) |
498 | 0 | { |
499 | 0 | subpicture_region_t *r; |
500 | | |
501 | | /* */ |
502 | 0 | fmt_region.i_width = |
503 | 0 | fmt_region.i_visible_width = region[i].x1 - region[i].x0; |
504 | 0 | fmt_region.i_height = |
505 | 0 | fmt_region.i_visible_height = region[i].y1 - region[i].y0; |
506 | |
|
507 | 0 | r = subpicture_region_New( &fmt_region ); |
508 | 0 | if( !r ) |
509 | 0 | break; |
510 | 0 | r->b_absolute = true; r->b_in_window = false; |
511 | 0 | r->i_x = region[i].x0; |
512 | 0 | r->i_y = region[i].y0; |
513 | 0 | r->i_align = SUBPICTURE_ALIGN_TOP | SUBPICTURE_ALIGN_LEFT; |
514 | | |
515 | | /* */ |
516 | 0 | RegionDraw( r, p_img ); |
517 | | |
518 | | /* */ |
519 | 0 | vlc_spu_regions_push(&p_subpic->regions, r); |
520 | 0 | } |
521 | 0 | vlc_mutex_unlock( &p_sys->lock ); |
522 | |
|
523 | 0 | } |
524 | | static void SubpictureDestroy( subpicture_t *p_subpic ) |
525 | 1.03k | { |
526 | 1.03k | libass_spu_updater_sys_t *p_spusys = p_subpic->updater.sys; |
527 | | |
528 | 1.03k | DecSysRelease( p_spusys->p_dec_sys ); |
529 | 1.03k | free( p_spusys ); |
530 | 1.03k | } |
531 | | |
532 | | static rectangle_t r_create( int x0, int y0, int x1, int y1 ) |
533 | 0 | { |
534 | 0 | rectangle_t r = { x0, y0, x1, y1 }; |
535 | 0 | return r; |
536 | 0 | } |
537 | | static rectangle_t r_img( const ASS_Image *p_img ) |
538 | 0 | { |
539 | 0 | return r_create( p_img->dst_x, p_img->dst_y, p_img->dst_x+p_img->w, p_img->dst_y+p_img->h ); |
540 | 0 | } |
541 | | static void r_add( rectangle_t *r, const rectangle_t *n ) |
542 | 0 | { |
543 | 0 | r->x0 = __MIN( r->x0, n->x0 ); |
544 | 0 | r->y0 = __MIN( r->y0, n->y0 ); |
545 | 0 | r->x1 = __MAX( r->x1, n->x1 ); |
546 | 0 | r->y1 = __MAX( r->y1, n->y1 ); |
547 | 0 | } |
548 | | static int r_surface( const rectangle_t *r ) |
549 | 0 | { |
550 | 0 | return (r->x1-r->x0) * (r->y1-r->y0); |
551 | 0 | } |
552 | | static bool r_overlap( const rectangle_t *a, const rectangle_t *b, int i_dx, int i_dy ) |
553 | 0 | { |
554 | 0 | return __MAX(a->x0-i_dx, b->x0) < __MIN( a->x1+i_dx, b->x1 ) && |
555 | 0 | __MAX(a->y0-i_dy, b->y0) < __MIN( a->y1+i_dy, b->y1 ); |
556 | 0 | } |
557 | | |
558 | | static int BuildRegions( rectangle_t *p_region, int i_max_region, ASS_Image *p_img_list, int i_width, int i_height ) |
559 | 0 | { |
560 | 0 | ASS_Image *p_tmp; |
561 | 0 | int i_count; |
562 | |
|
563 | | #ifdef DEBUG_REGION |
564 | | int64_t i_ck_start = vlc_tick_now(); |
565 | | #endif |
566 | |
|
567 | 0 | for( p_tmp = p_img_list, i_count = 0; p_tmp != NULL; p_tmp = p_tmp->next ) |
568 | 0 | if( p_tmp->w > 0 && p_tmp->h > 0 ) |
569 | 0 | i_count++; |
570 | 0 | if( i_count <= 0 ) |
571 | 0 | return 0; |
572 | | |
573 | 0 | ASS_Image **pp_img = calloc( i_count, sizeof(*pp_img) ); |
574 | 0 | if( !pp_img ) |
575 | 0 | return 0; |
576 | | |
577 | 0 | for( p_tmp = p_img_list, i_count = 0; p_tmp != NULL; p_tmp = p_tmp->next ) |
578 | 0 | if( p_tmp->w > 0 && p_tmp->h > 0 ) |
579 | 0 | pp_img[i_count++] = p_tmp; |
580 | | |
581 | | /* */ |
582 | 0 | const int i_w_inc = __MAX( ( i_width + 49 ) / 50, 32 ); |
583 | 0 | const int i_h_inc = __MAX( ( i_height + 99 ) / 100, 32 ); |
584 | 0 | int i_maxh = i_w_inc; |
585 | 0 | int i_maxw = i_h_inc; |
586 | 0 | int i_region; |
587 | 0 | rectangle_t region[i_max_region+1]; |
588 | |
|
589 | 0 | i_region = 0; |
590 | 0 | for( int i_used = 0; i_used < i_count; ) |
591 | 0 | { |
592 | 0 | int n; |
593 | 0 | for( n = 0; n < i_count; n++ ) |
594 | 0 | { |
595 | 0 | if( pp_img[n] ) |
596 | 0 | break; |
597 | 0 | } |
598 | 0 | assert( i_region < i_max_region + 1 ); |
599 | 0 | region[i_region++] = r_img( pp_img[n] ); |
600 | 0 | pp_img[n] = NULL; i_used++; |
601 | |
|
602 | 0 | bool b_ok; |
603 | 0 | do { |
604 | 0 | b_ok = false; |
605 | 0 | for( n = 0; n < i_count; n++ ) |
606 | 0 | { |
607 | 0 | ASS_Image *p_img = pp_img[n]; |
608 | 0 | if( !p_img ) |
609 | 0 | continue; |
610 | 0 | rectangle_t r = r_img( p_img ); |
611 | |
|
612 | 0 | int k; |
613 | 0 | int i_best = -1; |
614 | 0 | int i_best_s = INT_MAX; |
615 | 0 | for( k = 0; k < i_region; k++ ) |
616 | 0 | { |
617 | 0 | if( !r_overlap( ®ion[k], &r, i_maxw, i_maxh ) ) |
618 | 0 | continue; |
619 | 0 | int s = r_surface( &r ); |
620 | 0 | if( s < i_best_s ) |
621 | 0 | { |
622 | 0 | i_best_s = s; |
623 | 0 | i_best = k; |
624 | 0 | } |
625 | 0 | } |
626 | 0 | if( i_best >= 0 ) |
627 | 0 | { |
628 | 0 | r_add( ®ion[i_best], &r ); |
629 | 0 | pp_img[n] = NULL; i_used++; |
630 | 0 | b_ok = true; |
631 | 0 | } |
632 | 0 | } |
633 | 0 | } while( b_ok ); |
634 | |
|
635 | 0 | if( i_region > i_max_region ) |
636 | 0 | { |
637 | 0 | int i_best_i = -1; |
638 | 0 | int i_best_j = -1; |
639 | 0 | int i_best_ds = INT_MAX; |
640 | | |
641 | | /* merge best */ |
642 | 0 | for( int i = 0; i < i_region; i++ ) |
643 | 0 | { |
644 | 0 | for( int j = i+1; j < i_region; j++ ) |
645 | 0 | { |
646 | 0 | rectangle_t rect = region[i]; |
647 | 0 | r_add( &rect, ®ion[j] ); |
648 | 0 | int ds = r_surface( &rect ) - r_surface( ®ion[i] ) - r_surface( ®ion[j] ); |
649 | |
|
650 | 0 | if( ds < i_best_ds ) |
651 | 0 | { |
652 | 0 | i_best_i = i; |
653 | 0 | i_best_j = j; |
654 | 0 | i_best_ds = ds; |
655 | 0 | } |
656 | 0 | } |
657 | 0 | } |
658 | | #ifdef DEBUG_REGION |
659 | | msg_Err( p_spu, "Merging %d and %d", i_best_i, i_best_j ); |
660 | | #endif |
661 | 0 | if( i_best_j >= 0 && i_best_i >= 0 ) |
662 | 0 | { |
663 | 0 | r_add( ®ion[i_best_i], ®ion[i_best_j] ); |
664 | |
|
665 | 0 | if( i_best_j+1 < i_region ) |
666 | 0 | memmove( ®ion[i_best_j], ®ion[i_best_j+1], sizeof(*region) * ( i_region - (i_best_j+1) ) ); |
667 | 0 | i_region--; |
668 | 0 | } |
669 | 0 | } |
670 | 0 | } |
671 | | |
672 | | /* */ |
673 | 0 | for( int n = 0; n < i_region; n++ ) |
674 | 0 | p_region[n] = region[n]; |
675 | |
|
676 | | #ifdef DEBUG_REGION |
677 | | int64_t i_ck_time = vlc_tick_now() - i_ck_start; |
678 | | msg_Err( p_spu, "ASS: %d objects merged into %d region in %d micros", i_count, i_region, (int)(i_ck_time) ); |
679 | | #endif |
680 | |
|
681 | 0 | free( pp_img ); |
682 | |
|
683 | 0 | return i_region; |
684 | 0 | } |
685 | | |
686 | | static void RegionDraw( subpicture_region_t *p_region, ASS_Image *p_img ) |
687 | 0 | { |
688 | 0 | const plane_t *p = &p_region->p_picture->p[0]; |
689 | 0 | const int i_x = p_region->i_x; |
690 | 0 | const int i_y = p_region->i_y; |
691 | 0 | const int i_width = p_region->p_picture->format.i_width; |
692 | 0 | const int i_height = p_region->p_picture->format.i_height; |
693 | |
|
694 | 0 | memset( p->p_pixels, 0x00, p->i_pitch * p->i_visible_lines ); |
695 | 0 | for( ; p_img != NULL; p_img = p_img->next ) |
696 | 0 | { |
697 | 0 | int i_dst_x = p_img->dst_x - i_x; |
698 | 0 | int i_dst_y = p_img->dst_y - i_y; |
699 | 0 | if( i_dst_x < 0 || i_dst_x + p_img->w > i_width || |
700 | 0 | i_dst_y < 0 || i_dst_y + p_img->h > i_height ) |
701 | 0 | continue; |
702 | | |
703 | | /* /!\ Bogus alpha channel is inverted */ |
704 | 0 | const unsigned a = (~p_img->color )&0xff; |
705 | 0 | if( a == 0 ) |
706 | 0 | continue; |
707 | | |
708 | 0 | const unsigned r = (p_img->color >> 24)&0xff; |
709 | 0 | const unsigned g = (p_img->color >> 16)&0xff; |
710 | 0 | const unsigned b = (p_img->color >> 8)&0xff; |
711 | |
|
712 | 0 | const uint8_t *srcrow = p_img->bitmap; |
713 | 0 | int i_pitch_src = p_img->stride; |
714 | 0 | int i_pitch_dst = p->i_pitch; |
715 | 0 | uint8_t *dstrow = &p->p_pixels[i_dst_y * i_pitch_dst + 4 * i_dst_x]; |
716 | |
|
717 | 0 | for( int y = 0; y < p_img->h; y++ ) |
718 | 0 | { |
719 | 0 | const uint8_t *src = srcrow; |
720 | 0 | uint8_t *dst = dstrow; |
721 | |
|
722 | 0 | for( int x = 0; x < p_img->w; x++ ) |
723 | 0 | { |
724 | 0 | unsigned opacity = *src++; /* 1 Bpp channel */ |
725 | 0 | if( opacity != 0 ) /* we need to blend only non transparent content */ |
726 | 0 | { |
727 | 0 | unsigned i_an = a * opacity / 255U; |
728 | 0 | unsigned i_ao = dst[3]; |
729 | 0 | if( i_ao == 0 ) |
730 | 0 | { |
731 | 0 | dst[0] = r; |
732 | 0 | dst[1] = g; |
733 | 0 | dst[2] = b; |
734 | 0 | dst[3] = i_an; |
735 | 0 | } |
736 | 0 | else |
737 | 0 | { |
738 | 0 | unsigned i_ani = 255 - i_an; |
739 | 0 | dst[3] = 255 - (255 - i_ao) * i_ani / 255; |
740 | 0 | if( dst[3] != 0 ) |
741 | 0 | { |
742 | 0 | unsigned i_aoni = i_ao * i_ani / 255; |
743 | 0 | dst[0] = ( dst[0] * i_aoni + r * i_an ) / dst[3]; |
744 | 0 | dst[1] = ( dst[1] * i_aoni + g * i_an ) / dst[3]; |
745 | 0 | dst[2] = ( dst[2] * i_aoni + b * i_an ) / dst[3]; |
746 | 0 | } |
747 | 0 | } |
748 | 0 | } |
749 | 0 | dst += 4; |
750 | 0 | } |
751 | 0 | srcrow += i_pitch_src; |
752 | 0 | dstrow += i_pitch_dst; |
753 | 0 | } |
754 | 0 | } |
755 | |
|
756 | | #ifdef DEBUG_REGION |
757 | | /* XXX Draw a box for debug */ |
758 | | #define P(x,y) ((uint32_t*)&p->p_pixels[(y)*p->i_pitch + 4*(x)]) |
759 | | for( int y = 0; y < p->i_lines; y++ ) |
760 | | *P(0,y) = *P(p->i_visible_pitch/4-1,y) = 0xff000000; |
761 | | for( int x = 0; x < p->i_visible_pitch; x++ ) |
762 | | *P(x/4,0) = *P(x/4,p->i_visible_lines-1) = 0xff000000; |
763 | | #undef P |
764 | | #endif |
765 | 0 | } |
766 | | |
767 | | /* Patch [Script Info] aiming old and custom rendering engine |
768 | | * See #27771 */ |
769 | | static void OldEngineClunkyRollInfoPatch( decoder_t *p_dec, ASS_Track *p_track ) |
770 | 2.53k | { |
771 | 2.53k | if( !p_dec->fmt_in->i_extra ) |
772 | 17 | return; |
773 | | |
774 | 2.52k | stream_t *p_memstream = vlc_stream_MemoryNew( p_dec, p_dec->fmt_in->p_extra, |
775 | 2.52k | p_dec->fmt_in->i_extra, true ); |
776 | 2.52k | if (unlikely(!p_memstream)) |
777 | 0 | return; |
778 | | |
779 | 2.52k | char *s = vlc_stream_ReadLine( p_memstream ); |
780 | 2.52k | unsigned playres[2] = {0, 0}; |
781 | 2.52k | bool b_hotfix = false; |
782 | 2.52k | if( s && !strncmp( s, "[Script Info]", 13 ) ) |
783 | 1.18k | { |
784 | 1.18k | free( s ); |
785 | 1.18k | for( ;; ) |
786 | 23.6k | { |
787 | 23.6k | s = vlc_stream_ReadLine( p_memstream ); |
788 | 23.6k | if( !s || *s == '[' /* Next section */ ) |
789 | 1.03k | { |
790 | 1.03k | break; |
791 | 1.03k | } |
792 | 22.6k | else if( !strncmp( s, "PlayResX: ", 10 ) || |
793 | 22.4k | !strncmp( s, "PlayResY: ", 10 ) ) |
794 | 280 | { |
795 | 280 | playres[s[7] - 'X'] = atoi( &s[9] ); |
796 | 280 | } |
797 | 22.3k | else if( !strncmp( s, "Original Script: ", 17 ) ) |
798 | 81 | { |
799 | 81 | b_hotfix = !!strstr( s, "[http://www.crunchyroll.com/user/" ); |
800 | 81 | if( !b_hotfix ) |
801 | 81 | break; |
802 | 81 | } |
803 | 22.2k | else if( !strncmp( s, "LayoutRes", 9 ) || |
804 | 22.2k | !strncmp( s, "ScaledBorderAndShadow:", 22 ) ) |
805 | 75 | { |
806 | | /* They can still have fixed their mess in the future. Tell me, Marty. */ |
807 | 75 | b_hotfix = false; |
808 | 75 | break; |
809 | 75 | } |
810 | 22.4k | free( s ); |
811 | 22.4k | } |
812 | 1.18k | } |
813 | 2.52k | free( s ); |
814 | 2.52k | vlc_stream_Delete( p_memstream ); |
815 | 2.52k | if( b_hotfix && playres[0] && playres[1] ) |
816 | 0 | { |
817 | 0 | msg_Dbg( p_dec,"patching script info for custom rendering engine " |
818 | 0 | "(built against libass 0x%X)", LIBASS_VERSION ); |
819 | | /* Only modify struct _before_ any ass_process_chunk calls |
820 | | (see ass_types.h documentation for when modifications are allowed) */ |
821 | 0 | p_track->ScaledBorderAndShadow = 1; |
822 | 0 | p_track->YCbCrMatrix = YCBCR_NONE; |
823 | 0 | #if LIBASS_VERSION >= 0x01600020 |
824 | 0 | p_track->LayoutResX = playres[0]; |
825 | 0 | p_track->LayoutResY = playres[1]; |
826 | 0 | #endif |
827 | 0 | } |
828 | 2.52k | } |