2
* This file is part of bino, a 3D video player.
5
* Martin Lambers <marlam@marlam.de>
6
* Joe <cuchac@email.cz>
7
* FrƩdƩric Devernay <Frederic.Devernay@inrialpes.fr>
9
* This program is free software; you can redistribute it and/or modify
10
* it under the terms of the GNU General Public License as published by
11
* the Free Software Foundation; either version 3 of the License, or
12
* (at your option) any later version.
14
* This program is distributed in the hope that it will be useful,
15
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
* GNU General Public License for more details.
19
* You should have received a copy of the GNU General Public License
20
* along with this program. If not, see <http://www.gnu.org/licenses/>.
33
#if (defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__
46
#define _(string) gettext(string)
55
#include "subtitle_renderer.h"
58
subtitle_renderer_initializer::subtitle_renderer_initializer(subtitle_renderer &renderer) :
59
_subtitle_renderer(renderer)
63
void subtitle_renderer_initializer::run()
65
_subtitle_renderer.init();
69
/* Rendering subtitles with LibASS is not thread-safe.
71
* We have multiple concurrent subtitle rendering threads if we are running from Equalizer
72
* and the Equalizer configuration contains multiple channels per node (since each channel
73
* has a video output, which in turn has a subtitle renderer).
75
* Everything works fine as long as every channel is connected to the same X11 display.
76
* But if channels are connected to different X11 displays, the application crashes.
77
* The culprit here seems to be the freetype library, as this library causes the same
78
* trouble in other applications that are not related to LibASS.
80
* Anyway, to fix this we use one big global lock around LibASS calls.
82
static mutex global_libass_mutex;
84
subtitle_renderer::subtitle_renderer() :
87
_fontconfig_conffile(NULL),
95
subtitle_renderer::~subtitle_renderer()
97
if (_initializer.is_running())
101
_initializer.cancel();
109
ass_free_track(_ass_track);
113
ass_renderer_done(_ass_renderer);
117
ass_library_done(_ass_library);
119
if (_fontconfig_conffile)
121
(void)std::remove(_fontconfig_conffile);
125
static void libass_msg_callback(int level, const char *fmt, va_list args, void *)
127
msg::level_t msg_levels[8] = { msg::ERR, msg::ERR, msg::WRN, msg::WRN, msg::WRN, msg::INF, msg::DBG, msg::DBG };
128
msg::level_t l = (level < 0 || level > 7 ? msg::ERR : msg_levels[level]);
129
std::string s = str::vasprintf(fmt, args);
130
msg::msg(l, std::string("LibASS: ") + s);
133
const char *subtitle_renderer::get_fontconfig_conffile()
135
#if ((defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__) || defined __APPLE__
136
/* Fontconfig annoyingly requires a configuration file, but there is no
137
* default location for it on Windows or Mac OS X. So we just create a temporary one.
138
* Note that if something goes wrong and we return NULL here, the application will
139
* likely crash when trying to render a subtitle. */
141
# if ((defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__)
142
/* Note that due to lack of mkstemp() on Windows, this code has race conditions. */
143
char tmppathname[MAX_PATH];
144
static char tmpfilename[MAX_PATH];
146
ret = GetTempPath(MAX_PATH, tmppathname);
147
if (ret > MAX_PATH || ret == 0)
149
if (GetTempFileName(tmppathname, "fonts_conf", 0, tmpfilename) == 0)
151
if (!(f = fopen(tmpfilename, "w")))
153
# else /* __APPLE__ */
154
static char tmpfilename[] = "/tmp/fontsXXXXXX.conf";
155
int d = mkstemps(tmpfilename, 5);
158
if (!(f = fdopen(d, "w")))
162
"<?xml version=\"1.0\"?>\n"
163
"<!DOCTYPE fontconfig SYSTEM \"fonts.dtd\">\n"
165
# if ((defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__)
166
"<dir>WINDOWSFONTDIR</dir>\n"
167
"<dir>~/.fonts</dir>\n"
168
"<cachedir>WINDOWSTEMPDIR_FONTCONFIG_CACHE</cachedir>\n"
169
"<cachedir>~/.fontconfig</cachedir>\n"
170
# else /* __APPLE__ */
171
"<dir>/usr/share/fonts</dir>\n"
172
"<dir>/usr/X11/lib/X11/fonts</dir>\n"
173
"<dir>/usr/X11/share/fonts</dir>\n"
174
"<dir>/opt/X11/share/fonts</dir>\n"
175
"<dir>/Library/Fonts</dir>\n"
176
"<dir>/Network/Library/Fonts</dir>\n"
177
"<dir>/System/Library/Fonts</dir>\n"
178
"<dir>~/Library/Application Support/Bino/fonts</dir>\n"
179
"<cachedir>/var/cache/fontconfig</cachedir>\n"
180
"<cachedir>/usr/X11/var/cache/fontconfig</cachedir>\n"
181
"<cachedir>/opt/X11/var/cache/fontconfig</cachedir>\n"
182
"<cachedir>~/Library/Application Support/Bino/cache/fonts</cachedir>\n"
183
"<cachedir>~/.fontconfig</cachedir>\n"
187
"<int>0x0020</int> <int>0x00A0</int> <int>0x00AD</int> <int>0x034F</int> <int>0x0600</int>\n"
188
"<int>0x0601</int> <int>0x0602</int> <int>0x0603</int> <int>0x06DD</int> <int>0x070F</int>\n"
189
"<int>0x115F</int> <int>0x1160</int> <int>0x1680</int> <int>0x17B4</int> <int>0x17B5</int>\n"
190
"<int>0x180E</int> <int>0x2000</int> <int>0x2001</int> <int>0x2002</int> <int>0x2003</int>\n"
191
"<int>0x2004</int> <int>0x2005</int> <int>0x2006</int> <int>0x2007</int> <int>0x2008</int>\n"
192
"<int>0x2009</int> <int>0x200A</int> <int>0x200B</int> <int>0x200C</int> <int>0x200D</int>\n"
193
"<int>0x200E</int> <int>0x200F</int> <int>0x2028</int> <int>0x2029</int> <int>0x202A</int>\n"
194
"<int>0x202B</int> <int>0x202C</int> <int>0x202D</int> <int>0x202E</int> <int>0x202F</int>\n"
195
"<int>0x205F</int> <int>0x2060</int> <int>0x2061</int> <int>0x2062</int> <int>0x2063</int>\n"
196
"<int>0x206A</int> <int>0x206B</int> <int>0x206C</int> <int>0x206D</int> <int>0x206E</int>\n"
197
"<int>0x206F</int> <int>0x2800</int> <int>0x3000</int> <int>0x3164</int> <int>0xFEFF</int>\n"
198
"<int>0xFFA0</int> <int>0xFFF9</int> <int>0xFFFA</int> <int>0xFFFB</int>\n"
200
"<rescan><int>30</int></rescan>\n"
206
/* Systems other than Windows and Mac OS are expected to have a default
207
* fontconfig configuration file so that we can simply return NULL. */
212
void subtitle_renderer::init()
218
global_libass_mutex.lock();
220
ASS_Library *ass_library = ass_library_init();
223
throw exc(_("Cannot initialize LibASS."));
225
ass_set_message_cb(ass_library, libass_msg_callback, NULL);
226
ass_set_extract_fonts(ass_library, 1);
227
_ass_library = ass_library;
229
ASS_Renderer *ass_renderer = ass_renderer_init(_ass_library);
232
throw exc(_("Cannot initialize LibASS renderer."));
234
ass_set_hinting(ass_renderer, ASS_HINTING_NATIVE);
235
_fontconfig_conffile = get_fontconfig_conffile();
236
ass_set_fonts(ass_renderer, NULL, "sans-serif", 1, _fontconfig_conffile, 1);
237
_ass_renderer = ass_renderer;
240
global_libass_mutex.unlock();
244
// Either we threw this exception ourselves, or the thread was cancelled.
245
// Note that if the thread was cancelled, we likely leak ressources.
246
// But at least we avoid to deconstruct potentially inconsistent ASS_Library
247
// and ASS_Renderer objects.
248
global_libass_mutex.unlock();
254
bool subtitle_renderer::is_initialized()
256
if (_initializer.is_running())
262
// rethrow a possible exception if something went wrong
263
_initializer.finish();
268
bool subtitle_renderer::render_to_display_size(const subtitle_box &box) const
270
return (box.format != subtitle_box::image);
273
void subtitle_renderer::prerender(const subtitle_box &box, int64_t timestamp,
274
const parameters ¶ms,
275
int width, int height, float pixel_aspect_ratio,
276
int &bb_x, int &bb_y, int &bb_w, int &bb_h)
278
assert(_initialized);
282
case subtitle_box::text:
283
case subtitle_box::ass:
284
prerender_ass(box, timestamp, params, width, height, pixel_aspect_ratio);
286
case subtitle_box::image:
296
void subtitle_renderer::render(uint32_t *bgra32_buffer)
300
case subtitle_box::text:
301
case subtitle_box::ass:
302
render_ass(bgra32_buffer);
304
case subtitle_box::image:
305
render_img(bgra32_buffer);
310
void subtitle_renderer::blend_ass_image(const ASS_Image *img, uint32_t *buf)
312
const unsigned int R = (img->color >> 24u) & 0xffu;
313
const unsigned int G = (img->color >> 16u) & 0xffu;
314
const unsigned int B = (img->color >> 8u) & 0xffu;
315
const unsigned int A = 255u - (img->color & 0xffu);
317
unsigned char *src = img->bitmap;
318
for (int src_y = 0; src_y < img->h; src_y++)
320
int dst_y = src_y + img->dst_y - _bb_y;
325
for (int src_x = 0; src_x < img->w; src_x++)
327
unsigned int a = src[src_x] * A / 255u;
328
int dst_x = src_x + img->dst_x - _bb_x;
333
uint32_t oldval = buf[dst_y * _bb_w + dst_x];
334
// XXX: The BGRA layout used here may be wrong on big endian system
335
uint32_t newval = std::min(a + (oldval >> 24u), 255u) << 24u
336
| ((a * R + (255u - a) * ((oldval >> 16u) & 0xffu)) / 255u) << 16u
337
| ((a * G + (255u - a) * ((oldval >> 8u) & 0xffu)) / 255u) << 8u
338
| ((a * B + (255u - a) * ((oldval ) & 0xffu)) / 255u);
339
buf[dst_y * _bb_w + dst_x] = newval;
345
void subtitle_renderer::set_ass_parameters(const parameters ¶ms)
347
std::vector<std::string> overrides;
349
if (params.subtitle_font != "")
351
overrides.push_back(std::string("Default.Fontname=") + params.subtitle_font);
353
if (params.subtitle_size > 0)
355
overrides.push_back(std::string("Default.Fontsize=") + str::from(params.subtitle_size));
357
if (params.subtitle_color <= std::numeric_limits<uint32_t>::max())
359
unsigned int color = params.subtitle_color;
360
unsigned int a = 255u - ((color >> 24u) & 0xffu);
361
unsigned int r = (color >> 16u) & 0xffu;
362
unsigned int g = (color >> 8u) & 0xffu;
363
unsigned int b = color & 0xffu;
364
std::string color_str = str::asprintf("&H%02x%02x%02x%02x", a, b, g, r);
365
overrides.push_back(std::string("Default.PrimaryColour=") + color_str);
366
overrides.push_back(std::string("Default.SecondaryColour=") + color_str);
368
const char *ass_overrides[overrides.size() + 1];
369
for (size_t i = 0; i < overrides.size(); i++)
371
ass_overrides[i] = ::strdup(overrides[i].c_str());
373
ass_overrides[overrides.size()] = NULL;
374
ass_set_style_overrides(_ass_library, const_cast<char **>(ass_overrides));
375
ass_set_font_scale(_ass_renderer, (params.subtitle_scale >= 0.0f ? params.subtitle_scale : 1.0));
376
ass_process_force_style(_ass_track);
379
void subtitle_renderer::prerender_ass(const subtitle_box &box, int64_t timestamp,
380
const parameters ¶ms, int width, int height, float pixel_aspect_ratio)
383
global_libass_mutex.lock();
385
// Set basic parameters
386
ass_set_frame_size(_ass_renderer, width, height);
387
ass_set_aspect_ratio(_ass_renderer, 1.0, pixel_aspect_ratio);
389
// Put subtitle data into ASS track
392
ass_free_track(_ass_track);
394
_ass_track = ass_new_track(_ass_library);
397
global_libass_mutex.unlock();
398
throw exc(_("Cannot initialize LibASS track."));
400
std::string conv_str = box.str;
401
if (params.subtitle_encoding != "")
405
conv_str = str::convert(box.str, params.subtitle_encoding, "UTF-8");
407
catch (std::exception &e)
409
msg::err(_("Subtitle character set conversion failed: %s"), e.what());
410
conv_str = std::string("Dialogue: 0,0:00:00.00,9:59:59.99,") + e.what();
413
if (box.format == subtitle_box::ass)
415
ass_process_codec_private(_ass_track, const_cast<char *>(box.style.c_str()), box.style.length());
416
ass_process_data(_ass_track, const_cast<char *>(conv_str.c_str()), conv_str.length());
420
// Set a default ASS style for text subtitles
423
"ScriptType: v4.00+\n"
426
"Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, "
427
"OutlineColour, BackColour, Bold, Italic, Underline, BorderStyle, "
428
"Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding\n"
429
"Style: Default,Arial,16,&Hffffff,&Hffffff,&H0,&H0,0,0,0,1,1,0,2,10,10,10,0,0\n"
432
"Format: Layer, Start, End, Text\n"
434
ass_process_codec_private(_ass_track, const_cast<char *>(style.c_str()), style.length());
435
// Convert text to ASS
436
str::replace(conv_str, "\r\n", "\\N");
437
str::replace(conv_str, "\n", "\\N");
438
std::string str = "Dialogue: 0,0:00:00.00,9:59:59.99," + conv_str;
439
ass_process_data(_ass_track, const_cast<char *>(str.c_str()), str.length());
441
set_ass_parameters(params);
444
_ass_img = ass_render_frame(_ass_renderer, _ass_track, timestamp / 1000, NULL);
447
global_libass_mutex.unlock();
449
// Determine bounding box
454
ASS_Image *img = _ass_img;
455
while (img && img->w > 0 && img->h > 0)
457
if (img->dst_x < min_x)
459
if (img->dst_x + img->w - 1 > max_x)
460
max_x = img->dst_x + img->w - 1;
461
if (img->dst_y < min_y)
463
if (img->dst_y + img->h - 1 > max_y)
464
max_y = img->dst_y + img->h - 1;
477
_bb_w = max_x - min_x + 1;
479
_bb_h = max_y - min_y + 1;
483
void subtitle_renderer::render_ass(uint32_t *bgra32_buffer)
485
if (_bb_w > 0 && _bb_h > 0)
487
std::memset(bgra32_buffer, 0, _bb_w * _bb_h * sizeof(uint32_t));
488
ASS_Image *img = _ass_img;
489
while (img && img->w > 0 && img->h > 0)
491
blend_ass_image(img, bgra32_buffer);
497
void subtitle_renderer::prerender_img(const subtitle_box &box)
500
// Determine bounding box
501
int min_x = std::numeric_limits<int>::max();
503
int min_y = std::numeric_limits<int>::max();
505
for (size_t i = 0; i < box.images.size(); i++)
507
const subtitle_box::image_t &img = box.images[i];
510
if (img.x + img.w - 1 > max_x)
511
max_x = img.x + img.w - 1;
514
if (img.y + img.h - 1 > max_y)
515
max_y = img.y + img.h - 1;
527
_bb_w = max_x - min_x + 1;
529
_bb_h = max_y - min_y + 1;
533
void subtitle_renderer::render_img(uint32_t *bgra32_buffer)
535
if (_bb_w <= 0 || _bb_h <= 0)
540
std::memset(bgra32_buffer, 0, _bb_w * _bb_h * sizeof(uint32_t));
541
for (size_t i = 0; i < _img_box->images.size(); i++)
543
const subtitle_box::image_t &img = _img_box->images[i];
544
const uint8_t *src = &(img.data[0]);
545
for (int src_y = 0; src_y < img.h; src_y++)
547
int dst_y = src_y + img.y - _bb_y;
552
for (int src_x = 0; src_x < img.w; src_x++)
554
int dst_x = src_x + img.x - _bb_x;
559
int palette_index = src[src_x];
560
uint32_t palette_entry = reinterpret_cast<const uint32_t *>(&(img.palette[0]))[palette_index];
561
unsigned int A = (palette_entry >> 24u);
562
unsigned int R = (palette_entry >> 16u) & 0xffu;
563
unsigned int G = (palette_entry >> 8u) & 0xffu;
564
unsigned int B = palette_entry & 0xffu;
565
uint32_t oldval = bgra32_buffer[dst_y * _bb_w + dst_x];
566
// XXX: The BGRA layout used here may be wrong on big endian system
567
uint32_t newval = std::min(A + (oldval >> 24u), 255u) << 24u
568
| ((A * R + (255u - A) * ((oldval >> 16u) & 0xffu)) / 255u) << 16u
569
| ((A * G + (255u - A) * ((oldval >> 8u) & 0xffu)) / 255u) << 8u
570
| ((A * B + (255u - A) * ((oldval ) & 0xffu)) / 255u);
571
bgra32_buffer[dst_y * _bb_w + dst_x] = newval;