JPC-RR r11.7
[jpcrr.git] / streamtools / hardsubs.cpp
blob733821f91e2abcaeed6b7657dd36394f4b418d42
1 #include "hardsubs.hpp"
2 #include "misc.hpp"
3 #include <stdexcept>
4 #include <iomanip>
5 #include <fstream>
6 #include <iostream>
7 #include <map>
8 #include <sstream>
9 #include "timeparse.hpp"
10 #include "SDL_ttf.h"
11 #include "SDL.h"
13 namespace
16 uint32_t resolution_w = 0;
17 uint32_t resolution_h = 0;
19 std::map<unsigned char, std::string> variables;
21 void check_in_bounds(struct subtitle& sub)
23 bool oob = false;
24 int xalign_type = sub.xalign_type;
25 int yalign_type = sub.yalign_type;
26 int32_t xalign = sub.xalign;
27 int32_t yalign = sub.yalign;
28 uint32_t width = sub.subtitle_img->get_width();
29 uint32_t height = sub.subtitle_img->get_height();
30 if(xalign_type == ALIGN_CUSTOM && xalign < 0)
31 oob = true;
32 if(xalign_type == ALIGN_CUSTOM && xalign + width > resolution_w)
33 oob = true;
34 if(width > resolution_w)
35 oob = true;
36 if(yalign_type == ALIGN_CUSTOM && yalign < 0)
37 oob = true;
38 if(yalign_type == ALIGN_CUSTOM && yalign + height > resolution_h)
39 oob = true;
40 if(height > resolution_h)
41 oob = true;
42 if(oob)
43 std::cerr << "WARNING: Subtitles exceed display bounds" << std::endl;
46 void init_variables()
48 if(!variables.count('\\'))
49 variables['\\'] = '\\';
50 if(!variables.count('A'))
51 variables['A'] = "(unknown)";
52 if(!variables.count('R'))
53 variables['R'] = "(unknown)";
54 if(!variables.count('L'))
55 variables['L'] = "(unknown)";
56 if(!variables.count('G'))
57 variables['G'] = "(unknown)";
60 void copy_surface(SDL_Surface* s, unsigned char* buffer, uint32_t total_width, uint32_t y,
61 uint32_t align_type, uint32_t extralines)
63 uint32_t xalign = 0;
64 switch(align_type) {
65 case ALIGN_LEFT:
66 xalign = 0;
67 break;
68 case ALIGN_CENTER:
69 xalign = (total_width - s->w) / 2;
70 break;
71 case ALIGN_RIGHT:
72 xalign = total_width - s->w;
73 break;
75 SDL_LockSurface(s);
76 for(uint32_t y2 = 0; y2 < s->h + extralines; y2++)
77 for(uint32_t x = 0; x < total_width; x++) {
78 if(x < xalign || x > xalign + (uint32_t)s->w || y2 >= (uint32_t)s->h)
79 buffer[(y + y2) * total_width + x] = 0;
80 else
81 buffer[(y + y2) * total_width + x] =
82 ((unsigned char*)s->pixels)[y2 * s->pitch + (x - xalign)];
84 SDL_UnlockSurface(s);
87 void render_halo(uint32_t w, uint32_t h, unsigned char* data, uint32_t _thickness)
89 int32_t thickness = _thickness;
90 for(uint32_t y = 0; y < h; y++)
91 for(uint32_t x = 0; x < w; x++) {
92 bool left = (x > 0) ? (data[y * w + x - 1] == 1) : false;
93 bool up = (y > 0) ? (data[y * w + x - w] == 1) : false;
94 if(data[y * w + x] != 1)
95 continue;
97 for(int32_t j = up ? thickness: -thickness; j <= thickness; j++) {
98 if(y + j < 0 || y + j >= h)
99 continue;
100 for(int32_t i = left ? thickness : -thickness; i <= thickness; i++) {
101 if(x + i < 0 || x + i >= w)
102 continue;
103 if(data[(y + j) * w + (x + i)] == 0)
104 data[(y + j) * w + (x + i)] = 2;
111 image_frame_rgbx* hardsub_render_settings::operator()()
113 std::list<SDL_Surface*> lines;
114 if(font_name == "")
115 throw std::runtime_error("No font set");
117 //Initialize SDL_ttf.
118 if(!TTF_WasInit() && TTF_Init() < 0) {
119 std::stringstream str;
120 str << "Can't initialize SDL_ttf: " << TTF_GetError();
121 throw std::runtime_error(str.str());
124 //Open the font and render the text.
125 TTF_Font* font = TTF_OpenFont(font_name.c_str(), font_size);
126 try {
127 SDL_Color clr;
128 clr.r = clr.g = clr.b = 255;
129 std::string tmp = "";
130 bool escape = false;
131 for(size_t i = 0; i < text.length(); i++) {
132 char tmp2[2] = {0, 0};
133 if(!escape)
134 if(text[i] == '\\')
135 escape = true;
136 else {
137 tmp2[0] = text[i];
138 tmp += tmp2;
140 else if(variables.count(text[i])) {
141 tmp += variables[text[i]];
142 escape = false;
143 } else if(text[i] == 'n') {
144 SDL_Surface* s = TTF_RenderUTF8_Solid(font, tmp.c_str(), clr);
145 if(!s)
146 throw std::runtime_error("Can't render text");
147 lines.push_back(s);
148 tmp = "";
149 escape = false;
150 } else {
151 std::stringstream str;
152 str << "Bad escape character '" << text[i] << "'";
153 throw std::runtime_error(str.str());
156 if(tmp != "") {
157 SDL_Surface* s = TTF_RenderUTF8_Solid(font, tmp.c_str(), clr);
158 if(!s)
159 throw std::runtime_error("Can't render text");
160 lines.push_back(s);
162 } catch(...) {
163 TTF_CloseFont(font);
164 throw;
166 TTF_CloseFont(font);
168 //Calculate image size and allocate buffers.
169 uint32_t total_width = 0;
170 uint32_t total_height = 0;
171 unsigned char* buffer1 = NULL;
172 image_frame_rgbx* img = NULL;
173 for(std::list<SDL_Surface*>::iterator i = lines.begin(); i != lines.end(); ++i) {
174 if((*i)->w + 2 * halo_thickness > total_width)
175 total_width = (*i)->w + 2 * halo_thickness;
176 total_height += ((*i)->h + 2 * halo_thickness + spacing);
178 try {
179 buffer1 = new unsigned char[total_width * total_height];
180 img = new image_frame_rgbx(total_width, total_height);
181 } catch(...) {
182 if(buffer1)
183 delete[] buffer1;
184 if(img)
185 delete img;
188 //Copy SDL surfaces to index buffer.
189 uint32_t line = 0;
190 for(std::list<SDL_Surface*>::iterator i = lines.begin(); i != lines.end(); ++i) {
191 copy_surface(*i, buffer1, total_width, line, align_type, spacing + 2 * halo_thickness);
192 line += ((*i)->h + spacing + 2 * halo_thickness);
193 SDL_FreeSurface(*i);
195 render_halo(total_width, total_height, buffer1, halo_thickness);
197 //Make full color buffer from indexed buffer.
198 uint32_t v0 = 0, v1 = 0, v2 = 0;
199 unsigned char* data = img->get_pixels();
200 for(uint32_t y = 0; y < total_height; y++)
201 for(uint32_t x = 0; x < total_width; x++)
202 switch(buffer1[y * total_width + x]) {
203 case 0:
204 v0++;
205 data[y * 4 * total_width + 4 * x + 0] = background_r;
206 data[y * 4 * total_width + 4 * x + 1] = background_g;
207 data[y * 4 * total_width + 4 * x + 2] = background_b;
208 data[y * 4 * total_width + 4 * x + 3] = background_a;
209 break;
210 case 1:
211 v1++;
212 data[y * 4 * total_width + 4 * x + 0] = foreground_r;
213 data[y * 4 * total_width + 4 * x + 1] = foreground_g;
214 data[y * 4 * total_width + 4 * x + 2] = foreground_b;
215 data[y * 4 * total_width + 4 * x + 3] = foreground_a;
216 break;
217 case 2:
218 v2++;
219 data[y * 4 * total_width + 4 * x + 0] = halo_r;
220 data[y * 4 * total_width + 4 * x + 1] = halo_g;
221 data[y * 4 * total_width + 4 * x + 2] = halo_b;
222 data[y * 4 * total_width + 4 * x + 3] = halo_a;
223 break;
225 delete[] buffer1;
226 return img;
229 void hardsub_settings::reset()
231 rsettings.font_size = DEFAULT_FONT_SIZE;
232 rsettings.halo_thickness = DEFAULT_HALO_THICKNESS;
233 rsettings.foreground_r = DEFAULT_FOREGROUND_R;
234 rsettings.foreground_g = DEFAULT_FOREGROUND_G;
235 rsettings.foreground_b = DEFAULT_FOREGROUND_B;
236 rsettings.foreground_a = DEFAULT_FOREGROUND_A;
237 rsettings.halo_r = DEFAULT_HALO_R;
238 rsettings.halo_g = DEFAULT_HALO_G;
239 rsettings.halo_b = DEFAULT_HALO_B;
240 rsettings.halo_a = DEFAULT_HALO_A;
241 rsettings.background_r = DEFAULT_BACKGROUND_R;
242 rsettings.background_g = DEFAULT_BACKGROUND_G;
243 rsettings.background_b = DEFAULT_BACKGROUND_B;
244 rsettings.background_a = DEFAULT_BACKGROUND_A;
245 rsettings.align_type = DEFAULT_ALIGN_TYPE;
246 rsettings.spacing = DEFAULT_SPACING;
247 duration = DEFAULT_DURATION;
248 xalign_type = DEFAULT_XALIGN_TYPE;
249 yalign_type = DEFAULT_YALIGN_TYPE;
253 hardsub_settings::hardsub_settings()
255 reset();
258 subtitle* hardsub_settings::operator()()
260 init_variables();
261 image_frame_rgbx* img = rsettings();
262 try {
263 subtitle* sub = new subtitle();
264 sub->timecode = timecode;
265 sub->duration = duration;
266 sub->xalign_type = xalign_type;
267 sub->xalign = xalign;
268 sub->yalign_type = yalign_type;
269 sub->yalign = yalign;
270 sub->used_settings = rsettings;
271 sub->subtitle_img = img;
272 check_in_bounds(*sub);
273 return sub;
274 } catch(...) {
275 delete img;
276 throw;
280 void render_subtitle(image_frame_rgbx& bottom, struct subtitle& sub)
282 int32_t xalign, yalign;
284 //Compute X offset.
285 switch(sub.xalign_type) {
286 case ALIGN_LEFT:
287 xalign = 0;
288 break;
289 case ALIGN_CENTER:
290 xalign = ((int32_t)bottom.get_width() - (int32_t)sub.subtitle_img->get_width()) / 2;
291 break;
292 case ALIGN_RIGHT:
293 xalign = (int32_t)bottom.get_width() - (int32_t)sub.subtitle_img->get_width();
294 break;
295 default:
296 xalign = sub.xalign;
299 //Compute Y offset.
300 switch(sub.yalign_type) {
301 case ALIGN_TOP:
302 yalign = 0;
303 break;
304 case ALIGN_CENTER:
305 yalign = ((int32_t)bottom.get_height() - (int32_t)sub.subtitle_img->get_height()) / 2;
306 break;
307 case ALIGN_BOTTOM:
308 yalign = (int32_t)bottom.get_height() - (int32_t)sub.subtitle_img->get_height();
309 break;
310 default:
311 yalign = sub.yalign;
314 if(xalign < -(int32_t)sub.subtitle_img->get_width() || yalign < -(int32_t)sub.subtitle_img->get_height())
315 return; //Outside image.
316 if(xalign >= (int32_t)bottom.get_width() || yalign >= (int32_t)bottom.get_height())
317 return; //Outside image.
318 if(sub.subtitle_img->get_width() == 0 || sub.subtitle_img->get_height() == 0)
319 return; //Nothing to draw.
321 uint32_t overlay_xoffset = (xalign < 0) ? -xalign : 0;
322 uint32_t overlay_yoffset = (yalign < 0) ? -yalign : 0;
323 uint32_t overlay_width = sub.subtitle_img->get_width() - overlay_xoffset;
324 uint32_t overlay_height = sub.subtitle_img->get_height() - overlay_yoffset;
325 if(xalign < 0)
326 xalign = 0;
327 if(yalign < 0)
328 yalign = 0;
329 if(xalign + overlay_width > bottom.get_width())
330 overlay_width = bottom.get_width() - xalign;
331 if(yalign + overlay_height > bottom.get_height())
332 overlay_height = bottom.get_height() - yalign;
334 for(uint32_t y = 0; y < overlay_height; y++) {
335 unsigned char* bottomr = bottom.get_pixels() + ((y + yalign) * 4 * bottom.get_width()) + 4 * xalign;
336 unsigned char* overlayr = sub.subtitle_img->get_pixels() + ((y + overlay_yoffset) * 4 *
337 sub.subtitle_img->get_width()) + 4 * overlay_xoffset;
338 for(uint32_t x = 0; x < overlay_width; x++) {
339 uint32_t ibase = x * 4;
340 int alpha = overlayr[ibase + 3];
341 bottomr[ibase + 0] = (unsigned char)((overlayr[ibase + 0] * alpha + bottomr[ibase + 0] *
342 (255 - alpha)) / 255);
343 bottomr[ibase + 1] = (unsigned char)((overlayr[ibase + 1] * alpha + bottomr[ibase + 1] *
344 (255 - alpha)) / 255);
345 bottomr[ibase + 2] = (unsigned char)((overlayr[ibase + 2] * alpha + bottomr[ibase + 2] *
346 (255 - alpha)) / 255);
347 bottomr[ibase + 3] = 0;
352 std::list<hardsub_settings*> settings_stack;
354 namespace
356 int64_t signed_settingvalue(const std::string& _setting, int64_t limit_low, int64_t limit_high)
358 std::string setting = settingvalue(_setting);
359 int64_t parsed = 0;
360 uint64_t rawparsed = 0;
361 bool negative = false;
362 size_t index = 0;
363 if(setting.length() > 0 && setting[0] == '-') {
364 negative = true;
365 index++;
367 if(index == setting.length())
368 throw std::runtime_error("Bad number");
370 for(; index < setting.length(); index++) {
371 if(setting[index] < '0' || setting[index] > '9')
372 throw std::runtime_error("Bad number");
373 if(rawparsed >= 0xFFFFFFFFFFFFFFFFULL / 10)
374 throw std::runtime_error("Number absolute value too large");
375 rawparsed = 10 * rawparsed + (setting[index] - '0');
378 //Take negation if needed and check range.
379 if(!negative)
380 if(rawparsed > 0x7FFFFFFFFFFFFFFFULL)
381 throw std::runtime_error("Value overflows");
382 else
383 parsed = rawparsed;
384 else
385 if(rawparsed > 0x8000000000000000ULL)
386 throw std::runtime_error("Value underflows");
387 else if(rawparsed == 0x8000000000000000ULL)
388 parsed = 2 * -(int64_t)(1ULL << 62);
389 else
390 parsed = -(int64_t)rawparsed;
392 if(parsed < limit_low || parsed > limit_high)
393 throw std::runtime_error("Value outside valid range");
394 return parsed;
397 uint64_t time_settingvalue(const std::string& setting)
399 std::string v = settingvalue(setting);
400 return parse_timespec(v);
403 void color_settingvalue(const std::string& setting, uint8_t& r, uint8_t& g, uint8_t& b, uint8_t& a)
405 std::string v = settingvalue(setting);
406 uint8_t c[4];
407 size_t component = 0;
408 size_t index = 0;
409 while(index < v.length()) {
410 if(component > 3)
411 throw std::runtime_error("Bad color specification");
413 //Find the start of next component and component length.
414 size_t tmp = v.find_first_of(",", index);
415 size_t cstart = index;
416 size_t clength = 0;
417 if(tmp > v.length()) {
418 index = v.length();
419 clength = v.length() - cstart;
420 } else {
421 index = tmp + 1;
422 clength = tmp - cstart;
425 uint16_t value = 0;
426 if(clength == 0 || clength > 3)
427 throw std::runtime_error("Bad color specification");
428 for(size_t i = 0; i < clength; i++)
429 if(v[cstart + i] < '0' || v[cstart + i] > '9')
430 throw std::runtime_error("Bad color specification");
431 else
432 value = value * 10 + (v[cstart + i] - '0');
433 if(value > 255)
434 throw std::runtime_error("Bad color specification");
435 c[component] = value;
437 component++;
439 if(component == 0)
440 throw std::runtime_error("Bad color specification");
441 else if(component == 1) {
442 a = c[0];
443 } else if(component == 2) {
444 r = g = b = c[0];
445 a = c[1];
446 } else if(component == 3) {
447 r = c[0];
448 g = c[1];
449 b = c[2];
450 } else if(component == 4) {
451 r = c[0];
452 g = c[1];
453 b = c[2];
454 a = c[3];
458 subtitle* parse_subtitle_option_one(struct hardsub_settings& settings, const std::string& option)
460 if(isstringprefix(option, "font="))
461 settings.rsettings.font_name = settingvalue(option);
462 else if(isstringprefix(option, "size="))
463 settings.rsettings.font_size = signed_settingvalue(option, 1, 10000);
464 else if(isstringprefix(option, "spacing="))
465 settings.rsettings.spacing = signed_settingvalue(option, 0, 10000);
466 else if(isstringprefix(option, "duration="))
467 settings.duration = time_settingvalue(option);
468 else if(option == "xpos=left")
469 settings.xalign_type = ALIGN_LEFT;
470 else if(option == "xpos=center")
471 settings.xalign_type = ALIGN_CENTER;
472 else if(option == "xpos=right")
473 settings.xalign_type = ALIGN_RIGHT;
474 else if(isstringprefix(option, "xpos=")) {
475 settings.xalign = signed_settingvalue(option, -2000000000, 2000000000);
476 settings.xalign_type = ALIGN_CUSTOM;
477 } else if(option == "ypos=top")
478 settings.yalign_type = ALIGN_TOP;
479 else if(option == "ypos=center")
480 settings.yalign_type = ALIGN_CENTER;
481 else if(option == "ypos=bottom")
482 settings.yalign_type = ALIGN_BOTTOM;
483 else if(isstringprefix(option, "ypos=")) {
484 settings.yalign = signed_settingvalue(option, -2000000000, 2000000000);
485 settings.yalign_type = ALIGN_CUSTOM;
486 } else if(isstringprefix(option, "halo="))
487 settings.rsettings.halo_thickness = signed_settingvalue(option, 0, 1000);
488 else if(option == "textalign=left")
489 settings.rsettings.align_type = ALIGN_LEFT;
490 else if(option == "textalign=center")
491 settings.rsettings.align_type = ALIGN_CENTER;
492 else if(option == "textalign=right")
493 settings.rsettings.align_type = ALIGN_RIGHT;
494 else if(option == "reset")
495 settings.reset();
496 else if(option == "push")
497 settings_stack.push_back(new hardsub_settings(settings));
498 else if(option == "pop") {
499 if(settings_stack.empty())
500 throw std::runtime_error("Attempt to pop empty stack");
501 hardsub_settings* s = settings_stack.back();
502 settings_stack.pop_back();
503 settings = *s;
504 delete s;
505 } else if(isstringprefix(option, "text=")) {
506 std::string tmp = settingvalue(option);
507 size_t split = tmp.find_first_of(",");
508 if(split > tmp.length())
509 throw std::runtime_error("Bad text syntax");
510 settings.timecode = parse_timespec(tmp.substr(0, split));
511 settings.rsettings.text = tmp.substr(split + 1);
512 return settings();
513 } else if(isstringprefix(option, "foreground-color=")) {
514 uint8_t r = 255, g = 255, b = 255, a = 255;
515 color_settingvalue(option, r, g, b, a);
516 settings.rsettings.foreground_r = r;
517 settings.rsettings.foreground_g = g;
518 settings.rsettings.foreground_b = b;
519 settings.rsettings.foreground_a = a;
520 } else if(isstringprefix(option, "halo-color=")) {
521 uint8_t r = 0, g = 0, b = 0, a = 255;
522 color_settingvalue(option, r, g, b, a);
523 settings.rsettings.halo_r = r;
524 settings.rsettings.halo_g = g;
525 settings.rsettings.halo_b = b;
526 settings.rsettings.halo_a = a;
527 } else if(isstringprefix(option, "background-color=")) {
528 uint8_t r = 0, g = 0, b = 0, a = 0;
529 color_settingvalue(option, r, g, b, a);
530 settings.rsettings.background_r = r;
531 settings.rsettings.background_g = g;
532 settings.rsettings.background_b = b;
533 settings.rsettings.background_a = a;
534 } else
535 throw std::runtime_error("Unknown subtitle option");
536 return NULL;
541 std::list<subtitle*> parse_subtitle_option(struct hardsub_settings& settings, const std::string& option)
543 std::list<subtitle*> list;
545 if(isstringprefix(option, "script=")) {
546 std::string filename = settingvalue(option);
547 std::ifstream stream(filename.c_str());
548 if(!stream)
549 throw std::runtime_error("Can't open script file");
550 while(stream) {
551 std::string opt;
552 std::getline(stream, opt);
553 if(opt == "")
554 continue;
555 subtitle* x = parse_subtitle_option_one(settings, opt);
556 if(x)
557 list.push_back(x);
559 if(!stream.eof() && (stream.bad() || stream.fail()))
560 throw std::runtime_error("Can't read script file");
561 } else {
562 subtitle* x = parse_subtitle_option_one(settings, option);
563 if(x)
564 list.push_back(x);
567 return list;
570 void print_hardsubs_help(const std::string& prefix)
572 std::cout << prefix << "font=<file>" << std::endl;
573 std::cout << "\tUse the specified font." << std::endl;
574 std::cout << prefix << "size=<size>" << std::endl;
575 std::cout << "\tUse the specified font size (default 16)." << std::endl;
576 std::cout << prefix << "xpos=<pixels>" << std::endl;
577 std::cout << "\tUse the specified subtitle x-offset." << std::endl;
578 std::cout << prefix << "xpos=left" << std::endl;
579 std::cout << "\tPlace subtitles to left." << std::endl;
580 std::cout << prefix << "xpos=center" << std::endl;
581 std::cout << "\tPlace subtitles to center (default)." << std::endl;
582 std::cout << prefix << "xpos=right" << std::endl;
583 std::cout << "\tPlace subtitles to right." << std::endl;
584 std::cout << prefix << "ypos=<pixels>" << std::endl;
585 std::cout << "\tUse the specified subtitle y-offset." << std::endl;
586 std::cout << prefix << "ypos=top" << std::endl;
587 std::cout << "\tPlace subtitles to top." << std::endl;
588 std::cout << prefix << "ypos=center" << std::endl;
589 std::cout << "\tPlace subtitles to center." << std::endl;
590 std::cout << prefix << "ypos=bottom" << std::endl;
591 std::cout << "\tPlace subtitles to bottom (default)." << std::endl;
592 std::cout << prefix << "duration=<duration>" << std::endl;
593 std::cout << "\tSubtitles last <duration>." << std::endl;
594 std::cout << prefix << "halo=<thickness>" << std::endl;
595 std::cout << "\tSubtitle halo thickness <thickness>." << std::endl;
596 std::cout << prefix << "foreground-color=<a>" << std::endl;
597 std::cout << prefix << "foreground-color=<rgb>,<a>" << std::endl;
598 std::cout << prefix << "foreground-color=<r>,<g>,<b>" << std::endl;
599 std::cout << prefix << "foreground-color=<r>,<g>,<b>,<a>" << std::endl;
600 std::cout << "\tSet foreground color. Default is fully opaque white." << std::endl;
601 std::cout << prefix << "halo-color=<a>" << std::endl;
602 std::cout << prefix << "halo-color=<rgb>,<a>" << std::endl;
603 std::cout << prefix << "halo-color=<r>,<g>,<b>" << std::endl;
604 std::cout << prefix << "halo-color=<r>,<g>,<b>,<a>" << std::endl;
605 std::cout << "\tSet halo color. Default is fully opaque black." << std::endl;
606 std::cout << prefix << "background-color=<a>" << std::endl;
607 std::cout << prefix << "background-color=<rgb>,<a>" << std::endl;
608 std::cout << prefix << "background-color=<r>,<g>,<b>" << std::endl;
609 std::cout << prefix << "background-color=<r>,<g>,<b>,<a>" << std::endl;
610 std::cout << "\tSet background color. Default is fully transparent black." << std::endl;
611 std::cout << prefix << "textalign=left" << std::endl;
612 std::cout << prefix << "textalign=center" << std::endl;
613 std::cout << prefix << "textalign=right" << std::endl;
614 std::cout << "\tSet text alignment between lines. Default is center." << std::endl;
615 std::cout << prefix << "spacing=<spacing>" << std::endl;
616 std::cout << "\tSet text spacing between lines. Default is 1." << std::endl;
617 std::cout << prefix << "text=<timecode>,<text>" << std::endl;
618 std::cout << "\tDisplay <text> at <timecode>. '\\\\' stands for backslash," << std::endl;
619 std::cout << "\t'\\n' stands for newline." << std::endl;
620 std::cout << prefix << "reset" << std::endl;
621 std::cout << "\tReset to defaults." << std::endl;
622 std::cout << prefix << "push" << std::endl;
623 std::cout << "\tPush current settings to stack." << std::endl;
624 std::cout << prefix << "pop" << std::endl;
625 std::cout << "\tPop settings from stack." << std::endl;
626 std::cout << prefix << "script=<file>" << std::endl;
627 std::cout << "\tRead subtitle commands from <file> and do them." << std::endl;
630 std::list<subtitle*> process_hardsubs_options(struct hardsub_settings& settings, const std::string& prefix, int argc, char** argv)
632 std::list<subtitle*> global;
633 for(int i = 1; i < argc; i++) {
634 std::string arg = argv[i];
635 if(arg == "--")
636 break;
637 if(!isstringprefix(arg, prefix))
638 continue;
639 try {
640 std::list<subtitle*> local_list;
641 local_list = parse_subtitle_option(settings, arg.substr(prefix.length()));
642 for(std::list<subtitle*>::iterator i = local_list.begin(); i != local_list.end(); i++)
643 global.push_back(*i);
644 } catch(std::exception& e) {
645 std::stringstream str;
646 str << "Error processing option '" << arg << "': " << e.what();
647 throw std::runtime_error(str.str());
650 return global;
653 namespace
655 std::string decimalstring_frombinary(const std::vector<unsigned char>& num, bool ssh_style)
657 std::vector<unsigned char> reverse;
658 unsigned brounds = ssh_style ? 7 : 8;
659 size_t length = num.size();
660 if(ssh_style) {
661 for(length = 0; length < num.size(); length++)
662 if(num[length] < 128)
663 break;
664 length++;
665 if(length > num.size())
666 throw std::runtime_error("Invalid ssh-style mpint");
668 reverse.push_back('0');
669 for(size_t i = 0; i < length; i++) {
670 for(size_t j = 0; j < brounds; j++) {
671 bool carry = ((num[i] & (1 << (brounds - 1 - j))) != 0);
672 for(size_t k = 0; k < reverse.size(); k++) {
673 unsigned x = 2 * (reverse[k] - '0') + (carry ? 1 : 0);
674 carry = (x > 9);
675 reverse[k] = '0' + x % 10;
677 if(carry)
678 reverse.push_back('1');
681 std::string final(reverse.size(), '0');
682 std::copy(reverse.rbegin(), reverse.rend(), final.begin());
683 return final;
687 void subtitle_process_gameinfo(std::list<subtitle*>& subs, struct packet& p)
689 if(p.rp_minor == 'A' || p.rp_minor == 'G') {
690 std::stringstream str;
691 for(size_t i = 0; i < p.rp_payload.size(); i++)
692 str << p.rp_payload[i];
693 std::string newarg = str.str();
694 subtitle_update_parameter(subs, p.rp_minor, newarg);
695 } else if(p.rp_minor == 'R') {
696 subtitle_update_parameter(subs, p.rp_minor, decimalstring_frombinary(p.rp_payload, false));
697 } else if(p.rp_minor == 'L') {
698 std::stringstream str;
699 uint64_t v = 0;
700 if(p.rp_payload.size() < 8)
701 return;
702 v |= (uint64_t)p.rp_payload[0] << 56;
703 v |= (uint64_t)p.rp_payload[1] << 48;
704 v |= (uint64_t)p.rp_payload[2] << 40;
705 v |= (uint64_t)p.rp_payload[3] << 32;
706 v |= (uint64_t)p.rp_payload[4] << 24;
707 v |= (uint64_t)p.rp_payload[5] << 16;
708 v |= (uint64_t)p.rp_payload[6] << 8;
709 v |= (uint64_t)p.rp_payload[7];
710 v = (v + 999999) / 1000000;
711 uint64_t hours = v / 3600000;
712 v %= 3600000;
713 uint64_t minutes = v / 60000;
714 v %= 60000;
715 uint64_t seconds = v / 1000;
716 v %= 1000;
717 if(hours > 0)
718 str << hours << ":";
719 str << std::setfill('0') << std::setw(2) << minutes << ":" << std::setfill('0')
720 << std::setw(2) << seconds << "." << std::setfill('0') << std::setw(3) << v;
721 std::string newarg = str.str();
722 subtitle_update_parameter(subs, p.rp_minor, newarg);
723 } else {
724 std::cerr << "WARNING: Unknown gameinfo type " << (unsigned)p.rp_minor << "." << std::endl;
728 subtitle::~subtitle()
732 void subtitle_update_parameter(std::list<subtitle*>& subs, unsigned char parameter, const std::string& value)
734 variables[parameter] = value;
735 for(std::list<subtitle*>::iterator i = subs.begin(); i != subs.end(); ++i)
736 try {
737 image_frame_rgbx* subtitle_img = (*i)->subtitle_img;
738 (*i)->subtitle_img = (*i)->used_settings();
739 delete subtitle_img;
740 check_in_bounds(**i);
741 } catch(std::exception& e) {
742 std::cerr << "WARNING: Can't update subtitles: " << e.what() << std::endl;
746 void subtitle_set_resolution(uint32_t w, uint32_t h)
748 resolution_w = w;
749 resolution_h = h;
752 #ifdef SUBTITLE_TEST
755 int real_main(int argc, char** argv)
757 std::list<subtitle*> list, list2;
758 hardsub_settings s;
759 for(int i = 1; i < argc; i++) {
760 list2 = parse_subtitle_option(s, argv[i]);
761 for(std::list<subtitle*>::iterator j = list2.begin(); j != list2.end(); ++j)
762 list.push_back(*j);
765 SDL_Init(SDL_INIT_EVERYTHING);
766 for(std::list<subtitle*>::iterator i = list.begin(); i != list.end(); ++i) {
767 image_frame_rgbx& _i = *((*i)->subtitle_img);
768 uint32_t iwidth = _i.get_width();
769 const unsigned char* idata = _i.get_pixels();
770 SDL_Surface* s = SDL_SetVideoMode(_i.get_width(), _i.get_height(), 32, SDL_SWSURFACE | SDL_DOUBLEBUF);
772 SDL_LockSurface(s);
773 for(uint32_t y = 0; y < _i.get_height(); y++)
774 for(uint32_t x = 0; x < iwidth; x++) {
775 ((unsigned char*)s->pixels)[y * s->pitch + 4 * x + 0] =
776 idata[y * 4 * iwidth + 4 * x + 0];
777 ((unsigned char*)s->pixels)[y * s->pitch + 4 * x + 1] =
778 idata[y * 4 * iwidth + 4 * x + 1];
779 ((unsigned char*)s->pixels)[y * s->pitch + 4 * x + 2] =
780 idata[y * 4 * iwidth + 4 * x + 2];
781 ((unsigned char*)s->pixels)[y * s->pitch + 4 * x + 3] =
782 idata[y * 4 * iwidth + 4 * x + 3];
784 SDL_UnlockSurface(s);
786 SDL_Flip(s);
787 SDL_Event e;
788 wait_loop:
789 if(!SDL_WaitEvent(&e))
790 std::cerr << "Can't wait for event" << std::endl;
791 else if(e.type == SDL_QUIT)
792 continue;
793 goto wait_loop;
795 SDL_Quit();
797 return 0;
800 #endif