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