test_media_lib.c (7604B)
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 int 144 TEST_media_png_center (const void *data, 145 size_t data_len, 146 uint8_t rgb[3]) 147 { 148 #if ! TEST_MEDIA_HAVE_PNG 149 (void) data; 150 (void) data_len; 151 (void) rgb; 152 return 0; 153 #else 154 static const uint8_t magic[] = { 137, 'P', 'N', 'G', '\r', '\n', 26, '\n' }; 155 const uint8_t *d = data; 156 uint8_t *idat = NULL; 157 uint8_t *raw = NULL; 158 size_t idat_len = 0; 159 size_t pos = sizeof (magic); 160 uLongf raw_len; 161 uint32_t width = 0; 162 uint32_t height = 0; 163 uint32_t bpp = 0; 164 uint32_t stride; 165 const uint8_t *px; 166 int ret = 0; 167 168 if ( (data_len < sizeof (magic) + 12) || 169 (0 != memcmp (d, magic, sizeof (magic))) ) 170 return 0; 171 while (pos + 12 <= data_len) 172 { 173 uint32_t clen = get_u32 (&d[pos]); 174 const char *ctype = (const char *) &d[pos + 4]; 175 const uint8_t *cdata = &d[pos + 8]; 176 177 if ( (clen > data_len) || 178 (pos + 12 + clen > data_len) ) 179 break; 180 if (0 == memcmp (ctype, "IHDR", 4)) 181 { 182 if (clen < 13) 183 goto cleanup; 184 width = get_u32 (&cdata[0]); 185 height = get_u32 (&cdata[4]); 186 if ( (8 != cdata[8]) || 187 (0 != cdata[12]) ) 188 goto cleanup; /* not 8 bits per channel, or interlaced */ 189 if (2 == cdata[9]) 190 bpp = 3; 191 else if (6 == cdata[9]) 192 bpp = 4; 193 else 194 goto cleanup; /* palette or greyscale; our encoders emit neither */ 195 if ( (0 == width) || 196 (0 == height) || 197 (width > 4096) || 198 (height > 4096) ) 199 goto cleanup; 200 } 201 else if (0 == memcmp (ctype, "IDAT", 4)) 202 { 203 uint8_t *n = realloc (idat, idat_len + clen); 204 205 if (NULL == n) 206 goto cleanup; 207 idat = n; 208 memcpy (&idat[idat_len], 209 cdata, 210 clen); 211 idat_len += clen; 212 } 213 else if (0 == memcmp (ctype, "IEND", 4)) 214 { 215 break; 216 } 217 pos += 12 + clen; 218 } 219 if ( (0 == bpp) || 220 (NULL == idat) ) 221 goto cleanup; 222 stride = width * bpp; 223 raw_len = (uLongf) height * (stride + 1); 224 if (NULL == (raw = malloc (raw_len))) 225 goto cleanup; 226 if (Z_OK != uncompress (raw, 227 &raw_len, 228 idat, 229 (uLong) idat_len)) 230 goto cleanup; 231 if (raw_len != (uLongf) height * (stride + 1)) 232 goto cleanup; 233 if (! unfilter (raw, width, height, bpp)) 234 goto cleanup; 235 px = &raw[(height / 2) * (stride + 1) + 1 + (width / 2) * bpp]; 236 rgb[0] = px[0]; 237 rgb[1] = px[1]; 238 rgb[2] = px[2]; 239 ret = 1; 240 cleanup: 241 free (raw); 242 free (idat); 243 return ret; 244 #endif 245 } 246 247 248 int 249 TEST_media_png_center_is (const void *data, 250 size_t data_len, 251 uint8_t r, 252 uint8_t g, 253 uint8_t b) 254 { 255 uint8_t rgb[3]; 256 257 if (! TEST_media_png_center (data, 258 data_len, 259 rgb)) 260 { 261 #if TEST_MEDIA_HAVE_PNG 262 fprintf (stderr, 263 "Failed to decode the PNG the plugin produced\n"); 264 return 0; 265 #else 266 /* without zlib we cannot look inside; the magic number check that 267 the caller already did will have to do */ 268 return 1; 269 #endif 270 } 271 if ( (abs ((int) rgb[0] - (int) r) > COLOR_TOLERANCE) || 272 (abs ((int) rgb[1] - (int) g) > COLOR_TOLERANCE) || 273 (abs ((int) rgb[2] - (int) b) > COLOR_TOLERANCE) ) 274 { 275 fprintf (stderr, 276 "Thumbnail centre is rgb(%u,%u,%u), expected rgb(%u,%u,%u); " 277 "the plugin most likely sampled the wrong position\n", 278 rgb[0], 279 rgb[1], 280 rgb[2], 281 r, 282 g, 283 b); 284 return 0; 285 } 286 return 1; 287 } 288 289 290 int 291 TEST_media_is_ogg_opus (const void *data, 292 size_t data_len) 293 { 294 const uint8_t *d = data; 295 uint8_t segments; 296 size_t payload; 297 298 /* an Ogg page header is 27 bytes plus one byte per segment */ 299 if (data_len < 28 + 8) 300 return 0; 301 if (0 != memcmp (d, "OggS", 4)) 302 return 0; 303 if (0 != d[4]) 304 return 0; /* stream structure version */ 305 segments = d[26]; 306 payload = 27 + (size_t) segments; 307 if (payload + 8 > data_len) 308 return 0; 309 if (0 != memcmp (&d[payload], 310 "OpusHead", 311 8)) 312 { 313 fprintf (stderr, 314 "Ogg stream does not start with an OpusHead header\n"); 315 return 0; 316 } 317 return 1; 318 } 319 320 321 /* end of test_media_lib.c */