diff --git a/docs/html/config_dvr.html b/docs/html/config_dvr.html
index ef265ebd..fd82ab2a 100644
--- a/docs/html/config_dvr.html
+++ b/docs/html/config_dvr.html
@@ -95,6 +95,9 @@
See also Directory permissions in Subdirectory Options.
+
Filename charset
+ Character set for the created filename. Tvheadend will try to approximate characters to similarly looking ones.
+
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.
diff --git a/src/dvr/dvr.h b/src/dvr/dvr.h
index 16f027ff..fac68f1b 100644
--- a/src/dvr/dvr.h
+++ b/src/dvr/dvr.h
@@ -31,6 +31,8 @@ typedef struct dvr_config {
char *dvr_storage;
uint32_t dvr_retention_days;
int dvr_flags;
+ char *dvr_charset;
+ char *dvr_charset_id;
char *dvr_postproc;
int dvr_extra_time_pre;
int dvr_extra_time_post;
@@ -330,6 +332,8 @@ void dvr_entry_dec_ref(dvr_entry_t *de);
void dvr_storage_set(dvr_config_t *cfg, const char *storage);
+void dvr_charset_set(dvr_config_t *cfg, const char *charset);
+
void dvr_container_set(dvr_config_t *cfg, const char *container);
void dvr_file_permissions_set(dvr_config_t *cfg, int permissions);
diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c
index 0649ede9..147b6750 100644
--- a/src/dvr/dvr_db.c
+++ b/src/dvr/dvr_db.c
@@ -29,6 +29,7 @@
#include "notify.h"
#include "htsp_server.h"
#include "streaming.h"
+#include "intlconv.h"
static int de_tally;
@@ -163,6 +164,22 @@ dvr_entry_notify(dvr_entry_t *de)
}
+/**
+ *
+ */
+static void
+dvr_charset_update(dvr_config_t *cfg, const char *charset)
+{
+ const char *s, *id;
+
+ free(cfg->dvr_charset);
+ free(cfg->dvr_charset_id);
+ s = charset ? charset : "ASCII";
+ id = intlconv_charset_id(s, 1, 1);
+ cfg->dvr_charset = strdup(s);
+ cfg->dvr_charset_id = id ? strdup(id) : NULL;
+}
+
/**
*
*/
@@ -1190,7 +1207,9 @@ dvr_init(void)
if(!htsmsg_get_u32(m, "episode-before-date", &u32) && u32)
cfg->dvr_flags |= DVR_EPISODE_BEFORE_DATE;
- tvh_str_set(&cfg->dvr_postproc, htsmsg_get_str(m, "postproc"));
+ dvr_charset_update(cfg, htsmsg_get_str(m, "charset"));
+
+ tvh_str_set(&cfg->dvr_postproc, htsmsg_get_str(m, "postproc"));
}
htsmsg_destroy(l);
@@ -1245,6 +1264,8 @@ dvr_done(void)
pthread_mutex_lock(&global_lock);
while ((cfg = LIST_FIRST(&dvrconfigs)) != NULL) {
LIST_REMOVE(cfg, config_link);
+ free(cfg->dvr_charset_id);
+ free(cfg->dvr_charset);
free(cfg->dvr_storage);
free(cfg->dvr_config_name);
free(cfg);
@@ -1314,6 +1335,7 @@ dvr_config_create(const char *name)
cfg->dvr_retention_days = 31;
cfg->dvr_mc = MC_MATROSKA;
cfg->dvr_flags = DVR_TAG_FILES | DVR_SKIP_COMMERCIALS;
+ dvr_charset_update(cfg, "ASCII");
/* series link support */
cfg->dvr_sl_brand_lock = 1; // use brand linking
@@ -1359,6 +1381,9 @@ dvr_config_delete(const char *name)
cfg->dvr_config_name);
hts_settings_remove("dvr/config%s", cfg->dvr_config_name);
LIST_REMOVE(cfg, config_link);
+ free(cfg->dvr_charset_id);
+ free(cfg->dvr_charset);
+ free(cfg->dvr_storage);
free(cfg->dvr_config_name);
free(cfg);
@@ -1409,7 +1434,9 @@ dvr_save(dvr_config_t *cfg)
htsmsg_add_u32(m, "skip-commercials", !!(cfg->dvr_flags & DVR_SKIP_COMMERCIALS));
htsmsg_add_u32(m, "subtitle-in-title", !!(cfg->dvr_flags & DVR_SUBTITLE_IN_TITLE));
htsmsg_add_u32(m, "episode-before-date", !!(cfg->dvr_flags & DVR_EPISODE_BEFORE_DATE));
- if(cfg->dvr_postproc != NULL)
+ if (cfg->dvr_charset != NULL)
+ htsmsg_add_str(m, "charset", cfg->dvr_charset);
+ if (cfg->dvr_postproc != NULL)
htsmsg_add_str(m, "postproc", cfg->dvr_postproc);
hts_settings_save(m, "dvr/config%s", cfg->dvr_config_name);
@@ -1431,6 +1458,19 @@ dvr_storage_set(dvr_config_t *cfg, const char *storage)
dvr_save(cfg);
}
+/**
+ *
+ */
+void
+dvr_charset_set(dvr_config_t *cfg, const char *charset)
+{
+ if(cfg->dvr_charset != NULL && !strcmp(cfg->dvr_charset, charset))
+ return;
+
+ dvr_charset_update(cfg, charset);
+ dvr_save(cfg);
+}
+
/**
*
*/
diff --git a/src/dvr/dvr_rec.c b/src/dvr/dvr_rec.c
index 25814e81..a227f709 100644
--- a/src/dvr/dvr_rec.c
+++ b/src/dvr/dvr_rec.c
@@ -34,6 +34,7 @@
#include "plumbing/globalheaders.h"
#include "htsp_server.h"
#include "atomic.h"
+#include "intlconv.h"
#include "muxer.h"
@@ -122,11 +123,27 @@ dvr_rec_unsubscribe(dvr_entry_t *de, int stopcode)
/**
* Replace various chars with a dash
*/
-static void
-cleanupfilename(char *s, int dvr_flags)
+static char *
+cleanup_filename(char *s, dvr_config_t *cfg)
{
- int i, len = strlen(s);
- for(i = 0; i < len; i++) {
+ int i, len = strlen(s), dvr_flags = cfg->dvr_flags;
+ char *s1;
+
+ s1 = intlconv_utf8safestr(cfg->dvr_charset_id, s, len * 2);
+ if (s1 == NULL) {
+ tvherror("dvr", "Unsupported charset %s using ASCII", cfg->dvr_charset);
+ s1 = intlconv_utf8safestr(intlconv_charset_id("ASCII", 1, 1),
+ s, len * 2);
+ if (s1 == NULL)
+ return NULL;
+ }
+ s = s1;
+
+ /* Do not create hidden files */
+ if (s[0] == '.')
+ s[0] = '-';
+
+ for (i = 0, len = strlen(s); i < len; i++) {
if(s[i] == '/')
s[i] = '-';
@@ -140,6 +157,8 @@ cleanupfilename(char *s, int dvr_flags)
(strchr("/:\\<>|*?'\"", s[i]) != NULL)))
s[i] = '-';
}
+
+ return s;
}
/**
@@ -152,65 +171,64 @@ 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 fullname[PATH_MAX];
+ char path[PATH_MAX];
int tally = 0;
struct stat st;
- char filename[1000];
+ char *filename, *s;
struct tm tm;
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);
-
- snprintf(path, sizeof(path), "%s", cfg->dvr_storage);
+ strncpy(path, cfg->dvr_storage, sizeof(path));
+ path[sizeof(path)-1] = '\0';
/* Remove trailing slash */
-
if (path[strlen(path)-1] == '/')
path[strlen(path)-1] = '\0';
/* Append per-day directory */
-
- if(cfg->dvr_flags & DVR_DIR_PER_DAY) {
+ 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);
+ s = cleanup_filename(fullname, cfg);
+ if (s == NULL)
+ return -1;
+ snprintf(path + strlen(path), sizeof(path) - strlen(path), "/%s", s);
+ free(s);
}
/* Append per-channel directory */
-
- if(cfg->dvr_flags & DVR_DIR_PER_CHANNEL) {
-
+ 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);
+ s = cleanup_filename(chname, cfg);
free(chname);
+ if (s == NULL)
+ return -1;
+ snprintf(path + strlen(path), sizeof(path) - strlen(path), "/%s", s);
+ free(s);
}
// TODO: per-brand, per-season
/* Append per-title directory */
-
- if(cfg->dvr_flags & DVR_DIR_PER_TITLE) {
-
+ 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);
+ s = cleanup_filename(title, cfg);
free(title);
+ if (s == NULL)
+ return -1;
+ snprintf(path + strlen(path), sizeof(path) - strlen(path), "/%s", s);
+ free(s);
}
- if(makedirs(path, cfg->dvr_muxcnf.m_directory_permissions) != 0) {
+ if (makedirs(path, cfg->dvr_muxcnf.m_directory_permissions) != 0)
return -1;
- }
-
/* Construct final name */
-
+ dvr_make_title(fullname, sizeof(fullname), de);
+ filename = cleanup_filename(fullname, cfg);
+ if (filename == NULL)
+ return -1;
snprintf(fullname, sizeof(fullname), "%s/%s.%s",
path, filename, muxer_suffix(de->de_mux, ss));
@@ -229,6 +247,7 @@ pvr_generate_filename(dvr_entry_t *de, const streaming_start_t *ss)
snprintf(fullname, sizeof(fullname), "%s/%s-%d.%s",
path, filename, tally, muxer_suffix(de->de_mux, ss));
}
+ free(filename);
tvh_str_set(&de->de_filename, fullname);
diff --git a/src/webui/extjs.c b/src/webui/extjs.c
index 3a3af8d3..550cfbbb 100755
--- a/src/webui/extjs.c
+++ b/src/webui/extjs.c
@@ -1127,6 +1127,7 @@ 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, "charset", cfg->dvr_charset);
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 */
@@ -1174,7 +1175,10 @@ 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(cfg,s);
- if((s = http_arg_get(&hc->hc_req_args, "container")) != NULL)
+ if((s = http_arg_get(&hc->hc_req_args, "charset")) != NULL)
+ dvr_charset_set(cfg,s);
+
+ if((s = http_arg_get(&hc->hc_req_args, "container")) != NULL)
dvr_container_set(cfg,s);
/*
@@ -1182,13 +1186,13 @@ extjs_dvr(http_connection_t *hc, const char *remain, void *opaque)
* 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)
+ 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)
+ 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)
+ if((s = http_arg_get(&hc->hc_req_args, "cache")) != NULL)
dvr_mux_cache_set(cfg,atoi(s));
if((s = http_arg_get(&hc->hc_req_args, "postproc")) != NULL)
@@ -1197,11 +1201,11 @@ 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(cfg,atoi(s));
- if((s = http_arg_get(&hc->hc_req_args, "preExtraTime")) != NULL)
- dvr_extra_time_pre_set(cfg,atoi(s));
+ if((s = http_arg_get(&hc->hc_req_args, "preExtraTime")) != NULL)
+ dvr_extra_time_pre_set(cfg,atoi(s));
- 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, "postExtraTime")) != NULL)
+ dvr_extra_time_post_set(cfg,atoi(s));
if(http_arg_get(&hc->hc_req_args, "dayDirs") != NULL)
flags |= DVR_DIR_PER_DAY;
diff --git a/src/webui/static/app/dvr.js b/src/webui/static/app/dvr.js
index a4fc108c..2e4c83bf 100644
--- a/src/webui/static/app/dvr.js
+++ b/src/webui/static/app/dvr.js
@@ -27,6 +27,18 @@ tvheadend.containers = new Ext.data.JsonStore({
});
//For the cache configuration
+tvheadend.charsets = new Ext.data.JsonStore({
+ autoLoad: true,
+ root: 'entries',
+ fields: ['key', 'val'],
+ id: 'key',
+ url: 'api/intlconv/charsets',
+ baseParams: {
+ enum: 1
+ }
+});
+
+//For the charset configuration
tvheadend.caches = new Ext.data.JsonStore({
autoLoad: true,
root: 'entries',
@@ -784,7 +796,7 @@ tvheadend.dvrsettings = function() {
var confreader = new Ext.data.JsonReader({
root: 'dvrSettings'
}, ['storage', 'filePermissions', 'dirPermissions', 'postproc', 'retention', 'dayDirs', 'channelDirs',
- 'channelInTitle', 'container', 'cache', 'dateInTitle', 'timeInTitle',
+ 'channelInTitle', 'container', 'cache', 'charset', 'dateInTitle', 'timeInTitle',
'preExtraTime', 'postExtraTime', 'whitespaceInTitle', 'titleDirs',
'episodeInTitle', 'cleanTitle', 'tagFiles', 'commSkip', 'subtitleInTitle',
'episodeBeforeDate', 'rewritePAT', 'rewritePMT']);
@@ -880,6 +892,17 @@ tvheadend.dvrsettings = function() {
name: 'filePermissions'
});
+ var charset = new Ext.form.ComboBox({
+ store: tvheadend.charsets,
+ fieldLabel: 'Filename charset',
+ triggerAction: 'all',
+ displayField: 'val',
+ valueField: 'key',
+ editable: false,
+ width: 200,
+ hiddenName: 'charset'
+ });
+
/* 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({
@@ -992,7 +1015,7 @@ tvheadend.dvrsettings = function() {
autoHeight: true,
collapsible: true,
animCollapse: true,
- items: [recordingPath, recordingPermissions, PATrewrite, PMTrewrite, tagMetadata, skipCommercials]
+ items: [recordingPath, recordingPermissions, charset, PATrewrite, PMTrewrite, tagMetadata, skipCommercials]
});
/* Sub-Panel - Directory operations */