/* * framecode.c -- framecode list handling * Written by Andrew Church * * 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 #include #include #include #include /* for UINT_MAX and ULONG_MAX */ #include #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: */