/* 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 <mschimek@users.sf.net>
*
* 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 <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#include <locale.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/time.h>
#ifdef HAVE_GETOPT_LONG
# include <getopt.h>
#endif
#define HAVE_ZVBI 1
#include <assert.h>
#include "src/libzvbi.h"
#ifndef X_DISPLAY_MISSING
# include <X11/X.h>
# include <X11/Xlib.h>
# include <X11/Xutil.h>
# include <X11/Xproto.h>
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<<j))
k++;
}
if ((k & 1) == ((n>>7)&1))
mask|=0x00FF;
for (k = 1, j = 8; j < 15; j++) {
if (n & (1<<j))
k++;
}
if ((k & 1) == ((n>>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]<sample;i++);
#ifndef X_DISPLAY_MISSING
if (debugwin) {
for (clk=i;clk<i+57*18;clk+=57)
XDrawLine(dpy,Win,WinGC,clk/2,0,clk/2,128);
XFlush(dpy);
}
#endif
tmp = i+57;
for (i = 0; i < 16; i++)
if(decodebit(&vbiline[tmp + i * 57], sample))
packedbits |= 1<<i;
return packedbits&parityok(packedbits);
} /* decode */
static void
print_xds_info (unsigned int mode,
unsigned int type)
{
const char *infoptr;
if (!info[0][mode][type].print)
return;
infoptr = info[field][mode][type].packet;
switch ((mode << 8) + type) {
case 0x0101:
fprintf (xds_fp,
"%sTIMECODE: %d/%02d %d:%02d%s",
xds_info_prefix,
infoptr[3]&0x0f,infoptr[2]&0x1f,
infoptr[1]&0x1f,infoptr[0]&0x3f,
xds_info_suffix);
case 0x0102:
if ((infoptr[1]&0x3f)>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<length;y++)
printf(" %03d",newinfo[field][0][0][y]);
printf(" --- %s\n",newinfo[field][0][0]);
}
#endif
if (mode == 0) return 0;
if (b2 != 128-((infochecksum%128)&0x7F)) return 0;
length = infoptr - newinfo[field][mode][type];
//don't bug the user with repeated data
//only parse it if it's different
if (info[field][mode][type].length != length
|| 0 != memcmp (info[field][mode][type].packet,
newinfo[field][mode][type],
length))
{
memcpy (info[field][mode][type].packet,
newinfo[field][mode][type], 32);
info[field][mode][type].packet[length] = 0;
info[field][mode][type].length = length;
if (0)
fprintf (stderr, "XDS %d %d %d %d %d\n",
field, mode, type, length,
info[0][mode][type].print);
print_xds_info (mode, type);
}
mode = 0; type = 0;
in_xds[field] = FALSE;
} else if (b1 <= 31) {
/* Caption control code. */
in_xds[field] = FALSE;
} else if (in_xds[field]) {
if (infoptr >= &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]], "<ATTRIBUTE %d %d>\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<strlen(ccbuf[cur_ch[field]][ccmode]);n++)
for (y=0;y<keywords;y++)
if (!strncasecmp(keyword[y], ccbuf[cur_ch[field]][ccmode]+n, strlen(keyword[y])))
if (cc_fp[cur_ch[field]])
fprintf (cc_fp[cur_ch[field]], "\a");
append_control_seq ("\33[m");
append_char ('\n', '\n');
if (cc_fp[cur_ch[field]]) {
vbi_fputs_iconv_ucs2 (cc_fp[cur_ch[field]],
vbi_locale_codeset (),
cc_ubuf[cur_ch[field]][ccmode],
VBI_NUL_TERMINATED,
/* repl_char */ '?');
fflush (cc_fp[cur_ch[field]]);
}
/* FALL */
case 0x2A: //text restart
case 0x2E: //erase non-displayed memory
CLEAR (ccbuf[cur_ch[field]][ccmode]);
CLEAR (cc_ubuf[cur_ch[field]][ccmode]);
break;
}
break;
case 0x07: //misc (TAB)
for(x=0;x<(b2&0x03);x++) {
append_char (' ', ' ');
}
break;
}
}
}
lastcode=data;
return 0;
}
static int print_raw(int data)
{
int b1, b2;
if (data == -1)
return -1;
// this is just null data with two parity bits
// 100000010000000 = 0x8080
if (data == 0x8080)
return -1;
b1 = data & 0x7f;
b2 = (data>>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\
<mschimek@users.sf.net>; 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<keywords;x++)
printf("Keyword(%d): %s\n",x,keyword[x]);
#ifndef X_DISPLAY_MISSING
if (debugwin) {
dpy=XOpenDisplay(dpyname);
Root=DefaultRootWindow(dpy);
Win = XCreateSimpleWindow(dpy, Root, 10, 10, 1024, 128,0,0,0);
WinGC = XCreateGC(dpy, Win, 0, NULL);
WinGC0 = XCreateGC(dpy, Win, 0, NULL);
WinGC1 = XCreateGC(dpy, Win, 0, NULL);
XSetForeground(dpy, WinGC, getColor("blue",1));
XSetForeground(dpy, WinGC0, getColor("green",1));
XSetForeground(dpy, WinGC1, getColor("red",1));
if (useraw)
XMapWindow(dpy, Win);
}
#endif
#ifdef HAVE_ZVBI
for (;;) {
double timestamp;
int n_lines;
int r;
int i;
if (test) {
r = read_test_stream (sliced, &n_lines, src_h);
} else {
r = vbi_capture_read (cap, raw, sliced,
&n_lines, ×tamp, &timeout);
}
switch (r) {
case -1:
fprintf (stderr, "VBI read error: %d, %s%s\n",
errno, strerror (errno),
ignore_read_error ? " (ignored)" : "");
if (ignore_read_error) {
/* Avoid idle loop. */
usleep (250000);
continue;
} else {
exit (EXIT_FAILURE);
}
case 0:
fprintf (stderr, "VBI read timeout%s\n",
ignore_read_error ? " (ignored)" : "");
if (ignore_read_error) {
/* Avoid idle loop. */
usleep (250000);
continue;
} else {
exit (EXIT_FAILURE);
}
case 1: /* ok */
break;
default:
assert (0);
}
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<src_w/2;x++)
if (raw[src_w * rawline+x*2]/2<128 && raw[src_w * rawline+x*2+2]/2 < 128)
XDrawLine(dpy,Win,WinGC0,x,128-raw[src_w * rawline+x*2]/2,
x+1,128-raw[src_w * rawline+x*2+2]/2);
}
#endif
for (i = 0; i < n_lines; ++i) {
if (sliced[i].line == rawline) {
print_raw(sliced[i].data[0]
+ sliced[i].data[1] * 256);
}
}
#ifndef X_DISPLAY_MISSING
if (debugwin) {
XFlush(dpy);
usleep(100);
}
#endif
}
if (0 == n_lines && verbose > 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;
}
syntax highlighted by Code2HTML, v. 0.9.1