/* Compile: cc -O2 -o mpegtty mpegtty.c -lcl */ #include #include #include #include #include #ifdef DUMP_CHAR #include #include #include #endif /* DUMP_CHAR */ char *mpegtty_version = "1.1"; #define DITHER_SIZE 5 #if defined(sgi) #define INLINE __inline #elif defined(__GNUC__) #define INLINE inline #endif typedef struct _AsciiInfo { int ch; int n; } AsciiInfo; typedef struct _Asciis { int n; int cur; char *s; int conc; } Asciis; struct _dither { double **data; int n; }; typedef struct _dither* dither; #ifndef Bool #define Bool int #endif #ifdef DUMP_CHAR Display *disp; char *hostname = NULL; char *font_name = "a14"; #endif /* DUMP_CHAR */ char *image_chars = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; int group = 10; dither dith; int reverse_mode = 0; int imageWidth, imageHeight; int frame_no; int color_mode = 0; dither create_dither(int dn) { int n, i, j, k; dither d; double **data, base; d = (struct _dither*)malloc(sizeof(struct _dither)); n = 2 << dn; d->n = n; data = d->data = (double **)malloc(sizeof(double *) * n); for (i = 0; i < n; i++) { data[i] = d->data[i] = (double *)malloc(sizeof(double) * n); for (j = 0; j < n; data[i][j++] = 0) ; } data[0][0] = 0; data[1][1] = 1; data[0][1] = 2; data[1][0] = 3; for (k = 2; k < n; k <<= 1) { for (i = 0; i < k; i++) { for (j = 0; j < k; j++) { base = (data[i][j] *= 4); data[i + k][j + k] = base + 1; data[i][j + k] = base + 2; data[i + k][j] = base + 3; } } } for (i = 0; i < n; i++) for (j = 0; j < n; j++) data[i][j] /= (double)(n * n); return d; } void free_dither(dither d) { int i; for (i = 0; i < d->n; i++) free(d->data[i]); free(d); } static INLINE Bool is_hit_dither(dither d, double data, int x, int y) { register int n = d->n; return d->data[x % n][y % n] < data; } void set_char_concentration(AsciiInfo *ascii, char *s, int n) { #ifdef DUMP_CHAR int i; int char_w, char_h; int x_off, y_off; XFontStruct *fs; GC gc; Pixmap pm; XImage *im; int x, y; if((fs = XLoadQueryFont(disp, font_name)) == NULL) { fprintf(stderr, "Can't load font `%s'\n", font_name); exit(1); } char_w = fs->max_bounds.rbearing - fs->min_bounds.lbearing; char_h = fs->max_bounds.ascent + fs->max_bounds.descent; x_off = -fs->min_bounds.lbearing; y_off = fs->max_bounds.ascent; /* initialize pixmap for loading font image */ pm = XCreatePixmap(disp, RootWindow(disp, 0), char_w * n, char_h, DefaultDepth(disp, 0)); gc = XCreateGC(disp, pm, 0, NULL); XSetForeground(disp, gc, 1); XSetBackground(disp, gc, 0); XSetFont(disp, gc, fs->fid); XDrawImageString(disp, pm, gc, x_off, y_off, s, n); im = XGetImage(disp, pm, 0, 0, char_w * n, char_h, AllPlanes, ZPixmap); for(i = 0; i < n; i++) { ascii[i].ch = s[i]; if(s[i] == ' ') { ascii[i].n = 0; continue; } for(y = 0; y < char_h; y++) { for(x = char_w * i; x < char_w * (i + 1); x++) { ascii[i].n += XGetPixel(im, x, y); } } } XDestroyImage(im); XFreePixmap(disp, pm); XFreeFont(disp, fs); XFreeGC(disp, gc); #else static int char_conc[128] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 10, 28, 29, 26, 24, 5, 13, 13, 15, 11, 5, 6, 5, 13, 20, 16, 20, 22, 21, 25, 25, 17, 24, 25, 10, 9, 9, 12, 9, 14, 30, 24, 28, 20, 26, 23, 18, 25, 24, 18, 16, 20, 15, 28, 26, 24, 22, 30, 26, 22, 16, 22, 20, 28, 20, 15, 20, 19, 13, 19, 6, 6, 5, 21, 23, 15, 23, 21, 16, 24, 20, 14, 16, 18, 15, 20, 17, 18, 22, 22, 13, 16, 16, 17, 12, 18, 14, 22, 17, 15, 13, 15, 8, 0 }; int i; for(i = 0; i < n; i++) { ascii[i].ch = s[i]; ascii[i].n = char_conc[s[i]]; } #endif /* DUMP_CHAR */ } int calc_concentration_rank(AsciiInfo *ascii, int n) { int i, rank, conc; i = 0; rank = 0; while(i < n) { conc = ascii[i].n; while(i < n && ascii[i].n == conc) i++; rank++; } return rank; } #ifdef DUMP_CHAR void open_display(void) { if((disp = XOpenDisplay(hostname)) == NULL) { fprintf(stderr, "Can't open display `%s'\n", XDisplayName(hostname)); exit(1); } } void close_display(void) { XCloseDisplay(disp); } #endif /* DUMP_CHAR */ int cmp_ascii(const AsciiInfo *a1, const AsciiInfo *a2) { return a1->n - a2->n; } void sort_ascii(AsciiInfo *ascii, int n) { qsort(ascii, n, sizeof(AsciiInfo), (int (*)(const void*,const void*))cmp_ascii); } Asciis *make_asciis(char *s, int *rank_return) { AsciiInfo *ascii; Asciis *as; int n; int rank; int i, j, k, x, conc, r; int cval[128]; n = strlen(s); ascii = (AsciiInfo *)malloc(sizeof(AsciiInfo) * n); set_char_concentration(ascii, image_chars, n); #ifdef DUMP_CHAR memset(cval, 0, sizeof(cval)); for(i = 0; i < n; i++) cval[ascii[i].ch] = ascii[i].n; for(i = 0; i < 128; i++) printf("%d, ", cval[i]); printf("\n"); exit(0); #endif sort_ascii(ascii, n); rank = calc_concentration_rank(ascii, n); as = (Asciis *)malloc(sizeof(Asciis) * rank); j = 0; for(i = 0; i < rank; i++) { conc = ascii[j].n; k = 0; while(j < n && ascii[j].n == conc) { j++; k++; } as[i].n = k; as[i].cur = 0; as[i].s = (char *)malloc(k + 1); as[i].conc = conc; for(x = 0; x < k; x++) as[i].s[x] = ascii[j - k + x].ch; } free(ascii); *rank_return = rank; return as; } static INLINE int select_ascii(Asciis *as) { int ch; ch = as->s[as->cur]; as->cur = (as->cur + 1) % as->n; return ch; } /* 0.0 <= conc <= 1.0 */ int get_ascii(Asciis *as, int n, double conc, int x, int y) { int left, right; double t; int ndiv; double a; ndiv = n - 1; a = 1.0 / ndiv; t = conc * 0.9999 / a; left = (int)t; right = (int)(t + 1); if(left == right) return select_ascii(as + left); else { t = (conc - left * a) / a; if(is_hit_dither(dith, t, x, y)) return select_ascii(as + right); } return select_ascii(as + left); } static INLINE double calc_color_value(int frame_picel) { double r, g, b; r = (((frame_picel & 0x00ff0000) >> 16)) / 256.0; g = (((frame_picel & 0x0000ff00) >> 8)) / 256.0; b = (((frame_picel & 0x000000ff) >> 0)) / 256.0; if(reverse_mode) return 1 - (0.299 * r + 0.587 * g + 0.114 * b); /* 0.0 - 1.0 */ return 0.299 * r + 0.587 * g + 0.114 * b; /* 0.0 - 1.0 */ } static int last_rgb = -1; static INLINE void set_color(unsigned *frame, int w, int h, int x, int y, int scale) { unsigned pic; int rgb; char buff[6]; double d, r, g, b; pic = frame[(y * w + x) * scale]; if((pic & 0x00ffffff) == 0) rgb = 0; else { r = ((pic>> 0) & 0xff); g = ((pic>> 8) & 0xff); b = ((pic>>16) & 0xff); if(r < g) d = g; else d = r; if(d < b) d = b; d = (1.0 / d); r *= d; g *= d; b *= d; if(r < 0.90 || g < 0.90 || b < 0.90) { if(r < g && r < b) r = 0; else if(g < r && g < b) g = 0; else b = 0; } rgb = 0; if(r > 0.5) rgb |= 1; if(g > 0.5) rgb |= 2; if(b > 0.5) rgb |= 4; } if(last_rgb == rgb) return; buff[0] = '\033'; buff[1] = '['; buff[2] = '3'; buff[3] = '0' + rgb; buff[4] = 'm'; buff[5] = '\0'; fputs(buff, stdout); last_rgb = rgb; } void reset_color(void) { fputs("\033[0m", stdout); last_rgb = -1; } double **screen_concentrations(int* frame, int iw, int ih, int *ww, int *hh, int g) { static double **data = NULL; static int w, h; int i, xx, yy, x, y; double d, gg; w = *ww = iw / g - 1; h = *hh = ih / g - 1; if(data == NULL) { /* allocate data */ data = (double **)malloc(sizeof(double *) * h); for(i = 0; i < h; i++) data[i] = (double *)malloc(sizeof(double) * w); } gg = 1.0 / (g * g); for(y = 0; y < h; y++) { for(x = 0; x < w; x++) { d = 0.0; for(yy = 0; yy < g; yy++) { for(xx = 0; xx < g; xx++) { d += calc_color_value(frame[(y * g + yy) * iw + x * g + xx]); } } data[y][x] = d * gg; } } return data; } void dump_image(Asciis* as, int nascii, void* frame) { int* p = (int *)frame; int x, y, w, h, n; double** data; double cmax, cmin, cb; data = screen_concentrations((int *)frame, imageWidth, imageHeight, &w, &h, group); cmax = cmin = data[0][0]; for(y = 0; y < h; y++) { for(x = 0; x < w; x++) { if(data[y][x] > cmax) cmax = data[y][x]; else if(data[y][x] < cmin) cmin = data[y][x]; } } cb = cmax - cmin; putchar('\033'); putchar('['); putchar('H'); printf("%d\n", frame_no); for(y = h - 1; y >= 0; y--) { for(x = 0; x < w; x++) { int c1, c2; if(cb < 0.0001) { c1 = get_ascii(as, nascii, cmin, x, y); c2 = get_ascii(as, nascii, cmin, x, y); } else { double conc; conc = (data[y][x] - cmin) / cb; c1 = get_ascii(as, nascii, conc, x, y); c2 = get_ascii(as, nascii, conc, x, y); } if(color_mode) set_color((unsigned *)frame, imageWidth, imageHeight, x, y, group); putchar(c1); putchar(c2); } putchar('\n'); } if(color_mode) reset_color(); fflush(stdout); } void print_asciis(Asciis *as, int n) { int i, j; for(i = 0; i < n; i++) { printf("%d:%d<%d>:", i, as[i].conc, as[i].n); for(j = 0; j < as[i].n; j++) printf(" `%c'", as[i].s[j]); printf("\n"); } } void usage(void) { #ifdef DUMP_CHAR puts( "mpegtty [-display ] [-pixel ] " "[-c ] [-r] [-h] MPEG-file" ); #else puts( "mpegtty [-pixel ] [-color]" "[-c ] [-r] [-h] MPEG-file" ); #endif /* DUMP_CHAR */ } void main(int argc, char** argv) { char* fname; CLhandle decompressorHdl; int decompressionScheme; int inFile; char* header; int headerSize; int compressedBufferSize; CLbufferHdl compressedBufferHdl; CLbufferHdl frameBufferHdl; void* frameBuffer; Asciis *as; int nascii; argc--; argv++; for(;; argv++, argc--) { if(!strcmp(*argv, "-pixel")) { argv++, argc--; group = atoi(*argv); } #ifdef DUMP_CHAR else if(!strcmp(*argv, "-display")) { argv++, argc--; hostname = *argv; } #endif /* DUMP_CHAR */ else if(!strcmp(*argv, "-c")) { argv++, argc--; image_chars = *argv; } else if(!strcmp(*argv, "-v")) { printf("mpegtty version %s\n", mpegtty_version); exit(0); } else if(!strcmp(*argv, "-r")) { reverse_mode = 1; } else if(!strcmp(*argv, "-color")) { color_mode = 1; } else if(!strcmp(*argv, "-h")) { usage(); exit(0); } else break; } if(argc == 0) { usage(); exit(1); } #ifdef DUMP_CHAR open_display(); #endif /* DUMP_CHAR */ dith = create_dither(DITHER_SIZE); as = make_asciis(image_chars, &nascii); /* print_asciis(as, nascii); exit(0); */ fname = *argv; inFile = open(fname, O_RDONLY); if(inFile < 0) { perror(fname); exit(1); } /* Determine the scheme from the first 16 bytes of the header */ header = (char *)malloc(16); read(inFile, header, 16); decompressionScheme = clQueryScheme(header); if(decompressionScheme < 0) { fprintf(stderr, "Unknown scheme in stream header.\n"); exit(1); } free(header); /* Open the appropriate decompressor */ clOpenDecompressor(decompressionScheme, &decompressorHdl); /* Find out how much header information to provide */ headerSize = clQueryMaxHeaderSize(decompressionScheme); if(headerSize > 0) { /* Get the header data */ header = (char *)malloc(headerSize); lseek(inFile, 0, SEEK_SET); read(inFile, header, headerSize); /* Read the header */ clReadHeader(decompressorHdl, headerSize, header); free(header); /* Reset the stream */ lseek(inFile, 0, SEEK_SET); } imageWidth = clGetParam(decompressorHdl, CL_IMAGE_WIDTH); imageHeight = clGetParam(decompressorHdl, CL_IMAGE_HEIGHT); /* printf("## W/H = %d/%d\n", imageWidth, imageHeight); */ /* Allocate space for compressed buffer */ compressedBufferSize = clGetParam(decompressorHdl, CL_COMPRESSED_BUFFER_SIZE); /* compressedBuffer = malloc(compressedBufferSize); */ frameBufferHdl = clCreateBuf(decompressorHdl, CL_BUF_FRAME, 12, 4 * imageWidth * imageHeight, NULL); compressedBufferHdl = clCreateBuf(decompressorHdl, CL_BUF_COMPRESSED, compressedBufferSize, 1, NULL); frame_no = 0; fputs("\033[H\033[J", stdout); fflush(stdout); while(1) { int size, wrap; void* buf; /* printf("## frame=%d\n", frame_no); */ while((size = clQueryFree(compressedBufferHdl, 0, &buf, &wrap)) > 0) { if(read(inFile, buf, size) <= 0) goto done; clUpdateHead(compressedBufferHdl, size); } /* Decompress a frame */ clDecompress(decompressorHdl, 1, 0, NULL, NULL); /* Read the frame */ size = clQueryValid(frameBufferHdl, 1, &frameBuffer, &wrap); frame_no++; /* printf("## size=%d\n", size); */ dump_image(as, nascii, frameBuffer); clUpdateTail(frameBufferHdl, size); } done: #if 0 clCreateBuf(decompressorHdl, CL_BUF_COMPRESSED, compressedBufferSize, 1, NULL); /* Decompress a series of frames */ for(i = 0; i < N; i++) { /* Keep input buffer full with compressed data */ ... clDecompress(handle, 1, 0, NULL, frameBuffer); /* Write the frame to somewhere (such as the screen) */ ... } #endif /* Close the decompressor */ clCloseDecompressor(decompressorHdl); close(inFile); #ifdef DUMP_CHAR close_display(); #endif /* DUMP_CHAR */ exit(0); }