diff --git a/docs/html/config_dvr.html b/docs/html/config_dvr.html index adce28bc..68a44ee7 100644 --- a/docs/html/config_dvr.html +++ b/docs/html/config_dvr.html @@ -8,11 +8,14 @@

Configuration options: +

-
Recording system path -
Path to where Tvheadend will write recorded events. If components of - the path does not exist, Tvheadend will try to create them. - + +

+
+ DVR Behaviour +
+
Media container
Select the container format used to store recordings. @@ -34,88 +37,18 @@
Combination of two above variants.
- -
Rewrite PAT in passthrough mode -
Rewrite the original Program Association Table to only include - the active service. When this option is disabled, Tvheadend will - write the original PAT as broadcast, which lists all services from - the original multiplex. - -
Rewrite PMT in passthrough mode -
Generate a Program Map Table only listing the streams actually in - the output transport stream. When this option is disabled, - Tvheadend will write the original Program Map Table as - broadcast, which may include references to excluded streams such - as data and ECMs. - +
DVR Log retention time (days) -
Time that Tvheadend will keep information about the recording in - its internal database. Notice that the actual recorded file will not - be deleted when the log entry is deleted. +
Time that Tvheadend will keep information about the recording in its internal database. Notice that the actual recorded file will not be deleted when the log entry is deleted.
Extra time before recordings (minutes) -
Specify the number of minutes to record before the events scheduled - start time. Used to cope with small scheduling errors. +
Specify the number of minutes to record before the events scheduled start time. Used to cope with small scheduling errors.
Extra time after recordings (minutes) -
Specify the number of minutes to record after the events scheduled - stop time. Used to cope with small scheduling errors. - -
Make sub-directories per day -
If checked, Tvheadend will create a new directory per day in the - recording system path. Only days when anything is recorded will be - created. The format of the directory will be 'YYYY-MM-DD' (ISO standard) - -
Make sub-directories per channel -
If checked, Tvheadend will create a directory per channel when storing - events. If both this and the 'directory per day' checkbox is enabled, - the date-directory will be parent to the per-channel directory. - -
Make sub-directories per title -
If checked, Tvheadend will create a directory per title when storing - events. If the day/channel directory checkboxes are also enabled, those - directories will be parents of this directory. - -
Include channel name in title -
If checked, Tvheadend will include the name of the channel in the - event title. This applies to both the titled stored in the file - and to the file name itself. - -
Include date in title -
If checked, Tvheadend will include the date for the recording in the - event title. This applies to both the titled stored in the file - and to the file name itself. - -
Include time in title -
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 file name itself. - -
Include episode in title -
If checked, Tvheadend will include the season and episode in the - title (if such info is available). - -
Remove all unsafe characters from filename -
If checked, all characters that could possibly cause problems for - filenaming will be removed. - -
Replace whitespace in title with '-' -
If checked, all whitespace characters will be replaced with '-'. - -
Tag files with metadata -
If checked, media containers that support metadata will be tagged with - the metadata associated with the event being recorded. - -
Skip commercials -
If checked, commercials will be dropped from the recordings. At the - moment, commercial detection only works for the swedish channel TV4. +
Specify the number of minutes to record after the events scheduled stop time. Used to cope with small scheduling errors.
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. +
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:
@@ -133,7 +66,100 @@
Example usage: /path/to/ffmpeg -i %f -vcodec libx264 -acodec copy "/path/with white space/%b"
You need to use quotes or escape white spaces if you want white spaces in an argument. + +

