test_thumbnailffmpeg.c (21468B)
1 /* 2 This file is part of libextractor. 3 Copyright (C) 2013, 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/test_thumbnailffmpeg.c 22 * @brief testcase for the thumbnailffmpeg plugin 23 * @author Christian Grothoff 24 * 25 * The encoder's output depends on the FFmpeg version, so the JPEG can 26 * only be checked for its magic number. The PNG round, on the other 27 * hand, is decoded: testdata/thumbnail_pattern.ogv is black for the 28 * first 20 seconds and red afterwards, so the colour of the thumbnail 29 * says whether the plugin really sampled where it claims to. 30 * 31 * testdata/subtitles.mkv (see contrib/gen_testmedia.sh) exercises the 32 * subtitle languages, the "srt:LANG" dump and the "gallery:N" contact 33 * sheet. For those, every item the plugin emits is collected, so that 34 * the tests can also check what must NOT be there. 35 */ 36 #include "platform.h" 37 #include "test_lib.h" 38 #include "test_media_lib.h" 39 40 41 /** 42 * Check that the thumbnail was taken from the red part of the video. 43 * 44 * @param data the PNG the plugin produced 45 * @param data_len number of bytes in @a data 46 * @return non-zero if the thumbnail looks right 47 */ 48 static int 49 validate_red (const void *data, 50 size_t data_len) 51 { 52 return TEST_media_png_center_is (data, 53 data_len, 54 0xFE, 55 0x00, 56 0x00); 57 } 58 59 60 /** 61 * The subtitled test video. 62 */ 63 #define SUBTITLES_MKV "testdata/subtitles.mkv" 64 65 /** 66 * Upper bound for a gallery, see EXTRACTOR_MEDIA_MAX_GALLERY_BYTES. 67 */ 68 #define MAX_GALLERY_BYTES (4 * 1024 * 1024) 69 70 /** 71 * Upper bound for the pixels of a gallery sheet (the plugin's 72 * GALLERY_MAX_SHEET_PIXELS). 73 */ 74 #define MAX_GALLERY_PIXELS (4 * 1024 * 1024) 75 76 /** 77 * Most items we collect from one run. 78 */ 79 #define MAX_ITEMS 32 80 81 /** 82 * Expected SRT dump of the English track. 83 */ 84 static const char srt_en[] = 85 "1\n" 86 "00:00:01,000 --> 00:00:04,000\n" 87 "Hello, world!\n" 88 "\n" 89 "2\n" 90 "00:00:12,500 --> 00:00:15,250\n" 91 "This is the second line\n" 92 "spanning two rows.\n" 93 "\n" 94 "3\n" 95 "00:00:33,000 --> 00:00:38,000\n" 96 "Goodbye.\n" 97 "\n"; 98 99 /** 100 * Expected SRT dump of the German track. 101 */ 102 static const char srt_de[] = 103 "1\n" 104 "00:00:01,000 --> 00:00:04,000\n" 105 "Gr\xc3\xbc\xc3\x9f Gott, Welt!\n" 106 "\n" 107 "2\n" 108 "00:00:12,500 --> 00:00:15,250\n" 109 "Das ist die zweite Zeile\n" 110 "\xc3\xbc" "ber zwei Reihen.\n" 111 "\n" 112 "3\n" 113 "00:00:33,000 --> 00:00:38,000\n" 114 "Auf Wiedersehen.\n" 115 "\n"; 116 117 /** 118 * Expected SRT dump of the French (ASS) track: override tags and the 119 * ASS fields are gone, "\N" became a line break. 120 */ 121 static const char srt_fr[] = 122 "1\n" 123 "00:00:01,000 --> 00:00:04,000\n" 124 "Bonjour, le monde !\n" 125 "\n" 126 "2\n" 127 "00:00:12,500 --> 00:00:15,250\n" 128 "Deuxi\xc3\xa8me ligne\n" 129 "sur deux rangs.\n" 130 "\n"; 131 132 133 /** 134 * One item the plugin emitted. 135 */ 136 struct Item 137 { 138 /** 139 * Type of the item. 140 */ 141 enum EXTRACTOR_MetaType type; 142 143 /** 144 * Format of the item. 145 */ 146 enum EXTRACTOR_MetaFormat format; 147 148 /** 149 * Mime type, "" if none. 150 */ 151 char mime[64]; 152 153 /** 154 * Copy of the data. 155 */ 156 char *data; 157 158 /** 159 * Number of bytes in @e data. 160 */ 161 size_t data_len; 162 }; 163 164 165 /** 166 * Everything the plugin emitted for one file. 167 */ 168 struct Items 169 { 170 /** 171 * The items, in the order they were emitted. 172 */ 173 struct Item item[MAX_ITEMS]; 174 175 /** 176 * Number of valid entries in @e item. 177 */ 178 unsigned int count; 179 180 /** 181 * Set if there were more than #MAX_ITEMS. 182 */ 183 int overflow; 184 }; 185 186 187 /** 188 * Collect an item. Signature of #EXTRACTOR_MetaDataProcessor. 189 * 190 * @param cls our `struct Items` 191 * @param plugin_name name of the plugin 192 * @param type type of the item 193 * @param format format of the item 194 * @param data_mime_type mime type of the item 195 * @param data the item 196 * @param data_len number of bytes in @a data 197 * @return 0 (always continue) 198 */ 199 static int 200 collect (void *cls, 201 const char *plugin_name, 202 enum EXTRACTOR_MetaType type, 203 enum EXTRACTOR_MetaFormat format, 204 const char *data_mime_type, 205 const char *data, 206 size_t data_len) 207 { 208 struct Items *items = cls; 209 struct Item *it; 210 211 (void) plugin_name; 212 if (items->count == MAX_ITEMS) 213 { 214 items->overflow = 1; 215 return 0; 216 } 217 it = &items->item[items->count]; 218 it->type = type; 219 it->format = format; 220 snprintf (it->mime, 221 sizeof (it->mime), 222 "%s", 223 (NULL == data_mime_type) ? "" : data_mime_type); 224 if (NULL == (it->data = malloc (data_len + 1))) 225 abort (); 226 memcpy (it->data, 227 data, 228 data_len); 229 it->data[data_len] = '\0'; 230 it->data_len = data_len; 231 items->count++; 232 return 0; 233 } 234 235 236 /** 237 * Release what collect() allocated. 238 * 239 * @param[in,out] items items to release 240 */ 241 static void 242 items_clear (struct Items *items) 243 { 244 unsigned int i; 245 246 for (i = 0; i < items->count; i++) 247 free (items->item[i].data); 248 memset (items, 0, sizeof (*items)); 249 } 250 251 252 /** 253 * Run the plugin with configuration @a config on @a filename and 254 * collect everything it emits. 255 * 256 * @param config the plugin with its options, e.g. "thumbnailffmpeg(srt:de)" 257 * @param in_process 1 to run the plugin in-process 258 * @param filename file to extract from 259 * @param[out] items set to what the plugin emitted 260 */ 261 static void 262 run_collect (const char *config, 263 int in_process, 264 const char *filename, 265 struct Items *items) 266 { 267 struct EXTRACTOR_PluginList *pl; 268 269 memset (items, 0, sizeof (*items)); 270 pl = EXTRACTOR_plugin_add_config (NULL, 271 config, 272 in_process 273 ? EXTRACTOR_OPTION_IN_PROCESS 274 : EXTRACTOR_OPTION_DEFAULT_POLICY); 275 if (NULL == pl) 276 { 277 fprintf (stderr, 278 "Failed to load %s\n", 279 config); 280 abort (); 281 } 282 EXTRACTOR_extract (pl, 283 filename, 284 NULL, 285 0, 286 &collect, 287 items); 288 EXTRACTOR_plugin_remove_all (pl); 289 } 290 291 292 /** 293 * Count the items of type @a type. 294 * 295 * @param items items to search 296 * @param type type to look for 297 * @param[out] last set to the last item of that type, may be NULL 298 * @return number of items of type @a type 299 */ 300 static unsigned int 301 count_type (const struct Items *items, 302 enum EXTRACTOR_MetaType type, 303 const struct Item **last) 304 { 305 unsigned int n = 0; 306 unsigned int i; 307 308 for (i = 0; i < items->count; i++) 309 if (items->item[i].type == type) 310 { 311 n++; 312 if (NULL != last) 313 *last = &items->item[i]; 314 } 315 return n; 316 } 317 318 319 /** 320 * Check that the subtitle languages were reported exactly once each, 321 * in stream order, and that there is exactly one thumbnail. 322 * 323 * @param items what the plugin emitted 324 * @param what name of the run, for diagnostics 325 * @return 0 if everything is as expected 326 */ 327 static int 328 check_languages (const struct Items *items, 329 const char *what) 330 { 331 static const char *const expected[] = { "eng", "ger", "fre" }; 332 unsigned int n = 0; 333 unsigned int i; 334 335 for (i = 0; i < items->count; i++) 336 { 337 const struct Item *it = &items->item[i]; 338 339 if (EXTRACTOR_METATYPE_SUBTITLE_LANGUAGE != it->type) 340 continue; 341 if ( (n >= 3) || 342 (EXTRACTOR_METAFORMAT_UTF8 != it->format) || 343 (it->data_len != strlen (expected[n]) + 1) || 344 (0 != strcmp (it->data, 345 expected[n])) ) 346 { 347 fprintf (stderr, 348 "%s: unexpected subtitle language #%u `%s'\n", 349 what, 350 n, 351 it->data); 352 return 1; 353 } 354 n++; 355 } 356 if (3 != n) 357 { 358 fprintf (stderr, 359 "%s: got %u subtitle languages, expected 3\n", 360 what, 361 n); 362 return 1; 363 } 364 if (1 != count_type (items, 365 EXTRACTOR_METATYPE_THUMBNAIL, 366 NULL)) 367 { 368 fprintf (stderr, 369 "%s: expected exactly one thumbnail\n", 370 what); 371 return 1; 372 } 373 return 0; 374 } 375 376 377 /** 378 * Check a run with "srt:LANG". 379 * 380 * @param lang language to request 381 * @param in_process 1 to run the plugin in-process 382 * @param expected expected SRT file, NULL if none must be emitted 383 * @return 0 on success 384 */ 385 static int 386 test_srt (const char *lang, 387 int in_process, 388 const char *expected) 389 { 390 struct Items items; 391 const struct Item *it = NULL; 392 char config[64]; 393 unsigned int n; 394 int ret = 0; 395 396 snprintf (config, 397 sizeof (config), 398 "thumbnailffmpeg(srt:%s)", 399 lang); 400 run_collect (config, 401 in_process, 402 SUBTITLES_MKV, 403 &items); 404 ret |= check_languages (&items, 405 config); 406 n = count_type (&items, 407 EXTRACTOR_METATYPE_SUBTITLE_TEXT, 408 &it); 409 if (NULL == expected) 410 { 411 if (0 != n) 412 { 413 fprintf (stderr, 414 "%s: got subtitles for a language that is not there\n", 415 config); 416 ret = 1; 417 } 418 } 419 else if (1 != n) 420 { 421 fprintf (stderr, 422 "%s: got %u subtitle dumps, expected 1\n", 423 config, 424 n); 425 ret = 1; 426 } 427 else if ( (EXTRACTOR_METAFORMAT_UTF8 != it->format) || 428 (0 != strcmp (it->mime, 429 "application/x-subrip")) || 430 (it->data_len != strlen (expected) + 1) || 431 (0 != memcmp (it->data, 432 expected, 433 it->data_len)) ) 434 { 435 fprintf (stderr, 436 "%s: got\n%s\nexpected\n%s\n", 437 config, 438 it->data, 439 expected); 440 ret = 1; 441 } 442 items_clear (&items); 443 return ret; 444 } 445 446 447 /** 448 * Read a big endian 32 bit number. 449 * 450 * @param p bytes to read 451 * @return the number 452 */ 453 static uint32_t 454 be32 (const uint8_t *p) 455 { 456 return (((uint32_t) p[0]) << 24) 457 | (((uint32_t) p[1]) << 16) 458 | (((uint32_t) p[2]) << 8) 459 | ((uint32_t) p[3]); 460 } 461 462 463 /** 464 * Get the dimensions of a PNG from its IHDR chunk. 465 * 466 * @param it the item holding the PNG 467 * @param[out] width set to the width 468 * @param[out] height set to the height 469 * @return 1 if @a it is a PNG 470 */ 471 static int 472 png_size (const struct Item *it, 473 uint32_t *width, 474 uint32_t *height) 475 { 476 static const uint8_t magic[] = { 137, 'P', 'N', 'G', '\r', '\n', 26, '\n' }; 477 const uint8_t *d = (const uint8_t *) it->data; 478 479 if ( (it->data_len < 8 + 8 + 13) || 480 (0 != memcmp (d, magic, sizeof (magic))) || 481 (0 != memcmp (&d[12], "IHDR", 4)) ) 482 return 0; 483 *width = be32 (&d[16]); 484 *height = be32 (&d[20]); 485 return 1; 486 } 487 488 489 /** 490 * Rough colour of a tile. 491 */ 492 enum TileColor 493 { 494 /** 495 * Anything goes, as long as the tile is not basically black or white. 496 */ 497 TC_ANY, 498 499 /** 500 * Mostly red. 501 */ 502 TC_RED, 503 504 /** 505 * Mostly blue. 506 */ 507 TC_BLUE 508 }; 509 510 511 /** 512 * Check one tile of a decoded gallery. 513 * 514 * @param rgb the decoded sheet 515 * @param width width of the sheet 516 * @param x0 left edge of the tile 517 * @param y0 top edge of the tile 518 * @param tw width of the tile 519 * @param th height of the tile 520 * @param color colour the tile should have 521 * @param what name of the run, for diagnostics 522 * @param idx index of the tile, for diagnostics 523 * @return 0 if the tile looks right 524 */ 525 static int 526 check_tile (const uint8_t *rgb, 527 uint32_t width, 528 uint32_t x0, 529 uint32_t y0, 530 uint32_t tw, 531 uint32_t th, 532 enum TileColor color, 533 const char *what, 534 unsigned int idx) 535 { 536 uint64_t sum[3] = { 0, 0, 0 }; 537 uint64_t dark = 0; 538 uint64_t light = 0; 539 uint64_t n = (uint64_t) tw * th; 540 uint32_t x; 541 uint32_t y; 542 unsigned int c; 543 544 for (y = y0; y < y0 + th; y++) 545 for (x = x0; x < x0 + tw; x++) 546 { 547 const uint8_t *p = &rgb[((size_t) y * width + x) * 3]; 548 549 for (c = 0; c < 3; c++) 550 sum[c] += p[c]; 551 if ( (p[0] < 48) && (p[1] < 48) && (p[2] < 48) ) 552 dark++; 553 if ( (p[0] > 208) && (p[1] > 208) && (p[2] > 208) ) 554 light++; 555 } 556 for (c = 0; c < 3; c++) 557 sum[c] /= n; 558 if ( (dark * 2 > n) || 559 (light * 2 > n) ) 560 { 561 fprintf (stderr, 562 "%s: tile %u is mostly %s, the flat frame was not replaced\n", 563 what, 564 idx, 565 (dark * 2 > n) ? "black" : "white"); 566 return 1; 567 } 568 if ( ( (TC_RED == color) && 569 ( (sum[0] < sum[1] + 96) || (sum[0] < sum[2] + 96) ) ) || 570 ( (TC_BLUE == color) && 571 ( (sum[2] < sum[0] + 96) || (sum[2] < sum[1] + 96) ) ) ) 572 { 573 fprintf (stderr, 574 "%s: tile %u has average rgb(%u,%u,%u), expected %s\n", 575 what, 576 idx, 577 (unsigned int) sum[0], 578 (unsigned int) sum[1], 579 (unsigned int) sum[2], 580 (TC_RED == color) ? "red" : "blue"); 581 return 1; 582 } 583 return 0; 584 } 585 586 587 /** 588 * Run the plugin with a gallery configuration and check that it emits 589 * exactly one image of an acceptable size and no other thumbnail. 590 * 591 * @param config the plugin with its options 592 * @param png 1 if we asked for a PNG 593 * @param[out] items set to what the plugin emitted 594 * @param[out] gallery set to the gallery item 595 * @return 0 on success 596 */ 597 static int 598 run_gallery (const char *config, 599 int png, 600 struct Items *items, 601 const struct Item **gallery) 602 { 603 static const uint8_t jpeg_magic[] = { 0xFF, 0xD8, 0xFF }; 604 uint32_t w; 605 uint32_t h; 606 607 run_collect (config, 608 0, 609 SUBTITLES_MKV, 610 items); 611 if (0 != check_languages (items, 612 config)) 613 return 1; 614 if (0 != count_type (items, 615 EXTRACTOR_METATYPE_SUBTITLE_TEXT, 616 NULL)) 617 { 618 fprintf (stderr, 619 "%s: got subtitles without asking for them\n", 620 config); 621 return 1; 622 } 623 count_type (items, 624 EXTRACTOR_METATYPE_THUMBNAIL, 625 gallery); 626 if ( (EXTRACTOR_METAFORMAT_BINARY != (*gallery)->format) || 627 (0 != strcmp ((*gallery)->mime, 628 png ? "image/png" : "image/jpeg")) || 629 ((*gallery)->data_len > MAX_GALLERY_BYTES) ) 630 { 631 fprintf (stderr, 632 "%s: gallery has the wrong type or size (%s, %u bytes)\n", 633 config, 634 (*gallery)->mime, 635 (unsigned int) (*gallery)->data_len); 636 return 1; 637 } 638 if (! png) 639 { 640 if ( ((*gallery)->data_len < sizeof (jpeg_magic)) || 641 (0 != memcmp ((*gallery)->data, 642 jpeg_magic, 643 sizeof (jpeg_magic))) ) 644 { 645 fprintf (stderr, 646 "%s: gallery is not a JPEG\n", 647 config); 648 return 1; 649 } 650 return 0; 651 } 652 if ( (! png_size (*gallery, &w, &h)) || 653 (0 == w) || 654 (0 == h) || 655 ((uint64_t) w * h > MAX_GALLERY_PIXELS) ) 656 { 657 fprintf (stderr, 658 "%s: gallery is not a PNG of acceptable size\n", 659 config); 660 return 1; 661 } 662 return 0; 663 } 664 665 666 /** 667 * Check "gallery:N" on the subtitled video. 668 * 669 * @return 0 on success 670 */ 671 static int 672 test_gallery (void) 673 { 674 /* with gallery:4 the tiles are sampled at 5s (black, must be shifted 675 into the red part), 15s (red), 25s (white, must be shifted into the 676 blue part) and 35s (blue) */ 677 static const enum TileColor colors4[] = { 678 TC_RED, TC_RED, TC_BLUE, TC_BLUE 679 }; 680 struct Items items; 681 const struct Item *g = NULL; 682 const char *config; 683 uint8_t *rgb; 684 uint32_t w; 685 uint32_t h; 686 unsigned int i; 687 int ret = 0; 688 689 /* 160x90 tiles; the sheet closest to 16:9 for four of them is 2x2 */ 690 config = "thumbnailffmpeg(gallery:4,format=png,size=160)"; 691 if (0 != run_gallery (config, 1, &items, &g)) 692 ret = 1; 693 else if ( (! png_size (g, &w, &h)) || 694 (320 != w) || 695 (180 != h) ) 696 { 697 fprintf (stderr, 698 "%s: gallery is %ux%u, expected 320x180\n", 699 config, 700 (unsigned int) w, 701 (unsigned int) h); 702 ret = 1; 703 } 704 else if (NULL != (rgb = TEST_media_png_decode (g->data, 705 g->data_len, 706 &w, 707 &h))) 708 { 709 for (i = 0; i < 4; i++) 710 ret |= check_tile (rgb, 711 w, 712 (i % 2) * 160, 713 (i / 2) * 90, 714 160, 715 90, 716 colors4[i], 717 config, 718 i); 719 free (rgb); 720 } 721 #if TEST_MEDIA_HAVE_PNG 722 else 723 { 724 fprintf (stderr, 725 "%s: failed to decode the gallery\n", 726 config); 727 ret = 1; 728 } 729 #endif 730 items_clear (&items); 731 732 /* a single tile, sampled at 20s: white, so it must be shifted */ 733 config = "thumbnailffmpeg(gallery:1,format=png,size=160)"; 734 if (0 != run_gallery (config, 1, &items, &g)) 735 ret = 1; 736 else if ( (! png_size (g, &w, &h)) || 737 (160 != w) || 738 (90 != h) ) 739 { 740 fprintf (stderr, 741 "%s: gallery is %ux%u, expected 160x90\n", 742 config, 743 (unsigned int) w, 744 (unsigned int) h); 745 ret = 1; 746 } 747 else if (NULL != (rgb = TEST_media_png_decode (g->data, 748 g->data_len, 749 &w, 750 &h))) 751 { 752 ret |= check_tile (rgb, w, 0, 0, 160, 90, TC_ANY, config, 0); 753 free (rgb); 754 } 755 items_clear (&items); 756 757 config = "thumbnailffmpeg(gallery:1)"; 758 ret |= run_gallery (config, 0, &items, &g); 759 items_clear (&items); 760 config = "thumbnailffmpeg(gallery:100)"; 761 ret |= run_gallery (config, 0, &items, &g); 762 items_clear (&items); 763 config = "thumbnailffmpeg(gallery:100,format=png,size=1024)"; 764 ret |= run_gallery (config, 1, &items, &g); 765 items_clear (&items); 766 return ret; 767 } 768 769 770 /** 771 * Check the subtitle languages and the absence of optional output in 772 * a run without options. 773 * 774 * @return 0 on success 775 */ 776 static int 777 test_default (void) 778 { 779 struct Items items; 780 int ret = 0; 781 782 run_collect ("thumbnailffmpeg", 783 0, 784 SUBTITLES_MKV, 785 &items); 786 ret |= check_languages (&items, 787 "default"); 788 if (0 != count_type (&items, 789 EXTRACTOR_METATYPE_SUBTITLE_TEXT, 790 NULL)) 791 { 792 fprintf (stderr, 793 "default: got subtitles without asking for them\n"); 794 ret = 1; 795 } 796 if (items.count != 4) 797 { 798 fprintf (stderr, 799 "default: got %u items, expected 4\n", 800 items.count); 801 ret = 1; 802 } 803 items_clear (&items); 804 return ret; 805 } 806 807 808 /** 809 * Main function for the thumbnailffmpeg testcase. 810 * 811 * @param argc number of arguments (ignored) 812 * @param argv arguments (ignored) 813 * @return 0 on success 814 */ 815 int 816 main (int argc, char *argv[]) 817 { 818 uint8_t jpeg_magic[] = { 0xFF, 0xD8, 0xFF }; 819 uint8_t png_magic[] = { 137, 'P', 'N', 'G', '\r', '\n', 26, '\n' }; 820 struct SolutionData thumbnail_jpeg_sol[] = { 821 { 822 EXTRACTOR_METATYPE_THUMBNAIL, 823 EXTRACTOR_METAFORMAT_BINARY, 824 "image/jpeg", 825 (const char *) jpeg_magic, 826 sizeof (jpeg_magic), 827 0 828 }, 829 { 0, 0, NULL, NULL, 0, -1 } 830 }; 831 struct SolutionData thumbnail_png_sol[] = { 832 { 833 EXTRACTOR_METATYPE_THUMBNAIL, 834 EXTRACTOR_METAFORMAT_BINARY, 835 "image/png", 836 (const char *) png_magic, 837 sizeof (png_magic), 838 0, 839 0, 840 &validate_red 841 }, 842 { 0, 0, NULL, NULL, 0, -1 } 843 }; 844 struct ProblemSet ps_jpeg[] = { 845 { "testdata/thumbnail_pattern.ogv", 846 thumbnail_jpeg_sol }, 847 { NULL, NULL } 848 }; 849 struct ProblemSet ps_png[] = { 850 { "testdata/thumbnail_pattern.ogv", 851 thumbnail_png_sol }, 852 { NULL, NULL } 853 }; 854 int ret; 855 856 ret = ET_main ("thumbnailffmpeg", 857 ps_jpeg); 858 if (0 != ret) 859 return ret; 860 ret = ET_main ("thumbnailffmpeg(format=png,size=32)", 861 ps_png); 862 if (0 != ret) 863 return ret; 864 if (0 != access (SUBTITLES_MKV, 865 R_OK)) 866 { 867 fprintf (stderr, 868 "Failed to access %s, skipping subtitle tests\n", 869 SUBTITLES_MKV); 870 return 0; 871 } 872 ret |= test_default (); 873 ret |= test_srt ("en", 0, srt_en); 874 ret |= test_srt ("eng", 0, srt_en); 875 ret |= test_srt ("de", 0, srt_de); 876 ret |= test_srt ("de", 1, srt_de); 877 ret |= test_srt ("fr", 0, srt_fr); 878 ret |= test_srt ("xx", 0, NULL); 879 ret |= test_gallery (); 880 return ret; 881 } 882 883 884 /* end of test_thumbnailffmpeg.c */