You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

510 lines
16 KiB
C

/*
* framecode.c -- framecode list handling
* Written by Andrew Church <achurch@achurch.org>
*
* This file is part of transcode, a video stream processing tool.
* transcode is free software, distributable under the terms of the GNU
* General Public License (version 2 or later). See the file COPYING
* for details.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <limits.h> /* for UINT_MAX and ULONG_MAX */
#include <math.h>
#include "libtc.h"
#include "framecode.h"
/* Internal function prototypes: */
static void normalize_fc_time(struct fc_time *range);
static struct fc_time *parse_one_range(const char *string, double fps,
const char **errmsg_ret,
int *errpos_ret);
static int parse_one_time(const char **strptr, unsigned int *hour_ret,
unsigned int *min_ret, unsigned int *sec_ret,
unsigned int *frame_ret, const char **errmsg_ret);
static int parse_one_value(const char **strptr, unsigned int *value_ret,
const char **errmsg_ret);
/*************************************************************************/
/************************** External interface ***************************/
/*************************************************************************/
/**
* new_fc_time: Allocate a new, zeroed fc_time structure.
*
* Parameters:
* None.
* Return value:
* The allocated fc_time structure, or NULL on failure.
* Side effects:
* Prints an error message if allocation fails.
*/
struct fc_time *new_fc_time(void)
{
return tc_zalloc(sizeof(struct fc_time));
}
/*************************************************************************/
/**
* free_fc_time: Free a list of allocated fc_time structures.
*
* Parameters:
* list: The list of structures to free.
* Return value:
* None.
*/
void free_fc_time(struct fc_time *list)
{
while (list) {
struct fc_time *temp = list->next;
free(list);
list = temp;
}
}
/*************************************************************************/
/**
* set_fc_time: Set fields of an fc_time structure from frame indices.
*
* Parameters:
* range: The fc_time structure to modify.
* start: The frame index for the start time, or -1 for no change.
* end: The frame index for the end time, or -1 for no change.
* Return value:
* None.
* Side effects:
* Prints an error message if the `range' parameter is invalid (either
* the parameter is NULL or it points to a range whose `fps' field is
* not a positive value).
*/
void set_fc_time(struct fc_time *range, int start, int end)
{
if (!range || range->fps <= 0) {
tc_log_error(__FILE__, "set_fc_time() with invalid range!");
return;
}
if (start >= 0) {
range->sh = 0;
range->sm = 0;
range->ss = 0;
range->sf = start;
}
if (end >= 0) {
range->eh = 0;
range->em = 0;
range->es = 0;
range->ef = end;
}
normalize_fc_time(range);
}
/*************************************************************************/
/**
* fc_time_contains: Return whether a list of fc_time structures contains
* a given frame index.
*
* Parameters:
* list: List of fc_time structures to check.
* frame: Frame index.
* Return value:
* Nonzero if one of the ranges contains the given frame index, else 0.
*/
int fc_time_contains(const struct fc_time *list, unsigned int frame)
{
while (list) {
if (frame >= list->stf && frame < list->etf)
return 1;
list = list->next;
}
return 0;
}
/*************************************************************************/
/**
* new_fc_time_from_string: Parse a string into a list of fc_time
* structures.
*
* Parameters:
* string: The string to parse.
* separator: A string containing separators for distinct ranges
* within `string'.
* fps: The value to store in each range's `fps' field.
* verbose: If positive, each range will be printed as it is parsed.
* If negative, error messages will be suppressed.
* Return value:
* The list of fc_time structures on success, NULL on failure.
* Side effects:
* Prints an error message if parsing fails, unless verbose < 0.
*/
struct fc_time *new_fc_time_from_string(const char *string,
const char *separator,
double fps, int verbose)
{
struct fc_time *list, *tail;
char rangebuf[101]; /* Buffer to hold a single range for processing */
const char *s;
/* Sanity checks first */
if (!string) {
if (verbose >= 0) {
tc_log_error(__FILE__,
"new_fc_time_from_string(): string is NULL!");
}
return NULL;
}
if (!separator) {
if (verbose >= 0) {
tc_log_error(__FILE__,
"new_fc_time_from_string(): separator is NULL!");
}
return NULL;
}
if (fps <= 0) {
if (verbose >= 0) {
tc_log_error(__FILE__, "new_fc_time_from_string(): fps <= 0!");
}
return NULL;
}
/* Loop through all ranges in the string */
list = tail = NULL;
s = string + strspn(string,separator);
while (*s) {
struct fc_time *range; /* Newly-allocated fc_time structure */
const char *errmsg; /* Error message from parse_one_range() */
int errpos; /* Position of error within range string */
const char *t;
t = s + strcspn(s,separator);
if (t-s > sizeof(rangebuf)-1) {
if (verbose >= 0) {
tc_log_error(__FILE__, "new_fc_time_from_string():"
" range string too long! (%u/%u)",
(unsigned)(t-s), (unsigned)sizeof(rangebuf)-1);
/* Print out the string and the location of the error */
tc_log_error(__FILE__, "%s", string);
tc_log_error(__FILE__, "%*s", (int)((s-string)+1), "^");
}
/* Don't forget to free anything we already parsed */
free_fc_time(list);
return NULL;
}
memcpy(rangebuf, s, t-s);
rangebuf[t-s] = 0;
errmsg = "unknown error";
errpos = 0;
range = parse_one_range(rangebuf, fps, &errmsg, &errpos);
if (!range) {
if (verbose >= 0) {
tc_log_error(__FILE__, "Error parsing framecode range: %s",
errmsg);
tc_log_error(__FILE__, "%s", string);
tc_log_error(__FILE__, "%*s", (int)((s-string+errpos)+1), "^");
}
free_fc_time(list);
return NULL;
}
if (verbose > 0) {
tc_log_info(__FILE__, "Range: %u:%02u:%02u.%u (%u)"
" - %u:%02u:%02u.%u (%u)",
range->sh, range->sm, range->ss, range->sf,
range->stf,
range->eh, range->em, range->es, range->ef,
range->etf);
}
if (!list) {
list = tail = range;
} else {
tail->next = range;
tail = range;
}
s = t + strspn(t,separator);
}
/* Parsing completed successfully */
return list;
}
/*************************************************************************/
/************************** Internal functions ***************************/
/*************************************************************************/
/**
* normalize_fc_time: Convert the HH:MM:SS.FF times stored in the given
* fc_time structure to a normalized form, with MM < 60, SS < 60, and
* FF < range->fps; also, store the frame indices corresponding to the
* start and end times in range->stf and range->etf, respectively.
* Fractional frame numbers are rounded down to the next lowest integer.
* Used by set_fc_time() and parse_one_range().
*
* Parameters:
* range: fc_time structure to normalize.
* Return value:
* None.
* Preconditions:
* range != NULL
* range->fps > 0
*/
static void normalize_fc_time(struct fc_time *range)
{
/* Calculate frame index from time parameters (round down) */
range->stf = floor(((range->sh * 60 + range->sm) * 60 + range->ss)
* range->fps)
+ range->sf;
/* Calculate total number of seconds */
range->ss = (unsigned int)floor(range->stf / range->fps);
/* Calculate frame remainder */
range->sf = (unsigned int)floor(range->stf - (range->ss * range->fps));
/* Calculate normalized hours, minutes, and seconds */
range->sh = range->ss / 3600;
range->sm = (range->ss/60) % 60;
range->ss %= 60;
/* Repeat for end time */
range->etf = floor(((range->eh * 60 + range->em) * 60 + range->es)
* range->fps)
+ range->ef;
range->es = (unsigned int)floor(range->etf / range->fps);
range->ef = (unsigned int)floor(range->etf - (range->es * range->fps));
range->eh = range->es / 3600;
range->em = (range->es/60) % 60;
range->es %= 60;
}
/*************************************************************************/
/**
* parse_one_range: Parse a string containing a single framecode range,
* and return a newly allocated fc_time structure containing the range.
* Used by new_fc_time_from_string().
*
* Parameters:
* string: String to parse.
* fps: Frames-per-second value to use for the range.
* errmsg_ret: Pointer to location to store error message in. On
* failure, this is filled with a pointer to an error
* message; on success, the value is not modified.
* errpos_ret: Pointer to location to store error position in. On
* failure, this is filled with the offset in characters
* from the start of the string to the position at which
* the error occurred; on success, the value is not
* modified.
* Return value:
* The newly-allocated fc_time structure, or NULL on error.
* Preconditions:
* string != NULL
* fps > 0
* errmsg_ret != NULL
* errpos_ret != NULL
*/
static struct fc_time *parse_one_range(const char *string, double fps,
const char **errmsg_ret,
int *errpos_ret)
{
struct fc_time *range; /* New fc_time structure */
const char *s = string; /* Current parsing location */
/* Allocate new (cleared) fc_time and set FPS */
range = new_fc_time();
if (!range) {
*errmsg_ret = "out of memory";
*errpos_ret = 0;
return NULL;
}
range->fps = fps;
range->stepf = 1;
/* Parse start time */
if (!parse_one_time(&s, &range->sh, &range->sm, &range->ss, &range->sf,
errmsg_ret))
goto error;
/* Check for and skip intervening hyphen */
if (*s != '-') {
*errmsg_ret = "syntax error (expected '-')";
goto error;
}
s++;
/* Parse end time */
if (!parse_one_time(&s, &range->eh, &range->em, &range->es, &range->ef,
errmsg_ret))
goto error;
/* Parse step value, if present */
if (*s == '/') {
s++;
if (!parse_one_value(&s, &range->stepf, errmsg_ret))
goto error;
}
/* Make sure we're at the end of the string */
if (*s) {
*errmsg_ret = "garbage at end of range";
goto error;
}
/* Successfully parsed: normalize values and return */
normalize_fc_time(range);
return range;
error:
*errpos_ret = s - string;
free_fc_time(range);
return NULL;
}
/*************************************************************************/
/**
* parse_one_time: Parse an [[[HH:]MM:]SS.]FF time specification.
*
* Parameters:
* strptr: Pointer to the string to be parsed.
* hour_ret: Pointer to variable to receive the parsed hour value.
* The stored value is unchanged on error.
* min_ret: Pointer to variable to receive the parsed minute value.
* The stored value is unchanged on error.
* sec_ret: Pointer to variable to receive the parsed second value.
* The stored value is unchanged on error.
* frame_ret: Pointer to variable to receive the parsed frame value.
* The stored value is unchanged on error.
* errmsg_ret: As for parse_one_range().
* Return value:
* Nonzero if parsing succeeded, zero on error.
* Preconditions:
* strptr != NULL
* *strptr != NULL
* hour_ret != NULL
* min_ret != NULL
* sec_ret != NULL
* frame_ret != NULL
* errmsg_ret != NULL
* Side effects:
* `*strptr' is advanced to the first character beyond the parsed
* string on success; on failure, the stored value points to the
* location of the error.
*/
static int parse_one_time(const char **strptr, unsigned int *hour_ret,
unsigned int *min_ret, unsigned int *sec_ret,
unsigned int *frame_ret, const char **errmsg_ret)
{
unsigned int hour = 0, min = 0, sec = 0, frame = 0;
int saw_colon = 0; /* for deciding whether it's a bare frame count */
if (!parse_one_value(strptr, &hour, errmsg_ret))
return 0;
if (**strptr == ':') {
saw_colon = 1;
(*strptr)++;
if (!parse_one_value(strptr, &min, errmsg_ret))
return 0;
if (**strptr == ':') {
(*strptr)++;
if (!parse_one_value(strptr, &sec, errmsg_ret))
return 0;
} else {
sec = min;
min = hour;
hour = 0;
}
} else {
sec = hour;
hour = 0;
}
if (**strptr == '.') {
(*strptr)++;
if (!parse_one_value(strptr, &frame, errmsg_ret))
return 0;
} else if (!saw_colon) {
/* No colon or dot--must be a bare frame count */
frame = sec;
sec = 0;
}
/* Success */
*hour_ret = hour;
*min_ret = min;
*sec_ret = sec;
*frame_ret = frame;
return 1;
}
/*************************************************************************/
/**
* parse_one_value: Parse a single base-10 nonnegative integer value from
* the given string. Used by parse_one_range().
*
* Parameters:
* strptr: Pointer to the string to be parsed.
* value_ret: Pointer to variable to receive the parsed value. The
* stored value is unchanged on error.
* errmsg_ret: As for parse_one_range().
* Return value:
* Nonzero if parsing succeeded, zero on error.
* Preconditions:
* strptr != NULL
* *strptr != NULL
* value_ret != NULL
* errmsg_ret != NULL
* Side effects:
* `*strptr' is advanced to the first character beyond the parsed
* string on success; on failure, the stored value is unchanged.
*/
static int parse_one_value(const char **strptr, unsigned int *value_ret,
const char **errmsg_ret)
{
const char *s;
unsigned long lvalue;
errno = 0;
lvalue = (unsigned int)strtoul(*strptr, (char **)&s, 10);
if (s == *strptr) {
*errmsg_ret = "not a valid number";
return 0;
} else if (errno == ERANGE
#if ULONG_MAX > UINT_MAX
|| lvalue > UINT_MAX
#endif
) {
*errmsg_ret = "value out of range";
return 0;
}
*strptr = s;
*value_ret = (unsigned int)lvalue;
return 1;
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/