Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * This file is part of mpv. |
3 | | * |
4 | | * mpv is free software; you can redistribute it and/or |
5 | | * modify it under the terms of the GNU Lesser General Public |
6 | | * License as published by the Free Software Foundation; either |
7 | | * version 2.1 of the License, or (at your option) any later version. |
8 | | * |
9 | | * mpv is distributed in the hope that it will be useful, |
10 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | * GNU Lesser General Public License for more details. |
13 | | * |
14 | | * You should have received a copy of the GNU Lesser General Public |
15 | | * License along with mpv. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | |
18 | | #include <stdlib.h> |
19 | | #include <assert.h> |
20 | | #include <string.h> |
21 | | #include <math.h> |
22 | | #include <limits.h> |
23 | | |
24 | | #include <libavutil/common.h> |
25 | | #include <ass/ass.h> |
26 | | |
27 | | #include "mpv_talloc.h" |
28 | | |
29 | | #include "config.h" |
30 | | #include "options/m_config.h" |
31 | | #include "options/options.h" |
32 | | #include "options/path.h" |
33 | | #include "common/common.h" |
34 | | #include "common/msg.h" |
35 | | #include "demux/demux.h" |
36 | | #include "demux/packet_pool.h" |
37 | | #include "video/csputils.h" |
38 | | #include "video/mp_image.h" |
39 | | #include "dec_sub.h" |
40 | | #include "ass_mp.h" |
41 | | #include "sd.h" |
42 | | |
43 | | struct sd_ass_priv { |
44 | | struct ass_library *ass_library; |
45 | | struct ass_renderer *ass_renderer; |
46 | | struct ass_track *ass_track; |
47 | | struct ass_track *shadow_track; // for --sub-ass=no rendering |
48 | | bool ass_configured; |
49 | | bool is_converted; |
50 | | struct lavc_conv *converter; |
51 | | struct sd_filter **filters; |
52 | | int num_filters; |
53 | | bool clear_once; |
54 | | struct mp_ass_packer *packer; |
55 | | struct sub_bitmap_copy_cache *copy_cache; |
56 | | bstr last_text; |
57 | | struct mp_image_params video_params; |
58 | | struct mp_image_params last_params; |
59 | | struct mp_osd_res osd; |
60 | | struct seen_packet *seen_packets; |
61 | | int num_seen_packets; |
62 | | int *packets_animated; |
63 | | int num_packets_animated; |
64 | | bool check_animated; |
65 | | }; |
66 | | |
67 | | struct seen_packet { |
68 | | int64_t pos; |
69 | | double pts; |
70 | | }; |
71 | | |
72 | | #undef OPT_BASE_STRUCT |
73 | | #define OPT_BASE_STRUCT struct mp_sub_filter_opts |
74 | | |
75 | | const struct m_sub_options mp_sub_filter_opts = { |
76 | | .opts = (const struct m_option[]){ |
77 | | {"sdh", OPT_BOOL(sub_filter_SDH)}, |
78 | | {"sdh-harder", OPT_BOOL(sub_filter_SDH_harder)}, |
79 | | {"sdh-enclosures", OPT_STRINGLIST(sub_filter_SDH_enclosures)}, |
80 | | {"regex-enable", OPT_BOOL(rf_enable)}, |
81 | | {"regex-plain", OPT_BOOL(rf_plain)}, |
82 | | {"regex", OPT_STRINGLIST(rf_items)}, |
83 | | {"jsre", OPT_STRINGLIST(jsre_items)}, |
84 | | {"regex-warn", OPT_BOOL(rf_warn)}, |
85 | | {0} |
86 | | }, |
87 | | .size = sizeof(OPT_BASE_STRUCT), |
88 | | .defaults = &(OPT_BASE_STRUCT){ |
89 | | .sub_filter_SDH_enclosures = (char *[]) { |
90 | | "()", |
91 | | "[]", |
92 | | "\uFF08\uFF09", |
93 | | NULL |
94 | | }, |
95 | | .rf_enable = true, |
96 | | }, |
97 | | .change_flags = UPDATE_SUB_FILT, |
98 | | }; |
99 | | |
100 | | static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts); |
101 | | static void fill_plaintext(struct sd *sd, double pts); |
102 | | |
103 | | static const struct sd_filter_functions *const filters[] = { |
104 | | // Note: list order defines filter order. |
105 | | &sd_filter_sdh, |
106 | | #if HAVE_POSIX |
107 | | &sd_filter_regex, |
108 | | #endif |
109 | | #if HAVE_JAVASCRIPT |
110 | | &sd_filter_jsre, |
111 | | #endif |
112 | | NULL, |
113 | | }; |
114 | | |
115 | | // Add default styles, if the track does not have any styles yet. |
116 | | // Apply style overrides if the user provides any. |
117 | | static void mp_ass_add_default_styles(struct sd *sd, ASS_Track *track, struct mp_subtitle_opts *opts, |
118 | | struct mp_subtitle_shared_opts *shared_opts) |
119 | 1.98k | { |
120 | 1.98k | if (opts->ass_styles_file && shared_opts->ass_style_override[sd->order]) { |
121 | 0 | char *file = mp_get_user_path(NULL, sd->global, opts->ass_styles_file); |
122 | 0 | ass_read_styles(track, file, NULL); |
123 | 0 | talloc_free(file); |
124 | 0 | } |
125 | | |
126 | 1.98k | if (track->n_styles == 0) { |
127 | 0 | if (!track->PlayResY) { |
128 | 0 | track->PlayResX = MP_ASS_FONT_PLAYRESX; |
129 | 0 | track->PlayResY = MP_ASS_FONT_PLAYRESY; |
130 | 0 | } |
131 | 0 | track->Kerning = true; |
132 | 0 | int sid = ass_alloc_style(track); |
133 | 0 | track->default_style = sid; |
134 | 0 | ASS_Style *style = track->styles + sid; |
135 | 0 | style->Name = strdup("Default"); |
136 | 0 | mp_ass_set_style(style, track->PlayResY, opts->sub_style); |
137 | 0 | } |
138 | | |
139 | 1.98k | if (shared_opts->ass_style_override[sd->order]) |
140 | 1.98k | ass_process_force_style(track); |
141 | 1.98k | } |
142 | | |
143 | | static const char *const font_mimetypes[] = { |
144 | | "application/x-truetype-font", |
145 | | "application/vnd.ms-opentype", |
146 | | "application/x-font-otf", |
147 | | "application/x-font-ttf", |
148 | | "application/x-font", // probably incorrect |
149 | | "application/font-sfnt", |
150 | | "font/collection", |
151 | | "font/otf", |
152 | | "font/sfnt", |
153 | | "font/ttf", |
154 | | NULL |
155 | | }; |
156 | | |
157 | | static const char *const font_exts[] = {".ttf", ".ttc", ".otf", ".otc", NULL}; |
158 | | |
159 | | static bool attachment_is_font(struct mp_log *log, struct demux_attachment *f) |
160 | 73 | { |
161 | 73 | if (!f->name || !f->type || !f->data || !f->data_size) |
162 | 0 | return false; |
163 | 143 | for (int n = 0; font_mimetypes[n]; n++) { |
164 | 136 | if (strcmp(font_mimetypes[n], f->type) == 0) |
165 | 66 | return true; |
166 | 136 | } |
167 | | // fallback: match against file extension |
168 | 7 | char *ext = strlen(f->name) > 4 ? f->name + strlen(f->name) - 4 : ""; |
169 | 31 | for (int n = 0; font_exts[n]; n++) { |
170 | 25 | if (strcasecmp(ext, font_exts[n]) == 0) { |
171 | 1 | mp_warn(log, "Loading font attachment '%s' with MIME type %s. " |
172 | 1 | "Assuming this is a broken Matroska file, which was " |
173 | 1 | "muxed without setting a correct font MIME type.\n", |
174 | 1 | f->name, f->type); |
175 | 1 | return true; |
176 | 1 | } |
177 | 25 | } |
178 | 6 | return false; |
179 | 7 | } |
180 | | |
181 | | static void add_subtitle_fonts(struct sd *sd) |
182 | 994 | { |
183 | 994 | struct sd_ass_priv *ctx = sd->priv; |
184 | 994 | struct mp_subtitle_opts *opts = sd->opts; |
185 | 994 | if (!opts->ass_enabled || !opts->use_embedded_fonts || !sd->attachments) |
186 | 0 | return; |
187 | 1.06k | for (int i = 0; i < sd->attachments->num_entries; i++) { |
188 | 73 | struct demux_attachment *f = &sd->attachments->entries[i]; |
189 | 73 | if (attachment_is_font(sd->log, f)) |
190 | 67 | ass_add_font(ctx->ass_library, f->name, f->data, f->data_size); |
191 | 73 | } |
192 | 994 | } |
193 | | |
194 | | static void filters_destroy(struct sd *sd) |
195 | 1.98k | { |
196 | 1.98k | struct sd_ass_priv *ctx = sd->priv; |
197 | | |
198 | 1.98k | for (int n = 0; n < ctx->num_filters; n++) { |
199 | 0 | struct sd_filter *ft = ctx->filters[n]; |
200 | 0 | if (ft->driver->uninit) |
201 | 0 | ft->driver->uninit(ft); |
202 | 0 | talloc_free(ft); |
203 | 0 | } |
204 | 1.98k | ctx->num_filters = 0; |
205 | 1.98k | } |
206 | | |
207 | | static void filters_init(struct sd *sd) |
208 | 994 | { |
209 | 994 | struct sd_ass_priv *ctx = sd->priv; |
210 | | |
211 | 994 | filters_destroy(sd); |
212 | | |
213 | 2.98k | for (int n = 0; filters[n]; n++) { |
214 | 1.98k | struct sd_filter *ft = talloc_ptrtype(ctx, ft); |
215 | 1.98k | *ft = (struct sd_filter){ |
216 | 1.98k | .global = sd->global, |
217 | 1.98k | .log = sd->log, |
218 | 1.98k | .packet_pool = demux_packet_pool_get(sd->global), |
219 | 1.98k | .opts = mp_get_config_group(ft, sd->global, &mp_sub_filter_opts), |
220 | 1.98k | .driver = filters[n], |
221 | 1.98k | .codec = "ass", |
222 | 1.98k | .event_format = talloc_strdup(ft, ctx->ass_track->event_format), |
223 | 1.98k | }; |
224 | 1.98k | if (ft->driver->init(ft)) { |
225 | 0 | MP_TARRAY_APPEND(ctx, ctx->filters, ctx->num_filters, ft); |
226 | 1.98k | } else { |
227 | 1.98k | talloc_free(ft); |
228 | 1.98k | } |
229 | 1.98k | } |
230 | 994 | } |
231 | | |
232 | | static void enable_output(struct sd *sd, bool enable) |
233 | 3.97k | { |
234 | 3.97k | struct sd_ass_priv *ctx = sd->priv; |
235 | 3.97k | if (enable == !!ctx->ass_renderer) |
236 | 1.98k | return; |
237 | 1.98k | if (ctx->ass_renderer) { |
238 | 994 | ass_renderer_done(ctx->ass_renderer); |
239 | 994 | ctx->ass_renderer = NULL; |
240 | 994 | } else { |
241 | 994 | ctx->ass_renderer = ass_renderer_init(ctx->ass_library); |
242 | | |
243 | 994 | mp_ass_configure_fonts(ctx->ass_renderer, sd->opts->sub_style, |
244 | 994 | sd->global, sd->log); |
245 | 994 | } |
246 | 1.98k | } |
247 | | |
248 | | static void assobjects_init(struct sd *sd) |
249 | 994 | { |
250 | 994 | struct sd_ass_priv *ctx = sd->priv; |
251 | 994 | struct mp_subtitle_opts *opts = sd->opts; |
252 | 994 | struct mp_subtitle_shared_opts *shared_opts = sd->shared_opts; |
253 | | |
254 | 994 | ctx->ass_library = mp_ass_init(sd->global, sd->opts->sub_style, sd->log); |
255 | 994 | ass_set_extract_fonts(ctx->ass_library, opts->use_embedded_fonts); |
256 | | |
257 | 994 | add_subtitle_fonts(sd); |
258 | | |
259 | 994 | if (shared_opts->ass_style_override[sd->order]) |
260 | 994 | ass_set_style_overrides(ctx->ass_library, opts->ass_style_override_list); |
261 | | |
262 | 994 | ctx->ass_track = ass_new_track(ctx->ass_library); |
263 | 994 | ctx->ass_track->track_type = TRACK_TYPE_ASS; |
264 | | |
265 | 994 | ctx->shadow_track = ass_new_track(ctx->ass_library); |
266 | 994 | ctx->shadow_track->PlayResX = MP_ASS_FONT_PLAYRESX; |
267 | 994 | ctx->shadow_track->PlayResY = MP_ASS_FONT_PLAYRESY; |
268 | 994 | mp_ass_add_default_styles(sd, ctx->shadow_track, opts, shared_opts); |
269 | | |
270 | 994 | char *extradata = sd->codec->extradata; |
271 | 994 | int extradata_size = sd->codec->extradata_size; |
272 | 994 | if (ctx->converter) { |
273 | 629 | extradata = lavc_conv_get_extradata(ctx->converter); |
274 | 629 | extradata_size = extradata ? strlen(extradata) : 0; |
275 | 629 | } |
276 | 994 | if (extradata) |
277 | 974 | ass_process_codec_private(ctx->ass_track, extradata, extradata_size); |
278 | | |
279 | 994 | mp_ass_add_default_styles(sd, ctx->ass_track, opts, shared_opts); |
280 | | |
281 | 994 | #if LIBASS_VERSION >= 0x01302000 |
282 | 994 | ass_set_check_readorder(ctx->ass_track, sd->opts->sub_clear_on_seek ? 0 : 1); |
283 | 994 | #endif |
284 | | |
285 | 994 | #if LIBASS_VERSION >= 0x01703010 |
286 | 994 | ass_configure_prune(ctx->ass_track, sd->opts->ass_prune_delay * 1000.0); |
287 | 994 | #endif |
288 | | |
289 | 994 | enable_output(sd, true); |
290 | 994 | ass_set_cache_limits(ctx->ass_renderer, sd->opts->sub_glyph_limit, sd->opts->sub_bitmap_max_size); |
291 | 994 | } |
292 | | |
293 | | static void assobjects_destroy(struct sd *sd) |
294 | 994 | { |
295 | 994 | struct sd_ass_priv *ctx = sd->priv; |
296 | | |
297 | 994 | ass_free_track(ctx->ass_track); |
298 | 994 | ass_free_track(ctx->shadow_track); |
299 | 994 | enable_output(sd, false); |
300 | 994 | ass_library_done(ctx->ass_library); |
301 | 994 | } |
302 | | |
303 | | static int init(struct sd *sd) |
304 | 1.03k | { |
305 | 1.03k | struct sd_ass_priv *ctx = talloc_zero(sd, struct sd_ass_priv); |
306 | 1.03k | sd->priv = ctx; |
307 | | |
308 | | // Note: accept "null" as alias for "ass", so EDL delay_open subtitle |
309 | | // streams work. |
310 | 1.03k | if (strcmp(sd->codec->codec, "ass") != 0 && |
311 | 1.03k | strcmp(sd->codec->codec, "null") != 0) |
312 | 673 | { |
313 | 673 | ctx->is_converted = true; |
314 | 673 | ctx->converter = lavc_conv_create(sd); |
315 | 673 | if (!ctx->converter) |
316 | 44 | return -1; |
317 | 673 | } |
318 | | |
319 | 994 | assobjects_init(sd); |
320 | 994 | filters_init(sd); |
321 | | |
322 | 994 | ctx->packer = mp_ass_packer_alloc(ctx); |
323 | | |
324 | | // Subtitles does not have any profile value, so put the converted type as a profile. |
325 | 994 | const char *_Atomic *desc = ctx->converter ? &sd->codec->codec_profile : &sd->codec->codec_desc; |
326 | 994 | switch (ctx->ass_track->track_type) { |
327 | 760 | case TRACK_TYPE_ASS: |
328 | 760 | *desc = "Advanced Sub Station Alpha"; |
329 | 760 | break; |
330 | 234 | case TRACK_TYPE_SSA: |
331 | 234 | *desc = "Sub Station Alpha"; |
332 | 234 | break; |
333 | 994 | } |
334 | | |
335 | 994 | return 0; |
336 | 994 | } |
337 | | |
338 | | // Check if subtitle has events that would cause it to be animated inside {} |
339 | | static bool is_animated(const char *str) |
340 | 76 | { |
341 | 76 | const char *begin = str; |
342 | 77 | while ((str = strchr(str, '{'))) { |
343 | 75 | if (str++ > begin && str[-2] == '\\') |
344 | 0 | continue; |
345 | | |
346 | 75 | const char *end = strchr(str, '}'); |
347 | 75 | if (!end) |
348 | 1 | return false; |
349 | | |
350 | 300 | while ((str = memchr(str, '\\', end - str))) { |
351 | 598 | while (str[0] == '\\') |
352 | 299 | ++str; |
353 | 299 | while (str[0] == ' ' || str[0] == '\t') |
354 | 0 | ++str; |
355 | 299 | if (str[0] == 'k' || str[0] == 'K' || str[0] == 't' || |
356 | 299 | (str[0] == 'f' && str[1] == 'a' && str[2] == 'd') || |
357 | 299 | (str[0] == 'm' && str[1] == 'o' && str[2] == 'v' && str[3] == 'e')) |
358 | 73 | { |
359 | 73 | return true; |
360 | 73 | } |
361 | 299 | } |
362 | | |
363 | 1 | str = end + 1; |
364 | 1 | } |
365 | | |
366 | 2 | return false; |
367 | 76 | } |
368 | | |
369 | | // Note: pkt is not necessarily a fully valid refcounted packet. |
370 | | static void filter_and_add(struct sd *sd, struct demux_packet *pkt) |
371 | 1.11k | { |
372 | 1.11k | struct sd_ass_priv *ctx = sd->priv; |
373 | 1.11k | struct demux_packet *orig_pkt = pkt; |
374 | 1.11k | ASS_Track *track = ctx->ass_track; |
375 | 1.11k | int old_n_events = track->n_events; |
376 | | |
377 | 1.11k | for (int n = 0; n < ctx->num_filters; n++) { |
378 | 0 | struct sd_filter *ft = ctx->filters[n]; |
379 | 0 | struct demux_packet *npkt = ft->driver->filter(ft, pkt); |
380 | 0 | if (pkt != npkt && pkt != orig_pkt) |
381 | 0 | talloc_free(pkt); |
382 | 0 | pkt = npkt; |
383 | 0 | if (!pkt) |
384 | 0 | return; |
385 | 0 | } |
386 | | |
387 | 1.11k | ass_process_chunk(ctx->ass_track, pkt->buffer, pkt->len, |
388 | 1.11k | llrint(pkt->pts * 1000), |
389 | 1.11k | llrint(pkt->duration * 1000)); |
390 | | |
391 | | // This bookkeeping only has any practical use for ASS subs |
392 | | // over a VO with no video. |
393 | 1.11k | if (!ctx->is_converted) { |
394 | 200 | if (!pkt->seen) { |
395 | 366 | for (int n = track->n_events - 1; n >= 0; n--) { |
396 | 265 | if (n + 1 == old_n_events || pkt->animated == 1) |
397 | 93 | break; |
398 | 172 | ASS_Event *event = &track->events[n]; |
399 | | // Might as well mark pkt->animated here with effects if we can. |
400 | 172 | pkt->animated = (event->Effect && event->Effect[0]) ? 1 : -1; |
401 | 172 | if (ctx->check_animated && pkt->animated != 1) |
402 | 76 | pkt->animated = is_animated(event->Text); |
403 | 172 | } |
404 | 194 | MP_TARRAY_APPEND(ctx, ctx->packets_animated, ctx->num_packets_animated, pkt->animated); |
405 | 194 | } else { |
406 | 6 | if (ctx->check_animated && ctx->packets_animated[pkt->seen_pos] == -1) { |
407 | 0 | for (int n = track->n_events - 1; n >= 0; n--) { |
408 | 0 | if (n + 1 == old_n_events || pkt->animated == 1) |
409 | 0 | break; |
410 | 0 | ASS_Event *event = &track->events[n]; |
411 | 0 | ctx->packets_animated[pkt->seen_pos] = is_animated(event->Text); |
412 | 0 | pkt->animated = ctx->packets_animated[pkt->seen_pos]; |
413 | 0 | } |
414 | 6 | } else { |
415 | 6 | pkt->animated = ctx->packets_animated[pkt->seen_pos]; |
416 | 6 | } |
417 | 6 | } |
418 | 200 | } |
419 | | |
420 | 1.11k | if (pkt != orig_pkt) |
421 | 0 | talloc_free(pkt); |
422 | 1.11k | } |
423 | | |
424 | | // Test if the packet with the given file position and pts was already consumed. |
425 | | // Return false if the packet is new (and add it to the internal list), and |
426 | | // return true if it was already seen. |
427 | | static bool check_packet_seen(struct sd *sd, struct demux_packet *packet) |
428 | 1.11k | { |
429 | 1.11k | struct sd_ass_priv *priv = sd->priv; |
430 | 1.11k | int a = 0; |
431 | 1.11k | int b = priv->num_seen_packets; |
432 | 3.13k | while (a < b) { |
433 | 2.02k | int mid = a + (b - a) / 2; |
434 | 2.02k | struct seen_packet *seen_packet = &priv->seen_packets[mid]; |
435 | 2.02k | if (packet->pos == seen_packet->pos && packet->pts == seen_packet->pts) { |
436 | 6 | packet->seen_pos = mid; |
437 | 6 | return true; |
438 | 6 | } |
439 | 2.02k | if (packet->pos > seen_packet->pos || |
440 | 2.02k | (packet->pos == seen_packet->pos && packet->pts > seen_packet->pts)) { |
441 | 2.01k | a = mid + 1; |
442 | 2.01k | } else { |
443 | 3 | b = mid; |
444 | 3 | } |
445 | 2.02k | } |
446 | 1.10k | packet->seen_pos = a; |
447 | 1.10k | MP_TARRAY_INSERT_AT(priv, priv->seen_packets, priv->num_seen_packets, a, |
448 | 1.10k | (struct seen_packet){packet->pos, packet->pts}); |
449 | 1.10k | return false; |
450 | 1.10k | } |
451 | | |
452 | 1.01k | #define UNKNOWN_DURATION (INT_MAX / 1000) |
453 | | |
454 | | static void decode(struct sd *sd, struct demux_packet *packet) |
455 | 1.11k | { |
456 | 1.11k | struct sd_ass_priv *ctx = sd->priv; |
457 | 1.11k | ASS_Track *track = ctx->ass_track; |
458 | | |
459 | 1.11k | packet->sub_duration = packet->duration; |
460 | | |
461 | 1.11k | if (ctx->converter) { |
462 | 914 | if (!sd->opts->sub_clear_on_seek && packet->pos >= 0 && |
463 | 914 | check_packet_seen(sd, packet)) |
464 | 0 | return; |
465 | | |
466 | 914 | double sub_pts = 0; |
467 | 914 | double sub_duration = 0; |
468 | 914 | char **r = lavc_conv_decode(ctx->converter, packet, &sub_pts, |
469 | 914 | &sub_duration); |
470 | 914 | if (sd->opts->sub_stretch_durations || |
471 | 914 | packet->duration < 0 || sub_duration == UINT32_MAX) { |
472 | 500 | MP_VERBOSE(sd, "Subtitle with unknown duration.\n"); |
473 | 500 | sub_duration = UNKNOWN_DURATION; |
474 | 500 | } |
475 | | |
476 | 1.82k | for (int n = 0; r && r[n]; n++) { |
477 | 914 | struct demux_packet pkt2 = { |
478 | 914 | .pts = sub_pts, |
479 | 914 | .duration = sub_duration, |
480 | 914 | .buffer = r[n], |
481 | 914 | .len = strlen(r[n]), |
482 | 914 | }; |
483 | 914 | filter_and_add(sd, &pkt2); |
484 | 914 | } |
485 | 2.08k | for (int n = track->n_events - 1; n >= 0; n--) { |
486 | 1.64k | if (track->events[track->n_events - 1].Start == track->events[n].Start) |
487 | 1.13k | continue; |
488 | 511 | if (track->events[n].Duration == UNKNOWN_DURATION * 1000) { |
489 | 480 | if (track->events[n].Start < track->events[n + 1].Start) { |
490 | 448 | track->events[n].Duration = track->events[n + 1].Start - |
491 | 448 | track->events[n].Start; |
492 | 448 | } else if (track->events[n].Start == track->events[n + 1].Start) { |
493 | 1 | track->events[n].Duration = track->events[n + 1].Duration; |
494 | 1 | } |
495 | 480 | } |
496 | 511 | if (n > 0 && track->events[n].Start != track->events[n - 1].Start) |
497 | 469 | break; |
498 | 511 | } |
499 | 914 | } else { |
500 | | // Note that for this packet format, libass has an internal mechanism |
501 | | // for discarding duplicate (already seen) packets but we check this |
502 | | // anyways for our purposes for ASS subtitles. |
503 | 200 | packet->seen = check_packet_seen(sd, packet); |
504 | 200 | filter_and_add(sd, packet); |
505 | 200 | } |
506 | 1.11k | } |
507 | | |
508 | | // Calculate the height used for scaling subtitle text size so --sub-scale-with-window |
509 | | // can undo this scale and use frame size instead. The algorithm used is the following: |
510 | | // - If use_margins is disabled, the text is scaled with the visual size of the video. |
511 | | // - If use_margins is enabled, the text is scaled with the size of the video |
512 | | // as if the video is resized to "fit" the size of the frame. |
513 | | static float get_libass_scale_height(struct mp_osd_res *dim, bool use_margins) |
514 | 0 | { |
515 | 0 | float vidw = dim->w - (dim->ml + dim->mr); |
516 | 0 | float vidh = dim->h - (dim->mt + dim->mb); |
517 | 0 | if (!use_margins || vidw < 1.0) |
518 | 0 | return vidh; |
519 | 0 | else |
520 | 0 | return MPMIN(dim->h, dim->w / vidw * vidh); |
521 | 0 | } |
522 | | |
523 | | static void configure_ass(struct sd *sd, struct mp_osd_res *dim, |
524 | | bool converted, ASS_Track *track) |
525 | 0 | { |
526 | 0 | struct mp_subtitle_opts *opts = sd->opts; |
527 | 0 | struct mp_subtitle_shared_opts *shared_opts = sd->shared_opts; |
528 | 0 | struct sd_ass_priv *ctx = sd->priv; |
529 | 0 | ASS_Renderer *priv = ctx->ass_renderer; |
530 | |
|
531 | 0 | ass_set_frame_size(priv, dim->w, dim->h); |
532 | 0 | ass_set_margins(priv, dim->mt, dim->mb, dim->ml, dim->mr); |
533 | |
|
534 | 0 | bool set_use_margins = false; |
535 | 0 | float set_sub_pos = 0.0f; |
536 | 0 | float set_line_spacing = 0; |
537 | 0 | float set_font_scale = 1; |
538 | 0 | int set_hinting = 0; |
539 | 0 | bool set_scale_with_window = false; |
540 | 0 | bool set_scale_by_window = true; |
541 | 0 | bool total_override = false; |
542 | | // With forced overrides, apply the --sub-* specific options |
543 | 0 | if (converted || shared_opts->ass_style_override[sd->order] == ASS_STYLE_OVERRIDE_FORCE) { |
544 | 0 | set_scale_with_window = opts->sub_scale_with_window; |
545 | 0 | set_use_margins = opts->sub_use_margins; |
546 | 0 | set_scale_by_window = opts->sub_scale_by_window; |
547 | 0 | total_override = true; |
548 | 0 | } else { |
549 | 0 | set_scale_with_window = opts->ass_scale_with_window; |
550 | 0 | set_use_margins = opts->ass_use_margins; |
551 | 0 | } |
552 | 0 | if (converted || shared_opts->ass_style_override[sd->order]) { |
553 | 0 | set_sub_pos = 100.0f - shared_opts->sub_pos[sd->order]; |
554 | 0 | set_line_spacing = opts->sub_line_spacing; |
555 | 0 | set_hinting = opts->sub_hinting; |
556 | 0 | } |
557 | 0 | if (total_override || shared_opts->ass_style_override[sd->order] == ASS_STYLE_OVERRIDE_SCALE) { |
558 | 0 | set_font_scale = opts->sub_scale; |
559 | 0 | } |
560 | 0 | if (set_scale_with_window) { |
561 | 0 | set_font_scale *= dim->h / MPMAX(get_libass_scale_height(dim, set_use_margins), 1); |
562 | 0 | } |
563 | 0 | if (!set_scale_by_window) { |
564 | 0 | double factor = dim->h / 720.0; |
565 | 0 | if (factor != 0.0) |
566 | 0 | set_font_scale /= factor; |
567 | 0 | } |
568 | 0 | ass_set_use_margins(priv, set_use_margins); |
569 | 0 | ass_set_line_position(priv, set_sub_pos); |
570 | 0 | ass_set_shaper(priv, opts->sub_shaper); |
571 | 0 | int set_force_flags = 0; |
572 | 0 | if (total_override) { |
573 | 0 | set_force_flags |= ASS_OVERRIDE_BIT_FONT_NAME |
574 | 0 | | ASS_OVERRIDE_BIT_FONT_SIZE_FIELDS |
575 | 0 | | ASS_OVERRIDE_BIT_COLORS |
576 | 0 | | ASS_OVERRIDE_BIT_BORDER; |
577 | 0 | if (!opts->sub_scale_signs) |
578 | 0 | set_force_flags |= ASS_OVERRIDE_BIT_SELECTIVE_FONT_SCALE; |
579 | 0 | #if LIBASS_VERSION >= 0x01703020 |
580 | 0 | set_force_flags |= ASS_OVERRIDE_BIT_BLUR; |
581 | 0 | #endif |
582 | 0 | } |
583 | 0 | if (shared_opts->ass_style_override[sd->order] == ASS_STYLE_OVERRIDE_SCALE && |
584 | 0 | !opts->sub_scale_signs) |
585 | 0 | set_force_flags |= ASS_OVERRIDE_BIT_SELECTIVE_FONT_SCALE; |
586 | 0 | if (converted) |
587 | 0 | set_force_flags |= ASS_OVERRIDE_BIT_ALIGNMENT; |
588 | 0 | #if LIBASS_VERSION >= 0x01306000 |
589 | 0 | if ((converted || shared_opts->ass_style_override[sd->order]) && opts->ass_justify) |
590 | 0 | set_force_flags |= ASS_OVERRIDE_BIT_JUSTIFY; |
591 | 0 | #endif |
592 | 0 | ass_set_selective_style_override_enabled(priv, set_force_flags); |
593 | 0 | ASS_Style style = {0}; |
594 | 0 | mp_ass_set_style(&style, MP_ASS_FONT_PLAYRESY, opts->sub_style); |
595 | 0 | ass_set_selective_style_override(priv, &style); |
596 | 0 | free(style.FontName); |
597 | 0 | if (converted && track->default_style < track->n_styles) { |
598 | 0 | mp_ass_set_style(track->styles + track->default_style, |
599 | 0 | track->PlayResY, opts->sub_style); |
600 | 0 | } |
601 | 0 | ass_set_font_scale(priv, set_font_scale); |
602 | 0 | ass_set_hinting(priv, set_hinting); |
603 | 0 | ass_set_line_spacing(priv, set_line_spacing); |
604 | 0 | #if LIBASS_VERSION >= 0x01600010 |
605 | 0 | if (converted) { |
606 | 0 | ass_track_set_feature(track, ASS_FEATURE_WRAP_UNICODE, 1); |
607 | 0 | if (!opts->sub_vsfilter_bidi_compat) { |
608 | 0 | for (int n = 0; n < track->n_styles; n++) { |
609 | 0 | track->styles[n].Encoding = -1; |
610 | 0 | } |
611 | 0 | ass_track_set_feature(track, ASS_FEATURE_BIDI_BRACKETS, 1); |
612 | 0 | ass_track_set_feature(track, ASS_FEATURE_WHOLE_TEXT_LAYOUT, 1); |
613 | 0 | } |
614 | 0 | } |
615 | 0 | #endif |
616 | 0 | if (converted) { |
617 | 0 | bool override_playres = true; |
618 | 0 | char **ass_style_override_list = opts->ass_style_override_list; |
619 | 0 | for (int i = 0; ass_style_override_list && ass_style_override_list[i]; i++) { |
620 | 0 | if (bstr_find0(bstr0(ass_style_override_list[i]), "PlayResX") >= 0) |
621 | 0 | override_playres = false; |
622 | 0 | } |
623 | | |
624 | | // srt to ass conversion from ffmpeg has fixed PlayResX of 384 with an |
625 | | // aspect of 4:3. Starting with libass f08f8ea5 (pre 0.17) PlayResX |
626 | | // affects shadow and border widths, among others, so to render borders |
627 | | // and shadows correctly, we adjust PlayResX according to the DAR. |
628 | | // But PlayResX also affects margins, so we adjust those too. |
629 | | // This should ensure basic srt-to-ass ffmpeg conversion has correct |
630 | | // borders, but there could be other issues with some srt extensions |
631 | | // and/or different source formats which would be exposed over time. |
632 | | // Make these adjustments only if the user didn't set PlayResX. |
633 | 0 | if (override_playres) { |
634 | 0 | int vidw = dim->w - (dim->ml + dim->mr); |
635 | 0 | int vidh = dim->h - (dim->mt + dim->mb); |
636 | 0 | track->PlayResX = track->PlayResY * (double)vidw / MPMAX(vidh, 1); |
637 | | // ffmpeg and mpv use a default PlayResX of 384 when it is not known, |
638 | | // this comes from VSFilter. |
639 | 0 | double fix_margins = track->PlayResX / (double)MP_ASS_FONT_PLAYRESX; |
640 | 0 | for (int n = 0; n < track->n_styles; n++) { |
641 | 0 | track->styles[n].MarginL = lrint(track->styles[n].MarginL * fix_margins); |
642 | 0 | track->styles[n].MarginR = lrint(track->styles[n].MarginR * fix_margins); |
643 | 0 | track->styles[n].MarginV = lrint(track->styles[n].MarginV * set_font_scale); |
644 | 0 | } |
645 | 0 | } |
646 | 0 | } |
647 | 0 | } |
648 | | |
649 | | static bool has_overrides(char *s) |
650 | 0 | { |
651 | 0 | if (!s) |
652 | 0 | return false; |
653 | 0 | return strstr(s, "\\pos") || strstr(s, "\\move") || strstr(s, "\\clip") || |
654 | 0 | strstr(s, "\\iclip") || strstr(s, "\\org") || strstr(s, "\\p"); |
655 | 0 | } |
656 | | |
657 | 0 | #define END(ev) ((ev)->Start + (ev)->Duration) |
658 | | |
659 | | static long long find_timestamp(struct sd *sd, double pts) |
660 | 32 | { |
661 | 32 | struct sd_ass_priv *priv = sd->priv; |
662 | 32 | if (pts == MP_NOPTS_VALUE) |
663 | 0 | return 0; |
664 | | |
665 | 32 | long long ts = llrint(pts * 1000); |
666 | | |
667 | 32 | if (!sd->opts->sub_fix_timing || |
668 | 32 | sd->shared_opts->ass_style_override[sd->order] == ASS_STYLE_OVERRIDE_NONE) |
669 | 32 | return ts; |
670 | | |
671 | | // Try to fix small gaps and overlaps. |
672 | 0 | ASS_Track *track = priv->ass_track; |
673 | 0 | int threshold = SUB_GAP_THRESHOLD * 1000; |
674 | 0 | int keep = SUB_GAP_KEEP * 1000; |
675 | | |
676 | | // Find the "current" event. |
677 | 0 | ASS_Event *ev[2] = {0}; |
678 | 0 | int n_ev = 0; |
679 | 0 | for (int n = 0; n < track->n_events; n++) { |
680 | 0 | ASS_Event *event = &track->events[n]; |
681 | 0 | if (ts >= event->Start - threshold && ts <= END(event) + threshold) { |
682 | 0 | if (n_ev >= MP_ARRAY_SIZE(ev)) |
683 | 0 | return ts; // multiple overlaps - give up (probably complex subs) |
684 | 0 | ev[n_ev++] = event; |
685 | 0 | } |
686 | 0 | } |
687 | | |
688 | 0 | if (n_ev != 2) |
689 | 0 | return ts; |
690 | | |
691 | | // Simple/minor heuristic against destroying typesetting. |
692 | 0 | if (ev[0]->Style != ev[1]->Style || has_overrides(ev[0]->Text) || |
693 | 0 | has_overrides(ev[1]->Text)) |
694 | 0 | return ts; |
695 | | |
696 | | // Sort by start timestamps. |
697 | 0 | if (ev[0]->Start > ev[1]->Start) |
698 | 0 | MPSWAP(ASS_Event*, ev[0], ev[1]); |
699 | | |
700 | | // We want to fix partial overlaps only. |
701 | 0 | if (END(ev[0]) >= END(ev[1])) |
702 | 0 | return ts; |
703 | | |
704 | 0 | if (ev[0]->Duration < keep || ev[1]->Duration < keep) |
705 | 0 | return ts; |
706 | | |
707 | | // Gap between the events -> move ts to show the end of the first event. |
708 | 0 | if (ts >= END(ev[0]) && ts < ev[1]->Start && END(ev[0]) < ev[1]->Start && |
709 | 0 | END(ev[0]) + threshold >= ev[1]->Start) |
710 | 0 | return END(ev[0]) - 1; |
711 | | |
712 | | // Overlap -> move ts to the (exclusive) end of the first event. |
713 | | // Relies on the fact that the ASS_Renderer has no overlap registered, even |
714 | | // if there is one. This happens to work because we never render the |
715 | | // overlapped state, and libass never resolves a collision. |
716 | 0 | if (ts >= ev[1]->Start && ts <= END(ev[0]) && END(ev[0]) > ev[1]->Start && |
717 | 0 | END(ev[0]) <= ev[1]->Start + threshold) |
718 | 0 | return END(ev[0]); |
719 | | |
720 | 0 | return ts; |
721 | 0 | } |
722 | | |
723 | | #undef END |
724 | | |
725 | | static struct sub_bitmaps *get_bitmaps(struct sd *sd, struct mp_osd_res dim, |
726 | | int format, double pts) |
727 | 0 | { |
728 | 0 | struct sd_ass_priv *ctx = sd->priv; |
729 | 0 | struct mp_subtitle_opts *opts = sd->opts; |
730 | 0 | struct mp_subtitle_shared_opts *shared_opts = sd->shared_opts; |
731 | 0 | bool no_ass = !opts->ass_enabled || |
732 | 0 | shared_opts->ass_style_override[sd->order] == ASS_STYLE_OVERRIDE_STRIP; |
733 | 0 | bool converted = (ctx->is_converted && !lavc_conv_is_styled(ctx->converter)) || no_ass; |
734 | 0 | ASS_Track *track = no_ass ? ctx->shadow_track : ctx->ass_track; |
735 | 0 | ASS_Renderer *renderer = ctx->ass_renderer; |
736 | 0 | struct sub_bitmaps *res = &(struct sub_bitmaps){0}; |
737 | | |
738 | | // Always update the osd_res |
739 | 0 | struct mp_osd_res old_osd = ctx->osd; |
740 | 0 | ctx->osd = dim; |
741 | |
|
742 | 0 | if (pts == MP_NOPTS_VALUE || !renderer) |
743 | 0 | goto done; |
744 | | |
745 | | // Currently no supported text sub formats support a distinction between forced |
746 | | // and unforced lines, so we just assume everything's unforced and discard everything. |
747 | | // If we ever see a format that makes this distinction, we can add support here. |
748 | 0 | if (opts->sub_forced_events_only) |
749 | 0 | goto done; |
750 | | |
751 | 0 | double scale = dim.display_par; |
752 | 0 | if (!converted && (!shared_opts->ass_style_override[sd->order] || |
753 | 0 | opts->ass_use_video_data >= 1)) |
754 | 0 | { |
755 | | // Let's factor in video PAR for vsfilter compatibility: |
756 | 0 | double par = opts->ass_video_aspect > 0 ? |
757 | 0 | opts->ass_video_aspect : |
758 | 0 | ctx->video_params.p_w / (double)ctx->video_params.p_h; |
759 | 0 | if (isnormal(par)) |
760 | 0 | scale *= par; |
761 | 0 | } |
762 | 0 | if (!ctx->ass_configured || !osd_res_equals(old_osd, ctx->osd)) { |
763 | 0 | configure_ass(sd, &dim, converted, track); |
764 | 0 | ctx->ass_configured = true; |
765 | 0 | } |
766 | 0 | ass_set_pixel_aspect(renderer, scale); |
767 | 0 | if (!converted && (!shared_opts->ass_style_override[sd->order] || |
768 | 0 | opts->ass_use_video_data >= 2)) |
769 | 0 | { |
770 | 0 | ass_set_storage_size(renderer, ctx->video_params.w, ctx->video_params.h); |
771 | 0 | } else { |
772 | 0 | ass_set_storage_size(renderer, 0, 0); |
773 | 0 | } |
774 | 0 | long long ts = find_timestamp(sd, pts); |
775 | |
|
776 | 0 | if (no_ass) |
777 | 0 | fill_plaintext(sd, pts); |
778 | |
|
779 | 0 | int changed; |
780 | 0 | ASS_Image *imgs = ass_render_frame(renderer, track, ts, &changed); |
781 | 0 | mp_ass_packer_pack(ctx->packer, &imgs, 1, changed, !converted, format, res); |
782 | |
|
783 | 0 | done: |
784 | | // mangle_colors() modifies the color field, so copy the thing _before_. |
785 | 0 | res = sub_bitmaps_copy(&ctx->copy_cache, res); |
786 | |
|
787 | 0 | if (!converted && res) |
788 | 0 | mangle_colors(sd, res); |
789 | |
|
790 | 0 | return res; |
791 | 0 | } |
792 | | |
793 | | #define MAX_BUF_SIZE 1024 * 1024 |
794 | | #define MIN_EXPAND_SIZE 4096 |
795 | | |
796 | | static void append(bstr *b, char c) |
797 | 188 | { |
798 | 188 | bstr_xappend(NULL, b, (bstr){&c, 1}); |
799 | 188 | } |
800 | | |
801 | | static void ass_to_plaintext(bstr *b, const char *in) |
802 | 8 | { |
803 | 8 | const char *open_tag_pos = NULL; |
804 | 8 | bool in_drawing = false; |
805 | 413 | while (*in) { |
806 | 405 | if (open_tag_pos) { |
807 | 221 | if (in[0] == '}') { |
808 | 3 | in += 1; |
809 | 3 | open_tag_pos = NULL; |
810 | 218 | } else if (in[0] == '\\' && in[1] == 'p' && in[2] != 'o') { |
811 | 0 | in += 2; |
812 | | // Skip text between \pN and \p0 tags. A \p without a number |
813 | | // is the same as \p0, and leading 0s are also allowed. |
814 | 0 | in_drawing = false; |
815 | 0 | while (in[0] >= '0' && in[0] <= '9') { |
816 | 0 | if (in[0] != '0') |
817 | 0 | in_drawing = true; |
818 | 0 | in += 1; |
819 | 0 | } |
820 | 218 | } else { |
821 | 218 | in += 1; |
822 | 218 | } |
823 | 221 | } else { |
824 | 184 | if (in[0] == '\\' && (in[1] == 'N' || in[1] == 'n')) { |
825 | 0 | in += 2; |
826 | 0 | append(b, '\n'); |
827 | 184 | } else if (in[0] == '\\' && in[1] == 'h') { |
828 | 0 | in += 2; |
829 | 0 | append(b, ' '); |
830 | 184 | } else if (in[0] == '{') { |
831 | 4 | open_tag_pos = in; |
832 | 4 | in += 1; |
833 | 180 | } else { |
834 | 180 | if (!in_drawing) |
835 | 180 | append(b, in[0]); |
836 | 180 | in += 1; |
837 | 180 | } |
838 | 184 | } |
839 | 405 | } |
840 | | // A '{' without a closing '}' is always visible. |
841 | 8 | if (open_tag_pos) { |
842 | 1 | bstr_xappend(NULL, b, bstr0(open_tag_pos)); |
843 | 1 | } |
844 | 8 | } |
845 | | |
846 | | // Empty string counts as whitespace. |
847 | | static bool is_whitespace_only(bstr b) |
848 | 8 | { |
849 | 8 | for (int n = 0; n < b.len; n++) { |
850 | 8 | if (b.start[n] != ' ' && b.start[n] != '\t') |
851 | 8 | return false; |
852 | 8 | } |
853 | 0 | return true; |
854 | 8 | } |
855 | | |
856 | | static bstr get_text_buf(struct sd *sd, double pts, enum sd_text_type type) |
857 | 137 | { |
858 | 137 | struct sd_ass_priv *ctx = sd->priv; |
859 | 137 | ASS_Track *track = ctx->ass_track; |
860 | | |
861 | 137 | if (pts == MP_NOPTS_VALUE) |
862 | 105 | return (bstr){0}; |
863 | 32 | long long ipts = find_timestamp(sd, pts); |
864 | | |
865 | 32 | bstr *b = &ctx->last_text; |
866 | | |
867 | 32 | if (!b->start) |
868 | 13 | b->start = talloc_size(ctx, 4096); |
869 | | |
870 | 32 | b->len = 0; |
871 | | |
872 | 70 | for (int i = 0; i < track->n_events; ++i) { |
873 | 38 | ASS_Event *event = track->events + i; |
874 | 38 | if (ipts >= event->Start && ipts < event->Start + event->Duration) { |
875 | 8 | if (event->Text) { |
876 | 8 | int start = b->len; |
877 | 8 | if (type == SD_TEXT_TYPE_PLAIN) { |
878 | 8 | ass_to_plaintext(b, event->Text); |
879 | 8 | } else if (type == SD_TEXT_TYPE_ASS_FULL) { |
880 | 0 | long long s = event->Start; |
881 | 0 | long long e = s + event->Duration; |
882 | |
|
883 | 0 | ASS_Style *style = (event->Style < 0 || event->Style >= track->n_styles) ? NULL : &track->styles[event->Style]; |
884 | |
|
885 | 0 | int sh = (s / 60 / 60 / 1000); |
886 | 0 | int sm = (s / 60 / 1000) % 60; |
887 | 0 | int ss = (s / 1000) % 60; |
888 | 0 | int sc = (s / 10) % 100; |
889 | 0 | int eh = (e / 60 / 60 / 1000); |
890 | 0 | int em = (e / 60 / 1000) % 60; |
891 | 0 | int es = (e / 1000) % 60; |
892 | 0 | int ec = (e / 10) % 100; |
893 | |
|
894 | 0 | bstr_xappend_asprintf(NULL, b, "Dialogue: %d,%d:%02d:%02d.%02d,%d:%02d:%02d.%02d,%s,%s,%04d,%04d,%04d,%s,%s", |
895 | 0 | event->Layer, |
896 | 0 | sh, sm, ss, sc, |
897 | 0 | eh, em, es, ec, |
898 | 0 | (style && style->Name) ? style->Name : "", event->Name, |
899 | 0 | event->MarginL, event->MarginR, event->MarginV, |
900 | 0 | event->Effect, event->Text); |
901 | 0 | } else { |
902 | 0 | bstr_xappend(NULL, b, bstr0(event->Text)); |
903 | 0 | } |
904 | 8 | if (is_whitespace_only(bstr_cut(*b, start))) { |
905 | 0 | b->len = start; |
906 | 8 | } else { |
907 | 8 | append(b, '\n'); |
908 | 8 | } |
909 | 8 | } |
910 | 8 | } |
911 | 38 | } |
912 | | |
913 | 32 | bstr_eatend(b, (bstr)bstr0_lit("\n")); |
914 | | |
915 | 32 | return *b; |
916 | 137 | } |
917 | | |
918 | | static char *get_text(struct sd *sd, double pts, enum sd_text_type type) |
919 | 137 | { |
920 | 137 | return bstrto0(NULL, get_text_buf(sd, pts, type)); |
921 | 137 | } |
922 | | |
923 | | static struct sd_times get_times(struct sd *sd, double pts) |
924 | 0 | { |
925 | 0 | struct sd_ass_priv *ctx = sd->priv; |
926 | 0 | ASS_Track *track = ctx->ass_track; |
927 | 0 | struct sd_times res = { .start = MP_NOPTS_VALUE, .end = MP_NOPTS_VALUE }; |
928 | |
|
929 | 0 | if (pts == MP_NOPTS_VALUE) |
930 | 0 | return res; |
931 | | |
932 | 0 | long long ipts = find_timestamp(sd, pts); |
933 | |
|
934 | 0 | for (int i = 0; i < track->n_events; ++i) { |
935 | 0 | ASS_Event *event = track->events + i; |
936 | 0 | if (ipts >= event->Start && ipts < event->Start + event->Duration) { |
937 | 0 | double start = event->Start / 1000.0; |
938 | 0 | double end = event->Duration == UNKNOWN_DURATION ? |
939 | 0 | MP_NOPTS_VALUE : (event->Start + event->Duration) / 1000.0; |
940 | |
|
941 | 0 | if (res.start == MP_NOPTS_VALUE || res.start > start) |
942 | 0 | res.start = start; |
943 | |
|
944 | 0 | if (res.end == MP_NOPTS_VALUE || res.end < end) |
945 | 0 | res.end = end; |
946 | 0 | } |
947 | 0 | } |
948 | |
|
949 | 0 | return res; |
950 | 0 | } |
951 | | |
952 | | static void fill_plaintext(struct sd *sd, double pts) |
953 | 0 | { |
954 | 0 | struct sd_ass_priv *ctx = sd->priv; |
955 | 0 | ASS_Track *track = ctx->shadow_track; |
956 | |
|
957 | 0 | ass_flush_events(track); |
958 | |
|
959 | 0 | bstr text = get_text_buf(sd, pts, SD_TEXT_TYPE_PLAIN); |
960 | 0 | if (!text.len) |
961 | 0 | return; |
962 | | |
963 | 0 | bstr dst = {0}; |
964 | |
|
965 | 0 | while (text.len) { |
966 | 0 | if (*text.start == '{') { |
967 | 0 | bstr_xappend(NULL, &dst, bstr0("\\{")); |
968 | 0 | text = bstr_cut(text, 1); |
969 | 0 | } else if (*text.start == '\\') { |
970 | 0 | bstr_xappend(NULL, &dst, bstr0("\\")); |
971 | | // Break ASS escapes with U+2060 WORD JOINER |
972 | 0 | mp_append_utf8_bstr(NULL, &dst, 0x2060); |
973 | 0 | text = bstr_cut(text, 1); |
974 | 0 | } |
975 | |
|
976 | 0 | int i = bstrcspn(text, "{\\"); |
977 | 0 | bstr_xappend(NULL, &dst, (bstr){text.start, i}); |
978 | 0 | text = bstr_cut(text, i); |
979 | 0 | } |
980 | |
|
981 | 0 | if (!dst.start) |
982 | 0 | return; |
983 | | |
984 | 0 | int n = ass_alloc_event(track); |
985 | 0 | ASS_Event *event = track->events + n; |
986 | 0 | event->Start = 0; |
987 | 0 | event->Duration = INT_MAX; |
988 | 0 | event->Style = track->default_style; |
989 | 0 | event->Text = strdup(dst.start); |
990 | |
|
991 | 0 | talloc_free(dst.start); |
992 | 0 | } |
993 | | |
994 | | static void reset(struct sd *sd) |
995 | 1.98k | { |
996 | 1.98k | struct sd_ass_priv *ctx = sd->priv; |
997 | 1.98k | if (sd->opts->sub_clear_on_seek || ctx->clear_once) { |
998 | 0 | ass_flush_events(ctx->ass_track); |
999 | 0 | ctx->num_seen_packets = 0; |
1000 | 0 | sd->preload_ok = false; |
1001 | 0 | ctx->clear_once = false; |
1002 | 0 | } |
1003 | 1.98k | if (ctx->converter) |
1004 | 1.25k | lavc_conv_reset(ctx->converter); |
1005 | 1.98k | } |
1006 | | |
1007 | | static void uninit(struct sd *sd) |
1008 | 994 | { |
1009 | 994 | struct sd_ass_priv *ctx = sd->priv; |
1010 | | |
1011 | 994 | filters_destroy(sd); |
1012 | 994 | if (ctx->converter) |
1013 | 629 | lavc_conv_uninit(ctx->converter); |
1014 | 994 | assobjects_destroy(sd); |
1015 | 994 | talloc_free(ctx->copy_cache); |
1016 | 994 | } |
1017 | | |
1018 | | static int control(struct sd *sd, enum sd_ctrl cmd, void *arg) |
1019 | 39.1k | { |
1020 | 39.1k | struct sd_ass_priv *ctx = sd->priv; |
1021 | 39.1k | switch (cmd) { |
1022 | 0 | case SD_CTRL_SUB_STEP: { |
1023 | 0 | double *a = arg; |
1024 | 0 | long long ts = llrint(a[0] * 1000.0); |
1025 | 0 | long long res = ass_step_sub(ctx->ass_track, ts, a[1]); |
1026 | 0 | if (!res) |
1027 | 0 | return false; |
1028 | | // Try to account for overlapping durations |
1029 | 0 | a[0] += res / 1000.0 + SUB_SEEK_OFFSET; |
1030 | 0 | return true; |
1031 | 0 | } |
1032 | 20.6k | case SD_CTRL_SET_ANIMATED_CHECK: |
1033 | 20.6k | ctx->check_animated = *(bool *)arg; |
1034 | 20.6k | return CONTROL_OK; |
1035 | 18.5k | case SD_CTRL_SET_VIDEO_PARAMS: |
1036 | 18.5k | ctx->video_params = *(struct mp_image_params *)arg; |
1037 | 18.5k | return CONTROL_OK; |
1038 | 0 | case SD_CTRL_UPDATE_OPTS: { |
1039 | 0 | uint64_t flags = *(uint64_t *)arg; |
1040 | 0 | if (flags & UPDATE_SUB_FILT) { |
1041 | 0 | filters_destroy(sd); |
1042 | 0 | filters_init(sd); |
1043 | 0 | ctx->clear_once = true; // allow reloading on seeks |
1044 | 0 | reset(sd); |
1045 | 0 | } |
1046 | 0 | if (flags & UPDATE_SUB_HARD) { |
1047 | | // ass_track will be recreated, so clear duplicate cache |
1048 | 0 | ctx->clear_once = true; |
1049 | 0 | reset(sd); |
1050 | 0 | assobjects_destroy(sd); |
1051 | 0 | assobjects_init(sd); |
1052 | 0 | } |
1053 | 0 | ctx->ass_configured = false; // ass always needs to be reconfigured |
1054 | 0 | return CONTROL_OK; |
1055 | 0 | } |
1056 | 0 | default: |
1057 | 0 | return CONTROL_UNKNOWN; |
1058 | 39.1k | } |
1059 | 39.1k | } |
1060 | | |
1061 | | const struct sd_functions sd_ass = { |
1062 | | .name = "ass", |
1063 | | .accept_packets_in_advance = true, |
1064 | | .init = init, |
1065 | | .decode = decode, |
1066 | | .get_bitmaps = get_bitmaps, |
1067 | | .get_text = get_text, |
1068 | | .get_times = get_times, |
1069 | | .control = control, |
1070 | | .reset = reset, |
1071 | | .select = enable_output, |
1072 | | .uninit = uninit, |
1073 | | }; |
1074 | | |
1075 | | // Disgusting hack for (xy-)vsfilter color compatibility. |
1076 | | static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts) |
1077 | 0 | { |
1078 | 0 | struct mp_subtitle_opts *opts = sd->opts; |
1079 | 0 | struct sd_ass_priv *ctx = sd->priv; |
1080 | 0 | enum pl_color_system csp = 0; |
1081 | 0 | enum pl_color_levels levels = 0; |
1082 | 0 | if (opts->ass_vsfilter_color_compat == 0) // "no" |
1083 | 0 | return; |
1084 | 0 | bool force_601 = opts->ass_vsfilter_color_compat == 3; |
1085 | 0 | ASS_Track *track = ctx->ass_track; |
1086 | 0 | static const int ass_csp[] = { |
1087 | 0 | [YCBCR_BT601_TV] = PL_COLOR_SYSTEM_BT_601, |
1088 | 0 | [YCBCR_BT601_PC] = PL_COLOR_SYSTEM_BT_601, |
1089 | 0 | [YCBCR_BT709_TV] = PL_COLOR_SYSTEM_BT_709, |
1090 | 0 | [YCBCR_BT709_PC] = PL_COLOR_SYSTEM_BT_709, |
1091 | 0 | [YCBCR_SMPTE240M_TV] = PL_COLOR_SYSTEM_SMPTE_240M, |
1092 | 0 | [YCBCR_SMPTE240M_PC] = PL_COLOR_SYSTEM_SMPTE_240M, |
1093 | 0 | }; |
1094 | 0 | static const int ass_levels[] = { |
1095 | 0 | [YCBCR_BT601_TV] = PL_COLOR_LEVELS_LIMITED, |
1096 | 0 | [YCBCR_BT601_PC] = PL_COLOR_LEVELS_FULL, |
1097 | 0 | [YCBCR_BT709_TV] = PL_COLOR_LEVELS_LIMITED, |
1098 | 0 | [YCBCR_BT709_PC] = PL_COLOR_LEVELS_FULL, |
1099 | 0 | [YCBCR_SMPTE240M_TV] = PL_COLOR_LEVELS_LIMITED, |
1100 | 0 | [YCBCR_SMPTE240M_PC] = PL_COLOR_LEVELS_FULL, |
1101 | 0 | }; |
1102 | 0 | int trackcsp = track->YCbCrMatrix; |
1103 | 0 | if (force_601) |
1104 | 0 | trackcsp = YCBCR_BT601_TV; |
1105 | | // NONE is a bit random, but the intention is: don't modify colors. |
1106 | 0 | if (trackcsp == YCBCR_NONE) |
1107 | 0 | return; |
1108 | 0 | if (trackcsp < MP_ARRAY_SIZE(ass_csp)) |
1109 | 0 | csp = ass_csp[trackcsp]; |
1110 | 0 | if (trackcsp < MP_ARRAY_SIZE(ass_levels)) |
1111 | 0 | levels = ass_levels[trackcsp]; |
1112 | 0 | if (trackcsp == YCBCR_DEFAULT) { |
1113 | 0 | csp = PL_COLOR_SYSTEM_BT_601; |
1114 | 0 | levels = PL_COLOR_LEVELS_LIMITED; |
1115 | 0 | } |
1116 | | // Unknown colorspace (either YCBCR_UNKNOWN, or a valid value unknown to us) |
1117 | 0 | if (!csp || !levels) |
1118 | 0 | return; |
1119 | | |
1120 | 0 | struct mp_image_params params = ctx->video_params; |
1121 | |
|
1122 | 0 | if (force_601) { |
1123 | 0 | params.repr = (struct pl_color_repr){ |
1124 | 0 | .sys = PL_COLOR_SYSTEM_BT_709, |
1125 | 0 | .levels = PL_COLOR_LEVELS_LIMITED, |
1126 | 0 | }; |
1127 | 0 | } |
1128 | |
|
1129 | 0 | if ((csp == params.repr.sys && levels == params.repr.levels) || |
1130 | 0 | params.repr.sys == PL_COLOR_SYSTEM_RGB) // Even VSFilter doesn't mangle on RGB video |
1131 | 0 | return; |
1132 | | |
1133 | 0 | bool basic_conv = params.repr.sys == PL_COLOR_SYSTEM_BT_709 && |
1134 | 0 | params.repr.levels == PL_COLOR_LEVELS_LIMITED && |
1135 | 0 | csp == PL_COLOR_SYSTEM_BT_601 && |
1136 | 0 | levels == PL_COLOR_LEVELS_LIMITED; |
1137 | | |
1138 | | // With "basic", only do as much as needed for basic compatibility. |
1139 | 0 | if (opts->ass_vsfilter_color_compat == 1 && !basic_conv) |
1140 | 0 | return; |
1141 | | |
1142 | 0 | if (params.repr.sys != ctx->last_params.repr.sys || |
1143 | 0 | params.repr.levels != ctx->last_params.repr.levels) |
1144 | 0 | { |
1145 | 0 | int msgl = basic_conv ? MSGL_V : MSGL_WARN; |
1146 | 0 | ctx->last_params = params; |
1147 | 0 | MP_MSG(sd, msgl, "mangling colors like vsfilter: " |
1148 | 0 | "RGB -> %s %s -> %s %s -> RGB\n", |
1149 | 0 | m_opt_choice_str(pl_csp_names, csp), |
1150 | 0 | m_opt_choice_str(pl_csp_levels_names, levels), |
1151 | 0 | m_opt_choice_str(pl_csp_names, params.repr.sys), |
1152 | 0 | m_opt_choice_str(pl_csp_names, params.repr.levels)); |
1153 | 0 | } |
1154 | | |
1155 | | // Conversion that VSFilter would use |
1156 | 0 | struct mp_csp_params vs_params = MP_CSP_PARAMS_DEFAULTS; |
1157 | 0 | vs_params.repr.sys = csp; |
1158 | 0 | vs_params.repr.levels = levels; |
1159 | 0 | struct pl_transform3x3 vs_yuv2rgb; |
1160 | 0 | mp_get_csp_matrix(&vs_params, &vs_yuv2rgb); |
1161 | 0 | pl_transform3x3_invert(&vs_yuv2rgb); |
1162 | | |
1163 | | // Proper conversion to RGB |
1164 | 0 | struct mp_csp_params rgb_params = MP_CSP_PARAMS_DEFAULTS; |
1165 | 0 | rgb_params.repr = params.repr; |
1166 | 0 | rgb_params.color = params.color; |
1167 | 0 | struct pl_transform3x3 vs2rgb; |
1168 | 0 | mp_get_csp_matrix(&rgb_params, &vs2rgb); |
1169 | |
|
1170 | 0 | for (int n = 0; n < parts->num_parts; n++) { |
1171 | 0 | struct sub_bitmap *sb = &parts->parts[n]; |
1172 | 0 | uint32_t color = sb->libass.color; |
1173 | 0 | int r = (color >> 24u) & 0xff; |
1174 | 0 | int g = (color >> 16u) & 0xff; |
1175 | 0 | int b = (color >> 8u) & 0xff; |
1176 | 0 | int a = 0xff - (color & 0xff); |
1177 | 0 | int rgb[3] = {r, g, b}, yuv[3]; |
1178 | 0 | mp_map_fixp_color(&vs_yuv2rgb, 8, rgb, 8, yuv); |
1179 | 0 | mp_map_fixp_color(&vs2rgb, 8, yuv, 8, rgb); |
1180 | 0 | sb->libass.color = MP_ASS_RGBA(rgb[0], rgb[1], rgb[2], a); |
1181 | 0 | } |
1182 | 0 | } |
1183 | | |
1184 | | int sd_ass_fmt_offset(const char *evt_fmt) |
1185 | 0 | { |
1186 | | // "Text" is always last (as it's arbitrary content in buf), e.g. format: |
1187 | | // "Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text" |
1188 | 0 | int n = 0; |
1189 | 0 | while (evt_fmt && (evt_fmt = strchr(evt_fmt, ','))) |
1190 | 0 | evt_fmt++, n++; |
1191 | 0 | return n-1; // buffer is without the format's Start/End, with ReadOrder |
1192 | 0 | } |
1193 | | |
1194 | | bstr sd_ass_pkt_text(struct sd_filter *ft, struct demux_packet *pkt, int offset) |
1195 | 0 | { |
1196 | | // e.g. pkt->buffer ("4" is ReadOrder): "4,0,Default,,0,0,0,,fifth line" |
1197 | 0 | bstr txt = {(char *)pkt->buffer, pkt->len}, t0 = txt; |
1198 | 0 | while (offset-- > 0) { |
1199 | 0 | int n = bstrchr(txt, ','); |
1200 | 0 | if (n < 0) { // shouldn't happen |
1201 | 0 | MP_WARN(ft, "Malformed event '%.*s'\n", BSTR_P(t0)); |
1202 | 0 | return (bstr){NULL, 0}; |
1203 | 0 | } |
1204 | 0 | txt = bstr_cut(txt, n+1); |
1205 | 0 | } |
1206 | 0 | return txt; |
1207 | 0 | } |
1208 | | |
1209 | | bstr sd_ass_to_plaintext(char **out, const char *in) |
1210 | 0 | { |
1211 | 0 | bstr b = {*out}; |
1212 | 0 | ass_to_plaintext(&b, in); |
1213 | 0 | *out = b.start; |
1214 | 0 | return b; |
1215 | 0 | } |