/* * 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 3 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, see . */ #include #include "tvheadend.h" #include "dvr.h" /* * Internal defines controlling parsing */ /** * This is the max number of lines that will be read * from a cutpoint file (e.g. EDL or Comskip). * This is a safety against large files containing non-cutpoint/garbage data. */ #define DVR_MAX_READ_CUTFILE_LINES 10000 /** * This is the max number of entries that will be used * from a cutpoint file (e.g. EDL or Comskip). * This is a safety against using up resources due to * potentially large files containing weird data. */ #define DVR_MAX_CUT_ENTRIES 5000 /** * Max line length allowed in a cutpoints file. Excess will be ignored. */ #define DVR_MAX_CUTPOINT_LINE 128 /* ************************************************************************** * Parsers * *************************************************************************/ /** * Parse EDL data. * filename, in: full path to EDL file. * cut_list, in: empty list. out: the list filled with data. * return: number of read valid lines. * * Example of EDL file content: * * 2.00 98.36 3 * 596.92 665.92 3 * 1426.68 2160.16 3 * */ static int dvr_parse_edl ( const char *line, dvr_cutpoint_t *cutpoint, float *frame ) { int action = 0; float start = 0.0f, end = 0.0f; /* Invalid line */ if (sscanf(line, "%f\t%f\t%d", &start, &end, &action) != 3) return 1; /* Sanity Checks */ if(start < 0 || end < 0 || end < start || start == end || action < DVR_CP_CUT || action > DVR_CP_COMM) { tvhwarn("dvr", "Insane entry: start=%f, end=%f. Skipping.", start, end); return 1; } /* Set values */ cutpoint->dc_start_ms = (int) (start * 1000.0f); cutpoint->dc_end_ms = (int) (end * 1000.0f); cutpoint->dc_type = action; return 0; } /** * Parse comskip data. * filename, in: full path to comskip file. * cut_list, in: empty list. out: the list filled with data. * return: number of read valid lines. * * Example of comskip file content (format v2): * * FILE PROCESSING COMPLETE 53999 FRAMES AT 2500 * ------------------- * 50 2459 * 14923 23398 * 42417 54004 * */ static int dvr_parse_comskip ( const char *line, dvr_cutpoint_t *cutpoint, float *frame_rate ) { int start = 0, end = 0; /* Header */ if (sscanf(line, "FILE PROCESSING COMPLETE %*d FRAMES AT %f", frame_rate) == 1) { *frame_rate /= (*frame_rate > 1000.0f ? 100.0f : 1.0f); return 1; // TODO: probably not nice this returns "error" } /* Invalid line */ if(*frame_rate <= 0.0f || sscanf(line, "%d\t%d", &start, &end) != 2) return 1; /* Sanity Checks */ if(start < 0 || end < 0 || end < start || start == end) { tvherror("dvr", "Insane EDL entry: start=%d, end=%d. Skipping.", start, end); return 1; } /* Set values */ cutpoint->dc_start_ms = (int) ((start * 1000) / *frame_rate); cutpoint->dc_end_ms = (int) ((end * 1000) / *frame_rate); // Comskip don't have different actions, so use DVR_CP_COMM (Commercial skip) cutpoint->dc_type = DVR_CP_COMM; return 0; } /** * Wrapper for basic file processing */ static int dvr_parse_file ( const char *path, dvr_cutpoint_list_t *cut_list, void *p ) { int line_count = 0, valid_lines = -1; int (*parse) (const char *line, dvr_cutpoint_t *cp, float *framerate) = p; dvr_cutpoint_t *cp = NULL; float frate = 0.0; char line[DVR_MAX_CUTPOINT_LINE]; FILE *file = tvh_fopen(path, "r"); if (file == NULL) return -1; while (line_count < DVR_MAX_READ_CUTFILE_LINES && valid_lines < DVR_MAX_CUT_ENTRIES) { /* Read line */ if(fgets(line, DVR_MAX_CUTPOINT_LINE, file) == NULL) break; line_count++; /* Alloc cut point */ if (!(cp = calloc(1, sizeof(dvr_cutpoint_t)))) goto done; /* Parse */ if (!parse(line, cp, &frate)) { TAILQ_INSERT_TAIL(cut_list, cp, dc_link); valid_lines++; cp = NULL; } } done: if (cp) free(cp); fclose(file); return valid_lines; } /* ************************************************************************** * Public routines * *************************************************************************/ /* * Hooks for different decoders * * // TODO: possibly could be better with some sort of auto-detect */ static struct { const char *ext; int (*parse) (const char *path, dvr_cutpoint_list_t *, void *); void *opaque; } dvr_cutpoint_parsers[] = { { .ext = "txt", .parse = dvr_parse_file, .opaque = dvr_parse_comskip, }, { .ext = "edl", .parse = dvr_parse_file, .opaque = dvr_parse_edl, }, }; /* * Return cutpoint data for a recording (if present). */ dvr_cutpoint_list_t * dvr_get_cutpoint_list (dvr_entry_t *de) { int i; char *path, *sptr; dvr_cutpoint_list_t *cuts; /* Check this is a valid recording */ assert(de != NULL); if (de->de_filename == NULL) return NULL; /* Allocate list space */ cuts = calloc(1, sizeof(dvr_cutpoint_list_t)); if (cuts == NULL) return NULL; TAILQ_INIT(cuts); /* Get base filename */ // TODO: harcoded 3 for max extension path = alloca(strlen(de->de_filename) + 3); strcpy(path, de->de_filename); sptr = strrchr(path, '.'); if (!sptr) { free(cuts); return NULL; } /* Check each parser */ for (i = 0; i < ARRAY_SIZE(dvr_cutpoint_parsers); i++) { /* Add extension */ strcpy(sptr+1, dvr_cutpoint_parsers[i].ext); /* Check file exists (and readable) */ if (access(path, R_OK)) continue; /* Try parsing */ if (dvr_cutpoint_parsers[i].parse(path, cuts, dvr_cutpoint_parsers[i].opaque) != -1) break; } /* Cleanup */ if (i >= ARRAY_SIZE(dvr_cutpoint_parsers)) { dvr_cutpoint_list_destroy(cuts); return NULL; } return cuts; } /* * Delete list */ void dvr_cutpoint_list_destroy (dvr_cutpoint_list_t *list) { dvr_cutpoint_t *cp; if(!list) return; while ((cp = TAILQ_FIRST(list))) { TAILQ_REMOVE(list, cp, dc_link); free(cp); } free(list); }