libextractor

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

test_media_lib.c (8319B)


      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/test_media_lib.c
     22  * @brief helpers for testing the video thumbnail and audio preview plugins
     23  * @author Christian Grothoff
     24  */
     25 #include "platform.h"
     26 #include "test_media_lib.h"
     27 
     28 #if TEST_MEDIA_HAVE_PNG
     29 #include <zlib.h>
     30 #endif
     31 
     32 /**
     33  * How far a decoded colour may be off before we call it a mismatch.
     34  */
     35 #define COLOR_TOLERANCE 48
     36 
     37 
     38 /**
     39  * Read a big endian 32 bit number.
     40  *
     41  * @param p bytes to read
     42  * @return the number
     43  */
     44 static uint32_t
     45 get_u32 (const uint8_t *p)
     46 {
     47   return (((uint32_t) p[0]) << 24)
     48          | (((uint32_t) p[1]) << 16)
     49          | (((uint32_t) p[2]) << 8)
     50          | ((uint32_t) p[3]);
     51 }
     52 
     53 
     54 #if TEST_MEDIA_HAVE_PNG
     55 /**
     56  * The Paeth predictor of the PNG specification.
     57  *
     58  * @param a pixel to the left
     59  * @param b pixel above
     60  * @param c pixel above left
     61  * @return the predicted value
     62  */
     63 static uint8_t
     64 paeth (uint8_t a,
     65        uint8_t b,
     66        uint8_t c)
     67 {
     68   int p = (int) a + (int) b - (int) c;
     69   int pa = abs (p - (int) a);
     70   int pb = abs (p - (int) b);
     71   int pc = abs (p - (int) c);
     72 
     73   if ( (pa <= pb) &&
     74        (pa <= pc) )
     75     return a;
     76   if (pb <= pc)
     77     return b;
     78   return c;
     79 }
     80 
     81 
     82 /**
     83  * Undo the per-scanline filters of a decompressed PNG image.
     84  *
     85  * @param[in,out] raw the filtered scanlines, overwritten with the image
     86  * @param width width of the image in pixels
     87  * @param height height of the image in pixels
     88  * @param bpp number of bytes per pixel
     89  * @return 1 on success
     90  */
     91 static int
     92 unfilter (uint8_t *raw,
     93           uint32_t width,
     94           uint32_t height,
     95           uint32_t bpp)
     96 {
     97   uint32_t stride = width * bpp;
     98   uint8_t *prev = NULL;
     99   uint32_t y;
    100 
    101   for (y = 0; y < height; y++)
    102   {
    103     uint8_t *row = &raw[y * (stride + 1)];
    104     uint8_t ft = row[0];
    105     uint32_t x;
    106 
    107     row++;
    108     for (x = 0; x < stride; x++)
    109     {
    110       uint8_t a = (x >= bpp) ? row[x - bpp] : 0;
    111       uint8_t b = (NULL != prev) ? prev[x] : 0;
    112       uint8_t c = ( (x >= bpp) && (NULL != prev) ) ? prev[x - bpp] : 0;
    113 
    114       switch (ft)
    115       {
    116       case 0:
    117         break;
    118       case 1:
    119         row[x] += a;
    120         break;
    121       case 2:
    122         row[x] += b;
    123         break;
    124       case 3:
    125         row[x] += (uint8_t) (((int) a + (int) b) / 2);
    126         break;
    127       case 4:
    128         row[x] += paeth (a, b, c);
    129         break;
    130       default:
    131         return 0;
    132       }
    133     }
    134     prev = row;
    135   }
    136   return 1;
    137 }
    138 
    139 
    140 #endif
    141 
    142 
    143 uint8_t *
    144 TEST_media_png_decode (const void *data,
    145                        size_t data_len,
    146                        uint32_t *width,
    147                        uint32_t *height)
    148 {
    149 #if ! TEST_MEDIA_HAVE_PNG
    150   (void) data;
    151   (void) data_len;
    152   (void) width;
    153   (void) height;
    154   return NULL;
    155 #else
    156   static const uint8_t magic[] = { 137, 'P', 'N', 'G', '\r', '\n', 26, '\n' };
    157   const uint8_t *d = data;
    158   uint8_t *idat = NULL;
    159   uint8_t *raw = NULL;
    160   uint8_t *rgb = NULL;
    161   size_t idat_len = 0;
    162   size_t pos = sizeof (magic);
    163   uLongf raw_len;
    164   uint32_t w = 0;
    165   uint32_t h = 0;
    166   uint32_t bpp = 0;
    167   uint32_t stride;
    168   uint32_t x;
    169   uint32_t y;
    170 
    171   if ( (data_len < sizeof (magic) + 12) ||
    172        (0 != memcmp (d, magic, sizeof (magic))) )
    173     return NULL;
    174   while (pos + 12 <= data_len)
    175   {
    176     uint32_t clen = get_u32 (&d[pos]);
    177     const char *ctype = (const char *) &d[pos + 4];
    178     const uint8_t *cdata = &d[pos + 8];
    179 
    180     if ( (clen > data_len) ||
    181          (pos + 12 + clen > data_len) )
    182       break;
    183     if (0 == memcmp (ctype, "IHDR", 4))
    184     {
    185       if (clen < 13)
    186         goto cleanup;
    187       w = get_u32 (&cdata[0]);
    188       h = get_u32 (&cdata[4]);
    189       if ( (8 != cdata[8]) ||
    190            (0 != cdata[12]) )
    191         goto cleanup;   /* not 8 bits per channel, or interlaced */
    192       if (2 == cdata[9])
    193         bpp = 3;
    194       else if (6 == cdata[9])
    195         bpp = 4;
    196       else
    197         goto cleanup;   /* palette or greyscale; our encoders emit neither */
    198       if ( (0 == w) ||
    199            (0 == h) ||
    200            (w > 16384) ||
    201            (h > 16384) )
    202         goto cleanup;
    203     }
    204     else if (0 == memcmp (ctype, "IDAT", 4))
    205     {
    206       uint8_t *n = realloc (idat, idat_len + clen);
    207 
    208       if (NULL == n)
    209         goto cleanup;
    210       idat = n;
    211       memcpy (&idat[idat_len],
    212               cdata,
    213               clen);
    214       idat_len += clen;
    215     }
    216     else if (0 == memcmp (ctype, "IEND", 4))
    217     {
    218       break;
    219     }
    220     pos += 12 + clen;
    221   }
    222   if ( (0 == bpp) ||
    223        (NULL == idat) )
    224     goto cleanup;
    225   stride = w * bpp;
    226   raw_len = (uLongf) h * (stride + 1);
    227   if (NULL == (raw = malloc (raw_len)))
    228     goto cleanup;
    229   if (Z_OK != uncompress (raw,
    230                           &raw_len,
    231                           idat,
    232                           (uLong) idat_len))
    233     goto cleanup;
    234   if (raw_len != (uLongf) h * (stride + 1))
    235     goto cleanup;
    236   if (! unfilter (raw, w, h, bpp))
    237     goto cleanup;
    238   if (NULL == (rgb = malloc ((size_t) w * h * 3)))
    239     goto cleanup;
    240   for (y = 0; y < h; y++)
    241     for (x = 0; x < w; x++)
    242       memcpy (&rgb[((size_t) y * w + x) * 3],
    243               &raw[(size_t) y * (stride + 1) + 1 + (size_t) x * bpp],
    244               3);
    245   *width = w;
    246   *height = h;
    247 cleanup:
    248   free (raw);
    249   free (idat);
    250   return rgb;
    251 #endif
    252 }
    253 
    254 
    255 int
    256 TEST_media_png_center (const void *data,
    257                        size_t data_len,
    258                        uint8_t rgb[3])
    259 {
    260   uint8_t *img;
    261   uint32_t width;
    262   uint32_t height;
    263 
    264   if (NULL == (img = TEST_media_png_decode (data,
    265                                             data_len,
    266                                             &width,
    267                                             &height)))
    268     return 0;
    269   memcpy (rgb,
    270           &img[((size_t) (height / 2) * width + width / 2) * 3],
    271           3);
    272   free (img);
    273   return 1;
    274 }
    275 
    276 
    277 int
    278 TEST_media_png_center_is (const void *data,
    279                           size_t data_len,
    280                           uint8_t r,
    281                           uint8_t g,
    282                           uint8_t b)
    283 {
    284   uint8_t rgb[3];
    285 
    286   if (! TEST_media_png_center (data,
    287                                data_len,
    288                                rgb))
    289   {
    290 #if TEST_MEDIA_HAVE_PNG
    291     fprintf (stderr,
    292              "Failed to decode the PNG the plugin produced\n");
    293     return 0;
    294 #else
    295     /* without zlib we cannot look inside; the magic number check that
    296        the caller already did will have to do */
    297     return 1;
    298 #endif
    299   }
    300   if ( (abs ((int) rgb[0] - (int) r) > COLOR_TOLERANCE) ||
    301        (abs ((int) rgb[1] - (int) g) > COLOR_TOLERANCE) ||
    302        (abs ((int) rgb[2] - (int) b) > COLOR_TOLERANCE) )
    303   {
    304     fprintf (stderr,
    305              "Thumbnail centre is rgb(%u,%u,%u), expected rgb(%u,%u,%u); "
    306              "the plugin most likely sampled the wrong position\n",
    307              rgb[0],
    308              rgb[1],
    309              rgb[2],
    310              r,
    311              g,
    312              b);
    313     return 0;
    314   }
    315   return 1;
    316 }
    317 
    318 
    319 int
    320 TEST_media_is_ogg_opus (const void *data,
    321                         size_t data_len)
    322 {
    323   const uint8_t *d = data;
    324   uint8_t segments;
    325   size_t payload;
    326 
    327   /* an Ogg page header is 27 bytes plus one byte per segment */
    328   if (data_len < 28 + 8)
    329     return 0;
    330   if (0 != memcmp (d, "OggS", 4))
    331     return 0;
    332   if (0 != d[4])
    333     return 0;   /* stream structure version */
    334   segments = d[26];
    335   payload = 27 + (size_t) segments;
    336   if (payload + 8 > data_len)
    337     return 0;
    338   if (0 != memcmp (&d[payload],
    339                    "OpusHead",
    340                    8))
    341   {
    342     fprintf (stderr,
    343              "Ogg stream does not start with an OpusHead header\n");
    344     return 0;
    345   }
    346   return 1;
    347 }
    348 
    349 
    350 /* end of test_media_lib.c */