From c113843386ed96499bcf66174643b7121c949c99 Mon Sep 17 00:00:00 2001 From: pete Date: Sat, 8 Jun 2013 06:59:51 +0300 Subject: [PATCH] preprocessor command functionality, gui and settings saving --- docs/html/config_dvr.html | 30 +++-- src/dvr/dvr.h | 10 ++ src/dvr/dvr_db.c | 37 ++++++ src/dvr/dvr_rec.c | 257 ++++++++++++++++++++++++++---------- src/webui/extjs.c | 7 + src/webui/static/app/dvr.js | 169 ++++++++++++++++++------ 6 files changed, 386 insertions(+), 124 deletions(-) diff --git a/docs/html/config_dvr.html b/docs/html/config_dvr.html index 2b72e8a024..82c6a92090 100644 --- a/docs/html/config_dvr.html +++ b/docs/html/config_dvr.html @@ -84,19 +84,25 @@ 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:
+ +
External filename program +
Command run to generate a filename. The command is executed before starting + recording. The command should write to standard output. Trailing new line + marker is ignored but no other safety checks are made unless removing of + unsafe characters is enabled. + +
Post-processor and external filename program format strings - - - - - - - - - - + + + + + + + + + +
FormatDescriptionExample value
%fFull path to recording/home/user/Videos/News.mkv
%bBasename of recordingNews.mkv
%cChannel nameBBC world
%CWho created this recordinguser
%tProgram titleNews
%dProgram descriptionNews and stories...
%eError messageAborted by user
%SStart time stamp of recording, UNIX epoch1224421200
%EStop time stamp of recording, UNIX epoch1224426600
FormatDescriptionExample valueNotice
%fFull path to recording/home/user/Videos/News.mkvPost-processor only
%bBasename of recordingNews.mkvPost-processor only
%cChannel nameBBC world
%CWho created this recordinguser
%tProgram titleNews
%dProgram descriptionNews and stories...
%eError messageAborted by userUseful only for post-processor
%SStart time stamp of recording, UNIX epoch1224421200
%EStop time stamp of recording, UNIX epoch1224426600

