pax_global_header00006660000000000000000000000064130103642370014511gustar00rootroot0000000000000052 comment=f2607e60c97e026732ea3641d2f5e3235e64ec7e ttygif-1.4.0/000077500000000000000000000000001301036423700130215ustar00rootroot00000000000000ttygif-1.4.0/.gitignore000066400000000000000000000000411301036423700150040ustar00rootroot00000000000000*.o *.swp *.gif ttyrecord ttygif ttygif-1.4.0/LICENSE000066400000000000000000000020651301036423700140310ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 Ilia Choly Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ttygif-1.4.0/Makefile000066400000000000000000000007121301036423700144610ustar00rootroot00000000000000CC = gcc VERSION = '"1.4.0"' CFLAGS = -O2 -Wall -DVERSION=$(VERSION) PREFIX ?= /usr/local UNAME := $(shell uname) ifeq ($(UNAME), Darwin) CFLAGS += -DOS_DARWIN endif ifeq ($(UNAME), Linux) CFLAGS += -DOS_LINUX endif all: ttygif ttygif: ttygif.o io.o string_builder.o utils.o $(CC) $(CFLAGS) -o ttygif ttygif.o io.o string_builder.o utils.o install: ttygif mkdir -p $(PREFIX)/bin cp ttygif $(PREFIX)/bin/ttygif clean: rm -f *.o ttygif ttyrecord *~ ttygif-1.4.0/README.md000066400000000000000000000035711301036423700143060ustar00rootroot00000000000000![](ttygif.png) > ttygif converts a ttyrec file into gif files. > It's a stripped down version of ttyplay that captures every frame. ## Demo ![gif](http://i.imgur.com/nvEHTgn.gif) ## Setup ### Debian/Ubuntu ``` sh $ sudo apt-get install imagemagick ttyrec gcc x11-apps $ git clone https://github.com/icholy/ttygif.git $ cd ttygif $ make $ sudo make install ``` ### Fedora/Redhat ``` sh $ sudo dnf install ImageMagick gcc $ # install ttyrec from source patched ~> https://github.com/mattwilmott/ttyrec $ git clone https://github.com/icholy/ttygif.git $ cd ttygif $ make $ sudo make install ``` ### Arch Linux ``` sh $ git clone https://aur.archlinux.org/ttyrec $ cd ttyrec $ makepkg -i $ cd .. $ git clone https://aur.archlinux.org/ttygif $ cd ttygif $ makepkg -is ``` ### OSX ``` sh $ brew install https://raw.githubusercontent.com/icholy/ttygif/master/ttygif.rb ``` ## Usage: **1. Create ttyrec recording** ``` sh $ ttyrec myrecording ``` * Hit CTRL-D or type `exit` when done recording. **2. Convert to gif** ``` sh $ ttygif myrecording ``` On OSX optionally you can set a -f flag which will bypass cropping which is needed for terminal apps which aren't full screen. Both standard Terminal and iTerm apps are supported. ``` sh $ ttygif myrecording -f ``` ## Additional Notes If you're getting `Error: WINDOWID environment variable was empty.`, then you need to manually set `WINDOWID`. ``` sh export WINDOWID=23068679 ``` If you're on Ubuntu, you can use `xdotool` to find the WINDOWID ``` sh $ sudo apt-get install xdotool $ export WINDOWID=$(xdotool getwindowfocus) ``` ## Debugging If you're having issues, then export the `TTYGIF_DEBUG` env variable. ``` sh export TTYGIF_DEBUG=1 ``` This will print out all the commands it's trying to run. ## Credits The idea and approach was adapted from [tty2gif](https://bitbucket.org/antocuni/tty2gif) ![](http://i.imgur.com/9et8daN.jpg) ttygif-1.4.0/io.c000066400000000000000000000101411301036423700135710ustar00rootroot00000000000000/* * Copyright (c) 2000 Satoru Takabayashi * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include "ttyrec.h" #define SWAP_ENDIAN(val) ((unsigned int) ( \ (((unsigned int) (val) & (unsigned int) 0x000000ffU) << 24) | \ (((unsigned int) (val) & (unsigned int) 0x0000ff00U) << 8) | \ (((unsigned int) (val) & (unsigned int) 0x00ff0000U) >> 8) | \ (((unsigned int) (val) & (unsigned int) 0xff000000U) >> 24))) static int is_little_endian () { static int retval = -1; if (retval == -1) { int n = 1; char *p = (char *)&n; char x[] = {1, 0, 0, 0}; assert(sizeof(int) == 4); if (memcmp(p, x, 4) == 0) { retval = 1; } else { retval = 0; } } return retval; } static int convert_to_little_endian (int x) { if (is_little_endian()) { return x; } else { return SWAP_ENDIAN(x); } } int read_header (FILE *fp, Header *h) { int buf[3]; if (fread(buf, sizeof(int), 3, fp) == 0) { return 0; } h->tv.tv_sec = convert_to_little_endian(buf[0]); h->tv.tv_usec = convert_to_little_endian(buf[1]); h->len = convert_to_little_endian(buf[2]); return 1; } int write_header (FILE *fp, Header *h) { int buf[3]; buf[0] = convert_to_little_endian(h->tv.tv_sec); buf[1] = convert_to_little_endian(h->tv.tv_usec); buf[2] = convert_to_little_endian(h->len); if (fwrite(buf, sizeof(int), 3, fp) == 0) { return 0; } return 1; } static char *progname = ""; void set_progname (const char *name) { progname = strdup(name); } FILE * efopen (const char *path, const char *mode) { FILE *fp = fopen(path, mode); if (fp == NULL) { fprintf(stderr, "%s: %s: %s\n", progname, path, strerror(errno)); exit(EXIT_FAILURE); } return fp; } int edup (int oldfd) { int fd = dup(oldfd); if (fd == -1) { fprintf(stderr, "%s: dup failed: %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } return fd; } int edup2 (int oldfd, int newfd) { int fd = dup2(oldfd, newfd); if (fd == -1) { fprintf(stderr, "%s: dup2 failed: %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } return fd; } FILE * efdopen (int fd, const char *mode) { FILE *fp = fdopen(fd, mode); if (fp == NULL) { fprintf(stderr, "%s: fdopen failed: %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } return fp; } ttygif-1.4.0/io.h000066400000000000000000000006371301036423700136070ustar00rootroot00000000000000#ifndef __TTYREC_IO_H__ #define __TTYREC_IO_H__ #include "ttyrec.h" int read_header (FILE *fp, Header *h); int write_header (FILE *fp, Header *h); FILE* efopen (const char *path, const char *mode); void set_progname (const char *name); int edup (int oldfd); int edup2 (int oldfd, int newfd); FILE* efdopen (int fd, const char *mode); #endif ttygif-1.4.0/string_builder.c000066400000000000000000000024701301036423700162040ustar00rootroot00000000000000#include #include #include #include "string_builder.h" void * StringBuilder_malloc(size_t size) { void *p = malloc(size); if (p == NULL) { fprintf(stderr, "StringBuilder: OUT OF MEMORY\n"); exit(EXIT_FAILURE); } return p; } StringBuilder * StringBuilder_new(void) { StringBuilder *sb = StringBuilder_malloc(sizeof(StringBuilder)); sb->size = 1; sb->cap = INITIAL_CAPACITY; char *s = StringBuilder_malloc(sizeof(char) * INITIAL_CAPACITY); s[0] = '\0'; sb->s = s; return sb; } void StringBuilder_free(StringBuilder *sb) { free(sb->s); free(sb); } void StringBuilder_resize(StringBuilder *sb, size_t capacity) { char *s = StringBuilder_malloc(sizeof(char) * capacity); strncpy(s, sb->s, sb->size); free(sb->s); sb->s = s; sb->cap = capacity; } char * StringBuilder_str(StringBuilder *sb) { return sb->s; } void StringBuilder_write_char(StringBuilder *sb, char c) { size_t size = sb->size; if (size >= sb->cap) { StringBuilder_resize(sb, sb->cap * 2); } sb->s[size - 1] = c; sb->s[size - 0] = '\0'; sb->size++; } void StringBuilder_write(StringBuilder *sb, const char *s) { int i; for (i = 0; s[i] != '\0'; i++) { StringBuilder_write_char(sb, s[i]); } } ttygif-1.4.0/string_builder.h000066400000000000000000000006721301036423700162130ustar00rootroot00000000000000#ifndef STRING_BUILDER_H #define STRING_BUILDER_H typedef struct { size_t size; size_t cap; char *s; } StringBuilder; #define INITIAL_CAPACITY 256 StringBuilder * StringBuilder_new(void); void StringBuilder_free(StringBuilder *sb); char * StringBuilder_str(StringBuilder *sb); void StringBuilder_write_char(StringBuilder *sb, char c); void StringBuilder_write(StringBuilder *sb, const char *s); #endif /* STRING_BUILDER_H */ ttygif-1.4.0/ttygif.c000066400000000000000000000222201301036423700144710ustar00rootroot00000000000000/* * Copyright (c) 2000 Satoru Takabayashi * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include "ttyrec.h" #include "io.h" #include "string_builder.h" #include "utils.h" typedef struct { bool fullscreen; bool debug; int skip_limit; int skip_threshold; const char *window_id; const char *img_ext; const char *img_dir; const char *out_file; } Options; typedef int (*ReadFunc) (FILE *fp, Header *h, char **buf); typedef void (*WriteFunc) (char *buf, int len); typedef void (*ProcessFunc) (FILE *fp, ReadFunc read_func, Options o); struct timeval timeval_diff (struct timeval tv1, struct timeval tv2) { struct timeval diff; diff.tv_sec = tv2.tv_sec - tv1.tv_sec; diff.tv_usec = tv2.tv_usec - tv1.tv_usec; if (diff.tv_usec < 0) { diff.tv_sec--; diff.tv_usec += 1000000; } return diff; } int ttydelay (struct timeval prev, struct timeval cur) { struct timeval diff = timeval_diff(prev, cur); if (diff.tv_sec < 0) { diff.tv_sec = diff.tv_usec = 0; } return (diff.tv_sec * 1000) + (diff.tv_usec / 1000); } int ttyread (FILE *fp, Header *h, char **buf) { if (read_header(fp, h) == 0) { return 0; } *buf = malloc(h->len); if (*buf == NULL) { fatalf("Error: Out of memory (malloc)"); } if (fread(*buf, 1, h->len, fp) == 0) { fatalf("Error: Failed to read (fread)"); } return 1; } void ttywrite (char *buf, int len) { fwrite(buf, 1, len, stdout); fflush(stdout); } void clear_screen (void) { printf("\e[1;1H\e[2J"); } void system_exec(const char *cmd, Options o) { if (o.debug) { printf("DEBUG: %s\n", cmd); return; } if (system(cmd) != 0) { fatalf("failed to execute: %s\n", cmd); } } int take_snapshot_darwin(const char *img_path, Options o) { static char cmd [256]; if (sprintf(cmd, "screencapture -l%s -o -m %s &> /dev/null", o.window_id, img_path) < 0) { return -1; } system_exec(cmd, o); if (!o.fullscreen) { if (sprintf(cmd, "convert %s -background white -quiet -flatten +matte -crop +0+22 -crop +4+0 -crop -4-0 +repage %s &> /dev/null", img_path, img_path) < 0) { return -1; } } system_exec(cmd, o); return 0; } int take_snapshot_linux(const char *img_path, Options o) { static char cmd [256]; // ensure text has been written before taking screenshot usleep(50000); if (sprintf(cmd, "xwd -id %s -out %s", o.window_id, img_path) < 0) { return -1; } system_exec(cmd, o); return 0; } int take_snapshot(const char *img_path, Options o) { #ifdef OS_DARWIN return take_snapshot_darwin(img_path, o); #else return take_snapshot_linux(img_path, o); #endif } void ttyplay (FILE *fp, ReadFunc read_func, WriteFunc write_func, Options o) { int index = 0; int delay = 0; struct timeval prev; if (!o.debug) { clear_screen(); } setbuf(stdout, NULL); setbuf(fp, NULL); StringBuilder *sb = StringBuilder_new(); StringBuilder_write(sb, "convert -loop 0 "); int nskipped = 0; bool skip = false; while (true) { char *buf; Header h; static char img_path[256]; static char arg_buffer[256]; if (read_func(fp, &h, &buf) == 0) { break; } if (!o.debug) { write_func(buf, h.len); } if (index != 0) { delay = ttydelay(prev, h.tv); } if (delay <= o.skip_threshold) { skip = true; nskipped++; } else { skip = false; nskipped = 0; } if (skip && nskipped > o.skip_limit) { nskipped = 0; skip = false; } if (!skip && index != 0) { if (sprintf(arg_buffer, " -delay %f %s", delay * 0.1, img_path) < 0) { fatalf("Error: Failed to format 'convert' parameters"); } StringBuilder_write(sb, arg_buffer); } if (sprintf(img_path, "%s/%d.%s", o.img_dir, index, o.img_ext) < 0) { fatalf("Error: Failed to format filename"); } if (take_snapshot(img_path, o) != 0) { fatalf("Error: Failed to take snapshot"); } index++; prev = h.tv; free(buf); } StringBuilder_write(sb, " -layers Optimize "); StringBuilder_write(sb, o.out_file); StringBuilder_write(sb, " 2>&1"); printf("Creating Animated GIF ... this can take a while\n"); system_exec(sb->s, o); printf("Created: %s in the current directory!\n", o.out_file); StringBuilder_free(sb); } void ttyplayback (FILE *fp, ReadFunc read_func, Options o) { ttyplay(fp, ttyread, ttywrite, o); } void usage (void) { #ifdef OS_DARWIN printf("Usage: ttygif [FILE] [-f]\n"); printf(" -f : include window border\n"); #else printf("Usage: ttygif [FILE]\n"); #endif printf(" -h, --help : print this help\n"); printf(" -v, --version : print version\n"); } int main (int argc, char **argv) { ReadFunc read_func = ttyread; ProcessFunc process = ttyplayback; FILE *input = NULL; struct termios old, new; Options options; options.fullscreen = false; options.skip_limit = 5; options.skip_threshold = 0; options.debug = getenv("TTYGIF_DEBUG") != NULL; options.out_file = "tty.gif"; char dir_template[] = "/tmp/ttygif.XXXXXX"; options.img_dir = mkdtemp(dir_template); if (options.img_dir == NULL) { fatalf("Error: Failed to create tmp directory."); } #ifdef OS_DARWIN options.img_ext = "png"; const char *terminal_app = getenv("TERM_PROGRAM"); if (terminal_app == NULL || !strlen(terminal_app)) { fatalf("Error: TERM_PROGRAM environment variable was empty."); } if (strcmp(terminal_app, "Apple_Terminal") == 0) { terminal_app = "Terminal.app"; } int window_id = osx_get_window_id(terminal_app); char window_id_buffer[256]; sprintf(window_id_buffer, "%d", window_id); options.window_id = window_id_buffer; #else options.img_ext = "xwd"; options.window_id = getenv("WINDOWID"); if (options.window_id == NULL || !strlen(options.window_id)) { fatalf("Error: WINDOWID environment variable was empty."); } #endif if (options.debug) { printf("window_id: %s\n", options.window_id); } if (argc < 2) { usage(); exit(EXIT_FAILURE); } if (argc == 2) { if (strstr(argv[1], "-h") || strstr(argv[1], "--help")) { usage(); exit(EXIT_SUCCESS); } if (strstr(argv[1], "-v") || strstr(argv[1], "--version")) { printf(VERSION"\n"); exit(EXIT_SUCCESS); } } if (argc >= 3) { if (strstr(argv[2], "-f")) { options.fullscreen = true; } } set_progname(argv[0]); input = efopen(argv[1], "r"); assert(input != NULL); tcgetattr(0, &old); /* Get current terminal state */ new = old; /* Make a copy */ new.c_lflag &= ~(ICANON | ECHO | ECHONL); /* unbuffered, no echo */ tcsetattr(0, TCSANOW, &new); /* Make it current */ process(input, read_func, options); tcsetattr(0, TCSANOW, &old); /* Return terminal state */ return 0; } ttygif-1.4.0/ttygif.png000066400000000000000000000020241301036423700150330ustar00rootroot00000000000000PNG  IHDRePLTE???___V IDATxMnAF,Ir 707Hn vWOgf"[EzDODa'eo/:wןw{_|##T~UlO]O߻6vUoxY^ɫ.=.h^*mA|կ4%zvR%e TX{F;{F6ַ{>CT/Q]~&1nQH#ͱjT/R-bՋT1&nQHk?ը^zSKTzriTX~֪ԘRV;͏'W {.SG"S}ᄛvQ}1t0{ƝM6eHƒ+GQZՒXTZҹVkjTK:׺+cVFsjTK:2O T}QՒITZbk7ըTonQ->F$>%ՒaWeO5%AweO5%AGAuNi: T4Px|O7y~7j4P@uNi: T4P@uNi: T4P@uNi: T4j:CqTd7o Ti: T4P@uNi: T4P@ucI#IENDB`ttygif-1.4.0/ttygif.rb000066400000000000000000000005261301036423700146570ustar00rootroot00000000000000require 'formula' class Ttygif < Formula homepage 'https://github.com/icholy/ttygif' url 'https://github.com/icholy/ttygif/archive/1.3.0.zip' sha256 'a833cb7798b26ad0652658be24322b07629282452f822f659871e4f4bcdd8173' depends_on 'imagemagick' depends_on 'ttyrec' def install system 'make' bin.install('ttygif') end end ttygif-1.4.0/ttyrec.h000066400000000000000000000002211301036423700144770ustar00rootroot00000000000000#ifndef __TTYREC_H__ #define __TTYREC_H__ #include "sys/time.h" typedef struct header { struct timeval tv; int len; } Header; #endif ttygif-1.4.0/utils.c000066400000000000000000000020511301036423700143230ustar00rootroot00000000000000#include #include #include #include "string_builder.h" void fatalf(const char *format, ...) { va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); exit(EXIT_FAILURE); } int osx_get_window_id(const char *app_name) { char command[1024]; sprintf(command, "osascript -so -e 'tell app \"%s\" to id of window 1' 2> /dev/null", app_name); FILE *fp = popen(command, "r"); if (fp == NULL) { fatalf("Error: failed to run command: %s", command); } int window_id; if (fscanf(fp, "%d", &window_id) != 1) { fatalf("Error: failed to parse window id: %s", command); } pclose(fp); return window_id; } int exec_with_output(const char *command) { FILE *fp = popen(command, "r"); if (fp == NULL) { fatalf("Error: failed to run command: %s", command); } char buffer[1024]; while (fgets(buffer, sizeof(buffer), fp) != NULL) { printf("%s", buffer); } return pclose(fp); } ttygif-1.4.0/utils.h000066400000000000000000000002711301036423700143320ustar00rootroot00000000000000 #ifndef _UTILS_H #define _UTILS_H void fatalf(const char *format, ...); int osx_get_window_id(const char *app_name); int exec_with_output(const char *command); #endif /* _UTILS_H */