Skip to content
This repository
Newer
Older
100644 474 lines (414 sloc) 13.56 kb
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
1 // Copyright 2006 Google Inc. All Rights Reserved.
2 // Author: agl@imperialviolet.org (Adam Langley)
3 //
4 // Copyright (C) 2006 Google Inc.
5 //
6 // Licensed under the Apache License, Version 2.0 (the "License");
7 // you may not use this file except in compliance with the License.
8 // You may obtain a copy of the License at
9 //
10 // http://www.apache.org/licenses/LICENSE-2.0
11 //
12 // Unless required by applicable law or agreed to in writing, software
13 // distributed under the License is distributed on an "AS IS" BASIS,
14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 // See the License for the specific language governing permissions and
16 // limitations under the License.
17
7193de4c » Adam Langley
2009-04-28 Add version 0.2
18 #include <vector>
19
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
20 #include <sys/types.h>
21 #include <stdio.h>
22 #include <stdlib.h>
7193de4c » Adam Langley
2009-04-28 Add version 0.2
23 #include <fcntl.h>
eee67bda » Adam Langley
2009-04-28 Update for latest leptonica (1.61)
24 #include <string.h>
25 #include <unistd.h>
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
26
27 #include <allheaders.h>
28 #include <pix.h>
29
30 #include "jbig2enc.h"
31
b92225b9 » Adam Langley
2009-04-28 Add version 0.26
32 #if defined(WIN32)
33 #define WINBINARY O_BINARY
34 #else
35 #define WINBINARY 0
36 #endif
37
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
38 static void
39 usage(const char *argv0) {
7193de4c » Adam Langley
2009-04-28 Add version 0.2
40 fprintf(stderr, "Usage: %s [options] <input filenames...>\n", argv0);
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
41 fprintf(stderr, "Options:\n");
b44f1efc » Adam Langley
2009-04-28 Add version 0.21
42 fprintf(stderr, " -b <basename>: output file root name when using symbol coding\n");
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
43 fprintf(stderr, " -d --duplicate-line-removal: use TPGD in generic region coder\n");
44 fprintf(stderr, " -p --pdf: produce PDF ready data\n");
45 fprintf(stderr, " -s --symbol-mode: use text region, not generic coder\n");
46 fprintf(stderr, " -t <threshold>: set classification threshold for symbol coder (def: 0.85)\n");
7193de4c » Adam Langley
2009-04-28 Add version 0.2
47 fprintf(stderr, " -T <bw threshold>: set 1 bpp threshold (def: 188)\n");
48 fprintf(stderr, " -r --refine: use refinement (requires -s: lossless)\n");
49 fprintf(stderr, " -O <outfile>: dump thresholded image as PNG\n");
50 fprintf(stderr, " -2: upsample 2x before thresholding\n");
d685a514 » Adam Langley
2009-04-28 Add version 0.22
51 fprintf(stderr, " -4: upsample 4x before thresholding\n");
5fb19483 » Adam Langley
2009-04-28 Add version 0.23
52 fprintf(stderr, " -S: remove images from mixed input and save separately\n");
53 fprintf(stderr, " -j --jpeg-output: write images from mixed input as JPEG\n");
54 fprintf(stderr, " -v: be verbose\n");
55 }
56
57 static bool verbose = false;
58
59
60 static void
61 pixInfo(PIX *pix, const char *msg) {
62 if (msg != NULL) fprintf(stderr, "%s ", msg);
63 if (pix == NULL) {
64 fprintf(stderr, "NULL pointer!\n");
65 return;
66 }
67 fprintf(stderr, "%u x %u (%d bits) %udpi x %udpi, refcount = %u\n",
68 pix->w, pix->h, pix->d, pix->xres, pix->yres, pix->refcount);
d685a514 » Adam Langley
2009-04-28 Add version 0.22
69 }
70
b92225b9 » Adam Langley
2009-04-28 Add version 0.26
71 #ifdef _MSC_VER
72 // -----------------------------------------------------------------------------
73 // Windows, sadly, lacks asprintf
74 // -----------------------------------------------------------------------------
75 #include <stdarg.h>
76 int
77 asprintf(char **strp, const char *fmt, ...) {
78 va_list va;
79 va_start(va, fmt);
80
81 const int required = vsnprintf(NULL, 0, fmt, va);
82 char *const buffer = (char *) malloc(required + 1);
83 const int ret = vsnprintf(buffer, required + 1, fmt, va);
84 *strp = buffer;
85
86 va_end(va);
87
88 return ret;
89 }
90 #endif
91
d685a514 » Adam Langley
2009-04-28 Add version 0.22
92 // -----------------------------------------------------------------------------
93 // Morphological operations for segmenting an image into text regions
94 // -----------------------------------------------------------------------------
95 static const char *segment_mask_sequence = "r11";
5fb19483 » Adam Langley
2009-04-28 Add version 0.23
96 static const char *segment_seed_sequence = "r1143 + o4.4 + x4"; /* maybe o6.6 */
d685a514 » Adam Langley
2009-04-28 Add version 0.22
97 static const char *segment_dilation_sequence = "d3.3";
98
99 // -----------------------------------------------------------------------------
5fb19483 » Adam Langley
2009-04-28 Add version 0.23
100 // Takes two pix as input, generated from the same original image:
101 // 1. pixb - a binary thresholded image
102 // 2. piximg - a full color or grayscale image
103 // and segments them by finding the areas that contain color or grayscale
104 // graphics, removing those areas from the binary image, and doing the
105 // opposite for the full color/grayscale image. The upshot is that after
106 // this routine has been run, the binary image contains only text and the
107 // full color image contains only the graphics.
d685a514 » Adam Langley
2009-04-28 Add version 0.22
108 //
5fb19483 » Adam Langley
2009-04-28 Add version 0.23
109 // Both input images are modified by this procedure. If no text is found,
110 // pixb is set to NULL. If no graphics is found, piximg is set to NULL.
d685a514 » Adam Langley
2009-04-28 Add version 0.22
111 //
112 // Thanks to Dan Bloomberg for this
113 // -----------------------------------------------------------------------------
114
5fb19483 » Adam Langley
2009-04-28 Add version 0.23
115 static PIX*
116 segment_image(PIX *pixb, PIX *piximg) {
d685a514 » Adam Langley
2009-04-28 Add version 0.22
117 // Make seed and mask, and fill seed into mask
118 PIX *pixmask4 = pixMorphSequence(pixb, (char *) segment_mask_sequence, 0);
119 PIX *pixseed4 = pixMorphSequence(pixb, (char *) segment_seed_sequence, 0);
120 PIX *pixsf4 = pixSeedfillBinary(NULL, pixseed4, pixmask4, 8);
121 PIX *pixd4 = pixMorphSequence(pixsf4, (char *) segment_dilation_sequence, 0);
122
5fb19483 » Adam Langley
2009-04-28 Add version 0.23
123 // we want to force the binary mask to be the same size as the
124 // input color image, so we have to do it this way...
125 // is there a better way?
126 // PIX *pixd = pixExpandBinary(pixd4, 4);
127 PIX *pixd = pixCreate(piximg->w, piximg->h, 1);
128 pixCopyResolution(pixd, piximg);
129 if (verbose) pixInfo(pixd, "mask image: ");
b92225b9 » Adam Langley
2009-04-28 Add version 0.26
130 expandBinaryPower2Low(pixd->data, pixd->w, pixd->h, pixd->wpl,
131 pixd4->data, pixd4->w, pixd4->h, pixd4->wpl, 4);
d685a514 » Adam Langley
2009-04-28 Add version 0.22
132
133 pixDestroy(&pixd4);
134 pixDestroy(&pixsf4);
135 pixDestroy(&pixseed4);
136 pixDestroy(&pixmask4);
5fb19483 » Adam Langley
2009-04-28 Add version 0.23
137
138 pixSubtract(pixb, pixb, pixd);
139
140 // now see what we got from the segmentation
141 static l_int32 *tab = NULL;
142 if (tab == NULL) tab = makePixelSumTab8();
143
144 // if no image portion was found, set the image pointer to NULL and return
145 l_int32 pcount;
146 pixCountPixels(pixd, &pcount, tab);
147 if (verbose) fprintf(stderr, "pixel count of graphics image: %u\n", pcount);
148 if (pcount < 100) {
149 pixDestroy(&pixd);
150 return NULL;
151 }
152
153 // if no text portion found, set the binary pointer to NULL
154 pixCountPixels(pixb, &pcount, tab);
155 if (verbose) fprintf(stderr, "pixel count of binary image: %u\n", pcount);
156 if (pcount < 100) {
157 pixDestroy(&pixb);
158 }
159
160 PIX *piximg1;
161 if (piximg->d == 1 || piximg->d == 8 || piximg->d == 32) {
162 piximg1 = pixClone(piximg);
163 } else if (piximg->d > 8) {
164 piximg1 = pixConvertTo32(piximg);
165 } else {
b92225b9 » Adam Langley
2009-04-28 Add version 0.26
166 piximg1 = pixConvertTo8(piximg, FALSE);
5fb19483 » Adam Langley
2009-04-28 Add version 0.23
167 }
168
169 PIX *pixd1;
170 if (piximg1->d == 32) {
171 pixd1 = pixConvertTo32(pixd);
172 } else if (piximg1->d == 8) {
b92225b9 » Adam Langley
2009-04-28 Add version 0.26
173 pixd1 = pixConvertTo8(pixd, FALSE);
5fb19483 » Adam Langley
2009-04-28 Add version 0.23
174 } else {
175 pixd1 = pixClone(pixd);
176 }
177 pixDestroy(&pixd);
178
179 if (verbose) {
180 pixInfo(pixd1, "binary mask image:");
181 pixInfo(piximg1, "graphics image:");
182 }
183 pixRasteropFullImage(pixd1, piximg1, PIX_SRC | PIX_DST);
184
185 pixDestroy(&piximg1);
186 if (verbose) {
187 pixInfo(pixb, "segmented binary text image:");
188 pixInfo(pixd1, "segmented graphics image:");
189 }
190
191 return pixd1;
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
192 }
193
194 int
195 main(int argc, char **argv) {
196 bool duplicate_line_removal = false;
197 bool pdfmode = false;
198 float threshold = 0.85;
199 int bw_threshold = 188;
200 bool symbol_mode = false;
7193de4c » Adam Langley
2009-04-28 Add version 0.2
201 bool refine = false;
202 bool up2 = false, up4 = false;
203 const char *output_threshold = NULL;
b44f1efc » Adam Langley
2009-04-28 Add version 0.21
204 const char *basename = "output";
5fb19483 » Adam Langley
2009-04-28 Add version 0.23
205 l_int32 img_fmt = IFF_PNG;
206 const char *img_ext = "png";
d685a514 » Adam Langley
2009-04-28 Add version 0.22
207 bool segment = false;
7193de4c » Adam Langley
2009-04-28 Add version 0.2
208 int i;
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
209
7193de4c » Adam Langley
2009-04-28 Add version 0.2
210 for (i = 1; i < argc; ++i) {
b92225b9 » Adam Langley
2009-04-28 Add version 0.26
211 if (strcmp(argv[i], "-h") == 0 ||
212 strcmp(argv[i], "--help") == 0) {
213 usage(argv[0]);
214 return 0;
215 continue;
216 }
217
b44f1efc » Adam Langley
2009-04-28 Add version 0.21
218 if (strcmp(argv[i], "-b") == 0 ||
219 strcmp(argv[i], "--basename") == 0) {
220 basename = argv[i+1];
221 i++;
222 continue;
223 }
224
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
225 if (strcmp(argv[i], "-d") == 0 ||
226 strcmp(argv[i], "--duplicate-line-removal") == 0) {
227 duplicate_line_removal = true;
228 continue;
229 }
230
231 if (strcmp(argv[i], "-p") == 0 ||
232 strcmp(argv[i], "--pdf") == 0) {
233 pdfmode = true;
234 continue;
235 }
236
237 if (strcmp(argv[i], "-s") == 0 ||
238 strcmp(argv[i], "--symbol-mode") == 0) {
239 symbol_mode = true;
240 continue;
241 }
242
7193de4c » Adam Langley
2009-04-28 Add version 0.2
243 if (strcmp(argv[i], "-r") == 0 ||
244 strcmp(argv[i], "--refine") == 0) {
b92225b9 » Adam Langley
2009-04-28 Add version 0.26
245 fprintf(stderr, "Refinement broke in recent releases since it's "
246 "rarely used. If you need it you should bug "
247 "agl@imperialviolet.org to fix it\n");
248 return 1;
7193de4c » Adam Langley
2009-04-28 Add version 0.2
249 refine = true;
250 continue;
251 }
252
253 if (strcmp(argv[i], "-2") == 0) {
254 up2 = true;
255 continue;
256 }
257 if (strcmp(argv[i], "-4") == 0) {
258 up4 = true;
259 continue;
260 }
261
262 if (strcmp(argv[i], "-O") == 0) {
263 output_threshold = argv[i+1];
264 i++;
265 continue;
266 }
267
d685a514 » Adam Langley
2009-04-28 Add version 0.22
268 if (strcmp(argv[i], "-S") == 0) {
269 segment = true;
270 continue;
271 }
272
5fb19483 » Adam Langley
2009-04-28 Add version 0.23
273 if (strcmp(argv[i], "-j") == 0 ||
274 strcmp(argv[i], "--jpeg-output") == 0) {
275 img_ext = "jpg";
276 img_fmt = IFF_JFIF_JPEG;
d685a514 » Adam Langley
2009-04-28 Add version 0.22
277 continue;
278 }
279
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
280 if (strcmp(argv[i], "-t") == 0) {
281 char *endptr;
282 threshold = strtod(argv[i+1], &endptr);
283 if (*endptr) {
284 fprintf(stderr, "Cannot parse float value: %s\n", argv[i+1]);
285 usage(argv[0]);
286 return 1;
287 }
288
eee67bda » Adam Langley
2009-04-28 Update for latest leptonica (1.61)
289 if (threshold > 0.9 || threshold < 0.4) {
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
290 fprintf(stderr, "Invalid value for threshold\n");
d685a514 » Adam Langley
2009-04-28 Add version 0.22
291 fprintf(stderr, "(must be between 0.4 and 0.9)\n");
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
292 return 10;
293 }
294 i++;
295 continue;
296 }
297
298 if (strcmp(argv[i], "-T") == 0) {
299 char *endptr;
300 bw_threshold = strtol(argv[i+1], &endptr, 10);
301 if (*endptr) {
302 fprintf(stderr, "Cannot parse int value: %s\n", argv[i+1]);
303 usage(argv[0]);
304 return 1;
305 }
306 if (bw_threshold < 0 || bw_threshold > 255) {
307 fprintf(stderr, "Invalid bw threshold: (0..255)\n");
308 return 11;
309 }
310 i++;
311 continue;
312 }
313
5fb19483 » Adam Langley
2009-04-28 Add version 0.23
314 if (strcmp(argv[i], "-v") == 0) {
315 verbose = true;
316 continue;
317 }
318
7193de4c » Adam Langley
2009-04-28 Add version 0.2
319 break;
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
320 }
321
7193de4c » Adam Langley
2009-04-28 Add version 0.2
322 if (i == argc) {
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
323 fprintf(stderr, "No filename given\n\n");
324 usage(argv[0]);
325 return 4;
326 }
327
7193de4c » Adam Langley
2009-04-28 Add version 0.2
328 if (refine && !symbol_mode) {
329 fprintf(stderr, "Refinement makes not sense unless in symbol mode!\n");
330 fprintf(stderr, "(if you have -r, you must have -s)\n");
331 return 5;
332 }
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
333
7193de4c » Adam Langley
2009-04-28 Add version 0.2
334 if (up2 && up4) {
335 fprintf(stderr, "Can't have both -2 and -4!\n");
336 return 6;
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
337 }
338
7193de4c » Adam Langley
2009-04-28 Add version 0.2
339 struct jbig2ctx *ctx = jbig2_init(threshold, 0.5, 0, 0, !pdfmode, refine ? 10 : -1);
5fb19483 » Adam Langley
2009-04-28 Add version 0.23
340 int pageno = -1;
7193de4c » Adam Langley
2009-04-28 Add version 0.2
341
b92225b9 » Adam Langley
2009-04-28 Add version 0.26
342 int numsubimages=0, subimage=0, num_pages = 0;
7193de4c » Adam Langley
2009-04-28 Add version 0.2
343 while (i < argc) {
b92225b9 » Adam Langley
2009-04-28 Add version 0.26
344 if (subimage==numsubimages) {
345 subimage = numsubimages = 0;
346 FILE *fp;
347 if ((fp=fopen(argv[i], "r"))==NULL) {
348 fprintf(stderr, "Unable to open \"%s\"", argv[i]);
349 return 1;
350 }
bf54e121 » dkelly
2009-12-03 Updated Leptonica references to 1.63
351 l_int32 filetype;
888b22f2 » mistydemeo
2011-07-06 Upgrade to Leptonica 1.68 & fix build issues
352 findFileFormatStream(fp, &filetype);
b92225b9 » Adam Langley
2009-04-28 Add version 0.26
353 if (filetype==IFF_TIFF && tiffGetCount(fp, &numsubimages)) {
354 return 1;
355 }
356 fclose(fp);
357 }
358
359 PIX *source;
360 if (numsubimages<=1) {
361 source = pixRead(argv[i]);
362 } else {
363 source = pixReadTiff(argv[i], subimage++);
364 }
365
7193de4c » Adam Langley
2009-04-28 Add version 0.2
366 if (!source) return 3;
5fb19483 » Adam Langley
2009-04-28 Add version 0.23
367 if (verbose)
368 pixInfo(source, "source image:");
7193de4c » Adam Langley
2009-04-28 Add version 0.2
369
370 PIX *pixl, *gray, *pixt;
5fb19483 » Adam Langley
2009-04-28 Add version 0.23
371 if ((pixl = pixRemoveColormap(source, REMOVE_CMAP_BASED_ON_SRC)) == NULL) {
7193de4c » Adam Langley
2009-04-28 Add version 0.2
372 fprintf(stderr, "Failed to remove colormap from %s\n", argv[i]);
373 return 1;
374 }
375 pixDestroy(&source);
5fb19483 » Adam Langley
2009-04-28 Add version 0.23
376 pageno++;
7193de4c » Adam Langley
2009-04-28 Add version 0.2
377
378 if (pixl->d > 1) {
379 if (pixl->d > 8) {
380 gray = pixConvertRGBToGrayFast(pixl);
381 if (!gray) return 1;
382 } else {
383 gray = pixClone(pixl);
384 }
385 if (up2) {
386 pixt = pixScaleGray2xLIThresh(gray, bw_threshold);
387 } else if (up4) {
388 pixt = pixScaleGray4xLIThresh(gray, bw_threshold);
389 } else {
390 pixt = pixThresholdToBinary(gray, bw_threshold);
391 }
392 pixDestroy(&gray);
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
393 } else {
7193de4c » Adam Langley
2009-04-28 Add version 0.2
394 pixt = pixClone(pixl);
395 }
5fb19483 » Adam Langley
2009-04-28 Add version 0.23
396 if (verbose)
397 pixInfo(pixt, "thresholded image:");
7193de4c » Adam Langley
2009-04-28 Add version 0.2
398
399 if (output_threshold) {
400 pixWrite(output_threshold, pixt, IFF_PNG);
401 }
402
5fb19483 » Adam Langley
2009-04-28 Add version 0.23
403 if (segment && pixl->d > 1) {
404 PIX *graphics = segment_image(pixt, pixl);
405 if (graphics) {
406 if (verbose)
407 pixInfo(graphics, "graphics image:");
408 char *filename;
409 asprintf(&filename, "%s.%04d.%s", basename, pageno, img_ext);
410 pixWrite(filename, graphics, img_fmt);
b92225b9 » Adam Langley
2009-04-28 Add version 0.26
411 free(filename);
5fb19483 » Adam Langley
2009-04-28 Add version 0.23
412 } else if (verbose) {
413 fprintf(stderr, "%s: no graphics found in input image\n", argv[i]);
414 }
415 if (! pixt) {
416 fprintf(stderr, "%s: no text portion found in input image\n", argv[i]);
417 i++;
418 continue;
419 }
d685a514 » Adam Langley
2009-04-28 Add version 0.22
420 }
421
5fb19483 » Adam Langley
2009-04-28 Add version 0.23
422 pixDestroy(&pixl);
423
7193de4c » Adam Langley
2009-04-28 Add version 0.2
424 if (!symbol_mode) {
425 int length;
426 uint8_t *ret;
427 ret = jbig2_encode_generic(pixt, !pdfmode, 0, 0, duplicate_line_removal,
428 &length);
429 write(1, ret, length);
430 return 0;
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
431 }
7193de4c » Adam Langley
2009-04-28 Add version 0.2
432
433 jbig2_add_page(ctx, pixt);
434 pixDestroy(&pixt);
b92225b9 » Adam Langley
2009-04-28 Add version 0.26
435 num_pages++;
436 if (subimage==numsubimages) {
437 i++;
438 }
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
439 }
7193de4c » Adam Langley
2009-04-28 Add version 0.2
440
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
441 uint8_t *ret;
442 int length;
7193de4c » Adam Langley
2009-04-28 Add version 0.2
443 ret = jbig2_pages_complete(ctx, &length);
444 if (pdfmode) {
b44f1efc » Adam Langley
2009-04-28 Add version 0.21
445 char *filename;
446 asprintf(&filename, "%s.sym", basename);
b92225b9 » Adam Langley
2009-04-28 Add version 0.26
447 const int fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT | WINBINARY, 0600);
448 free(filename);
7193de4c » Adam Langley
2009-04-28 Add version 0.2
449 if (fd < 0) abort();
450 write(fd, ret, length);
451 close(fd);
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
452 } else {
7193de4c » Adam Langley
2009-04-28 Add version 0.2
453 write(1, ret, length);
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
454 }
7193de4c » Adam Langley
2009-04-28 Add version 0.2
455 free(ret);
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
456
7193de4c » Adam Langley
2009-04-28 Add version 0.2
457 for (int i = 0; i < num_pages; ++i) {
458 ret = jbig2_produce_page(ctx, i, -1, -1, &length);
459 if (pdfmode) {
460 char *filename;
b44f1efc » Adam Langley
2009-04-28 Add version 0.21
461 asprintf(&filename, "%s.%04d", basename, i);
b92225b9 » Adam Langley
2009-04-28 Add version 0.26
462 const int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | WINBINARY, 0600);
463 free(filename);
7193de4c » Adam Langley
2009-04-28 Add version 0.2
464 if (fd < 0) abort();
465 write(fd, ret, length);
466 close(fd);
467 } else {
468 write(1, ret, length);
469 }
470 free(ret);
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
471 }
472
7193de4c » Adam Langley
2009-04-28 Add version 0.2
473 jbig2_destroy(ctx);
3f1a6ee5 » Adam Langley
2009-04-28 Add version 0.1
474 }
5fb19483 » Adam Langley
2009-04-28 Add version 0.23
475
Something went wrong with that request. Please try again.