1 /*
2 * Copyright (C) 2014 haru <uobikiemukot at gmail dot com>
3 * Copyright (C) 2014 Hayaki Saito <user@zuse.jp>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "config.h"
20 #include "yaft.h"
21 #include "util.h"
22 #include "pseudo.h"
23 #include "terminal.h"
24 #include "function.h"
25 #include "dcs.h"
26 #include "parse.h"
27 #include "gifsave89.h"
28
29 #include <stdio.h>
30
31 #if HAVE_STDLIB_H
32 # include <stdlib.h>
33 #endif
34 #if HAVE_STRING_H
35 # include <string.h>
36 #endif
37 #if HAVE_UNISTD_H
38 # include <unistd.h>
39 #endif
40 #if HAVE_GETOPT_H
41 # include <getopt.h>
42 #endif
43 #if HAVE_ERRNO_H
44 # include <errno.h>
45 #endif
46 #if HAVE_FCNTL_H
47 # include <fcntl.h>
48 #endif
49
50
51 #if !defined(HAVE_MEMCPY)
52 # define memcpy(d, s, n) (bcopy ((s), (d), (n)))
53 #endif
54
55 #if !defined(O_BINARY) && defined(_O_BINARY)
56 # define O_BINARY _O_BINARY
57 #endif /* !defined(O_BINARY) && !defined(_O_BINARY) */
58
59 struct settings_t {
60 int width;
61 int height;
62 int show_version;
63 int show_help;
64 int last_frame_delay;
65 int foreground_color;
66 int background_color;
67 int cursor_color;
68 int tabwidth;
69 int cjkwidth;
70 int repeat;
71 char *input;
72 char *output;
73 };
74
75 enum cmap_bitfield {
76 RED_SHIFT = 5,
77 GREEN_SHIFT = 2,
78 BLUE_SHIFT = 0,
79 RED_MASK = 3,
80 GREEN_MASK = 3,
81 BLUE_MASK = 2
82 };
83
pb_init(struct pseudobuffer * pb,int width,int height)84 static void pb_init(struct pseudobuffer *pb, int width, int height)
85 {
86 pb->width = width;
87 pb->height = height;
88 pb->bytes_per_pixel = BYTES_PER_PIXEL;
89 pb->line_length = pb->width * pb->bytes_per_pixel;
90 pb->buf = ecalloc(pb->width * pb->height, pb->bytes_per_pixel);
91 }
92
pb_die(struct pseudobuffer * pb)93 static void pb_die(struct pseudobuffer *pb)
94 {
95 free(pb->buf);
96 }
97
set_colormap(int colormap[COLORS * BYTES_PER_PIXEL+1])98 static void set_colormap(int colormap[COLORS * BYTES_PER_PIXEL + 1])
99 {
100 int i, ci, r, g, b;
101 uint8_t index;
102
103 /* colormap: terminal 256color
104 for (i = 0; i < COLORS; i++) {
105 ci = i * BYTES_PER_PIXEL;
106
107 r = (color_list[i] >> 16) & bit_mask[8];
108 g = (color_list[i] >> 8) & bit_mask[8];
109 b = (color_list[i] >> 0) & bit_mask[8];
110
111 colormap[ci + 0] = r;
112 colormap[ci + 1] = g;
113 colormap[ci + 2] = b;
114 }
115 */
116
117 /* colormap: red/green: 3bit blue: 2bit
118 */
119 for (i = 0; i < COLORS; i++) {
120 index = (uint8_t) i;
121 ci = i * BYTES_PER_PIXEL;
122
123 r = (index >> RED_SHIFT) & bit_mask[RED_MASK];
124 g = (index >> GREEN_SHIFT) & bit_mask[GREEN_MASK];
125 b = (index >> BLUE_SHIFT) & bit_mask[BLUE_MASK];
126
127 colormap[ci + 0] = r * bit_mask[BITS_PER_BYTE] / bit_mask[RED_MASK];
128 colormap[ci + 1] = g * bit_mask[BITS_PER_BYTE] / bit_mask[GREEN_MASK];
129 colormap[ci + 2] = b * bit_mask[BITS_PER_BYTE] / bit_mask[BLUE_MASK];
130 }
131 colormap[COLORS * BYTES_PER_PIXEL] = -1;
132 }
133
pixel2index(uint32_t pixel)134 static uint32_t pixel2index(uint32_t pixel)
135 {
136 /* pixel is always 24bpp */
137 uint32_t r, g, b;
138
139 /* split r, g, b bits */
140 r = (pixel >> 16) & bit_mask[8];
141 g = (pixel >> 8) & bit_mask[8];
142 b = (pixel >> 0) & bit_mask[8];
143
144 /* colormap: terminal 256color
145 if (r == g && r == b) { // 24 gray scale
146 r = 24 * r / COLORS;
147 return 232 + r;
148 } // 6x6x6 color cube
149
150 r = 6 * r / COLORS;
151 g = 6 * g / COLORS;
152 b = 6 * b / COLORS;
153
154 return 16 + (r * 36) + (g * 6) + b;
155 */
156
157 /* colormap: red/green: 3bit blue: 2bit
158 */
159 // get MSB ..._MASK bits
160 r = (r >> (8 - RED_MASK)) & bit_mask[RED_MASK];
161 g = (g >> (8 - GREEN_MASK)) & bit_mask[GREEN_MASK];
162 b = (b >> (8 - BLUE_MASK)) & bit_mask[BLUE_MASK];
163
164 return (r << RED_SHIFT) | (g << GREEN_SHIFT) | (b << BLUE_SHIFT);
165 }
166
apply_colormap(struct pseudobuffer * pb,unsigned char * img)167 static void apply_colormap(struct pseudobuffer *pb, unsigned char *img)
168 {
169 int w, h;
170 uint32_t pixel = 0;
171
172 for (h = 0; h < pb->height; h++) {
173 for (w = 0; w < pb->width; w++) {
174 memcpy(&pixel, pb->buf + h * pb->line_length
175 + w * pb->bytes_per_pixel, pb->bytes_per_pixel);
176 *(img + h * pb->width + w) = pixel2index(pixel) & bit_mask[BITS_PER_BYTE];
177 }
178 }
179 }
180
write_gif(unsigned char * gifimage,int size,FILE * f)181 static size_t write_gif(unsigned char *gifimage, int size, FILE *f)
182 {
183 size_t wsize = 0;
184
185 wsize = fwrite(gifimage, sizeof(unsigned char), size, f);
186 return wsize;
187 }
188
show_version()189 static void show_version()
190 {
191 printf(PACKAGE_NAME " " PACKAGE_VERSION "\n"
192 "Copyright (C) 2014 haru <uobikiemukot at gmail dot com>\n"
193 "Copyright (C) 2012-2014 Hayaki Saito <user@zuse.jp>.\n"
194 "\n"
195 "This program is free software; you can redistribute it and/or modify\n"
196 "it under the terms of the GNU General Public License as published by\n"
197 "the Free Software Foundation; either version 3 of the License, or\n"
198 "(at your option) any later version.\n"
199 "\n"
200 "This program is distributed in the hope that it will be useful,\n"
201 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
202 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
203 "GNU General Public License for more details.\n"
204 "\n"
205 "You should have received a copy of the GNU General Public License\n"
206 "along with this program. If not, see http://www.gnu.org/licenses/.\n"
207 "%s\n", copyright
208 );
209 }
210
show_help()211 static void show_help()
212 {
213 fprintf(stderr,
214 "Usage: seq2gif [Options] < ttyrecord > record.gif\n"
215 " seq2gif [Options] -i ttyrecord -o record.gif\n"
216 "\n"
217 "Options:\n"
218 "-w WIDTH, --width=WIDTH specify terminal width in cell size.\n"
219 " (default: 80)\n"
220 "-h HEIGHT, --height=HEIGHT specify terminal height in cell size.\n"
221 " (default: 24)\n"
222 "-l DELAY, --last-frame-delay=DELAY specify delay in msec which is added\n"
223 " to the last frame. (default: 300)\n"
224 "-f COLORNO --foreground-color=COLORNO specify foreground color palette.\n"
225 " number.\n"
226 "-b COLORNO --background-color=COLORNO specify background color palette\n"
227 " number.\n"
228 "-c COLORNO --cursor-color=COLORNO specify cursor color palette\n"
229 " number.\n"
230 "-t TABSTOP --tabstop=TABSTOP specify hardware tabstop(default: 8)\n"
231 "-j --cjkwidth treat East Asian Ambiguous width\n"
232 " characters (UAX#11) as wide.\n"
233 "-r COUNT --repeat=COUNT specify animation repeat count. loop\n"
234 " infinitely if 0 is given. (default: 0)\n"
235 "-i FILE --input=FILE specify input file name. use STDIN\n"
236 " if '-' is given. (default: '-')\n"
237 "-o FILE --output=FILE specify output file name. use STDOUT\n"
238 " if '-' is given. (default: '-')\n"
239 "-V, --version show version and license information.\n"
240 "-H, --help show this help.\n"
241 );
242 }
243
parse_args(int argc,char * argv[],struct settings_t * psettings)244 static int parse_args(int argc, char *argv[], struct settings_t *psettings)
245 {
246 int long_opt;
247 int n;
248 char const *optstring = "w:h:HVl:f:b:c:t:jr:i:o:";
249 #if HAVE_GETOPT_LONG
250 int option_index;
251 #endif /* HAVE_GETOPT_LONG */
252
253 #if HAVE_GETOPT_LONG
254 struct option long_options[] = {
255 {"width", required_argument, &long_opt, 'w'},
256 {"height", required_argument, &long_opt, 'h'},
257 {"last-frame-delay", required_argument, &long_opt, 'l'},
258 {"foreground-color", required_argument, &long_opt, 'f'},
259 {"background-color", required_argument, &long_opt, 'b'},
260 {"cursor-color", required_argument, &long_opt, 'c'},
261 {"tabstop", required_argument, &long_opt, 't'},
262 {"cjkwidth", no_argument, &long_opt, 'j'},
263 {"repeat", required_argument, &long_opt, 'r'},
264 {"input", required_argument, &long_opt, 'i'},
265 {"output", required_argument, &long_opt, 'o'},
266 {"help", no_argument, &long_opt, 'H'},
267 {"version", no_argument, &long_opt, 'V'},
268 {0, 0, 0, 0}
269 };
270 #endif /* HAVE_GETOPT_LONG */
271
272 for (;;) {
273
274 #if HAVE_GETOPT_LONG
275 n = getopt_long(argc, argv, optstring,
276 long_options, &option_index);
277 #else
278 n = getopt(argc, argv, optstring);
279 #endif /* HAVE_GETOPT_LONG */
280 if (n == -1) {
281 break;
282 }
283 if (n == 0) {
284 n = long_opt;
285 }
286 switch(n) {
287 case 'w':
288 psettings->width = atoi(optarg);
289 if (psettings->width < 1) {
290 goto argerr;
291 }
292 break;
293 case 'h':
294 psettings->height = atoi(optarg);
295 if (psettings->height < 1) {
296 goto argerr;
297 }
298 break;
299 case 'l':
300 psettings->last_frame_delay = atoi(optarg);
301 if (psettings->last_frame_delay < 0) {
302 goto argerr;
303 }
304 break;
305 case 'f':
306 psettings->foreground_color = atoi(optarg);
307 if (psettings->foreground_color < 0) {
308 goto argerr;
309 }
310 if (psettings->foreground_color > 255) {
311 goto argerr;
312 }
313 break;
314 case 'b':
315 psettings->background_color = atoi(optarg);
316 if (psettings->background_color < 0) {
317 goto argerr;
318 }
319 if (psettings->background_color > 255) {
320 goto argerr;
321 }
322 break;
323 case 'c':
324 psettings->cursor_color = atoi(optarg);
325 if (psettings->cursor_color < 0) {
326 goto argerr;
327 }
328 if (psettings->cursor_color > 255) {
329 goto argerr;
330 }
331 break;
332 case 't':
333 psettings->tabwidth = atoi(optarg);
334 if (psettings->tabwidth < 0) {
335 goto argerr;
336 }
337 if (psettings->tabwidth > 255) {
338 goto argerr;
339 }
340 break;
341 case 'j':
342 psettings->cjkwidth = 1;
343 break;
344 case 'r':
345 psettings->repeat = atoi(optarg);
346 if (psettings->repeat < 0) {
347 goto argerr;
348 }
349 if (psettings->repeat > 0xffff) {
350 goto argerr;
351 }
352 break;
353 case 'i':
354 psettings->input = strdup(optarg);
355 break;
356 case 'o':
357 psettings->output = strdup(optarg);
358 break;
359 case 'H':
360 psettings->show_help = 1;
361 break;
362 case 'V':
363 psettings->show_version = 1;
364 break;
365 default:
366 goto argerr;
367 }
368 }
369 return 0;
370
371 argerr:
372 show_help();
373 return 1;
374 }
375
open_input_file(char const * filename)376 static FILE * open_input_file(char const *filename)
377 {
378 FILE *f;
379
380 if (filename == NULL || strcmp(filename, "-") == 0) {
381 /* for windows */
382 #if defined(O_BINARY)
383 # if HAVE__SETMODE
384 _setmode(fileno(stdin), O_BINARY);
385 # elif HAVE_SETMODE
386 setmode(fileno(stdin), O_BINARY);
387 # endif /* HAVE_SETMODE */
388 #endif /* defined(O_BINARY) */
389 return stdin;
390 }
391 f = fopen(filename, "rb");
392 if (!f) {
393 #if _ERRNO_H
394 fprintf(stderr, "fopen('%s') failed.\n" "reason: %s.\n",
395 filename, strerror(errno));
396 #endif /* HAVE_ERRNO_H */
397 return NULL;
398 }
399 return f;
400 }
401
open_output_file(char const * filename)402 static FILE * open_output_file(char const *filename)
403 {
404 FILE *f;
405
406 if (filename == NULL || strcmp(filename, "-") == 0) {
407 /* for windows */
408 #if defined(O_BINARY)
409 # if HAVE__SETMODE
410 _setmode(fileno(stdout), O_BINARY);
411 # elif HAVE_SETMODE
412 setmode(fileno(stdout), O_BINARY);
413 # endif /* HAVE_SETMODE */
414 #endif /* defined(O_BINARY) */
415 return stdout;
416 }
417 f = fopen(filename, "wb");
418 if (!f) {
419 #if _ERRNO_H
420 fprintf(stderr, "fopen('%s') failed.\n" "reason: %s.\n",
421 filename, strerror(errno));
422 #endif /* HAVE_ERRNO_H */
423 return NULL;
424 }
425 return f;
426 }
427
readtime(FILE * f,uint8_t * obuf)428 static int32_t readtime(FILE *f, uint8_t *obuf)
429 {
430 int nread;
431 int32_t tv_sec;
432 int32_t tv_usec;
433
434 nread = fread(obuf, 1, sizeof(tv_sec), f);
435 if (nread != sizeof(tv_sec)) {
436 return -1;
437 }
438 tv_sec = obuf[0] | obuf[1] << 8
439 | obuf[2] << 16 | obuf[3] << 24;
440 nread = fread(obuf, 1, sizeof(tv_usec), f);
441 if (nread != sizeof(tv_usec)) {
442 return -1;
443 }
444 tv_usec = obuf[0] | obuf[1] << 8
445 | obuf[2] << 16 | obuf[3] << 24;
446
447 return tv_sec * 1000000 + tv_usec;
448 }
449
readlen(FILE * f,uint8_t * obuf)450 static int32_t readlen(FILE *f, uint8_t *obuf)
451 {
452 int nread;
453 uint32_t len;
454
455 nread = fread(obuf, 1, sizeof(len), f);
456 if (nread != sizeof(len)) {
457 return -1;
458 }
459 len = obuf[0]
460 | obuf[1] << 8
461 | obuf[2] << 16
462 | obuf[3] << 24;
463
464 return len;
465 }
466
main(int argc,char * argv[])467 int main(int argc, char *argv[])
468 {
469 uint8_t *obuf;
470 ssize_t nread;
471 struct terminal term;
472 struct pseudobuffer pb;
473 uint32_t prev = 0;
474 uint32_t now = 0;
475 ssize_t len = 0;
476 size_t maxlen = 0;
477 int delay = 0;
478 int dirty = 0;
479 FILE *in_file = NULL;
480 FILE *out_file = NULL;
481 int nret = EXIT_SUCCESS;
482
483 void *gsdata;
484 unsigned char *gifimage = NULL;
485 int gifsize, colormap[COLORS * BYTES_PER_PIXEL + 1];
486 unsigned char *img;
487
488 struct settings_t settings = {
489 80, /* width */
490 24, /* height */
491 0, /* show_version */
492 0, /* show_help */
493 300, /* last_frame_delay */
494 7, /* foreground_color */
495 0, /* background_color */
496 2, /* cursor_color */
497 8, /* tabwidth */
498 0, /* cjkwidth */
499 0, /* repeat */
500 NULL, /* input */
501 NULL, /* output */
502 };
503
504 if (parse_args(argc, argv, &settings) != 0) {
505 exit(1);
506 }
507
508 if (settings.show_help) {
509 show_help();
510 exit(0);
511 }
512
513 if (settings.show_version) {
514 show_version();
515 exit(0);
516 }
517
518 /* init */
519 pb_init(&pb, settings.width * CELL_WIDTH, settings.height * CELL_HEIGHT);
520 term_init(&term, pb.width, pb.height,
521 settings.foreground_color,
522 settings.background_color,
523 settings.cursor_color,
524 settings.tabwidth,
525 settings.cjkwidth);
526
527 /* init gif */
528 img = (unsigned char *) ecalloc(pb.width * pb.height, 1);
529 set_colormap(colormap);
530 if (!(gsdata = newgif((void **) &gifimage, pb.width, pb.height, colormap, 0))) {
531 free(img);
532 term_die(&term);
533 pb_die(&pb);
534 return EXIT_FAILURE;
535 }
536
537 animategif(gsdata, /* repetitions */ settings.repeat, 0,
538 /* transparent background */ -1, /* disposal */ 2);
539
540 in_file = open_input_file(settings.input);
541 out_file = open_output_file(settings.output);
542
543 maxlen = 2048;
544 obuf = malloc(maxlen);
545 prev = now = readtime(in_file, obuf);
546
547 /* main loop */
548 for(;;) {
549 len = readlen(in_file, obuf);
550 if (len <= 0) {
551 nret = EXIT_FAILURE;
552 break;
553 }
554 if (len > maxlen) {
555 obuf = realloc(obuf, len);
556 maxlen = len;
557 }
558 nread = fread(obuf, 1, len, in_file);
559 if (nread != len) {
560 nret = EXIT_FAILURE;
561 break;
562 }
563 parse(&term, obuf, nread, &dirty);
564 now = readtime(in_file, obuf);
565 if (now == -1) {
566 break;
567 }
568 if (term.esc.state != STATE_DCS || dirty) {
569 refresh(&pb, &term);
570 delay = now - prev;
571
572 /* take screenshot */
573 apply_colormap(&pb, img);
574 controlgif(gsdata, -1, delay / 10000, 0, 0);
575 prev = now;
576 putgif(gsdata, img);
577 }
578 dirty = 0;
579 }
580 if (settings.last_frame_delay > 0) {
581 controlgif(gsdata, -1, settings.last_frame_delay / 10, 0, 0);
582 putgif(gsdata, img);
583 }
584
585 /* output gif */
586 gifsize = endgif(gsdata);
587 if (gifsize > 0) {
588 write_gif(gifimage, gifsize, out_file);
589 free(gifimage);
590 }
591
592 /* normal exit */
593 free(img);
594 if (settings.input) {
595 if (fileno(in_file) != STDIN_FILENO) {
596 fclose(in_file);
597 }
598 free(settings.input);
599 }
600 if (settings.output) {
601 if (fileno(out_file) != STDOUT_FILENO) {
602 fclose(out_file);
603 }
604 free(settings.output);
605 }
606 term_die(&term);
607 pb_die(&pb);
608 free(obuf);
609
610 return nret;
611 }
612
613 /* emacs, -*- Mode: C; tab-width: 4; indent-tabs-mode: nil -*- */
614 /* vim: set expandtab ts=4 : */
615 /* EOF */
616