#define _GNU_SOURCE 1
#undef NDEBUG
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <float.h>
#include <math.h>
#ifdef _WIN32
#define __STDC_WANT_LIB_EXT1__ 1
#endif
#include <time.h>
#include <locale.h>
#include <ctype.h>
#include <unistd.h>
#include <getopt.h>
#include <limits.h>
#include <errno.h>
#include <assert.h>
#include <libzvbi.h>
#ifdef _WIN32
#include "src/strptime.h"
#define timegm _mkgmtime
#endif
#ifndef N_ELEMENTS
# define N_ELEMENTS(array) (sizeof (array) / sizeof (*(array)))
#endif
#ifndef MIN
# define MIN(x, y) ((x) < (y) ? (x) : (y))
#endif
static vbi_capture * cap;
static const char * dev_name;
static vbi_bool quit;
static int exit_code;
static time_t audience_time;
static double timestamp;
struct lc_state {
double last_at;
};
enum vcr_state {
VCR_STATE_STBY,
VCR_STATE_SCAN,
VCR_STATE_PTR,
VCR_STATE_REC
};
static enum vcr_state vcr_state;
static double vcr_state_since;
static vbi_bool timer_control_mode;
static double delayed_stop_at;
struct program {
struct program * next;
unsigned int index;
time_t start_time;
time_t end_time;
time_t pil_valid_start;
time_t pil_valid_end;
vbi_bool continues;
};
static struct program * schedule;
static struct program * curr_program;
static vbi_bool test_mode;
static enum vcr_state test_exp_vcr_state;
static const double
};
static const double
};
#define D printf ("%s:%u\n", __FILE__, __LINE__)
static void
print_time (time_t time)
{
char buffer[80];
struct tm tm;
memset (&tm, 0, sizeof (tm));
#ifdef _WIN32
localtime_s (&tm, &time);
#else
localtime_r (&time, &tm);
#endif
strftime (buffer, sizeof (buffer),
"%Y-%m-%d %H:%M:%S %Z = ", &tm);
fputs (buffer, stdout);
memset (&tm, 0, sizeof (tm));
#ifdef _WIN32
gmtime_s (&tm, &time);
#else
gmtime_r (&time, &tm);
#endif
strftime (buffer, sizeof (buffer),
"%Y-%m-%d %H:%M:%S UTC", &tm);
puts (buffer);
}
static const char *
{
static char buffer[32];
switch (pil) {
return "NSPV/END";
default:
snprintf (buffer, sizeof (buffer),
"%02u%02uT%02u%02u",
return buffer;
}
}
static void
msg (const char * templ,
...)
{
va_list ap;
va_start (ap, templ);
if (test_mode) {
char buffer[80];
struct tm tm;
memset (&tm, 0, sizeof (tm));
#ifdef _WIN32
localtime_s (&tm, &audience_time);
#else
localtime_r (&audience_time, &tm);
#endif
strftime (buffer, sizeof (buffer), "%Y%m%dT%H%M%S ", &tm);
fputs (buffer, stdout);
}
vprintf (templ, ap);
va_end (ap);
}
static void
remove_program_from_schedule (struct program * p)
{
struct program **pp;
if (p == curr_program) {
assert (quit
|| VCR_STATE_STBY == vcr_state
|| VCR_STATE_SCAN == vcr_state);
curr_program = NULL;
}
for (pp = &schedule; NULL != *pp; pp = &(*pp)->next) {
if (*pp == p) {
*pp = p->next;
free (p);
break;
}
}
}
static void
remove_stale_programs_from_schedule (void)
{
struct program *p;
struct program *p_next;
for (p = schedule; NULL != p; p = p_next) {
p_next = p->next;
if (audience_time >= p->end_time
&& audience_time >= p->pil_valid_end) {
msg ("PIL %s no longer valid, "
"removing program %u from schedule.\n",
pil_str (p->pil), p->index);
remove_program_from_schedule (p);
}
}
}
static struct program *
{
struct program *p;
for (p = schedule; NULL != p; p = p->next) {
if (pil == p->pil)
return p;
}
return NULL;
}
static const char *
vcr_state_name (enum vcr_state state)
{
switch (state) {
#define CASE(x) case VCR_STATE_ ## x: return #x;
CASE (STBY)
CASE (SCAN)
CASE (PTR)
CASE (REC)
#undef CASE
}
assert (0);
}
static void
change_vcr_state (enum vcr_state new_state)
{
if (new_state == vcr_state)
return;
msg ("VCR state %s -> %s.\n",
vcr_state_name (vcr_state),
vcr_state_name (new_state));
vcr_state = new_state;
vcr_state_since = timestamp;
}
static vbi_bool
teletext_8302_available (void)
{
}
static void
disable_timer_control (void)
{
if (!timer_control_mode)
return;
msg ("Leaving timer control mode.\n");
timer_control_mode = FALSE;
}
static void
enable_timer_control (void)
{
if (timer_control_mode)
return;
msg ("Entering timer control mode.\n");
timer_control_mode = TRUE;
}
static void
stop_recording_now (void)
{
assert (VCR_STATE_REC == vcr_state);
msg ("Program %u ended according to %s%s.\n",
curr_program->index,
timer_control_mode ? "schedule" : "VPS/PDC signal",
(delayed_stop_at < DBL_MAX) ? " with delay" : "");
change_vcr_state (VCR_STATE_SCAN);
delayed_stop_at = DBL_MAX;
}
static void
{
assert (VCR_STATE_REC == vcr_state);
if (NULL == pid) {
memset (&delayed_stop_pid, 0,
sizeof (delayed_stop_pid));
} else {
delayed_stop_pid = *pid;
}
if (NULL == pid && 0 != curr_pid.
pil) {
delayed_stop_at = lc_state[curr_pid.
channel].last_at + 31;
} else {
delayed_stop_at = timestamp + 30;
}
msg ("Will stop recording in %d seconds.\n",
(int)(delayed_stop_at - timestamp));
}
static void
start_recording_by_pil (struct program * p,
{
assert (!timer_control_mode);
assert (VCR_STATE_SCAN == vcr_state
|| VCR_STATE_PTR == vcr_state);
msg ("Recording program %u using VPS/PDC signal.\n",
p->index);
if (!p->continues) {
p->end_time += audience_time - p->start_time;
p->start_time = audience_time;
p->continues = TRUE;
}
change_vcr_state (VCR_STATE_REC);
curr_program = p;
curr_pid = *pid;
}
static void
prepare_to_record_by_pil (struct program * p,
{
assert (!timer_control_mode);
assert (VCR_STATE_SCAN == vcr_state);
change_vcr_state (VCR_STATE_PTR);
curr_program = p;
curr_pid = *pid;
}
static void
start_recording_by_timer (struct program * p)
{
assert (timer_control_mode);
assert (VCR_STATE_SCAN == vcr_state);
msg ("Recording program %u using timer.\n",
p->index);
change_vcr_state (VCR_STATE_REC);
curr_program = p;
memset (&curr_pid, 0, sizeof (curr_pid));
}
static void
remove_program_if_ended (struct program * p,
{
if (timer_control_mode) {
return;
return;
}
else if (NULL != pid && pid->
luf) {
}
remove_program_from_schedule (p);
}
static void
signal_or_service_lost (void)
{
struct program *p;
if (timer_control_mode)
return;
enable_timer_control ();
switch (vcr_state) {
case VCR_STATE_STBY:
assert (0);
case VCR_STATE_SCAN:
break;
case VCR_STATE_PTR:
p = curr_program;
msg ("Recording program %u using lost "
"PDC signal with PRF=1.\n",
p->index);
p->end_time = p->end_time - p->start_time + audience_time;
p->end_time += 60 - MIN (vcr_state_since - timestamp,
60.0);
p->start_time = audience_time;
change_vcr_state (VCR_STATE_REC);
memset (&curr_pid, 0, sizeof (curr_pid));
break;
case VCR_STATE_REC:
if (delayed_stop_at < DBL_MAX) {
msg ("PDC signal lost; already stopping in "
"%d seconds.\n",
(int)(delayed_stop_at - timestamp));
} else if (curr_program->start_time
== curr_program->end_time) {
stop_recording_in_30s ( NULL);
} else {
memset (&curr_pid, 0, sizeof (curr_pid));
}
break;
}
}
static void
{
vbi_bool mi;
switch (vcr_state) {
case VCR_STATE_STBY:
case VCR_STATE_SCAN:
assert (0);
case VCR_STATE_PTR:
assert (!timer_control_mode);
msg ("PIL %s is no longer present on LC %u.\n",
pil_str (curr_program->pil),
change_vcr_state (VCR_STATE_SCAN);
return;
case VCR_STATE_REC:
assert (!timer_control_mode);
msg ("PIL %s is no longer present on LC %u.\n",
pil_str (curr_program->pil),
if (delayed_stop_at < DBL_MAX) {
msg ("Already stopping in %d seconds.\n",
(int)(delayed_stop_at - timestamp));
return;
}
break;
}
if (NULL != pid
} else {
mi = TRUE;
} else {
}
}
if (mi) {
stop_recording_now ();
remove_program_if_ended (curr_program, pid);
} else {
stop_recording_in_30s (pid);
}
}
static void
{
switch (vcr_state) {
case VCR_STATE_STBY:
assert (0);
case VCR_STATE_SCAN:
disable_timer_control ();
return;
case VCR_STATE_PTR:
assert (!timer_control_mode);
msg ("Ignore %s/%02X with different LCI.\n",
pil_str (pid->
pil), pid->
pty);
return;
}
break;
case VCR_STATE_REC:
if (timer_control_mode) {
return;
msg ("Ignore %s/%02X with different LCI.\n",
pil_str (pid->
pil), pid->
pty);
return;
}
break;
}
pil_no_longer_transmitted (pid);
}
static void
{
struct program *p;
switch (vcr_state) {
case VCR_STATE_STBY:
assert (0);
case VCR_STATE_SCAN:
disable_timer_control ();
return;
p = find_program_by_pil (pid->
pil);
break;
case VCR_STATE_PTR:
assert (!timer_control_mode);
msg ("Ignore %s/%02X with different LCI.\n",
pil_str (pid->
pil), pid->
pty);
return;
pil_no_longer_transmitted (pid);
return;
}
else if (pid->
pil != curr_pid.
pil) {
pil_no_longer_transmitted (pid);
p = find_program_by_pil (pid->
pil);
break;
if (timestamp >= vcr_state_since + 60) {
msg ("Overriding stuck PRF flag.\n");
} else {
msg ("Already prepared to record.\n");
return;
}
}
start_recording_by_pil (curr_program, pid);
return;
case VCR_STATE_REC:
if (timer_control_mode) {
return;
}
p = find_program_by_pil (pid->
pil);
if (p == curr_program) {
disable_timer_control ();
msg ("Continue recording using "
"VPS/PDC signal.\n");
curr_pid = *pid;
delayed_stop_at = DBL_MAX;
return;
} else if (NULL == p) {
stop_recording_in_30s ( NULL);
return;
} else {
disable_timer_control ();
stop_recording_now ();
}
msg ("Ignore %s/%02X with different LCI.\n",
pil_str (pid->
pil), pid->
pty);
return;
pil_no_longer_transmitted (pid);
return;
}
else if (pid->
pil == curr_pid.
pil) {
if (delayed_stop_at < DBL_MAX) {
delayed_stop_at = DBL_MAX;
msg ("Delayed stop canceled.\n");
return;
} else {
msg ("Already recording.\n");
return;
}
} else {
pil_no_longer_transmitted (pid);
if (VCR_STATE_SCAN != vcr_state) {
return;
}
p = find_program_by_pil (pid->
pil);
}
break;
}
assert (VCR_STATE_SCAN == vcr_state);
if (NULL == p)
return;
prepare_to_record_by_pil (p, pid);
} else {
start_recording_by_pil (p, pid);
}
}
static void
void * user_data)
{
user_data = user_data;
assert (VCR_STATE_STBY != vcr_state);
switch (lci) {
break;
if (teletext_8302_available ())
goto finish;
break;
default:
return;
}
msg ("Received PIL %s/%02X on LC %u.\n",
pil_str (pid->
pil), pid->
pty, lci);
signal_or_service_lost ();
break;
received_int_rit (pid);
break;
default:
received_pil (pid);
break;
}
finish:
lc_state[lci].pil = pid->
pil;
lc_state[lci].last_at = timestamp;
}
static vbi_bool
in_pil_validity_window (void)
{
struct program *p;
for (p = schedule; NULL != p; p = p->next) {
if ((audience_time >= p->start_time
&& audience_time < p->end_time)
|| (audience_time >= p->pil_valid_start
&& audience_time < p->pil_valid_end))
return TRUE;
}
return FALSE;
}
static void
timer_control (void)
{
struct program *p;
assert (timer_control_mode);
switch (vcr_state) {
case VCR_STATE_STBY:
case VCR_STATE_PTR:
assert (0);
case VCR_STATE_SCAN:
break;
case VCR_STATE_REC:
if (delayed_stop_at < DBL_MAX) {
return;
} else if (audience_time >= curr_program->end_time) {
stop_recording_now ();
remove_program_from_schedule (curr_program);
} else {
return;
}
assert (VCR_STATE_SCAN == vcr_state);
break;
}
for (p = schedule; NULL != p; p = p->next) {
if (audience_time >= p->start_time
&& audience_time < p->end_time) {
start_recording_by_timer (p);
return;
}
}
}
static void
pdc_signal_check (void)
{
static const unsigned int ttx_chs =
static const unsigned int vps_ch =
unsigned int active_chs;
unsigned int lost_chs;
if (timer_control_mode)
return;
active_chs = 0;
lost_chs = 0;
double timeout_at;
if (0 == lc_state[i].pil)
continue;
timeout_at = lc_state[i].last_at + signal_timeout[i];
if (timestamp >= timeout_at) {
lost_chs |= 1 << i;
} else {
active_chs |= 1 << i;
}
}
if (0 == active_chs) {
if (0 != lost_chs) {
msg ("All Teletext and VPS signals lost, "
"will fall back to timer control.\n");
signal_or_service_lost ();
}
} else {
if (vps_ch == active_chs
&& 0 != (lost_chs & ttx_chs)) {
msg ("Teletext signal lost, "
"will fall back to VPS.\n");
}
}
if ((VCR_STATE_PTR == vcr_state
|| VCR_STATE_REC == vcr_state)
&& 0 != (lost_chs & (1 << curr_pid.
channel))) {
pil_no_longer_transmitted ( NULL);
}
}
if (0 != lost_chs) {
if (0 == (lost_chs & (1 << i)))
continue;
lc_state[i].pil = 0;
lc_state[i].last_at = timestamp;
}
}
}
static void
parse_test_file_line (time_t * timestamp,
enum vcr_state * exp_state,
unsigned int line_counter,
const char * test_file_line)
{
struct tm tm;
const char *s;
char *s_end;
const char *detail;
unsigned long ul;
s = test_file_line;
while (isalnum (*s))
++s;
detail = "channel field";
if (!isspace (*s))
goto invalid;
memset (&tm, 0, sizeof (tm));
tm.tm_isdst = -1;
s = strptime (s, "%Y%m%dT%H%M%S", &tm);
detail = "date field";
if (NULL == s)
goto invalid;
while (isspace (*s))
++s;
if ('Z' == *s) {
++s;
*timestamp = timegm (&tm);
} else {
*timestamp = mktime (&tm);
}
if ((time_t) -1 == *timestamp)
goto invalid;
memset (pid, 0, sizeof (*pid));
while (isspace (*s))
++s;
if (0 == strncmp (s, "VPS", 3)) {
s += 3;
} else {
ul = strtoul (s, &s_end, 0);
detail = "LCI field";
if (s_end == s
goto invalid;
s = s_end;
}
while (isspace (*s))
++s;
if (!isdigit (*s)) {
} else {
ul = strtoul (s, &s_end, 0);
detail = "LUF field";
if (s_end == s || ul > 1)
goto invalid;
s = s_end;
while (isspace (*s))
++s;
if ('x' == *s) {
++s;
} else {
ul = strtoul (s, &s_end, 0);
detail = "MI field";
if (s_end == s || ul > 1)
goto invalid;
s = s_end;
}
while (isspace (*s))
++s;
if ('x' == *s) {
++s;
} else {
ul = strtoul (s, &s_end, 0);
detail = "PRF field";
if (s_end == s || ul > 1)
goto invalid;
s = s_end;
}
while (isspace (*s))
++s;
if (0 == strncmp (s, "CONT", 4)) {
s += 4;
} else if (0 == strncmp (s, "END", 3)) {
s += 3;
} else if (0 == strncmp (s, "INT", 3)) {
s += 3;
} else if (0 == strncmp (s, "NSPV", 4)) {
s += 4;
} else if (0 == strncmp (s, "RI/T", 4)) {
s += 4;
} else if (0 == strncmp (s, "TC", 2)) {
s += 2;
} else {
ul = strtoul (s, &s_end, 10);
detail = "PIL field";
if (s_end == s
|| ul % 100 > 31
|| ul > 1531)
goto invalid;
s = s_end;
if (ul > 0) {
ul % 100, 0, 0);
if ('T' != *s++)
goto invalid;
ul = strtoul (s, &s_end, 10);
if (s_end == s
|| ul % 100 > 63
|| ul > 3163)
goto invalid;
s = s_end;
ul / 100,
ul % 100);
}
}
if ('/' == *s) {
do ++s;
while (isspace (*s));
if (isalpha (s[0]) && 0x20 == s[1]) {
} else {
ul = strtoul (s, &s_end, 16);
detail = "PTY field";
if (s_end == s || ul > 0xFF)
goto invalid;
s = s_end;
}
} else {
}
while (isspace (*s))
++s;
while (isalnum (*s))
++s;
detail = "CNI field";
if (0 != *s && !isspace (*s))
goto invalid;
} else {
}
}
while (isspace (*s))
++s;
if ('#' == *s || 0 == *s) {
*exp_state = -1;
return;
} else if (0 == strncmp (s, "PTR", 3)) {
*exp_state = VCR_STATE_PTR;
s += 3;
} else if (0 == strncmp (s, "REC", 3)) {
*exp_state = VCR_STATE_REC;
s += 3;
} else if (0 == strncmp (s, "SCAN", 4)) {
*exp_state = VCR_STATE_SCAN;
s += 4;
} else if (0 == strncmp (s, "STBY", 4)) {
*exp_state = VCR_STATE_STBY;
s += 4;
} else {
detail = "VCR state field";
goto invalid;
}
while (isspace (*s))
++s;
if ('#' == *s || 0 == *s)
return;
detail = "garbage at end of line";
invalid:
fprintf (stderr, "Error in test file line %u, %s:\n%s\n",
line_counter, detail, test_file_line);
exit (EXIT_FAILURE);
}
static void
simulate_signals (void)
{
static char buffer[256];
static time_t next_event_time = 0;
static enum vcr_state next_exp_vcr_state = (enum vcr_state) -1;
static unsigned int line_counter;
while (timestamp >= next_event_time) {
if (0 != buffer[0]) {
printf ("> %s", buffer);
test_pid[next_pid.
channel] = next_pid;
if ((enum vcr_state) -1 == next_exp_vcr_state)
test_exp_vcr_state = test_exp_vcr_state;
else
test_exp_vcr_state = next_exp_vcr_state;
}
for (;;) {
const char *s;
if (NULL == fgets (buffer, sizeof (buffer),
stdin)) {
printf ("End of test file.\n");
next_event_time = INT_MAX;
quit = TRUE;
break;
}
s = buffer;
while (isspace (*s))
++s;
if (0 == *s)
continue;
if ('#' == *s) {
printf ("> %s", s);
continue;
}
parse_test_file_line (&next_event_time,
&next_pid,
&next_exp_vcr_state,
line_counter, s);
++line_counter;
break;
}
}
audience_time = (time_t) timestamp;
if (VCR_STATE_REC == vcr_state
&& timestamp >= delayed_stop_at) {
stop_recording_now ();
assert (VCR_STATE_SCAN == vcr_state);
remove_program_if_ended (curr_program,
&delayed_stop_pid);
}
if (0 != test_pid[i].pil) {
memset (&ev, 0, sizeof (ev));
ev.ev.prog_id = &test_pid[i];
}
}
}
static void
capture_and_decode_frame (void)
{
struct timeval timeout;
vbi_capture_buffer *sliced_buffer;
unsigned int n_lines;
int r;
timeout.tv_sec = 2;
timeout.tv_usec = 0;
r = vbi_capture_pull (cap,
NULL,
&sliced_buffer,
&timeout);
switch (r) {
case -1:
fprintf (stderr,
"VBI read error: %s.\n",
strerror (errno));
exit (EXIT_FAILURE);
case 0:
fprintf (stderr, "VBI read timeout\n");
exit (EXIT_FAILURE);
case 1:
break;
default:
assert (0);
}
timestamp = sliced_buffer->timestamp;
n_lines = sliced_buffer->size /
sizeof (
vbi_sliced);
audience_time = (time_t) timestamp;
if (VCR_STATE_REC == vcr_state
&& timestamp >= delayed_stop_at) {
stop_recording_now ();
assert (VCR_STATE_SCAN == vcr_state);
remove_program_if_ended (curr_program,
&delayed_stop_pid);
}
n_lines, timestamp);
}
static void
close_vbi_device (void)
{
vbi_capture_delete (cap);
cap = NULL;
}
static void
open_vbi_device (void)
{
vbi_service_set services;
char *errstr;
services = (VBI_SLICED_TELETEXT_B |
VBI_SLICED_VPS);
cap = vbi_capture_v4l2_new (dev_name,
5,
&services,
0,
&errstr,
FALSE);
if (NULL == cap) {
fprintf (stderr,
"Cannot capture VBI data from %s "
"with V4L2 interface:\n"
"%s\n",
dev_name, errstr);
free (errstr);
exit (EXIT_FAILURE);
}
}
static void
capture_loop (void)
{
double last_timestamp;
assert (VCR_STATE_STBY == vcr_state);
if (!test_mode)
open_vbi_device ();
change_vcr_state (VCR_STATE_SCAN);
last_timestamp = 0;
while (VCR_STATE_STBY != vcr_state && !quit) {
if (test_mode) {
simulate_signals ();
} else {
capture_and_decode_frame ();
}
if ((long) last_timestamp != (long) timestamp) {
if (!timer_control_mode) {
pdc_signal_check ();
}
if (timer_control_mode)
timer_control ();
}
last_timestamp = timestamp;
if (VCR_STATE_SCAN == vcr_state
&& !in_pil_validity_window ()) {
change_vcr_state (VCR_STATE_STBY);
}
if (test_mode) {
if ((enum vcr_state) -1 != test_exp_vcr_state
&& test_exp_vcr_state != vcr_state) {
printf ("*** Unexpected VCR state %s\n",
vcr_state_name (vcr_state));
exit_code = EXIT_FAILURE;
}
++timestamp;
}
}
if (!test_mode)
close_vbi_device ();
}
static void
standby_loop (void)
{
while (!quit) {
struct program *p;
time_t first_scan;
assert (VCR_STATE_STBY == vcr_state);
if (test_mode) {
audience_time = (time_t) timestamp;
} else {
audience_time = time (NULL);
}
remove_stale_programs_from_schedule ();
if (NULL == schedule) {
printf ("Recording schedule is empty.\n");
break;
}
first_scan = schedule->start_time;
for (p = schedule; NULL != p; p = p->next) {
if (p->start_time < first_scan)
first_scan = p->start_time;
if (p->pil_valid_start < first_scan)
first_scan = p->pil_valid_start;
}
while (first_scan > audience_time) {
char buffer[80];
struct tm tm;
memset (&tm, 0, sizeof (tm));
#ifdef _WIN32
localtime_s (&tm, &first_scan);
#else
localtime_r (&first_scan, &tm);
#endif
strftime (buffer, sizeof (buffer),
"%Y-%m-%d %H:%M:%S %Z", &tm);
msg ("Sleeping until %s.\n", buffer);
if (test_mode) {
audience_time = first_scan;
timestamp = first_scan;
} else {
sleep (first_scan - audience_time);
audience_time = time (NULL);
}
}
capture_loop ();
}
}
static void
reset_state (void)
{
unsigned int i;
audience_time = 0.0;
timestamp = 0.0;
lc_state[i].pil = 0;
lc_state[i].last_at = 0.0;
}
vcr_state = VCR_STATE_STBY;
vcr_state_since = 0.0;
timer_control_mode = FALSE;
delayed_stop_at = DBL_MAX;
test_exp_vcr_state = (enum vcr_state) -1;
}
static void
add_program_to_schedule (const struct tm * start_tm,
const struct tm * end_tm,
const struct tm * pdc_tm)
{
struct program *p;
struct program **pp;
struct tm tm;
time_t pil_time;
p = calloc (1, sizeof (*p));
assert (NULL != p);
tm = *start_tm;
tm.tm_isdst = -1;
p->start_time = mktime (&tm);
if ((time_t) -1 == p->start_time) {
fprintf (stderr, "Invalid start time.\n");
exit (EXIT_FAILURE);
}
tm = *start_tm;
tm.tm_isdst = -1;
tm.tm_hour = end_tm->tm_hour;
tm.tm_min = end_tm->tm_min;
if (end_tm->tm_hour < start_tm->tm_hour) {
++tm.tm_mday;
}
p->end_time = mktime (&tm);
if ((time_t) -1 == p->end_time) {
fprintf (stderr, "Invalid end time.\n");
exit (EXIT_FAILURE);
}
tm = *start_tm;
tm.tm_isdst = -1;
tm.tm_hour = pdc_tm->tm_hour;
tm.tm_min = pdc_tm->tm_min;
if (pdc_tm->tm_hour >= start_tm->tm_hour + 12) {
--tm.tm_mday;
} else if (pdc_tm->tm_hour + 12 < start_tm->tm_hour) {
++tm.tm_mday;
}
pil_time = mktime (&tm);
#ifdef _WIN32
if ((time_t) -1 == pil_time
|| 0 != localtime_s (&tm, &pil_time)) {
#else
if ((time_t) -1 == pil_time
|| NULL == localtime_r (&pil_time, &tm)) {
#endif
fprintf (stderr, "Cannot determine PIL month/day.\n");
exit (EXIT_FAILURE);
}
tm.tm_mday,
tm.tm_hour,
tm.tm_min);
&p->pil_valid_end,
p->pil,
p->start_time,
NULL )) {
fprintf (stderr, "Cannot determine PIL validity.\n");
exit (EXIT_FAILURE);
}
p->index = 0;
for (pp = &schedule; NULL != *pp; pp = &(*pp)->next)
++p->index;
*pp = p;
if (0) {
printf ("Program %u start: ", p->index);
print_time (p->start_time);
printf ("End: ");
print_time (p->end_time);
printf ("PIL: ");
print_time (pil_time);
printf ("PIL valid from: ");
print_time (p->pil_valid_start);
printf ("PIL valid until: ");
print_time (p->pil_valid_end);
}
}
static void
usage (FILE * fp)
{
fprintf (fp,
"Please specify the start time of a program in the format\n"
"YYYY-MM-DD HH:MM, the end time HH:MM and a VPS/PDC time HH:MM.\n");
}
static void
parse_args (int argc,
char ** argv)
{
struct tm start_tm;
struct tm end_tm;
struct tm pdc_tm;
dev_name = "/dev/vbi";
for (;;) {
int c;
c = getopt (argc, argv, "d:ht");
if (-1 == c)
break;
switch (c) {
case 'd':
dev_name = optarg;
break;
case 'h':
usage (stdout);
exit (EXIT_SUCCESS);
case 't':
test_mode = TRUE;
break;
default:
usage (stderr);
exit (EXIT_FAILURE);
}
}
while (argc - optind >= 4) {
memset (&start_tm, 0, sizeof (struct tm));
if (NULL == strptime (argv[optind + 0], "%Y-%m-%d",
&start_tm))
goto invalid;
if (NULL == strptime (argv[optind + 1], "%H:%M",
&start_tm))
goto invalid;
memset (&end_tm, 0, sizeof (struct tm));
if (NULL == strptime (argv[optind + 2], "%H:%M",
&end_tm))
goto invalid;
memset (&pdc_tm, 0, sizeof (struct tm));
if (NULL == strptime (argv[optind + 3], "%H:%M",
&pdc_tm))
goto invalid;
add_program_to_schedule (&start_tm, &end_tm, &pdc_tm);
optind += 4;
}
if (argc != optind)
goto invalid;
return;
invalid:
usage (stderr);
exit (EXIT_FAILURE);
}
int
main (int argc,
char ** argv)
{
vbi_bool success;
setlocale (LC_ALL, "");
parse_args (argc, argv);
exit_code = EXIT_SUCCESS;
assert (NULL != dec);
NULL);
assert (success);
reset_state ();
standby_loop ();
while (NULL != schedule)
remove_program_from_schedule (schedule);
exit (exit_code);
}
vbi_bool vbi_event_handler_register(vbi_decoder *vbi, int event_mask, vbi_event_handler handler, void *user_data)
Definition vbi.c:278
#define VBI_PIL_DAY(pil)
Definition pdc.h:79
vbi_pid_channel
Sources of PIDs.
Definition pdc.h:246
#define VBI_PIL(month, day, hour, minute)
Macro to create a PIL.
Definition pdc.h:72
#define VBI_PIL_HOUR(pil)
Definition pdc.h:82
unsigned int vbi_pil
Program Identification Label.
Definition pdc.h:57
#define VBI_PIL_MINUTE(pil)
Definition pdc.h:85
#define VBI_PIL_MONTH(pil)
Definition pdc.h:76
vbi_bool vbi_pil_validity_window(time_t *begin, time_t *end, vbi_pil pil, time_t start, const char *tz)
Definition pdc.c:1412
@ VBI_PID_CHANNEL_VPS
Definition pdc.h:283
@ VBI_MAX_PID_CHANNELS
Definition pdc.h:313
@ VBI_PID_CHANNEL_LCI_0
Definition pdc.h:266
@ VBI_PID_CHANNEL_LCI_3
Definition pdc.h:275
@ VBI_PID_CHANNEL_LCI_2
Definition pdc.h:272
@ VBI_PID_CHANNEL_LCI_1
Definition pdc.h:269
@ VBI_PIL_CONTINUE
Definition pdc.h:145
@ VBI_PIL_END
Definition pdc.h:162
@ VBI_PIL_INTERRUPTION
Definition pdc.h:135
@ VBI_PIL_INHIBIT_TERMINATE
Definition pdc.h:120
@ VBI_PIL_NSPV
Definition pdc.h:154
@ VBI_PIL_TIMER_CONTROL
Definition pdc.h:108
vbi_decoder * vbi_decoder_new(void)
Allocate a new data service decoder instance.
Definition vbi.c:870
void vbi_decode(vbi_decoder *vbi, vbi_sliced *sliced, int lines, double time)
Main function of the data service decoder.
Definition vbi.c:423
void vbi_channel_switched(vbi_decoder *vbi, vbi_nuid nuid)
Definition vbi.c:580
void vbi_decoder_delete(vbi_decoder *vbi)
Delete a data service decoder instance.
Definition vbi.c:832
Event union.
Definition event.h:736
Program Identification.
Definition pdc.h:351
unsigned int pty
Definition pdc.h:421
vbi_pil pil
Definition pdc.h:375
vbi_bool prf
Definition pdc.h:412
vbi_cni_type cni_type
Definition pdc.h:361
vbi_pid_channel channel
Definition pdc.h:353
vbi_bool mi
Definition pdc.h:404
unsigned int cni
Definition pdc.h:369
vbi_bool luf
Definition pdc.h:393
This structure holds one scan line of sliced vbi data.
Definition sliced.h:320