91 Commits

Author SHA1 Message Date
xteve-project
0e999b85b9 Update readme.md 2021-03-29 20:26:19 +02:00
xteve-project
e4b3667fe0 v2.2.0.0200 2021-03-29 19:55:06 +02:00
xteve-project
e44eff0645 Add go.mod and go.sum. Require go v1.16 2021-03-29 19:47:56 +02:00
xteve-project
884b427754 v2.1.3.0131 2021-01-21 20:02:12 +01:00
xteve-project
25bad13800 compile js 2021-01-21 19:50:35 +01:00
xteve-project
d2456b0506 Merge branch 'fix/209/unset-category' 2021-01-21 19:38:45 +01:00
beardypig
19b9a259b1 Correctly unset the category for a channel in XEPG
Stores `""` to `x-category` when the category is unset, previosuly `"-"` was stored which would append a `<category>` to each `<programme>` for the channel.

fixes #209
2021-01-21 11:59:50 +01:00
xteve-project
441cefa3a4 v2.1.3.0130 2020-11-22 17:17:20 +01:00
xteve-project
014f5b7218 Dummy new times
Fixed searching for XMLTV file (Mapping)
2020-11-20 22:13:52 +01:00
xteve-project
0310b7e738 v2.1.2.0129 2020-10-17 14:08:25 +02:00
xteve-project
6d890cfd33 Fixed: undefined playlist 2020-10-16 16:43:56 +02:00
marmei
410cc3648f v2.1.2.0128 2020-10-09 13:00:28 +02:00
marmei
07f9cac3c5 gitignore 2020-10-03 11:57:40 +02:00
marmei
f43ce0f7c5 Bug fix: Image caching (#172) 2020-10-03 11:56:50 +02:00
marmei
db59f7ef37 Add XML tag: rating, credits 2020-09-26 18:35:16 +02:00
marmei
b407ec5bf0 Exit disabled when GitHub is unreachable 2020-07-13 16:19:28 +02:00
marmei
d8fc1aea97 Schedule can be deactivated 2020-06-03 23:11:25 +02:00
marmei
38333d65cb Add content type (BUffer): video/x-matroska 2020-05-16 09:21:34 +02:00
marmei
a6a9b90937 Merge branch 'pr/136' into beta 2020-05-16 09:19:04 +02:00
marmei
2b3fe6a09d v2.1.2.0122 2020-05-15 19:43:07 +02:00
Raf
a09eca59a7 cleanup channel hash map logic 2020-05-15 11:53:42 -04:00
marmei
0df3b4d755 Merge branch 'pr/136' into beta 2020-05-15 00:20:21 +02:00
marmei
5552514a1f Merge branch 'pr/134' into beta 2020-05-14 23:44:30 +02:00
Raf
5fceb1d34f update change log to match upstream beta 2020-05-14 10:15:42 -04:00
Raf
fb5d0a3904 freenumbergen-start at settings.firstchannel
if free
2020-05-14 09:39:28 -04:00
Raf
28fe4dcf1c Speedup db update for large files 2020-05-14 09:39:28 -04:00
marmei
bc307a7cd4 2.1.2.0121 2020-05-14 09:39:28 -04:00
marmei
fd3024d7ff v2.1.2.0120 2020-05-14 09:39:28 -04:00
marmei
d3f6725ba6 Update changelog-beta.md 2020-01-24 2020-05-14 09:38:30 -04:00
marmei
c266012db3 Update changelog-beta.md 2020-01-13 2020-05-14 09:38:30 -04:00
5Ub-Z3r0
d602b60710 Merge branch 'master' into beta 2020-05-13 21:34:27 +02:00
5Ub-Z3r0
67b7ba6df9 Add support for proxying multicast streams through a UDPxy server.
This commit adds support for proxying multicast streams through a UDPxy
server. If the stream is multicast, and a udpxy server is set in the
configuration, then the channel URL is rewritten to use the UDPxy
service configured.

The stream URL rewriting is done regardless of the buffer option set.

Signed-off-by: 5Ub-Z3r0 <1673590+5Ub-Z3r0@users.noreply.github.com>
2020-05-13 21:26:24 +02:00
marmei
71dfe91272 New flag -dev: Activates developer mode 2020-05-13 10:25:50 +02:00
marmei
534510a4ec Websocket error 2020-03-02 17:03:56 +01:00
marmei
2d10fc9313 Merge branch 'beta' 2020-02-19 19:39:05 +01:00
marmei
a63a9c0d8f 2.1.2.0121 2020-02-19 19:38:56 +01:00
marmei
dd911d6e5d M3U error message changed 2020-02-19 19:35:41 +01:00
marmei
c1970a8393 v2.1.2.0120 2020-02-15 09:01:25 +01:00
marmei
493d612d52 Merge branch 'beta' 2020-02-14 20:02:28 +01:00
marmei
4fc4330a94 FFmpeg changed: user-agent to user_agent 2020-02-08 11:10:14 +01:00
marmei
a683533824 Update changelog-beta.md 2020-01-24 2020-01-24 19:58:00 +01:00
marmei
4b9f5826cf If no user agent is specified, the default FFmpeg or VLC user agent is used. 2020-01-24 19:48:29 +01:00
marmei
ca49d70910 Update changelog-beta.md 2020-01-13 2020-01-13 23:17:55 +01:00
marmei
1b425018d4 v2.1.1.0115-beta 2020-01-13 23:04:34 +01:00
marmei
87b36c283b URL format removed from the settings 2020-01-04 17:17:28 +01:00
marmei
6da26ff4fb Merge branch 'pr/76' into beta 2020-01-04 17:01:32 +01:00
marmei
cd08985e79 Set global domain for /web 2020-01-04 17:00:58 +01:00
Thomas Dall'Agnese
dc04519229 Use relative path when posting HTML forms 2019-12-29 20:03:37 +01:00
Thomas Dall'Agnese
c4ad96b715 Use relative path when posting HTML forms 2019-12-28 18:55:46 +01:00
Thomas Dall'Agnese
45b5e602bb Revert "Use relative path when posting HTML forms"
This reverts commit d5328f6b1a.
2019-12-28 18:43:48 +01:00
marmei
1cefbf022d Workaround for IPTVX content-type bug 2019-12-28 12:28:33 +01:00
Thomas Dall'Agnese
d5328f6b1a Use relative path when posting HTML forms 2019-12-28 10:23:54 +01:00
marmei
91b80bc8bb Add xteve.xml GZIP 2019-12-16 20:23:05 +01:00
marmei
aa763726a3 Fixed broken merge 2019-12-14 10:17:42 +01:00
marmei
66c01dd1fb Missing placeholder 2019-12-13 21:05:14 +01:00
marmei
03e1abbe90 v2.1.1.0110 2019-12-13 20:31:23 +01:00
marmei
469581e280 Add mapping desc. function 2019-12-13 19:01:18 +01:00
marmei
019c98996a Update changelog-beta.md 2019-12-08 2019-12-08 17:49:50 +01:00
marmei
36db927794 v2.1.0.0106 2019-12-08 17:47:54 +01:00
marmei
f0a49788cc User-Agent for FFmpeg and VLC 2019-12-08 17:33:46 +01:00
marmei
72767d7dbd Update changelog-beta.md 2019-12-06 2019-12-06 21:00:44 +01:00
marmei
8eecbf2b78 Update changelog-beta.md 2019-12-06 2019-12-06 20:57:26 +01:00
marmei
1a1e37fe15 v2.1.0.0105: Settings for URI scheme 2019-12-06 20:48:59 +01:00
marmei
08f6fb60e3 Fix log entry, wrong buffer value 2019-11-15 10:09:19 +01:00
marmei
eded490ac7 Update changelog-beta.md 2019-11-06 2019-11-06 18:16:14 +01:00
marmei
ed770b9dbc Merge remote-tracking branch 'origin/master' 2019-11-06 18:11:21 +01:00
marmei
6129b4911a Merge pull request #53 from taeram/patch-1
Small spelling / grammar fixes.
2019-11-05 20:29:12 +01:00
Jesse Patching
477c5f30c1 Small spelling / grammar fixes. 2019-11-04 20:28:45 -07:00
marmei
65ddc6f301 v2.1.2.0101-beta 2019-11-04 18:28:41 +01:00
marmei
3ef95c1950 RV Proxy 2019-11-04 18:23:20 +01:00
marmei
3a3798cd2d WiteHeader 2019-10-23 19:02:54 +02:00
marmei
72eb7fb599 v2.1.0.0100 2019-10-11 17:59:11 +02:00
marmei
4b969b8cee Merge branch 'beta' 2019-10-11 17:54:06 +02:00
marmei
3d9266dabe XEPG performance 2019-10-11 17:53:20 +02:00
marmei
48218cda50 Update changelog-beta.md 2019-10-04 2019-10-04 19:52:44 +02:00
marmei
dc42afcd05 New beta 2019-10-04 19:39:54 +02:00
marmei
20e5e1b545 Buffer RTSP performance 2019-10-04 19:39:20 +02:00
Aaron Chi
11dd830110 fix #34 2019-10-04 17:08:16 +08:00
marmei
7c87d1d5bd Wrong CVLC path 2019-09-27 22:20:19 +02:00
marmei
6cdd44357b update changelog-beta.md 2019-09-27 19:55:36 +02:00
marmei
ad992eb615 Add FFmpeg and VLC support 2019-09-27 19:24:31 +02:00
marmei
11453c6053 v2.0.3.0030 2019-09-07 12:51:21 +02:00
marmei
81e8ae33d7 Merge branch 'Bugs_in_2.0.2' 2019-09-07 12:07:55 +02:00
marmei
f3be0fca47 Wrong warning (root) 2019-09-05 19:37:55 +02:00
marmei
1c1c89cd74 Update changelog-beta.md 2019-09-04 2019-09-04 21:16:04 +02:00
marmei
3d73dba422 Code removed #1 2019-09-04 20:16:11 +02:00
marmei
c6e74fe11c Interception of repeated save 2019-09-04 20:12:26 +02:00
marmei
18dba46c02 Bug #16 1 2019-09-01 00:24:06 +02:00
marmei
efa55b39a9 Bug #16 0 2019-08-31 20:39:39 +02:00
marmei
717fa68b7e Add an error message if the filter rule is empty.
Set the streaming status to active only once.
2019-08-28 17:56:15 +02:00
marmei
878531ff79 IPTV removed as a source 2019-08-28 16:49:41 +02:00
41 changed files with 2448 additions and 954 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
.DS_Store
demo
dev
compiler
files
update_xteve*.sh

View File

@@ -31,7 +31,7 @@ Documentation for setup and configuration is [here](https://github.com/xteve-pro
* Merge external M3U files
* Merge external XMLTV files
* Automatic M3U and XMLTV update
* M3U und XMLTV export
* M3U and XMLTV export
#### Channel management
* Filtering streams
@@ -84,7 +84,7 @@ Including:
---
### xTeVe Beta branch
New features and bug fixes are only available in beta brunch. Only after successful testing, they are merged into the master branch.
New features and bug fixes are only available in beta branch. Only after successful testing are they are merged into the master branch.
**It is not recommended to use the beta version in a production system.**
@@ -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)

View File

@@ -1,3 +1,55 @@
#### 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.
```
#### 2.1.0.0105-beta
```diff
+ Fixed wrong buffer value in log
+ New setting: URL protocol for M3U and XML file
+ Add xml tag premiere to xteve.xml
```
#### 2.1.0.0101-beta
```diff
+ Reverse proxy fix
```
#### 2.0.3.0042-beta
**Version 2.0.3.0042 changes the settings.json.**
Settings from the current beta can not be used for the current master version 2.0.3
- New default options for VLC and FFmpeg
- VLC and FFmpeg log entries in the xTeVe log
- Less CPU load with VLC and FFmpeg
#### 2.0.3.0035-beta
```diff
+ FFmpeg support
+ VLC support
```
**Version 2.0.3.0035 changes the settings.json.**
Settings from the current beta can not be used for the current master version 2.0.3
#### 2.0.2.0024-beta
```diff
+ Improved monitoring of the buffer process
+ Update the XEPG database a bit faster
```
##### Fixes
- Error message if filter rule is missing
- Channels are lost when saving again (Mapping)
- Plex log, invalid source: IPTV
#### 2.0.1.0012-beta
```diff
+ Add support for "video/m2ts" video streams (Pull request #14)

9
go.mod Normal file
View 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
View 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=

View File

@@ -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="">

View File

@@ -20,7 +20,7 @@ menuItems.push(new MainMenuItem("logout", "{{.mainMenu.item.logout}}", "logout.p
var settingsCategory = new Array();
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"));
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] + " ";
}
}
});
});