+
+ Recording File Options +
+ +
Recording system path +
Path to where Tvheadend will write recorded events. If components of the path does not exist, Tvheadend will try to create them. + +
File permissions +
The permissions to be set on the resultant recording files. This is useful if you need to manipulate the files after recording under a different user ID, e.g. to chop out commercials. + +
+ +
+ + + +
Common examples:
0644 == rw-r--r--
0664 == rw-rw-r-- (default)
0666 == rw-rw-rw-
+ + Note that the applicable umask applies, so 0666 with umask 0022 will produce 0644 (rw-r--r--). + + See also Directory permissions in Subdirectory Options. + +
Rewrite PAT in passthrough mode +
Rewrite the original Program Association Table to only include the active service. When this option is disabled, Tvheadend will write the original PAT as broadcast, which lists all services from the original multiplex. + +
Rewrite PMT in passthrough mode +
Generate a Program Map Table only listing the streams actually in the output transport stream. When this option is disabled, Tvheadend will write the original Program Map Table as broadcast, which may include references to excluded streams such as data and ECMs. + +
Tag files with metadata +
If checked, media containers that support metadata will be tagged with the metadata associated with the event being recorded. + +
Skip commercials +
If checked, commercials will be dropped from the recordings. At the moment, commercial detection only works for the swedish channel TV4. + +

+
+ Subdirectory Options +
+ +
Directory permissions +
The permissions to be set on any sub-directories created for recordings. This is useful if you need to manipulate the files after recording under a different user ID, e.g. to chop out commercials. + + + +
+ + + +
Common examples:
0755 == rwxr-xr-x
0775 == rwxrwxr-x (default)
0777 == rwxrwxrwx
+ + Note that the applicable umask applies, so 0777 with umask 0022 will produce 0755 (rwxr-xr-x). + + See also File permissions in Recording File Options. + +
Make sub-directories per day +
If checked, create a new directory per day in the recording system path. Only days when anything is recorded will be created. The format of the directory will be 'YYYY-MM-DD' (ISO standard) + +
Make sub-directories per channel +
If checked, create a directory per channel when storing events. If both this and the 'directory per day' checkbox is enabled, the date-directory will be parent to the per-channel directory. + +
Make sub-directories per title +
If checked, create a directory per title when storing events. If the day/channel directory checkboxes are also enabled, those directories will be parents of this directory. + +

