Make root directory, log retention time and DVR titles/filenames configurable.
This commit is contained in:
parent
026a7bbc2c
commit
fb33332ebd
5 changed files with 227 additions and 30 deletions
11
dvr/dvr.h
11
dvr/dvr.h
|
@ -28,6 +28,13 @@ extern char *dvr_storage;
|
|||
extern char *dvr_format;
|
||||
extern char *dvr_file_postfix;
|
||||
extern uint32_t dvr_retention_days;
|
||||
extern int dvr_flags;
|
||||
|
||||
#define DVR_DIR_PER_DAY 0x1
|
||||
#define DVR_DIR_PER_CHANNEL 0x2
|
||||
#define DVR_CHANNEL_IN_TITLE 0x4
|
||||
#define DVR_DATE_IN_TITLE 0x8
|
||||
#define DVR_TIME_IN_TITLE 0x10
|
||||
|
||||
LIST_HEAD(dvr_rec_stream_list, dvr_rec_stream);
|
||||
|
||||
|
@ -71,6 +78,8 @@ typedef struct dvr_entry {
|
|||
char *de_filename; /* Initially null if no filename has been
|
||||
generated yet */
|
||||
char *de_title; /* Title in UTF-8 (from EPG) */
|
||||
char *de_ititle; /* Internal title optionally with channelname
|
||||
date and time pre/post/fixed */
|
||||
char *de_desc; /* Description in UTF-8 (from EPG) */
|
||||
|
||||
char *de_error;
|
||||
|
@ -131,6 +140,8 @@ void dvr_storage_set(const char *storage);
|
|||
|
||||
void dvr_retention_set(int days);
|
||||
|
||||
void dvr_flags_set(int flags);
|
||||
|
||||
/**
|
||||
* Query interface
|
||||
*/
|
||||
|
|
77
dvr/dvr_db.c
77
dvr/dvr_db.c
|
@ -32,6 +32,7 @@ char *dvr_storage;
|
|||
char *dvr_format;
|
||||
char *dvr_file_postfix;
|
||||
uint32_t dvr_retention_days;
|
||||
int dvr_flags;
|
||||
|
||||
|
||||
static int de_tally;
|
||||
|
@ -58,6 +59,39 @@ dvrdb_changed(void)
|
|||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
dvr_make_title(char *output, size_t outlen, const char *title,
|
||||
const char *channel, time_t starttime)
|
||||
{
|
||||
struct tm tm;
|
||||
char buf[40];
|
||||
|
||||
if(dvr_flags & DVR_CHANNEL_IN_TITLE)
|
||||
snprintf(output, outlen, "%s-", channel);
|
||||
else
|
||||
output[0] = 0;
|
||||
|
||||
snprintf(output + strlen(output), outlen - strlen(output),
|
||||
"%s", title);
|
||||
|
||||
localtime_r(&starttime, &tm);
|
||||
|
||||
if(dvr_flags & DVR_DATE_IN_TITLE) {
|
||||
strftime(buf, sizeof(buf), "%F", &tm);
|
||||
snprintf(output + strlen(output), outlen - strlen(output), "-%s", buf);
|
||||
}
|
||||
|
||||
if(dvr_flags & DVR_TIME_IN_TITLE) {
|
||||
strftime(buf, sizeof(buf), "%R", &tm);
|
||||
snprintf(output + strlen(output), outlen - strlen(output), "-%s", buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -65,6 +99,12 @@ static void
|
|||
dvr_entry_link(dvr_entry_t *de)
|
||||
{
|
||||
time_t now, preamble;
|
||||
char buf[100];
|
||||
|
||||
dvr_make_title(buf, sizeof(buf), de->de_title, de->de_channel->ch_name,
|
||||
de->de_start);
|
||||
|
||||
de->de_ititle = strdup(buf);
|
||||
|
||||
de->de_refcnt = 1;
|
||||
|
||||
|
@ -87,6 +127,7 @@ dvr_entry_link(dvr_entry_t *de)
|
|||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -146,6 +187,7 @@ dvr_entry_dec_ref(dvr_entry_t *de)
|
|||
|
||||
free(de->de_creator);
|
||||
free(de->de_title);
|
||||
free(de->de_ititle);
|
||||
free(de->de_desc);
|
||||
|
||||
free(de);
|
||||
|
@ -393,6 +435,7 @@ dvr_init(void)
|
|||
char buf[500];
|
||||
const char *homedir;
|
||||
struct stat st;
|
||||
uint32_t u32;
|
||||
|
||||
/* Default settings */
|
||||
|
||||
|
@ -406,6 +449,21 @@ dvr_init(void)
|
|||
htsmsg_get_u32(m, "retention-days", &dvr_retention_days);
|
||||
tvh_str_set(&dvr_storage, htsmsg_get_str(m, "storage"));
|
||||
|
||||
if(!htsmsg_get_u32(m, "day-dir", &u32) && u32)
|
||||
dvr_flags |= DVR_DIR_PER_DAY;
|
||||
|
||||
if(!htsmsg_get_u32(m, "channel-dir", &u32) && u32)
|
||||
dvr_flags |= DVR_DIR_PER_CHANNEL;
|
||||
|
||||
if(!htsmsg_get_u32(m, "channel-in-title", &u32) && u32)
|
||||
dvr_flags |= DVR_CHANNEL_IN_TITLE;
|
||||
|
||||
if(!htsmsg_get_u32(m, "date-in-title", &u32) && u32)
|
||||
dvr_flags |= DVR_DATE_IN_TITLE;
|
||||
|
||||
if(!htsmsg_get_u32(m, "time-in-title", &u32) && u32)
|
||||
dvr_flags |= DVR_TIME_IN_TITLE;
|
||||
|
||||
htsmsg_destroy(m);
|
||||
}
|
||||
|
||||
|
@ -447,6 +505,11 @@ dvr_save(void)
|
|||
htsmsg_t *m = htsmsg_create();
|
||||
htsmsg_add_str(m, "storage", dvr_storage);
|
||||
htsmsg_add_u32(m, "retention-days", dvr_retention_days);
|
||||
htsmsg_add_u32(m, "day-dir", !!(dvr_flags & DVR_DIR_PER_DAY));
|
||||
htsmsg_add_u32(m, "channel-dir", !!(dvr_flags & DVR_DIR_PER_CHANNEL));
|
||||
htsmsg_add_u32(m, "channel-in-title", !!(dvr_flags & DVR_CHANNEL_IN_TITLE));
|
||||
htsmsg_add_u32(m, "date-in-title", !!(dvr_flags & DVR_DATE_IN_TITLE));
|
||||
htsmsg_add_u32(m, "time-in-title", !!(dvr_flags & DVR_TIME_IN_TITLE));
|
||||
|
||||
hts_settings_save(m, "dvr");
|
||||
htsmsg_destroy(m);
|
||||
|
@ -488,6 +551,20 @@ dvr_retention_set(int days)
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
dvr_flags_set(int flags)
|
||||
{
|
||||
if(dvr_flags == flags)
|
||||
return;
|
||||
|
||||
dvr_flags = flags;
|
||||
dvr_save();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
|
126
dvr/dvr_rec.c
126
dvr/dvr_rec.c
|
@ -135,6 +135,53 @@ dvr_rec_unsubscribe(dvr_entry_t *de)
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int
|
||||
makedirs(const char *path)
|
||||
{
|
||||
struct stat st;
|
||||
char *p;
|
||||
int l, r;
|
||||
|
||||
if(stat(path, &st) == 0 && S_ISDIR(st.st_mode))
|
||||
return 0; /* Dir already there */
|
||||
|
||||
if(mkdir(path, 0777) == 0)
|
||||
return 0; /* Dir created ok */
|
||||
|
||||
if(errno == ENOENT) {
|
||||
|
||||
/* Parent does not exist, try to create it */
|
||||
/* Allocate new path buffer and strip off last directory component */
|
||||
|
||||
l = strlen(path);
|
||||
p = alloca(l + 1);
|
||||
memcpy(p, path, l);
|
||||
p[l--] = 0;
|
||||
|
||||
for(; l >= 0; l--)
|
||||
if(p[l] == '/')
|
||||
break;
|
||||
if(l == 0) {
|
||||
return ENOENT;
|
||||
}
|
||||
p[l] = 0;
|
||||
|
||||
if((r = makedirs(p)) != 0)
|
||||
return r;
|
||||
|
||||
/* Try again */
|
||||
if(mkdir(path, 0777) == 0)
|
||||
return 0; /* Dir created ok */
|
||||
}
|
||||
r = errno;
|
||||
|
||||
tvhlog(LOG_DEBUG, "dvr", "Unable to create directory \"%s\" -- %s",
|
||||
path, strerror(r));
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
@ -155,28 +202,52 @@ deslashify(char *s)
|
|||
* - avoid duplicate filenames
|
||||
*
|
||||
*/
|
||||
static void
|
||||
static int
|
||||
pvr_generate_filename(dvr_entry_t *de)
|
||||
{
|
||||
char fullname[1000];
|
||||
char path[500];
|
||||
int tally = 0;
|
||||
struct stat st;
|
||||
char *chname;
|
||||
char *filename;
|
||||
char *chname;
|
||||
struct tm tm;
|
||||
|
||||
if(de->de_filename != NULL) {
|
||||
free(de->de_filename);
|
||||
de->de_filename = NULL;
|
||||
}
|
||||
|
||||
filename = strdup(de->de_title);
|
||||
filename = strdup(de->de_ititle);
|
||||
deslashify(filename);
|
||||
|
||||
chname = strdup(de->de_channel->ch_name);
|
||||
deslashify(chname);
|
||||
av_strlcpy(path, dvr_storage, sizeof(path));
|
||||
|
||||
snprintf(fullname, sizeof(fullname), "%s/%s-%s.%s",
|
||||
dvr_storage, chname, filename, dvr_file_postfix);
|
||||
/* Append per-day directory */
|
||||
|
||||
if(dvr_flags & DVR_DIR_PER_DAY) {
|
||||
localtime_r(&de->de_start, &tm);
|
||||
strftime(fullname, sizeof(fullname), "%F", &tm);
|
||||
deslashify(fullname);
|
||||
snprintf(path + strlen(path), sizeof(path) - strlen(path),
|
||||
"/%s", fullname);
|
||||
}
|
||||
|
||||
/* Append per-channel directory */
|
||||
|
||||
if(dvr_flags & DVR_DIR_PER_CHANNEL) {
|
||||
|
||||
chname = strdup(de->de_channel->ch_name);
|
||||
deslashify(chname);
|
||||
snprintf(path + strlen(path), sizeof(path) - strlen(path),
|
||||
"/%s", chname);
|
||||
free(chname);
|
||||
}
|
||||
|
||||
/* */
|
||||
if(makedirs(path) != 0)
|
||||
return -1;
|
||||
|
||||
|
||||
/* Construct final name */
|
||||
|
||||
snprintf(fullname, sizeof(fullname), "%s/%s.%s",
|
||||
path, filename, dvr_file_postfix);
|
||||
|
||||
while(1) {
|
||||
if(stat(fullname, &st) == -1) {
|
||||
|
@ -190,14 +261,14 @@ pvr_generate_filename(dvr_entry_t *de)
|
|||
|
||||
tally++;
|
||||
|
||||
snprintf(fullname, sizeof(fullname), "%s/%s-%s-%d.%s",
|
||||
dvr_storage, chname, filename, tally,dvr_file_postfix);
|
||||
snprintf(fullname, sizeof(fullname), "%s/%s-%d.%s",
|
||||
path, filename, tally, dvr_file_postfix);
|
||||
}
|
||||
|
||||
de->de_filename = strdup(fullname);
|
||||
tvh_str_set(&de->de_filename, fullname);
|
||||
|
||||
free(filename);
|
||||
free(chname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -230,7 +301,10 @@ dvr_rec_start(dvr_entry_t *de, streaming_pad_t *sp)
|
|||
pthread_t ptid;
|
||||
pthread_attr_t attr;
|
||||
|
||||
pvr_generate_filename(de);
|
||||
if(pvr_generate_filename(de) != 0) {
|
||||
dvr_rec_fatal_error(de, "Unable to create directories");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Find lavf format */
|
||||
|
||||
|
@ -245,7 +319,7 @@ dvr_rec_start(dvr_entry_t *de, streaming_pad_t *sp)
|
|||
|
||||
fctx = av_alloc_format_context();
|
||||
|
||||
av_strlcpy(fctx->title, de->de_title ?: "",
|
||||
av_strlcpy(fctx->title, de->de_ititle ?: "",
|
||||
sizeof(fctx->title));
|
||||
|
||||
av_strlcpy(fctx->comment, de->de_desc ?: "",
|
||||
|
@ -309,7 +383,7 @@ dvr_rec_start(dvr_entry_t *de, streaming_pad_t *sp)
|
|||
if(codec == NULL) {
|
||||
tvhlog(LOG_ERR, "dvr",
|
||||
"%s - Cannot find codec for %s, ignoring stream",
|
||||
de->de_title, codec_name);
|
||||
de->de_ititle, codec_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -325,7 +399,7 @@ dvr_rec_start(dvr_entry_t *de, streaming_pad_t *sp)
|
|||
if(avcodec_open(ctx, codec) < 0) {
|
||||
tvhlog(LOG_ERR, "dvr",
|
||||
"%s - Cannot open codec for %s, ignoring stream",
|
||||
de->de_title, codec_name);
|
||||
de->de_ititle, codec_name);
|
||||
free(ctx);
|
||||
continue;
|
||||
}
|
||||
|
@ -503,7 +577,7 @@ dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt)
|
|||
tvhlog(LOG_DEBUG, "dvr", "%s - "
|
||||
"Stream #%d: \"%s\" decoded a complete audio frame: "
|
||||
"%d channels in %d Hz",
|
||||
de->de_title, st->index, ctx->codec->name,
|
||||
de->de_ititle, st->index, ctx->codec->name,
|
||||
ctx->channels, ctx->sample_rate);
|
||||
|
||||
drs->drs_decoded = 1;
|
||||
|
@ -522,7 +596,7 @@ dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt)
|
|||
tvhlog(LOG_DEBUG, "dvr", "%s - "
|
||||
"Stream #%d: \"%s\" decoded a complete video frame: "
|
||||
"%d x %d at %.2fHz",
|
||||
de->de_title, st->index, ctx->codec->name,
|
||||
de->de_ititle, st->index, ctx->codec->name,
|
||||
ctx->width, st->codec->height,
|
||||
(float)ctx->time_base.den / (float)ctx->time_base.num);
|
||||
|
||||
|
@ -541,7 +615,7 @@ dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt)
|
|||
if(av_write_header(fctx)) {
|
||||
tvhlog(LOG_ERR, "dvr",
|
||||
"%s - Unable to write header",
|
||||
de->de_title);
|
||||
de->de_ititle);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -549,7 +623,7 @@ dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt)
|
|||
|
||||
tvhlog(LOG_ERR, "dvr",
|
||||
"%s - Header written to file, stream dump:",
|
||||
de->de_title);
|
||||
de->de_ititle);
|
||||
|
||||
for(i = 0; i < fctx->nb_streams; i++) {
|
||||
stx = fctx->streams[i];
|
||||
|
@ -557,7 +631,7 @@ dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt)
|
|||
avcodec_string(txt, sizeof(txt), stx->codec, 1);
|
||||
|
||||
tvhlog(LOG_ERR, "dvr", "%s - Stream #%d: %s [%d/%d]",
|
||||
de->de_title, i, txt,
|
||||
de->de_ititle, i, txt,
|
||||
stx->time_base.num, stx->time_base.den);
|
||||
|
||||
}
|
||||
|
@ -610,7 +684,7 @@ dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt)
|
|||
de->de_rec_state = DE_RS_RUNNING;
|
||||
|
||||
tvhlog(LOG_ERR, "dvr", "%s - Skipped %lld seconds of commercials",
|
||||
de->de_title, (pkt->pkt_dts - de->de_ts_com_start) / 1000000);
|
||||
de->de_ititle, (pkt->pkt_dts - de->de_ts_com_start) / 1000000);
|
||||
goto outputpacket;
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -891,6 +891,7 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque)
|
|||
event_t *e;
|
||||
dvr_entry_t *de;
|
||||
const char *s;
|
||||
int flags = 0;
|
||||
|
||||
if(op == NULL)
|
||||
op = "loadSettings";
|
||||
|
@ -927,6 +928,12 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque)
|
|||
r = htsmsg_create();
|
||||
htsmsg_add_str(r, "storage", dvr_storage);
|
||||
htsmsg_add_u32(r, "retention", dvr_retention_days);
|
||||
htsmsg_add_u32(r, "dayDirs", !!(dvr_flags & DVR_DIR_PER_DAY));
|
||||
htsmsg_add_u32(r, "channelDirs", !!(dvr_flags & DVR_DIR_PER_CHANNEL));
|
||||
htsmsg_add_u32(r, "channelInTitle", !!(dvr_flags & DVR_CHANNEL_IN_TITLE));
|
||||
htsmsg_add_u32(r, "dateInTitle", !!(dvr_flags & DVR_DATE_IN_TITLE));
|
||||
htsmsg_add_u32(r, "timeInTitle", !!(dvr_flags & DVR_TIME_IN_TITLE));
|
||||
|
||||
out = json_single_record(r, "dvrSettings");
|
||||
|
||||
} else if(!strcmp(op, "saveSettings")) {
|
||||
|
@ -937,6 +944,18 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque)
|
|||
if((s = http_arg_get(&hc->hc_req_args, "retention")) != NULL)
|
||||
dvr_retention_set(atoi(s));
|
||||
|
||||
if(http_arg_get(&hc->hc_req_args, "dayDirs") != NULL)
|
||||
flags |= DVR_DIR_PER_DAY;
|
||||
if(http_arg_get(&hc->hc_req_args, "channelDirs") != NULL)
|
||||
flags |= DVR_DIR_PER_CHANNEL;
|
||||
if(http_arg_get(&hc->hc_req_args, "channelInTitle") != NULL)
|
||||
flags |= DVR_CHANNEL_IN_TITLE;
|
||||
if(http_arg_get(&hc->hc_req_args, "dateInTitle") != NULL)
|
||||
flags |= DVR_DATE_IN_TITLE;
|
||||
if(http_arg_get(&hc->hc_req_args, "timeInTitle") != NULL)
|
||||
flags |= DVR_TIME_IN_TITLE;
|
||||
|
||||
dvr_flags_set(flags);
|
||||
|
||||
out = htsmsg_create();
|
||||
htsmsg_add_u32(out, "success", 1);
|
||||
|
|
|
@ -197,8 +197,9 @@ tvheadend.dvrsettings = function() {
|
|||
|
||||
var confreader = new Ext.data.JsonReader({
|
||||
root: 'dvrSettings',
|
||||
}, ['storage','retention']);
|
||||
|
||||
}, ['storage','retention','dayDirs',
|
||||
'channelDirs','channelInTitle',
|
||||
'dateInTitle','timeInTitle']);
|
||||
|
||||
var confpanel = new Ext.FormPanel({
|
||||
title:'Digital Video Recorder',
|
||||
|
@ -206,7 +207,7 @@ tvheadend.dvrsettings = function() {
|
|||
bodyStyle:'padding:15px',
|
||||
anchor: '100% 50%',
|
||||
labelAlign: 'right',
|
||||
labelWidth: 150,
|
||||
labelWidth: 200,
|
||||
waitMsgTarget: true,
|
||||
reader: confreader,
|
||||
defaultType: 'textfield',
|
||||
|
@ -219,8 +220,23 @@ tvheadend.dvrsettings = function() {
|
|||
allowNegative: false,
|
||||
allowDecimals: false,
|
||||
minValue: 1,
|
||||
fieldLabel: 'Retention time (days)',
|
||||
fieldLabel: 'DVR Log retention time (days)',
|
||||
name: 'retention'
|
||||
}), new Ext.form.Checkbox({
|
||||
fieldLabel: 'Make subdirectories per day',
|
||||
name: 'dayDirs'
|
||||
}), new Ext.form.Checkbox({
|
||||
fieldLabel: 'Make subdirectories per channel',
|
||||
name: 'channelDirs'
|
||||
}), new Ext.form.Checkbox({
|
||||
fieldLabel: 'Include channel name in title',
|
||||
name: 'channelInTitle'
|
||||
}), new Ext.form.Checkbox({
|
||||
fieldLabel: 'Include date in title',
|
||||
name: 'dateInTitle'
|
||||
}), new Ext.form.Checkbox({
|
||||
fieldLabel: 'Include time in title',
|
||||
name: 'timeInTitle'
|
||||
})],
|
||||
tbar: [{
|
||||
tooltip: 'Save changes made to channel configuration below',
|
||||
|
|
Loading…
Add table
Reference in a new issue