/src/jpegoptim/jpegoptim.c
Line | Count | Source (jump to first uncovered line) |
1 | | /******************************************************************* |
2 | | * JPEGoptim |
3 | | * Copyright (c) Timo Kokkonen, 1996-2025. |
4 | | * All Rights Reserved. |
5 | | * |
6 | | * requires libjpeg (Independent JPEG Group's JPEG software |
7 | | * release 6a or later...) |
8 | | * |
9 | | * SPDX-License-Identifier: GPL-3.0-or-later |
10 | | * |
11 | | * This file is part of JPEGoptim. |
12 | | * |
13 | | * JPEGoptim is free software: you can redistribute it and/or modify |
14 | | * it under the terms of the GNU General Public License as published by |
15 | | * the Free Software Foundation, either version 3 of the License, or |
16 | | * (at your option) any later version. |
17 | | * |
18 | | * JPEGoptim is distributed in the hope that it will be useful, |
19 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
20 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
21 | | * GNU General Public License for more details. |
22 | | * |
23 | | * You should have received a copy of the GNU General Public License |
24 | | * along with JPEGoptim. If not, see <https://www.gnu.org/licenses/>. |
25 | | */ |
26 | | |
27 | | #ifdef HAVE_CONFIG_H |
28 | | #include "config.h" |
29 | | #endif |
30 | | #include <stdio.h> |
31 | | #include <stdlib.h> |
32 | | #ifdef HAVE_UNISTD_H |
33 | | #include <unistd.h> |
34 | | #endif |
35 | | #if HAVE_DIRENT_H |
36 | | #include <dirent.h> |
37 | | #endif |
38 | | #if HAVE_FCNTL_H |
39 | | #include <fcntl.h> |
40 | | #endif |
41 | | #if HAVE_SYS_STAT_H |
42 | | #include <sys/stat.h> |
43 | | #endif |
44 | | #if HAVE_SYS_TYPES_H |
45 | | #include <sys/types.h> |
46 | | #endif |
47 | | #if HAVE_SYS_WAIT_H |
48 | | #include <sys/wait.h> |
49 | | #endif |
50 | | #if HAVE_GETOPT_H && HAVE_GETOPT_LONG |
51 | | #include <getopt.h> |
52 | | #else |
53 | | #include "getopt.h" |
54 | | #endif |
55 | | #include <signal.h> |
56 | | #include <string.h> |
57 | | #include <jpeglib.h> |
58 | | #include <jerror.h> |
59 | | #include <setjmp.h> |
60 | | #include <time.h> |
61 | | #include <math.h> |
62 | | |
63 | | #include "jpegmarker.h" |
64 | | #include "jpegoptim.h" |
65 | | |
66 | | |
67 | 0 | #define VERSION "1.5.6beta" |
68 | 0 | #define COPYRIGHT "Copyright (C) 1996-2025, Timo Kokkonen" |
69 | | |
70 | | #if HAVE_WAIT && HAVE_FORK |
71 | | #define PARALLEL_PROCESSING 1 |
72 | 0 | #define MAX_WORKERS 256 |
73 | | #endif |
74 | | |
75 | 2.00k | #define IN_BUF_SIZE (256 * 1024) |
76 | | |
77 | | |
78 | | struct my_error_mgr { |
79 | | struct jpeg_error_mgr pub; |
80 | | jmp_buf setjmp_buffer; |
81 | | int jump_set; |
82 | | }; |
83 | | typedef struct my_error_mgr * my_error_ptr; |
84 | | |
85 | | |
86 | | #ifdef PARALLEL_PROCESSING |
87 | | struct worker { |
88 | | pid_t pid; |
89 | | int read_pipe; |
90 | | }; |
91 | | struct worker *workers; |
92 | | int worker_count = 0; |
93 | | #endif |
94 | | |
95 | | |
96 | | int verbose_mode = 0; |
97 | | int quiet_mode = 0; |
98 | | int preserve_mode = 0; |
99 | | int preserve_perms = 0; |
100 | | int overwrite_mode = 0; |
101 | | int retry_mode = 0; |
102 | | int totals_mode = 0; |
103 | | int stdin_mode = 0; |
104 | | int stdout_mode = 0; |
105 | | int noaction = 0; |
106 | | int quality = -1; |
107 | | int dest = 0; |
108 | | int force = 0; |
109 | | int save_extra = 0; |
110 | | int save_exif = 1; |
111 | | int save_iptc = 1; |
112 | | int save_com = 1; |
113 | | int save_icc = 1; |
114 | | int save_xmp = 1; |
115 | | int save_adobe = 0; |
116 | | int save_jfxx = 0; |
117 | | int save_jfif = 1; |
118 | | int strip_none = 0; |
119 | | double threshold = -1.0; |
120 | | int csv = 0; |
121 | | int auto_mode = 0; |
122 | | int all_normal = 0; |
123 | | int all_progressive = 0; |
124 | | int target_size = 0; |
125 | | #ifdef HAVE_ARITH_CODE |
126 | | int arith_mode = -1; |
127 | | #endif |
128 | | int max_workers = 1; |
129 | | int nofix_mode = 0; |
130 | | int files_stdin = 0; |
131 | | FILE *files_from = NULL; |
132 | | |
133 | | int compress_err_count = 0; |
134 | | int decompress_err_count = 0; |
135 | | int global_error_counter = 0; |
136 | | char last_error[JMSG_LENGTH_MAX+1]; |
137 | | FILE *jpeg_log_fh; |
138 | | long average_count = 0; |
139 | | double average_rate = 0.0; |
140 | | double total_save = 0.0; |
141 | | |
142 | | const struct option long_options[] = { |
143 | | #ifdef HAVE_ARITH_CODE |
144 | | { "all-arith", 0, &arith_mode, 1 }, |
145 | | { "all-huffman", 0, &arith_mode, 0 }, |
146 | | #endif |
147 | | { "auto-mode", 0, &auto_mode, 1 }, |
148 | | { "all-normal", 0, &all_normal, 1 }, |
149 | | { "all-progressive", 0, &all_progressive, 1 }, |
150 | | { "csv", 0, 0, 'b' }, |
151 | | { "dest", 1, 0, 'd' }, |
152 | | { "files-stdin", 0, &files_stdin, 1 }, |
153 | | { "files-from", 1, 0, 'F' }, |
154 | | { "force", 0, 0, 'f' }, |
155 | | { "help", 0, 0, 'h' }, |
156 | | { "keep-adobe", 0, &save_adobe, 1 }, |
157 | | { "keep-all", 0, &strip_none, 1 }, |
158 | | { "keep-com", 0, &save_com, 1 }, |
159 | | { "keep-exif", 0, &save_exif, 1 }, |
160 | | { "keep-iptc", 0, &save_iptc, 1 }, |
161 | | { "keep-icc", 0, &save_icc, 1 }, |
162 | | { "keep-jfif", 0, &save_jfif, 1 }, |
163 | | { "keep-jfxx", 0, &save_jfxx, 1 }, |
164 | | { "keep-xmp", 0, &save_xmp, 1 }, |
165 | | { "max", 1, 0, 'm' }, |
166 | | { "noaction", 0, 0, 'n' }, |
167 | | { "nofix", 0, &nofix_mode, 1 }, |
168 | | { "overwrite", 0, 0, 'o' }, |
169 | | { "preserve", 0, 0, 'p' }, |
170 | | { "preserve-perms", 0, 0, 'P' }, |
171 | | { "quiet", 0, 0, 'q' }, |
172 | | { "retry", 0, &retry_mode, 'r' }, |
173 | | { "save-extra", 0, &save_extra, 1 }, |
174 | | { "size", 1, 0, 'S' }, |
175 | | { "stdin", 0, &stdin_mode, 1 }, |
176 | | { "stdout", 0, &stdout_mode, 1 }, |
177 | | { "strip-all", 0, 0, 's' }, |
178 | | { "strip-none", 0, &strip_none, 1 }, |
179 | | { "strip-com", 0, &save_com, 0 }, |
180 | | { "strip-exif", 0, &save_exif, 0 }, |
181 | | { "strip-iptc", 0, &save_iptc, 0 }, |
182 | | { "strip-icc", 0, &save_icc, 0 }, |
183 | | { "strip-xmp", 0, &save_xmp, 0 }, |
184 | | { "strip-jfif", 0, &save_jfif, 0 }, |
185 | | { "strip-jfxx", 0, &save_jfxx, 0 }, |
186 | | { "strip-adobe", 0, &save_adobe, 0 }, |
187 | | { "threshold", 1, 0, 'T' }, |
188 | | { "totals", 0, 0, 't' }, |
189 | | { "verbose", 0, 0, 'v' }, |
190 | | { "version", 0, 0, 'V' }, |
191 | | #ifdef PARALLEL_PROCESSING |
192 | | { "workers", 1, &max_workers, 'w' }, |
193 | | #endif |
194 | | { 0, 0, 0, 0 } |
195 | | }; |
196 | | |
197 | | |
198 | | /*****************************************************************/ |
199 | | |
200 | | |
201 | | void free_line_buf(JSAMPARRAY *buf, unsigned int lines) |
202 | 929 | { |
203 | 929 | if (*buf == NULL) |
204 | 590 | return; |
205 | | |
206 | 2.57M | for (unsigned int i = 0; i < lines; i++) { |
207 | 2.57M | if ((*buf)[i]) |
208 | 2.57M | free((*buf)[i]); |
209 | 2.57M | } |
210 | 339 | free(*buf); |
211 | 339 | *buf = NULL; |
212 | 339 | } |
213 | | |
214 | | |
215 | | METHODDEF(void) my_error_exit (j_common_ptr cinfo) |
216 | 398 | { |
217 | 398 | my_error_ptr myerr = (my_error_ptr)cinfo->err; |
218 | | |
219 | 398 | (*cinfo->err->output_message)(cinfo); |
220 | 398 | if (myerr->jump_set) |
221 | 398 | longjmp(myerr->setjmp_buffer, 1); |
222 | 0 | else |
223 | 0 | fatal("fatal error"); |
224 | 398 | } |
225 | | |
226 | | |
227 | | METHODDEF(void) my_output_message (j_common_ptr cinfo) |
228 | 1.31k | { |
229 | 1.31k | char buffer[JMSG_LENGTH_MAX+1]; |
230 | | |
231 | 1.31k | (*cinfo->err->format_message)((j_common_ptr)cinfo, buffer); |
232 | 1.31k | buffer[sizeof(buffer) - 1] = 0; |
233 | | |
234 | 1.31k | if (verbose_mode) |
235 | 0 | fprintf(jpeg_log_fh, " (%s) ", buffer); |
236 | | |
237 | 1.31k | global_error_counter++; |
238 | 1.31k | strncopy(last_error, buffer, sizeof(last_error)); |
239 | 1.31k | } |
240 | | |
241 | | |
242 | | void print_usage(void) |
243 | 0 | { |
244 | 0 | fprintf(stderr,PROGRAMNAME " v" VERSION " " COPYRIGHT "\n"); |
245 | |
|
246 | 0 | fprintf(stderr, |
247 | 0 | "Usage: " PROGRAMNAME " [options] <filenames> \n\n" |
248 | 0 | " -d<path>, --dest=<path>\n" |
249 | 0 | " specify alternative destination directory for \n" |
250 | 0 | " optimized files (default is to overwrite originals)\n" |
251 | 0 | " -f, --force force optimization\n" |
252 | 0 | " -h, --help display this help and exit\n" |
253 | 0 | " -m<quality>, --max=<quality>\n" |
254 | 0 | " set maximum image quality factor (disables lossless\n" |
255 | 0 | " optimization mode, which is by default on)\n" |
256 | 0 | " Valid quality values: 0 - 100\n" |
257 | 0 | " -n, --noaction don't really optimize files, just print results\n" |
258 | 0 | " -S<size>, --size=<size>\n" |
259 | 0 | " Try to optimize file to given size (disables lossless\n" |
260 | 0 | " optimization mode). Target size is specified either in\n" |
261 | 0 | " kilo bytes (1 - n) or as percentage (1%% - 99%%)\n" |
262 | 0 | " -T<threshold>, --threshold=<threshold>\n" |
263 | 0 | " keep old file if the gain is below a threshold (%%)\n" |
264 | 0 | #ifdef PARALLEL_PROCESSING |
265 | 0 | " -w<max>, --workers=<max>\n" |
266 | 0 | " set maximum number of parallel threads (default is 1)\n" |
267 | 0 | #endif |
268 | 0 | " -b, --csv print progress info in CSV format\n" |
269 | 0 | " -o, --overwrite overwrite target file even if it exists (meaningful\n" |
270 | 0 | " only when used with -d, --dest option)\n" |
271 | 0 | " -p, --preserve preserve file timestamps\n" |
272 | 0 | " -P, --preserve-perms\n" |
273 | 0 | " preserve original file permissions by overwriting it\n" |
274 | 0 | " -q, --quiet quiet mode\n" |
275 | 0 | " -r, --retry try (recursively) optimize until file size does not change anymore\n" |
276 | 0 | " -t, --totals print totals after processing all files\n" |
277 | 0 | " -v, --verbose enable verbose mode (positively chatty)\n" |
278 | 0 | " -V, --version print program version\n\n" |
279 | 0 | " -s, --strip-all strip all markers from output file\n" |
280 | 0 | " --strip-none do not strip any markers\n" |
281 | 0 | " --strip-adobe strip Adobe (APP14) markers from output file\n" |
282 | 0 | " --strip-com strip Comment markers from output file\n" |
283 | 0 | " --strip-exif strip Exif markers from output file\n" |
284 | 0 | " --strip-iptc strip IPTC/Photoshop (APP13) markers from output file\n" |
285 | 0 | " --strip-icc strip ICC profile markers from output file\n" |
286 | 0 | " --strip-jfif strip JFIF markers from output file\n" |
287 | 0 | " --strip-jfxx strip JFXX (JFIF Extension) markers from output file\n" |
288 | 0 | " --strip-xmp strip XMP markers markers from output file\n" |
289 | 0 | "\n" |
290 | 0 | " --keep-all do not strip any markers (same as --strip-none)\n" |
291 | 0 | " --keep-adobe preserve any Adobe (APP14) markers\n" |
292 | 0 | " --keep-com preserve any Comment markers\n" |
293 | 0 | " --keep-exif preserve any Exif markers\n" |
294 | 0 | " --keep-iptc preserve any IPTC/Photoshop (APP13) markers\n" |
295 | 0 | " --keep-icc preserve any ICC profile markers\n" |
296 | 0 | " --keep-jfif preserve any JFIF markers\n" |
297 | 0 | " --keep-jfxx preserve any JFXX (JFIF Extension) markers\n" |
298 | 0 | " --keep-xmp preserve any XMP markers markers\n" |
299 | 0 | "\n" |
300 | 0 | " --all-normal force all output files to be non-progressive\n" |
301 | 0 | " --all-progressive force all output files to be progressive\n" |
302 | 0 | " --auto-mode select normal or progressive based on which produces\n" |
303 | 0 | " smaller output file\n" |
304 | 0 | #ifdef HAVE_ARITH_CODE |
305 | 0 | " --all-arith force all output files to use arithmetic coding\n" |
306 | 0 | " --all-huffman force all output files to use Huffman coding\n" |
307 | 0 | #endif |
308 | 0 | " --stdout send output to standard output (instead of a file)\n" |
309 | 0 | " --stdin read input from standard input (instead of a file)\n" |
310 | 0 | " --files-stdin Read names of files to process from stdin\n" |
311 | 0 | " --files-from=FILE Read names of files to process from a file\n" |
312 | 0 | " --nofix skip processing of input files if they contain any errors\n" |
313 | 0 | " --save-extra preserve extraneous data after the end of image\n" |
314 | 0 | "\n\n"); |
315 | 0 | } |
316 | | |
317 | | |
318 | | void print_version() |
319 | 0 | { |
320 | 0 | struct jpeg_error_mgr jerr; |
321 | |
|
322 | 0 | #ifdef __DATE__ |
323 | 0 | printf(PROGRAMNAME " v%s %s (%s)\n",VERSION, HOST_TYPE, __DATE__); |
324 | | #else |
325 | | printf(PROGRAMNAME " v%s %s\n", VERSION, HOST_TYPE); |
326 | | #endif |
327 | 0 | printf(COPYRIGHT "\n\n"); |
328 | 0 | printf("This program comes with ABSOLUTELY NO WARRANTY. This is free software,\n" |
329 | 0 | "and you are welcome to redistribute it under certain conditions.\n" |
330 | 0 | "See the GNU General Public License for more details.\n\n"); |
331 | |
|
332 | 0 | if (!jpeg_std_error(&jerr)) |
333 | 0 | fatal("jpeg_std_error() failed"); |
334 | |
|
335 | 0 | printf("\nlibjpeg version: %s\n%s\n", |
336 | 0 | jerr.jpeg_message_table[JMSG_VERSION], |
337 | 0 | jerr.jpeg_message_table[JMSG_COPYRIGHT]); |
338 | 0 | } |
339 | | |
340 | | |
341 | | void parse_arguments(int argc, char **argv, char *dest_path, size_t dest_path_len) |
342 | 0 | { |
343 | 0 | int opt_index; |
344 | 0 | int i, c; |
345 | |
|
346 | 0 | while(1) { |
347 | 0 | opt_index=0; |
348 | 0 | if ((c = getopt_long(argc,argv,"d:hm:nstqvfVpProT:S:bw:", |
349 | 0 | long_options, &opt_index)) == -1) |
350 | 0 | break; |
351 | | |
352 | 0 | switch (c) { |
353 | | |
354 | 0 | case 'm': |
355 | 0 | { |
356 | 0 | int tmpvar; |
357 | |
|
358 | 0 | if (sscanf(optarg,"%d",&tmpvar) == 1) { |
359 | 0 | quality=tmpvar; |
360 | 0 | if (quality < 0) quality=0; |
361 | 0 | if (quality > 100) quality=100; |
362 | 0 | } |
363 | 0 | else |
364 | 0 | fatal("invalid argument for -m, --max"); |
365 | 0 | } |
366 | 0 | break; |
367 | | |
368 | 0 | case 'd': |
369 | 0 | if (realpath(optarg,dest_path)==NULL) |
370 | 0 | fatal("invalid destination directory: %s", optarg); |
371 | 0 | if (!is_directory(dest_path)) |
372 | 0 | fatal("destination not a directory: %s", dest_path); |
373 | 0 | strncatenate(dest_path, DIR_SEPARATOR_S, dest_path_len); |
374 | 0 | if (verbose_mode) |
375 | 0 | fprintf(stderr,"Destination directory: %s\n",dest_path); |
376 | 0 | dest=1; |
377 | 0 | break; |
378 | | |
379 | 0 | case 'v': |
380 | 0 | verbose_mode++; |
381 | 0 | break; |
382 | | |
383 | 0 | case 'h': |
384 | 0 | print_usage(); |
385 | 0 | exit(0); |
386 | 0 | break; |
387 | | |
388 | 0 | case 'q': |
389 | 0 | quiet_mode=1; |
390 | 0 | break; |
391 | | |
392 | 0 | case 'r': |
393 | 0 | retry_mode=1; |
394 | 0 | break; |
395 | | |
396 | 0 | case 't': |
397 | 0 | totals_mode=1; |
398 | 0 | break; |
399 | | |
400 | 0 | case 'n': |
401 | 0 | noaction=1; |
402 | 0 | break; |
403 | | |
404 | 0 | case 'f': |
405 | 0 | force=1; |
406 | 0 | break; |
407 | | |
408 | 0 | case 'b': |
409 | 0 | csv=1; |
410 | 0 | quiet_mode=1; |
411 | 0 | break; |
412 | | |
413 | 0 | case 'V': |
414 | 0 | print_version(); |
415 | 0 | exit(0); |
416 | 0 | break; |
417 | | |
418 | 0 | case 'o': |
419 | 0 | overwrite_mode=1; |
420 | 0 | break; |
421 | | |
422 | 0 | case 'p': |
423 | 0 | preserve_mode=1; |
424 | 0 | break; |
425 | | |
426 | 0 | case 'P': |
427 | 0 | preserve_perms=1; |
428 | 0 | break; |
429 | | |
430 | 0 | case 's': |
431 | 0 | save_exif=0; |
432 | 0 | save_iptc=0; |
433 | 0 | save_com=0; |
434 | 0 | save_icc=0; |
435 | 0 | save_xmp=0; |
436 | 0 | save_adobe=0; |
437 | 0 | save_jfif=0; |
438 | 0 | save_jfxx=0; |
439 | 0 | break; |
440 | | |
441 | 0 | case 'T': |
442 | 0 | { |
443 | 0 | double tmpvar; |
444 | 0 | if (sscanf(optarg,"%lf", &tmpvar) == 1) { |
445 | 0 | threshold=tmpvar; |
446 | 0 | if (threshold < 0) threshold=0; |
447 | 0 | if (threshold > 100) threshold=100; |
448 | 0 | } |
449 | 0 | else fatal("invalid argument for -T, --threshold"); |
450 | 0 | } |
451 | 0 | break; |
452 | | |
453 | 0 | case 'S': |
454 | 0 | { |
455 | 0 | unsigned int tmpvar; |
456 | 0 | if (sscanf(optarg,"%u", &tmpvar) == 1) { |
457 | 0 | if (tmpvar > 0 && tmpvar < 100 && |
458 | 0 | optarg[strlen(optarg)-1] == '%' ) { |
459 | 0 | target_size=-tmpvar; |
460 | 0 | } else { |
461 | 0 | target_size=tmpvar; |
462 | 0 | } |
463 | 0 | quality=100; |
464 | 0 | } |
465 | 0 | else fatal("invalid argument for -S, --size"); |
466 | 0 | } |
467 | 0 | break; |
468 | | |
469 | 0 | #ifdef PARALLEL_PROCESSING |
470 | 0 | case 'w': |
471 | 0 | { |
472 | 0 | int tmpvar; |
473 | 0 | if (sscanf(optarg, "%d", &tmpvar) == 1) { |
474 | 0 | if (tmpvar > 0 && tmpvar <= MAX_WORKERS) |
475 | 0 | max_workers = tmpvar; |
476 | 0 | } |
477 | 0 | else fatal("invalid argument for -w, --workers"); |
478 | 0 | } |
479 | 0 | break; |
480 | 0 | #endif |
481 | | |
482 | 0 | case 'F': |
483 | 0 | { |
484 | 0 | if (optarg[0] == '-' && optarg[1] == 0) { |
485 | 0 | files_stdin = 1; |
486 | 0 | break; |
487 | 0 | } |
488 | 0 | if (!is_file(optarg, NULL)) |
489 | 0 | fatal("argument for --files-from must be a file"); |
490 | 0 | if ((files_from = fopen(optarg, "r")) == NULL) |
491 | 0 | fatal("cannot open file: '%s'", optarg); |
492 | 0 | } |
493 | 0 | break; |
494 | | |
495 | 0 | case '?': |
496 | 0 | exit(1); |
497 | |
|
498 | 0 | } |
499 | 0 | } |
500 | | |
501 | | |
502 | | /* check for '-' option indicating input is from stdin... */ |
503 | 0 | i = optind; |
504 | 0 | while (argv[i]) { |
505 | 0 | if (argv[i][0]=='-' && argv[i][1]==0) |
506 | 0 | stdin_mode=1; |
507 | 0 | i++; |
508 | 0 | } |
509 | |
|
510 | 0 | if (stdin_mode) |
511 | 0 | stdout_mode=1; |
512 | 0 | if (files_stdin) |
513 | 0 | files_from = stdin; |
514 | 0 | if (stdin_mode && files_from == stdin) |
515 | 0 | fatal("cannot specify both --stdin and --files-stdin"); |
516 | 0 | if (all_normal && all_progressive) |
517 | 0 | fatal("cannot specify both --all-normal and --all-progressive"); |
518 | 0 | if (auto_mode && (all_normal || all_progressive)) |
519 | 0 | fatal("cannot specify --all-normal or --all-progressive if using --auto-mode"); |
520 | 0 | } |
521 | | |
522 | | |
523 | | void own_signal_handler(int a) |
524 | 0 | { |
525 | 0 | if (verbose_mode > 1) |
526 | 0 | fprintf(stderr,PROGRAMNAME ": died from signal: %d\n",a); |
527 | 0 | exit(2); |
528 | 0 | } |
529 | | |
530 | | |
531 | | void write_markers(struct jpeg_decompress_struct *dinfo, |
532 | | struct jpeg_compress_struct *cinfo) |
533 | 1.33k | { |
534 | 1.33k | jpeg_saved_marker_ptr mrk; |
535 | 1.33k | int write_marker; |
536 | 1.33k | const char *s_name; |
537 | | |
538 | 1.33k | if (!cinfo || !dinfo) |
539 | 0 | fatal("invalid call to write_markers()"); |
540 | | |
541 | 1.33k | mrk=dinfo->marker_list; |
542 | 2.08M | while (mrk) { |
543 | 2.08M | write_marker = 0; |
544 | 2.08M | s_name = jpeg_special_marker_name(mrk); |
545 | | |
546 | | /* Check for markers to save... */ |
547 | | |
548 | 2.08M | if (save_com && mrk->marker == JPEG_COM) |
549 | 77.3k | write_marker++; |
550 | | |
551 | 2.08M | if (save_iptc && !strncmp(s_name, "IPTC", 5)) |
552 | 599 | write_marker++; |
553 | | |
554 | 2.08M | if (save_exif && !strncmp(s_name, "Exif", 5)) |
555 | 1.16k | write_marker++; |
556 | | |
557 | 2.08M | if (save_icc && !strncmp(s_name, "ICC", 4)) |
558 | 956 | write_marker++; |
559 | | |
560 | 2.08M | if (save_xmp && !strncmp(s_name, "XMP", 4)) |
561 | 2.88k | write_marker++; |
562 | | |
563 | 2.08M | if (save_jfxx && !strncmp(s_name, "JFXX", 5)) |
564 | 0 | write_marker++; |
565 | | |
566 | 2.08M | if (save_adobe && !strncmp(s_name, "Adobe", 6)) |
567 | 0 | write_marker++; |
568 | | |
569 | 2.08M | if (strip_none) |
570 | 0 | write_marker++; |
571 | | |
572 | | |
573 | | /* libjpeg emits some markers automatically so skip these to avoid duplicates... */ |
574 | | |
575 | 2.08M | if (!strncmp(s_name, "JFIF", 5)) |
576 | 1.31k | write_marker=0; |
577 | | |
578 | | |
579 | 2.08M | if (verbose_mode > 2) |
580 | 0 | fprintf(jpeg_log_fh, " (Marker %s [%s]: %s)", jpeg_marker_name(mrk->marker), |
581 | 0 | s_name, (write_marker ? "Keep" : "Discard")); |
582 | 2.08M | if (write_marker) |
583 | 82.9k | jpeg_write_marker(cinfo, mrk->marker, mrk->data, mrk->data_length); |
584 | | |
585 | 2.08M | mrk=mrk->next; |
586 | 2.08M | } |
587 | 1.33k | } |
588 | | |
589 | | |
590 | | unsigned int parse_markers(const struct jpeg_decompress_struct *dinfo, |
591 | | char *str, unsigned int str_size, unsigned int *markers_total_size) |
592 | 866 | { |
593 | 866 | jpeg_saved_marker_ptr m; |
594 | 866 | unsigned int count = 0; |
595 | 866 | char *seen; |
596 | 866 | size_t marker_types = jpeg_special_marker_types_count(); |
597 | 866 | int com_seen = 0; |
598 | 866 | int special; |
599 | | |
600 | 866 | if ((seen = calloc(marker_types, 1)) == NULL) |
601 | 0 | fatal("not enough of memory"); |
602 | | |
603 | 866 | str[0] = 0; |
604 | 866 | *markers_total_size = 0; |
605 | | |
606 | 866 | m = dinfo->marker_list; |
607 | 607k | while (m) { |
608 | 606k | count++; |
609 | 606k | *markers_total_size += m->data_length; |
610 | | |
611 | 606k | if ((special = jpeg_special_marker(m)) >= 0) { |
612 | 559k | if (!seen[special]) |
613 | 3.03k | str_add_list(str, str_size, jpeg_special_marker_types[special].name, ","); |
614 | 559k | seen[special]++; |
615 | 559k | } |
616 | | |
617 | 606k | if (m->marker == JPEG_COM && !com_seen) { |
618 | 161 | str_add_list(str, str_size, "COM", ","); |
619 | 161 | com_seen = 1; |
620 | 161 | } |
621 | | |
622 | 606k | m = m->next; |
623 | 606k | } |
624 | | |
625 | 866 | free(seen); |
626 | | |
627 | 866 | return count; |
628 | 866 | } |
629 | | |
630 | | |
631 | | |
632 | | int optimize(FILE *log_fh, const char *filename, const char *newname, |
633 | | const char *tmpdir, struct stat *file_stat, |
634 | | double *rate, double *saved) |
635 | 678 | { |
636 | 678 | FILE *infile = NULL; |
637 | 678 | FILE *outfile = NULL; |
638 | 678 | const char *outfname = NULL; |
639 | 678 | char tmpfilename[MAXPATHLEN]; |
640 | 678 | struct jpeg_decompress_struct dinfo; |
641 | 678 | struct jpeg_compress_struct cinfo; |
642 | 678 | struct my_error_mgr jcerr, jderr; |
643 | 678 | JSAMPARRAY buf = NULL; |
644 | | |
645 | 678 | unsigned char *outbuffer = NULL; |
646 | 678 | size_t outbuffersize = 0; |
647 | 678 | unsigned char *inbuffer = NULL; |
648 | 678 | size_t inbuffersize = 0; |
649 | 678 | size_t inbufferused = 0; |
650 | 678 | unsigned char *tmpbuffer = NULL; |
651 | 678 | size_t tmpbuffersize = 0; |
652 | 678 | unsigned char *extrabuffer = NULL; |
653 | 678 | size_t extrabuffersize = 0; |
654 | | |
655 | 678 | jvirt_barray_ptr *coef_arrays = NULL; |
656 | 678 | char marker_str[256]; |
657 | 678 | unsigned int marker_in_count, marker_in_size; |
658 | | |
659 | 678 | long in_image_size = 0; |
660 | 678 | long insize = 0, outsize = 0, lastsize = 0; |
661 | 678 | int oldquality, searchdone; |
662 | 678 | double ratio; |
663 | 678 | size_t last_retry_size = 0; |
664 | 678 | int retry_count = 0; |
665 | 678 | int retry = 0; |
666 | 678 | int res = -1; |
667 | | |
668 | 678 | jpeg_log_fh = log_fh; |
669 | | |
670 | | /* Initialize decompression object */ |
671 | 678 | dinfo.err = jpeg_std_error(&jderr.pub); |
672 | 678 | jpeg_create_decompress(&dinfo); |
673 | 678 | jderr.pub.error_exit=my_error_exit; |
674 | 678 | jderr.pub.output_message=my_output_message; |
675 | 678 | jderr.jump_set = 0; |
676 | | |
677 | | /* Initialize compression object */ |
678 | 678 | cinfo.err = jpeg_std_error(&jcerr.pub); |
679 | 678 | jpeg_create_compress(&cinfo); |
680 | 678 | jcerr.pub.error_exit=my_error_exit; |
681 | 678 | jcerr.pub.output_message=my_output_message; |
682 | 678 | jcerr.jump_set = 0; |
683 | | |
684 | 678 | if (rate) |
685 | 678 | *rate = 0.0; |
686 | 678 | if (saved) |
687 | 678 | *saved = 0.0; |
688 | | |
689 | 678 | if (filename) { |
690 | 678 | if ((infile = fopen(filename, "rb")) == NULL) { |
691 | 0 | warn("cannot open file: %s", filename); |
692 | 0 | res = 1; |
693 | 0 | goto exit_point; |
694 | 0 | } |
695 | 678 | } else { |
696 | 0 | infile = stdin; |
697 | 0 | set_filemode_binary(infile); |
698 | 0 | } |
699 | | |
700 | 929 | retry_point: |
701 | | |
702 | 929 | if (setjmp(jderr.setjmp_buffer)) { |
703 | | /* Error handler for decompress */ |
704 | 340 | abort_decompress: |
705 | 340 | jpeg_abort_decompress(&dinfo); |
706 | 340 | fclose(infile); |
707 | 340 | free_line_buf(&buf, dinfo.output_height); |
708 | 340 | if (!quiet_mode || csv) |
709 | 340 | fprintf(log_fh,csv ? ",,,,,error\n" : " [ERROR]\n"); |
710 | 340 | jderr.jump_set=0; |
711 | 340 | res = 1; |
712 | 340 | goto exit_point; |
713 | 589 | } else { |
714 | 589 | jderr.jump_set=1; |
715 | 589 | } |
716 | | |
717 | | |
718 | | /* Prepare to decompress */ |
719 | 678 | if (!retry) { |
720 | 678 | if (!quiet_mode || csv) { |
721 | 678 | fprintf(log_fh,csv ? "%s," : "%s ",(filename ? filename:"stdin")); |
722 | 678 | fflush(log_fh); |
723 | 678 | } |
724 | | |
725 | 678 | if (stdin_mode || stdout_mode) { |
726 | 0 | inbuffersize = IN_BUF_SIZE; |
727 | 678 | } else { |
728 | 678 | if ((inbuffersize = file_size(infile)) < IN_BUF_SIZE) |
729 | 653 | inbuffersize = IN_BUF_SIZE; |
730 | 678 | } |
731 | 678 | if (inbuffer) |
732 | 0 | free(inbuffer); |
733 | 678 | if (!(inbuffer=calloc(inbuffersize, 1))) |
734 | 0 | fatal("not enough memory"); |
735 | 678 | } |
736 | 589 | global_error_counter=0; |
737 | 589 | jpeg_save_markers(&dinfo, JPEG_COM, 0xffff); |
738 | 15.4k | for (int i = 0; i < 16; i++) { |
739 | 14.8k | jpeg_save_markers(&dinfo, JPEG_APP0 + i, 0xffff); |
740 | 14.8k | } |
741 | 678 | if (!retry) { |
742 | 678 | jpeg_custom_src(&dinfo, infile, &inbuffer, &inbuffersize, &inbufferused, IN_BUF_SIZE); |
743 | 18.4E | } else { |
744 | 18.4E | if (retry == 1) |
745 | 251 | jpeg_custom_mem_src(&dinfo, inbuffer, inbufferused); |
746 | 18.4E | else |
747 | 18.4E | jpeg_custom_mem_src(&dinfo, tmpbuffer, tmpbuffersize); |
748 | 18.4E | } |
749 | 589 | jpeg_read_header(&dinfo, TRUE); |
750 | | |
751 | | |
752 | | /* Check for known (Exif, IPTC, ICC , XMP, ...) markers */ |
753 | 589 | marker_in_count = parse_markers(&dinfo, marker_str, sizeof(marker_str), |
754 | 589 | &marker_in_size); |
755 | | |
756 | 615 | if (!retry) { |
757 | 615 | if (verbose_mode > 1) { |
758 | 0 | fprintf(log_fh,"%d markers found in input file (total size %d bytes)\n", |
759 | 0 | marker_in_count,marker_in_size); |
760 | 0 | fprintf(log_fh,"coding: %s\n", (dinfo.arith_code == TRUE ? "Arithmetic" : "Huffman")); |
761 | 0 | } |
762 | | |
763 | 615 | if (!quiet_mode || csv) { |
764 | 615 | fprintf(log_fh,csv ? "%dx%d,%dbit,%c," : "%dx%d %dbit %c ",(int)dinfo.image_width, |
765 | 615 | (int)dinfo.image_height,(int)dinfo.num_components*8, |
766 | 615 | (dinfo.progressive_mode?'P':'N')); |
767 | 615 | if (!csv) |
768 | 615 | fprintf(log_fh,"%s",marker_str); |
769 | 615 | fflush(log_fh); |
770 | 615 | } |
771 | 615 | } |
772 | | |
773 | | /* Decompress the image */ |
774 | 670 | if (quality >= 0 && retry != 1) { |
775 | 419 | jpeg_start_decompress(&dinfo); |
776 | | |
777 | | /* Allocate line buffer to store the decompressed image */ |
778 | 419 | if (!(buf = calloc(dinfo.output_height, sizeof(JSAMPROW)))) |
779 | 0 | fatal("not enough memory"); |
780 | 2.87M | for (int i = 0; i < dinfo.output_height; i++) { |
781 | 2.87M | if (!(buf[i]=calloc((size_t)dinfo.output_width * dinfo.out_color_components, |
782 | 2.87M | sizeof(JSAMPLE)))) |
783 | 0 | fatal("not enough memory"); |
784 | 2.87M | } |
785 | | |
786 | 2.31M | while (dinfo.output_scanline < dinfo.output_height) { |
787 | 2.31M | jpeg_read_scanlines(&dinfo,&buf[dinfo.output_scanline], |
788 | 2.31M | dinfo.output_height-dinfo.output_scanline); |
789 | 2.31M | } |
790 | 419 | } else { |
791 | 170 | coef_arrays = jpeg_read_coefficients(&dinfo); |
792 | 170 | if (!coef_arrays) { |
793 | 0 | if (!quiet_mode) |
794 | 0 | fprintf(log_fh, " (failed to read coefficients) "); |
795 | 0 | goto abort_decompress; |
796 | 0 | } |
797 | 170 | } |
798 | 589 | if (!retry) { |
799 | 391 | in_image_size = inbufferused - dinfo.src->bytes_in_buffer; |
800 | 391 | if(verbose_mode > 2) |
801 | 0 | fprintf(log_fh, " (input image size: %lu (%lu))", |
802 | 0 | in_image_size, inbufferused); |
803 | 391 | if (stdin_mode) { |
804 | 0 | insize = in_image_size; |
805 | 391 | } else { |
806 | 391 | if ((insize = file_size(infile)) < 0) |
807 | 0 | fatal("failed to stat() input file"); |
808 | 391 | if (in_image_size > 0 && in_image_size < insize) { |
809 | 187 | if (!quiet_mode) |
810 | 187 | fprintf(log_fh, " (%lu bytes extraneous data found after end of image) ", |
811 | 187 | insize - in_image_size); |
812 | 187 | if (nofix_mode) |
813 | 0 | global_error_counter++; |
814 | 187 | if (save_extra) { |
815 | 0 | extrabuffersize = insize - in_image_size; |
816 | 0 | if (extrabuffer) |
817 | 0 | free(extrabuffer); |
818 | 0 | if (!(extrabuffer = calloc(extrabuffersize, 1))) |
819 | 0 | fatal("not enough memory"); |
820 | 0 | if (fseek(infile, in_image_size, SEEK_SET)) |
821 | 0 | fatal("failed to seek input file"); |
822 | 0 | if (fread(extrabuffer, extrabuffersize, 1, infile) != 1) |
823 | 0 | fatal("failed to read inputfile"); |
824 | 0 | } |
825 | 187 | } |
826 | 391 | } |
827 | 391 | if (!quiet_mode) { |
828 | 391 | fprintf(log_fh,(global_error_counter==0 ? " [OK] " : " [WARNING] ")); |
829 | 391 | fflush(log_fh); |
830 | 391 | } |
831 | | |
832 | 391 | if (nofix_mode && global_error_counter != 0) { |
833 | | /* Skip files containing any errors (or warnings) */ |
834 | 0 | goto abort_decompress; |
835 | 0 | } |
836 | | |
837 | 391 | if (dest && !noaction) { |
838 | 0 | if (file_exists(newname) && !overwrite_mode) { |
839 | 0 | if (!quiet_mode) |
840 | 0 | fprintf(log_fh, " (target file already exists) "); |
841 | 0 | goto abort_decompress; |
842 | 0 | } |
843 | 0 | } |
844 | 391 | } |
845 | | |
846 | | |
847 | | /* Prepare to compress... */ |
848 | 589 | if (setjmp(jcerr.setjmp_buffer)) { |
849 | | /* Error handler for compress failures */ |
850 | 58 | if (!quiet_mode) |
851 | 58 | fprintf(log_fh," [Compress ERROR: %s]\n",last_error); |
852 | 58 | jpeg_abort_compress(&cinfo); |
853 | 58 | jpeg_abort_decompress(&dinfo); |
854 | 58 | fclose(infile); |
855 | 58 | free_line_buf(&buf, dinfo.output_height); |
856 | 58 | jcerr.jump_set=0; |
857 | 58 | res = 2; |
858 | 58 | goto exit_point; |
859 | 531 | } else { |
860 | 531 | jcerr.jump_set=1; |
861 | 531 | } |
862 | | |
863 | 531 | lastsize = 0; |
864 | 531 | searchdone = 0; |
865 | 531 | if (!retry) { |
866 | 391 | oldquality = 200; |
867 | 391 | if (target_size != 0) { |
868 | | /* Always start with quality 100 if -S option specified... */ |
869 | 391 | quality = 100; |
870 | 391 | } |
871 | 391 | } |
872 | | |
873 | | |
874 | 1.35k | binary_search_loop: |
875 | | |
876 | | /* Allocate memory buffer that should be large enough to store the output JPEG... */ |
877 | 1.35k | if (outbuffer) |
878 | 961 | free(outbuffer); |
879 | 1.35k | outbuffersize = insize + 32768; |
880 | 1.35k | if (!(outbuffer=calloc(outbuffersize, 1))) |
881 | 0 | fatal("not enough memory"); |
882 | | |
883 | | /* setup custom "destination manager" for libjpeg to write to our buffer */ |
884 | 1.35k | jpeg_memory_dest(&cinfo, &outbuffer, &outbuffersize, 65536); |
885 | | |
886 | | |
887 | 1.35k | if (quality >= 0 && retry != 1) { |
888 | | /* Lossy "optimization" ... */ |
889 | | |
890 | 1.10k | cinfo.in_color_space=dinfo.out_color_space; |
891 | 1.10k | cinfo.input_components=dinfo.output_components; |
892 | 1.10k | cinfo.image_width=dinfo.image_width; |
893 | 1.10k | cinfo.image_height=dinfo.image_height; |
894 | 1.10k | jpeg_set_defaults(&cinfo); |
895 | 1.10k | jpeg_set_quality(&cinfo,quality,TRUE); |
896 | | #ifdef HAVE_JINT_DC_SCAN_OPT_MODE |
897 | | if (jpeg_c_int_param_supported(&cinfo, JINT_DC_SCAN_OPT_MODE)) |
898 | | jpeg_c_set_int_param(&cinfo, JINT_DC_SCAN_OPT_MODE, 1); |
899 | | #endif |
900 | 1.10k | if (all_normal || (!dinfo.progressive_mode && !all_progressive)) { |
901 | | /* Explicitly disable progressive mode. */ |
902 | 883 | cinfo.scan_info = NULL; |
903 | 883 | cinfo.num_scans = 0; |
904 | 883 | } else if (all_progressive || dinfo.progressive_mode) { |
905 | | /* Enable progressive mode. */ |
906 | 220 | jpeg_simple_progression(&cinfo); |
907 | 220 | } |
908 | 1.10k | cinfo.optimize_coding = TRUE; |
909 | 1.10k | #ifdef HAVE_ARITH_CODE |
910 | 1.10k | if (arith_mode >= 0) |
911 | 0 | cinfo.arith_code = (arith_mode > 0 ? TRUE : FALSE); |
912 | 1.10k | #endif |
913 | 1.10k | if (dinfo.saw_JFIF_marker && (save_jfif || strip_none)) { |
914 | 336 | cinfo.write_JFIF_header = TRUE; |
915 | 767 | } else { |
916 | 767 | cinfo.write_JFIF_header = FALSE; |
917 | 767 | } |
918 | 1.10k | if (dinfo.saw_Adobe_marker && (save_adobe || strip_none)) { |
919 | | /* If outputting Adobe marker, don't write JFIF marker... */ |
920 | 0 | cinfo.write_JFIF_header = FALSE; |
921 | 0 | } |
922 | | |
923 | 1.10k | jpeg_start_compress(&cinfo,TRUE); |
924 | | |
925 | | /* Write markers */ |
926 | 1.10k | write_markers(&dinfo,&cinfo); |
927 | | |
928 | | /* Write image */ |
929 | 2.19k | while (cinfo.next_scanline < cinfo.image_height) { |
930 | 1.09k | jpeg_write_scanlines(&cinfo,&buf[cinfo.next_scanline], |
931 | 1.09k | dinfo.output_height); |
932 | 1.09k | } |
933 | | |
934 | 1.10k | } else { |
935 | | /* Lossless optimization ... */ |
936 | | |
937 | 249 | jpeg_copy_critical_parameters(&dinfo, &cinfo); |
938 | | #ifdef HAVE_JINT_DC_SCAN_OPT_MODE |
939 | | if (jpeg_c_int_param_supported(&cinfo, JINT_DC_SCAN_OPT_MODE)) |
940 | | jpeg_c_set_int_param(&cinfo, JINT_DC_SCAN_OPT_MODE, 1); |
941 | | #endif |
942 | 249 | if (all_normal || (!dinfo.progressive_mode && !all_progressive)) { |
943 | | /* Explicitly disable progressive mode. */ |
944 | 182 | cinfo.scan_info = NULL; |
945 | 182 | cinfo.num_scans = 0; |
946 | 182 | } else if (all_progressive || dinfo.progressive_mode) { |
947 | | /* Enable progressive mode. */ |
948 | 55 | jpeg_simple_progression(&cinfo); |
949 | 55 | } |
950 | 249 | cinfo.optimize_coding = TRUE; |
951 | 249 | #ifdef HAVE_ARITH_CODE |
952 | 249 | if (arith_mode >= 0) |
953 | 0 | cinfo.arith_code = (arith_mode > 0 ? TRUE : FALSE); |
954 | 249 | #endif |
955 | 249 | if (dinfo.saw_JFIF_marker && (save_jfif || strip_none)) { |
956 | 27 | cinfo.write_JFIF_header = TRUE; |
957 | 222 | } else { |
958 | 222 | cinfo.write_JFIF_header = FALSE; |
959 | 222 | } |
960 | 249 | if (dinfo.saw_Adobe_marker && (save_adobe || strip_none)) { |
961 | | /* If outputting Adobe marker, don't write JFIF marker... */ |
962 | 0 | cinfo.write_JFIF_header = FALSE; |
963 | 0 | } |
964 | | |
965 | | /* Write image */ |
966 | 249 | jpeg_write_coefficients(&cinfo, coef_arrays); |
967 | | |
968 | | /* Write markers */ |
969 | 249 | write_markers(&dinfo,&cinfo); |
970 | | |
971 | 249 | } |
972 | | |
973 | 1.35k | jpeg_finish_compress(&cinfo); |
974 | 1.35k | outsize = outbuffersize + extrabuffersize; |
975 | 1.35k | if (verbose_mode > 2) |
976 | 0 | fprintf(log_fh, " (output image size: %lu (%lu))", outsize,extrabuffersize); |
977 | | |
978 | 1.35k | if (target_size != 0 && !retry) { |
979 | | /* Perform (binary) search to try to reach target file size... */ |
980 | | |
981 | 1.09k | long osize = outsize/1024; |
982 | 1.09k | long isize = insize/1024; |
983 | 1.09k | long tsize = target_size; |
984 | | |
985 | 1.09k | if (verbose_mode > 1) |
986 | 0 | fprintf(log_fh, "(size=%ld)",outsize); |
987 | 1.09k | if (tsize < 0) { |
988 | 1.09k | tsize=((-target_size)*insize/100)/1024; |
989 | 1.09k | if (tsize < 1) |
990 | 394 | tsize = 1; |
991 | 1.09k | } |
992 | | |
993 | 1.09k | if (osize == tsize || searchdone || tsize > isize) { |
994 | 397 | if (searchdone < 42 && lastsize > 0) { |
995 | 159 | if (labs(osize-tsize) > labs(lastsize-tsize)) { |
996 | 14 | if (verbose_mode) |
997 | 0 | fprintf(log_fh,"(revert to %d)",oldquality); |
998 | 14 | searchdone = 42; |
999 | 14 | quality = oldquality; |
1000 | 14 | goto binary_search_loop; |
1001 | 14 | } |
1002 | 159 | } |
1003 | 383 | if (verbose_mode) |
1004 | 0 | fprintf(log_fh," "); |
1005 | | |
1006 | 698 | } else { |
1007 | 698 | int newquality; |
1008 | 698 | double dif = abs(oldquality-quality) / 2.0; |
1009 | | |
1010 | 698 | if (osize > tsize) |
1011 | 494 | newquality = quality - dif; |
1012 | 204 | else |
1013 | 204 | newquality = quality + dif + 0.5; |
1014 | | |
1015 | 698 | if (dif < 1.0) |
1016 | 17 | searchdone = 1; |
1017 | 698 | if (newquality < 0) { |
1018 | 57 | newquality = 0; |
1019 | 57 | searchdone = 1; |
1020 | 57 | } |
1021 | 698 | if (newquality > 100) { |
1022 | 48 | newquality = 100; |
1023 | 48 | searchdone = 1; |
1024 | 48 | } |
1025 | | |
1026 | 698 | oldquality = quality; |
1027 | 698 | quality = newquality; |
1028 | 698 | lastsize = osize; |
1029 | 698 | if (verbose_mode) |
1030 | 0 | fprintf(log_fh,"(try %d)",quality); |
1031 | 698 | goto binary_search_loop; |
1032 | 698 | } |
1033 | 1.09k | } |
1034 | | |
1035 | 640 | jpeg_finish_decompress(&dinfo); |
1036 | 640 | free_line_buf(&buf, dinfo.output_height); |
1037 | | |
1038 | 640 | if (retry_mode) { |
1039 | 0 | if ((retry == 0 || retry == 2) && quality >= 0 && outsize <= insize) { |
1040 | | /* Retry compression until output file stops getting smaller |
1041 | | or we hit max limit of iterations (10)... */ |
1042 | 0 | if (retry_count == 0) |
1043 | 0 | last_retry_size = outsize + 1; |
1044 | 0 | if (++retry_count < 10 && outsize < last_retry_size) { |
1045 | 0 | if (tmpbuffer) |
1046 | 0 | free(tmpbuffer); |
1047 | 0 | tmpbuffer = outbuffer; |
1048 | 0 | tmpbuffersize = outbuffersize; |
1049 | 0 | outbuffer = NULL; |
1050 | 0 | last_retry_size = outsize; |
1051 | 0 | retry = 2; |
1052 | 0 | if (verbose_mode) |
1053 | 0 | fprintf(log_fh, "(retry%d: %lu) ", retry_count, outsize); |
1054 | 0 | goto retry_point; |
1055 | 0 | } |
1056 | 0 | } |
1057 | 0 | if (retry == 2) { |
1058 | 0 | if (verbose_mode) |
1059 | 0 | fprintf(log_fh, "(retry done: %lu) ", outsize); |
1060 | 0 | if (outsize > last_retry_size) { |
1061 | 0 | if (outbuffer) |
1062 | 0 | free(outbuffer); |
1063 | 0 | outbuffer = tmpbuffer; |
1064 | 0 | outbuffersize = tmpbuffersize; |
1065 | 0 | outsize = outbuffersize + extrabuffersize; |
1066 | 0 | tmpbuffer = NULL; |
1067 | 0 | } |
1068 | 0 | } |
1069 | 0 | } |
1070 | | |
1071 | | /* If auto_mode, try both progressive and non-progressive... */ |
1072 | 640 | if (auto_mode) { |
1073 | 0 | int newmode = (dinfo.progressive_mode ? 0 : 1); |
1074 | 0 | if (retry != 3) { |
1075 | 0 | if (newmode) |
1076 | 0 | all_progressive = 1; |
1077 | 0 | else |
1078 | 0 | all_normal = 1; |
1079 | 0 | if (tmpbuffer) |
1080 | 0 | free(tmpbuffer); |
1081 | 0 | tmpbuffer = outbuffer; |
1082 | 0 | tmpbuffersize = outbuffersize; |
1083 | 0 | outbuffer = NULL; |
1084 | 0 | last_retry_size = outsize; |
1085 | 0 | retry = 3; |
1086 | 0 | if (verbose_mode) |
1087 | 0 | fprintf(log_fh, "(retry w/%s) ", (newmode ? "progressive" : "normal")); |
1088 | 0 | goto retry_point; |
1089 | 0 | } else { |
1090 | 0 | if (verbose_mode > 1) |
1091 | 0 | fprintf(log_fh, "(automode done: %lu) ", outsize); |
1092 | 0 | if (outsize > last_retry_size) { |
1093 | 0 | if (verbose_mode) |
1094 | 0 | fprintf(log_fh, "(revert to %s) ", (!newmode ? "progressive" : "normal")); |
1095 | 0 | all_progressive = 0; |
1096 | 0 | all_normal = 0; |
1097 | 0 | if (outbuffer) |
1098 | 0 | free(outbuffer); |
1099 | 0 | outbuffer = tmpbuffer; |
1100 | 0 | outbuffersize = tmpbuffersize; |
1101 | 0 | outsize = outbuffersize + extrabuffersize; |
1102 | 0 | tmpbuffer = NULL; |
1103 | 0 | } |
1104 | 0 | } |
1105 | 0 | } |
1106 | | |
1107 | | /* In case "lossy" compression resulted larger file than original, retry with "lossless"... */ |
1108 | 640 | if (quality >= 0 && outsize >= insize && retry != 1) { |
1109 | 251 | retry = 1; |
1110 | 251 | if (verbose_mode) |
1111 | 0 | fprintf(log_fh, "(retry w/lossless) "); |
1112 | 251 | goto retry_point; |
1113 | 251 | } |
1114 | | |
1115 | 389 | fclose(infile); |
1116 | | |
1117 | 389 | ratio = (insize - outsize) * 100.0 / insize; |
1118 | 389 | if (!quiet_mode || csv) |
1119 | 280 | fprintf(log_fh,csv ? "%ld,%ld,%0.2f," : "%ld --> %ld bytes (%0.2f%%), ",insize,outsize,ratio); |
1120 | 389 | if (rate) { |
1121 | 280 | *rate = (ratio < 0 ? 0.0 : ratio); |
1122 | 280 | } |
1123 | | |
1124 | 389 | if ((outsize < insize && ratio >= threshold) || force) { |
1125 | 93 | if (saved) { |
1126 | 93 | *saved = (insize - outsize) / 1024.0; |
1127 | 93 | } |
1128 | 93 | if (!quiet_mode || csv) |
1129 | 93 | fprintf(log_fh,csv ? "optimized\n" : "optimized.\n"); |
1130 | 93 | if (noaction) { |
1131 | 0 | res = 0; |
1132 | 0 | goto exit_point; |
1133 | 0 | } |
1134 | | |
1135 | 93 | if (stdout_mode) { |
1136 | 0 | outfname=NULL; |
1137 | 0 | set_filemode_binary(stdout); |
1138 | 0 | if (fwrite(outbuffer,outbuffersize,1,stdout) != 1) |
1139 | 0 | fatal("%s, write failed to stdout",(stdin_mode ? "stdin" : filename)); |
1140 | 93 | } else { |
1141 | 93 | if (preserve_perms && !dest) { |
1142 | | /* make backup of the original file */ |
1143 | 0 | int newlen = snprintf(tmpfilename, sizeof(tmpfilename), |
1144 | 0 | "%s.jpegoptim.bak", newname); |
1145 | 0 | if (newlen >= sizeof(tmpfilename)) |
1146 | 0 | fatal("temp filename too long: %s", tmpfilename); |
1147 | |
|
1148 | 0 | if (verbose_mode > 1) |
1149 | 0 | fprintf(log_fh,"%s, creating backup as: %s\n", |
1150 | 0 | (stdin_mode ? "stdin" : filename), tmpfilename); |
1151 | 0 | if (file_exists(tmpfilename)) |
1152 | 0 | fatal("%s, backup file already exists: %s", |
1153 | 0 | (stdin_mode ?" stdin" : filename), tmpfilename); |
1154 | 0 | if (copy_file(newname,tmpfilename)) |
1155 | 0 | fatal("%s, failed to create backup: %s", |
1156 | 0 | (stdin_mode ? "stdin" : filename), tmpfilename); |
1157 | 0 | if ((outfile=create_file(newname))==NULL) |
1158 | 0 | fatal("%s, error opening output file: %s", |
1159 | 0 | (stdin_mode ? "stdin" : filename), newname); |
1160 | 0 | outfname = newname; |
1161 | 93 | } else { |
1162 | 93 | if (!(outfile = create_temp_file(tmpdir, "jpegoptim", tmpfilename, sizeof(tmpfilename)))) |
1163 | 0 | fatal("error creating temporary file: %s", tmpfilename); |
1164 | 93 | outfname = tmpfilename; |
1165 | 93 | } |
1166 | | |
1167 | 93 | if (verbose_mode > 1) |
1168 | 0 | fprintf(log_fh,"writing %lu bytes to file: %s\n", |
1169 | 0 | (long unsigned int)outbuffersize, outfname); |
1170 | 93 | if (fwrite(outbuffer, outbuffersize, 1, outfile) != 1) |
1171 | 0 | fatal("write failed to file: %s", outfname); |
1172 | 93 | if (save_extra && extrabuffersize > 0) { |
1173 | 0 | if (verbose_mode > 1) |
1174 | 0 | fprintf(log_fh,"writing %lu bytes to file: %s\n", extrabuffersize, outfname); |
1175 | 0 | if (fwrite(extrabuffer, extrabuffersize, 1, outfile) != 1) |
1176 | 0 | fatal("write failed to file: %s", outfname); |
1177 | 0 | } |
1178 | 93 | fclose(outfile); |
1179 | 93 | } |
1180 | | |
1181 | 93 | if (outfname) { |
1182 | 93 | if (preserve_mode) { |
1183 | | /* preserve file modification time */ |
1184 | 0 | if (verbose_mode > 1) |
1185 | 0 | fprintf(log_fh,"set file modification time same as in original: %s\n", |
1186 | 0 | outfname); |
1187 | 0 | #if defined(HAVE_UTIMENSAT) && defined(HAVE_STRUCT_STAT_ST_MTIM) |
1188 | 0 | struct timespec time_save[2]; |
1189 | 0 | time_save[0].tv_sec = 0; |
1190 | 0 | time_save[0].tv_nsec = UTIME_OMIT; /* omit atime */ |
1191 | 0 | time_save[1] = file_stat->st_mtim; |
1192 | 0 | if (utimensat(AT_FDCWD,outfname,time_save,0) != 0) |
1193 | 0 | warn("failed to reset output file time/date"); |
1194 | | #else |
1195 | | struct utimbuf time_save; |
1196 | | time_save.actime=file_stat->st_atime; |
1197 | | time_save.modtime=file_stat->st_mtime; |
1198 | | if (utime(outfname,&time_save) != 0) |
1199 | | warn("failed to reset output file time/date"); |
1200 | | #endif |
1201 | 0 | } |
1202 | | |
1203 | 93 | if (preserve_perms && !dest) { |
1204 | | /* original file was already replaced, remove backup... */ |
1205 | 0 | if (verbose_mode > 1) |
1206 | 0 | fprintf(log_fh,"removing backup file: %s\n", tmpfilename); |
1207 | 0 | if (delete_file(tmpfilename)) |
1208 | 0 | warn("failed to remove backup file: %s", tmpfilename); |
1209 | 93 | } else { |
1210 | | /* make temp file to be the original file... */ |
1211 | | |
1212 | | /* preserve file mode */ |
1213 | 93 | if (chmod(outfname,(file_stat->st_mode & 0777)) != 0) |
1214 | 0 | warn("failed to set output file mode"); |
1215 | | |
1216 | | /* preserve file group (and owner if run by root) */ |
1217 | 93 | if (chown(outfname, |
1218 | 93 | (geteuid()==0 ? file_stat->st_uid : -1), |
1219 | 93 | file_stat->st_gid) != 0) |
1220 | 0 | warn("failed to reset output file group/owner"); |
1221 | | |
1222 | 93 | if (verbose_mode > 1) |
1223 | 0 | fprintf(log_fh,"renaming: %s to %s\n", outfname, newname); |
1224 | 93 | if (rename_file(outfname, newname)) |
1225 | 0 | fatal("cannot rename temp file"); |
1226 | 93 | } |
1227 | 93 | } |
1228 | 296 | } else { |
1229 | 296 | if (!quiet_mode || csv) |
1230 | 187 | fprintf(log_fh,csv ? "skipped\n" : "skipped.\n"); |
1231 | 296 | if (stdout_mode) { |
1232 | 0 | set_filemode_binary(stdout); |
1233 | 0 | if (fwrite(inbuffer, in_image_size, 1, stdout) != 1) |
1234 | 0 | fatal("%s, write failed to stdout", |
1235 | 0 | (stdin_mode ? "stdin" : filename)); |
1236 | 0 | } |
1237 | 296 | } |
1238 | | |
1239 | 389 | res = 0; |
1240 | | |
1241 | 678 | exit_point: |
1242 | 678 | if (inbuffer) |
1243 | 678 | free(inbuffer); |
1244 | 678 | if (outbuffer) |
1245 | 391 | free(outbuffer); |
1246 | 678 | if (tmpbuffer) |
1247 | 0 | free(tmpbuffer); |
1248 | 678 | if (extrabuffer) |
1249 | 0 | free(extrabuffer); |
1250 | 678 | jpeg_destroy_compress(&cinfo); |
1251 | 678 | jpeg_destroy_decompress(&dinfo); |
1252 | | |
1253 | 678 | return res; |
1254 | 389 | } |
1255 | | |
1256 | | |
1257 | | #ifdef PARALLEL_PROCESSING |
1258 | | int wait_for_worker(FILE *log_fh) |
1259 | 0 | { |
1260 | 0 | FILE *p; |
1261 | 0 | struct worker *w; |
1262 | 0 | char buf[1024]; |
1263 | 0 | int wstatus; |
1264 | 0 | pid_t pid; |
1265 | 0 | int j, e; |
1266 | 0 | int state = 0; |
1267 | 0 | double val; |
1268 | 0 | double rate = 0.0; |
1269 | 0 | double saved = 0.0; |
1270 | | |
1271 | |
|
1272 | 0 | if ((pid = wait(&wstatus)) < 0) |
1273 | 0 | return pid; |
1274 | | |
1275 | 0 | w = NULL; |
1276 | 0 | for (j = 0; j < MAX_WORKERS; j++) { |
1277 | 0 | if (workers[j].pid == pid) { |
1278 | 0 | w = &workers[j]; |
1279 | 0 | break; |
1280 | 0 | } |
1281 | 0 | } |
1282 | 0 | if (!w) |
1283 | 0 | fatal("Unknown worker[%d] process found\n", pid); |
1284 | |
|
1285 | 0 | if (WIFEXITED(wstatus)) { |
1286 | 0 | e = WEXITSTATUS(wstatus); |
1287 | 0 | if (verbose_mode) |
1288 | 0 | fprintf(log_fh, "worker[%d] [slot=%d] exited: %d\n", |
1289 | 0 | pid, j, e); |
1290 | 0 | if (e == 0) { |
1291 | | //average_count++; |
1292 | | //average_rate += rate; |
1293 | | //total_save += saved; |
1294 | 0 | } else if (e == 1) { |
1295 | 0 | decompress_err_count++; |
1296 | 0 | } else if (e == 2) { |
1297 | 0 | compress_err_count++; |
1298 | 0 | } |
1299 | 0 | } else { |
1300 | 0 | fatal("worker[%d] killed", pid); |
1301 | 0 | } |
1302 | |
|
1303 | 0 | p = fdopen(w->read_pipe, "r"); |
1304 | 0 | if (!p) fatal("fdopen failed()"); |
1305 | 0 | while (fgets(buf, sizeof(buf), p)) { |
1306 | 0 | if (verbose_mode > 2) |
1307 | 0 | fprintf(log_fh, "worker[%d] PIPE: %s", pid, buf); |
1308 | 0 | if (state == 0 && buf[0] == '\n') { |
1309 | 0 | state=1; |
1310 | 0 | continue; |
1311 | 0 | } |
1312 | 0 | if (state == 1 && !strncmp(buf, "STAT", 4)) { |
1313 | 0 | state=2; |
1314 | 0 | continue; |
1315 | 0 | } |
1316 | 0 | if (state >= 2) { |
1317 | 0 | if (sscanf(buf, "%lf", &val) == 1) { |
1318 | 0 | if (state == 2) { |
1319 | 0 | rate = val; |
1320 | 0 | } |
1321 | 0 | else if (state == 3) { |
1322 | 0 | saved = val; |
1323 | 0 | average_count++; |
1324 | 0 | average_rate += rate; |
1325 | 0 | total_save += saved; |
1326 | 0 | } |
1327 | 0 | } |
1328 | 0 | state++; |
1329 | 0 | continue; |
1330 | 0 | } |
1331 | 0 | if (state == 0) |
1332 | 0 | fprintf(log_fh, "%s", buf); |
1333 | 0 | } |
1334 | 0 | close(w->read_pipe); |
1335 | 0 | w->pid = -1; |
1336 | 0 | w->read_pipe = -1; |
1337 | 0 | worker_count --; |
1338 | |
|
1339 | 0 | return pid; |
1340 | 0 | } |
1341 | | #endif |
1342 | | |
1343 | | |
1344 | | #ifndef BUILD_FOR_OSS_FUZZ // Libfuzzer provides its own fuzzer |
1345 | | /****************************************************************************/ |
1346 | | int main(int argc, char **argv) |
1347 | | { |
1348 | | struct stat file_stat; |
1349 | | char tmpfilename[MAXPATHLEN + 1],tmpdir[MAXPATHLEN + 1]; |
1350 | | char newname[MAXPATHLEN + 1], dest_path[MAXPATHLEN + 1]; |
1351 | | char namebuf[MAXPATHLEN + 2]; |
1352 | | const char *filename; |
1353 | | int arg_idx; |
1354 | | int res; |
1355 | | double rate, saved; |
1356 | | FILE *log_fh; |
1357 | | #ifdef PARALLEL_PROCESSING |
1358 | | struct worker *w; |
1359 | | int pipe_fd[2]; |
1360 | | pid_t pid; |
1361 | | |
1362 | | |
1363 | | /* Allocate table to keep track of child processes... */ |
1364 | | if (!(workers = calloc(MAX_WORKERS, sizeof(struct worker)))) |
1365 | | fatal("not enough memory"); |
1366 | | for (int i = 0; i < MAX_WORKERS; i++) { |
1367 | | workers[i].pid = -1; |
1368 | | workers[i].read_pipe = -1; |
1369 | | } |
1370 | | #endif |
1371 | | |
1372 | | umask(077); |
1373 | | signal(SIGINT,own_signal_handler); |
1374 | | signal(SIGTERM,own_signal_handler); |
1375 | | |
1376 | | /* Parse command line parameters */ |
1377 | | parse_arguments(argc, argv, dest_path, sizeof(dest_path)); |
1378 | | log_fh = (stdout_mode ? stderr : stdout); |
1379 | | if (quiet_mode) |
1380 | | verbose_mode = 0; |
1381 | | |
1382 | | if (verbose_mode) { |
1383 | | if (quality >= 0 && target_size == 0) |
1384 | | fprintf(log_fh, "Image quality limit set to: %d\n", quality); |
1385 | | if (threshold >= 0) |
1386 | | fprintf(log_fh, "Compression threshold (%%) set to: %0.1lf\n", threshold); |
1387 | | if (all_normal) |
1388 | | fprintf(log_fh, "All output files will be non-progressive\n"); |
1389 | | if (all_progressive) |
1390 | | fprintf(log_fh, "All output files will be progressive\n"); |
1391 | | if (target_size > 0) |
1392 | | fprintf(log_fh, "Target size for output files set to: %d Kbytes.\n", |
1393 | | target_size); |
1394 | | if (target_size < 0) |
1395 | | fprintf(log_fh, "Target size for output files set to: %d%%\n", |
1396 | | -target_size); |
1397 | | #ifdef PARALLEL_PROCESSING |
1398 | | if (max_workers > 0) |
1399 | | fprintf(log_fh, "Using maximum of %d parallel threads\n", max_workers); |
1400 | | #endif |
1401 | | } |
1402 | | |
1403 | | |
1404 | | if (stdin_mode) { |
1405 | | /* Process just one file, if source is stdin... */ |
1406 | | res = optimize(stderr, NULL, NULL, NULL, &file_stat, NULL, NULL); |
1407 | | return (res == 0 ? 0 : 1); |
1408 | | } |
1409 | | |
1410 | | arg_idx = (optind > 0 ? optind : 1); |
1411 | | if (files_from == NULL && argc <= arg_idx) { |
1412 | | if (!quiet_mode) |
1413 | | fprintf(stderr, PROGRAMNAME ": file argument(s) missing\n" |
1414 | | "Try '" PROGRAMNAME " --help' for more information.\n"); |
1415 | | exit(1); |
1416 | | } |
1417 | | |
1418 | | |
1419 | | /* Main loop to process input files */ |
1420 | | do { |
1421 | | if (files_from) { |
1422 | | if (!fgetstr(namebuf, sizeof(namebuf), files_from)) |
1423 | | break; |
1424 | | filename = namebuf; |
1425 | | } else { |
1426 | | filename = argv[arg_idx]; |
1427 | | } |
1428 | | |
1429 | | if (*filename == 0) |
1430 | | continue; |
1431 | | if (verbose_mode > 1) |
1432 | | fprintf(log_fh, "processing file: %s\n", filename); |
1433 | | if (strnlen(filename, MAXPATHLEN + 1) > MAXPATHLEN) { |
1434 | | warn("skipping too long filename: %s", filename); |
1435 | | continue; |
1436 | | } |
1437 | | |
1438 | | if (!noaction) { |
1439 | | /* generate tmp dir & new filename */ |
1440 | | if (dest) { |
1441 | | strncopy(tmpdir, dest_path, sizeof(tmpdir)); |
1442 | | strncopy(newname, dest_path, sizeof(newname)); |
1443 | | if (!splitname(filename, tmpfilename, sizeof(tmpfilename))) |
1444 | | fatal("splitname() failed for: %s", filename); |
1445 | | strncatenate(newname, tmpfilename, sizeof(newname)); |
1446 | | } else { |
1447 | | if (!splitdir(filename, tmpdir, sizeof(tmpdir))) |
1448 | | fatal("splitdir() failed for: %s", filename); |
1449 | | strncopy(newname, filename, sizeof(newname)); |
1450 | | } |
1451 | | } |
1452 | | |
1453 | | if (file_exists(filename)) { |
1454 | | if (!is_file(filename, &file_stat)) { |
1455 | | if (is_directory(filename)) |
1456 | | warn("skipping directory: %s", filename); |
1457 | | else |
1458 | | warn("skipping special file: %s", filename); |
1459 | | continue; |
1460 | | } |
1461 | | } else { |
1462 | | warn("file not found: %s", filename); |
1463 | | continue; |
1464 | | } |
1465 | | |
1466 | | #ifdef PARALLEL_PROCESSING |
1467 | | if (max_workers > 1) { |
1468 | | /* Multi process mode, run up to max_workers processes simultaneously... */ |
1469 | | |
1470 | | if (worker_count >= max_workers) { |
1471 | | // wait for a worker to exit... |
1472 | | wait_for_worker(log_fh); |
1473 | | } |
1474 | | if (pipe(pipe_fd) < 0) |
1475 | | fatal("failed to open pipe"); |
1476 | | pid = fork(); |
1477 | | if (pid < 0) |
1478 | | fatal("fork() failed"); |
1479 | | if (pid == 0) { |
1480 | | /* Child process starts here... */ |
1481 | | if (files_from) |
1482 | | fclose(files_from); |
1483 | | close(pipe_fd[0]); |
1484 | | FILE *p; |
1485 | | |
1486 | | if (!(p = fdopen(pipe_fd[1],"w"))) |
1487 | | fatal("worker: fdopen failed"); |
1488 | | |
1489 | | res = optimize(p, filename, newname, tmpdir, &file_stat, &rate, &saved); |
1490 | | if (res == 0) |
1491 | | fprintf(p, "\n\nSTATS\n%lf\n%lf\n", rate, saved); |
1492 | | exit(res); |
1493 | | } else { |
1494 | | /* Parent continues here... */ |
1495 | | int j; |
1496 | | |
1497 | | close(pipe_fd[1]); |
1498 | | |
1499 | | w = NULL; |
1500 | | for (j = 0; j < MAX_WORKERS; j++) { |
1501 | | if (workers[j].pid < 0) { |
1502 | | w = &workers[j]; |
1503 | | break; |
1504 | | } |
1505 | | } |
1506 | | if (!w) |
1507 | | fatal("no space to start a new worker (%d)", worker_count); |
1508 | | w->pid = pid; |
1509 | | w->read_pipe = pipe_fd[0]; |
1510 | | worker_count++; |
1511 | | if (verbose_mode > 0) |
1512 | | fprintf(log_fh, "worker[%d] [slot=%d] started\n", pid, j);; |
1513 | | } |
1514 | | |
1515 | | } else |
1516 | | #endif |
1517 | | { |
1518 | | /* Single process mode, process one file at a time... */ |
1519 | | |
1520 | | res = optimize(log_fh, filename, newname, tmpdir, &file_stat, &rate, &saved); |
1521 | | if (res == 0) { |
1522 | | average_count++; |
1523 | | average_rate += rate; |
1524 | | total_save += saved; |
1525 | | } else if (res == 1) { |
1526 | | decompress_err_count++; |
1527 | | } else if (res == 2) { |
1528 | | compress_err_count++; |
1529 | | } |
1530 | | } |
1531 | | |
1532 | | } while (files_from || ++arg_idx < argc); |
1533 | | |
1534 | | |
1535 | | #ifdef PARALLEL_PROCESSING |
1536 | | /* Wait for any child processes to exit... */ |
1537 | | if (max_workers > 1) { |
1538 | | if (verbose_mode) { |
1539 | | fprintf(log_fh, "Waiting for %d workers to finish...\n", worker_count); |
1540 | | } |
1541 | | while ((pid = wait_for_worker(log_fh)) > 0) { |
1542 | | if (verbose_mode > 2) |
1543 | | fprintf(log_fh, "worker[%d] done\n", pid); |
1544 | | } |
1545 | | } |
1546 | | #endif |
1547 | | |
1548 | | if (totals_mode && !quiet_mode) |
1549 | | fprintf(log_fh, "Average ""compression"" (%ld files): %0.2f%% (total saved %0.0fk)\n", |
1550 | | average_count, average_rate/average_count, total_save); |
1551 | | |
1552 | | |
1553 | | return (decompress_err_count > 0 || compress_err_count > 0 ? 1 : 0);; |
1554 | | } |
1555 | | #else |
1556 | | void fuzz_set_target_size(const int new_target_size) |
1557 | 1 | { |
1558 | 1 | target_size = new_target_size; |
1559 | 1 | } |
1560 | | #endif /* BUILD_FOR_OSS_FUZZ */ |
1561 | | /* eof :-) */ |