dvr: clean up the new cutpoint code

This commit is contained in:
Adam Sutton 2014-01-27 09:35:09 +00:00
parent d9bc3b6d41
commit c2ecbf4185
2 changed files with 172 additions and 162 deletions

View file

@ -406,25 +406,6 @@ void dvr_inotify_del ( dvr_entry_t *de );
* Cutpoints support * Cutpoints support
**/ **/
/**
* 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
typedef struct dvr_cutpoint { typedef struct dvr_cutpoint {
TAILQ_ENTRY(dvr_cutpoint) dc_link; TAILQ_ENTRY(dvr_cutpoint) dc_link;
uint64_t dc_start_ms; uint64_t dc_start_ms;

View file

@ -17,44 +17,39 @@
#include "tvheadend.h" #include "tvheadend.h"
#include "dvr.h" #include "dvr.h"
/*
* Internal defines controlling parsing
*/
/** /**
* Replaces the extension of a filename with a different extension. * This is the max number of lines that will be read
* filename, in: full path to file. * from a cutpoint file (e.g. EDL or Comskip).
* new_ext, in: new extension including leading '.'., e.g. ".edl" * This is a safety against large files containing non-cutpoint/garbage data.
* new_filename, in: pre-allocated char*, out: filename with the new extension. */
* Return 1 on success, otherwise 0.
**/
static int
dvr_switch_file_extension(const char *filename, const char *new_ext, char *new_filename)
{
char *ext = strrchr(filename, '.');
// No '.' found. Probably not a good path/file then... #define DVR_MAX_READ_CUTFILE_LINES 10000
if(ext == NULL) {
return 0;
}
int len = (ext - filename); /**
if(len <= 0) { * This is the max number of entries that will be used
return 0; * 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
// Allocate length for stripped filename + new_ext + "\0" /**
int ext_len = strlen(new_ext); * Max line length allowed in a cutpoints file. Excess will be ignored.
*/
#define DVR_MAX_CUTPOINT_LINE 128
// Build the new filename. /* **************************************************************************
memcpy(new_filename, filename, len); * Parsers
memcpy(&new_filename[len], new_ext, ext_len); * *************************************************************************/
new_filename[len + ext_len] = 0;
return 1;
}
/** /**
* Parse EDL data. * Parse EDL data.
* filename, in: full path to EDL file. * filename, in: full path to EDL file.
* cut_list, in: empty list. out: the list filled with data. * cut_list, in: empty list. out: the list filled with data.
* Don't forget to call dvr_cutpoint_list_destroy for the cut_list when done.
* return: number of read valid lines. * return: number of read valid lines.
* *
* Example of EDL file content: * Example of EDL file content:
@ -63,55 +58,31 @@ dvr_switch_file_extension(const char *filename, const char *new_ext, char *new_f
* 596.92 665.92 3 * 596.92 665.92 3
* 1426.68 2160.16 3 * 1426.68 2160.16 3
* *
**/ */
static int static int
dvr_parse_edl(const char *filename, dvr_cutpoint_list_t *cut_list) dvr_parse_edl
( const char *line, dvr_cutpoint_t *cutpoint, float *frame )
{ {
char line[DVR_MAX_CUTPOINT_LINE]; int action = 0;
int line_count = 0, valid_lines = 0, action = 0;
float start = 0.0f, end = 0.0f; float start = 0.0f, end = 0.0f;
dvr_cutpoint_t *cutpoint;
FILE *file = fopen(filename, "r"); /* Invalid line */
if (sscanf(line, "%f\t%f\t%d", &start, &end, &action) != 3)
return 1;
// No file found. Which is perfectly ok. /* Sanity Checks */
if (file == NULL) if(start < 0 || end < 0 || end < start || start == end ||
return -1; action < DVR_CP_CUT || action > DVR_CP_COMM) {
tvhwarn("dvr", "Insane entry: start=%f, end=%f. Skipping.", start, end);
while(line_count < DVR_MAX_READ_CUTFILE_LINES) { return 1;
if(fgets(line, DVR_MAX_CUTPOINT_LINE, file) == NULL)
break;
line_count++;
if (sscanf(line, "%f\t%f\t%d", &start, &end, &action) == 3) {
// 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);
continue;
}
cutpoint = calloc(1, sizeof(dvr_cutpoint_t));
if(cutpoint == NULL) {
fclose(file);
return 0;
}
cutpoint->dc_start_ms = (int) (start * 1000.0f);
cutpoint->dc_end_ms = (int) (end * 1000.0f);
cutpoint->dc_type = action;
TAILQ_INSERT_TAIL(cut_list, cutpoint, dc_link);
valid_lines++;
if(valid_lines >= DVR_MAX_CUT_ENTRIES)
break;
}
} }
fclose(file);
return valid_lines; /* Set values */
cutpoint->dc_start_ms = (int) (start * 1000.0f);
cutpoint->dc_end_ms = (int) (end * 1000.0f);
cutpoint->dc_type = action;
return 0;
} }
@ -119,7 +90,6 @@ dvr_parse_edl(const char *filename, dvr_cutpoint_list_t *cut_list)
* Parse comskip data. * Parse comskip data.
* filename, in: full path to comskip file. * filename, in: full path to comskip file.
* cut_list, in: empty list. out: the list filled with data. * cut_list, in: empty list. out: the list filled with data.
* Don't forget to call dvr_cutpoint_list_destroy for the cut_list when done.
* return: number of read valid lines. * return: number of read valid lines.
* *
* Example of comskip file content (format v2): * Example of comskip file content (format v2):
@ -130,115 +100,174 @@ dvr_parse_edl(const char *filename, dvr_cutpoint_list_t *cut_list)
* 14923 23398 * 14923 23398
* 42417 54004 * 42417 54004
* *
**/ */
static int static int
dvr_parse_comskip(const char *filename, dvr_cutpoint_list_t *cut_list) 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) {
if (*frame_rate <= 0.0)
return 1;
*frame_rate /= (*frame_rate > 1000.0f ? 100.0f : 1.0f);
return 0;
}
/* 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]; char line[DVR_MAX_CUTPOINT_LINE];
float frame_rate = 0.0f; FILE *file = fopen(path, "r");
int line_count = 0, valid_lines = 0, start = 0, end = 0;
dvr_cutpoint_t *cutpoint;
FILE *file = fopen(filename, "r"); if (file == NULL)
// No file found. Which is perfectly ok.
if (file == NULL)
return -1; return -1;
while(line_count < DVR_MAX_READ_CUTFILE_LINES) { 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) if(fgets(line, DVR_MAX_CUTPOINT_LINE, file) == NULL)
break; break;
line_count++; line_count++;
if (sscanf(line, "FILE PROCESSING COMPLETE %*d FRAMES AT %f", &frame_rate) == 1)
continue;
if(frame_rate > 0.0f && sscanf(line, "%d\t%d", &start, &end) == 2) {
// Sanity checks...
if(start < 0 || end < 0 || end < start || start == end) {
tvherror("DVR",
"Insane EDL entry: start=%d, end=%d. Skipping.", start, end);
continue;
}
// Support frame rate stated as both 25 and 2500 /* Alloc cut point */
frame_rate /= (frame_rate > 1000.0f ? 100.0f : 1.0f); if (!(cp = calloc(1, sizeof(dvr_cutpoint_t))))
goto done;
cutpoint = calloc(1, sizeof(dvr_cutpoint_t)); /* Parse */
if(cutpoint == NULL) { if (!parse(line, cp, &frate)) {
fclose(file); TAILQ_INSERT_TAIL(cut_list, cp, dc_link);
return 0;
}
// Convert frame numbers to timestamps (in ms)
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;
TAILQ_INSERT_TAIL(cut_list, cutpoint, dc_link);
valid_lines++; valid_lines++;
cp = NULL;
if(valid_lines >= DVR_MAX_CUT_ENTRIES)
break;
} }
} }
fclose(file);
done:
if (cp) free(cp);
fclose(file);
return valid_lines; return valid_lines;
} }
/* **************************************************************************
* Public routines
* *************************************************************************/
/** /*
* Return cutpoint data for a recording (if present). * 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_cutpoint_list_t *
dvr_get_cutpoint_list (uint32_t dvr_entry_id) dvr_get_cutpoint_list (uint32_t dvr_entry_id)
{ {
int i;
dvr_entry_t *de; dvr_entry_t *de;
char *path, *sptr;
dvr_cutpoint_list_t *cuts;
if ((de = dvr_entry_find_by_id(dvr_entry_id)) == NULL) /* Check this is a valid recording */
if ((de = dvr_entry_find_by_id(dvr_entry_id)) == NULL)
return NULL; return NULL;
if (de->de_filename == NULL) if (de->de_filename == NULL)
return NULL; return NULL;
char *dc_filename = alloca(strlen(de->de_filename) + 4); /* Allocate list space */
if(dc_filename == NULL) cuts = calloc(1, sizeof(dvr_cutpoint_list_t));
return NULL; if (cuts == NULL)
// First we try with comskip file. (.txt)
if(!dvr_switch_file_extension(de->de_filename, ".txt", dc_filename))
return NULL;
dvr_cutpoint_list_t *cuts = calloc(1, sizeof(dvr_cutpoint_list_t));
if (cuts == NULL)
return NULL; return NULL;
TAILQ_INIT(cuts); TAILQ_INIT(cuts);
if(dvr_parse_comskip(dc_filename, cuts) == -1) { /* Get base filename */
// Then try with edl file. (.edl) // TODO: harcoded 3 for max extension
if(!dvr_switch_file_extension(de->de_filename, ".edl", dc_filename)) { path = alloca(strlen(de->de_filename) + 3);
dvr_cutpoint_list_destroy(cuts); strcpy(path, de->de_filename);
return NULL; sptr = strrchr(path, '.');
} if (!sptr)
if(dvr_parse_edl(dc_filename, cuts) == -1) { return NULL;
// No cutpoint file found
dvr_cutpoint_list_destroy(cuts); /* Check each parser */
return NULL; for (i = 0; i < ARRAY_SIZE(dvr_cutpoint_parsers); i++) {
}
/* Add extension */
strcpy(sptr, 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; return cuts;
} }
/*************************** /*
* Helpers * Delete list
***************************/ */
void
void
dvr_cutpoint_list_destroy (dvr_cutpoint_list_t *list) dvr_cutpoint_list_destroy (dvr_cutpoint_list_t *list)
{ {
if(!list) return;
dvr_cutpoint_t *cp; dvr_cutpoint_t *cp;
if(!list) return;
while ((cp = TAILQ_FIRST(list))) { while ((cp = TAILQ_FIRST(list))) {
TAILQ_REMOVE(list, cp, dc_link); TAILQ_REMOVE(list, cp, dc_link);
free(cp); free(cp);