diff --git a/docs/header.html b/docs/header.html index 87c8bcec..b2e4959e 100644 --- a/docs/header.html +++ b/docs/header.html @@ -32,12 +32,12 @@ body { padding-right: 60px; } -.hts-doc-text dt { +.hts-doc-text dt,th { padding-top: 10px; font-weight: bold; } -.hts-doc-text dl { +.hts-doc-text dl,td { padding-bottom: 10px; } @@ -53,4 +53,4 @@ body { - \ No newline at end of file + diff --git a/docs/html/config_dvr.html b/docs/html/config_dvr.html index cd8e4c19..90abcd66 100644 --- a/docs/html/config_dvr.html +++ b/docs/html/config_dvr.html @@ -42,6 +42,27 @@
If checked, Tvheadend will include the time for the recording in the event title. This applies to both the titled stored in the file and to the filename itself. +
Post-processor command +
Command to run after finishing a recording. The command will be + run in background and is executed even if a recording is aborted + or an error occurred. Use the %e error formatting string to check + for errors, the error string is empty if recording finished + successfully. +

+ Support format strings:
+ + + + + + + + + + +
FormatDescriptionExample value
%fFull path to recoding/home/user/Videos/News.mkv
%bBasename of recodingNews.mkv
%cChannel nameBBC world
%CWho created this recodinguser
%tProgram titleNews
%cProgram descriptionNews and stories...
%SStart timestmap of recoding, UNIX epoch1224421200
%EStop timestamp of recoding, UNIX epoch1224426600
+
+ Example usage: /path/to/ffmpeg -i %f -vcodec libx264 -acodec copy "/path/with white space/%b" Changes to any of these settings must be confirmed by pressing the 'Save configuration' button before taking effect. diff --git a/dvr/dvr.h b/dvr/dvr.h index 500a31cd..c25e45a1 100644 --- a/dvr/dvr.h +++ b/dvr/dvr.h @@ -29,6 +29,7 @@ extern char *dvr_format; extern char *dvr_file_postfix; extern uint32_t dvr_retention_days; extern int dvr_flags; +extern char *dvr_postproc; #define DVR_DIR_PER_DAY 0x1 #define DVR_DIR_PER_CHANNEL 0x2 @@ -144,6 +145,8 @@ void dvr_entry_dec_ref(dvr_entry_t *de); void dvr_storage_set(const char *storage); +void dvr_postproc_set(const char *postproc); + void dvr_retention_set(int days); void dvr_flags_set(int flags); diff --git a/dvr/dvr_db.c b/dvr/dvr_db.c index 7ec7b578..42c49a88 100644 --- a/dvr/dvr_db.c +++ b/dvr/dvr_db.c @@ -33,7 +33,7 @@ char *dvr_format; char *dvr_file_postfix; uint32_t dvr_retention_days; int dvr_flags; - +char *dvr_postproc; static int de_tally; @@ -488,6 +488,8 @@ dvr_init(void) if(!htsmsg_get_u32(m, "time-in-title", &u32) && u32) dvr_flags |= DVR_TIME_IN_TITLE; + + tvh_str_set(&dvr_postproc, htsmsg_get_str(m, "postproc")); htsmsg_destroy(m); } @@ -538,6 +540,8 @@ dvr_save(void) 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)); + if(dvr_postproc != NULL) + htsmsg_add_str(m, "postproc", dvr_postproc); hts_settings_save(m, "dvr/config"); htsmsg_destroy(m); @@ -556,6 +560,19 @@ dvr_storage_set(const char *storage) dvr_save(); } +/** + * + */ +void +dvr_postproc_set(const char *postproc) +{ + if(dvr_postproc != NULL && !strcmp(dvr_postproc, postproc)) + return; + + tvh_str_set(&dvr_postproc, !strcmp(postproc, "") ? NULL : postproc); + dvr_save(); +} + /** * diff --git a/dvr/dvr_rec.c b/dvr/dvr_rec.c index edb52630..101a3627 100644 --- a/dvr/dvr_rec.c +++ b/dvr/dvr_rec.c @@ -20,13 +20,17 @@ #include #include #include +#include /* basename */ #include #include +#include + #include "tvhead.h" #include "streaming.h" #include "dvr.h" +#include "spawn.h" typedef struct dvr_rec_stream { LIST_ENTRY(dvr_rec_stream) drs_link; @@ -46,6 +50,7 @@ static void dvr_rec_start(dvr_entry_t *de, streaming_pad_t *sp); static void dvr_rec_stop(dvr_entry_t *de); static void *dvr_thread(void *aux); static void dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt); +static void dvr_spawn_postproc(dvr_entry_t *de); static void dvr_thread_epilog(dvr_entry_t *de); /** @@ -695,6 +700,57 @@ dvr_thread_new_pkt(dvr_entry_t *de, th_pkt_t *pkt) } } +/** + * + */ +static void dvr_spawn_postproc(dvr_entry_t *de) +{ + char *fmap[256]; + char **args; + char start[16]; + char stop[16]; + char *fbasename; /* filename dup for basename */ + int i; + + args = htsstr_argsplit(dvr_postproc); + /* no arguments at all */ + if(!args[0]) { + htsstr_argsplit_free(args); + return; + } + + fbasename = strdup(de->de_filename); + snprintf(start, sizeof(start), "%ld", de->de_start); + snprintf(stop, sizeof(stop), "%ld", de->de_stop); + + memset(fmap, 0, sizeof(fmap)); + fmap['f'] = de->de_filename; /* full path to recoding */ + fmap['b'] = basename(fbasename); /* basename of recoding */ + fmap['c'] = de->de_channel->ch_name; /* channel name */ + fmap['C'] = de->de_creator; /* user who created this recording */ + fmap['t'] = de->de_title; /* program title */ + fmap['d'] = de->de_desc; /* program description */ + fmap['e'] = de->de_error; /* error message, empty if no error (FIXME:?) */ + fmap['S'] = start; /* start time, unix epoch */ + fmap['E'] = stop; /* stop time, unix epoch */ + + printf("error=%s\n", de->de_error); + + /* format arguments */ + for(i = 0; args[i]; i++) { + char *s; + + s = htsstr_format(args[i], fmap); + free(args[i]); + args[i] = s; + } + + spawnv(args[0], (void *)args); + + free(fbasename); + htsstr_argsplit_free(args); +} + /** * */ @@ -731,4 +787,7 @@ dvr_thread_epilog(dvr_entry_t *de) LIST_REMOVE(drs, drs_link); free(drs); } + + if(dvr_postproc) + dvr_spawn_postproc(de); } diff --git a/webui/extjs.c b/webui/extjs.c index 286ddab8..51ec1ced 100644 --- a/webui/extjs.c +++ b/webui/extjs.c @@ -1063,6 +1063,7 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque) r = htsmsg_create(); htsmsg_add_str(r, "storage", dvr_storage); + htsmsg_add_str(r, "postproc", dvr_postproc); 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)); @@ -1076,6 +1077,9 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque) if((s = http_arg_get(&hc->hc_req_args, "storage")) != NULL) dvr_storage_set(s); + + if((s = http_arg_get(&hc->hc_req_args, "postproc")) != NULL) + dvr_postproc_set(s); if((s = http_arg_get(&hc->hc_req_args, "retention")) != NULL) dvr_retention_set(atoi(s)); diff --git a/webui/static/app/dvr.js b/webui/static/app/dvr.js index c89c1bfa..dde1677a 100644 --- a/webui/static/app/dvr.js +++ b/webui/static/app/dvr.js @@ -319,7 +319,7 @@ tvheadend.dvrsettings = function() { var confreader = new Ext.data.JsonReader({ root: 'dvrSettings', - }, ['storage','retention','dayDirs', + }, ['storage','postproc','retention','dayDirs', 'channelDirs','channelInTitle', 'dateInTitle','timeInTitle']); @@ -359,7 +359,11 @@ tvheadend.dvrsettings = function() { }), new Ext.form.Checkbox({ fieldLabel: 'Include time in title', name: 'timeInTitle' - })], + }), { + width: 300, + fieldLabel: 'Post-processor command', + name: 'postproc' + }], tbar: [{ tooltip: 'Save changes made to channel configuration below', iconCls:'save', diff --git a/webui/static/app/ext.css b/webui/static/app/ext.css index 3c015afe..26be1352 100644 --- a/webui/static/app/ext.css +++ b/webui/static/app/ext.css @@ -154,4 +154,4 @@ .about-title { font:normal 24px verdana; font-weight: bold; -} \ No newline at end of file +}