+
+ Filename Options +
+ +
Include channel name in title +
If checked, include the name of the channel in the event title. This applies to both the titled stored in the file and to the file name itself. + +
Include date in title +
If checked, include the date for the recording in the event title. This applies to both the titled stored in the file and to the file name itself. + +
Include time in title +
If checked, include the time for the recording in the event title. This applies to both the titled stored in the file and to the file name itself. + +
Include episode in title +
If checked, include the season and episode in the title (if such info is available). + +
Include subtitle in title +
If checked, include the episode subtitle in the title (if such info is available). + +
Put episode in filename before date and time +
If checked, insert the episode number before the data and time rather than after (assumes Include date, Include time and Include episode options are set). + +
Remove all unsafe characters from filename +
If checked, all characters that could possibly cause problems for filenaming will be removed. + +
Replace whitespace in title with '-' +
If checked, all whitespace characters will be replaced with '-'. + - Changes to any of these settings must be confirmed by pressing the - 'Save configuration' button before taking effect. + Changes to any of these settings must be confirmed by pressing the 'Save configuration' button before taking effect. diff --git a/src/dvr/dvr.h b/src/dvr/dvr.h index 43460634..16f027ff 100644 --- a/src/dvr/dvr.h +++ b/src/dvr/dvr.h @@ -332,6 +332,10 @@ void dvr_storage_set(dvr_config_t *cfg, const char *storage); void dvr_container_set(dvr_config_t *cfg, const char *container); +void dvr_file_permissions_set(dvr_config_t *cfg, int permissions); + +void dvr_directory_permissions_set(dvr_config_t *cfg, int permissions); + void dvr_mux_cache_set(dvr_config_t *cfg, int mcache); void dvr_postproc_set(dvr_config_t *cfg, const char *postproc); diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c index 514f2485..0649ede9 100644 --- a/src/dvr/dvr_db.c +++ b/src/dvr/dvr_db.c @@ -1140,6 +1140,17 @@ dvr_init(void) htsmsg_get_u32(m, "retention-days", &cfg->dvr_retention_days); tvh_str_set(&cfg->dvr_storage, htsmsg_get_str(m, "storage")); +/* + * Convert 0xxx format permission strings to integer for internal use + * Note no checking that strtol won't overflow int - this should never happen with three-digit numbers + */ + + if ((s = htsmsg_get_str(m, "file-permissions"))) + cfg->dvr_muxcnf.m_file_permissions = (int)strtol(s,NULL,0); + + if ((s = htsmsg_get_str(m, "directory-permissions"))) + cfg->dvr_muxcnf.m_directory_permissions = (int)strtol(s,NULL,0); + if(!htsmsg_get_u32(m, "day-dir", &u32) && u32) cfg->dvr_flags |= DVR_DIR_PER_DAY; @@ -1319,6 +1330,11 @@ dvr_config_create(const char *name) /* dup detect */ cfg->dvr_dup_detect_episode = 1; // detect dup episodes + /* Default recording file and directory permissions */ + + cfg->dvr_muxcnf.m_file_permissions = 0664; + cfg->dvr_muxcnf.m_directory_permissions = 0775; + LIST_INSERT_HEAD(&dvrconfigs, cfg, config_link); return LIST_FIRST(&dvrconfigs); @@ -1357,9 +1373,20 @@ static void dvr_save(dvr_config_t *cfg) { htsmsg_t *m = htsmsg_create_map(); + char buffer[5]; // Permissions buffer: leading zero, three octal digits plus terminating null + if (cfg->dvr_config_name != NULL && strlen(cfg->dvr_config_name) != 0) htsmsg_add_str(m, "config_name", cfg->dvr_config_name); htsmsg_add_str(m, "storage", cfg->dvr_storage); + +/* Convert permissions to 0xxx octal format and output */ + + snprintf(buffer,sizeof(buffer),"%04o",cfg->dvr_muxcnf.m_file_permissions); + htsmsg_add_str(m, "file-permissions", buffer); + + snprintf(buffer,sizeof(buffer),"%04o",cfg->dvr_muxcnf.m_directory_permissions); + htsmsg_add_str(m, "directory-permissions", buffer); + htsmsg_add_u32(m, "container", cfg->dvr_mc); htsmsg_add_u32(m, "cache", cfg->dvr_muxcnf.m_cache); htsmsg_add_u32(m, "rewrite-pat", @@ -1404,6 +1431,32 @@ dvr_storage_set(dvr_config_t *cfg, const char *storage) dvr_save(cfg); } +/** + * + */ +void +dvr_file_permissions_set(dvr_config_t *cfg, int permissions) +{ + if(cfg->dvr_muxcnf.m_file_permissions == permissions) + return; + + cfg->dvr_muxcnf.m_file_permissions = permissions; + dvr_save(cfg); +} + +/** + * + */ +void +dvr_directory_permissions_set(dvr_config_t *cfg, int permissions) +{ + if(cfg->dvr_muxcnf.m_directory_permissions == permissions) + return; + + cfg->dvr_muxcnf.m_directory_permissions = permissions; + dvr_save(cfg); +} + /** * */ @@ -1468,7 +1521,7 @@ dvr_retention_set(dvr_config_t *cfg, int days) cfg->dvr_retention_days = days; - /* Also, rearm all timres */ + /* Also, rearm all timers */ LIST_FOREACH(de, &dvrentries, de_global_link) if(de->de_sched_state == DVR_COMPLETED) diff --git a/src/dvr/dvr_rec.c b/src/dvr/dvr_rec.c index de6169e9..25814e81 100644 --- a/src/dvr/dvr_rec.c +++ b/src/dvr/dvr_rec.c @@ -204,9 +204,7 @@ pvr_generate_filename(dvr_entry_t *de, const streaming_start_t *ss) free(title); } - - /* */ - if(makedirs(path, 0755) != 0) { + if(makedirs(path, cfg->dvr_muxcnf.m_directory_permissions) != 0) { return -1; } @@ -324,6 +322,7 @@ dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss) "adapter: \"%s\", " "network: \"%s\", mux: \"%s\", provider: \"%s\", " "service: \"%s\"", + de->de_filename ?: lang_str_get(de->de_title, NULL), si->si_adapter ?: "", si->si_network ?: "", diff --git a/src/muxer.h b/src/muxer.h index 9f147d58..1c0a85ae 100644 --- a/src/muxer.h +++ b/src/muxer.h @@ -45,8 +45,16 @@ typedef enum { /* Muxer configuration used when creating a muxer. */ typedef struct muxer_config { - int m_flags; + int m_flags; muxer_cache_type_t m_cache; + +/* + * directory_permissions should really be in dvr.h as it's not really needed for the muxer + * but it's kept with file_permissions for neatness + */ + + int m_file_permissions; + int m_directory_permissions; } muxer_config_t; struct muxer; diff --git a/src/muxer/muxer_pass.c b/src/muxer/muxer_pass.c index 64b53411..906099a6 100644 --- a/src/muxer/muxer_pass.c +++ b/src/muxer/muxer_pass.c @@ -378,7 +378,10 @@ pass_muxer_open_file(muxer_t *m, const char *filename) int fd; pass_muxer_t *pm = (pass_muxer_t*)m; - fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); + tvhtrace("pass", "Creating file \"%s\" with file permissions \"%o\"", filename, pm->m_config.m_file_permissions); + + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, pm->m_config.m_file_permissions); + if(fd < 0) { pm->pm_error = errno; tvhlog(LOG_ERR, "pass", "%s: Unable to create file, open failed -- %s", diff --git a/src/muxer/muxer_tvh.c b/src/muxer/muxer_tvh.c index 44768dd8..715328ce 100644 --- a/src/muxer/muxer_tvh.c +++ b/src/muxer/muxer_tvh.c @@ -138,7 +138,7 @@ tvh_muxer_open_file(muxer_t *m, const char *filename) { tvh_muxer_t *tm = (tvh_muxer_t*)m; - if(mk_mux_open_file(tm->tm_ref, filename)) { + if(mk_mux_open_file(tm->tm_ref, filename, tm->m_config.m_file_permissions)) { tm->m_errors++; return -1; } diff --git a/src/muxer/tvh/mkmux.c b/src/muxer/tvh/mkmux.c index c5a2a8fb..5f366717 100644 --- a/src/muxer/tvh/mkmux.c +++ b/src/muxer/tvh/mkmux.c @@ -1046,11 +1046,14 @@ mk_mux_open_stream(mk_mux_t *mkm, int fd) * */ int -mk_mux_open_file(mk_mux_t *mkm, const char *filename) +mk_mux_open_file(mk_mux_t *mkm, const char *filename, int permissions) { int fd; - fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); + tvhtrace("mkv", "Creating file \"%s\" with file permissions \"%o\"", filename, permissions); + + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, permissions); + if(fd < 0) { mkm->error = errno; tvhlog(LOG_ERR, "mkv", "%s: Unable to create file, open failed -- %s", diff --git a/src/muxer/tvh/mkmux.h b/src/muxer/tvh/mkmux.h index 6840d3f1..a107ff40 100644 --- a/src/muxer/tvh/mkmux.h +++ b/src/muxer/tvh/mkmux.h @@ -30,7 +30,7 @@ struct event; mk_mux_t *mk_mux_create(muxer_t *m, int webm); -int mk_mux_open_file (mk_mux_t *mkm, const char *filename); +int mk_mux_open_file (mk_mux_t *mkm, const char *filename, int permissions); int mk_mux_open_stream(mk_mux_t *mkm, int fd); int mk_mux_init(mk_mux_t *mkm, const char *title, diff --git a/src/utils.c b/src/utils.c index 6278facc..b1459757 100644 --- a/src/utils.c +++ b/src/utils.c @@ -441,6 +441,7 @@ makedirs ( const char *inpath, int mode ) path[x] = 0; if (stat(path, &st)) { err = mkdir(path, mode); + tvhtrace("settings", "Creating directory \"%s\" with octal permissions \"%o\"", path, mode); } else { err = S_ISDIR(st.st_mode) ? 0 : 1; errno = ENOTDIR; diff --git a/src/webui/extjs.c b/src/webui/extjs.c index 378f2e17..d83e5039 100755 --- a/src/webui/extjs.c +++ b/src/webui/extjs.c @@ -971,6 +971,7 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque) int flags = 0; dvr_config_t *cfg; epg_broadcast_t *e; + char buffer[5]; // Permissions buffer: leading zero, three octal digits plus terminating null if(op == NULL) op = "loadSettings"; @@ -1127,6 +1128,14 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque) r = htsmsg_create_map(); htsmsg_add_str(r, "storage", cfg->dvr_storage); htsmsg_add_str(r, "container", muxer_container_type2txt(cfg->dvr_mc)); + +/* Convert integer permissions to an octal-format 0xxx string and store it in the config file */ + + snprintf(buffer,sizeof(buffer),"%04o",cfg->dvr_muxcnf.m_file_permissions); + htsmsg_add_str(r, "filePermissions", buffer); + snprintf(buffer,sizeof(buffer),"%04o",cfg->dvr_muxcnf.m_directory_permissions); + htsmsg_add_str(r, "dirPermissions", buffer); + htsmsg_add_u32(r, "cache", cfg->dvr_muxcnf.m_cache); htsmsg_add_u32(r, "rewritePAT", !!(cfg->dvr_muxcnf.m_flags & MC_REWRITE_PAT)); @@ -1168,6 +1177,17 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque) if((s = http_arg_get(&hc->hc_req_args, "container")) != NULL) dvr_container_set(cfg,s); +/* + * Convert 0xxx format permission strings to integer for internal use + * Note no checking that strtol won't overflow int - this should never happen with three-digit numbers + */ + + if((s = http_arg_get(&hc->hc_req_args, "filePermissions")) != NULL) + dvr_file_permissions_set(cfg,(int)strtol(s,NULL,0)); + + if((s = http_arg_get(&hc->hc_req_args, "dirPermissions")) != NULL) + dvr_directory_permissions_set(cfg,(int)strtol(s,NULL,0)); + if((s = http_arg_get(&hc->hc_req_args, "cache")) != NULL) dvr_mux_cache_set(cfg,atoi(s)); diff --git a/src/webui/static/app/dvr.js b/src/webui/static/app/dvr.js index 7a97a183..9b9163bf 100644 --- a/src/webui/static/app/dvr.js +++ b/src/webui/static/app/dvr.js @@ -744,10 +744,11 @@ tvheadend.dvrsettings = function() { var confreader = new Ext.data.JsonReader({ root : 'dvrSettings' - }, [ 'storage', 'postproc', 'retention', 'dayDirs', 'channelDirs', + }, [ 'storage', 'filePermissions', 'dirPermissions', 'postproc', 'retention', 'dayDirs', 'channelDirs', 'channelInTitle', 'container', 'cache', 'dateInTitle', 'timeInTitle', 'preExtraTime', 'postExtraTime', 'whitespaceInTitle', 'titleDirs', - 'episodeInTitle', 'cleanTitle', 'tagFiles', 'commSkip', 'subtitleInTitle', 'episodeBeforeDate', 'rewritePAT', 'rewritePMT' ]); + 'episodeInTitle', 'cleanTitle', 'tagFiles', 'commSkip', 'subtitleInTitle', + 'episodeBeforeDate', 'rewritePAT', 'rewritePMT' ]); var confcombo = new Ext.form.ComboBox({ store : tvheadend.configNames, @@ -768,6 +769,215 @@ tvheadend.dvrsettings = function() { disabled : true }); +/* Config panel variables */ + +/* DVR Behaviour */ + + var recordingContainer = new Ext.form.ComboBox({ + store : tvheadend.containers, + fieldLabel : 'Media container', + triggerAction : 'all', + displayField : 'description', + valueField : 'name', + editable : false, + width : 200, + hiddenName : 'container' + }); + + var cacheScheme = new Ext.form.ComboBox({ + store : tvheadend.caches, + fieldLabel : 'Cache scheme', + triggerAction : 'all', + displayField : 'description', + valueField : 'index', + editable : false, + width : 200, + hiddenName : 'cache' + }); + + var logRetention = new Ext.form.NumberField({ + allowNegative : false, + allowDecimals : false, + minValue : 1, + fieldLabel : 'DVR Log retention time (days)', + name : 'retention' + }); + + var timeBefore = new Ext.form.NumberField({ + allowDecimals : false, + fieldLabel : 'Extra time before recordings (minutes)', + name : 'preExtraTime' + }); + + var timeAfter = new Ext.form.NumberField({ + allowDecimals : false, + fieldLabel : 'Extra time after recordings (minutes)', + name : 'postExtraTime' + }); + + var postProcessing = new Ext.form.TextField({ + width : 300, + fieldLabel : 'Post-processor command', + name : 'postproc' + }); + +/* Recording File Options */ + + var recordingPath = new Ext.form.TextField({ + width : 300, + fieldLabel : 'Recording system path', + name : 'storage' + }); + +/* NB: recordingPermissions is defined as a TextField for validation purposes (leading zeros), but is ultimately a number */ + + var recordingPermissions = new Ext.form.TextField({ + regex : /^[0][0-7]{3}$/, + maskRe : /[0-7]/, + width : 100, + allowBlank : false, + blankText : 'You must provide a value - use octal chmod notation, e.g. 0664', + fieldLabel : 'File permissions (octal, e.g. 0664)', + name : 'filePermissions' + }); + +/* TO DO - Add 'override user umask?' option, then trigger fchmod in mkmux.c, muxer_pass.c after file created */ + + var PATrewrite = new Ext.form.Checkbox({ + fieldLabel : 'Rewrite PAT in passthrough mode', + name : 'rewritePAT' + }); + + var PMTrewrite = new Ext.form.Checkbox({ + fieldLabel : 'Rewrite PMT in passthrough mode', + name : 'rewritePMT' + }); + + var tagMetadata = new Ext.form.Checkbox({ + fieldLabel : 'Tag files with metadata', + name : 'tagFiles' + }); + + var skipCommercials = new Ext.form.Checkbox({ + fieldLabel : 'Skip commercials', + name : 'commSkip' + }); + +/* Subdirectories and filename handling */ + +/* NB: directoryPermissions is defined as a TextField for validation purposes (leading zeros), but is ultimately a number */ + + var directoryPermissions = new Ext.form.TextField({ + regex : /^[0][0-7]{3}$/, + maskRe : /[0-7]/, + width : 100, + allowBlank : false, + blankText : 'You must provide a value - use octal chmod notation, e.g. 0775', + fieldLabel : 'Directory permissions (octal, e.g. 0775)', + name : 'dirPermissions' + }); + +/* TO DO - Add 'override user umask?' option, then trigger fchmod in utils.c after directory created */ + + var dirsPerDay = new Ext.form.Checkbox({ + fieldLabel : 'Make subdirectories per day', + name : 'dayDirs' + }); + + var dirsPerChannel = new Ext.form.Checkbox({ + fieldLabel : 'Make subdirectories per channel', + name : 'channelDirs' + }); + + var dirsPerTitle = new Ext.form.Checkbox({ + fieldLabel : 'Make subdirectories per title', + name : 'titleDirs' + }); + + var incChannelInTitle = new Ext.form.Checkbox({ + fieldLabel : 'Include channel name in filename', + name : 'channelInTitle' + }); + + var incDateInTitle = new Ext.form.Checkbox({ + fieldLabel : 'Include date in filename', + name : 'dateInTitle' + }); + + var incTimeInTitle = new Ext.form.Checkbox({ + fieldLabel : 'Include time in filename', + name : 'timeInTitle' + }); + + var incEpisodeInTitle = new Ext.form.Checkbox({ + fieldLabel : 'Include episode in filename', + name : 'episodeInTitle' + }); + + var incSubtitleInTitle = new Ext.form.Checkbox({ + fieldLabel : 'Include subtitle in filename', + name : 'subtitleInTitle' + }); + + var episodeFirst = new Ext.form.Checkbox({ + fieldLabel : 'Put episode in filename before date and time', + name : 'episodeBeforeDate' + }); + + var stripUnsafeChars = new Ext.form.Checkbox({ + fieldLabel : 'Remove all unsafe characters from filename', + name : 'cleanTitle' + }); + + var stripWhitespace = new Ext.form.Checkbox({ + fieldLabel : 'Replace whitespace in title with \'-\'', + name : 'whitespaceInTitle' + }); + + +/* Sub-Panel - DVR behaviour */ + + var DVRBehaviour = new Ext.form.FieldSet({ + title: 'DVR Behaviour', + width: 700, + autoHeight: true, + collapsible: true, + items : [ recordingContainer, cacheScheme, logRetention, timeBefore, timeAfter, postProcessing ] + }); + +/* Sub-Panel - File Output */ + + var FileOutputPanel = new Ext.form.FieldSet({ + title: 'Recording File Options', + width: 700, + autoHeight: true, + collapsible: true, + items : [ recordingPath, recordingPermissions, PATrewrite, PMTrewrite, tagMetadata, skipCommercials ] + }); + +/* Sub-Panel - Directory operations */ + + var DirHandlingPanel = new Ext.form.FieldSet({ + title: 'Subdirectory Options', + width: 700, + autoHeight: true, + collapsible: true, + items : [ directoryPermissions, dirsPerDay, dirsPerChannel, dirsPerTitle ] + }); + +/* Sub-Panel - File operations */ + + var FileHandlingPanel = new Ext.form.FieldSet({ + title: 'Filename Options', + width: 700, + autoHeight: true, + collapsible: true, + items : [ incChannelInTitle, incDateInTitle, incTimeInTitle, incEpisodeInTitle, + incSubtitleInTitle, episodeFirst, stripUnsafeChars, stripWhitespace ] + }); + +/* Main (form) panel */ + var confpanel = new Ext.FormPanel({ title : 'Digital Video Recorder', iconCls : 'drive', @@ -780,92 +990,8 @@ tvheadend.dvrsettings = function() { reader : confreader, defaultType : 'textfield', layout : 'form', - items : [ { - width : 300, - fieldLabel : 'Recording system path', - name : 'storage' - }, new Ext.form.ComboBox({ - store : tvheadend.containers, - fieldLabel : 'Media container', - triggerAction : 'all', - displayField : 'description', - valueField : 'name', - editable : false, - width : 200, - hiddenName : 'container' - }), new Ext.form.ComboBox({ - store : tvheadend.caches, - fieldLabel : 'Cache scheme', - triggerAction : 'all', - displayField : 'description', - valueField : 'index', - editable : false, - width : 200, - hiddenName : 'cache' - }), new Ext.form.Checkbox({ - fieldLabel : 'Rewrite PAT in passthrough mode', - name : 'rewritePAT' - }), new Ext.form.Checkbox({ - fieldLabel : 'Rewrite PMT in passthrough mode', - name : 'rewritePMT' - }), new Ext.form.NumberField({ - allowNegative : false, - allowDecimals : false, - minValue : 1, - fieldLabel : 'DVR Log retention time (days)', - name : 'retention' - }), new Ext.form.NumberField({ - allowDecimals : false, - fieldLabel : 'Extra time before recordings (minutes)', - name : 'preExtraTime' - }), new Ext.form.NumberField({ - allowDecimals : false, - fieldLabel : 'Extra time after recordings (minutes)', - name : 'postExtraTime' - }), 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 : 'Make subdirectories per title', - name : 'titleDirs' - }), new Ext.form.Checkbox({ - fieldLabel : 'Include channel name in filename', - name : 'channelInTitle' - }), new Ext.form.Checkbox({ - fieldLabel : 'Include date in filename', - name : 'dateInTitle' - }), new Ext.form.Checkbox({ - fieldLabel : 'Include time in filename', - name : 'timeInTitle' - }), 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' - }), new Ext.form.Checkbox({ - fieldLabel : 'Include subtitle in filename', - name : 'subtitleInTitle' - }), new Ext.form.Checkbox({ - fieldLabel : 'Put episode in filename before date and time', - name : 'episodeBeforeDate' - }), { - width : 300, - fieldLabel : 'Post-processor command', - name : 'postproc' - } ], + items : [ DVRBehaviour, FileOutputPanel, DirHandlingPanel, FileHandlingPanel ], + tbar : [ confcombo, { tooltip : 'Save changes made to dvr configuration below', iconCls : 'save',