libextractor

GNU libextractor
Log | Files | Refs | Submodules | README | LICENSE

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 */