View File

@@ -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) {

View File

@@ -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) {
@@ -127,7 +127,7 @@ var Content = /** @class */ (function () {
var cell = new Cell();
cell.child = true;
cell.childType = "P";
if (SERVER["settings"]["buffer"] == true) {
if (SERVER["settings"]["buffer"] != "-") {
cell.value = data[key]["tuner"];
}
else {
@@ -901,7 +901,7 @@ function openPopUp(dataType, element) {
input.setAttribute("placeholder", "{{.playlist.fileM3U.placeholder}}");
content.appendRow("{{.playlist.fileM3U.title}}", input);
// Tuner
if (SERVER["settings"]["buffer"] == true) {
if (SERVER["settings"]["buffer"] != "-") {
var text = new Array();
var values = new Array();
for (var i = 1; i <= 100; i++) {
@@ -971,7 +971,7 @@ function openPopUp(dataType, element) {
input.setAttribute("placeholder", "{{.playlist.fileHDHR.placeholder}}");
content.appendRow("{{.playlist.fileHDHR.title}}", input);
// Tuner
if (SERVER["settings"]["buffer"] == true) {
if (SERVER["settings"]["buffer"] != "-") {
var text = new Array();
var values = new Array();
for (var i = 1; i <= 100; i++) {
@@ -1253,6 +1253,12 @@ function openPopUp(dataType, element) {
}
content.appendRow("{{.mapping.channelName.title}}", input);
content.description(data["name"]);
// Beschreibung
var dbKey = "x-description";
var input = content.createInput("text", dbKey, data[dbKey]);
input.setAttribute("placeholder", "{{.mapping.description.placeholder}}");
input.setAttribute("onchange", "javascript: this.className = 'changed'");
content.appendRow("{{.mapping.description.title}}", input);
// Aktualisierung des Kanalnamens
if (data.hasOwnProperty("_uuid.key")) {
if (data["_uuid.key"] != "") {
@@ -1279,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);

View File

@@ -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) {
@@ -85,6 +85,50 @@ var SettingsCategory = /** @class */ (function () {
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "ffmpeg.path":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "{{.settings.ffmpegPath.title}}" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "ffmpeg.path", data);
input.setAttribute("placeholder", "{{.settings.ffmpegPath.placeholder}}");
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "ffmpeg.options":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "{{.settings.ffmpegOptions.title}}" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "ffmpeg.options", data);
input.setAttribute("placeholder", "{{.settings.ffmpegOptions.placeholder}}");
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "vlc.path":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "{{.settings.vlcPath.title}}" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "vlc.path", data);
input.setAttribute("placeholder", "{{.settings.vlcPath.placeholder}}");
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "vlc.options":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "{{.settings.vlcOptions.title}}" + ":";
var tdRight = document.createElement("TD");
var input = content.createInput("text", "vlc.options", data);
input.setAttribute("placeholder", "{{.settings.vlcOptions.placeholder}}");
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
// Checkboxen
case "authentication.web":
var tdLeft = document.createElement("TD");
@@ -185,17 +229,6 @@ var SettingsCategory = /** @class */ (function () {
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "buffer":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "{{.settings.streamBuffering.title}}" + ":";
var tdRight = document.createElement("TD");
var input = content.createCheckbox(settingsKey);
input.checked = data;
input.setAttribute("onchange", "javascript: this.className = 'changed'");
tdRight.appendChild(input);
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "api":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "{{.settings.api.title}}" + ":";
@@ -260,6 +293,29 @@ var SettingsCategory = /** @class */ (function () {
setting.appendChild(tdLeft);
setting.appendChild(tdRight);
break;
case "buffer":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "{{.settings.streamBuffering.title}}" + ":";
var tdRight = document.createElement("TD");
var text = ["{{.settings.streamBuffering.info_false}}", "xTeVe: ({{.settings.streamBuffering.info_xteve}})", "FFmpeg: ({{.settings.streamBuffering.info_ffmpeg}})", "VLC: ({{.settings.streamBuffering.info_vlc}})"];
var values = ["-", "xteve", "ffmpeg", "vlc"];
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 "udpxy":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "{{.settings.udpxy.title}}" + ":";
var tdRight = document.createElement("TD");
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;
}
return setting;
};
@@ -308,6 +364,18 @@ var SettingsCategory = /** @class */ (function () {
case "user.agent":
text = "{{.settings.userAgent.description}}";
break;
case "ffmpeg.path":
text = "{{.settings.ffmpegPath.description}}";
break;
case "ffmpeg.options":
text = "{{.settings.ffmpegOptions.description}}";
break;
case "vlc.path":
text = "{{.settings.vlcPath.description}}";
break;
case "vlc.options":
text = "{{.settings.vlcOptions.description}}";
break;
case "epgSource":
text = "{{.settings.epgSource.description}}";
break;
@@ -329,6 +397,9 @@ var SettingsCategory = /** @class */ (function () {
case "xepg.replace.missing.images":
text = "{{.settings.replaceEmptyImages.description}}";
break;
case "udpxy":
text = "{{.settings.udpxy.description}}";
break;
default:
text = "";
break;

View File

@@ -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,54 +212,71 @@
"xmltvFile": "XMLTV File",
"xmltvID": "XMLTV ID"
},
"active": {
"active":
{
"title": "Active",
"placeholder": "",
"description": ""
},
"channelName": {
"channelName":
{
"title": "Channel Name",
"placeholder": "",
"description": ""
},
"updateChannelName": {
"description":
{
"title": "Channel Description",
"placeholder": "Used by the Dummy as an XML description",
"description": ""
},
"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",
@@ -236,185 +285,262 @@
"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."
},
"filesUpdate": {
"filesUpdate":
{
"title": "Updates all files at startup",
"description": "Updates all playlists, tuner and XMLTV files at startup."
},
"cacheImages": {
"title": "Image caching",
"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": "- The stream is passed from xTeVe to Plex / Emby / M3U Player<br>- Small jerking of the streams can be compensated<br>- HLS / M3U8 support<br>- Re-streaming<br>- Separate tuner limit for each playlist"
"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)",
"info_xteve": "xTeVe connects to the streaming server",
"info_ffmpeg": "FFmpeg connects to the streaming server",
"info_vlc": "VLC connects to the streaming server"
},
"bufferSize": {
"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":
{
"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":
{
"title": "VLC / CVLC Binary Path",
"description": "Path to VLC / CVLC binary.",
"placeholder": "/path/to/cvlc"
},
"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":
{
"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": {
"title": "User agent",
"description": "User Agent for HTTP requests",
"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"
}

View File

@@ -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="">

View File

@@ -6,7 +6,7 @@ import (
"net/http"
"strings"
"../src/internal/authentication"
"xteve/src/internal/authentication"
)
func activatedSystemAuthentication() (err error) {

File diff suppressed because it is too large Load Diff

View File

@@ -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
}

View File

@@ -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
@@ -29,6 +29,9 @@ var BufferInformation sync.Map
// BufferClients : Anzahl der Clients die einen Stream über den Buffer abspielen
var BufferClients sync.Map
// Lock : Lock Map
var Lock = sync.RWMutex{}
// Init : Systeminitialisierung
func Init() (err error) {
@@ -43,9 +46,14 @@ 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
System.FFmpeg.DefaultOptions = "-hide_banner -loglevel error -i [URL] -c copy -f mpegts pipe:1"
System.VLC.DefaultOptions = "-I dummy [URL] --sout #std{mux=ts,access=file,dst=-}"
// Default Logeinträge, wird später von denen aus der settings.json überschrieben. Muss gemacht werden, damit die ersten Einträge auch im Log (webUI aangezeigt werden)
Settings.LogEntriesRAM = 500
@@ -91,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
@@ -109,7 +119,7 @@ func Init() (err error) {
// Überprüfen ob xTeVe als root läuft
if os.Geteuid() == 0 {
showWarning(2010)
showWarning(2110)
}
if System.Flag.Debug > 0 {
@@ -133,7 +143,6 @@ func Init() (err error) {
// Bedingte Update Änderungen durchführen
err = conditionalUpdateChanges()
if err != nil {
ShowError(err, 0)
return
}
@@ -221,7 +230,8 @@ func StartSystem(updateProviderFiles bool) (err error) {
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 {

View File

@@ -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))
@@ -24,6 +25,8 @@ func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err e
var createXEPGFiles = false
var debug string
// -vvv [URL] --sout '#transcode{vcodec=mp4v, acodec=mpga} :standard{access=http, mux=ogg}'
for key, value := range newSettings {
if _, ok := oldSettings[key]; ok {
@@ -38,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{}) {
@@ -54,6 +57,10 @@ func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err e
}
if len(newUpdateTimes) == 0 {
//newUpdateTimes = append(newUpdateTimes, "0000")
}
value = newUpdateTimes
case "cache.images":
@@ -94,6 +101,20 @@ func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err e
return
}
case "ffmpeg.path", "vlc.path":
var path = value.(string)
if len(path) > 0 {
err = checkFile(path)
if err != nil {
return
}
}
case "scheme.m3u", "scheme.xml":
createXEPGFiles = true
}
oldSettings[key] = value
@@ -138,6 +159,33 @@ func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err e
}
// Buffer Einstellungen überprüfen
if len(Settings.FFmpegOptions) == 0 {
Settings.FFmpegOptions = System.FFmpeg.DefaultOptions
}
if len(Settings.VLCOptions) == 0 {
Settings.VLCOptions = System.VLC.DefaultOptions
}
switch Settings.Buffer {
case "ffmpeg":
if len(Settings.FFmpegPath) == 0 {
err = errors.New(getErrMsg(2020))
return
}
case "vlc":
if len(Settings.VLCPath) == 0 {
err = errors.New(getErrMsg(2021))
return
}
}
err = saveSettings(Settings)
if err == nil {
@@ -156,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)
}()
}
}
@@ -361,11 +422,12 @@ 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{})
var defaultFilter FilterStruct
var newFilter = false
defaultFilter.Active = true
defaultFilter.CaseSensitive = false
@@ -389,6 +451,7 @@ func saveFilter(request RequestStruct) (settings SettingsStrcut, err error) {
if dataID == -1 {
// Neuer Filter
newFilter = true
dataID = createNewID()
filterMap[dataID] = jsonToMap(mapToJSON(defaultFilter))
@@ -397,15 +460,28 @@ func saveFilter(request RequestStruct) (settings SettingsStrcut, err error) {
// Filter aktualisieren / löschen
for key, value := range data.(map[string]interface{}) {
var oldData = filterMap[dataID].(map[string]interface{})
oldData[key] = value
// Filter löschen
if _, ok := data.(map[string]interface{})["delete"]; ok {
delete(filterMap, dataID)
break
}
if filter, ok := data.(map[string]interface{})["filter"].(string); ok {
if len(filter) == 0 {
err = errors.New(getErrMsg(1014))
if newFilter == true {
delete(filterMap, dataID)
}
return
}
}
if oldData, ok := filterMap[dataID].(map[string]interface{}); ok {
oldData[key] = value
}
}
@@ -434,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
@@ -446,8 +527,44 @@ func saveXEpgMapping(request RequestStruct) (err error) {
Data.XEPG.Channels = request.EpgMapping
cleanupXEPG()
buildXEPG(true)
if System.ScanInProgress == 0 {
System.ScanInProgress = 1
cleanupXEPG()
System.ScanInProgress = 0
buildXEPG(true)
} else {
// Wenn während des erstellen der Datanbank das Mapping erneut gespeichert wird, wird die Datenbank erst später erneut aktualisiert.
go func() {
if System.BackgroundProcess == true {
return
}
System.BackgroundProcess = true
for {
time.Sleep(time.Duration(1) * time.Second)
if System.ScanInProgress == 0 {
break
}
}
System.ScanInProgress = 1
cleanupXEPG()
System.ScanInProgress = 0
buildXEPG(false)
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
System.BackgroundProcess = false
}()
}
return
}
@@ -612,6 +729,7 @@ func saveWizard(request RequestStruct) (nextStep int, err error) {
}
buildXEPG(false)
System.ScanInProgress = 0
}
@@ -679,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{}
@@ -837,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)
@@ -846,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)
}

View File

@@ -95,7 +95,7 @@ func getLineupStatus() (jsonContent []byte, err error) {
lineupStatus.ScanInProgress = System.ScanInProgress
lineupStatus.ScanPossible = 0
lineupStatus.Source = "Cable"
lineupStatus.SourceList = []string{"IPTV", "Cable"}
lineupStatus.SourceList = []string{"Cable"}
jsonContent, err = json.MarshalIndent(lineupStatus, "", " ")

View File

@@ -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.WEB, 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
}
@@ -163,7 +23,7 @@ func uploadLogo(input, filename string) (logoURL string, err error) {
return
}
logoURL = fmt.Sprintf("%s://%s/data_images/%s", System.ServerProtocol.WEB, System.Domain, filename)
logoURL = fmt.Sprintf("%s://%s/data_images/%s", System.ServerProtocol.XML, System.Domain, filename)
return

View File

@@ -85,7 +85,8 @@ func ShowSystemInfo() {
println("---")
fmt.Println("Settings [Streaming]")
fmt.Println(fmt.Sprintf("Buffer: %t", Settings.Buffer))
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))

View 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
}

View 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
}

View File

@@ -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

View File

@@ -2,6 +2,8 @@ package m3u
import (
"errors"
"fmt"
"log"
"net/url"
"regexp"
"strings"
@@ -12,6 +14,7 @@ func MakeInterfaceFromM3U(byteStream []byte) (allChannels []interface{}, err err
var content = string(byteStream)
var channelName string
var uuids []string
var parseMetaData = func(channel string) (stream map[string]string) {
@@ -53,7 +56,7 @@ func MakeInterfaceFromM3U(byteStream []byte) (allChannels []interface{}, err err
line = strings.Replace(line, p, "", 1)
p = strings.Replace(p, `"`, "", -1)
var parameter = strings.Split(p, "=")
var parameter = strings.SplitN(p, "=", 2)
if len(parameter) == 2 {
@@ -120,9 +123,15 @@ func MakeInterfaceFromM3U(byteStream []byte) (allChannels []interface{}, err err
if strings.Contains(strings.ToLower(key), "id") {
if indexOfString(value, uuids) != -1 {
log.Println(fmt.Sprintf("Channel: %s - %s = %s ", stream["name"], key, value))
break
}
uuids = append(uuids, value)
stream["_uuid.key"] = key
stream["_uuid.value"] = value
//os.Exit(0)
break
}
@@ -135,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") {
@@ -154,9 +167,20 @@ 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.")
}
return
}
func indexOfString(element string, data []string) int {
for k, v := range data {
if element == v {
return k
}
}
return -1
}

View File

@@ -9,7 +9,7 @@ import (
"strconv"
"strings"
m3u "../src/internal/m3u-parser"
m3u "xteve/src/internal/m3u-parser"
)
// Playlisten parsen
@@ -43,6 +43,10 @@ func filterThisStream(s interface{}) (status bool) {
for _, filter := range Data.Filter {
if filter.Rule == "" {
continue
}
var group, name, search string
var exclude, include string
var match = false
@@ -177,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
@@ -219,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"

View File

@@ -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)

View File

@@ -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:
@@ -245,6 +245,8 @@ func getErrMsg(errCode int) (errMsg string) {
errMsg = fmt.Sprintf("Invalid formatting of the time")
case 1013:
errMsg = fmt.Sprintf("Invalid settings file (settings.json), file must be at least version %s", System.Compatibility)
case 1014:
errMsg = fmt.Sprintf("Invalid filter rule")
case 1020:
errMsg = fmt.Sprintf("Data could not be saved, invalid keyword")
@@ -252,12 +254,13 @@ func getErrMsg(errCode int) (errMsg string) {
// Datenbank Update
case 1030:
errMsg = fmt.Sprintf("Invalid settings file (%s)", System.File.Settings)
case 1031:
errMsg = fmt.Sprintf("Database error. The database version of your settings is not compatible with this version.")
// M3U Parser
case 1050:
errMsg = fmt.Sprintf("Invalid duration specification in the M3U8 playlist.")
// M3U Parser
case 1060:
errMsg = fmt.Sprintf("Invalid characters found in the tvg parameters, streams with invalid parameters were skipped.")
@@ -266,6 +269,8 @@ func getErrMsg(errCode int) (errMsg string) {
errMsg = fmt.Sprintf("Folder could not be created.")
case 1071:
errMsg = fmt.Sprintf("File could not be created")
case 1072:
errMsg = fmt.Sprintf("File not found")
// Backup
case 1090:
@@ -290,12 +295,14 @@ func getErrMsg(errCode int) (errMsg string) {
errMsg = fmt.Sprintf("Steaming URL could not be found in any playlist")
case 1203:
errMsg = fmt.Sprintf("Steaming URL could not be found in any playlist")
case 1204:
errMsg = fmt.Sprintf("Streaming was stopped by third party transcoder (FFmpeg / VLC)")
// 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:
@@ -306,6 +313,10 @@ func getErrMsg(errCode int) (errMsg string) {
errMsg = fmt.Sprintf("There are no channels mapped, use the mapping menu to assign EPG data to the channels.")
case 2010:
errMsg = fmt.Sprintf("No valid streaming URL")
case 2020:
errMsg = fmt.Sprintf("FFmpeg binary was not found. Check the FFmpeg binary path in the xTeVe settings.")
case 2021:
errMsg = fmt.Sprintf("VLC binary was not found. Check the VLC path binary in the xTeVe settings.")
case 2099:
errMsg = fmt.Sprintf("Updates have been disabled by the developer")
@@ -345,6 +356,8 @@ func getErrMsg(errCode int) (errMsg string) {
errMsg = fmt.Sprintf("This error message comes from the provider")
case 4005:
errMsg = fmt.Sprintf("Temporary buffer files could not be deleted")
case 4006:
errMsg = fmt.Sprintf("Server connection timeout")
// Buffer (M3U8)
case 4050:
@@ -368,7 +381,7 @@ func getErrMsg(errCode int) (errMsg string) {
case 6002:
errMsg = fmt.Sprintf("Update failed")
case 6003:
errMsg = fmt.Sprintf("Server not available")
errMsg = fmt.Sprintf("Update server not available")
case 6004:
errMsg = fmt.Sprintf("xTeVe update available")

View File

@@ -108,3 +108,147 @@ type BandwidthCalculation struct {
Stop time.Time
TimeDiff float64
}
/*
var args = "-hide_banner -loglevel panic -re -i " + url + " -codec copy -f mpegts pipe:1"
//var args = "-re -i " + url + " -codec copy -f mpegts pipe:1"
cmd := exec.Command("/usr/local/bin/ffmpeg", strings.Split(args, " ")...)
//run := exec.Command("/usr/local/bin/ffmpeg", "-hide_banner", "-loglevel", "panic", "-re", "-i", url, "-codec", "copy", "-f", "mpegts", "pipe:1")
//run := exec.Command("/usr/local/bin/ffmpeg", "-re", "-i", url, "-codec", "copy", "-f", "mpegts", "pipe:1")
stderr, _ := cmd.StderrPipe()
cmd.Start()
scanner := bufio.NewScanner(stderr)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
m := scanner.Text()
fmt.Println(m)
}
cmd.Wait()
os.Exit(0)
*/
/*
ffmpegOut, _ := run.StderrPipe()
//run.Start()
scanner = bufio.NewScanner(ffmpegOut)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
m := scanner.Text()
fmt.Println(m)
}
ffmpegOut, err = run.StdoutPipe()
if err != nil {
ShowError(err, 0)
return
}
stderr, stderrErr := run.StderrPipe()
if stderrErr != nil {
fmt.Println(stderrErr)
}
_ = stderr
if startErr := run.Start(); startErr != nil {
fmt.Println(startErr)
return
}
n, err := ffmpegOut.Read(buffer)
_ = n
_ = stream
_ = fileSize
if err != nil && err != io.EOF {
ShowError(err, 0)
addErrorToStream(err)
return
}
defer bufferFile.Close()
scanner = bufio.NewScanner(ffmpegOut)
for scanner.Scan() {
//fmt.Printf("%s\n", scanner.Text())
//fmt.Println(scanner)
thisLine := scanner.Text()
line := make([]byte, len(thisLine))
buffer = append(buffer, line...)
fmt.Println(len(buffer))
if len(buffer) > tmpFileSize {
if _, err := bufferFile.Write(buffer[:]); err != nil {
ShowError(err, 0)
addErrorToStream(err)
run.Process.Kill()
return
}
buffer = make([]byte, 1024*Settings.BufferSize*2)
debug = fmt.Sprintf("Buffer Status:Done (%s)", tmpFile)
showDebug(debug, 2)
bufferFile.Close()
stream.Status = true
playlist.Streams[streamID] = stream
BufferInformation.Store(playlistID, playlist)
tmpSegment++
tmpFile = fmt.Sprintf("%s%d.ts", tmpFolder, tmpSegment)
if clientConnection(stream) == false {
bufferFile.Close()
run.Process.Kill()
err = os.RemoveAll(stream.Folder)
if err != nil {
ShowError(err, 4005)
}
return
}
bufferFile, err = os.Create(tmpFile)
if err != nil {
addErrorToStream(err)
run.Process.Kill()
return
}
fileSize = 0
if n == 0 {
bufferFile.Close()
run.Process.Kill()
break
}
os.Exit(0)
}
}
*/

View File

@@ -1,5 +1,7 @@
package src
import "xteve/src/internal/imgcache"
// SystemStruct : Beinhaltet alle Systeminformationen
type SystemStruct struct {
Addresses struct {
@@ -8,18 +10,30 @@ type SystemStruct struct {
XML string
}
APIVersion string
AppName string
ARCH string
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
Path string
}
VLC struct {
DefaultOptions string
Path string
}
File struct {
Authentication string
@@ -31,6 +45,10 @@ type SystemStruct struct {
XML string
}
Compressed struct {
GZxml string
}
Flag struct {
Branch string
Debug int
@@ -84,6 +102,7 @@ type SystemStruct struct {
}
URLBase string
UDPxy string
Version string
WEB struct {
Menu []string
@@ -99,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
@@ -176,6 +196,7 @@ type XEPGChannelStruct struct {
XName string `json:"x-name,required"`
XUpdateChannelIcon bool `json:"x-update-channel-icon,required"`
XUpdateChannelName bool `json:"x-update-channel-name,required"`
XDescription string `json:"x-description,required"`
}
// M3UChannelStructXEPG : M3U Struktur für XEPG
@@ -230,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"`
@@ -241,11 +262,15 @@ type SettingsStrcut struct {
BackupKeep int `json:"backup.keep"`
BackupPath string `json:"backup.path"`
Branch string `json:"git.branch,omitempty"`
Buffer bool `json:"buffer"`
Buffer string `json:"buffer"`
BufferSize int `json:"buffer.size.kb"`
BufferTimeout float64 `json:"buffer.timeout"`
CacheImages bool `json:"cache.images"`
EpgSource string `json:"epgSource"`
FFmpegOptions string `json:"ffmpeg.options"`
FFmpegPath string `json:"ffmpeg.path"`
VLCOptions string `json:"vlc.options"`
VLCPath string `json:"vlc.path"`
FileM3U []string `json:"file,omitempty"` // Beim Wizard wird die M3U in ein Slice gespeichert
FileXMLTV []string `json:"xmltv,omitempty"` // Altes Speichersystem der Provider XML Datei Slice (Wird für die Umwandlung auf das neue benötigt)
@@ -270,6 +295,7 @@ type SettingsStrcut struct {
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"`

View File

@@ -25,18 +25,25 @@ type RequestStruct struct {
AuthenticationXML *bool `json:"authentication.xml,omitempty"`
BackupKeep *int `json:"backup.keep,omitempty"`
BackupPath *string `json:"backup.path,omitempty"`
Buffer *bool `json:"buffer,omitempty"`
Buffer *string `json:"buffer,omitempty"`
BufferSize *int `json:"buffer.size.kb, omitempty"`
BufferTimeout *float64 `json:"buffer.timeout,omitempty"`
CacheImages *bool `json:"cache.images,omitempty"`
EpgSource *string `json:"epgSource,omitempty"`
FFmpegOptions *string `json:"ffmpeg.options,omitempty"`
FFmpegPath *string `json:"ffmpeg.path,omitempty"`
VLCOptions *string `json:"vlc.options,omitempty"`
VLCPath *string `json:"vlc.path,omitempty"`
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"`
XteveAutoUpdate *bool `json:"xteveAutoUpdate,omitempty"`
SchemeM3U *string `json:"scheme.m3u,omitempty"`
SchemeXML *string `json:"scheme.xml,omitempty"`
} `json:"settings,omitempty"`
// Upload Logo
@@ -103,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"`

View File

@@ -42,12 +42,16 @@ 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"`
PreviouslyShown *PreviouslyShown `xml:"previously-shown"`
New *New `xml:"new"`
Live *Live `xml:"live"`
Premiere *Live `xml:"premiere"`
}
// Title : Programmtitel
@@ -74,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"`
@@ -99,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"`

View File

@@ -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 {
@@ -113,11 +113,13 @@ func loadSettings() (settings SettingsStrcut, err error) {
defaults["authentication.xml"] = false
defaults["backup.keep"] = 10
defaults["backup.path"] = System.Folder.Backup
defaults["buffer"] = false
defaults["buffer"] = "-"
defaults["buffer.size.kb"] = 1024
defaults["buffer.timeout"] = 500
defaults["cache.images"] = false
defaults["epgSource"] = "PMS"
defaults["ffmpeg.options"] = System.FFmpeg.DefaultOptions
defaults["vlc.options"] = System.VLC.DefaultOptions
defaults["files"] = dataMap
defaults["files.update"] = true
defaults["filter"] = make(map[string]interface{})
@@ -133,6 +135,7 @@ 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
@@ -159,15 +162,32 @@ func loadSettings() (settings SettingsStrcut, err error) {
showInfo(fmt.Sprintf("Git Branch:Switching Git Branch to -> %s", settings.Branch))
}
if len(settings.FFmpegPath) == 0 {
settings.FFmpegPath = searchFileInOS("ffmpeg")
}
if len(settings.VLCPath) == 0 {
settings.VLCPath = searchFileInOS("cvlc")
}
settings.Version = System.DBVersion
err = saveSettings(settings)
// Warung wenn FFmpeg nicht gefunden wurde
if len(Settings.FFmpegPath) == 0 && Settings.Buffer == "ffmpeg" {
showWarning(2020)
}
if len(Settings.VLCPath) == 0 && Settings.Buffer == "vlc" {
showWarning(2021)
}
return
}
// Einstellungen speichern (xTeVe)
func saveSettings(settings SettingsStrcut) (err error) {
func saveSettings(settings SettingsStruct) (err error) {
if settings.BackupKeep == 0 {
settings.BackupKeep = 10

View File

@@ -10,8 +10,11 @@ import (
"io/ioutil"
"net"
"os"
"os/exec"
"os/user"
"path/filepath"
"runtime"
"strings"
"text/template"
)
@@ -42,13 +45,25 @@ func checkFolder(path string) (err error) {
return nil
}
// Prüft ob die datei im Dateisystem existiert
// Prüft ob die Datei im Dateisystem existiert
func checkFile(filename string) (err error) {
var file = getPlatformFile(filename)
if _, err = os.Stat(file); os.IsNotExist(err) {
return
return err
}
fi, err := os.Stat(file)
if err != nil {
return err
}
switch mode := fi.Mode(); {
case mode.IsDir():
err = fmt.Errorf("%s: %s", file, getErrMsg(1072))
case mode.IsRegular():
break
}
return
@@ -77,6 +92,7 @@ func GetUserHomeDirectory() (userHomeDirectory string) {
return
}
// Prüft Dateiberechtigung
func checkFilePermission(dir string) (err error) {
var filename = dir + "permission.test"
@@ -115,6 +131,34 @@ func removeOldSystemData() {
os.RemoveAll(System.Folder.Temp)
}
// Sucht eine Datei im OS
func searchFileInOS(file string) (path string) {
switch runtime.GOOS {
case "linux", "darwin", "freebsd":
var args = file
var cmd = exec.Command("which", strings.Split(args, " ")...)
out, err := cmd.CombinedOutput()
if err == nil {
var slice = strings.Split(strings.Replace(string(out), "\r\n", "\n", -1), "\n")
if len(slice) > 0 {
path = strings.Trim(slice[0], "\r\n")
}
}
default:
return
}
return
}
//
func removeChildItems(dir string) error {
@@ -276,6 +320,22 @@ func resolveHostIP() (err error) {
}
if len(System.IPAddress) == 0 {
switch len(System.IPAddressesV4) {
case 0:
if len(System.IPAddressesV6) > 0 {
System.IPAddress = System.IPAddressesV6[0]
}
default:
System.IPAddress = System.IPAddressesV4[0]
}
}
System.Hostname, err = os.Hostname()
if err != nil {
return

View File

@@ -7,7 +7,7 @@ import (
"io/ioutil"
"net/http"
up2date "../src/internal/up2date/client"
up2date "xteve/src/internal/up2date/client"
"reflect"
)
@@ -41,8 +41,8 @@ func BinaryUpdate() (err error) {
resp, err := http.Get(gitInfo)
if err != nil {
ShowError(err, 0)
return err
ShowError(err, 6003)
return nil
}
if resp.StatusCode != http.StatusOK {
@@ -169,6 +169,13 @@ checkVersion:
if settingsVersion, ok := settingsMap["version"].(string); ok {
if settingsVersion > System.DBVersion {
showInfo("Settings DB Version:" + settingsVersion)
showInfo("System DB Version:" + System.DBVersion)
err = errors.New(getErrMsg(1031))
return
}
// Letzte Kompatible Version (1.4.4)
if settingsVersion < System.Compatibility {
err = errors.New(getErrMsg(1013))
@@ -204,10 +211,37 @@ checkVersion:
}
case "2.0.0":
if oldBuffer, ok := settingsMap["buffer"].(bool); ok {
var newBuffer string
switch oldBuffer {
case true:
newBuffer = "xteve"
case false:
newBuffer = "-"
}
settingsMap["buffer"] = newBuffer
settingsMap["version"] = "2.1.0"
err = saveMapToJSONFile(System.File.Settings, settingsMap)
if err != nil {
return
}
goto checkVersion
} else {
err = errors.New(getErrMsg(1030))
return
}
case "2.1.0":
// Falls es in einem späteren Update Änderungen an der Datenbank gibt, geht es hier weiter
break
}
} else {

File diff suppressed because one or more lines are too long

View File

@@ -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,20 +130,37 @@ func Stream(w http.ResponseWriter, r *http.Request) {
return
}
if strings.Index(streamInfo.URL, "rtsp://") != -1 || strings.Index(streamInfo.URL, "rtp://") != -1 {
err = errors.New("RTSP and RTP streams are not supported")
ShowError(err, 2004)
showInfo("Streaming URL:" + streamInfo.URL)
http.Redirect(w, r, streamInfo.URL, 302)
showInfo("Streaming Info:URL was passed to the client")
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://@"))
}
showInfo(fmt.Sprintf("Buffer:%t", Settings.Buffer))
switch Settings.Buffer {
if Settings.Buffer == true {
case "-":
showInfo(fmt.Sprintf("Buffer:false [%s]", Settings.Buffer))
case "xteve":
if strings.Index(streamInfo.URL, "rtsp://") != -1 || strings.Index(streamInfo.URL, "rtp://") != -1 {
err = errors.New("RTSP and RTP streams are not supported")
ShowError(err, 2004)
showInfo("Streaming URL:" + streamInfo.URL)
http.Redirect(w, r, streamInfo.URL, 302)
showInfo("Streaming Info:URL was passed to the client")
return
}
showInfo(fmt.Sprintf("Buffer:true [%s]", Settings.Buffer))
default:
showInfo(fmt.Sprintf("Buffer:true [%s]", Settings.Buffer))
}
if Settings.Buffer != "-" {
showInfo(fmt.Sprintf("Buffer Size:%d KB", Settings.BufferSize))
}
@@ -152,16 +170,16 @@ func Stream(w http.ResponseWriter, r *http.Request) {
// Prüfen ob der Buffer verwendet werden soll
switch Settings.Buffer {
case true:
bufferingStream(streamInfo.PlaylistID, streamInfo.URL, streamInfo.Name, w, r)
case false:
case "-":
showInfo("Streaming URL:" + streamInfo.URL)
http.Redirect(w, r, streamInfo.URL, 302)
showInfo("Streaming Info:URL was passed to the client.")
showInfo("Streaming Info:xTeVe is no longer involved, the client connects directly to the streaming server.")
default:
bufferingStream(streamInfo.PlaylistID, streamInfo.URL, streamInfo.Name, w, r)
}
return
@@ -195,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{}
@@ -205,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)
@@ -249,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))
}
@@ -305,15 +329,18 @@ func WS(w http.ResponseWriter, r *http.Request) {
var newToken string
if r.Header.Get("Origin") != "http://"+r.Host {
httpStatusError(w, r, 403)
return
}
/*
if r.Header.Get("Origin") != "http://"+r.Host {
httpStatusError(w, r, 403)
return
}
*/
conn, err := websocket.Upgrade(w, r, w.Header(), 1024, 1024)
if err != nil {
ShowError(err, 0)
http.Error(w, "Could not open websocket connection", http.StatusBadRequest)
return
}
setGlobalDomain(r.Host)
@@ -572,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))

View File

@@ -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
@@ -306,7 +367,6 @@ func createXEPGDatabase() (err error) {
}
if len(xepgChannel.XChannelID) == 0 {
fmt.Println(mapToJSON(xepgChannel))
delete(Data.XEPG.Channels, id)
}
@@ -316,11 +376,24 @@ func createXEPGDatabase() (err error) {
}
// 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)
@@ -328,45 +401,55 @@ 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 Data.XEPG.Channels {
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:
// Bereits vorhandener Kanal
var xepgChannel XEPGChannelStruct
@@ -429,7 +512,7 @@ func createXEPGDatabase() (err error) {
}
}
showInfo("XEPG:" + "Save DB file")
err = saveMapToJSONFile(System.File.XEPG, Data.XEPG.Channels)
if err != nil {
return
@@ -562,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{}
@@ -609,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)
@@ -635,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
}
@@ -684,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
@@ -711,6 +808,9 @@ func getProgramData(xepgChannel XEPGChannelStruct) (xepgXML XMLTV, err error) {
// Live
program.Live = xmltvProgram.Live
// Premiere
program.Premiere = xmltvProgram.Premiere
xepgXML.Program = append(xepgXML.Program, program)
}
@@ -723,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]
@@ -753,10 +854,15 @@ func createDummyProgram(xepgChannel XEPGChannelStruct) (dummyXMLTV XMLTV) {
epg.Start = epgStartTime.Format("20060102150405") + offset
epg.Stop = epgStopTime.Format("20060102150405") + offset
epg.Title = append(epg.Title, &Title{Value: xepgChannel.XName + " (" + epgStartTime.Weekday().String()[0:2] + ". " + epgStartTime.Format("15:04") + " - " + epgStopTime.Format("15:04") + ")", Lang: "en"})
epg.Desc = append(epg.Desc, &Desc{Value: "xTeVe: (" + strconv.Itoa(dummyLength) + " Minutes) " + epgStartTime.Weekday().String() + " " + epgStartTime.Format("15:04") + " - " + epgStopTime.Format("15:04"), Lang: "en"})
if len(xepgChannel.XDescription) == 0 {
epg.Desc = append(epg.Desc, &Desc{Value: "xTeVe: (" + strconv.Itoa(dummyLength) + " Minutes) " + epgStartTime.Weekday().String() + " " + epgStartTime.Format("15:04") + " - " + epgStopTime.Format("15:04"), Lang: "en"})
} else {
epg.Desc = append(epg.Desc, &Desc{Value: xepgChannel.XDescription, Lang: "en"})
}
if Settings.XepgReplaceMissingImages == true {
poster.Src = getCacheImageURL(xepgChannel.TvgLogo)
poster.Src = imgc.Image.GetURL(xepgChannel.TvgLogo)
epg.Poster = append(epg.Poster, poster)
}
@@ -803,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)
}
@@ -812,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)
}
@@ -923,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
@@ -932,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 {
@@ -940,6 +1060,10 @@ func cleanupXEPG() {
}
}
if indexOfString(xepgChannel.FileM3UID, sourceIDs) == -1 {
delete(Data.XEPG.Channels, id)
}
}
}

View File

@@ -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"))
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"))
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] + " "
}
}

View File

@@ -147,7 +147,7 @@ class Content {
var cell:Cell = new Cell()
cell.child = true
cell.childType = "P"
if (SERVER["settings"]["buffer"] == true) {
if (SERVER["settings"]["buffer"] != "-") {
cell.value = data[key]["tuner"]
} else {
cell.value = "-"
@@ -1113,7 +1113,7 @@ function openPopUp(dataType, element) {
content.appendRow("{{.playlist.fileM3U.title}}", input)
// Tuner
if (SERVER["settings"]["buffer"] == true) {
if (SERVER["settings"]["buffer"] != "-") {
var text:string[] = new Array()
var values:string[] = new Array()
@@ -1192,7 +1192,7 @@ function openPopUp(dataType, element) {
content.appendRow("{{.playlist.fileHDHR.title}}", input)
// Tuner
if (SERVER["settings"]["buffer"] == true) {
if (SERVER["settings"]["buffer"] != "-") {
var text:string[] = new Array()
var values:string[] = new Array()
@@ -1532,6 +1532,13 @@ function openPopUp(dataType, element) {
content.description(data["name"])
// Beschreibung
var dbKey:string = "x-description"
var input = content.createInput("text", dbKey, data[dbKey])
input.setAttribute("placeholder", "{{.mapping.description.placeholder}}")
input.setAttribute("onchange", "javascript: this.className = 'changed'")
content.appendRow("{{.mapping.description.title}}", input)
// Aktualisierung des Kanalnamens
if (data.hasOwnProperty("_uuid.key")) {
if (data["_uuid.key"] != "") {
@@ -1561,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)

View File

@@ -89,6 +89,62 @@ class SettingsCategory {
setting.appendChild(tdRight)
break
case "ffmpeg.path":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.ffmpegPath.title}}" + ":"
var tdRight = document.createElement("TD")
var input = content.createInput("text", "ffmpeg.path", data)
input.setAttribute("placeholder", "{{.settings.ffmpegPath.placeholder}}")
input.setAttribute("onchange", "javascript: this.className = 'changed'")
tdRight.appendChild(input)
setting.appendChild(tdLeft)
setting.appendChild(tdRight)
break
case "ffmpeg.options":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.ffmpegOptions.title}}" + ":"
var tdRight = document.createElement("TD")
var input = content.createInput("text", "ffmpeg.options", data)
input.setAttribute("placeholder", "{{.settings.ffmpegOptions.placeholder}}")
input.setAttribute("onchange", "javascript: this.className = 'changed'")
tdRight.appendChild(input)
setting.appendChild(tdLeft)
setting.appendChild(tdRight)
break
case "vlc.path":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.vlcPath.title}}" + ":"
var tdRight = document.createElement("TD")
var input = content.createInput("text", "vlc.path", data)
input.setAttribute("placeholder", "{{.settings.vlcPath.placeholder}}")
input.setAttribute("onchange", "javascript: this.className = 'changed'")
tdRight.appendChild(input)
setting.appendChild(tdLeft)
setting.appendChild(tdRight)
break
case "vlc.options":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.vlcOptions.title}}" + ":"
var tdRight = document.createElement("TD")
var input = content.createInput("text", "vlc.options", data)
input.setAttribute("placeholder", "{{.settings.vlcOptions.placeholder}}")
input.setAttribute("onchange", "javascript: this.className = 'changed'")
tdRight.appendChild(input)
setting.appendChild(tdLeft)
setting.appendChild(tdRight)
break
// Checkboxen
case "authentication.web":
var tdLeft = document.createElement("TD")
@@ -216,20 +272,6 @@ class SettingsCategory {
setting.appendChild(tdRight)
break
case "buffer":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.streamBuffering.title}}" + ":"
var tdRight = document.createElement("TD")
var input = content.createCheckbox(settingsKey)
input.checked = data
input.setAttribute("onchange", "javascript: this.className = 'changed'")
tdRight.appendChild(input)
setting.appendChild(tdLeft)
setting.appendChild(tdRight)
break
case "api":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.api.title}}" + ":"
@@ -314,6 +356,37 @@ class SettingsCategory {
setting.appendChild(tdRight)
break
case "buffer":
var tdLeft = document.createElement("TD")
tdLeft.innerHTML = "{{.settings.streamBuffering.title}}" + ":"
var tdRight = document.createElement("TD")
var text:any[] = ["{{.settings.streamBuffering.info_false}}", "xTeVe: ({{.settings.streamBuffering.info_xteve}})", "FFmpeg: ({{.settings.streamBuffering.info_ffmpeg}})", "VLC: ({{.settings.streamBuffering.info_vlc}})"]
var values:any[] = ["-", "xteve", "ffmpeg", "vlc"]
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 "udpxy":
var tdLeft = document.createElement("TD");
tdLeft.innerHTML = "{{.settings.udpxy.title}}" + ":"
var tdRight = document.createElement("TD")
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
}
return setting
@@ -381,6 +454,22 @@ class SettingsCategory {
text = "{{.settings.userAgent.description}}"
break
case "ffmpeg.path":
text = "{{.settings.ffmpegPath.description}}"
break
case "ffmpeg.options":
text = "{{.settings.ffmpegOptions.description}}"
break
case "vlc.path":
text = "{{.settings.vlcPath.description}}"
break
case "vlc.options":
text = "{{.settings.vlcOptions.description}}"
break
case "epgSource":
text = "{{.settings.epgSource.description}}"
break
@@ -409,6 +498,10 @@ class SettingsCategory {
text = "{{.settings.replaceEmptyImages.description}}"
break
case "udpxy":
text = "{{.settings.udpxy.description}}"
break
default:
text = ""
break

View File

@@ -13,7 +13,7 @@ import (
"runtime"
"strings"
"./src"
"xteve/src"
)
// GitHubStruct : GitHub Account. Über diesen Account werden die Updates veröffentlicht
@@ -35,21 +35,18 @@ var GitHub = GitHubStruct{Branch: "master", User: "xteve-project", Repo: "xTeVe-
Update: Automatic updates from the GitHub repository [true|false]
*/
// Name : Programname
// Name : Programmname
const Name = "xTeVe"
// Version : Version, die Build Nummer wird in der main func geparst.
const Version = "2.0.2.0020"
const Version = "2.2.0.0200"
// DBVersion : Datanbank Version
const DBVersion = "2.0.0"
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)