summaryrefslogtreecommitdiffstats
path: root/debian/transcode/transcode-1.1.7/libtc/framecode.c
blob: aba1f69a03e85a379613c2352f87a84ed3e1f300 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
/*
 * 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:
 */