dvr: clean up the new cutpoint code
This commit is contained in:
parent
d9bc3b6d41
commit
c2ecbf4185
2 changed files with 172 additions and 162 deletions
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Add table
Reference in a new issue