mediautil.c (20510B)
1 /* 2 This file is part of libextractor. 3 Copyright (C) 2026 Vidyut Samanta and Christian Grothoff 4 5 libextractor is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published 7 by the Free Software Foundation; either version 3, or (at your 8 option) any later version. 9 10 libextractor is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with libextractor; see the file COPYING. If not, write to the 17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 Boston, MA 02110-1301, USA. 19 */ 20 /** 21 * @file plugins/mediautil.c 22 * @brief helpers shared by the video thumbnail and audio preview plugins 23 * @author Christian Grothoff 24 */ 25 #include "platform.h" 26 #include "mediautil.h" 27 #include <magic.h> 28 29 /** 30 * How many bytes we give libmagic to make up its mind. 31 */ 32 #define SNIFF_SIZE (16 * 1024) 33 34 35 void 36 EXTRACTOR_media_options_parse_ (const char *config, 37 struct EXTRACTOR_MediaOptions *opt) 38 { 39 const char *pos; 40 int explicit_deadline = 0; 41 42 opt->size = 128; 43 opt->png = 0; 44 opt->offset = 30; 45 opt->length = 15; 46 opt->bitrate = 24000; 47 opt->deadline = 10000; 48 opt->srt_lang[0] = '\0'; 49 opt->gallery = 0; 50 if (NULL == config) 51 return; 52 pos = config; 53 while ('\0' != *pos) 54 { 55 unsigned long long v; 56 char *endp; 57 58 if (0 == strncmp (pos, 59 "format=", 60 strlen ("format="))) 61 { 62 pos += strlen ("format="); 63 opt->png = (0 == strncmp (pos, 64 "png", 65 strlen ("png"))); 66 } 67 else if (0 == strncmp (pos, 68 "size=", 69 strlen ("size="))) 70 { 71 pos += strlen ("size="); 72 v = strtoull (pos, &endp, 10); 73 if ( (endp != pos) && 74 (v >= 8) && 75 (v <= 1024) ) 76 opt->size = (unsigned int) v; 77 } 78 else if (0 == strncmp (pos, 79 "offset=", 80 strlen ("offset="))) 81 { 82 pos += strlen ("offset="); 83 v = strtoull (pos, &endp, 10); 84 if ( (endp != pos) && 85 (v <= 24 * 60 * 60) ) 86 opt->offset = (unsigned int) v; 87 } 88 else if (0 == strncmp (pos, 89 "length=", 90 strlen ("length="))) 91 { 92 pos += strlen ("length="); 93 v = strtoull (pos, &endp, 10); 94 if ( (endp != pos) && 95 (v >= 1) && 96 (v <= 60) ) 97 opt->length = (unsigned int) v; 98 } 99 else if (0 == strncmp (pos, 100 "bitrate=", 101 strlen ("bitrate="))) 102 { 103 pos += strlen ("bitrate="); 104 v = strtoull (pos, &endp, 10); 105 if ( (endp != pos) && 106 (v >= 6000) && 107 (v <= 128000) ) 108 opt->bitrate = (unsigned int) v; 109 } 110 else if (0 == strncmp (pos, 111 "deadline=", 112 strlen ("deadline="))) 113 { 114 pos += strlen ("deadline="); 115 v = strtoull (pos, &endp, 10); 116 if ( (endp != pos) && 117 (v >= 100) && 118 (v <= 120000) ) 119 { 120 opt->deadline = (unsigned int) v; 121 explicit_deadline = 1; 122 } 123 } 124 else if ( (0 == strncmp (pos, 125 "srt:", 126 strlen ("srt:"))) || 127 (0 == strncmp (pos, 128 "srt=", 129 strlen ("srt="))) ) 130 { 131 size_t n; 132 133 pos += strlen ("srt:"); 134 n = strcspn (pos, ","); 135 if ( (n > 0) && 136 (n < sizeof (opt->srt_lang)) ) 137 { 138 memcpy (opt->srt_lang, 139 pos, 140 n); 141 opt->srt_lang[n] = '\0'; 142 } 143 } 144 else if ( (0 == strncmp (pos, 145 "gallery:", 146 strlen ("gallery:"))) || 147 (0 == strncmp (pos, 148 "gallery=", 149 strlen ("gallery="))) ) 150 { 151 pos += strlen ("gallery:"); 152 v = strtoull (pos, &endp, 10); 153 if ( (endp != pos) && 154 (v >= 1) ) 155 opt->gallery = (v > EXTRACTOR_MEDIA_MAX_GALLERY) 156 ? EXTRACTOR_MEDIA_MAX_GALLERY 157 : (unsigned int) v; 158 } 159 /* advance to the next assignment */ 160 pos = strchr (pos, ','); 161 if (NULL == pos) 162 break; 163 pos++; 164 } 165 /* a gallery or a full subtitle track means decoding across the 166 entire file, which the single-thumbnail budget does not cover */ 167 if ( (! explicit_deadline) && 168 ( (0 != opt->gallery) || 169 ('\0' != opt->srt_lang[0]) ) ) 170 opt->deadline = 60000; 171 } 172 173 174 int 175 EXTRACTOR_media_is_media_ (struct EXTRACTOR_ExtractContext *ec, 176 int want_audio) 177 { 178 magic_t magic; 179 void *data; 180 ssize_t iret; 181 const char *mime; 182 int ret; 183 184 if (-1 == (iret = ec->read (ec->cls, 185 &data, 186 SNIFF_SIZE))) 187 return 0; 188 ret = 0; 189 if (NULL == (magic = magic_open (MAGIC_MIME_TYPE))) 190 return 0; 191 if (0 == magic_load (magic, NULL)) 192 { 193 mime = magic_buffer (magic, 194 data, 195 iret); 196 if (NULL != mime) 197 { 198 if (0 == strncmp (mime, 199 "video/", 200 strlen ("video/"))) 201 ret = 1; 202 if ( (0 != want_audio) && 203 (0 == strncmp (mime, 204 "audio/", 205 strlen ("audio/"))) ) 206 ret = 1; 207 /* Ogg and Matroska files carrying video are frequently reported as 208 "application/ogg" or "video/x-matroska"; the former would be lost 209 here, so let it through and let the demuxer decide. */ 210 if (0 == strcmp (mime, 211 "application/ogg")) 212 ret = 1; 213 } 214 } 215 magic_close (magic); 216 if (0 != ec->seek (ec->cls, 217 0, 218 SEEK_SET)) 219 return 0; 220 return ret; 221 } 222 223 224 /** 225 * Current time in milliseconds since an arbitrary epoch. 226 * 227 * @return the time 228 */ 229 static uint64_t 230 now_ms (void) 231 { 232 struct timespec ts; 233 234 if (0 != clock_gettime (CLOCK_MONOTONIC, 235 &ts)) 236 return 0; 237 return ((uint64_t) ts.tv_sec) * 1000ULL 238 + ((uint64_t) ts.tv_nsec) / 1000000ULL; 239 } 240 241 242 void 243 EXTRACTOR_media_deadline_start_ (struct EXTRACTOR_MediaDeadline *dl, 244 unsigned int ms) 245 { 246 dl->expiration = now_ms () + ms; 247 } 248 249 250 int 251 EXTRACTOR_media_deadline_expired_ (const struct EXTRACTOR_MediaDeadline *dl) 252 { 253 return now_ms () >= dl->expiration; 254 } 255 256 257 uint64_t 258 EXTRACTOR_media_deadline_remaining_ (const struct EXTRACTOR_MediaDeadline *dl) 259 { 260 uint64_t now = now_ms (); 261 262 if (now >= dl->expiration) 263 return 0; 264 return dl->expiration - now; 265 } 266 267 268 int 269 EXTRACTOR_media_thumbnail_size_ (unsigned int width, 270 unsigned int height, 271 unsigned int box, 272 unsigned int *rwidth, 273 unsigned int *rheight) 274 { 275 uint64_t w = width; 276 uint64_t h = height; 277 278 if ( (0 == width) || 279 (0 == height) || 280 (width > EXTRACTOR_MEDIA_MAX_DIMENSION) || 281 (height > EXTRACTOR_MEDIA_MAX_DIMENSION) || 282 (((uint64_t) width) * height > EXTRACTOR_MEDIA_MAX_PIXELS) ) 283 return 0; 284 if (h > box) 285 { 286 w = w * box / h; 287 h = box; 288 } 289 if (w > box) 290 { 291 h = h * box / w; 292 w = box; 293 } 294 if ( (0 == w) || 295 (0 == h) ) 296 return 0; 297 /* many encoders dislike odd dimensions, and we lose nothing by rounding */ 298 w &= ~1ULL; 299 h &= ~1ULL; 300 if ( (0 == w) || 301 (0 == h) ) 302 return 0; 303 *rwidth = (unsigned int) w; 304 *rheight = (unsigned int) h; 305 return 1; 306 } 307 308 309 void 310 EXTRACTOR_media_fade_s16_ (int16_t *samples, 311 size_t num_samples, 312 size_t fade_samples) 313 { 314 size_t i; 315 316 if (0 == fade_samples) 317 return; 318 if (fade_samples * 2 > num_samples) 319 fade_samples = num_samples / 2; 320 for (i = 0; i < fade_samples; i++) 321 { 322 samples[i] = (int16_t) (((int32_t) samples[i]) * (int32_t) i 323 / (int32_t) fade_samples); 324 samples[num_samples - 1 - i] = 325 (int16_t) (((int32_t) samples[num_samples - 1 - i]) * (int32_t) i 326 / (int32_t) fade_samples); 327 } 328 } 329 330 331 /** 332 * ISO 639 codes of a language: 639-1, 639-2/T, 639-2/B. 333 */ 334 struct LanguageCodes 335 { 336 const char *iso1; 337 const char *iso2t; 338 const char *iso2b; 339 }; 340 341 /** 342 * Languages for which the three codes differ in ways a plain prefix 343 * comparison would not catch. Languages not listed here still match 344 * if the user gives the same code the stream uses. 345 */ 346 static const struct LanguageCodes languages[] = { 347 { "ar", "ara", "ara" }, { "bg", "bul", "bul" }, { "bn", "ben", "ben" }, 348 { "bo", "bod", "tib" }, { "ca", "cat", "cat" }, { "cs", "ces", "cze" }, 349 { "cy", "cym", "wel" }, { "da", "dan", "dan" }, { "de", "deu", "ger" }, 350 { "el", "ell", "gre" }, { "en", "eng", "eng" }, { "eo", "epo", "epo" }, 351 { "es", "spa", "spa" }, { "et", "est", "est" }, { "eu", "eus", "baq" }, 352 { "fa", "fas", "per" }, { "fi", "fin", "fin" }, { "fr", "fra", "fre" }, 353 { "ga", "gle", "gle" }, { "gl", "glg", "glg" }, { "he", "heb", "heb" }, 354 { "hi", "hin", "hin" }, { "hr", "hrv", "hrv" }, { "hu", "hun", "hun" }, 355 { "hy", "hye", "arm" }, { "id", "ind", "ind" }, { "is", "isl", "ice" }, 356 { "it", "ita", "ita" }, { "ja", "jpn", "jpn" }, { "ka", "kat", "geo" }, 357 { "ko", "kor", "kor" }, { "lt", "lit", "lit" }, { "lv", "lav", "lav" }, 358 { "mk", "mkd", "mac" }, { "ms", "msa", "may" }, { "mt", "mlt", "mlt" }, 359 { "my", "mya", "bur" }, { "nb", "nob", "nob" }, { "nl", "nld", "dut" }, 360 { "nn", "nno", "nno" }, { "no", "nor", "nor" }, { "pl", "pol", "pol" }, 361 { "pt", "por", "por" }, { "ro", "ron", "rum" }, { "ru", "rus", "rus" }, 362 { "sk", "slk", "slo" }, { "sl", "slv", "slv" }, { "sq", "sqi", "alb" }, 363 { "sr", "srp", "srp" }, { "sv", "swe", "swe" }, { "sw", "swa", "swa" }, 364 { "ta", "tam", "tam" }, { "th", "tha", "tha" }, { "tr", "tur", "tur" }, 365 { "uk", "ukr", "ukr" }, { "ur", "urd", "urd" }, { "vi", "vie", "vie" }, 366 { "zh", "zho", "chi" }, 367 { NULL, NULL, NULL } 368 }; 369 370 371 /** 372 * Reduce a language tag to its primary subtag, lower-cased. 373 * 374 * @param tag language tag, e.g. "de-AT" 375 * @param[out] out buffer for the primary subtag 376 * @param out_size size of @a out 377 */ 378 static void 379 primary_subtag (const char *tag, 380 char *out, 381 size_t out_size) 382 { 383 size_t i; 384 385 for (i = 0; (i + 1 < out_size) && ('\0' != tag[i]); i++) 386 { 387 if ( ('-' == tag[i]) || 388 ('_' == tag[i]) ) 389 break; 390 out[i] = (char) tolower ((unsigned char) tag[i]); 391 } 392 out[i] = '\0'; 393 } 394 395 396 /** 397 * Map a primary language subtag to its row in #languages. 398 * 399 * @param code lower-case 2- or 3-letter code 400 * @return index into #languages, -1 if unknown 401 */ 402 static int 403 language_index (const char *code) 404 { 405 unsigned int i; 406 407 for (i = 0; NULL != languages[i].iso1; i++) 408 if ( (0 == strcmp (code, languages[i].iso1)) || 409 (0 == strcmp (code, languages[i].iso2t)) || 410 (0 == strcmp (code, languages[i].iso2b)) ) 411 return (int) i; 412 return -1; 413 } 414 415 416 int 417 EXTRACTOR_media_lang_matches_ (const char *want, 418 const char *have) 419 { 420 char w[16]; 421 char h[16]; 422 int wi; 423 424 if ( (NULL == want) || 425 (NULL == have) ) 426 return 0; 427 primary_subtag (want, w, sizeof (w)); 428 primary_subtag (have, h, sizeof (h)); 429 if ( ('\0' == w[0]) || 430 ('\0' == h[0]) ) 431 return 0; 432 if (0 == strcmp (w, h)) 433 return 1; 434 wi = language_index (w); 435 return ( (-1 != wi) && 436 (wi == language_index (h)) ) ? 1 : 0; 437 } 438 439 440 /** 441 * Largest spread of a channel (in levels out of 256) that still 442 * counts as "narrow". 443 */ 444 #define FLAT_RANGE 32 445 446 /** 447 * Upper bound on the number of pixels we look at per frame. 448 */ 449 #define FLAT_SAMPLES (256 * 1024) 450 451 452 int 453 EXTRACTOR_media_frame_is_flat_ (const uint8_t *rgb, 454 unsigned int width, 455 unsigned int height, 456 size_t stride) 457 { 458 uint32_t hist[3][256]; 459 uint64_t total; 460 uint64_t need; 461 unsigned int step; 462 unsigned int x; 463 unsigned int y; 464 unsigned int c; 465 466 if ( (0 == width) || 467 (0 == height) ) 468 return 1; 469 memset (hist, 0, sizeof (hist)); 470 step = 1; 471 while ( ((uint64_t) width / step) * ((uint64_t) height / step) 472 > FLAT_SAMPLES) 473 step++; 474 total = 0; 475 for (y = 0; y < height; y += step) 476 { 477 const uint8_t *row = rgb + (size_t) y * stride; 478 479 for (x = 0; x < width; x += step) 480 { 481 hist[0][row[3 * x]]++; 482 hist[1][row[3 * x + 1]]++; 483 hist[2][row[3 * x + 2]]++; 484 total++; 485 } 486 } 487 need = (total * 99 + 99) / 100; 488 for (c = 0; c < 3; c++) 489 { 490 uint64_t win = 0; 491 uint64_t best = 0; 492 unsigned int i; 493 494 /* largest population of any FLAT_RANGE-wide window */ 495 for (i = 0; i < 256; i++) 496 { 497 win += hist[c][i]; 498 if (i >= FLAT_RANGE) 499 win -= hist[c][i - FLAT_RANGE]; 500 if (win > best) 501 best = win; 502 } 503 if (best < need) 504 return 0; 505 } 506 return 1; 507 } 508 509 510 void 511 EXTRACTOR_media_gallery_layout_ (unsigned int n, 512 unsigned int tile_w, 513 unsigned int tile_h, 514 unsigned int *cols, 515 unsigned int *rows) 516 { 517 const double target = 16.0 / 9.0; 518 double best_score = -1; 519 unsigned int best_waste = 0; 520 unsigned int c; 521 522 if (0 == n) 523 n = 1; 524 if (0 == tile_w) 525 tile_w = 1; 526 if (0 == tile_h) 527 tile_h = 1; 528 *cols = n; 529 *rows = 1; 530 for (c = 1; c <= n; c++) 531 { 532 unsigned int r = (n + c - 1) / c; 533 double aspect = ((double) c * tile_w) / ((double) r * tile_h); 534 double score = (aspect > target) ? aspect / target : target / aspect; 535 unsigned int waste = c * r - n; 536 537 if ( (best_score < 0) || 538 (score < best_score - 1e-9) || 539 ( (score < best_score + 1e-9) && 540 (waste < best_waste) ) ) 541 { 542 best_score = score; 543 best_waste = waste; 544 *cols = c; 545 *rows = r; 546 } 547 } 548 } 549 550 551 uint64_t 552 EXTRACTOR_media_gallery_time_ (unsigned int i, 553 unsigned int n, 554 uint64_t duration_ms) 555 { 556 if (0 == n) 557 return 0; 558 return (duration_ms / n) * i + duration_ms / (2 * (uint64_t) n); 559 } 560 561 562 /** 563 * Append @a len bytes to @a srt. 564 * 565 * @param srt file to append to 566 * @param data bytes to append 567 * @param len number of bytes 568 * @return 0 on success, -1 if full 569 */ 570 static int 571 srt_put (struct EXTRACTOR_MediaSrt *srt, 572 const char *data, 573 size_t len) 574 { 575 if (srt->full) 576 return -1; 577 if (srt->len + len + 1 > EXTRACTOR_MEDIA_MAX_SRT) 578 { 579 srt->full = 1; 580 return -1; 581 } 582 if (srt->len + len + 1 > srt->cap) 583 { 584 size_t ncap = (0 == srt->cap) ? 4096 : srt->cap; 585 char *nbuf; 586 587 while (ncap < srt->len + len + 1) 588 ncap *= 2; 589 if (ncap > EXTRACTOR_MEDIA_MAX_SRT) 590 ncap = EXTRACTOR_MEDIA_MAX_SRT; 591 nbuf = realloc (srt->buf, ncap); 592 if (NULL == nbuf) 593 { 594 srt->full = 1; 595 return -1; 596 } 597 srt->buf = nbuf; 598 srt->cap = ncap; 599 } 600 memcpy (&srt->buf[srt->len], data, len); 601 srt->len += len; 602 srt->buf[srt->len] = '\0'; 603 return 0; 604 } 605 606 607 /** 608 * Length of the valid UTF-8 sequence at @a s. 609 * 610 * @param s input 611 * @param len bytes available, at least 1 612 * @return length of the sequence, 0 if invalid 613 */ 614 static size_t 615 utf8_seq_len (const unsigned char *s, 616 size_t len) 617 { 618 size_t n; 619 size_t i; 620 uint32_t cp; 621 622 if (s[0] < 0x80) 623 return 1; 624 if ((s[0] & 0xE0) == 0xC0) 625 n = 2, cp = s[0] & 0x1F; 626 else if ((s[0] & 0xF0) == 0xE0) 627 n = 3, cp = s[0] & 0x0F; 628 else if ((s[0] & 0xF8) == 0xF0) 629 n = 4, cp = s[0] & 0x07; 630 else 631 return 0; 632 if (n > len) 633 return 0; 634 for (i = 1; i < n; i++) 635 { 636 if ((s[i] & 0xC0) != 0x80) 637 return 0; 638 cp = (cp << 6) | (s[i] & 0x3F); 639 } 640 if ( ( (2 == n) && (cp < 0x80) ) || 641 ( (3 == n) && (cp < 0x800) ) || 642 ( (4 == n) && (cp < 0x10000) ) || 643 (cp > 0x10FFFF) || 644 ( (cp >= 0xD800) && (cp <= 0xDFFF) ) ) 645 return 0; 646 return n; 647 } 648 649 650 /** 651 * Format @a ms as "HH:MM:SS,mmm". 652 * 653 * @param ms time in milliseconds 654 * @param[out] out buffer of at least 32 bytes 655 */ 656 static void 657 srt_time (uint64_t ms, 658 char out[32]) 659 { 660 snprintf (out, 661 32, 662 "%02llu:%02llu:%02llu,%03llu", 663 (unsigned long long) (ms / 3600000), 664 (unsigned long long) ((ms / 60000) % 60), 665 (unsigned long long) ((ms / 1000) % 60), 666 (unsigned long long) (ms % 1000)); 667 } 668 669 670 void 671 EXTRACTOR_media_srt_add_ (struct EXTRACTOR_MediaSrt *srt, 672 uint64_t start_ms, 673 uint64_t end_ms, 674 const char *text, 675 size_t text_len, 676 int is_ass) 677 { 678 const unsigned char *in = (const unsigned char *) text; 679 char *clean; 680 size_t i; 681 size_t o; 682 size_t line_start; 683 char hdr[96]; 684 char t1[32]; 685 char t2[32]; 686 687 if ( (srt->full) || 688 (NULL == text) ) 689 return; 690 i = 0; 691 if (is_ass) 692 { 693 unsigned int commas = 8; 694 695 if ( (text_len >= 9) && 696 (0 == strncmp (text, "Dialogue:", 9)) ) 697 commas = 9; 698 while ( (i < text_len) && 699 (commas > 0) ) 700 if (',' == in[i++]) 701 commas--; 702 } 703 clean = malloc (text_len - i + 1); 704 if (NULL == clean) 705 return; 706 o = 0; 707 line_start = 0; 708 while ( (i < text_len) && 709 ('\0' != in[i]) ) 710 { 711 size_t n; 712 713 if (is_ass && 714 ('{' == in[i]) ) 715 { 716 /* ASS override block */ 717 while ( (i < text_len) && ('}' != in[i]) ) 718 i++; 719 i++; 720 continue; 721 } 722 if (is_ass && 723 ('\\' == in[i]) && 724 (i + 1 < text_len) && 725 ( ('N' == in[i + 1]) || ('n' == in[i + 1]) || 726 ('h' == in[i + 1]) ) ) 727 { 728 clean[o++] = ('h' == in[i + 1]) ? ' ' : '\n'; 729 i += 2; 730 } 731 else if ('\r' == in[i]) 732 { 733 i++; 734 continue; 735 } 736 else 737 { 738 n = utf8_seq_len (&in[i], text_len - i); 739 if (0 == n) 740 { 741 clean[o++] = '?'; 742 i++; 743 } 744 else 745 { 746 memcpy (&clean[o], &in[i], n); 747 o += n; 748 i += n; 749 } 750 } 751 if ('\n' == clean[o - 1]) 752 { 753 /* drop trailing blanks, and the line itself if it is empty */ 754 size_t e = o - 1; 755 756 while ( (e > line_start) && 757 ( (' ' == clean[e - 1]) || ('\t' == clean[e - 1]) ) ) 758 e--; 759 if (e == line_start) 760 o = line_start; 761 else 762 { 763 clean[e] = '\n'; 764 o = e + 1; 765 line_start = o; 766 } 767 } 768 } 769 while ( (o > 0) && 770 ( ('\n' == clean[o - 1]) || (' ' == clean[o - 1]) || 771 ('\t' == clean[o - 1]) ) ) 772 o--; 773 if (0 == o) 774 { 775 free (clean); 776 return; 777 } 778 if (end_ms < start_ms) 779 end_ms = start_ms; 780 srt_time (start_ms, t1); 781 srt_time (end_ms, t2); 782 snprintf (hdr, 783 sizeof (hdr), 784 "%u\n%s --> %s\n", 785 srt->count + 1, 786 t1, 787 t2); 788 { 789 size_t old_len = srt->len; 790 791 if ( (0 == srt_put (srt, hdr, strlen (hdr))) && 792 (0 == srt_put (srt, clean, o)) && 793 (0 == srt_put (srt, "\n\n", 2)) ) 794 { 795 srt->count++; 796 } 797 else if (NULL != srt->buf) 798 { 799 /* never emit a partial cue */ 800 srt->len = old_len; 801 srt->buf[old_len] = '\0'; 802 } 803 } 804 free (clean); 805 } 806 807 808 int 809 EXTRACTOR_media_srt_emit_ (const struct EXTRACTOR_MediaSrt *srt, 810 struct EXTRACTOR_ExtractContext *ec, 811 const char *plugin_name) 812 { 813 if ( (0 == srt->count) || 814 (0 == srt->len) ) 815 return 0; 816 return ec->proc (ec->cls, 817 plugin_name, 818 EXTRACTOR_METATYPE_SUBTITLE_TEXT, 819 EXTRACTOR_METAFORMAT_UTF8, 820 "application/x-subrip", 821 srt->buf, 822 srt->len + 1); 823 } 824 825 826 void 827 EXTRACTOR_media_srt_clear_ (struct EXTRACTOR_MediaSrt *srt) 828 { 829 free (srt->buf); 830 memset (srt, 0, sizeof (*srt)); 831 } 832 833 834 /* end of mediautil.c */