/* cc.c -- closed caption decoder * Mike Baker (mbm@linux.com) * (based on code by timecop@japan.co.jp) * Buffer overflow bugfix by Mark K. Kim (dev@cbreak.org), 2003.05.22 * * Libzvbi port, various fixes and improvements * (C) 2005-2007 Michael H. Schimek * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_GETOPT_LONG # include #endif #define HAVE_ZVBI 1 #include #include "src/libzvbi.h" #ifndef X_DISPLAY_MISSING # include # include # include # include Display *dpy; Window Win,Root; char dpyname[256] = ""; GC WinGC; GC WinGC0; GC WinGC1; int x; #endif #undef PROGRAM #define PROGRAM "CCDecoder" #undef VERSION #define VERSION "0.12" #define N_ELEMENTS(array) (sizeof (array) / sizeof (*(array))) #define CLEAR(var) memset (&(var), 0, sizeof (var)) static char * my_name; static unsigned int field; static vbi_bool in_xds[2]; static int cur_ch[2]; //XDSdecode static struct { char packet[34]; uint8_t length; int print : 1; } info[2][8][25]; char newinfo[2][8][25][34]; char *infoptr=newinfo[0][0][0]; int mode,type; char infochecksum; static const char * xds_info_prefix = "\33[33m% "; static const char * xds_info_suffix = "\33[0m\n"; static FILE * xds_fp; //ccdecode const char *ratings[] = {"(NOT RATED)","TV-Y","TV-Y7","TV-G","TV-PG","TV-14","TV-MA","(NOT RATED)"}; int rowdata[] = {11,-1,1,2,3,4,12,13,14,15,5,6,7,8,9,10}; const char *specialchar[] = {"®","°","½","¿","(TM)","¢","£","o/~ ","à"," ","è","â","ê","î","ô","û"}; const char *modes[]={"current","future","channel","miscellaneous","public service","reserved","invalid","invalid","invalid","invalid"}; int lastcode; int ccmode=1; //cc1 or cc2 char ccbuf[8][3][256]; //cc is 32 columns per row, this allows for extra characters static uint16_t cc_ubuf[8][3][256]; int keywords=0; char *keyword[32]; static int is_upper[8]; static FILE * cc_fp[8]; //args (this should probably be put into a structure later) char useraw=0; char semirawdata=0; char usexds=0; char usecc=0; char plain=0; char usesen=0; char debugwin=0; char test=0; char usewebtv=1; char rawline=-1; int sen; int inval; static int parityok(int n) /* check parity for 2 bytes packed in n */ { int mask=0; int j, k; for (k = 1, j = 0; j < 7; j++) { if (n & (1<>7)&1)) mask|=0x00FF; for (k = 1, j = 8; j < 15; j++) { if (n & (1<>15)&1)) mask|=0xFF00; return mask; } static int decodebit(unsigned char *data, int threshold) { int i, sum = 0; for (i = 0; i < 23; i++) sum += data[i]; return (sum > threshold*23); } static int decode(unsigned char *vbiline) { int max[7], min[7], val[7], i, clk, tmp, sample, packedbits = 0; for (clk=0; clk<7; clk++) max[clk] = min[clk] = val[clk] = -1; clk = tmp = 0; i=30; while (i < 600 && clk < 7) { /* find and lock all 7 clocks */ sample = vbiline[i]; if (max[clk] < 0) { /* find maximum value before drop */ if (sample > 85 && sample > val[clk]) (val[clk] = sample, tmp = i); /* mark new maximum found */ else if (val[clk] - sample > 30) /* far enough */ (max[clk] = tmp, i = tmp + 10); } else { /* find minimum value after drop */ if (sample < 85 && sample < val[clk]) (val[clk] = sample, tmp = i); /* mark new minimum found */ else if (sample - val[clk] > 30) /* searched far enough */ (min[clk++] = tmp, i = tmp + 10); } i++; } i=min[6]=min[5]-max[5]+max[6]; if (clk != 7 || vbiline[max[3]] - vbiline[min[5]] < 45) /* failure to locate clock lead-in */ return -1; #ifndef X_DISPLAY_MISSING if (debugwin) { for (clk=0;clk<7;clk++) { XDrawLine(dpy,Win,WinGC,min[clk]/2,0,min[clk]/2,128); XDrawLine(dpy,Win,WinGC1,max[clk]/2,0,max[clk]/2,128); } XFlush(dpy); } #endif /* calculate threshold */ for (i=0,sample=0;i<7;i++) sample=(sample + vbiline[min[i]] + vbiline[max[i]])/3; for(i=min[6];vbiline[i]5) break; fprintf (xds_fp, "%s LENGTH: %d:%02d:%02d of %d:%02d:00%s", xds_info_prefix, infoptr[3]&0x3f,infoptr[2]&0x3f, infoptr[4]&0x3f,infoptr[1]&0x3f, infoptr[0]&0x3f, xds_info_suffix); break; case 0x0103: fprintf (xds_fp, "%s TITLE: %s%s", xds_info_prefix, infoptr, xds_info_suffix); break; case 0x0105: fprintf (xds_fp, "%s RATING: %s (%d)", xds_info_prefix, ratings[infoptr[0]&0x07],infoptr[0]); if ((infoptr[0]&0x07)>0) { if (infoptr[0]&0x20) fputs (" VIOLENCE", xds_fp); if (infoptr[0]&0x10) fputs (" SEXUAL", xds_fp); if (infoptr[0]&0x08) fputs (" LANGUAGE", xds_fp); } fputs (xds_info_suffix, xds_fp); break; case 0x0501: fprintf (xds_fp, "%s NETWORK: %s%s", xds_info_prefix, infoptr, xds_info_suffix); break; case 0x0502: fprintf (xds_fp, "%s CALL: %s%s", xds_info_prefix, infoptr, xds_info_suffix); break; case 0x0701: fprintf (xds_fp, "%sCUR.TIME: %d:%02d %d/%02d/%04d UTC%s", xds_info_prefix, infoptr[1]&0x1F,infoptr[0]&0x3f, infoptr[3]&0x0f,infoptr[2]&0x1f, (infoptr[5]&0x3f)+1990, xds_info_suffix); break; case 0x0704: //timezone fprintf (xds_fp, "%sTIMEZONE: UTC-%d%s", xds_info_prefix, infoptr[0]&0x1f, xds_info_suffix); break; case 0x0104: //program genere break; case 0x0110: case 0x0111: case 0x0112: case 0x0113: case 0x0114: case 0x0115: case 0x0116: case 0x0117: fprintf (xds_fp, "%s DESC: %s%s", xds_info_prefix, infoptr, xds_info_suffix); break; } fflush (xds_fp); } static int XDSdecode(int data) { static vbi_bool in_xds[2]; int b1, b2, length; if (data == -1) return -1; b1 = data & 0x7F; b2 = (data>>8) & 0x7F; if (0 == b1) { /* Filler, discard. */ return -1; } else if (b1 < 15) // start packet { mode = b1; type = b2; infochecksum = b1 + b2 + 15; if (mode > 8 || type > 20) { // printf("%% Unsupported mode %s(%d) [%d]\n",modes[(mode-1)>>1],mode,type); mode=0; type=0; } infoptr = newinfo[field][mode][type]; in_xds[field] = TRUE; } else if (b1 == 15) // eof (next byte is checksum) { #if 0 //debug if (mode == 0) { length=infoptr - newinfo[field][0][0]; infoptr[1]=0; printf("LEN: %d\n",length); for (y=0;y= &newinfo[field][mode][type][32]) { /* Bad packet. */ mode = 0; type = 0; in_xds[field] = 0; } else { infoptr[0] = b1; infoptr++; infoptr[0] = b2; infoptr++; infochecksum += b1 + b2; } } return 0; } static int webtv_check(char * buf,int len) { unsigned long sum; unsigned long nwords; unsigned short csum=0; char temp[9]; int nbytes=0; while (buf[0]!='<' && len > 6) //search for the start { buf++; len--; } if (len == 6) //failure to find start return 0; while (nbytes+6 <= len) { //look for end of object checksum, it's enclosed in []'s and there shouldn't be any [' after if (buf[nbytes] == '[' && buf[nbytes+5] == ']' && buf[nbytes+6] != '[') break; else nbytes++; } if (nbytes+6>len) //failure to find end return 0; nwords = nbytes >> 1; sum = 0; //add up all two byte words while (nwords-- > 0) { sum += *buf++ << 8; sum += *buf++; } if (nbytes & 1) { sum += *buf << 8; } csum = (unsigned short)(sum >> 16); while(csum !=0) { sum = csum + (sum & 0xffff); csum = (unsigned short)(sum >> 16); } sprintf(temp,"%04X\n",(int)~sum&0xffff); buf++; if(!strncmp(buf,temp,4)) { buf[5]=0; if (cur_ch[field] >= 0 && cc_fp[cur_ch[field]]) { if (!plain) fprintf(cc_fp[cur_ch[field]], "\33[35mWEBTV: %s\33[0m\n",buf-nbytes-1); else fprintf(cc_fp[cur_ch[field]], "WEBTV: %s\n",buf-nbytes-1); fflush (cc_fp[cur_ch[field]]); } } return 0; } static int unicode (int c) { if (c >= 'a' && c <= 'z') { is_upper[cur_ch[field]] = 0; } else if (c >= 'A' && c <= 'Z') { if (is_upper[cur_ch[field]] < 3) ++is_upper[cur_ch[field]]; } /* The standard character set has no upper case accented characters, so we convert to upper case if that appears to be intended. */ return vbi_caption_unicode (c, (is_upper[cur_ch[field]] >= 3)); } static void append_char (int c, int uc) { unsigned int ch = cur_ch[field]; unsigned int dlen; dlen = strlen (ccbuf[ch][ccmode]); if (dlen < N_ELEMENTS (ccbuf[0][0]) - 1) { ccbuf[ch][ccmode][dlen] = c; ccbuf[ch][ccmode][dlen + 1] = 0; } dlen = vbi_strlen_ucs2 (cc_ubuf[ch][ccmode]); if (dlen < N_ELEMENTS (cc_ubuf[0][0]) - 1) { cc_ubuf[ch][ccmode][dlen] = uc; cc_ubuf[ch][ccmode][dlen + 1] = 0; } } static void append_special_char (int b2) { unsigned int ch = cur_ch[field]; unsigned int dlen; unsigned int slen; slen = strlen (specialchar[b2&0x0f]); dlen = strlen (ccbuf[ch][ccmode]); if (dlen + slen < N_ELEMENTS (ccbuf[0][0]) - 1) { strcpy (&ccbuf[ch][ccmode][dlen], specialchar[b2&0x0f]); } dlen = vbi_strlen_ucs2 (cc_ubuf[ch][ccmode]); if (dlen < N_ELEMENTS (cc_ubuf[0][0]) - 1) { cc_ubuf[ch][ccmode][dlen] = unicode (0x1100 | b2); cc_ubuf[ch][ccmode][dlen + 1] = 0; } } static void append_control_seq (const char * seq) { unsigned int ch = cur_ch[field]; unsigned int slen; unsigned int dlen; if (plain) return; slen = strlen (seq); dlen = strlen (ccbuf[ch][ccmode]); if (dlen + slen < N_ELEMENTS (ccbuf[0][0]) - 1) { strcpy (&ccbuf[ch][ccmode][dlen], seq); } dlen = vbi_strlen_ucs2 (cc_ubuf[ch][ccmode]); if (dlen + slen < N_ELEMENTS (cc_ubuf[0][0]) - 1) { unsigned int i; for (i = 0; 0 != seq[i]; ++i) { /* ASCII -> UCS-2 */ cc_ubuf[ch][ccmode][dlen + i] = seq[i]; } cc_ubuf[ch][ccmode][dlen + i] = 0; } } static int CCdecode(int data) { int b1, b2, row, x,y; if (cur_ch[field] < 0) return -1; if (data == -1) //invalid data. flush buffers to be safe. { CLEAR (ccbuf); CLEAR (cc_ubuf); return -1; } b1 = data & 0x7f; b2 = (data>>8) & 0x7f; if(ccmode >= 3) ccmode = 0; if (b1&0x60 && data != lastcode) // text { append_char (b1, unicode (b1)); if (b2&0x60) { append_char (b2, unicode (b2)); } if ((b1 == ']' || b2 == ']') && usewebtv) webtv_check(ccbuf[cur_ch[field]][ccmode], strlen (ccbuf[cur_ch[field]][ccmode])); } else if ((b1&0x10) && (b2>0x1F) && (data != lastcode)) //codes are always transmitted twice (apparently not, ignore the second occurance) { ccmode=((b1>>3)&1)+1; if (b2 & 0x40) //preamble address code (row & indent) { row=rowdata[((b1<<1)&14)|((b2>>5)&1)]; if (strlen (ccbuf[cur_ch[field]][ccmode]) > 0) { append_char ('\n', '\n'); } if (b2&0x10) //row contains indent flag for (x=0;x<(b2&0x0F)<<1;x++) { append_char (' ', ' '); } } else { switch (b1 & 0x07) { case 0x00: //attribute if (cc_fp[cur_ch[field]]) { // fprintf (cc_fp[cur_ch[field]], "\n",b1,b2); // fflush (cc_fp[cur_ch[field]]); } break; case 0x01: //midrow or char switch (b2&0x70) { case 0x20: //midrow attribute change switch (b2&0x0e) { case 0x00: //italics off append_control_seq ("\33[0m "); break; case 0x0e: //italics on append_control_seq ("\33[36m "); break; } if (b2&0x01) { //underline append_control_seq ("\33[4m"); } else { append_control_seq ("\33[24m"); } break; case 0x30: //special character.. append_special_char (b2); break; } break; case 0x04: //misc case 0x05: //misc + F if (cc_fp[cur_ch[field]]) { // fprintf (cc_fp[cur_ch[field]], "ccmode %d cmd %02x\n",ccmode,b2); } switch (b2) { size_t n; unsigned int dlen; case 0x21: //backspace dlen = strlen (ccbuf[cur_ch[field]][ccmode]); if (dlen > 0) { ccbuf[cur_ch[field]][ccmode][dlen - 1] = 0; } dlen = vbi_strlen_ucs2 (cc_ubuf[cur_ch[field]][ccmode]); if (dlen > 0) { cc_ubuf[cur_ch[field]][ccmode][dlen - 1] = 0; } break; /* these codes are insignifigant if we're ignoring positioning */ case 0x25: //2 row caption case 0x26: //3 row caption case 0x27: //4 row caption case 0x29: //resume direct caption case 0x2B: //resume text display case 0x2C: //erase displayed memory break; case 0x2D: //carriage return if (ccmode==2) break; case 0x2F: //end caption + swap memory case 0x20: //resume caption (new caption) if (!strlen(ccbuf[cur_ch[field]][ccmode])) break; for (n=0;n>8) & 0x7f; if (!semirawdata) { fprintf(stderr,"%c%c",b1,b2); fflush(stderr); return 0; } // semi-raw data output begins here... // a control code. if ( ( b1 >= 0x10 ) && ( b1 <= 0x1F ) ) { if ( ( b2 >= 0x20 ) && ( b2 <= 0x7F ) ) fprintf(stderr,"[%02X-%02X]",b1,b2); fflush(stderr); return 0; } // next two rules: // supposed to be one printable char // and the other char to be discarded if ( ( b1 >= 0x0 ) && ( b1 <= 0xF ) ) { fprintf(stderr,"(%02x)%c",b1,b2); //fprintf(stderr,"%c",b2); //fprintf(stderr,"%c%c",0,b2); fflush(stderr); return 0; } if ( ( b2 >= 0x0 ) && ( b2 <= 0xF ) ) { fprintf(stderr,"%c{%02x}",b1,b2); //fprintf(stderr,"%c",b1); //fprintf(stderr,"%c%c",b1,1); fflush(stderr); return 0; } // just classic two chars to print. fprintf(stderr,"%c%c",b1,b2); fflush(stderr); return 0; } static int sentence(int data) { int b1, b2; if (data == -1) return -1; if (cur_ch[field] < 0 || !cc_fp[cur_ch[field]]) return 0; b1 = data & 0x7f; b2 = (data>>8) & 0x7f; inval++; if (data==lastcode) { if (sen==1) { fprintf (cc_fp[cur_ch[field]], " "); fflush (cc_fp[cur_ch[field]]); sen=0; } if (inval>10 && sen) { fprintf (cc_fp[cur_ch[field]], "\n"); fflush (cc_fp[cur_ch[field]]); sen=0; } return 0; } lastcode=data; if (b1&96) { inval=0; if (sen==2 && b1!='.' && b2!='.' && b1!='!' && b2!='!' && b1!='?' && b2!='?' && b1!=')' && b2!=')') { fprintf (cc_fp[cur_ch[field]], "\n"); sen=1; } else if (b1=='.' || b2=='.' || b1=='!' || b2=='!' || b1=='?' || b2=='?' || b1==')' || b2==')') sen=2; else sen=1; fprintf (cc_fp[cur_ch[field]], "%c%c",tolower(b1),tolower(b2)); fflush (cc_fp[cur_ch[field]]); } return 0; } #ifndef X_DISPLAY_MISSING static unsigned long getColor(const char *colorName, float dim) { XColor Color; XWindowAttributes Attributes; XGetWindowAttributes(dpy, Root, &Attributes); Color.pixel = 0; XParseColor (dpy, Attributes.colormap, colorName, &Color); Color.red=(unsigned short)(Color.red/dim); Color.blue=(unsigned short)(Color.blue/dim); Color.green=(unsigned short)(Color.green/dim); Color.flags=DoRed | DoGreen | DoBlue; XAllocColor (dpy, Attributes.colormap, &Color); return Color.pixel; } #endif static void caption_filter (unsigned int c1, unsigned int c2) { unsigned int p; p = c1 + c2 * 256; p ^= p >> 4; p ^= p >> 2; p ^= p >> 1; c1 &= 0x7F; c2 &= 0x7F; if (0x0101 != (p & 0x0101)) { /* Parity error. */ cur_ch[field] = -1; } else if (0 == c1) { /* Filler. */ } else if (c1 < 0x10) { in_xds[field] = TRUE; } else if (c1 < 0x20) { in_xds[field] = FALSE; if (c2 < 0x20) { /* Invalid. */ } else { cur_ch[field] &= ~1; cur_ch[field] |= (c1 >> 3) & 1; if (c2 < 0x30 && 0x14 == (c1 & 0xF6)) { cur_ch[field] &= ~2; cur_ch[field] |= (c1 << 1) & 2; switch (c2) { case 0x20: /* RCL */ case 0x25: /* RU2 */ case 0x26: /* RU3 */ case 0x27: /* RU4 */ case 0x29: /* RDC */ cur_ch[field] &= 3; break; case 0x2A: /* TR */ case 0x2B: /* RTD */ cur_ch[field] &= 3; /* now >= 0 */ cur_ch[field] |= 4; break; default: break; } } } } if (0) { fprintf (stderr, "in_xds=%d cur_ch=%d\n", in_xds[field], cur_ch[field]); } } static ssize_t read_test_stream (vbi_sliced * sliced, int * n_lines, unsigned int max_lines) { char buf[256]; double dt; unsigned int n_items; vbi_sliced *s; if (ferror (stdin) || !fgets (buf, 255, stdin)) { fprintf (stderr, "End of test stream\n"); exit (EXIT_SUCCESS); } dt = strtod (buf, NULL); n_items = fgetc (stdin); assert (n_items < max_lines); s = sliced; while (n_items-- > 0) { int index; index = fgetc (stdin); s->line = (fgetc (stdin) + 256 * fgetc (stdin)) & 0xFFF; if (index < 0) { fprintf (stderr, "Bad index in test stream\n"); exit (EXIT_FAILURE); } switch (index) { case 0: s->id = VBI_SLICED_TELETEXT_B; fread (s->data, 1, 42, stdin); break; case 1: s->id = VBI_SLICED_CAPTION_625; fread (s->data, 1, 2, stdin); break; case 2: s->id = VBI_SLICED_VPS; fread (s->data, 1, 13, stdin); break; case 3: s->id = VBI_SLICED_WSS_625; fread (s->data, 1, 2, stdin); break; case 4: s->id = VBI_SLICED_WSS_CPR1204; fread (s->data, 1, 3, stdin); break; case 7: s->id = VBI_SLICED_CAPTION_525; fread(s->data, 1, 2, stdin); break; default: fprintf (stderr, "\nUnknown data type %d " "in test stream\n", index); exit (EXIT_FAILURE); } ++s; } *n_lines = s - sliced; return 1; /* success */ } static void xds_filter_option (const char * optarg) { const char *s; /* Attention: may be called multiple times. */ if (NULL == optarg || 0 == strcasecmp (optarg, "all")) { unsigned int i; for (i = 0; i < (N_ELEMENTS (info[0]) * N_ELEMENTS (info[0][0])); ++i) { info[0][0][i].print = TRUE; } return; } s = optarg; while (0 != *s) { char buf[16]; unsigned int len; for (;;) { if (0 == *s) return; if (isalnum (*s)) break; ++s; } for (len = 0; len < N_ELEMENTS (buf) - 1; ++len) { if (!isalnum (*s)) break; buf[len] = *s++; } buf[len] = 0; if (0 == strcasecmp (buf, "timecode")) { info[0][1][1].print = TRUE; } else if (0 == strcasecmp (buf, "length")) { info[0][1][2].print = TRUE; } else if (0 == strcasecmp (buf, "title")) { info[0][1][3].print = TRUE; } else if (0 == strcasecmp (buf, "rating")) { info[0][1][5].print = TRUE; } else if (0 == strcasecmp (buf, "network")) { info[0][5][1].print = TRUE; } else if (0 == strcasecmp (buf, "call")) { info[0][5][2].print = TRUE; } else if (0 == strcasecmp (buf, "time")) { info[0][7][1].print = TRUE; } else if (0 == strcasecmp (buf, "timezone")) { info[0][7][4].print = TRUE; } else if (0 == strcasecmp (buf, "desc")) { info[0][1][0x10].print = TRUE; info[0][1][0x11].print = TRUE; info[0][1][0x12].print = TRUE; info[0][1][0x13].print = TRUE; info[0][1][0x14].print = TRUE; info[0][1][0x15].print = TRUE; info[0][1][0x16].print = TRUE; info[0][1][0x17].print = TRUE; } else { fprintf (stderr, "Unknown XDS info '%s'\n", buf); } } } static void usage (FILE * fp) { fprintf (fp, "\ " PROGRAM " " VERSION " -- Closed Caption and XDS decoder\n\ Copyright (C) 2003-2006 Mike Baker, Mark K. Kim, Michael H. Schimek\n\ ; Based on code by timecop@japan.co.jp.\n\ This program is licensed under GPL 2 or later. NO WARRANTIES.\n\n\ Usage: %s [options]\n\ Options:\n\ -? | -h | --help | --usage Print this message and exit\n\ -1 ... -4 | --cc1-file ... --cc4-file filename\n\ Append caption channel CC1 ... CC4 to this file\n\ -b | --no-webtv Do not print WebTV links\n\ -c | --cc Print Closed Caption (includes WebTV)\n\ -d | --device filename VBI device [/dev/vbi]\n\ -f | --filter type[,type]* Select XDS info: all, call, desc, length,\n\ network, rating, time, timecode, timezone,\n\ title. Multiple -f options accumulate. [all]\n\ -k | --keyword string Break caption line at this word (broken?).\n\ Multiple -k options accumulate.\n\ -l | --channel number Select caption channel 1 ... 4 [no filter]\n\ -p | --plain-ascii Print plain ASCII, else insert VT.100 color,\n\ italic and underline control codes\n\ -r | --raw line-number Dump raw VBI data\n\ -s | --sentences Decode caption by sentences\n\ -v | --verbose Increase verbosity\n\ -w | --window Open debugging window (with -r option)\n\ -x | --xds Print XDS info\n\ -C | --cc-file filename Append all caption to this file [stdout]\n\ -R | --semi-raw Dump semi-raw VBI data (with -r option)\n\ -X | --xds-file filename Append XDS info to this file [stdout]\n\ ", my_name); } static const char short_options [] = "?1:2:3:4:5:6:7:8:bcd:f:hkl:pr:stvwxC:RX:"; #ifdef HAVE_GETOPT_LONG static const struct option long_options [] = { { "help", no_argument, NULL, '?' }, { "cc1-file", required_argument, NULL, '1' }, { "cc2-file", required_argument, NULL, '2' }, { "cc3-file", required_argument, NULL, '3' }, { "cc4-file", required_argument, NULL, '4' }, { "t1-file", required_argument, NULL, '5' }, { "t2-file", required_argument, NULL, '6' }, { "t3-file", required_argument, NULL, '7' }, { "t4-file", required_argument, NULL, '8' }, { "no-webtv", no_argument, NULL, 'b' }, { "cc", no_argument, NULL, 'c' }, { "device", required_argument, NULL, 'd' }, { "filter", required_argument, NULL, 'f' }, { "help", no_argument, NULL, 'h' }, { "keyword", required_argument, NULL, 'k' }, { "channel", required_argument, NULL, 'l' }, { "plain-ascii", no_argument, NULL, 'p' }, { "raw", required_argument, NULL, 'r' }, { "sentences", no_argument, NULL, 's' }, { "test", no_argument, NULL, 't' }, { "verbose", no_argument, NULL, 'v' }, { "window", no_argument, NULL, 'w' }, { "xds", no_argument, NULL, 'x' }, { "usage", no_argument, NULL, 'u' }, { "cc-file", required_argument, NULL, 'C' }, { "semi-raw", no_argument, NULL, 'R' }, { "xds-file", required_argument, NULL, 'X' }, { NULL, 0, 0, 0 } }; #else # define getopt_long(ac, av, s, l, i) getopt(ac, av, s) #endif static int option_index; static FILE * open_output_file (const char * name) { FILE *fp; if (NULL == name || 0 == strcmp (name, "-")) { fp = stdout; } else { fp = fopen (name, "a"); if (NULL == fp) { fprintf (stderr, "Couldn't open '%s' for appending: %s.\n", name, strerror (errno)); exit (EXIT_FAILURE); } } return fp; } int main(int argc,char **argv) { unsigned char buf[65536]; int arg; int args=0; int vbifd; fd_set rfds; int x; const char *device_file_name; const char *cc_file_name[8]; const char *xds_file_name; int verbose; int have_xds_filter_option; vbi_bool use_cc_filter; unsigned int i; unsigned int channels; #ifdef HAVE_ZVBI vbi_capture *cap; char *errstr; unsigned int services; int scanning; int strict; int ignore_read_error; vbi_raw_decoder *par; unsigned int src_w; unsigned int src_h; uint8_t *raw; vbi_sliced *sliced; struct timeval timeout; #endif my_name = argv[0]; setlocale (LC_ALL, ""); device_file_name = "/dev/vbi"; for (i = 0; i < 8; ++i) cc_file_name[i] = "-"; xds_file_name = "-"; verbose = 0; channels = 0; have_xds_filter_option = FALSE; use_cc_filter = FALSE; for (;;) { int c; c = getopt_long (argc, argv, short_options, long_options, &option_index); if (-1 == c) break; switch (c) { case '?': case 'h': usage (stdout); exit (EXIT_SUCCESS); case '1' ... '8': assert (NULL != optarg); cc_file_name[c - '1'] = optarg; channels |= 1 << (c - '1'); use_cc_filter = TRUE; usecc=1; break; case 'b': usewebtv=0; /* sic, compatibility */ break; case 'c': usecc=1; break; case 'd': assert (NULL != optarg); device_file_name = optarg; break; case 'f': usexds = TRUE; xds_filter_option (optarg); have_xds_filter_option = TRUE; break; case 'l': { long ch; assert (NULL != optarg); ch = strtol (optarg, NULL, 0); if (ch < 1 || ch > 8) { fprintf (stderr, "Invalid channel number %ld, " "should be 1 ... 8.\n", ch); exit (EXIT_FAILURE); } channels |= 1 << (ch - 1); use_cc_filter = TRUE; usecc=1; break; } case 'k': keyword[keywords++]=optarg; break; case 'p': plain=1; xds_info_prefix = "% "; xds_info_suffix = "\n"; break; case 'r': assert (NULL != optarg); useraw=1; rawline=atoi(optarg); break; case 's': usesen=1; break; case 't': test=1; break; case 'v': ++verbose; break; case 'w': debugwin=1; break; case 'x': usexds=1; break; case 'C': assert (NULL != optarg); for (i = 0; i < 8; ++i) cc_file_name[i] = optarg; usecc=1; break; case 'R': semirawdata=1; break; case 'X': assert (NULL != optarg); xds_file_name = optarg; break; default: usage (stderr); exit (EXIT_FAILURE); } } if (!(usecc | usexds | useraw)) { fprintf (stderr, "Give one of the -c, -x or -r options " "or -h for help.\n"); exit (EXIT_FAILURE); } if (usecc && 0 == channels) channels = 0x01; if (usexds && !have_xds_filter_option) xds_filter_option ("all"); #ifdef HAVE_ZVBI errstr = NULL; /* What we want. */ services = VBI_SLICED_CAPTION_525; /* This is a hint in case the device can't tell the current video standard. */ scanning = 525; /* Strict sampling parameter matching: 0, 1, 2 */ strict = 1; ignore_read_error = 1; do { if (test) { break; } /* Linux */ /* DVB interface omitted, doesn't support NTSC/ATSC. */ cap = vbi_capture_v4l2_new (device_file_name, /* buffers */ 5, &services, strict, &errstr, !!verbose); if (cap) { break; } fprintf (stderr, "Cannot capture vbi data with v4l2 interface:\n" "%s\nWill try v4l.\n", errstr); free (errstr); cap = vbi_capture_v4l_new (device_file_name, scanning, &services, strict, &errstr, !!verbose); if (cap) break; fprintf (stderr, "Cannot capture vbi data with v4l interface:\n" "%s\n", errstr); /* FreeBSD to do */ free (errstr); exit(EXIT_FAILURE); } while (0); if (test) { src_w = 1440; src_h = 50; } else { par = vbi_capture_parameters (cap); assert (NULL != par); src_w = par->bytes_per_line / 1; src_h = par->count[0] + par->count[1]; if (useraw && (unsigned int) rawline >= src_h) { fprintf (stderr, "-r must be in range 0 ... %u\n", src_h - 1); exit (EXIT_FAILURE); } } raw = calloc (1, src_w * src_h); sliced = malloc (sizeof (vbi_sliced) * src_h); assert (NULL != raw); assert (NULL != sliced); /* How long to wait for a frame. */ timeout.tv_sec = 2; timeout.tv_usec = 0; #else if ((vbifd = open(device_file_name, O_RDONLY)) < 0) { perror(vbifile); exit(1); } #endif if (usecc) { for (i = 0; i < 8; ++i) { if (channels & (1 << i)) cc_fp[i] = open_output_file (cc_file_name[i]); } } if (usexds) xds_fp = open_output_file (xds_file_name); for (x=0;x 2) fprintf (stderr, "No data in this frame\n"); for (i = 0; i < n_lines; ++i) { unsigned int c1, c2; c1 = sliced[i].data[0]; c2 = sliced[i].data[1]; if (verbose > 2) fprintf (stderr, "Line %3d %02x %02x\n", sliced[i].line, c1, c2); /* No need to check sliced[i].id because we requested only caption. */ if (21 == sliced[i].line) { field = 0; caption_filter (c1, c2); if (!in_xds[field]) { /* fields swapped? */ if (usecc) CCdecode(c1 + c2 * 256); if (usesen) sentence(c1 + c2 * 256); } if (usexds) /* fields swapped? */ XDSdecode(c1 + c2 * 256); } else if (284 == sliced[i].line) { field = 1; caption_filter (c1, c2); if (!in_xds[field]) { if (usecc) CCdecode(c1 + c2 * 256); if (usesen) sentence(c1 + c2 * 256); } if (usexds) XDSdecode(c1 + c2 * 256); } } #ifndef X_DISPLAY_MISSING if (debugwin) { XFlush(dpy); usleep(100); } #endif } #else /* !HAVE_ZVBI */ //mainloop while(1){ FD_ZERO(&rfds); FD_SET(vbifd, &rfds); select(FD_SETSIZE, &rfds, NULL, NULL, NULL); if (FD_ISSET(vbifd, &rfds)) { if (read(vbifd, buf , 65536)!=65536) printf("read error\n"); if (useraw) { #ifndef X_DISPLAY_MISSING if (debugwin) { XClearArea(dpy,Win,0,0,1024,128,0); XDrawLine(dpy,Win,WinGC1,0,128-85/2,1024,128-85/2); for (x=0;x<1024;x++) if (buf[2048 * rawline+x*2]/2<128 && buf[2048 * rawline+x*2+2]/2 < 128) XDrawLine(dpy,Win,WinGC0,x,128-buf[2048 * rawline+x*2]/2, x+1,128-buf[2048 * rawline+x*2+2]/2); } #endif print_raw(decode(&buf[2048 * rawline])); #ifndef X_DISPLAY_MISSING if (debugwin) { XFlush(dpy); usleep(100); } #endif } if (usexds) XDSdecode(decode(&buf[2048 * 27])); if (usecc) CCdecode(decode(&buf[2048 * 11])); if (usesen) sentence(decode(&buf[2048 * 11])); #ifndef X_DISPLAY_MISSING if (debugwin) { XFlush(dpy); usleep(100); } #endif } } #endif /* !HAVE_ZVBI */ return 0; }