Compare commits
54 Commits
2.1.1.0111
...
2.2.0.200
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e999b85b9 | ||
|
|
e4b3667fe0 | ||
|
|
e44eff0645 | ||
|
|
884b427754 | ||
|
|
25bad13800 | ||
|
|
d2456b0506 | ||
|
|
19b9a259b1 | ||
|
|
441cefa3a4 | ||
|
|
014f5b7218 | ||
|
|
0310b7e738 | ||
|
|
6d890cfd33 | ||
|
|
410cc3648f | ||
|
|
07f9cac3c5 | ||
|
|
f43ce0f7c5 | ||
|
|
db59f7ef37 | ||
|
|
b407ec5bf0 | ||
|
|
d8fc1aea97 | ||
|
|
38333d65cb | ||
|
|
a6a9b90937 | ||
|
|
2b3fe6a09d | ||
|
|
a09eca59a7 | ||
|
|
0df3b4d755 | ||
|
|
5552514a1f | ||
|
|
5fceb1d34f | ||
|
|
fb5d0a3904 | ||
|
|
28fe4dcf1c | ||
|
|
bc307a7cd4 | ||
|
|
fd3024d7ff | ||
|
|
d3f6725ba6 | ||
|
|
c266012db3 | ||
|
|
d602b60710 | ||
|
|
67b7ba6df9 | ||
|
|
71dfe91272 | ||
|
|
534510a4ec | ||
|
|
2d10fc9313 | ||
|
|
a63a9c0d8f | ||
|
|
dd911d6e5d | ||
|
|
c1970a8393 | ||
|
|
493d612d52 | ||
|
|
4fc4330a94 | ||
|
|
a683533824 | ||
|
|
4b9f5826cf | ||
|
|
ca49d70910 | ||
|
|
1b425018d4 | ||
|
|
87b36c283b | ||
|
|
6da26ff4fb | ||
|
|
cd08985e79 | ||
|
|
dc04519229 | ||
|
|
c4ad96b715 | ||
|
|
45b5e602bb | ||
|
|
1cefbf022d | ||
|
|
d5328f6b1a | ||
|
|
91b80bc8bb | ||
|
|
aa763726a3 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
.DS_Store
|
||||
demo
|
||||
dev
|
||||
compiler
|
||||
files
|
||||
update_xteve*.sh
|
||||
|
||||
@@ -117,7 +117,7 @@ When the branch is changed, an update is only performed if there is a new versio
|
||||
## Build from source code [Go / Golang]
|
||||
|
||||
#### Requirements
|
||||
* [Go](https://golang.org) (go1.12.4 or newer)
|
||||
* [Go](https://golang.org) (go1.16.2 or newer)
|
||||
|
||||
#### Dependencies
|
||||
* [go-ssdp](https://github.com/koron/go-ssdp)
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
#### 2.1.1.0116-beta
|
||||
If no user agent is specified, the default FFmpeg or VLC user agent is used.
|
||||
|
||||
#### 2.1.1.0115-beta
|
||||
```diff
|
||||
+ GZIP compression for xteve.xml file. (http://xteve.ip:34400/xmltv/xteve.xml.gz)
|
||||
- Removed protocol setting for reverse proxy. HTTPS can also be configured in the proxy, where it makes more sense.
|
||||
```
|
||||
|
||||
#### 2.1.0.0106-beta
|
||||
```diff
|
||||
+ User-Agent is now also used by VLC and FFmpeg.
|
||||
|
||||
9
go.mod
Normal file
9
go.mod
Normal file
@@ -0,0 +1,9 @@
|
||||
module xteve
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||
github.com/koron/go-ssdp v0.0.2 // indirect
|
||||
)
|
||||
16
go.sum
Normal file
16
go.sum
Normal file
@@ -0,0 +1,16 @@
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/koron/go-ssdp v0.0.2 h1:fL3wAoyT6hXHQlORyXUW4Q23kkQpJRgEAYcZB5BR71o=
|
||||
github.com/koron/go-ssdp v0.0.2/go.mod h1:XoLfkAiA2KeZsYh4DbHxD7h3nR2AZNqVQOa+LJuqPYs=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
<div id="content">
|
||||
|
||||
<form id="authentication" action="/web/" method="post">
|
||||
<form id="authentication" action="" method="post">
|
||||
|
||||
<h5>{{.account.username.title}}:</h5>
|
||||
<input id="username" type="text" name="username" placeholder="Username" value="">
|
||||
|
||||
@@ -18,9 +18,9 @@ menuItems.push(new MainMenuItem("log", "{{.mainMenu.item.log}}", "log.png", "{{.
|
||||
menuItems.push(new MainMenuItem("logout", "{{.mainMenu.item.logout}}", "logout.png", "{{.mainMenu.headline.logout}}"));
|
||||
// Kategorien für die Einstellungen
|
||||
var settingsCategory = new Array();
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.general}}", "xteveAutoUpdate,tuner,epgSource,api,scheme.m3u,scheme.xml"));
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.general}}", "xteveAutoUpdate,tuner,epgSource,api"));
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.files}}", "update,files.update,temp.path,cache.images,xepg.replace.missing.images"));
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.streaming}}", "buffer,buffer.size.kb,buffer.timeout,user.agent,ffmpeg.path,ffmpeg.options,vlc.path,vlc.options"));
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.streaming}}", "buffer,udpxy,buffer.size.kb,buffer.timeout,user.agent,ffmpeg.path,ffmpeg.options,vlc.path,vlc.options"));
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.backup}}", "backup.path,backup.keep"));
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.authentication}}", "authentication.web,authentication.pms,authentication.m3u,authentication.xml,authentication.api"));
|
||||
function showPopUpElement(elm) {
|
||||
@@ -226,7 +226,7 @@ function createSearchObj() {
|
||||
SEARCH_MAPPING = new Object();
|
||||
var data = SERVER["xepg"]["epgMapping"];
|
||||
var channels = getObjKeys(data);
|
||||
var channelKeys = ["x-active", "x-channelID", "x-name", "_file.m3u.name", "x-group-title"];
|
||||
var channelKeys = ["x-active", "x-channelID", "x-name", "_file.m3u.name", "x-group-title", "x-xmltv-file"];
|
||||
channels.forEach(function (id) {
|
||||
channelKeys.forEach(function (key) {
|
||||
if (key == "x-active") {
|
||||
@@ -240,7 +240,15 @@ function createSearchObj() {
|
||||
}
|
||||
}
|
||||
else {
|
||||
SEARCH_MAPPING[id] = SEARCH_MAPPING[id] + data[id][key] + " ";
|
||||
if (key == "x-xmltv-file") {
|
||||
var xmltvFile = getValueFromProviderFile(data[id][key], "xmltv", "name");
|
||||
if (xmltvFile != undefined) {
|
||||
SEARCH_MAPPING[id] = SEARCH_MAPPING[id] + xmltvFile + " ";
|
||||
}
|
||||
}
|
||||
else {
|
||||
SEARCH_MAPPING[id] = SEARCH_MAPPING[id] + data[id][key] + " ";
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
|
||||
@@ -2,7 +2,7 @@ var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
@@ -1285,7 +1285,7 @@ function openPopUp(dataType, element) {
|
||||
// Erweitern der EPG Kategorie
|
||||
var dbKey = "x-category";
|
||||
var text = ["-", "Kids (Emby only)", "News", "Movie", "Series", "Sports"];
|
||||
var values = ["-", "Kids", "News", "Movie", "Series", "Sports"];
|
||||
var values = ["", "Kids", "News", "Movie", "Series", "Sports"];
|
||||
var select = content.createSelect(text, values, data[dbKey], dbKey);
|
||||
select.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||
content.appendRow("{{.mapping.epgCategory.title}}", select);
|
||||
|
||||
@@ -2,7 +2,7 @@ var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
@@ -305,27 +305,14 @@ var SettingsCategory = /** @class */ (function () {
|
||||
setting.appendChild(tdLeft);
|
||||
setting.appendChild(tdRight);
|
||||
break;
|
||||
case "scheme.m3u":
|
||||
case "udpxy":
|
||||
var tdLeft = document.createElement("TD");
|
||||
tdLeft.innerHTML = "{{.settings.schemeM3U.title}}" + ":";
|
||||
tdLeft.innerHTML = "{{.settings.udpxy.title}}" + ":";
|
||||
var tdRight = document.createElement("TD");
|
||||
var text = ["HTTP", "HTTPS"];
|
||||
var values = ["HTTP", "HTTPS"];
|
||||
var select = content.createSelect(text, values, data, settingsKey);
|
||||
select.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||
tdRight.appendChild(select);
|
||||
setting.appendChild(tdLeft);
|
||||
setting.appendChild(tdRight);
|
||||
break;
|
||||
case "scheme.xml":
|
||||
var tdLeft = document.createElement("TD");
|
||||
tdLeft.innerHTML = "{{.settings.schemeXML.title}}" + ":";
|
||||
var tdRight = document.createElement("TD");
|
||||
var text = ["HTTP", "HTTPS"];
|
||||
var values = ["HTTP", "HTTPS"];
|
||||
var select = content.createSelect(text, values, data, settingsKey);
|
||||
select.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||
tdRight.appendChild(select);
|
||||
var input = content.createInput("text", "udpxy", data);
|
||||
input.setAttribute("placeholder", "{{.settings.udpxy.placeholder}}");
|
||||
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||
tdRight.appendChild(input);
|
||||
setting.appendChild(tdLeft);
|
||||
setting.appendChild(tdRight);
|
||||
break;
|
||||
@@ -410,11 +397,8 @@ var SettingsCategory = /** @class */ (function () {
|
||||
case "xepg.replace.missing.images":
|
||||
text = "{{.settings.replaceEmptyImages.description}}";
|
||||
break;
|
||||
case "scheme.m3u":
|
||||
text = "{{.settings.schemeM3U.description}}";
|
||||
break;
|
||||
case "scheme.xml":
|
||||
text = "{{.settings.schemeXML.description}}";
|
||||
case "udpxy":
|
||||
text = "{{.settings.udpxy.description}}";
|
||||
break;
|
||||
default:
|
||||
text = "";
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"mainMenu": {
|
||||
"item":{
|
||||
"mainMenu":
|
||||
{
|
||||
"item":
|
||||
{
|
||||
"playlist": "Playlist",
|
||||
"pmsID": "PMS ID",
|
||||
"filter": "Filter",
|
||||
@@ -11,7 +13,8 @@
|
||||
"log": "Log",
|
||||
"logout": "Logout"
|
||||
},
|
||||
"headline": {
|
||||
"headline":
|
||||
{
|
||||
"playlist": "Local or remote playlists",
|
||||
"filter": "Filter playlist",
|
||||
"xmltv": "Local or remote XMLTV files",
|
||||
@@ -22,15 +25,18 @@
|
||||
"logout": "Logout"
|
||||
}
|
||||
},
|
||||
"confirm":{
|
||||
"confirm":
|
||||
{
|
||||
"restore": "All data will be replaced with those from the backup. Should the files be restored?"
|
||||
},
|
||||
"alert": {
|
||||
"alert":
|
||||
{
|
||||
"fileLoadingError": "File couldn't be loaded",
|
||||
"invalidChannelNumber": "Invalid channel number",
|
||||
"missingInput": "Missing input"
|
||||
},
|
||||
"button":{
|
||||
"button":
|
||||
{
|
||||
"back": "Back",
|
||||
"backup": "Backup",
|
||||
"bulkEdit": "Bulk Edit",
|
||||
@@ -48,58 +54,70 @@
|
||||
"resetLogs": "Reset Logs",
|
||||
"uploadLogo": "Upload Logo"
|
||||
},
|
||||
"filter": {
|
||||
"table": {
|
||||
"filter":
|
||||
{
|
||||
"table":
|
||||
{
|
||||
"name": "Filter Name",
|
||||
"type": "Filter Type",
|
||||
"filter": "Filter"
|
||||
},
|
||||
"custom": "Custom",
|
||||
"group": "Group",
|
||||
"name": {
|
||||
"name":
|
||||
{
|
||||
"title": "Filter Name",
|
||||
"placeholder": "Filter name",
|
||||
"description": ""
|
||||
},
|
||||
"description": {
|
||||
"description":
|
||||
{
|
||||
"title": "Description",
|
||||
"placeholder": "Description",
|
||||
"description": ""
|
||||
},
|
||||
"type": {
|
||||
"type":
|
||||
{
|
||||
"title": "Type",
|
||||
"groupTitle": "Group Title",
|
||||
"customFilter": "Custom Filter"
|
||||
},
|
||||
"caseSensitive": {
|
||||
"caseSensitive":
|
||||
{
|
||||
"title": "Case Sensitive",
|
||||
"placeholder": "",
|
||||
"description": ""
|
||||
},
|
||||
"filterRule": {
|
||||
"filterRule":
|
||||
{
|
||||
"title": "Filter Rule",
|
||||
"placeholder": "Sport {HD} !{ES,IT}",
|
||||
"description": ""
|
||||
},
|
||||
"filterGroup": {
|
||||
"filterGroup":
|
||||
{
|
||||
"title": "Group Title",
|
||||
"placeholder": "",
|
||||
"description": "Select a M3U group. (Counter)<br>Changing the group title in the M3U invalidates the filter."
|
||||
},
|
||||
"include": {
|
||||
"include":
|
||||
{
|
||||
"title": "Include",
|
||||
"placeholder": "FHD,UHD",
|
||||
"description": "Channel name must include.<br>(Comma separated) Comma means or"
|
||||
},
|
||||
"exclude": {
|
||||
"exclude":
|
||||
{
|
||||
"title": "Exclude",
|
||||
"placeholder": "ES,IT",
|
||||
"description": "Channel name must not contain.<br>(Comma separated) Comma means or"
|
||||
}
|
||||
|
||||
},
|
||||
"playlist": {
|
||||
"table": {
|
||||
"playlist":
|
||||
{
|
||||
"table":
|
||||
{
|
||||
"playlist": "Playlist",
|
||||
"tuner": "Tuner",
|
||||
"lastUpdate": "Last Update",
|
||||
@@ -110,68 +128,82 @@
|
||||
"tvgID": "tvg-id",
|
||||
"uniqueID": "Unique ID"
|
||||
},
|
||||
"playlistType": {
|
||||
"playlistType":
|
||||
{
|
||||
"title": "Playlist type",
|
||||
"placeholder": "",
|
||||
"description": ""
|
||||
},
|
||||
"type": {
|
||||
"type":
|
||||
{
|
||||
"title": "Type",
|
||||
"placeholder": "",
|
||||
"description": ""
|
||||
},
|
||||
"name": {
|
||||
"name":
|
||||
{
|
||||
"title": "Name",
|
||||
"placeholder": "Playlist name",
|
||||
"description": ""
|
||||
},
|
||||
"description": {
|
||||
"description":
|
||||
{
|
||||
"title": "Description",
|
||||
"placeholder": "Description",
|
||||
"description": ""
|
||||
},
|
||||
"fileM3U": {
|
||||
"fileM3U":
|
||||
{
|
||||
"title": "M3U File",
|
||||
"placeholder": "File path or URL of the M3U",
|
||||
"description": ""
|
||||
},
|
||||
"fileHDHR": {
|
||||
"fileHDHR":
|
||||
{
|
||||
"title": "HDHomeRun IP",
|
||||
"placeholder": "IP address and port (192.168.1.10:5004)",
|
||||
"description": ""
|
||||
},
|
||||
"tuner": {
|
||||
"tuner":
|
||||
{
|
||||
"title": "Tuner / Streams",
|
||||
"placeholder": "",
|
||||
"description": "Number of parallel connections that can be established to the provider. <br>Only available with activated buffer.<br>New settings will only be applied after quitting all streams."
|
||||
}
|
||||
},
|
||||
"xmltv": {
|
||||
"table": {
|
||||
"xmltv":
|
||||
{
|
||||
"table":
|
||||
{
|
||||
"guide": "Guide",
|
||||
"lastUpdate": "Last Update",
|
||||
"availability": "Availability",
|
||||
"channels": "Channels",
|
||||
"programs": "Programs"
|
||||
},
|
||||
"name": {
|
||||
"name":
|
||||
{
|
||||
"title": "Name",
|
||||
"placeholder": "Guide name",
|
||||
"description": ""
|
||||
},
|
||||
"description": {
|
||||
"description":
|
||||
{
|
||||
"title": "Description",
|
||||
"placeholder": "Description",
|
||||
"description": ""
|
||||
},
|
||||
"fileXMLTV": {
|
||||
"fileXMLTV":
|
||||
{
|
||||
"title": "XMLTV File",
|
||||
"placeholder": "File path or URL of the XMLTV",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"mapping": {
|
||||
"table": {
|
||||
"mapping":
|
||||
{
|
||||
"table":
|
||||
{
|
||||
"chNo": "Ch. No.",
|
||||
"logo": "Logo",
|
||||
"channelName": "Channel Name",
|
||||
@@ -180,59 +212,71 @@
|
||||
"xmltvFile": "XMLTV File",
|
||||
"xmltvID": "XMLTV ID"
|
||||
},
|
||||
"active": {
|
||||
"active":
|
||||
{
|
||||
"title": "Active",
|
||||
"placeholder": "",
|
||||
"description": ""
|
||||
},
|
||||
"channelName": {
|
||||
"channelName":
|
||||
{
|
||||
"title": "Channel Name",
|
||||
"placeholder": "",
|
||||
"description": ""
|
||||
},
|
||||
"description": {
|
||||
"description":
|
||||
{
|
||||
"title": "Channel Description",
|
||||
"placeholder": "Used by the Dummy as an XML description",
|
||||
"description": ""
|
||||
},
|
||||
"updateChannelName": {
|
||||
"updateChannelName":
|
||||
{
|
||||
"title": "Update Channel Name",
|
||||
"placeholder": "",
|
||||
"description": ""
|
||||
},
|
||||
"channelLogo": {
|
||||
"channelLogo":
|
||||
{
|
||||
"title": "Logo URL",
|
||||
"placeholder": "",
|
||||
"description": ""
|
||||
},
|
||||
"updateChannelLogo": {
|
||||
"updateChannelLogo":
|
||||
{
|
||||
"title": "Update Channel Logo",
|
||||
"placeholder": "",
|
||||
"description": ""
|
||||
},
|
||||
"epgCategory": {
|
||||
"epgCategory":
|
||||
{
|
||||
"title": "EPG Category",
|
||||
"placeholder": "",
|
||||
"description": ""
|
||||
},
|
||||
"m3uGroupTitle": {
|
||||
"m3uGroupTitle":
|
||||
{
|
||||
"title": "Group Title (xteve.m3u)",
|
||||
"placeholder": "",
|
||||
"description": ""
|
||||
},
|
||||
"xmltvFile": {
|
||||
"xmltvFile":
|
||||
{
|
||||
"title": "XMLTV File",
|
||||
"placeholder": "",
|
||||
"description": ""
|
||||
},
|
||||
"xmltvChannel": {
|
||||
"xmltvChannel":
|
||||
{
|
||||
"title": "XMLTV Channel",
|
||||
"placeholder": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"table": {
|
||||
"users":
|
||||
{
|
||||
"table":
|
||||
{
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"web": "WEB",
|
||||
@@ -241,97 +285,108 @@
|
||||
"xml": "XML",
|
||||
"api": "API"
|
||||
},
|
||||
"username": {
|
||||
"username":
|
||||
{
|
||||
"title": "Username",
|
||||
"placeholder": "Username",
|
||||
"description": ""
|
||||
},
|
||||
"password": {
|
||||
"password":
|
||||
{
|
||||
"title": "Password",
|
||||
"placeholder": "Password",
|
||||
"description": ""
|
||||
},
|
||||
"confirm": {
|
||||
"confirm":
|
||||
{
|
||||
"title": "Confirm",
|
||||
"placeholder": "Password confirm",
|
||||
"description": ""
|
||||
},
|
||||
"web": {
|
||||
"web":
|
||||
{
|
||||
"title": "Web Access",
|
||||
"placeholder": "",
|
||||
"description": ""
|
||||
},
|
||||
"pms": {
|
||||
"pms":
|
||||
{
|
||||
"title": "PMS Access",
|
||||
"placeholder": "",
|
||||
"description": ""
|
||||
},
|
||||
"m3u": {
|
||||
"m3u":
|
||||
{
|
||||
"title": "M3U Access",
|
||||
"placeholder": "",
|
||||
"description": ""
|
||||
},
|
||||
"xml": {
|
||||
"xml":
|
||||
{
|
||||
"title": "XML Access",
|
||||
"placeholder": "",
|
||||
"description": ""
|
||||
},
|
||||
"api": {
|
||||
"api":
|
||||
{
|
||||
"title": "API Access",
|
||||
"placeholder": "",
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"category": {
|
||||
"settings":
|
||||
{
|
||||
"category":
|
||||
{
|
||||
"general": "General",
|
||||
"files": "Files",
|
||||
"streaming": "Streaming",
|
||||
"backup": "Backup",
|
||||
"authentication": "Authentication"
|
||||
},
|
||||
"update": {
|
||||
"update":
|
||||
{
|
||||
"title": "Schedule for updating (Playlist, XMLTV, Backup)",
|
||||
"placeholder": "0000,1000,2000",
|
||||
"description": "Time in 24 hour format (0800 = 8:00 am). More times can be entered comma separated."
|
||||
"description": "Time in 24 hour format (0800 = 8:00 am). More times can be entered comma separated. Leave this field empty if no updates are to be carried out."
|
||||
},
|
||||
"api": {
|
||||
"api":
|
||||
{
|
||||
"title": "API Interface",
|
||||
"description": "Via API interface it is possible to send commands to xTeVe. API documentation is <a href='https://github.com/xteve-project/xTeVe-Documentation/blob/master/en/configuration.md#api'>here</a>"
|
||||
},
|
||||
"epgSource": {
|
||||
"epgSource":
|
||||
{
|
||||
"title": "EPG Source",
|
||||
"description": "PMS:<br>- Use EPG data from Plex or Emby <br><br>XEPG:<br>- Use of one or more XMLTV files<br>- Channel management<br>- M3U / XMLTV export (HTTP link for IPTV apps)"
|
||||
},
|
||||
"tuner":{
|
||||
"tuner":
|
||||
{
|
||||
"title": "Number of Tuners",
|
||||
"description": "Number of parallel connections that can be established to the provider.<br>Available for: Plex, Emby (HDHR), M3U (with active buffer).<br>After a change, xTeVe must be delete in the Plex / Emby DVR settings and set up again."
|
||||
},
|
||||
"schemeM3U":{
|
||||
"title": "URL protocol for xteve.m3u",
|
||||
"description": "Determines which URL protocol is used for the xTeVe streaming URLs. If you using a reverse proxy over HTTPS, set this to HTTPS."
|
||||
},
|
||||
"schemeXML":{
|
||||
"title": "URL protocol for xteve.xml",
|
||||
"description": "Determines which URL protocol is used for the xTeVe image URLs. If you using a reverse proxy over HTTPS, set this to HTTPS."
|
||||
},
|
||||
"filesUpdate": {
|
||||
"filesUpdate":
|
||||
{
|
||||
"title": "Updates all files at startup",
|
||||
"description": "Updates all playlists, tuner and XMLTV files at startup."
|
||||
},
|
||||
"cacheImages": {
|
||||
"cacheImages":
|
||||
{
|
||||
"title": "Image Caching",
|
||||
"description": "All images from the XMLTV file are cached, allowing faster rendering of the grid in the client.<br>Downloading the images may take a while and will be done in the background."
|
||||
},
|
||||
"replaceEmptyImages": {
|
||||
"replaceEmptyImages":
|
||||
{
|
||||
"title": "Replace missing program images",
|
||||
"description": "If the poster in the XMLTV program is missing, the channel logo will be used."
|
||||
},
|
||||
"xteveAutoUpdate": {
|
||||
"xteveAutoUpdate":
|
||||
{
|
||||
"title": "Automatic update of xTeVe",
|
||||
"description": "If a new version of xTeVe is available, it will be automatically installed. The updates are downloaded from GitHub."
|
||||
},
|
||||
"streamBuffering": {
|
||||
"streamBuffering":
|
||||
{
|
||||
"title": "Stream Buffer",
|
||||
"description": "Functions of the buffer:<br>- The stream is passed from xTeVe, FFmpeg or VLC to Plex, Emby or M3U Player<br>- Small jerking of the streams can be compensated<br>- HLS / M3U8 support<br>- RTP / RTPS support (only FFmpeg or VLC)<br>- Re-streaming<br>- Separate tuner limit for each playlist",
|
||||
"info_false": "No Buffer (Client connects to the streaming server)",
|
||||
@@ -340,119 +395,152 @@
|
||||
"info_vlc": "VLC connects to the streaming server"
|
||||
|
||||
},
|
||||
"ffmpegPath": {
|
||||
"udpxy":
|
||||
{
|
||||
"title": "UDPxy address",
|
||||
"description": "The address of your UDPxy server. If set, and the channel URLs in the m3u is multicast, xTeVe will rewrite it so that it is accessed via the UDPxy service.",
|
||||
"placeholder": "host:port"
|
||||
},
|
||||
"ffmpegPath":
|
||||
{
|
||||
"title": "FFmpeg Binary Path",
|
||||
"description": "Path to FFmpeg binary.",
|
||||
"placeholder": "/path/to/ffmpeg"
|
||||
},
|
||||
"ffmpegOptions": {
|
||||
"ffmpegOptions":
|
||||
{
|
||||
"title": "FFmpeg Options",
|
||||
"description": "FFmpeg options.<br>Only change if you know what you are doing.<br>Leave blank to set default settings.",
|
||||
"placeholder": "Leave blank to set default settings"
|
||||
},
|
||||
"vlcPath": {
|
||||
"vlcPath":
|
||||
{
|
||||
"title": "VLC / CVLC Binary Path",
|
||||
"description": "Path to VLC / CVLC binary.",
|
||||
"placeholder": "/path/to/cvlc"
|
||||
},
|
||||
"vlcOptions": {
|
||||
"vlcOptions":
|
||||
{
|
||||
"title": "VLC / CVLC Options",
|
||||
"description": "VLC / CVLC options.<br>Only change if you know what you are doing.<br>Leave blank to set default settings.",
|
||||
"placeholder": "Leave blank to set default settings"
|
||||
},
|
||||
"bufferSize": {
|
||||
"bufferSize":
|
||||
{
|
||||
"title": "Buffer Size",
|
||||
"description": "Buffer size in MB.<br>M3U8: If the TS segment smaller then the buffer size, the file size of the segment is used."
|
||||
},
|
||||
"bufferTimeout": {
|
||||
"bufferTimeout":
|
||||
{
|
||||
"title": "Timeout for new client connections",
|
||||
"description": "The xTeVe buffer waits until new client connections are established. Helpful for fast channel switching. Value in milliseconds.",
|
||||
"placeholder": "100"
|
||||
},
|
||||
"userAgent": {
|
||||
"userAgent":
|
||||
{
|
||||
"title": "User Agent",
|
||||
"description": "User Agent for HTTP requests. For every HTTP connection, this value is used for the user agent. Should only be changed if xTeVe is blocked.",
|
||||
"placeholder": "xTeVe"
|
||||
},
|
||||
"backupPath": {
|
||||
"backupPath":
|
||||
{
|
||||
"title": "Location for automatic backups",
|
||||
"placeholder": "/mnt/data/backup/xteve/",
|
||||
"description": "Before any update of the provider data by the schedule, xTeVe creates a backup. The path for the automatic backups can be changed. xTeVe requires write permission for this folder."
|
||||
},
|
||||
"tempPath": {
|
||||
"tempPath":
|
||||
{
|
||||
"title": "Location for the temporary files",
|
||||
"placeholder": "/tmp/xteve/",
|
||||
"description": "Location for the buffer files."
|
||||
},
|
||||
"backupKeep": {
|
||||
"backupKeep":
|
||||
{
|
||||
"title": "Number of backups to keep",
|
||||
"description": "Number of backups to keep. Older backups are automatically deleted."
|
||||
},
|
||||
"authenticationWEB": {
|
||||
"authenticationWEB":
|
||||
{
|
||||
"title": "WEB Authentication",
|
||||
"description": "Access to the web interface only possible with credentials."
|
||||
},
|
||||
"authenticationPMS": {
|
||||
"authenticationPMS":
|
||||
{
|
||||
"title": "PMS Authentication",
|
||||
"description": "Plex requests are only possible with authentication. <br><b>Warning!!!</b> After activating this function xTeVe must be delete in the PMS DVR settings and set up again."
|
||||
},
|
||||
"authenticationM3U": {
|
||||
"authenticationM3U":
|
||||
{
|
||||
"title": "M3U Authentication",
|
||||
"description": "Downloading the xteve.m3u file via an HTTP request is only possible with authentication."
|
||||
},
|
||||
"authenticationXML": {
|
||||
"authenticationXML":
|
||||
{
|
||||
"title": "XML Authentication",
|
||||
"description": "Downloading the xteve.xml file via an HTTP request is only possible with authentication"
|
||||
},
|
||||
"authenticationAPI": {
|
||||
"authenticationAPI":
|
||||
{
|
||||
"title": "API Authentication",
|
||||
"description": "Access to the API interface is only possible with authentication."
|
||||
}
|
||||
},
|
||||
"wizard": {
|
||||
"epgSource": {
|
||||
"wizard":
|
||||
{
|
||||
"epgSource":
|
||||
{
|
||||
"title": "EPG Source",
|
||||
"description": "PMS:<br>- Use EPG data from Plex or Emby <br><br>XEPG:<br>- Use of one or more XMLTV files<br>- Channel management<br>- M3U / XMLTV export (HTTP link for IPTV apps)"
|
||||
},
|
||||
"tuner":{
|
||||
"tuner":
|
||||
{
|
||||
"title": "Number of tuners",
|
||||
"description": "Number of parallel connections that can be established to the provider.<br>Available for: Plex, Emby (HDHR), M3U (with active buffer).<br>After a change, xTeVe must be delete in the Plex / Emby DVR settings and set up again."
|
||||
},
|
||||
"m3u": {
|
||||
"m3u":
|
||||
{
|
||||
"title": "M3U Playlist",
|
||||
"placeholder": "File path or URL of the M3U",
|
||||
"description": "Local or remote playlists"
|
||||
},
|
||||
"xmltv": {
|
||||
"xmltv":
|
||||
{
|
||||
"title": "XMLTV File",
|
||||
"placeholder": "File path or URL of the XMLTV",
|
||||
"description": "Local or remote XMLTV file"
|
||||
}
|
||||
},
|
||||
"login": {
|
||||
"login":
|
||||
{
|
||||
"failed": "User authentication failed",
|
||||
"headline": "Login",
|
||||
"username": {
|
||||
"username":
|
||||
{
|
||||
"title": "Username",
|
||||
"placeholder": "Username"
|
||||
},
|
||||
"password": {
|
||||
"password":
|
||||
{
|
||||
"title": "Password",
|
||||
"placeholder": "Password"
|
||||
}
|
||||
},
|
||||
"account": {
|
||||
"account":
|
||||
{
|
||||
"failed": "Password does not match",
|
||||
"headline": "Create user account",
|
||||
"username": {
|
||||
"username":
|
||||
{
|
||||
"title": "Username",
|
||||
"placeholder": "Username"
|
||||
},
|
||||
"password": {
|
||||
"password":
|
||||
{
|
||||
"title": "Password",
|
||||
"placeholder": "Password"
|
||||
},
|
||||
"confirm": {
|
||||
"confirm":
|
||||
{
|
||||
"title": "Confirm",
|
||||
"placeholder": "Confirm"
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
<div id="content">
|
||||
|
||||
<form id="authentication" action="/web/" method="post">
|
||||
<form id="authentication" action="" method="post">
|
||||
|
||||
<h5>{{.login.username.title}}:</h5>
|
||||
<input id="username" type="text" name="username" placeholder="Username" value="">
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"../src/internal/authentication"
|
||||
"xteve/src/internal/authentication"
|
||||
)
|
||||
|
||||
func activatedSystemAuthentication() (err error) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package src
|
||||
|
||||
/*
|
||||
Tuner-Limit Bild als Video rendern [ffmpeg]
|
||||
-loop 1 -i stream-limit.jpg -c:v libx264 -t 1 -pix_fmt yuv420p -vf scale=1920:1080 stream-limit.ts
|
||||
Tuner-Limit Bild als Video rendern [ffmpeg]
|
||||
-loop 1 -i stream-limit.jpg -c:v libx264 -t 1 -pix_fmt yuv420p -vf scale=1920:1080 stream-limit.ts
|
||||
*/
|
||||
|
||||
import (
|
||||
@@ -228,7 +228,7 @@ func bufferingStream(playlistID, streamingURL, channelName string, w http.Respon
|
||||
|
||||
}
|
||||
|
||||
//w.WriteHeader(200)
|
||||
w.WriteHeader(200)
|
||||
|
||||
for { // Loop 1: Warten bis das erste Segment durch den Buffer heruntergeladen wurde
|
||||
|
||||
@@ -332,23 +332,22 @@ func bufferingStream(playlistID, streamingURL, channelName string, w http.Respon
|
||||
|
||||
if streaming == false {
|
||||
|
||||
contentType := http.DetectContentType(buffer) + "; name=stream.ts"
|
||||
//_ = contentType
|
||||
contentType := http.DetectContentType(buffer)
|
||||
_ = contentType
|
||||
//w.Header().Set("Content-type", "video/mpeg")
|
||||
w.Header().Add("Content-type", contentType)
|
||||
w.Header().Set("Content-type", contentType)
|
||||
w.Header().Set("Content-Length", "0")
|
||||
w.Header().Set("Connection", "close")
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
// HDHR Header
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
w.Header().Set("transferMode.dlna.org", "Streaming")
|
||||
// HDHR Header
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
w.Header().Set("transferMode.dlna.org", "Streaming")
|
||||
*/
|
||||
|
||||
w.WriteHeader(200)
|
||||
_, err := w.Write(buffer)
|
||||
|
||||
if err != nil {
|
||||
@@ -825,7 +824,7 @@ func connectToStreamingServer(streamID int, playlistID string) {
|
||||
}
|
||||
|
||||
// Video Stream (TS)
|
||||
case "video/mpeg", "video/mp4", "video/mp2t", "video/m2ts", "application/octet-stream", "binary/octet-stream", "application/mp2t":
|
||||
case "video/mpeg", "video/mp4", "video/mp2t", "video/m2ts", "application/octet-stream", "binary/octet-stream", "application/mp2t", "video/x-matroska":
|
||||
|
||||
var fileSize int
|
||||
|
||||
@@ -1430,7 +1429,9 @@ func thirdPartyBuffer(streamID int, playlistID string) {
|
||||
case "FFMPEG":
|
||||
a = strings.Replace(a, "[URL]", url, -1)
|
||||
if i == 0 {
|
||||
args = []string{"-user-agent", Settings.UserAgent}
|
||||
if len(Settings.UserAgent) != 0 {
|
||||
args = []string{"-user_agent", Settings.UserAgent}
|
||||
}
|
||||
}
|
||||
|
||||
args = append(args, a)
|
||||
@@ -1439,7 +1440,10 @@ func thirdPartyBuffer(streamID int, playlistID string) {
|
||||
if a == "[URL]" {
|
||||
a = strings.Replace(a, "[URL]", url, -1)
|
||||
args = append(args, a)
|
||||
args = append(args, fmt.Sprintf(":http-user-agent=%s", Settings.UserAgent))
|
||||
|
||||
if len(Settings.UserAgent) != 0 {
|
||||
args = append(args, fmt.Sprintf(":http-user-agent=%s", Settings.UserAgent))
|
||||
}
|
||||
|
||||
} else {
|
||||
args = append(args, a)
|
||||
|
||||
@@ -146,3 +146,20 @@ func extractGZIP(gzipBody []byte, fileSource string) (body []byte, err error) {
|
||||
body = resB.Bytes()
|
||||
return
|
||||
}
|
||||
|
||||
func compressGZIP(data *[]byte, file string) (err error) {
|
||||
|
||||
if len(file) != 0 {
|
||||
|
||||
f, err := os.Create(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := gzip.NewWriter(f)
|
||||
w.Write(*data)
|
||||
w.Close()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ var System SystemStruct
|
||||
var WebScreenLog WebScreenLogStruct
|
||||
|
||||
// Settings : Inhalt der settings.json
|
||||
var Settings SettingsStrcut
|
||||
var Settings SettingsStruct
|
||||
|
||||
// Data : Alle Daten werden hier abgelegt. (Lineup, XMLTV)
|
||||
var Data DataStruct
|
||||
@@ -46,7 +46,8 @@ func Init() (err error) {
|
||||
System.ServerProtocol.M3U = "http"
|
||||
System.ServerProtocol.WEB = "http"
|
||||
System.ServerProtocol.XML = "http"
|
||||
System.DVRLimit = 480
|
||||
System.PlexChannelLimit = 480
|
||||
System.UnfilteredChannelLimit = 480
|
||||
System.Compatibility = "1.4.4"
|
||||
|
||||
// FFmpeg Default Einstellungen
|
||||
@@ -98,6 +99,8 @@ func Init() (err error) {
|
||||
System.File.XML = getPlatformFile(fmt.Sprintf("%s%s.xml", System.Folder.Data, System.AppName))
|
||||
System.File.M3U = getPlatformFile(fmt.Sprintf("%s%s.m3u", System.Folder.Data, System.AppName))
|
||||
|
||||
System.Compressed.GZxml = getPlatformFile(fmt.Sprintf("%s%s.xml.gz", System.Folder.Data, System.AppName))
|
||||
|
||||
err = activatedSystemAuthentication()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -223,13 +226,12 @@ func StartSystem(updateProviderFiles bool) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
setURLScheme()
|
||||
|
||||
// Systeminformationen in der Konsole ausgeben
|
||||
showInfo(fmt.Sprintf("UUID:%s", Settings.UUID))
|
||||
showInfo(fmt.Sprintf("Tuner (Plex / Emby):%d", Settings.Tuner))
|
||||
showInfo(fmt.Sprintf("EPG Source:%s", Settings.EpgSource))
|
||||
showInfo(fmt.Sprintf("Plex Channel Limit:%d", System.DVRLimit))
|
||||
showInfo(fmt.Sprintf("Plex Channel Limit:%d", System.PlexChannelLimit))
|
||||
showInfo(fmt.Sprintf("Unfiltered Chan. Limit:%d", System.UnfilteredChannelLimit))
|
||||
|
||||
// Providerdaten aktualisieren
|
||||
if len(Settings.Files.M3U) > 0 && Settings.FilesUpdate == true || updateProviderFiles == true {
|
||||
|
||||
86
src/data.go
86
src/data.go
@@ -11,11 +11,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"../src/internal/authentication"
|
||||
"xteve/src/internal/authentication"
|
||||
"xteve/src/internal/imgcache"
|
||||
)
|
||||
|
||||
// Einstellungen ändern (WebUI)
|
||||
func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err error) {
|
||||
func updateServerSettings(request RequestStruct) (settings SettingsStruct, err error) {
|
||||
|
||||
var oldSettings = jsonToMap(mapToJSON(Settings))
|
||||
var newSettings = jsonToMap(mapToJSON(request.Settings))
|
||||
@@ -40,7 +41,7 @@ func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err e
|
||||
|
||||
case "update":
|
||||
// Leerzeichen aus den Werten entfernen und Formatierung der Uhrzeit überprüfen (0000 - 2359)
|
||||
var newUpdateTimes []string
|
||||
var newUpdateTimes = make([]string, 0)
|
||||
|
||||
for _, v := range value.([]interface{}) {
|
||||
|
||||
@@ -56,6 +57,10 @@ func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err e
|
||||
|
||||
}
|
||||
|
||||
if len(newUpdateTimes) == 0 {
|
||||
//newUpdateTimes = append(newUpdateTimes, "0000")
|
||||
}
|
||||
|
||||
value = newUpdateTimes
|
||||
|
||||
case "cache.images":
|
||||
@@ -144,8 +149,6 @@ func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err e
|
||||
return
|
||||
}
|
||||
|
||||
setURLScheme()
|
||||
|
||||
if Settings.AuthenticationWEB == false {
|
||||
|
||||
Settings.AuthenticationAPI = false
|
||||
@@ -201,25 +204,38 @@ func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err e
|
||||
|
||||
if cacheImages == true {
|
||||
|
||||
if Settings.EpgSource == "XEPG" {
|
||||
if Settings.EpgSource == "XEPG" && System.ImageCachingInProgress == 0 {
|
||||
|
||||
go func() {
|
||||
Data.Cache.Images, err = imgcache.New(System.Folder.ImagesCache, fmt.Sprintf("%s://%s/images/", System.ServerProtocol.WEB, System.Domain), Settings.CacheImages)
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
}
|
||||
|
||||
if Settings.CacheImages == true {
|
||||
switch Settings.CacheImages {
|
||||
|
||||
createXMLTVFile()
|
||||
cachingImages()
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
case false:
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
|
||||
} else {
|
||||
case true:
|
||||
go func() {
|
||||
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
|
||||
}
|
||||
System.ImageCachingInProgress = 1
|
||||
showInfo("Image Caching:Images are cached")
|
||||
|
||||
}()
|
||||
Data.Cache.Images.Image.Caching()
|
||||
showInfo("Image Caching:Done")
|
||||
|
||||
System.ImageCachingInProgress = 0
|
||||
|
||||
buildXEPG(false)
|
||||
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -406,7 +422,7 @@ func deleteLocalProviderFiles(dataID, fileType string) {
|
||||
}
|
||||
|
||||
// Filtereinstellungen speichern (WebUI)
|
||||
func saveFilter(request RequestStruct) (settings SettingsStrcut, err error) {
|
||||
func saveFilter(request RequestStruct) (settings SettingsStruct, err error) {
|
||||
|
||||
var filterMap = make(map[int64]interface{})
|
||||
var newData = make(map[int64]interface{})
|
||||
@@ -494,6 +510,11 @@ func saveXEpgMapping(request RequestStruct) (err error) {
|
||||
|
||||
var tmp = Data.XEPG
|
||||
|
||||
Data.Cache.Images, err = imgcache.New(System.Folder.ImagesCache, fmt.Sprintf("%s://%s/images/", System.ServerProtocol.WEB, System.Domain), Settings.CacheImages)
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(mapToJSON(request.EpgMapping)), &tmp)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -510,19 +531,9 @@ func saveXEpgMapping(request RequestStruct) (err error) {
|
||||
|
||||
System.ScanInProgress = 1
|
||||
cleanupXEPG()
|
||||
System.ScanInProgress = 0
|
||||
buildXEPG(true)
|
||||
|
||||
go func() {
|
||||
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
||||
go cachingImages()
|
||||
|
||||
System.ScanInProgress = 0
|
||||
|
||||
}()
|
||||
|
||||
} else {
|
||||
|
||||
// Wenn während des erstellen der Datanbank das Mapping erneut gespeichert wird, wird die Datenbank erst später erneut aktualisiert.
|
||||
@@ -543,15 +554,10 @@ func saveXEpgMapping(request RequestStruct) (err error) {
|
||||
}
|
||||
|
||||
System.ScanInProgress = 1
|
||||
|
||||
cleanupXEPG()
|
||||
buildXEPG(false)
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
||||
go cachingImages()
|
||||
|
||||
System.ScanInProgress = 0
|
||||
buildXEPG(false)
|
||||
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
||||
|
||||
System.BackgroundProcess = false
|
||||
|
||||
@@ -791,9 +797,9 @@ func buildDatabaseDVR() (err error) {
|
||||
|
||||
System.ScanInProgress = 1
|
||||
|
||||
Data.Streams.All = make([]interface{}, 0)
|
||||
Data.Streams.Active = make([]interface{}, 0)
|
||||
Data.Streams.Inactive = make([]interface{}, 0)
|
||||
Data.Streams.All = make([]interface{}, 0, System.UnfilteredChannelLimit)
|
||||
Data.Streams.Active = make([]interface{}, 0, System.UnfilteredChannelLimit)
|
||||
Data.Streams.Inactive = make([]interface{}, 0, System.UnfilteredChannelLimit)
|
||||
Data.Playlist.M3U.Groups.Text = []string{}
|
||||
Data.Playlist.M3U.Groups.Value = []string{}
|
||||
Data.StreamPreviewUI.Active = []string{}
|
||||
@@ -949,7 +955,7 @@ func buildDatabaseDVR() (err error) {
|
||||
sort.Strings(Data.Playlist.M3U.Groups.Text)
|
||||
sort.Strings(Data.Playlist.M3U.Groups.Value)
|
||||
|
||||
if len(Data.Streams.Active) == 0 && len(Data.Streams.All) <= System.DVRLimit && len(Settings.Filter) == 0 {
|
||||
if len(Data.Streams.Active) == 0 && len(Data.Streams.All) <= System.UnfilteredChannelLimit && len(Settings.Filter) == 0 {
|
||||
Data.Streams.Active = Data.Streams.All
|
||||
Data.Streams.Inactive = make([]interface{}, 0)
|
||||
|
||||
@@ -958,11 +964,11 @@ func buildDatabaseDVR() (err error) {
|
||||
|
||||
}
|
||||
|
||||
if len(Data.Streams.Active) > System.DVRLimit {
|
||||
if len(Data.Streams.Active) > System.PlexChannelLimit {
|
||||
showWarning(2000)
|
||||
}
|
||||
|
||||
if len(Settings.Filter) == 0 && len(Data.Streams.All) > System.DVRLimit {
|
||||
if len(Settings.Filter) == 0 && len(Data.Streams.All) > System.UnfilteredChannelLimit {
|
||||
showWarning(2001)
|
||||
}
|
||||
|
||||
|
||||
140
src/images.go
140
src/images.go
@@ -3,155 +3,15 @@ package src
|
||||
import (
|
||||
b64 "encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getCacheImageURL(imageURL string) (cacheImageURL string) {
|
||||
|
||||
if Settings.CacheImages == false {
|
||||
return imageURL
|
||||
}
|
||||
|
||||
imageURL = strings.Trim(imageURL, "\r\n")
|
||||
|
||||
p, err := url.Parse(imageURL)
|
||||
if err != nil {
|
||||
// URL konnte nicht geparst werden, die ursprüngliche image url wird zurückgegeben
|
||||
showInfo(fmt.Sprintf("Image Caching:Image URL: %s", imageURL))
|
||||
showWarning(4101)
|
||||
return imageURL
|
||||
}
|
||||
var urlMD5 = getMD5(imageURL)
|
||||
var fileExtension = filepath.Ext(p.Path)
|
||||
|
||||
if len(fileExtension) == 0 {
|
||||
// Keine Dateierweiterung vorhanden, die ursprüngliche image url wird zurückgegeben
|
||||
return imageURL
|
||||
}
|
||||
|
||||
if indexOfString(urlMD5+fileExtension, Data.Cache.ImagesFiles) == -1 {
|
||||
Data.Cache.ImagesFiles = append(Data.Cache.ImagesFiles, urlMD5+fileExtension)
|
||||
}
|
||||
|
||||
if System.ImageCachingInProgress == 1 {
|
||||
return imageURL
|
||||
}
|
||||
|
||||
if indexOfString(urlMD5+fileExtension, Data.Cache.ImagesCache) != -1 {
|
||||
|
||||
cacheImageURL = fmt.Sprintf("%s://%s/images/%s%s", System.ServerProtocol.XML, System.Domain, urlMD5, fileExtension)
|
||||
|
||||
} else {
|
||||
|
||||
if strings.Contains(imageURL, System.Domain+"/images/") == false {
|
||||
|
||||
if indexOfString(imageURL, Data.Cache.ImagesURLS) == -1 {
|
||||
Data.Cache.ImagesURLS = append(Data.Cache.ImagesURLS, imageURL)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cacheImageURL = imageURL
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func cachingImages() {
|
||||
|
||||
if Settings.CacheImages == false || System.ImageCachingInProgress == 1 {
|
||||
return
|
||||
}
|
||||
|
||||
System.ImageCachingInProgress = 1
|
||||
|
||||
showInfo("Image Caching:Images are cached")
|
||||
|
||||
for _, imageURL := range Data.Cache.ImagesURLS {
|
||||
|
||||
if len(imageURL) > 0 {
|
||||
cacheImage(imageURL)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
showInfo("Image Caching:Done")
|
||||
|
||||
// Bilder die nicht mehr verwendet werden, werden gelöscht
|
||||
files, err := ioutil.ReadDir(System.Folder.ImagesCache)
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
return
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
|
||||
if indexOfString(file.Name(), Data.Cache.ImagesFiles) == -1 {
|
||||
|
||||
var debug = fmt.Sprintf("Image Caching:Remove file: %s %s %d", System.Folder.ImagesCache+file.Name(), file.Name(), len(file.Name()))
|
||||
showDebug(debug, 1)
|
||||
err := os.RemoveAll(System.Folder.ImagesCache + file.Name())
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
System.ImageCachingInProgress = 0
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func cacheImage(imageURL string) {
|
||||
|
||||
var debug string
|
||||
var urlMD5 = getMD5(imageURL)
|
||||
var fileExtension = filepath.Ext(imageURL)
|
||||
|
||||
debug = fmt.Sprintf("Image Caching:File: %s Download: %s", urlMD5+fileExtension, imageURL)
|
||||
showDebug(debug, 1)
|
||||
|
||||
resp, err := http.Get(imageURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return
|
||||
}
|
||||
|
||||
var filePath = System.Folder.ImagesCache + urlMD5 + fileExtension
|
||||
|
||||
// Datei speichern
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(file, resp.Body)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func uploadLogo(input, filename string) (logoURL string, err error) {
|
||||
|
||||
b64data := input[strings.IndexByte(input, ',')+1:]
|
||||
|
||||
// BAse64 in bytes umwandeln un speichern
|
||||
sDec, err := b64.StdEncoding.DecodeString(b64data)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ func ShowSystemInfo() {
|
||||
|
||||
fmt.Println("Settings [Streaming]")
|
||||
fmt.Println(fmt.Sprintf("Buffer: %s", Settings.Buffer))
|
||||
fmt.Println(fmt.Sprintf("UDPxy: %s", Settings.UDPxy))
|
||||
fmt.Println(fmt.Sprintf("Buffer Size: %d KB", Settings.BufferSize))
|
||||
fmt.Println(fmt.Sprintf("Timeout: %d ms", int(Settings.BufferTimeout)))
|
||||
fmt.Println(fmt.Sprintf("User Agent: %s", Settings.UserAgent))
|
||||
|
||||
178
src/internal/imgcache/cache.go
Normal file
178
src/internal/imgcache/cache.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package imgcache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Cache : Cache strcut
|
||||
type Cache struct {
|
||||
path string
|
||||
cacheURL string
|
||||
caching bool
|
||||
images map[string]string
|
||||
Queue []string
|
||||
Cache []string
|
||||
Image imageFunc
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
type imageFunc struct {
|
||||
GetURL func(string) string
|
||||
Caching func()
|
||||
Remove func()
|
||||
}
|
||||
|
||||
// New : New cahce
|
||||
func New(path, chacheURL string, caching bool) (c *Cache, err error) {
|
||||
|
||||
c = &Cache{}
|
||||
|
||||
c.images = make(map[string]string)
|
||||
c.path = path
|
||||
c.cacheURL = chacheURL
|
||||
c.caching = caching
|
||||
c.Queue = []string{}
|
||||
c.Cache = []string{}
|
||||
|
||||
var queue []string
|
||||
|
||||
c.Image.GetURL = func(src string) (cacheURL string) {
|
||||
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
src = strings.Trim(src, "\r\n")
|
||||
|
||||
if c.caching == false {
|
||||
return src
|
||||
}
|
||||
|
||||
u, err := url.Parse(src)
|
||||
if err != nil || len(filepath.Ext(u.Path)) == 0 {
|
||||
return src
|
||||
}
|
||||
|
||||
var filename = fmt.Sprintf("%s%s", strToMD5(src), filepath.Ext(u.Path))
|
||||
if cacheURL, ok := c.images[fmt.Sprintf("%s%s", strToMD5(src), filepath.Ext(u.Path))]; ok {
|
||||
return cacheURL
|
||||
}
|
||||
|
||||
if indexOfString(filename, c.Cache) == -1 {
|
||||
|
||||
if indexOfString(src, c.Queue) == -1 {
|
||||
c.Queue = append(c.Queue, src)
|
||||
}
|
||||
|
||||
} else {
|
||||
c.images[filename] = c.cacheURL + filename
|
||||
src = c.cacheURL + filename
|
||||
}
|
||||
|
||||
/*
|
||||
if _, err := os.Stat(c.path + filename); err != nil {
|
||||
//c.images[filename] = c.cacheURL + filename
|
||||
if indexOfString(src, c.Queue) == -1 {
|
||||
c.Queue = append(c.Queue, src)
|
||||
}
|
||||
} else {
|
||||
c.images[filename] = c.cacheURL + filename
|
||||
}
|
||||
*/
|
||||
|
||||
return src
|
||||
}
|
||||
|
||||
c.Image.Caching = func() {
|
||||
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
var filename string
|
||||
|
||||
for _, src := range c.Queue {
|
||||
|
||||
resp, err := http.Get(src)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
continue
|
||||
}
|
||||
|
||||
filename = fmt.Sprintf("%s%s%s%s", c.path, string(os.PathSeparator), strToMD5(src), filepath.Ext(src))
|
||||
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(file, resp.Body)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
u, err := url.Parse(src)
|
||||
if err == nil {
|
||||
c.images[fmt.Sprintf("%s%s", strToMD5(src), filepath.Ext(u.Path))] = c.cacheURL + filename
|
||||
}
|
||||
|
||||
queue = append(queue, src)
|
||||
|
||||
}
|
||||
|
||||
for _, q := range queue {
|
||||
c.Queue = removeStringFromSlice(q, c.Queue)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
c.Image.Remove = func() {
|
||||
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
files, err := ioutil.ReadDir(c.path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
|
||||
switch c.caching {
|
||||
|
||||
case true:
|
||||
if _, ok := c.images[file.Name()]; !ok {
|
||||
os.RemoveAll(c.path + file.Name())
|
||||
}
|
||||
|
||||
case false:
|
||||
os.RemoveAll(c.path + file.Name())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(c.path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
c.Cache = append(c.Cache, file.Name())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
34
src/internal/imgcache/tools.go
Normal file
34
src/internal/imgcache/tools.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package imgcache
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func strToMD5(str string) string {
|
||||
md5Hasher := md5.New()
|
||||
md5Hasher.Write([]byte(str))
|
||||
return hex.EncodeToString(md5Hasher.Sum(nil))
|
||||
}
|
||||
|
||||
func indexOfString(str string, slice []string) int {
|
||||
|
||||
for i, v := range slice {
|
||||
if str == v {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func removeStringFromSlice(str string, slice []string) []string {
|
||||
|
||||
var i = indexOfString(str, slice)
|
||||
|
||||
if i != -1 {
|
||||
slice = append(slice[:i], slice[i+1:]...)
|
||||
}
|
||||
|
||||
return slice
|
||||
}
|
||||
@@ -1,7 +1,13 @@
|
||||
#EXTM3U url-tvg="http://example.com/file.xml" x-tvg-url="http://example.com/xteve.xml"
|
||||
#EXTINF:0 channelID="1" tvg-chno="1" tvg-name="Channel.1" tvg-id="tvg.id.1" tvg-logo="https://example/logo.png" group-title="Group 1",Channel 1
|
||||
#EXTINF:0 channelID="1" tvg-chno="1" tvg-name="Channel.1" tvg-id="tvg.id.1" tvg-logo="https://example/logo.png" group-title="Group 1", Channel 1
|
||||
http://example.com/stream/1
|
||||
|
||||
#EXTINF:0 channelID="2" tvg-chno="2" tvg-name="Channel.2" tvg-id="tvg.id.2" tvg-logo="https://example/logo.png" group-title="Group 2",Channel 2
|
||||
#123
|
||||
http://example.com/stream/2
|
||||
|
||||
#EXTINF:123, Sample artist - Sample title
|
||||
http://example.com/stream/3
|
||||
|
||||
#EXTINF:321,Example Artist - Example title
|
||||
http://example.com/stream/4
|
||||
|
||||
@@ -144,6 +144,10 @@ func MakeInterfaceFromM3U(byteStream []byte) (allChannels []interface{}, err err
|
||||
}
|
||||
|
||||
//fmt.Println(content)
|
||||
if strings.Contains(content, "#EXT-X-TARGETDURATION") || strings.Contains(content, "#EXT-X-MEDIA-SEQUENCE") {
|
||||
err = errors.New("Invalid M3U file, an extended M3U file is required.")
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(content, "#EXTM3U") {
|
||||
|
||||
@@ -163,7 +167,7 @@ func MakeInterfaceFromM3U(byteStream []byte) (allChannels []interface{}, err err
|
||||
|
||||
} else {
|
||||
|
||||
err = errors.New("No valid m3u file")
|
||||
err = errors.New("Invalid M3U file, an extended M3U file is required.")
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
m3u "../src/internal/m3u-parser"
|
||||
m3u "xteve/src/internal/m3u-parser"
|
||||
)
|
||||
|
||||
// Playlisten parsen
|
||||
@@ -181,6 +181,7 @@ func checkConditions(streamValues, conditions, coType string) (status bool) {
|
||||
// xTeVe M3U Datei erstellen
|
||||
func buildM3U(groups []string) (m3u string, err error) {
|
||||
|
||||
var imgc = Data.Cache.Images
|
||||
var m3uChannels = make(map[float64]XEPGChannelStruct)
|
||||
var channelNumbers []float64
|
||||
|
||||
@@ -223,7 +224,8 @@ func buildM3U(groups []string) (m3u string, err error) {
|
||||
for _, channelNumber := range channelNumbers {
|
||||
|
||||
var channel = m3uChannels[channelNumber]
|
||||
var parameter = fmt.Sprintf(`#EXTINF:0 channelID="%s" tvg-chno="%s" tvg-name="%s" tvg-id="%s" tvg-logo="%s" group-title="%s",%s`+"\n", channel.XEPG, channel.XChannelID, channel.XName, channel.XChannelID, getCacheImageURL(channel.TvgLogo), channel.XGroupTitle, channel.XName)
|
||||
|
||||
var parameter = fmt.Sprintf(`#EXTINF:0 channelID="%s" tvg-chno="%s" tvg-name="%s" tvg-id="%s" tvg-logo="%s" group-title="%s",%s`+"\n", channel.XEPG, channel.XChannelID, channel.XName, channel.XChannelID, imgc.Image.GetURL(channel.TvgLogo), channel.XGroupTitle, channel.XName)
|
||||
var stream, err = createStreamingURL("M3U", channel.FileM3UID, channel.XChannelID, channel.XName, channel.URL)
|
||||
if err == nil {
|
||||
m3u = m3u + parameter + stream + "\n"
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
m3u "../src/internal/m3u-parser"
|
||||
m3u "xteve/src/internal/m3u-parser"
|
||||
)
|
||||
|
||||
// fileType: Welcher Dateityp soll aktualisiert werden (m3u, hdhr, xml) | fileID: Update einer bestimmten Datei (Provider ID)
|
||||
|
||||
@@ -16,7 +16,7 @@ func showInfo(str string) {
|
||||
return
|
||||
}
|
||||
|
||||
var max = 22
|
||||
var max = 23
|
||||
var msg = strings.SplitN(str, ":", 2)
|
||||
var length = len(msg[0])
|
||||
var space string
|
||||
@@ -48,7 +48,7 @@ func showDebug(str string, level int) {
|
||||
return
|
||||
}
|
||||
|
||||
var max = 22
|
||||
var max = 23
|
||||
var msg = strings.SplitN(str, ":", 2)
|
||||
var length = len(msg[0])
|
||||
var space string
|
||||
@@ -78,7 +78,7 @@ func showDebug(str string, level int) {
|
||||
|
||||
func showHighlight(str string) {
|
||||
|
||||
var max = 22
|
||||
var max = 23
|
||||
var msg = strings.SplitN(str, ":", 2)
|
||||
var length = len(msg[0])
|
||||
var space string
|
||||
@@ -232,7 +232,7 @@ func getErrMsg(errCode int) (errMsg string) {
|
||||
case 1004:
|
||||
errMsg = fmt.Sprintf("File not found")
|
||||
case 1005:
|
||||
errMsg = fmt.Sprintf("Invalide m3u")
|
||||
errMsg = fmt.Sprintf("Invalid M3U file, an extended M3U file is required.")
|
||||
case 1006:
|
||||
errMsg = fmt.Sprintf("No playlist!")
|
||||
case 1007:
|
||||
@@ -300,9 +300,9 @@ func getErrMsg(errCode int) (errMsg string) {
|
||||
|
||||
// Warnings
|
||||
case 2000:
|
||||
errMsg = fmt.Sprintf("Plex can not handle more than %d streams. If you do not use Plex, you can ignore this warning.", System.DVRLimit)
|
||||
errMsg = fmt.Sprintf("Plex can not handle more than %d streams. If you do not use Plex, you can ignore this warning.", System.PlexChannelLimit)
|
||||
case 2001:
|
||||
errMsg = fmt.Sprintf("%s has loaded more than %d streams. Use the filter to reduce the number of streams.", System.Name, System.DVRLimit)
|
||||
errMsg = fmt.Sprintf("%s has loaded more than %d streams. Use the filter to reduce the number of streams.", System.Name, System.UnfilteredChannelLimit)
|
||||
case 2002:
|
||||
errMsg = fmt.Sprintf("PMS can not play m3u8 streams")
|
||||
case 2003:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package src
|
||||
|
||||
import "xteve/src/internal/imgcache"
|
||||
|
||||
// SystemStruct : Beinhaltet alle Systeminformationen
|
||||
type SystemStruct struct {
|
||||
Addresses struct {
|
||||
@@ -8,19 +10,20 @@ type SystemStruct struct {
|
||||
XML string
|
||||
}
|
||||
|
||||
APIVersion string
|
||||
AppName string
|
||||
ARCH string
|
||||
BackgroundProcess bool
|
||||
Branch string
|
||||
Build string
|
||||
Compatibility string
|
||||
ConfigurationWizard bool
|
||||
DBVersion string
|
||||
Dev bool
|
||||
DeviceID string
|
||||
Domain string
|
||||
DVRLimit int
|
||||
APIVersion string
|
||||
AppName string
|
||||
ARCH string
|
||||
BackgroundProcess bool
|
||||
Branch string
|
||||
Build string
|
||||
Compatibility string
|
||||
ConfigurationWizard bool
|
||||
DBVersion string
|
||||
Dev bool
|
||||
DeviceID string
|
||||
Domain string
|
||||
PlexChannelLimit int
|
||||
UnfilteredChannelLimit int
|
||||
|
||||
FFmpeg struct {
|
||||
DefaultOptions string
|
||||
@@ -42,6 +45,10 @@ type SystemStruct struct {
|
||||
XML string
|
||||
}
|
||||
|
||||
Compressed struct {
|
||||
GZxml string
|
||||
}
|
||||
|
||||
Flag struct {
|
||||
Branch string
|
||||
Debug int
|
||||
@@ -95,6 +102,7 @@ type SystemStruct struct {
|
||||
}
|
||||
|
||||
URLBase string
|
||||
UDPxy string
|
||||
Version string
|
||||
WEB struct {
|
||||
Menu []string
|
||||
@@ -110,6 +118,7 @@ type GitStruct struct {
|
||||
// DataStruct : Alle Daten werden hier abgelegt. (Lineup, XMLTV)
|
||||
type DataStruct struct {
|
||||
Cache struct {
|
||||
Images *imgcache.Cache
|
||||
ImagesCache []string
|
||||
ImagesFiles []string
|
||||
ImagesURLS []string
|
||||
@@ -242,8 +251,8 @@ type Notification struct {
|
||||
Type string `json:"type,required"`
|
||||
}
|
||||
|
||||
// SettingsStrcut : Inhalt der settings.json
|
||||
type SettingsStrcut struct {
|
||||
// SettingsStruct : Inhalt der settings.json
|
||||
type SettingsStruct struct {
|
||||
API bool `json:"api"`
|
||||
AuthenticationAPI bool `json:"authentication.api"`
|
||||
AuthenticationM3U bool `json:"authentication.m3u"`
|
||||
@@ -280,14 +289,13 @@ type SettingsStrcut struct {
|
||||
MappingFirstChannel float64 `json:"mapping.first.channel"`
|
||||
Port string `json:"port"`
|
||||
SSDP bool `json:"ssdp"`
|
||||
SchemeM3U string `json:"scheme.m3u"`
|
||||
SchemeXML string `json:"scheme.xml"`
|
||||
TempPath string `json:"temp.path"`
|
||||
Tuner int `json:"tuner"`
|
||||
Update []string `json:"update"`
|
||||
UpdateURL string `json:"update.url,omitempty"`
|
||||
UserAgent string `json:"user.agent"`
|
||||
UUID string `json:"uuid"`
|
||||
UDPxy string `json:"udpxy"`
|
||||
Version string `json:"version"`
|
||||
XepgReplaceMissingImages bool `json:"xepg.replace.missing.images"`
|
||||
XteveAutoUpdate bool `json:"xteveAutoUpdate"`
|
||||
|
||||
@@ -37,6 +37,7 @@ type RequestStruct struct {
|
||||
FilesUpdate *bool `json:"files.update,omitempty"`
|
||||
TempPath *string `json:"temp.path,omitempty"`
|
||||
Tuner *int `json:"tuner,omitempty"`
|
||||
UDPxy *string `json:"udpxy,omitempty"`
|
||||
Update *[]string `json:"update,omitempty"`
|
||||
UserAgent *string `json:"user.agent,omitempty"`
|
||||
XepgReplaceMissingImages *bool `json:"xepg.replace.missing.images,omitempty"`
|
||||
@@ -109,7 +110,7 @@ type ResponseStruct struct {
|
||||
OpenLink string `json:"openLink,omitempty"`
|
||||
OpenMenu string `json:"openMenu,omitempty"`
|
||||
Reload bool `json:"reload,omitempty"`
|
||||
Settings SettingsStrcut `json:"settings,required"`
|
||||
Settings SettingsStruct `json:"settings,required"`
|
||||
Status bool `json:"status,required"`
|
||||
Token string `json:"token,omitempty"`
|
||||
Users map[string]interface{} `json:"users,omitempty"`
|
||||
|
||||
@@ -42,6 +42,9 @@ type Program struct {
|
||||
Country []*Country `xml:"country"`
|
||||
EpisodeNum []*EpisodeNum `xml:"episode-num"`
|
||||
Poster []Poster `xml:"icon"`
|
||||
Credits Credits `xml:"credits,omitempty"` //`xml:",innerxml,omitempty"`
|
||||
Rating []Rating `xml:"rating"`
|
||||
StarRating []StarRating `xml:"star-rating"`
|
||||
Language []*Language `xml:"language"`
|
||||
Video Video `xml:"video"`
|
||||
Date string `xml:"date"`
|
||||
@@ -75,6 +78,19 @@ type Category struct {
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// Rating : Bewertung
|
||||
type Rating struct {
|
||||
System string `xml:"system,attr"`
|
||||
Value string `xml:"value"`
|
||||
Icon []Icon `xml:"icon"`
|
||||
}
|
||||
|
||||
// StarRating : Bewertung / Kritiken
|
||||
type StarRating struct {
|
||||
Value string `xml:"value"`
|
||||
System string `xml:"system,attr"`
|
||||
}
|
||||
|
||||
// Language : Sprachen
|
||||
type Language struct {
|
||||
Value string `xml:",chardata"`
|
||||
@@ -100,6 +116,41 @@ type Poster struct {
|
||||
Width string `xml:"width,attr"`
|
||||
}
|
||||
|
||||
// Credits : Credits
|
||||
type Credits struct {
|
||||
Director []Director `xml:"director,omitempty"`
|
||||
Actor []Actor `xml:"actor,omitempty"`
|
||||
Writer []Writer `xml:"writer,omitempty"`
|
||||
Presenter []Presenter `xml:"presenter,omitempty"`
|
||||
Producer []Producer `xml:"producer,omitempty"`
|
||||
}
|
||||
|
||||
// Director : Director
|
||||
type Director struct {
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// Actor : Actor
|
||||
type Actor struct {
|
||||
Value string `xml:",chardata"`
|
||||
Role string `xml:"role,attr,omitempty"`
|
||||
}
|
||||
|
||||
// Writer : Writer
|
||||
type Writer struct {
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// Presenter : Presenter
|
||||
type Presenter struct {
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// Producer : Producer
|
||||
type Producer struct {
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// Video : Video Metadaten
|
||||
type Video struct {
|
||||
Aspect string `xml:"aspect,omitempty"`
|
||||
|
||||
@@ -90,7 +90,7 @@ func createSystemFiles() (err error) {
|
||||
}
|
||||
|
||||
// Einstellungen laden und default Werte setzen (xTeVe)
|
||||
func loadSettings() (settings SettingsStrcut, err error) {
|
||||
func loadSettings() (settings SettingsStruct, err error) {
|
||||
|
||||
settingsMap, err := loadJSONFileToMap(System.File.Settings)
|
||||
if err != nil {
|
||||
@@ -135,11 +135,10 @@ func loadSettings() (settings SettingsStrcut, err error) {
|
||||
defaults["update"] = []string{"0000"}
|
||||
defaults["user.agent"] = System.Name
|
||||
defaults["uuid"] = createUUID()
|
||||
defaults["udpxy"] = ""
|
||||
defaults["version"] = System.DBVersion
|
||||
defaults["xteveAutoUpdate"] = true
|
||||
defaults["temp.path"] = System.Folder.Temp
|
||||
defaults["scheme.M3U"] = "HTTP"
|
||||
defaults["scheme.XML"] = "HTTP"
|
||||
|
||||
// Default Werte setzen
|
||||
for key, value := range defaults {
|
||||
@@ -188,7 +187,7 @@ func loadSettings() (settings SettingsStrcut, err error) {
|
||||
}
|
||||
|
||||
// Einstellungen speichern (xTeVe)
|
||||
func saveSettings(settings SettingsStrcut) (err error) {
|
||||
func saveSettings(settings SettingsStruct) (err error) {
|
||||
|
||||
if settings.BackupKeep == 0 {
|
||||
settings.BackupKeep = 10
|
||||
@@ -254,14 +253,6 @@ func setGlobalDomain(domain string) {
|
||||
return
|
||||
}
|
||||
|
||||
func setURLScheme() {
|
||||
|
||||
System.ServerProtocol.M3U = strings.ToLower(Settings.SchemeM3U)
|
||||
System.ServerProtocol.XML = strings.ToLower(Settings.SchemeXML)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UUID generieren
|
||||
func createUUID() (uuid string) {
|
||||
uuid = time.Now().Format("2006-01") + "-" + randomString(4) + "-" + randomString(6)
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
up2date "../src/internal/up2date/client"
|
||||
up2date "xteve/src/internal/up2date/client"
|
||||
|
||||
"reflect"
|
||||
)
|
||||
|
||||
72
src/webUI.go
72
src/webUI.go
File diff suppressed because one or more lines are too long
@@ -10,7 +10,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"../src/internal/authentication"
|
||||
"xteve/src/internal/authentication"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
@@ -30,6 +30,7 @@ func StartWebserver() (err error) {
|
||||
http.HandleFunc("/api/", API)
|
||||
http.HandleFunc("/images/", Images)
|
||||
http.HandleFunc("/data_images/", DataImages)
|
||||
|
||||
//http.HandleFunc("/auto/", Auto)
|
||||
|
||||
showInfo("DVR IP:" + System.IPAddress + ":" + Settings.Port)
|
||||
@@ -129,6 +130,12 @@ func Stream(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// If an UDPxy host is set, and the stream URL is multicast (i.e. starts with 'udp://@'),
|
||||
// then streamInfo.URL needs to be rewritten to point to UDPxy.
|
||||
if Settings.UDPxy != "" && strings.HasPrefix(streamInfo.URL, "udp://@") {
|
||||
streamInfo.URL = fmt.Sprintf("http://%s/udp/%s/", Settings.UDPxy, strings.TrimPrefix(streamInfo.URL, "udp://@"))
|
||||
}
|
||||
|
||||
switch Settings.Buffer {
|
||||
|
||||
case "-":
|
||||
@@ -206,7 +213,7 @@ func Auto(w http.ResponseWriter, r *http.Request) {
|
||||
// xTeVe : Web Server /xmltv/ und /m3u/
|
||||
func xTeVe(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var requestType, groupTitle, file, content string
|
||||
var requestType, groupTitle, file, content, contentType string
|
||||
var err error
|
||||
var path = strings.TrimPrefix(r.URL.Path, "/")
|
||||
var groups = []string{}
|
||||
@@ -216,7 +223,6 @@ func xTeVe(w http.ResponseWriter, r *http.Request) {
|
||||
// XMLTV Datei
|
||||
if strings.Contains(path, "xmltv/") {
|
||||
|
||||
w.Header().Set("Content-Type", "application/xml")
|
||||
requestType = "xml"
|
||||
|
||||
file = System.Folder.Data + getFilenameFromPath(path)
|
||||
@@ -260,6 +266,13 @@ func xTeVe(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
contentType = http.DetectContentType([]byte(content))
|
||||
if strings.Contains(strings.ToLower(contentType), "xml") {
|
||||
contentType = "application/xml; charset=utf-8"
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
|
||||
if err == nil {
|
||||
w.Write([]byte(content))
|
||||
}
|
||||
@@ -327,6 +340,7 @@ func WS(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
http.Error(w, "Could not open websocket connection", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
setGlobalDomain(r.Host)
|
||||
@@ -585,6 +599,8 @@ func Web(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var language LanguageUI
|
||||
|
||||
setGlobalDomain(r.Host)
|
||||
|
||||
if System.Dev == true {
|
||||
|
||||
lang, err = loadJSONFileToMap(fmt.Sprintf("html/lang/%s.json", Settings.Language))
|
||||
|
||||
218
src/xepg.go
218
src/xepg.go
@@ -8,10 +8,15 @@ import (
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"runtime"
|
||||
"sort"
|
||||
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"xteve/src/internal/imgcache"
|
||||
)
|
||||
|
||||
// Provider XMLTV Datei überprüfen
|
||||
@@ -42,6 +47,13 @@ func buildXEPG(background bool) {
|
||||
|
||||
System.ScanInProgress = 1
|
||||
|
||||
var err error
|
||||
|
||||
Data.Cache.Images, err = imgcache.New(System.Folder.ImagesCache, fmt.Sprintf("%s://%s/images/", System.ServerProtocol.WEB, System.Domain), Settings.CacheImages)
|
||||
if err != nil {
|
||||
ShowError(err, 0)
|
||||
}
|
||||
|
||||
if Settings.EpgSource == "XEPG" {
|
||||
|
||||
switch background {
|
||||
@@ -56,10 +68,29 @@ func buildXEPG(background bool) {
|
||||
cleanupXEPG()
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
go cachingImages()
|
||||
|
||||
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
||||
|
||||
if Settings.CacheImages == true && System.ImageCachingInProgress == 0 {
|
||||
|
||||
go func() {
|
||||
|
||||
System.ImageCachingInProgress = 1
|
||||
showInfo(fmt.Sprintf("Image Caching:Images are cached (%d)", len(Data.Cache.Images.Queue)))
|
||||
|
||||
Data.Cache.Images.Image.Caching()
|
||||
Data.Cache.Images.Image.Remove()
|
||||
showInfo("Image Caching:Done")
|
||||
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
|
||||
System.ImageCachingInProgress = 0
|
||||
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
System.ScanInProgress = 0
|
||||
|
||||
// Cache löschen
|
||||
@@ -82,7 +113,27 @@ func buildXEPG(background bool) {
|
||||
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
go cachingImages()
|
||||
|
||||
if Settings.CacheImages == true && System.ImageCachingInProgress == 0 {
|
||||
|
||||
go func() {
|
||||
|
||||
System.ImageCachingInProgress = 1
|
||||
showInfo(fmt.Sprintf("Image Caching:Images are cached (%d)", len(Data.Cache.Images.Queue)))
|
||||
|
||||
Data.Cache.Images.Image.Caching()
|
||||
Data.Cache.Images.Image.Remove()
|
||||
showInfo("Image Caching:Done")
|
||||
|
||||
createXMLTVFile()
|
||||
createM3UFile()
|
||||
|
||||
System.ImageCachingInProgress = 0
|
||||
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
||||
|
||||
System.ScanInProgress = 0
|
||||
@@ -232,7 +283,7 @@ func createXEPGMapping() {
|
||||
|
||||
// Auswahl für den Dummy erstellen
|
||||
var dummy = make(map[string]interface{})
|
||||
var times = []string{"30", "60", "90", "120"}
|
||||
var times = []string{"30", "60", "90", "120", "180", "240", "360"}
|
||||
|
||||
for _, i := range times {
|
||||
|
||||
@@ -253,8 +304,9 @@ func createXEPGMapping() {
|
||||
// XEPG Datenbank erstellen / aktualisieren
|
||||
func createXEPGDatabase() (err error) {
|
||||
|
||||
var allChannelNumbers []float64
|
||||
Data.Cache.Streams.Active = []string{}
|
||||
var allChannelNumbers = make([]float64, 0, System.UnfilteredChannelLimit)
|
||||
Data.Cache.Streams.Active = make([]string, 0, System.UnfilteredChannelLimit)
|
||||
Data.XEPG.Channels = make(map[string]interface{}, System.UnfilteredChannelLimit)
|
||||
|
||||
Data.XEPG.Channels, err = loadJSONFileToMap(System.File.XEPG)
|
||||
if err != nil {
|
||||
@@ -279,24 +331,33 @@ func createXEPGDatabase() (err error) {
|
||||
|
||||
var getFreeChannelNumber = func() (xChannelID string) {
|
||||
|
||||
sort.Float64s(allChannelNumbers)
|
||||
|
||||
var firstFreeNumber float64 = Settings.MappingFirstChannel
|
||||
|
||||
newNumber:
|
||||
for {
|
||||
|
||||
if indexOfFloat64(firstFreeNumber, allChannelNumbers) == -1 {
|
||||
xChannelID = fmt.Sprintf("%g", firstFreeNumber)
|
||||
allChannelNumbers = append(allChannelNumbers, firstFreeNumber)
|
||||
return
|
||||
}
|
||||
|
||||
if indexOfFloat64(firstFreeNumber, allChannelNumbers) == -1 {
|
||||
xChannelID = fmt.Sprintf("%g", firstFreeNumber)
|
||||
allChannelNumbers = append(allChannelNumbers, firstFreeNumber)
|
||||
} else {
|
||||
firstFreeNumber++
|
||||
goto newNumber
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var generateHashForChannel = func(m3uID string, groupTitle string, tvgID string, tvgName string, uuidKey string, uuidValue string) string {
|
||||
hash := md5.Sum([]byte(m3uID + groupTitle + tvgID + tvgName + uuidKey + uuidValue))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
showInfo("XEPG:" + "Update database")
|
||||
|
||||
// Kanal mit fehlenden Kanalnummern löschen
|
||||
// Kanal mit fehlenden Kanalnummern löschen. Delete channel with missing channel numbers
|
||||
for id, dxc := range Data.XEPG.Channels {
|
||||
|
||||
var xepgChannel XEPGChannelStruct
|
||||
@@ -315,17 +376,24 @@ func createXEPGDatabase() (err error) {
|
||||
|
||||
}
|
||||
|
||||
var xepgChannels = make(map[string]interface{})
|
||||
|
||||
for k, v := range Data.XEPG.Channels {
|
||||
xepgChannels[k] = v
|
||||
// Make a map of the db channels based on their previously downloaded attributes -- filename, group, title, etc
|
||||
var xepgChannelsValuesMap = make(map[string]XEPGChannelStruct, System.UnfilteredChannelLimit)
|
||||
for _, v := range Data.XEPG.Channels {
|
||||
var channel XEPGChannelStruct
|
||||
err = json.Unmarshal([]byte(mapToJSON(v)), &channel)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
channelHash := generateHashForChannel(channel.FileM3UID, channel.GroupTitle, channel.TvgID, channel.TvgName, channel.UUIDKey, channel.UUIDValue)
|
||||
xepgChannelsValuesMap[channelHash] = channel
|
||||
}
|
||||
|
||||
for _, dsa := range Data.Streams.Active {
|
||||
|
||||
var channelExists = false // Entscheidet ob ein Kanal neu zu Datenbank hinzugefügt werden soll.
|
||||
var channelHasUUID = false // Überprüft, ob der Kanal (Stream) eindeutige ID's besitzt
|
||||
var currentXEPGID string // Aktuelle Datenbank ID (XEPG). Wird verwendet, um den Kanal in der Datenbank mit dem Stream der M3u zu aktualisieren
|
||||
var channelExists = false // Entscheidet ob ein Kanal neu zu Datenbank hinzugefügt werden soll. Decides whether a channel should be added to the database
|
||||
var channelHasUUID = false // Überprüft, ob der Kanal (Stream) eindeutige ID's besitzt. Checks whether the channel (stream) has unique IDs
|
||||
var currentXEPGID string // Aktuelle Datenbank ID (XEPG). Wird verwendet, um den Kanal in der Datenbank mit dem Stream der M3u zu aktualisieren. Current database ID (XEPG) Used to update the channel in the database with the stream of the M3u
|
||||
|
||||
var m3uChannel M3UChannelStructXEPG
|
||||
|
||||
err = json.Unmarshal([]byte(mapToJSON(dsa)), &m3uChannel)
|
||||
@@ -333,44 +401,53 @@ func createXEPGDatabase() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
Data.Cache.Streams.Active = append(Data.Cache.Streams.Active, m3uChannel.Name)
|
||||
Data.Cache.Streams.Active = append(Data.Cache.Streams.Active, m3uChannel.Name+m3uChannel.FileM3UID)
|
||||
|
||||
// XEPG Datenbank durchlaufen um nach dem Kanal zu suchen.
|
||||
for xepg, dxc := range xepgChannels {
|
||||
|
||||
var xepgChannel XEPGChannelStruct
|
||||
err = json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
||||
if err != nil {
|
||||
return
|
||||
// Try to find the channel based on matching all known values. If that fails, then move to full channel scan
|
||||
m3uChannelHash := generateHashForChannel(m3uChannel.FileM3UID, m3uChannel.GroupTitle, m3uChannel.TvgID, m3uChannel.TvgName, m3uChannel.UUIDKey, m3uChannel.UUIDValue)
|
||||
if val, ok := xepgChannelsValuesMap[m3uChannelHash]; ok {
|
||||
channelExists = true
|
||||
currentXEPGID = val.XEPG
|
||||
if len(m3uChannel.UUIDValue) > 0 {
|
||||
channelHasUUID = true
|
||||
}
|
||||
} else {
|
||||
|
||||
// Vergleichen des Streams anhand einer UUID in der M3U mit dem Kanal in der Databank
|
||||
if len(xepgChannel.UUIDValue) > 0 && len(m3uChannel.UUIDValue) > 0 {
|
||||
// XEPG Datenbank durchlaufen um nach dem Kanal zu suchen. Run through the XEPG database to search for the channel (full scan)
|
||||
for _, dxc := range xepgChannelsValuesMap {
|
||||
|
||||
if xepgChannel.UUIDValue == m3uChannel.UUIDValue && xepgChannel.UUIDKey == m3uChannel.UUIDKey {
|
||||
if m3uChannel.FileM3UID == dxc.FileM3UID {
|
||||
|
||||
channelExists = true
|
||||
channelHasUUID = true
|
||||
currentXEPGID = xepg
|
||||
break
|
||||
dxc.FileM3UID = m3uChannel.FileM3UID
|
||||
dxc.FileM3UName = m3uChannel.FileM3UName
|
||||
|
||||
// Vergleichen des Streams anhand einer UUID in der M3U mit dem Kanal in der Databank. Compare the stream using a UUID in the M3U with the channel in the database
|
||||
if len(dxc.UUIDValue) > 0 && len(m3uChannel.UUIDValue) > 0 {
|
||||
|
||||
if dxc.UUIDValue == m3uChannel.UUIDValue && dxc.UUIDKey == m3uChannel.UUIDKey {
|
||||
|
||||
channelExists = true
|
||||
channelHasUUID = true
|
||||
currentXEPGID = dxc.XEPG
|
||||
break
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
// Vergleichen des Streams mit dem Kanal in der Databank anhand des Kanalnamens. Compare the stream to the channel in the database using the channel name
|
||||
if dxc.Name == m3uChannel.Name {
|
||||
channelExists = true
|
||||
currentXEPGID = dxc.XEPG
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
// Vergleichen des Streams mit dem Kanal in der Databank anhand des Kanalnamens
|
||||
//fmt.Println(xepgChannel.Name, xepgChannel.UUIDKey, xepgChannel.UUIDValue)
|
||||
if xepgChannel.Name == m3uChannel.Name {
|
||||
channelExists = true
|
||||
currentXEPGID = xepg
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//os.Exit(0)
|
||||
|
||||
switch channelExists {
|
||||
|
||||
case true:
|
||||
@@ -435,7 +512,7 @@ func createXEPGDatabase() (err error) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
showInfo("XEPG:" + "Save DB file")
|
||||
err = saveMapToJSONFile(System.File.XEPG, Data.XEPG.Channels)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -568,6 +645,10 @@ func mapping() (err error) {
|
||||
// XMLTV Datei erstellen
|
||||
func createXMLTVFile() (err error) {
|
||||
|
||||
// Image Cache
|
||||
// 4edd81ab7c368208cc6448b615051b37.jpg
|
||||
var imgc = Data.Cache.Images
|
||||
|
||||
Data.Cache.ImagesFiles = []string{}
|
||||
Data.Cache.ImagesURLS = []string{}
|
||||
Data.Cache.ImagesCache = []string{}
|
||||
@@ -615,7 +696,7 @@ func createXMLTVFile() (err error) {
|
||||
// Kanäle
|
||||
var channel Channel
|
||||
channel.ID = xepgChannel.XChannelID
|
||||
channel.Icon = Icon{Src: getCacheImageURL(xepgChannel.TvgLogo)}
|
||||
channel.Icon = Icon{Src: imgc.Image.GetURL(xepgChannel.TvgLogo)}
|
||||
channel.DisplayName = append(channel.DisplayName, DisplayName{Value: xepgChannel.XName})
|
||||
|
||||
xepgXML.Channel = append(xepgXML.Channel, &channel)
|
||||
@@ -641,9 +722,10 @@ func createXMLTVFile() (err error) {
|
||||
var xmlOutput = []byte(xml.Header + string(content))
|
||||
writeByteToFile(System.File.XML, xmlOutput)
|
||||
|
||||
xepgXML = XMLTV{}
|
||||
showInfo("XEPG:" + fmt.Sprintf("Compress XMLTV file (%s)", System.Compressed.GZxml))
|
||||
err = compressGZIP(&xmlOutput, System.Compressed.GZxml)
|
||||
|
||||
//saveMapToJSONFile(System.File.Images, Data.Cache.ImageCache)
|
||||
xepgXML = XMLTV{}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -690,6 +772,15 @@ func getProgramData(xepgChannel XEPGChannelStruct) (xepgXML XMLTV, err error) {
|
||||
// Category (Kategorie)
|
||||
getCategory(program, xmltvProgram, xepgChannel)
|
||||
|
||||
// Credits : (Credits)
|
||||
program.Credits = xmltvProgram.Credits
|
||||
|
||||
// Rating (Bewertung)
|
||||
program.Rating = xmltvProgram.Rating
|
||||
|
||||
// StarRating (Bewertung / Kritiken)
|
||||
program.StarRating = xmltvProgram.StarRating
|
||||
|
||||
// Country (Länder)
|
||||
program.Country = xmltvProgram.Country
|
||||
|
||||
@@ -732,6 +823,7 @@ func getProgramData(xepgChannel XEPGChannelStruct) (xepgXML XMLTV, err error) {
|
||||
// Dummy Daten erstellen (createXMLTVFile)
|
||||
func createDummyProgram(xepgChannel XEPGChannelStruct) (dummyXMLTV XMLTV) {
|
||||
|
||||
var imgc = Data.Cache.Images
|
||||
var currentTime = time.Now()
|
||||
var dateArray = strings.Fields(currentTime.String())
|
||||
var offset = " " + dateArray[2]
|
||||
@@ -770,7 +862,7 @@ func createDummyProgram(xepgChannel XEPGChannelStruct) (dummyXMLTV XMLTV) {
|
||||
}
|
||||
|
||||
if Settings.XepgReplaceMissingImages == true {
|
||||
poster.Src = getCacheImageURL(xepgChannel.TvgLogo)
|
||||
poster.Src = imgc.Image.GetURL(xepgChannel.TvgLogo)
|
||||
epg.Poster = append(epg.Poster, poster)
|
||||
}
|
||||
|
||||
@@ -817,8 +909,10 @@ func getCategory(program *Program, xmltvProgram *Program, xepgChannel XEPGChanne
|
||||
// Programm Poster Cover aus der XMLTV Datei laden
|
||||
func getPoster(program *Program, xmltvProgram *Program, xepgChannel XEPGChannelStruct) {
|
||||
|
||||
var imgc = Data.Cache.Images
|
||||
|
||||
for _, poster := range xmltvProgram.Poster {
|
||||
poster.Src = getCacheImageURL(poster.Src)
|
||||
poster.Src = imgc.Image.GetURL(poster.Src)
|
||||
program.Poster = append(program.Poster, poster)
|
||||
}
|
||||
|
||||
@@ -826,7 +920,7 @@ func getPoster(program *Program, xmltvProgram *Program, xepgChannel XEPGChannelS
|
||||
|
||||
if len(xmltvProgram.Poster) == 0 {
|
||||
var poster Poster
|
||||
poster.Src = getCacheImageURL(xepgChannel.TvgLogo)
|
||||
poster.Src = imgc.Image.GetURL(poster.Src)
|
||||
program.Poster = append(program.Poster, poster)
|
||||
}
|
||||
|
||||
@@ -937,6 +1031,18 @@ func createM3UFile() {
|
||||
// XEPG Datenbank bereinigen
|
||||
func cleanupXEPG() {
|
||||
|
||||
//fmt.Println(Settings.Files.M3U)
|
||||
|
||||
var sourceIDs []string
|
||||
|
||||
for source := range Settings.Files.M3U {
|
||||
sourceIDs = append(sourceIDs, source)
|
||||
}
|
||||
|
||||
for source := range Settings.Files.HDHR {
|
||||
sourceIDs = append(sourceIDs, source)
|
||||
}
|
||||
|
||||
showInfo("XEPG:" + fmt.Sprintf("Cleanup database"))
|
||||
Data.XEPG.XEPGCount = 0
|
||||
|
||||
@@ -946,7 +1052,7 @@ func cleanupXEPG() {
|
||||
err := json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
||||
if err == nil {
|
||||
|
||||
if indexOfString(xepgChannel.Name, Data.Cache.Streams.Active) == -1 {
|
||||
if indexOfString(xepgChannel.Name+xepgChannel.FileM3UID, Data.Cache.Streams.Active) == -1 {
|
||||
delete(Data.XEPG.Channels, id)
|
||||
} else {
|
||||
if xepgChannel.XActive == true {
|
||||
@@ -954,6 +1060,10 @@ func cleanupXEPG() {
|
||||
}
|
||||
}
|
||||
|
||||
if indexOfString(xepgChannel.FileM3UID, sourceIDs) == -1 {
|
||||
delete(Data.XEPG.Channels, id)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,9 +21,8 @@ menuItems.push(new MainMenuItem("logout", "{{.mainMenu.item.logout}}", "logout.p
|
||||
|
||||
// Kategorien für die Einstellungen
|
||||
var settingsCategory = new Array()
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.general}}", "xteveAutoUpdate,tuner,epgSource,api,scheme.m3u,scheme.xml"))
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.files}}", "update,files.update,temp.path,cache.images,xepg.replace.missing.images"))
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.streaming}}", "buffer,buffer.size.kb,buffer.timeout,user.agent,ffmpeg.path,ffmpeg.options,vlc.path,vlc.options"))
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.general}}", "xteveAutoUpdate,tuner,epgSource,api"));settingsCategory.push(new SettingsCategoryItem("{{.settings.category.files}}", "update,files.update,temp.path,cache.images,xepg.replace.missing.images"))
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.streaming}}", "buffer,udpxy,buffer.size.kb,buffer.timeout,user.agent,ffmpeg.path,ffmpeg.options,vlc.path,vlc.options"))
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.backup}}", "backup.path,backup.keep"))
|
||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.authentication}}", "authentication.web,authentication.pms,authentication.m3u,authentication.xml,authentication.api"))
|
||||
|
||||
@@ -310,7 +309,7 @@ function createSearchObj() {
|
||||
var data = SERVER["xepg"]["epgMapping"]
|
||||
var channels = getObjKeys(data)
|
||||
|
||||
var channelKeys:string[] = ["x-active", "x-channelID", "x-name", "_file.m3u.name", "x-group-title"]
|
||||
var channelKeys:string[] = ["x-active", "x-channelID", "x-name", "_file.m3u.name", "x-group-title", "x-xmltv-file"]
|
||||
|
||||
channels.forEach(id => {
|
||||
|
||||
@@ -331,7 +330,17 @@ function createSearchObj() {
|
||||
|
||||
} else {
|
||||
|
||||
SEARCH_MAPPING[id] = SEARCH_MAPPING[id] + data[id][key] + " "
|
||||
if (key == "x-xmltv-file") {
|
||||
var xmltvFile = getValueFromProviderFile(data[id][key], "xmltv", "name")
|
||||
|
||||
if (xmltvFile != undefined) {
|
||||
SEARCH_MAPPING[id] = SEARCH_MAPPING[id] + xmltvFile + " "
|
||||
}
|
||||
|
||||
} else {
|
||||
SEARCH_MAPPING[id] = SEARCH_MAPPING[id] + data[id][key] + " "
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1568,7 +1568,7 @@ function openPopUp(dataType, element) {
|
||||
// Erweitern der EPG Kategorie
|
||||
var dbKey:string = "x-category"
|
||||
var text:string[] = ["-", "Kids (Emby only)", "News", "Movie", "Series", "Sports"]
|
||||
var values:string[] = ["-", "Kids", "News", "Movie", "Series", "Sports"]
|
||||
var values:string[] = ["", "Kids", "News", "Movie", "Series", "Sports"]
|
||||
var select = content.createSelect(text, values, data[dbKey], dbKey)
|
||||
select.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||
content.appendRow("{{.mapping.epgCategory.title}}", select)
|
||||
|
||||
@@ -372,33 +372,16 @@ class SettingsCategory {
|
||||
setting.appendChild(tdRight)
|
||||
break
|
||||
|
||||
case "scheme.m3u":
|
||||
var tdLeft = document.createElement("TD")
|
||||
tdLeft.innerHTML = "{{.settings.schemeM3U.title}}" + ":"
|
||||
case "udpxy":
|
||||
|
||||
var tdLeft = document.createElement("TD");
|
||||
tdLeft.innerHTML = "{{.settings.udpxy.title}}" + ":"
|
||||
|
||||
var tdRight = document.createElement("TD")
|
||||
var text:any[] = ["HTTP", "HTTPS"]
|
||||
var values:any[] = ["HTTP", "HTTPS"]
|
||||
|
||||
var select = content.createSelect(text, values, data, settingsKey)
|
||||
select.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||
tdRight.appendChild(select)
|
||||
|
||||
setting.appendChild(tdLeft)
|
||||
setting.appendChild(tdRight)
|
||||
break
|
||||
|
||||
case "scheme.xml":
|
||||
var tdLeft = document.createElement("TD")
|
||||
tdLeft.innerHTML = "{{.settings.schemeXML.title}}" + ":"
|
||||
|
||||
var tdRight = document.createElement("TD")
|
||||
var text:any[] = ["HTTP", "HTTPS"]
|
||||
var values:any[] = ["HTTP", "HTTPS"]
|
||||
|
||||
var select = content.createSelect(text, values, data, settingsKey)
|
||||
select.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||
tdRight.appendChild(select)
|
||||
var input = content.createInput("text", "udpxy", data)
|
||||
input.setAttribute("placeholder", "{{.settings.udpxy.placeholder}}")
|
||||
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||
tdRight.appendChild(input)
|
||||
|
||||
setting.appendChild(tdLeft)
|
||||
setting.appendChild(tdRight)
|
||||
@@ -515,15 +498,10 @@ class SettingsCategory {
|
||||
text = "{{.settings.replaceEmptyImages.description}}"
|
||||
break
|
||||
|
||||
case "scheme.m3u":
|
||||
text = "{{.settings.schemeM3U.description}}"
|
||||
case "udpxy":
|
||||
text = "{{.settings.udpxy.description}}"
|
||||
break
|
||||
|
||||
case "scheme.xml":
|
||||
text = "{{.settings.schemeXML.description}}"
|
||||
break
|
||||
|
||||
|
||||
default:
|
||||
text = ""
|
||||
break
|
||||
|
||||
14
xteve.go
14
xteve.go
@@ -13,7 +13,7 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"./src"
|
||||
"xteve/src"
|
||||
)
|
||||
|
||||
// GitHubStruct : GitHub Account. Über diesen Account werden die Updates veröffentlicht
|
||||
@@ -39,7 +39,7 @@ var GitHub = GitHubStruct{Branch: "master", User: "xteve-project", Repo: "xTeVe-
|
||||
const Name = "xTeVe"
|
||||
|
||||
// Version : Version, die Build Nummer wird in der main func geparst.
|
||||
const Version = "2.1.1.0111"
|
||||
const Version = "2.2.0.0200"
|
||||
|
||||
// DBVersion : Datanbank Version
|
||||
const DBVersion = "2.1.0"
|
||||
@@ -47,9 +47,6 @@ const DBVersion = "2.1.0"
|
||||
// APIVersion : API Version
|
||||
const APIVersion = "1.1.0"
|
||||
|
||||
// Dev : Aktiviert den Entwicklungsmodus. Für den Webserver werden dann die lokalen Dateien verwendet.
|
||||
const Dev = false
|
||||
|
||||
var homeDirectory = fmt.Sprintf("%s%s.%s%s", src.GetUserHomeDirectory(), string(os.PathSeparator), strings.ToLower(Name), string(os.PathSeparator))
|
||||
var samplePath = fmt.Sprintf("%spath%sto%sxteve%s", string(os.PathSeparator), string(os.PathSeparator), string(os.PathSeparator), string(os.PathSeparator))
|
||||
var sampleRestore = fmt.Sprintf("%spath%sto%sfile%s", string(os.PathSeparator), string(os.PathSeparator), string(os.PathSeparator), string(os.PathSeparator))
|
||||
@@ -63,6 +60,9 @@ var debug = flag.Int("debug", 0, ": Debug level [0 - 3] (default: 0)")
|
||||
var info = flag.Bool("info", false, ": Show system info")
|
||||
var h = flag.Bool("h", false, ": Show help")
|
||||
|
||||
// Aktiviert den Entwicklungsmodus. Für den Webserver werden dann die lokalen Dateien verwendet.
|
||||
var dev = flag.Bool("dev", false, ": Activates the developer mode, the source code must be available. The local files for the web interface are used.")
|
||||
|
||||
func main() {
|
||||
|
||||
// Build-Nummer von der Versionsnummer trennen
|
||||
@@ -73,7 +73,6 @@ func main() {
|
||||
system.Branch = GitHub.Branch
|
||||
system.Build = build[len(build)-1:][0]
|
||||
system.DBVersion = DBVersion
|
||||
system.Dev = Dev
|
||||
system.GitHub = GitHub
|
||||
system.Name = Name
|
||||
system.Version = strings.Join(build[0:len(build)-1], ".")
|
||||
@@ -122,6 +121,8 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
system.Dev = *dev
|
||||
|
||||
// Systeminformationen anzeigen
|
||||
if *info {
|
||||
|
||||
@@ -189,7 +190,6 @@ func main() {
|
||||
err = src.BinaryUpdate()
|
||||
if err != nil {
|
||||
src.ShowError(err, 0)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
err = src.StartSystem(false)
|
||||
|
||||
Reference in New Issue
Block a user