Example usage: /path/to/ffmpeg -i %f -vcodec libx264 -acodec copy "/path/with white space/%b"
diff --git a/src/dvr/dvr.h b/src/dvr/dvr.h index 59d285ccfb..11546ba749 100644 --- a/src/dvr/dvr.h +++ b/src/dvr/dvr.h @@ -48,6 +48,9 @@ typedef struct dvr_config { /* Duplicate detect */ int dvr_dup_detect_episode; + int dvr_filename_mode; + char *dvr_filename_external; + LIST_ENTRY(dvr_config) config_link; } dvr_config_t; @@ -55,6 +58,9 @@ extern struct dvr_config_list dvrconfigs; extern struct dvr_entry_list dvrentries; +#define DVR_FILENAMEMODE_BASIC 1 +#define DVR_FILENAMEMODE_EXTERNAL 2 + #define DVR_DIR_PER_DAY 0x1 #define DVR_DIR_PER_CHANNEL 0x2 #define DVR_CHANNEL_IN_TITLE 0x4 @@ -335,6 +341,10 @@ void dvr_extra_time_pre_set(dvr_config_t *cfg, int d); void dvr_extra_time_post_set(dvr_config_t *cfg, int d); +void dvr_filename_mode_set(dvr_config_t *cfg, int mode); + +void dvr_filename_external_set(dvr_config_t *cfg, const char *external); + void dvr_entry_delete(dvr_entry_t *de); void dvr_entry_cancel_delete(dvr_entry_t *de); diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c index d6a1562ca7..2bdf1f3744 100644 --- a/src/dvr/dvr_db.c +++ b/src/dvr/dvr_db.c @@ -1088,6 +1088,9 @@ dvr_init(void) htsmsg_get_u32(m, "retention-days", &cfg->dvr_retention_days); tvh_str_set(&cfg->dvr_storage, htsmsg_get_str(m, "storage")); + htsmsg_get_s32(m, "filename_mode", &cfg->dvr_filename_mode); + tvh_str_set(&cfg->dvr_filename_external, htsmsg_get_str(m, "filename_external")); + if(!htsmsg_get_u32(m, "day-dir", &u32) && u32) cfg->dvr_flags |= DVR_DIR_PER_DAY; @@ -1232,6 +1235,9 @@ dvr_config_create(const char *name) /* dup detect */ cfg->dvr_dup_detect_episode = 1; // detect dup episodes + cfg->dvr_filename_mode = DVR_FILENAMEMODE_BASIC; + cfg->dvr_filename_external = NULL; + LIST_INSERT_HEAD(&dvrconfigs, cfg, config_link); return LIST_FIRST(&dvrconfigs); @@ -1274,6 +1280,9 @@ dvr_save(dvr_config_t *cfg) htsmsg_add_u32(m, "retention-days", cfg->dvr_retention_days); htsmsg_add_u32(m, "pre-extra-time", cfg->dvr_extra_time_pre); htsmsg_add_u32(m, "post-extra-time", cfg->dvr_extra_time_post); + htsmsg_add_u32(m, "filename_mode", cfg->dvr_filename_mode); + if(cfg->dvr_filename_external != NULL) + htsmsg_add_str(m, "filename_external", cfg->dvr_filename_external); htsmsg_add_u32(m, "day-dir", !!(cfg->dvr_flags & DVR_DIR_PER_DAY)); htsmsg_add_u32(m, "channel-dir", !!(cfg->dvr_flags & DVR_DIR_PER_CHANNEL)); htsmsg_add_u32(m, "channel-in-title", !!(cfg->dvr_flags & DVR_CHANNEL_IN_TITLE)); @@ -1406,6 +1415,34 @@ dvr_extra_time_post_set(dvr_config_t *cfg, int d) } +/** + * + */ +void +dvr_filename_mode_set(dvr_config_t *cfg, int mode) +{ + if(cfg->dvr_filename_mode == mode) + return; + + cfg->dvr_filename_mode = mode; + dvr_save(cfg); +} + + +/** + * + */ +void +dvr_filename_external_set(dvr_config_t *cfg, const char *external) +{ + if(cfg->dvr_filename_external != NULL && !strcmp(cfg->dvr_filename_external, external)) + return; + + tvh_str_set(&cfg->dvr_filename_external, !strcmp(external, "") ? NULL : external); + dvr_save(cfg); +} + + /** * */ diff --git a/src/dvr/dvr_rec.c b/src/dvr/dvr_rec.c index c5726b019b..482c75c295 100644 --- a/src/dvr/dvr_rec.c +++ b/src/dvr/dvr_rec.c @@ -40,6 +40,7 @@ * */ static void *dvr_thread(void *aux); +static int dvr_spawn_proc(dvr_entry_t *de, const char *prog, char **output); static void dvr_spawn_postproc(dvr_entry_t *de, const char *dvr_postproc); static void dvr_thread_epilog(dvr_entry_t *de); @@ -117,6 +118,15 @@ dvr_rec_unsubscribe(dvr_entry_t *de, int stopcode) de->de_last_error = stopcode; } +static void +remove_slashes(char *s) +{ + for (; *s; s++) + { + if(*s == '/') + *s = '-'; + } +} /** * Replace various chars with a dash @@ -124,20 +134,108 @@ dvr_rec_unsubscribe(dvr_entry_t *de, int stopcode) static void cleanupfilename(char *s, int dvr_flags) { - int i, len = strlen(s); - for(i = 0; i < len; i++) { + for (; *s; s++) + { + if((dvr_flags & DVR_WHITESPACE_IN_TITLE) && + (*s == ' ' || *s == '\t')) + *s = '-'; - if(s[i] == '/') - s[i] = '-'; + else if((dvr_flags & DVR_CLEAN_TITLE) && + ((*s < 32) || (*s > 122) || + (strchr(":\\<>|*?'\"", *s) != NULL))) + *s = '-'; + } +} - else if((dvr_flags & DVR_WHITESPACE_IN_TITLE) && - (s[i] == ' ' || s[i] == '\t')) - s[i] = '-'; +static void +generate_filename_basic(dvr_config_t *cfg, char *filename, size_t filename_size, dvr_entry_t *de) +{ + size_t buffer_size = filename_size; + char *buffer = malloc(buffer_size); + int temp; - else if((dvr_flags & DVR_CLEAN_TITLE) && - ((s[i] < 32) || (s[i] > 122) || - (strchr("/:\\<>|*?'\"", s[i]) != NULL))) - s[i] = '-'; + /* Append per-day directory */ + + if(cfg->dvr_flags & DVR_DIR_PER_DAY) { + struct tm tm; + + localtime_r(&de->de_start, &tm); + size_t day_length = strftime(filename, filename_size, "/%F", &tm); + + if(!day_length) + *filename = '\0'; + else + { + filename += day_length; + filename_size -= day_length; + } + } + + /* Append per-channel directory */ + + if(cfg->dvr_flags & DVR_DIR_PER_CHANNEL) { + + snprintf(buffer, buffer_size, "%s", DVR_CH_NAME(de)); + remove_slashes(buffer); + temp = snprintf(filename, filename_size, "/%s", buffer); + + if(temp > 0) + { + if(temp > filename_size) + temp = filename_size; + filename += temp; + filename_size -= temp; + } + } + + // TODO: per-brand, per-season + + /* Append per-title directory */ + + if(cfg->dvr_flags & DVR_DIR_PER_TITLE) { + + snprintf(buffer, buffer_size, "%s", lang_str_get(de->de_title, NULL)); + remove_slashes(buffer); + temp = snprintf(filename, filename_size, "/%s", buffer); + + if(temp > 0) + { + if(temp > filename_size) + temp = filename_size; + filename += temp; + filename_size -= temp; + } + } + + dvr_make_title(buffer, buffer_size, de); + remove_slashes(buffer); + snprintf(filename, filename_size, "/%s", buffer); + + free(buffer); +} + +/** + * Executes external program to get the filename. Filename is read from standard output. + * + * No character safety checks are made except the last character is ignored if it is '\n' (new line). + */ +static void +generate_filename_external(dvr_config_t *cfg, char *filename, size_t filename_size, dvr_entry_t *de) +{ + char *output; + int output_size; + + if(cfg->dvr_filename_external) + { + output_size = dvr_spawn_proc(de, cfg->dvr_filename_external, &output); + if(output_size > 0) + { + if(output[output_size - 1] == '\n') + output[output_size - 1] = '\0'; + + snprintf(filename, filename_size, "%.*s", output_size, output); + free(output); + } } } @@ -151,87 +249,77 @@ cleanupfilename(char *s, int dvr_flags) static int pvr_generate_filename(dvr_entry_t *de, const streaming_start_t *ss) { - char fullname[1000]; - char path[500]; + char path[1000]; int tally = 0; struct stat st; char filename[1000]; - struct tm tm; + const char *suffix; dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name); - dvr_make_title(filename, sizeof(filename), de); - cleanupfilename(filename,cfg->dvr_flags); + filename[0] = '\0'; - snprintf(path, sizeof(path), "%s", cfg->dvr_storage); + if(cfg->dvr_filename_mode == DVR_FILENAMEMODE_EXTERNAL) + generate_filename_external(cfg, filename, sizeof(filename), de); - /* Remove trailing slash */ + if(filename[0] == '\0') + generate_filename_basic(cfg, filename, sizeof(filename), de); - if (path[strlen(path)-1] == '/') - path[strlen(path)-1] = '\0'; + cleanupfilename(filename, cfg->dvr_flags); - /* Append per-day directory */ + /* Combine filename parts */ + { + int temp = snprintf(path, sizeof(path), "%s", cfg->dvr_storage); + if(temp < sizeof(path)) + { + /* Remove trailing slashes from storage path and leadind slashes from filename */ + while(temp > 0 && path[temp - 1] == '/') + temp--; - if(cfg->dvr_flags & DVR_DIR_PER_DAY) { - localtime_r(&de->de_start, &tm); - strftime(fullname, sizeof(fullname), "%F", &tm); - cleanupfilename(fullname,cfg->dvr_flags); - snprintf(path + strlen(path), sizeof(path) - strlen(path), - "/%s", fullname); - } + char *temp2 = filename; + while(*temp2 == '/') + temp2++; - /* Append per-channel directory */ - - if(cfg->dvr_flags & DVR_DIR_PER_CHANNEL) { - - char *chname = strdup(DVR_CH_NAME(de)); - cleanupfilename(chname,cfg->dvr_flags); - snprintf(path + strlen(path), sizeof(path) - strlen(path), - "/%s", chname); - free(chname); + snprintf(path + temp, sizeof(path) - temp, "/%s", temp2); + } + snprintf(filename, sizeof(filename), "%s", path); } - // TODO: per-brand, per-season - - /* Append per-title directory */ - - if(cfg->dvr_flags & DVR_DIR_PER_TITLE) { - - char *title = strdup(lang_str_get(de->de_title, NULL)); - cleanupfilename(title,cfg->dvr_flags); - snprintf(path + strlen(path), sizeof(path) - strlen(path), - "/%s", title); - free(title); + /* Directory path */ + { + char *pathend = strrchr(path, '/'); + if(!pathend) + pathend = path; + *pathend = '\0'; } - /* */ if(makedirs(path, 0777) != 0) { return -1; } - /* Construct final name */ - - snprintf(fullname, sizeof(fullname), "%s/%s.%s", - path, filename, muxer_suffix(de->de_mux, ss)); + + suffix = muxer_suffix(de->de_mux, ss); + snprintf(path, sizeof(path), "%s.%s", + filename, suffix); while(1) { - if(stat(fullname, &st) == -1) { + if(stat(path, &st) == -1) { tvhlog(LOG_DEBUG, "dvr", "File \"%s\" -- %s -- Using for recording", - fullname, strerror(errno)); + path, strerror(errno)); break; } tvhlog(LOG_DEBUG, "dvr", "Overwrite protection, file \"%s\" exists", - fullname); + path); tally++; - snprintf(fullname, sizeof(fullname), "%s/%s-%d.%s", - path, filename, tally, muxer_suffix(de->de_mux, ss)); + snprintf(path, sizeof(path), "%s-%d.%s", + filename, tally, suffix); } - tvh_str_set(&de->de_filename, fullname); + tvh_str_set(&de->de_filename, path); return 0; } @@ -573,32 +661,35 @@ dvr_thread(void *aux) /** - * + * Execute the given program + * + * If output-variable isn't NULL, store program output in a malloc()ed buffer. + * + * *output will point to the allocated buffer. + * The function will return the size of the buffer or negative value on a failure. */ -static void -dvr_spawn_postproc(dvr_entry_t *de, const char *dvr_postproc) +static int +dvr_spawn_proc(dvr_entry_t *de, const char *prog, char **output) { const char *fmap[256]; char **args; char start[16]; char stop[16]; - char *fbasename; /* filename dup for basename */ + char *fbasename = NULL; /* filename dup for basename */ int i; + int result; - args = htsstr_argsplit(dvr_postproc); + args = htsstr_argsplit(prog); /* no arguments at all */ if(!args[0]) { htsstr_argsplit_free(args); - return; + return -1; } - fbasename = strdup(de->de_filename); snprintf(start, sizeof(start), "%"PRItime_t, de->de_start - de->de_start_extra); snprintf(stop, sizeof(stop), "%"PRItime_t, de->de_stop + de->de_stop_extra); memset(fmap, 0, sizeof(fmap)); - fmap['f'] = de->de_filename; /* full path to recoding */ - fmap['b'] = basename(fbasename); /* basename of recoding */ fmap['c'] = DVR_CH_NAME(de); /* channel name */ fmap['C'] = de->de_creator; /* user who created this recording */ fmap['t'] = lang_str_get(de->de_title, NULL); /* program title */ @@ -609,6 +700,13 @@ dvr_spawn_postproc(dvr_entry_t *de, const char *dvr_postproc) fmap['E'] = stop; /* stop time, unix epoch */ // TODO: brand, season + if(de->de_filename) + { + fbasename = strdup(de->de_filename); + fmap['f'] = de->de_filename; /* full path to recoding */ + fmap['b'] = basename(fbasename); /* basename of recoding */ + } + /* format arguments */ for(i = 0; args[i]; i++) { char *s; @@ -617,11 +715,26 @@ dvr_spawn_postproc(dvr_entry_t *de, const char *dvr_postproc) free(args[i]); args[i] = s; } - - spawnv(args[0], (void *)args); - - free(fbasename); + + if(!output) + result = spawnv(args[0], (void *)args); + else + result = spawn_and_store_stdout(args[0], (void *)args, output); + + if(fbasename) + free(fbasename); htsstr_argsplit_free(args); + + return result; +} + +/** + * + */ +static void +dvr_spawn_postproc(dvr_entry_t *de, const char *dvr_postproc) +{ + dvr_spawn_proc(de, dvr_postproc, NULL); } /** diff --git a/src/webui/extjs.c b/src/webui/extjs.c index 9478725d14..89d41e2e21 100644 --- a/src/webui/extjs.c +++ b/src/webui/extjs.c @@ -1255,6 +1255,9 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque) htsmsg_add_u32(r, "retention", cfg->dvr_retention_days); htsmsg_add_u32(r, "preExtraTime", cfg->dvr_extra_time_pre); htsmsg_add_u32(r, "postExtraTime", cfg->dvr_extra_time_post); + htsmsg_add_u32(r, "filename_mode", cfg->dvr_filename_mode); + if(cfg->dvr_filename_external != NULL) + htsmsg_add_str(r, "filename_external", cfg->dvr_filename_external); htsmsg_add_u32(r, "dayDirs", !!(cfg->dvr_flags & DVR_DIR_PER_DAY)); htsmsg_add_u32(r, "channelDirs", !!(cfg->dvr_flags & DVR_DIR_PER_CHANNEL)); htsmsg_add_u32(r, "channelInTitle", !!(cfg->dvr_flags & DVR_CHANNEL_IN_TITLE)); @@ -1296,6 +1299,10 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque) if((s = http_arg_get(&hc->hc_req_args, "postExtraTime")) != NULL) dvr_extra_time_post_set(cfg,atoi(s)); + if((s = http_arg_get(&hc->hc_req_args, "filename_mode")) != NULL) + dvr_filename_mode_set(cfg,atoi(s)); + if((s = http_arg_get(&hc->hc_req_args, "filename_external")) != NULL) + dvr_filename_external_set(cfg,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) diff --git a/src/webui/static/app/dvr.js b/src/webui/static/app/dvr.js index 02021eb3b8..70f9a84f20 100644 --- a/src/webui/static/app/dvr.js +++ b/src/webui/static/app/dvr.js @@ -729,7 +729,8 @@ tvheadend.dvrsettings = function() { }, [ 'storage', 'postproc', 'retention', 'dayDirs', 'channelDirs', 'channelInTitle', 'container', 'dateInTitle', 'timeInTitle', 'preExtraTime', 'postExtraTime', 'whitespaceInTitle', 'titleDirs', - 'episodeInTitle', 'cleanTitle', 'tagFiles', 'commSkip' ]); + 'episodeInTitle', 'cleanTitle', 'tagFiles', 'commSkip', + 'filename_mode', 'filename_external' ]); var confcombo = new Ext.form.ComboBox({ store : tvheadend.configNames, @@ -750,16 +751,17 @@ tvheadend.dvrsettings = function() { disabled : true }); - var confpanel = new Ext.FormPanel({ - title : 'Digital Video Recorder', - iconCls : 'drive', - border : false, + /* + * General panel + */ + var confpanel = new Ext.Panel({ + title : 'General recording settings', + border : true, + style : 'margin:10px', bodyStyle : 'padding:15px', anchor : '100% 50%', labelAlign : 'right', labelWidth : 250, - waitMsgTarget : true, - reader : confreader, defaultType : 'textfield', layout : 'form', items : [ { @@ -790,6 +792,51 @@ tvheadend.dvrsettings = function() { fieldLabel : 'Extra time after recordings (minutes)', name : 'postExtraTime' }), new Ext.form.Checkbox({ + fieldLabel : 'Remove all unsafe characters from filename', + name : 'cleanTitle' + }), new Ext.form.Checkbox({ + fieldLabel : 'Replace whitespace in title with \'-\'', + name : 'whitespaceInTitle' + }), new Ext.form.Checkbox({ + fieldLabel : 'Tag files with metadata', + name : 'tagFiles' + }), new Ext.form.Checkbox({ + fieldLabel : 'Skip commercials', + name : 'commSkip' + }), { + width : 300, + fieldLabel : 'Post-processor command', + name : 'postproc' + } ] + }); + + var filenameModeButton = new Ext.CycleButton({ + prependText: 'Mode: ', + showText: true, + changeHandler: function(cycleButton, item) { filenamingpanel.layout.setActiveItem(item.itemId); }, + items: [{ + text: 'Basic', + itemId: '1' + }, { + text: 'External program', + itemId: '2' + } ] + }) + + /* + * Basic filenaming panel + */ + var filenaming_basic = new Ext.Panel({ + id: '1', /* has to be equal to DVR_FILENAMEMODE_BASIC in src/dvr/dvr.h */ + width: '100%', + height: '100%', + labelAlign : 'right', + labelWidth : 200, + border: false, + bodyStyle : 'padding:15px', + layout : 'form', + items : [ + new Ext.form.Checkbox({ fieldLabel : 'Make subdirectories per day', name : 'dayDirs' }), new Ext.form.Checkbox({ @@ -810,23 +857,57 @@ tvheadend.dvrsettings = function() { }), new Ext.form.Checkbox({ fieldLabel : 'Include episode in filename', name : 'episodeInTitle' - }), new Ext.form.Checkbox({ - fieldLabel : 'Remove all unsafe characters from filename', - name : 'cleanTitle' - }), new Ext.form.Checkbox({ - fieldLabel : 'Replace whitespace in title with \'-\'', - name : 'whitespaceInTitle' - }), new Ext.form.Checkbox({ - fieldLabel : 'Tag files with metadata', - name : 'tagFiles' - }), new Ext.form.Checkbox({ - fieldLabel : 'Skip commercials', - name : 'commSkip' - }), { - width : 300, - fieldLabel : 'Post-processor command', - name : 'postproc' - } ], + }) ] + }); + + /* + * External program filenaming panel + */ + var filenaming_external = new Ext.Panel({ + id: '2', /* has to be equal to DVR_FILENAMEMODE_EXTERNAL in src/dvr/dvr.h */ + width: '100%', + height: '100%', + labelAlign : 'right', + labelWidth : 75, + border: false, + bodyStyle : 'padding:15px', + layout : 'form', + items : [ + new Ext.form.TextField({ + fieldLabel : 'Command', + name : 'filename_external', + width: 380 + }) ] + }); + + /* + * Filename panel + */ + var filenamingpanel = new Ext.Panel({ + title : 'Filename settings', + border : true, + style : 'margin:10px', + anchor : '100% 50%', + width: 500, + height: 310, + layout: 'card', + waitMsgTarget : true, + items : [ filenaming_basic, filenaming_external ], + tbar: [ filenameModeButton ] + }); + + /** + * Main panel + */ + var panel = new Ext.form.FormPanel({ + title : 'Digital Video Recorder', + iconCls : 'drive', + layout : 'column', + autoScroll : true, + autoHeight: true, + waitMsgTarget : true, + reader : confreader, + items : [ confpanel, filenamingpanel ], tbar : [ confcombo, { tooltip : 'Save changes made to dvr configuration below', iconCls : 'save', @@ -840,36 +921,44 @@ tvheadend.dvrsettings = function() { } ] }); + confcombo.on('select', function() { + if (confcombo.getValue() == '') delButton.disable(); + else delButton.enable(); + loadConfig(); + }); + + panel.on('render', function() { + loadConfig(); + }); + function loadConfig() { - confpanel.getForm().load({ + panel.getForm().load({ url : 'dvr', params : { 'op' : 'loadSettings', 'config_name' : confcombo.getValue() }, success : function(form, action) { - confpanel.enable(); + var filename_mode = action.result.data.filename_mode; + if (!filename_mode) + filename_mode = '1'; + filenameModeButton.setActiveItem(filename_mode); + + panel.enable(); } }); } - confcombo.on('select', function() { - if (confcombo.getValue() == '') delButton.disable(); - else delButton.enable(); - loadConfig(); - }); - - confpanel.on('render', function() { - loadConfig(); - }); - function saveChanges() { var config_name = confcombo.getValue(); - confpanel.getForm().submit({ + var filename_mode = filenameModeButton.getActiveItem().getItemId(); + + panel.getForm().submit({ url : 'dvr', params : { 'op' : 'saveSettings', - 'config_name' : config_name + 'config_name' : config_name, + 'filename_mode' : filename_mode }, waitMsg : 'Saving Data...', success : function(form, action) { @@ -892,7 +981,7 @@ tvheadend.dvrsettings = function() { function deleteAction(btn) { if (btn == 'yes') { - confpanel.getForm().submit({ + panel.getForm().submit({ url : 'dvr', params : { 'op' : 'deleteSettings', @@ -910,5 +999,5 @@ tvheadend.dvrsettings = function() { } } - return confpanel; + return panel; }