Compare commits
124 Commits
v2.0.0.000
...
2.2.0.200
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e999b85b9 | ||
|
|
e4b3667fe0 | ||
|
|
e44eff0645 | ||
|
|
884b427754 | ||
|
|
25bad13800 | ||
|
|
d2456b0506 | ||
|
|
19b9a259b1 | ||
|
|
441cefa3a4 | ||
|
|
014f5b7218 | ||
|
|
0310b7e738 | ||
|
|
6d890cfd33 | ||
|
|
410cc3648f | ||
|
|
07f9cac3c5 | ||
|
|
f43ce0f7c5 | ||
|
|
db59f7ef37 | ||
|
|
b407ec5bf0 | ||
|
|
d8fc1aea97 | ||
|
|
38333d65cb | ||
|
|
a6a9b90937 | ||
|
|
2b3fe6a09d | ||
|
|
a09eca59a7 | ||
|
|
0df3b4d755 | ||
|
|
5552514a1f | ||
|
|
5fceb1d34f | ||
|
|
fb5d0a3904 | ||
|
|
28fe4dcf1c | ||
|
|
bc307a7cd4 | ||
|
|
fd3024d7ff | ||
|
|
d3f6725ba6 | ||
|
|
c266012db3 | ||
|
|
d602b60710 | ||
|
|
67b7ba6df9 | ||
|
|
71dfe91272 | ||
|
|
534510a4ec | ||
|
|
2d10fc9313 | ||
|
|
a63a9c0d8f | ||
|
|
dd911d6e5d | ||
|
|
c1970a8393 | ||
|
|
493d612d52 | ||
|
|
4fc4330a94 | ||
|
|
a683533824 | ||
|
|
4b9f5826cf | ||
|
|
ca49d70910 | ||
|
|
1b425018d4 | ||
|
|
87b36c283b | ||
|
|
6da26ff4fb | ||
|
|
cd08985e79 | ||
|
|
dc04519229 | ||
|
|
c4ad96b715 | ||
|
|
45b5e602bb | ||
|
|
1cefbf022d | ||
|
|
d5328f6b1a | ||
|
|
91b80bc8bb | ||
|
|
aa763726a3 | ||
|
|
66c01dd1fb | ||
|
|
03e1abbe90 | ||
|
|
469581e280 | ||
|
|
019c98996a | ||
|
|
36db927794 | ||
|
|
f0a49788cc | ||
|
|
72767d7dbd | ||
|
|
8eecbf2b78 | ||
|
|
1a1e37fe15 | ||
|
|
08f6fb60e3 | ||
|
|
eded490ac7 | ||
|
|
ed770b9dbc | ||
|
|
6129b4911a | ||
|
|
477c5f30c1 | ||
|
|
65ddc6f301 | ||
|
|
3ef95c1950 | ||
|
|
3a3798cd2d | ||
|
|
72eb7fb599 | ||
|
|
4b969b8cee | ||
|
|
3d9266dabe | ||
|
|
48218cda50 | ||
|
|
dc42afcd05 | ||
|
|
20e5e1b545 | ||
|
|
11dd830110 | ||
|
|
7c87d1d5bd | ||
|
|
6cdd44357b | ||
|
|
ad992eb615 | ||
|
|
11453c6053 | ||
|
|
81e8ae33d7 | ||
|
|
f3be0fca47 | ||
|
|
1c1c89cd74 | ||
|
|
3d73dba422 | ||
|
|
c6e74fe11c | ||
|
|
18dba46c02 | ||
|
|
efa55b39a9 | ||
|
|
717fa68b7e | ||
|
|
878531ff79 | ||
|
|
792fd9a373 | ||
|
|
a1ec0287ef | ||
|
|
a79e824ef8 | ||
|
|
c843f424fe | ||
|
|
5c6637c048 | ||
|
|
1062e072d6 | ||
|
|
d831a099f0 | ||
|
|
a06baef4d3 | ||
|
|
f9d1a45bbd | ||
|
|
8a4fb8ba30 | ||
|
|
28dad4932b | ||
|
|
1769b1e6db | ||
|
|
30ab13f871 | ||
|
|
0f37835206 | ||
|
|
67fe80b4fd | ||
|
|
96e10ff51d | ||
|
|
d550beaa01 | ||
|
|
00ec32456c | ||
|
|
b3600f8a14 | ||
|
|
849493a802 | ||
|
|
3730d1187d | ||
|
|
2a06bf6b01 | ||
|
|
c389b990b4 | ||
|
|
56f3e3389b | ||
|
|
50e6ed274e | ||
|
|
8490187d31 | ||
|
|
4dc9dfabf2 | ||
|
|
5fc5f773d3 | ||
|
|
c9bc4aedbc | ||
|
|
51bab26d59 | ||
|
|
0063dcc523 | ||
|
|
23dd3ef08c | ||
|
|
9b84982bab |
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Please only useful feature request.
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,6 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
demo
|
||||||
|
dev
|
||||||
compiler
|
compiler
|
||||||
files
|
files
|
||||||
update_xteve*.sh
|
update_xteve*.sh
|
||||||
|
|||||||
44
README.md
44
README.md
@@ -31,7 +31,7 @@ Documentation for setup and configuration is [here](https://github.com/xteve-pro
|
|||||||
* Merge external M3U files
|
* Merge external M3U files
|
||||||
* Merge external XMLTV files
|
* Merge external XMLTV files
|
||||||
* Automatic M3U and XMLTV update
|
* Automatic M3U and XMLTV update
|
||||||
* M3U und XMLTV export
|
* M3U and XMLTV export
|
||||||
|
|
||||||
#### Channel management
|
#### Channel management
|
||||||
* Filtering streams
|
* Filtering streams
|
||||||
@@ -65,10 +65,11 @@ Thanks to @alturismo and @LeeD for creating the Docker Images.
|
|||||||
**Created by alturismo:**
|
**Created by alturismo:**
|
||||||
[xTeVe](https://hub.docker.com/r/alturismo/xteve)
|
[xTeVe](https://hub.docker.com/r/alturismo/xteve)
|
||||||
[xTeVe / Guide2go](https://hub.docker.com/r/alturismo/xteve_guide2go)
|
[xTeVe / Guide2go](https://hub.docker.com/r/alturismo/xteve_guide2go)
|
||||||
|
[xTeVe / Guide2go / owi2plex](https://hub.docker.com/r/alturismo/xteve_g2g_owi)
|
||||||
|
|
||||||
Including:
|
Including:
|
||||||
- Guide2go: XMLTV grabber for Schedules Direct
|
- Guide2go: XMLTV grabber for Schedules Direct
|
||||||
|
- owi2plex: XMLTV file grabber for Enigma receivers
|
||||||
|
|
||||||
**Created by LeeD:**
|
**Created by LeeD:**
|
||||||
[xTeVe / Guide2go / Zap2XML](https://hub.docker.com/r/dnsforge/xteve)
|
[xTeVe / Guide2go / Zap2XML](https://hub.docker.com/r/dnsforge/xteve)
|
||||||
@@ -82,19 +83,50 @@ Including:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### xTeVe Beta 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.**
|
||||||
|
|
||||||
|
With the command line argument `branch` the Git Branch can be changed. xTeVe must be started via the terminal.
|
||||||
|
|
||||||
|
#### Switch from master to beta branch:
|
||||||
|
```
|
||||||
|
xteve -branch beta
|
||||||
|
|
||||||
|
...
|
||||||
|
[xTeVe] GitHub: https://github.com/xteve-project
|
||||||
|
[xTeVe] Git Branch: beta [xteve-project]
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Switch from beta to master branch:
|
||||||
|
```
|
||||||
|
xteve -branch master
|
||||||
|
|
||||||
|
...
|
||||||
|
[xTeVe] GitHub: https://github.com/xteve-project
|
||||||
|
[xTeVe] Git Branch: master [xteve-project]
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
When the branch is changed, an update is only performed if there is a new version and the update function is activated in the settings.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Build from source code [Go / Golang]
|
## Build from source code [Go / Golang]
|
||||||
|
|
||||||
#### Requirements
|
#### Requirements
|
||||||
* Go (go1.12.4 or newer)
|
* [Go](https://golang.org) (go1.16.2 or newer)
|
||||||
|
|
||||||
#### Dependancys
|
#### Dependencies
|
||||||
* [go-ssdp](https://github.com/koron/go-ssdp)
|
* [go-ssdp](https://github.com/koron/go-ssdp)
|
||||||
* [websocket](https://github.com/gorilla/websocket)
|
* [websocket](https://github.com/gorilla/websocket)
|
||||||
* [osext](https://github.com/kardianos/osext)
|
* [osext](https://github.com/kardianos/osext)
|
||||||
|
|
||||||
#### Build
|
#### Build
|
||||||
1. Download source code
|
1. Download source code
|
||||||
2. Install dependancys
|
2. Install dependencies
|
||||||
```
|
```
|
||||||
go get github.com/koron/go-ssdp
|
go get github.com/koron/go-ssdp
|
||||||
go get github.com/gorilla/websocket
|
go get github.com/gorilla/websocket
|
||||||
@@ -108,7 +140,7 @@ go build xteve.go
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Fork without pull request :mega:
|
## Fork without pull request :mega:
|
||||||
When creating a fork, the xTeVe GitHub account must be changed from the sorce code or the update function disabled.
|
When creating a fork, the xTeVe GitHub account must be changed from the source code or the update function disabled.
|
||||||
Future updates of the xteve-project would update your fork. :wink:
|
Future updates of the xteve-project would update your fork. :wink:
|
||||||
|
|
||||||
xteve.go - Line: 29
|
xteve.go - Line: 29
|
||||||
|
|||||||
88
changelog-beta.md
Normal file
88
changelog-beta.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#### 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)
|
||||||
|
```
|
||||||
|
#### 2.0.1.0011-beta
|
||||||
|
```diff
|
||||||
|
+ Original group title is shown in the Mapping Editor
|
||||||
|
```
|
||||||
|
##### Fixes
|
||||||
|
- incorrect original-air-date
|
||||||
|
|
||||||
|
#### 2.0.1.0010-beta
|
||||||
|
```diff
|
||||||
|
+ Set timestamp to <episode-num system="original-air-date">
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.0.0.0008-beta
|
||||||
|
##### Fixes
|
||||||
|
- Pull request #6 [Error in http/https detection] window.location.protocol return "https:", not "https://"
|
||||||
|
|
||||||
|
#### 2.0.0.0007-beta
|
||||||
|
```diff
|
||||||
|
+ Buffer HLS: Add VOD tag from M3U8
|
||||||
|
+ CLI: Add new arguments [-restore]
|
||||||
|
+ CLI: Add new arguments [-info]
|
||||||
|
```
|
||||||
|
##### Fixes
|
||||||
|
- Missing images with caching for localhost URL
|
||||||
|
|
||||||
|
|
||||||
|
#### 2.0.0.0001-beta
|
||||||
|
```diff
|
||||||
|
+ Wizard: Add HTML input placeholder (M3U, XMLTV)
|
||||||
|
+ Wizard: Alert by empty value (M3U, XMLTV)
|
||||||
|
+ Image caching: Ignore invalid image URLs
|
||||||
|
```
|
||||||
9
go.mod
Normal file
9
go.mod
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
module xteve
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gorilla/websocket v1.4.2 // indirect
|
||||||
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||||
|
github.com/koron/go-ssdp v0.0.2 // indirect
|
||||||
|
)
|
||||||
16
go.sum
Normal file
16
go.sum
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||||
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
||||||
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||||
|
github.com/koron/go-ssdp v0.0.2 h1:fL3wAoyT6hXHQlORyXUW4Q23kkQpJRgEAYcZB5BR71o=
|
||||||
|
github.com/koron/go-ssdp v0.0.2/go.mod h1:XoLfkAiA2KeZsYh4DbHxD7h3nR2AZNqVQOa+LJuqPYs=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
|
||||||
|
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
<div id="content">
|
<div id="content">
|
||||||
|
|
||||||
<form id="authentication" action="/web/" method="post">
|
<form id="authentication" action="" method="post">
|
||||||
|
|
||||||
<h5>{{.account.username.title}}:</h5>
|
<h5>{{.account.username.title}}:</h5>
|
||||||
<input id="username" type="text" name="username" placeholder="Username" value="">
|
<input id="username" type="text" name="username" placeholder="Username" value="">
|
||||||
|
|||||||
@@ -299,6 +299,8 @@ tbody {
|
|||||||
|
|
||||||
#content_table input[type=text]{
|
#content_table input[type=text]{
|
||||||
width: 80%;
|
width: 80%;
|
||||||
|
min-width: 35px;
|
||||||
|
max-width: 60px;
|
||||||
border: 0px;
|
border: 0px;
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
h1 {
|
|
||||||
color: green;
|
|
||||||
}
|
|
||||||
@@ -20,7 +20,7 @@ menuItems.push(new MainMenuItem("logout", "{{.mainMenu.item.logout}}", "logout.p
|
|||||||
var settingsCategory = new Array();
|
var settingsCategory = new Array();
|
||||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.general}}", "xteveAutoUpdate,tuner,epgSource,api"));
|
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.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.backup}}", "backup.path,backup.keep"));
|
||||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.authentication}}", "authentication.web,authentication.pms,authentication.m3u,authentication.xml,authentication.api"));
|
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.authentication}}", "authentication.web,authentication.pms,authentication.m3u,authentication.xml,authentication.api"));
|
||||||
function showPopUpElement(elm) {
|
function showPopUpElement(elm) {
|
||||||
@@ -226,7 +226,7 @@ function createSearchObj() {
|
|||||||
SEARCH_MAPPING = new Object();
|
SEARCH_MAPPING = new Object();
|
||||||
var data = SERVER["xepg"]["epgMapping"];
|
var data = SERVER["xepg"]["epgMapping"];
|
||||||
var channels = getObjKeys(data);
|
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) {
|
channels.forEach(function (id) {
|
||||||
channelKeys.forEach(function (key) {
|
channelKeys.forEach(function (key) {
|
||||||
if (key == "x-active") {
|
if (key == "x-active") {
|
||||||
@@ -239,9 +239,17 @@ function createSearchObj() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
if (key == "x-xmltv-file") {
|
||||||
|
var xmltvFile = getValueFromProviderFile(data[id][key], "xmltv", "name");
|
||||||
|
if (xmltvFile != undefined) {
|
||||||
|
SEARCH_MAPPING[id] = SEARCH_MAPPING[id] + xmltvFile + " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
SEARCH_MAPPING[id] = SEARCH_MAPPING[id] + data[id][key] + " ";
|
SEARCH_MAPPING[id] = SEARCH_MAPPING[id] + data[id][key] + " ";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ var __extends = (this && this.__extends) || (function () {
|
|||||||
var extendStatics = function (d, b) {
|
var extendStatics = function (d, b) {
|
||||||
extendStatics = Object.setPrototypeOf ||
|
extendStatics = Object.setPrototypeOf ||
|
||||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
({ __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 extendStatics(d, b);
|
||||||
};
|
};
|
||||||
return function (d, b) {
|
return function (d, b) {
|
||||||
@@ -63,6 +63,7 @@ var WizardItem = /** @class */ (function (_super) {
|
|||||||
break;
|
break;
|
||||||
case "m3u":
|
case "m3u":
|
||||||
var input = content.createInput("text", key, "");
|
var input = content.createInput("text", key, "");
|
||||||
|
input.setAttribute("placeholder", "{{.wizard.m3u.placeholder}}");
|
||||||
input.setAttribute("class", "wizard");
|
input.setAttribute("class", "wizard");
|
||||||
input.id = key;
|
input.id = key;
|
||||||
doc.appendChild(input);
|
doc.appendChild(input);
|
||||||
@@ -70,6 +71,7 @@ var WizardItem = /** @class */ (function (_super) {
|
|||||||
break;
|
break;
|
||||||
case "xmltv":
|
case "xmltv":
|
||||||
var input = content.createInput("text", key, "");
|
var input = content.createInput("text", key, "");
|
||||||
|
input.setAttribute("placeholder", "{{.wizard.xmltv.placeholder}}");
|
||||||
input.setAttribute("class", "wizard");
|
input.setAttribute("class", "wizard");
|
||||||
input.id = key;
|
input.id = key;
|
||||||
doc.appendChild(input);
|
doc.appendChild(input);
|
||||||
@@ -117,6 +119,11 @@ function saveWizard() {
|
|||||||
case "text":
|
case "text":
|
||||||
name = config[i].name;
|
name = config[i].name;
|
||||||
value = config[i].value;
|
value = config[i].value;
|
||||||
|
if (value.length == 0) {
|
||||||
|
var msg = name.toUpperCase() + ": " + "{{.alert.missingInput}}";
|
||||||
|
alert(msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
wizard[name] = value;
|
wizard[name] = value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ var __extends = (this && this.__extends) || (function () {
|
|||||||
var extendStatics = function (d, b) {
|
var extendStatics = function (d, b) {
|
||||||
extendStatics = Object.setPrototypeOf ||
|
extendStatics = Object.setPrototypeOf ||
|
||||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
({ __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 extendStatics(d, b);
|
||||||
};
|
};
|
||||||
return function (d, b) {
|
return function (d, b) {
|
||||||
@@ -127,7 +127,7 @@ var Content = /** @class */ (function () {
|
|||||||
var cell = new Cell();
|
var cell = new Cell();
|
||||||
cell.child = true;
|
cell.child = true;
|
||||||
cell.childType = "P";
|
cell.childType = "P";
|
||||||
if (SERVER["settings"]["buffer"] == true) {
|
if (SERVER["settings"]["buffer"] != "-") {
|
||||||
cell.value = data[key]["tuner"];
|
cell.value = data[key]["tuner"];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -583,7 +583,7 @@ var ShowContent = /** @class */ (function (_super) {
|
|||||||
return;
|
return;
|
||||||
break;
|
break;
|
||||||
case "log":
|
case "log":
|
||||||
var input = this.createInput("button", menuKey, "{{.button.resetlogs}}");
|
var input = this.createInput("button", menuKey, "{{.button.resetLogs}}");
|
||||||
input.setAttribute("onclick", 'javascript: resetLogs();');
|
input.setAttribute("onclick", 'javascript: resetLogs();');
|
||||||
interaction.appendChild(input);
|
interaction.appendChild(input);
|
||||||
var wrapper = document.createElement("DIV");
|
var wrapper = document.createElement("DIV");
|
||||||
@@ -901,7 +901,7 @@ function openPopUp(dataType, element) {
|
|||||||
input.setAttribute("placeholder", "{{.playlist.fileM3U.placeholder}}");
|
input.setAttribute("placeholder", "{{.playlist.fileM3U.placeholder}}");
|
||||||
content.appendRow("{{.playlist.fileM3U.title}}", input);
|
content.appendRow("{{.playlist.fileM3U.title}}", input);
|
||||||
// Tuner
|
// Tuner
|
||||||
if (SERVER["settings"]["buffer"] == true) {
|
if (SERVER["settings"]["buffer"] != "-") {
|
||||||
var text = new Array();
|
var text = new Array();
|
||||||
var values = new Array();
|
var values = new Array();
|
||||||
for (var i = 1; i <= 100; i++) {
|
for (var i = 1; i <= 100; i++) {
|
||||||
@@ -971,7 +971,7 @@ function openPopUp(dataType, element) {
|
|||||||
input.setAttribute("placeholder", "{{.playlist.fileHDHR.placeholder}}");
|
input.setAttribute("placeholder", "{{.playlist.fileHDHR.placeholder}}");
|
||||||
content.appendRow("{{.playlist.fileHDHR.title}}", input);
|
content.appendRow("{{.playlist.fileHDHR.title}}", input);
|
||||||
// Tuner
|
// Tuner
|
||||||
if (SERVER["settings"]["buffer"] == true) {
|
if (SERVER["settings"]["buffer"] != "-") {
|
||||||
var text = new Array();
|
var text = new Array();
|
||||||
var values = new Array();
|
var values = new Array();
|
||||||
for (var i = 1; i <= 100; i++) {
|
for (var i = 1; i <= 100; i++) {
|
||||||
@@ -1252,6 +1252,13 @@ function openPopUp(dataType, element) {
|
|||||||
input.setAttribute("readonly", "true");
|
input.setAttribute("readonly", "true");
|
||||||
}
|
}
|
||||||
content.appendRow("{{.mapping.channelName.title}}", input);
|
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
|
// Aktualisierung des Kanalnamens
|
||||||
if (data.hasOwnProperty("_uuid.key")) {
|
if (data.hasOwnProperty("_uuid.key")) {
|
||||||
if (data["_uuid.key"] != "") {
|
if (data["_uuid.key"] != "") {
|
||||||
@@ -1278,7 +1285,7 @@ function openPopUp(dataType, element) {
|
|||||||
// Erweitern der EPG Kategorie
|
// Erweitern der EPG Kategorie
|
||||||
var dbKey = "x-category";
|
var dbKey = "x-category";
|
||||||
var text = ["-", "Kids (Emby only)", "News", "Movie", "Series", "Sports"];
|
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);
|
var select = content.createSelect(text, values, data[dbKey], dbKey);
|
||||||
select.setAttribute("onchange", "javascript: this.className = 'changed'");
|
select.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
content.appendRow("{{.mapping.epgCategory.title}}", select);
|
content.appendRow("{{.mapping.epgCategory.title}}", select);
|
||||||
@@ -1287,6 +1294,9 @@ function openPopUp(dataType, element) {
|
|||||||
var input = content.createInput("text", dbKey, data[dbKey]);
|
var input = content.createInput("text", dbKey, data[dbKey]);
|
||||||
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
input.setAttribute("onchange", "javascript: this.className = 'changed'");
|
||||||
content.appendRow("{{.mapping.m3uGroupTitle.title}}", input);
|
content.appendRow("{{.mapping.m3uGroupTitle.title}}", input);
|
||||||
|
if (data["group-title"] != undefined) {
|
||||||
|
content.description(data["group-title"]);
|
||||||
|
}
|
||||||
// XMLTV Datei
|
// XMLTV Datei
|
||||||
var dbKey = "x-xmltv-file";
|
var dbKey = "x-xmltv-file";
|
||||||
var xmlFile = data[dbKey];
|
var xmlFile = data[dbKey];
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ var Server = /** @class */ (function () {
|
|||||||
case "http:":
|
case "http:":
|
||||||
this.protocol = "ws://";
|
this.protocol = "ws://";
|
||||||
break;
|
break;
|
||||||
case "https://":
|
case "https:":
|
||||||
this.protocol = "wss://";
|
this.protocol = "wss://";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ var __extends = (this && this.__extends) || (function () {
|
|||||||
var extendStatics = function (d, b) {
|
var extendStatics = function (d, b) {
|
||||||
extendStatics = Object.setPrototypeOf ||
|
extendStatics = Object.setPrototypeOf ||
|
||||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
({ __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 extendStatics(d, b);
|
||||||
};
|
};
|
||||||
return function (d, b) {
|
return function (d, b) {
|
||||||
@@ -85,6 +85,50 @@ var SettingsCategory = /** @class */ (function () {
|
|||||||
setting.appendChild(tdLeft);
|
setting.appendChild(tdLeft);
|
||||||
setting.appendChild(tdRight);
|
setting.appendChild(tdRight);
|
||||||
break;
|
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
|
// Checkboxen
|
||||||
case "authentication.web":
|
case "authentication.web":
|
||||||
var tdLeft = document.createElement("TD");
|
var tdLeft = document.createElement("TD");
|
||||||
@@ -185,17 +229,6 @@ var SettingsCategory = /** @class */ (function () {
|
|||||||
setting.appendChild(tdLeft);
|
setting.appendChild(tdLeft);
|
||||||
setting.appendChild(tdRight);
|
setting.appendChild(tdRight);
|
||||||
break;
|
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":
|
case "api":
|
||||||
var tdLeft = document.createElement("TD");
|
var tdLeft = document.createElement("TD");
|
||||||
tdLeft.innerHTML = "{{.settings.api.title}}" + ":";
|
tdLeft.innerHTML = "{{.settings.api.title}}" + ":";
|
||||||
@@ -260,6 +293,29 @@ var SettingsCategory = /** @class */ (function () {
|
|||||||
setting.appendChild(tdLeft);
|
setting.appendChild(tdLeft);
|
||||||
setting.appendChild(tdRight);
|
setting.appendChild(tdRight);
|
||||||
break;
|
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;
|
return setting;
|
||||||
};
|
};
|
||||||
@@ -308,6 +364,18 @@ var SettingsCategory = /** @class */ (function () {
|
|||||||
case "user.agent":
|
case "user.agent":
|
||||||
text = "{{.settings.userAgent.description}}";
|
text = "{{.settings.userAgent.description}}";
|
||||||
break;
|
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":
|
case "epgSource":
|
||||||
text = "{{.settings.epgSource.description}}";
|
text = "{{.settings.epgSource.description}}";
|
||||||
break;
|
break;
|
||||||
@@ -329,6 +397,9 @@ var SettingsCategory = /** @class */ (function () {
|
|||||||
case "xepg.replace.missing.images":
|
case "xepg.replace.missing.images":
|
||||||
text = "{{.settings.replaceEmptyImages.description}}";
|
text = "{{.settings.replaceEmptyImages.description}}";
|
||||||
break;
|
break;
|
||||||
|
case "udpxy":
|
||||||
|
text = "{{.settings.udpxy.description}}";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
text = "";
|
text = "";
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"mainMenu": {
|
"mainMenu":
|
||||||
"item":{
|
{
|
||||||
|
"item":
|
||||||
|
{
|
||||||
"playlist": "Playlist",
|
"playlist": "Playlist",
|
||||||
"pmsID": "PMS ID",
|
"pmsID": "PMS ID",
|
||||||
"filter": "Filter",
|
"filter": "Filter",
|
||||||
@@ -11,7 +13,8 @@
|
|||||||
"log": "Log",
|
"log": "Log",
|
||||||
"logout": "Logout"
|
"logout": "Logout"
|
||||||
},
|
},
|
||||||
"headline": {
|
"headline":
|
||||||
|
{
|
||||||
"playlist": "Local or remote playlists",
|
"playlist": "Local or remote playlists",
|
||||||
"filter": "Filter playlist",
|
"filter": "Filter playlist",
|
||||||
"xmltv": "Local or remote XMLTV files",
|
"xmltv": "Local or remote XMLTV files",
|
||||||
@@ -22,14 +25,18 @@
|
|||||||
"logout": "Logout"
|
"logout": "Logout"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"confirm":{
|
"confirm":
|
||||||
|
{
|
||||||
"restore": "All data will be replaced with those from the backup. Should the files be restored?"
|
"restore": "All data will be replaced with those from the backup. Should the files be restored?"
|
||||||
},
|
},
|
||||||
"alert": {
|
"alert":
|
||||||
|
{
|
||||||
"fileLoadingError": "File couldn't be loaded",
|
"fileLoadingError": "File couldn't be loaded",
|
||||||
"invalidChannelNumber": "Invalid channel number"
|
"invalidChannelNumber": "Invalid channel number",
|
||||||
|
"missingInput": "Missing input"
|
||||||
},
|
},
|
||||||
"button":{
|
"button":
|
||||||
|
{
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
"backup": "Backup",
|
"backup": "Backup",
|
||||||
"bulkEdit": "Bulk Edit",
|
"bulkEdit": "Bulk Edit",
|
||||||
@@ -44,61 +51,73 @@
|
|||||||
"search": "Search",
|
"search": "Search",
|
||||||
"update": "Update",
|
"update": "Update",
|
||||||
"craeteAccount": "Create Account",
|
"craeteAccount": "Create Account",
|
||||||
"resetlogs": "Reset Logs",
|
"resetLogs": "Reset Logs",
|
||||||
"uploadLogo": "Upload Logo"
|
"uploadLogo": "Upload Logo"
|
||||||
},
|
},
|
||||||
"filter": {
|
"filter":
|
||||||
"table": {
|
{
|
||||||
|
"table":
|
||||||
|
{
|
||||||
"name": "Filter Name",
|
"name": "Filter Name",
|
||||||
"type": "Filter Type",
|
"type": "Filter Type",
|
||||||
"filter": "Filter"
|
"filter": "Filter"
|
||||||
},
|
},
|
||||||
"custom": "Custom",
|
"custom": "Custom",
|
||||||
"group": "Group",
|
"group": "Group",
|
||||||
"name": {
|
"name":
|
||||||
|
{
|
||||||
"title": "Filter Name",
|
"title": "Filter Name",
|
||||||
"placeholder": "Filter name",
|
"placeholder": "Filter name",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"description": {
|
"description":
|
||||||
|
{
|
||||||
"title": "Description",
|
"title": "Description",
|
||||||
"placeholder": "Description",
|
"placeholder": "Description",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"type": {
|
"type":
|
||||||
|
{
|
||||||
"title": "Type",
|
"title": "Type",
|
||||||
"groupTitle": "Group Title",
|
"groupTitle": "Group Title",
|
||||||
"customFilter": "Custom Filter"
|
"customFilter": "Custom Filter"
|
||||||
},
|
},
|
||||||
"caseSensitive": {
|
"caseSensitive":
|
||||||
|
{
|
||||||
"title": "Case Sensitive",
|
"title": "Case Sensitive",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"filterRule": {
|
"filterRule":
|
||||||
|
{
|
||||||
"title": "Filter Rule",
|
"title": "Filter Rule",
|
||||||
"placeholder": "Sport {HD} !{ES,IT}",
|
"placeholder": "Sport {HD} !{ES,IT}",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"filterGroup": {
|
"filterGroup":
|
||||||
|
{
|
||||||
"title": "Group Title",
|
"title": "Group Title",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"description": "Select a M3U group. (Counter)<br>Changing the group title in the M3U invalidates the filter."
|
"description": "Select a M3U group. (Counter)<br>Changing the group title in the M3U invalidates the filter."
|
||||||
},
|
},
|
||||||
"include": {
|
"include":
|
||||||
|
{
|
||||||
"title": "Include",
|
"title": "Include",
|
||||||
"placeholder": "FHD,UHD",
|
"placeholder": "FHD,UHD",
|
||||||
"description": "Channel name must include.<br>(Comma separated) Comma means or"
|
"description": "Channel name must include.<br>(Comma separated) Comma means or"
|
||||||
},
|
},
|
||||||
"exclude": {
|
"exclude":
|
||||||
|
{
|
||||||
"title": "Exclude",
|
"title": "Exclude",
|
||||||
"placeholder": "ES,IT",
|
"placeholder": "ES,IT",
|
||||||
"description": "Channel name must not contain.<br>(Comma separated) Comma means or"
|
"description": "Channel name must not contain.<br>(Comma separated) Comma means or"
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
"playlist": {
|
"playlist":
|
||||||
"table": {
|
{
|
||||||
|
"table":
|
||||||
|
{
|
||||||
"playlist": "Playlist",
|
"playlist": "Playlist",
|
||||||
"tuner": "Tuner",
|
"tuner": "Tuner",
|
||||||
"lastUpdate": "Last Update",
|
"lastUpdate": "Last Update",
|
||||||
@@ -109,68 +128,82 @@
|
|||||||
"tvgID": "tvg-id",
|
"tvgID": "tvg-id",
|
||||||
"uniqueID": "Unique ID"
|
"uniqueID": "Unique ID"
|
||||||
},
|
},
|
||||||
"playlistType": {
|
"playlistType":
|
||||||
|
{
|
||||||
"title": "Playlist type",
|
"title": "Playlist type",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"type": {
|
"type":
|
||||||
|
{
|
||||||
"title": "Type",
|
"title": "Type",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"name": {
|
"name":
|
||||||
|
{
|
||||||
"title": "Name",
|
"title": "Name",
|
||||||
"placeholder": "Playlist name",
|
"placeholder": "Playlist name",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"description": {
|
"description":
|
||||||
|
{
|
||||||
"title": "Description",
|
"title": "Description",
|
||||||
"placeholder": "Description",
|
"placeholder": "Description",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"fileM3U": {
|
"fileM3U":
|
||||||
|
{
|
||||||
"title": "M3U File",
|
"title": "M3U File",
|
||||||
"placeholder": "File path or URL of the M3U",
|
"placeholder": "File path or URL of the M3U",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"fileHDHR": {
|
"fileHDHR":
|
||||||
|
{
|
||||||
"title": "HDHomeRun IP",
|
"title": "HDHomeRun IP",
|
||||||
"placeholder": "IP address and port (192.168.1.10:5004)",
|
"placeholder": "IP address and port (192.168.1.10:5004)",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"tuner": {
|
"tuner":
|
||||||
|
{
|
||||||
"title": "Tuner / Streams",
|
"title": "Tuner / Streams",
|
||||||
"placeholder": "",
|
"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."
|
"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": {
|
"xmltv":
|
||||||
"table": {
|
{
|
||||||
|
"table":
|
||||||
|
{
|
||||||
"guide": "Guide",
|
"guide": "Guide",
|
||||||
"lastUpdate": "Last Update",
|
"lastUpdate": "Last Update",
|
||||||
"availability": "Availability",
|
"availability": "Availability",
|
||||||
"channels": "Channels",
|
"channels": "Channels",
|
||||||
"programs": "Programs"
|
"programs": "Programs"
|
||||||
},
|
},
|
||||||
"name": {
|
"name":
|
||||||
|
{
|
||||||
"title": "Name",
|
"title": "Name",
|
||||||
"placeholder": "Guide name",
|
"placeholder": "Guide name",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"description": {
|
"description":
|
||||||
|
{
|
||||||
"title": "Description",
|
"title": "Description",
|
||||||
"placeholder": "Description",
|
"placeholder": "Description",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"fileXMLTV": {
|
"fileXMLTV":
|
||||||
|
{
|
||||||
"title": "XMLTV File",
|
"title": "XMLTV File",
|
||||||
"placeholder": "File path or URL of the XMLTV",
|
"placeholder": "File path or URL of the XMLTV",
|
||||||
"description": ""
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mapping": {
|
"mapping":
|
||||||
"table": {
|
{
|
||||||
|
"table":
|
||||||
|
{
|
||||||
"chNo": "Ch. No.",
|
"chNo": "Ch. No.",
|
||||||
"logo": "Logo",
|
"logo": "Logo",
|
||||||
"channelName": "Channel Name",
|
"channelName": "Channel Name",
|
||||||
@@ -179,54 +212,71 @@
|
|||||||
"xmltvFile": "XMLTV File",
|
"xmltvFile": "XMLTV File",
|
||||||
"xmltvID": "XMLTV ID"
|
"xmltvID": "XMLTV ID"
|
||||||
},
|
},
|
||||||
"active": {
|
"active":
|
||||||
|
{
|
||||||
"title": "Active",
|
"title": "Active",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"channelName": {
|
"channelName":
|
||||||
|
{
|
||||||
"title": "Channel Name",
|
"title": "Channel Name",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"updateChannelName": {
|
"description":
|
||||||
|
{
|
||||||
|
"title": "Channel Description",
|
||||||
|
"placeholder": "Used by the Dummy as an XML description",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"updateChannelName":
|
||||||
|
{
|
||||||
"title": "Update Channel Name",
|
"title": "Update Channel Name",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"channelLogo": {
|
"channelLogo":
|
||||||
|
{
|
||||||
"title": "Logo URL",
|
"title": "Logo URL",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"updateChannelLogo": {
|
"updateChannelLogo":
|
||||||
|
{
|
||||||
"title": "Update Channel Logo",
|
"title": "Update Channel Logo",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"epgCategory": {
|
"epgCategory":
|
||||||
|
{
|
||||||
"title": "EPG Category",
|
"title": "EPG Category",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"m3uGroupTitle": {
|
"m3uGroupTitle":
|
||||||
|
{
|
||||||
"title": "Group Title (xteve.m3u)",
|
"title": "Group Title (xteve.m3u)",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"xmltvFile": {
|
"xmltvFile":
|
||||||
|
{
|
||||||
"title": "XMLTV File",
|
"title": "XMLTV File",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"xmltvChannel": {
|
"xmltvChannel":
|
||||||
|
{
|
||||||
"title": "XMLTV Channel",
|
"title": "XMLTV Channel",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"users": {
|
"users":
|
||||||
"table": {
|
{
|
||||||
|
"table":
|
||||||
|
{
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"web": "WEB",
|
"web": "WEB",
|
||||||
@@ -235,183 +285,262 @@
|
|||||||
"xml": "XML",
|
"xml": "XML",
|
||||||
"api": "API"
|
"api": "API"
|
||||||
},
|
},
|
||||||
"username": {
|
"username":
|
||||||
|
{
|
||||||
"title": "Username",
|
"title": "Username",
|
||||||
"placeholder": "Username",
|
"placeholder": "Username",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"password": {
|
"password":
|
||||||
|
{
|
||||||
"title": "Password",
|
"title": "Password",
|
||||||
"placeholder": "Passoword",
|
"placeholder": "Password",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"confirm": {
|
"confirm":
|
||||||
|
{
|
||||||
"title": "Confirm",
|
"title": "Confirm",
|
||||||
"placeholder": "Password confirm",
|
"placeholder": "Password confirm",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"web": {
|
"web":
|
||||||
|
{
|
||||||
"title": "Web Access",
|
"title": "Web Access",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"pms": {
|
"pms":
|
||||||
|
{
|
||||||
"title": "PMS Access",
|
"title": "PMS Access",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"m3u": {
|
"m3u":
|
||||||
|
{
|
||||||
"title": "M3U Access",
|
"title": "M3U Access",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"xml": {
|
"xml":
|
||||||
|
{
|
||||||
"title": "XML Access",
|
"title": "XML Access",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"api": {
|
"api":
|
||||||
|
{
|
||||||
"title": "API Access",
|
"title": "API Access",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings":
|
||||||
"category": {
|
{
|
||||||
|
"category":
|
||||||
|
{
|
||||||
"general": "General",
|
"general": "General",
|
||||||
"files": "Files",
|
"files": "Files",
|
||||||
"streaming": "Streaming",
|
"streaming": "Streaming",
|
||||||
"backup": "Backup",
|
"backup": "Backup",
|
||||||
"authentication": "Authentication"
|
"authentication": "Authentication"
|
||||||
},
|
},
|
||||||
"update": {
|
"update":
|
||||||
|
{
|
||||||
"title": "Schedule for updating (Playlist, XMLTV, Backup)",
|
"title": "Schedule for updating (Playlist, XMLTV, Backup)",
|
||||||
"placeholder": "0000,1000,2000",
|
"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",
|
"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>"
|
"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",
|
"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)"
|
"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",
|
"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."
|
"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",
|
"title": "Updates all files at startup",
|
||||||
"description": "Updates all playlists, tuner and XMLTV files at startup."
|
"description": "Updates all playlists, tuner and XMLTV files at startup."
|
||||||
},
|
},
|
||||||
"cacheImages": {
|
"cacheImages":
|
||||||
"title": "Image caching",
|
{
|
||||||
|
"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."
|
"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",
|
"title": "Replace missing program images",
|
||||||
"description": "If the poster in the XMLTV program is missing, the channel logo will be used."
|
"description": "If the poster in the XMLTV program is missing, the channel logo will be used."
|
||||||
},
|
},
|
||||||
"xteveAutoUpdate": {
|
"xteveAutoUpdate":
|
||||||
|
{
|
||||||
"title": "Automatic update of xTeVe",
|
"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."
|
"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",
|
"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"
|
"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",
|
"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."
|
"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",
|
"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.",
|
"description": "The xTeVe buffer waits until new client connections are established. Helpful for fast channel switching. Value in milliseconds.",
|
||||||
"placeholder": "100"
|
"placeholder": "100"
|
||||||
},
|
},
|
||||||
"userAgent": {
|
"userAgent":
|
||||||
"title": "User agent",
|
{
|
||||||
"description": "User Agent for HTTP requests",
|
"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"
|
"placeholder": "xTeVe"
|
||||||
},
|
},
|
||||||
"backupPath": {
|
"backupPath":
|
||||||
|
{
|
||||||
"title": "Location for automatic backups",
|
"title": "Location for automatic backups",
|
||||||
"placeholder": "/mnt/data/backup/xteve/",
|
"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."
|
"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",
|
"title": "Location for the temporary files",
|
||||||
"placeholder": "/tmp/xteve/",
|
"placeholder": "/tmp/xteve/",
|
||||||
"description": "Location for the buffer files."
|
"description": "Location for the buffer files."
|
||||||
},
|
},
|
||||||
"backupKeep": {
|
"backupKeep":
|
||||||
|
{
|
||||||
"title": "Number of backups to keep",
|
"title": "Number of backups to keep",
|
||||||
"description": "Number of backups to keep. Older backups are automatically deleted."
|
"description": "Number of backups to keep. Older backups are automatically deleted."
|
||||||
},
|
},
|
||||||
"authenticationWEB": {
|
"authenticationWEB":
|
||||||
|
{
|
||||||
"title": "WEB Authentication",
|
"title": "WEB Authentication",
|
||||||
"description": "Access to the web interface only possible with credentials."
|
"description": "Access to the web interface only possible with credentials."
|
||||||
},
|
},
|
||||||
"authenticationPMS": {
|
"authenticationPMS":
|
||||||
|
{
|
||||||
"title": "PMS Authentication",
|
"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."
|
"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",
|
"title": "M3U Authentication",
|
||||||
"description": "Downloading the xteve.m3u file via an HTTP request is only possible with authentication."
|
"description": "Downloading the xteve.m3u file via an HTTP request is only possible with authentication."
|
||||||
},
|
},
|
||||||
"authenticationXML": {
|
"authenticationXML":
|
||||||
|
{
|
||||||
"title": "XML Authentication",
|
"title": "XML Authentication",
|
||||||
"description": "Downloading the xteve.xml file via an HTTP request is only possible with authentication"
|
"description": "Downloading the xteve.xml file via an HTTP request is only possible with authentication"
|
||||||
},
|
},
|
||||||
"authenticationAPI": {
|
"authenticationAPI":
|
||||||
|
{
|
||||||
"title": "API Authentication",
|
"title": "API Authentication",
|
||||||
"description": "Access to the API interface is only possible with authentication."
|
"description": "Access to the API interface is only possible with authentication."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"wizard": {
|
"wizard":
|
||||||
"epgSource": {
|
{
|
||||||
|
"epgSource":
|
||||||
|
{
|
||||||
"title": "EPG Source",
|
"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)"
|
"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",
|
"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."
|
"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",
|
"title": "M3U Playlist",
|
||||||
|
"placeholder": "File path or URL of the M3U",
|
||||||
"description": "Local or remote playlists"
|
"description": "Local or remote playlists"
|
||||||
},
|
},
|
||||||
"xmltv": {
|
"xmltv":
|
||||||
|
{
|
||||||
"title": "XMLTV File",
|
"title": "XMLTV File",
|
||||||
|
"placeholder": "File path or URL of the XMLTV",
|
||||||
"description": "Local or remote XMLTV file"
|
"description": "Local or remote XMLTV file"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"login": {
|
"login":
|
||||||
|
{
|
||||||
"failed": "User authentication failed",
|
"failed": "User authentication failed",
|
||||||
"headline": "Login",
|
"headline": "Login",
|
||||||
"username": {
|
"username":
|
||||||
|
{
|
||||||
"title": "Username",
|
"title": "Username",
|
||||||
"placeholder": "Username"
|
"placeholder": "Username"
|
||||||
},
|
},
|
||||||
"password": {
|
"password":
|
||||||
|
{
|
||||||
"title": "Password",
|
"title": "Password",
|
||||||
"placeholder": "Password"
|
"placeholder": "Password"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"account": {
|
"account":
|
||||||
|
{
|
||||||
"failed": "Password does not match",
|
"failed": "Password does not match",
|
||||||
"headline": "Create user account",
|
"headline": "Create user account",
|
||||||
"username": {
|
"username":
|
||||||
|
{
|
||||||
"title": "Username",
|
"title": "Username",
|
||||||
"placeholder": "Username"
|
"placeholder": "Username"
|
||||||
},
|
},
|
||||||
"password": {
|
"password":
|
||||||
|
{
|
||||||
"title": "Password",
|
"title": "Password",
|
||||||
"placeholder": "Password"
|
"placeholder": "Password"
|
||||||
},
|
},
|
||||||
"confirm": {
|
"confirm":
|
||||||
|
{
|
||||||
"title": "Confirm",
|
"title": "Confirm",
|
||||||
"placeholder": "Confirm"
|
"placeholder": "Confirm"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
<div id="content">
|
<div id="content">
|
||||||
|
|
||||||
<form id="authentication" action="/web/" method="post">
|
<form id="authentication" action="" method="post">
|
||||||
|
|
||||||
<h5>{{.login.username.title}}:</h5>
|
<h5>{{.login.username.title}}:</h5>
|
||||||
<input id="username" type="text" name="username" placeholder="Username" value="">
|
<input id="username" type="text" name="username" placeholder="Username" value="">
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"../src/internal/authentication"
|
"xteve/src/internal/authentication"
|
||||||
)
|
)
|
||||||
|
|
||||||
func activatedSystemAuthentication() (err error) {
|
func activatedSystemAuthentication() (err error) {
|
||||||
|
|||||||
109
src/backup.go
109
src/backup.go
@@ -2,6 +2,7 @@ package src
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
b64 "encoding/base64"
|
b64 "encoding/base64"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@@ -124,35 +125,44 @@ func xteveBackup() (archiv string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func xteveRestore(input string) (newWebURL string, err error) {
|
func xteveRestore(archive string) (newWebURL string, err error) {
|
||||||
|
|
||||||
var newPort, oldPort string
|
var newPort, oldPort, backupVersion, tmpRestore string
|
||||||
|
|
||||||
// Base64 Json String in base64 umwandeln
|
tmpRestore = System.Folder.Temp + "restore" + string(os.PathSeparator)
|
||||||
b64data := input[strings.IndexByte(input, ',')+1:]
|
|
||||||
|
|
||||||
// Base64 in bytes umwandeln und speichern
|
|
||||||
sDec, err := b64.StdEncoding.DecodeString(b64data)
|
|
||||||
|
|
||||||
|
err = checkFolder(tmpRestore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var archive = System.Folder.Temp + "restore.zip"
|
// Zip Archiv in tmp entpacken
|
||||||
|
err = extractZIP(archive, tmpRestore)
|
||||||
err = writeByteToFile(archive, sDec)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zip Archiv entpacken
|
// Neue Config laden um den Port und die Version zu überprüfen
|
||||||
|
newConfig, err := loadJSONFileToMap(tmpRestore + "settings.json")
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
backupVersion = newConfig["version"].(string)
|
||||||
|
if backupVersion < System.Compatibility {
|
||||||
|
err = errors.New(getErrMsg(1013))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zip Archiv in den Config Ordner entpacken
|
||||||
err = extractZIP(archive, System.Folder.Config)
|
err = extractZIP(archive, System.Folder.Config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Neue Config laden um den Port zu überprüfen
|
// Neue Config laden um den Port und die Version zu überprüfen
|
||||||
newConfig, err := loadJSONFileToMap(System.Folder.Config + "settings.json")
|
newConfig, err = loadJSONFileToMap(System.Folder.Config + "settings.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ShowError(err, 0)
|
ShowError(err, 0)
|
||||||
return
|
return
|
||||||
@@ -187,5 +197,78 @@ func xteveRestore(input string) (newWebURL string, err error) {
|
|||||||
var url = System.URLBase + "/web/"
|
var url = System.URLBase + "/web/"
|
||||||
newWebURL = strings.Replace(url, ":"+oldPort, ":"+newPort, 1)
|
newWebURL = strings.Replace(url, ":"+oldPort, ":"+newPort, 1)
|
||||||
|
|
||||||
|
os.RemoveAll(tmpRestore)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func xteveRestoreFromWeb(input string) (newWebURL string, err error) {
|
||||||
|
|
||||||
|
// Base64 Json String in base64 umwandeln
|
||||||
|
b64data := input[strings.IndexByte(input, ',')+1:]
|
||||||
|
|
||||||
|
// Base64 in bytes umwandeln und speichern
|
||||||
|
sDec, err := b64.StdEncoding.DecodeString(b64data)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var archive = System.Folder.Temp + "restore.zip"
|
||||||
|
|
||||||
|
err = writeByteToFile(archive, sDec)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newWebURL, err = xteveRestore(archive)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// XteveRestoreFromCLI : Wiederherstellung über die Kommandozeile
|
||||||
|
func XteveRestoreFromCLI(archive string) (err error) {
|
||||||
|
|
||||||
|
var confirm string
|
||||||
|
|
||||||
|
println()
|
||||||
|
showInfo(fmt.Sprintf("Version:%s Build: %s", System.Version, System.Build))
|
||||||
|
showInfo(fmt.Sprintf("Backup File:%s", archive))
|
||||||
|
showInfo(fmt.Sprintf("System Folder:%s", getPlatformPath(System.Folder.Config)))
|
||||||
|
println()
|
||||||
|
|
||||||
|
fmt.Print("All data will be replaced with those from the backup. Should the files be restored? [yes|no]:")
|
||||||
|
|
||||||
|
fmt.Scanln(&confirm)
|
||||||
|
|
||||||
|
switch strings.ToLower(confirm) {
|
||||||
|
|
||||||
|
case "yes":
|
||||||
|
break
|
||||||
|
|
||||||
|
case "no":
|
||||||
|
return
|
||||||
|
|
||||||
|
default:
|
||||||
|
fmt.Println("Invalid input")
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(System.Folder.Config) > 0 {
|
||||||
|
|
||||||
|
err = checkFilePermission(System.Folder.Config)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = xteveRestore(archive)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
showHighlight(fmt.Sprintf("Restor:Backup was successfully restored. %s can now be started normally", System.Name))
|
||||||
|
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
389
src/buffer.go
389
src/buffer.go
@@ -6,6 +6,8 @@ package src
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -13,6 +15,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -205,7 +208,17 @@ func bufferingStream(playlistID, streamingURL, channelName string, w http.Respon
|
|||||||
playlist.Streams[streamID] = stream
|
playlist.Streams[streamID] = stream
|
||||||
BufferInformation.Store(playlistID, playlist)
|
BufferInformation.Store(playlistID, playlist)
|
||||||
|
|
||||||
go connectToStreamingServer(streamID, playlist)
|
switch Settings.Buffer {
|
||||||
|
|
||||||
|
case "xteve":
|
||||||
|
go connectToStreamingServer(streamID, playlistID)
|
||||||
|
case "ffmpeg", "vlc":
|
||||||
|
go thirdPartyBuffer(streamID, playlistID)
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
showInfo(fmt.Sprintf("Streaming Status:Playlist: %s - Tuner: %d / %d", playlist.PlaylistName, len(playlist.Streams), playlist.Tuner))
|
showInfo(fmt.Sprintf("Streaming Status:Playlist: %s - Tuner: %d / %d", playlist.PlaylistName, len(playlist.Streams), playlist.Tuner))
|
||||||
|
|
||||||
@@ -219,6 +232,10 @@ func bufferingStream(playlistID, streamingURL, channelName string, w http.Respon
|
|||||||
|
|
||||||
for { // Loop 1: Warten bis das erste Segment durch den Buffer heruntergeladen wurde
|
for { // Loop 1: Warten bis das erste Segment durch den Buffer heruntergeladen wurde
|
||||||
|
|
||||||
|
if p, ok := BufferInformation.Load(playlistID); ok {
|
||||||
|
|
||||||
|
var playlist = p.(Playlist)
|
||||||
|
|
||||||
if stream, ok := playlist.Streams[streamID]; ok {
|
if stream, ok := playlist.Streams[streamID]; ok {
|
||||||
|
|
||||||
if stream.Status == false {
|
if stream.Status == false {
|
||||||
@@ -244,7 +261,9 @@ func bufferingStream(playlistID, streamingURL, channelName string, w http.Respon
|
|||||||
var oldSegments []string
|
var oldSegments []string
|
||||||
|
|
||||||
for { // Loop 2: Temporäre Datein sind vorhanden, Daten können zum Client gesendet werden
|
for { // Loop 2: Temporäre Datein sind vorhanden, Daten können zum Client gesendet werden
|
||||||
|
|
||||||
// HTTP Clientverbindung überwachen
|
// HTTP Clientverbindung überwachen
|
||||||
|
|
||||||
cn, ok := w.(http.CloseNotifier)
|
cn, ok := w.(http.CloseNotifier)
|
||||||
if ok {
|
if ok {
|
||||||
|
|
||||||
@@ -377,6 +396,8 @@ func bufferingStream(playlistID, streamingURL, channelName string, w http.Respon
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // Ende BufferInformation
|
||||||
|
|
||||||
} // Ende Loop 1
|
} // Ende Loop 1
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -394,7 +415,7 @@ func getTmpFiles(stream *ThisStream) (tmpFiles []string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(files) > 1 {
|
if len(files) > 2 {
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
|
|
||||||
@@ -430,6 +451,9 @@ func getTmpFiles(stream *ThisStream) (tmpFiles []string) {
|
|||||||
|
|
||||||
func killClientConnection(streamID int, playlistID string, force bool) {
|
func killClientConnection(streamID int, playlistID string, force bool) {
|
||||||
|
|
||||||
|
Lock.Lock()
|
||||||
|
defer Lock.Unlock()
|
||||||
|
|
||||||
if p, ok := BufferInformation.Load(playlistID); ok {
|
if p, ok := BufferInformation.Load(playlistID); ok {
|
||||||
|
|
||||||
var playlist = p.(Playlist)
|
var playlist = p.(Playlist)
|
||||||
@@ -454,6 +478,7 @@ func killClientConnection(streamID int, playlistID string, force bool) {
|
|||||||
if clients.Connection <= 0 {
|
if clients.Connection <= 0 {
|
||||||
BufferClients.Delete(playlistID + stream.MD5)
|
BufferClients.Delete(playlistID + stream.MD5)
|
||||||
delete(playlist.Streams, streamID)
|
delete(playlist.Streams, streamID)
|
||||||
|
delete(playlist.Clients, streamID)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -473,6 +498,8 @@ func killClientConnection(streamID int, playlistID string, force bool) {
|
|||||||
func clientConnection(stream ThisStream) (status bool) {
|
func clientConnection(stream ThisStream) (status bool) {
|
||||||
|
|
||||||
status = true
|
status = true
|
||||||
|
Lock.Lock()
|
||||||
|
defer Lock.Unlock()
|
||||||
|
|
||||||
if _, ok := BufferClients.Load(stream.PlaylistID + stream.MD5); !ok {
|
if _, ok := BufferClients.Load(stream.PlaylistID + stream.MD5); !ok {
|
||||||
|
|
||||||
@@ -507,7 +534,11 @@ func clientConnection(stream ThisStream) (status bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func connectToStreamingServer(streamID int, playlist Playlist) {
|
func connectToStreamingServer(streamID int, playlistID string) {
|
||||||
|
|
||||||
|
if p, ok := BufferInformation.Load(playlistID); ok {
|
||||||
|
|
||||||
|
var playlist = p.(Playlist)
|
||||||
|
|
||||||
var timeOut = 0
|
var timeOut = 0
|
||||||
var debug string
|
var debug string
|
||||||
@@ -516,6 +547,9 @@ func connectToStreamingServer(streamID int, playlist Playlist) {
|
|||||||
var m3u8Segments []string
|
var m3u8Segments []string
|
||||||
var bandwidth BandwidthCalculation
|
var bandwidth BandwidthCalculation
|
||||||
var networkBandwidth = Settings.M3U8AdaptiveBandwidthMBPS * 1e+6
|
var networkBandwidth = Settings.M3U8AdaptiveBandwidthMBPS * 1e+6
|
||||||
|
// Größe des Buffers
|
||||||
|
var bufferSize = Settings.BufferSize
|
||||||
|
var buffer = make([]byte, 1024*bufferSize*2)
|
||||||
|
|
||||||
var defaultSegment = func() {
|
var defaultSegment = func() {
|
||||||
|
|
||||||
@@ -548,11 +582,11 @@ func connectToStreamingServer(streamID int, playlist Playlist) {
|
|||||||
|
|
||||||
var stream = playlist.Streams[streamID]
|
var stream = playlist.Streams[streamID]
|
||||||
|
|
||||||
if c, ok := BufferClients.Load(stream.PlaylistID + stream.MD5); ok {
|
if c, ok := BufferClients.Load(playlistID + stream.MD5); ok {
|
||||||
|
|
||||||
var clients = c.(ClientConnection)
|
var clients = c.(ClientConnection)
|
||||||
clients.Error = err
|
clients.Error = err
|
||||||
BufferClients.Store(stream.PlaylistID+stream.MD5, clients)
|
BufferClients.Store(playlistID+stream.MD5, clients)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -650,7 +684,7 @@ InitBuffer:
|
|||||||
|
|
||||||
addErrorToStream(err)
|
addErrorToStream(err)
|
||||||
|
|
||||||
killClientConnection(streamID, stream.PlaylistID, true)
|
killClientConnection(streamID, playlistID, true)
|
||||||
clientConnection(stream)
|
clientConnection(stream)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -709,7 +743,7 @@ InitBuffer:
|
|||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
|
||||||
showInfo("Content type:" + contentType)
|
showInfo("Content Type:" + contentType)
|
||||||
showInfo("Streaming Status:" + httpStatusInfo)
|
showInfo("Streaming Status:" + httpStatusInfo)
|
||||||
showInfo("Error with this URL:" + currentURL)
|
showInfo("Error with this URL:" + currentURL)
|
||||||
|
|
||||||
@@ -722,7 +756,7 @@ InitBuffer:
|
|||||||
BufferInformation.Store(playlist.PlaylistID, playlist)
|
BufferInformation.Store(playlist.PlaylistID, playlist)
|
||||||
addErrorToStream(err)
|
addErrorToStream(err)
|
||||||
|
|
||||||
killClientConnection(streamID, stream.PlaylistID, true)
|
killClientConnection(streamID, playlistID, true)
|
||||||
clientConnection(stream)
|
clientConnection(stream)
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
|
|
||||||
@@ -754,7 +788,7 @@ InitBuffer:
|
|||||||
showDebug(debug, 1)
|
showDebug(debug, 1)
|
||||||
|
|
||||||
showInfo("Streaming Status:" + "HTTP Response Status [" + strconv.Itoa(resp.StatusCode) + "] " + http.StatusText(resp.StatusCode))
|
showInfo("Streaming Status:" + "HTTP Response Status [" + strconv.Itoa(resp.StatusCode) + "] " + http.StatusText(resp.StatusCode))
|
||||||
showInfo("Content type:" + contentType)
|
showInfo("Content Type:" + contentType)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
@@ -772,7 +806,7 @@ InitBuffer:
|
|||||||
switch contentType {
|
switch contentType {
|
||||||
|
|
||||||
// M3U8 Playlist
|
// M3U8 Playlist
|
||||||
case "application/x-mpegurl", "application/vnd.apple.mpegurl", "audio/mpegurl":
|
case "application/x-mpegurl", "application/vnd.apple.mpegurl", "audio/mpegurl", "audio/x-mpegurl":
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ShowError(err, 0)
|
ShowError(err, 0)
|
||||||
@@ -790,13 +824,13 @@ InitBuffer:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Video Stream (TS)
|
// Video Stream (TS)
|
||||||
case "video/mpeg", "video/mp4", "video/mp2t", "application/octet-stream":
|
case "video/mpeg", "video/mp4", "video/mp2t", "video/m2ts", "application/octet-stream", "binary/octet-stream", "application/mp2t", "video/x-matroska":
|
||||||
|
|
||||||
var fileSize int
|
var fileSize int
|
||||||
|
|
||||||
// Größe des Buffers
|
// Größe des Buffers
|
||||||
buffer := make([]byte, 1024*Settings.BufferSize*2)
|
buffer = make([]byte, 1024*bufferSize*2)
|
||||||
var tmpFileSize = 1024 * Settings.BufferSize * 1
|
var tmpFileSize = 1024 * bufferSize * 1
|
||||||
|
|
||||||
debug = fmt.Sprintf("Buffer Size:%d KB [SERVER CONNECTION]", len(buffer)/1024)
|
debug = fmt.Sprintf("Buffer Size:%d KB [SERVER CONNECTION]", len(buffer)/1024)
|
||||||
showDebug(debug, 3)
|
showDebug(debug, 3)
|
||||||
@@ -872,7 +906,9 @@ InitBuffer:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Buffer auf die Festplatte speichern
|
// Buffer auf die Festplatte speichern
|
||||||
if fileSize >= tmpFileSize || n == 0 {
|
if fileSize >= tmpFileSize/2 || n == 0 {
|
||||||
|
|
||||||
|
Lock.Lock()
|
||||||
|
|
||||||
bandwidth.Stop = time.Now()
|
bandwidth.Stop = time.Now()
|
||||||
bandwidth.Size += fileSize
|
bandwidth.Size += fileSize
|
||||||
@@ -891,6 +927,9 @@ InitBuffer:
|
|||||||
|
|
||||||
stream.Status = true
|
stream.Status = true
|
||||||
playlist.Streams[streamID] = stream
|
playlist.Streams[streamID] = stream
|
||||||
|
BufferInformation.Store(playlistID, playlist)
|
||||||
|
Lock.Unlock()
|
||||||
|
|
||||||
tmpSegment++
|
tmpSegment++
|
||||||
|
|
||||||
tmpFile = fmt.Sprintf("%s%d.ts", tmpFolder, tmpSegment)
|
tmpFile = fmt.Sprintf("%s%d.ts", tmpFolder, tmpSegment)
|
||||||
@@ -916,6 +955,7 @@ InitBuffer:
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileSize = 0
|
fileSize = 0
|
||||||
|
buffer = make([]byte, 1024*bufferSize*2)
|
||||||
|
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
bufferFile.Close()
|
bufferFile.Close()
|
||||||
@@ -931,7 +971,7 @@ InitBuffer:
|
|||||||
|
|
||||||
// Umbekanntes Format
|
// Umbekanntes Format
|
||||||
default:
|
default:
|
||||||
showInfo("Content type:" + resp.Header.Get("Content-Type"))
|
showInfo("Content Type:" + resp.Header.Get("Content-Type"))
|
||||||
err = errors.New("Streaming error")
|
err = errors.New("Streaming error")
|
||||||
ShowError(err, 4003)
|
ShowError(err, 4003)
|
||||||
|
|
||||||
@@ -986,6 +1026,8 @@ InitBuffer:
|
|||||||
|
|
||||||
} // Ende for loop
|
} // Ende for loop
|
||||||
|
|
||||||
|
} // Ende BufferInformation
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseM3U8(stream *ThisStream) (err error) {
|
func parseM3U8(stream *ThisStream) (err error) {
|
||||||
@@ -1027,7 +1069,7 @@ func parseM3U8(stream *ThisStream) (err error) {
|
|||||||
|
|
||||||
line = strings.Trim(line, "\r\n")
|
line = strings.Trim(line, "\r\n")
|
||||||
|
|
||||||
var parameters = []string{"#EXT-X-VERSION:", "#EXT-X-MEDIA-SEQUENCE:", "#EXT-X-STREAM-INF:", "#EXTINF:"}
|
var parameters = []string{"#EXT-X-VERSION:", "#EXT-X-PLAYLIST-TYPE:", "#EXT-X-MEDIA-SEQUENCE:", "#EXT-X-STREAM-INF:", "#EXTINF:"}
|
||||||
|
|
||||||
for _, parameter := range parameters {
|
for _, parameter := range parameters {
|
||||||
|
|
||||||
@@ -1043,6 +1085,9 @@ func parseM3U8(stream *ThisStream) (err error) {
|
|||||||
segment.Version = version
|
segment.Version = version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "#EXT-X-PLAYLIST-TYPE:":
|
||||||
|
segment.PlaylistType = value
|
||||||
|
|
||||||
case "#EXT-X-MEDIA-SEQUENCE:":
|
case "#EXT-X-MEDIA-SEQUENCE:":
|
||||||
n, err := strconv.ParseInt(value, 10, 64)
|
n, err := strconv.ParseInt(value, 10, 64)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -1194,6 +1239,11 @@ func parseM3U8(stream *ThisStream) (err error) {
|
|||||||
noNewSegment = false
|
noNewSegment = false
|
||||||
stream.LastSequence = segment.Sequence
|
stream.LastSequence = segment.Sequence
|
||||||
|
|
||||||
|
// Stream ist vom Typ VOD. Es muss das erste Segment der M3U8 Playlist verwendet werden.
|
||||||
|
if strings.ToUpper(segment.PlaylistType) == "VOD" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if segment.Sequence > stream.LastSequence {
|
if segment.Sequence > stream.LastSequence {
|
||||||
@@ -1233,12 +1283,6 @@ func parseM3U8(stream *ThisStream) (err error) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
err := checkFile(stream.Folder + "remove")
|
|
||||||
if err == nil {
|
|
||||||
os.RemoveAll(stream.Folder)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1297,14 +1341,311 @@ func switchBandwidth(stream *ThisStream) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Buffer mit FFMPEG
|
||||||
|
func thirdPartyBuffer(streamID int, playlistID string) {
|
||||||
|
|
||||||
|
if p, ok := BufferInformation.Load(playlistID); ok {
|
||||||
|
|
||||||
|
var playlist = p.(Playlist)
|
||||||
|
var debug, path, options, bufferType string
|
||||||
|
var tmpSegment = 1
|
||||||
|
var bufferSize = Settings.BufferSize * 1024
|
||||||
|
var stream = playlist.Streams[streamID]
|
||||||
|
var buf bytes.Buffer
|
||||||
|
var fileSize = 0
|
||||||
|
var streamStatus = make(chan bool)
|
||||||
|
|
||||||
|
var tmpFolder = playlist.Streams[streamID].Folder
|
||||||
|
var url = playlist.Streams[streamID].URL
|
||||||
|
|
||||||
|
stream.Status = false
|
||||||
|
|
||||||
|
bufferType = strings.ToUpper(Settings.Buffer)
|
||||||
|
|
||||||
|
switch Settings.Buffer {
|
||||||
|
|
||||||
|
case "ffmpeg":
|
||||||
|
path = Settings.FFmpegPath
|
||||||
|
options = Settings.FFmpegOptions
|
||||||
|
|
||||||
|
case "vlc":
|
||||||
|
path = Settings.VLCPath
|
||||||
|
options = Settings.VLCOptions
|
||||||
|
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var addErrorToStream = func(err error) {
|
||||||
|
|
||||||
|
var stream = playlist.Streams[streamID]
|
||||||
|
|
||||||
|
if c, ok := BufferClients.Load(playlistID + stream.MD5); ok {
|
||||||
|
|
||||||
|
var clients = c.(ClientConnection)
|
||||||
|
clients.Error = err
|
||||||
|
BufferClients.Store(playlistID+stream.MD5, clients)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
os.RemoveAll(getPlatformPath(tmpFolder))
|
||||||
|
|
||||||
|
err := checkFolder(tmpFolder)
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
addErrorToStream(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = checkFile(path)
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
addErrorToStream(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
showInfo(fmt.Sprintf("%s path:%s", bufferType, path))
|
||||||
|
showInfo("Streaming URL:" + stream.URL)
|
||||||
|
|
||||||
|
var tmpFile = fmt.Sprintf("%s%d.ts", tmpFolder, tmpSegment)
|
||||||
|
|
||||||
|
f, err := os.Create(tmpFile)
|
||||||
|
f.Close()
|
||||||
|
if err != nil {
|
||||||
|
addErrorToStream(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//args = strings.Replace(args, "[USER-AGENT]", Settings.UserAgent, -1)
|
||||||
|
|
||||||
|
// User-Agent setzen
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
for i, a := range strings.Split(options, " ") {
|
||||||
|
|
||||||
|
switch bufferType {
|
||||||
|
case "FFMPEG":
|
||||||
|
a = strings.Replace(a, "[URL]", url, -1)
|
||||||
|
if i == 0 {
|
||||||
|
if len(Settings.UserAgent) != 0 {
|
||||||
|
args = []string{"-user_agent", Settings.UserAgent}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
args = append(args, a)
|
||||||
|
|
||||||
|
case "VLC":
|
||||||
|
if a == "[URL]" {
|
||||||
|
a = strings.Replace(a, "[URL]", url, -1)
|
||||||
|
args = append(args, a)
|
||||||
|
|
||||||
|
if len(Settings.UserAgent) != 0 {
|
||||||
|
args = append(args, fmt.Sprintf(":http-user-agent=%s", Settings.UserAgent))
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
args = append(args, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmd = exec.Command(path, args...)
|
||||||
|
|
||||||
|
debug = fmt.Sprintf("%s:%s %s", bufferType, path, args)
|
||||||
|
showDebug(debug, 1)
|
||||||
|
|
||||||
|
// Byte-Daten vom Prozess
|
||||||
|
stdOut, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
cmd.Process.Kill()
|
||||||
|
cmd.Wait()
|
||||||
|
addErrorToStream(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log-Daten vom Prozess
|
||||||
|
logOut, err := cmd.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
cmd.Process.Kill()
|
||||||
|
cmd.Wait()
|
||||||
|
addErrorToStream(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(buf.Bytes()) == 0 && stream.Status == false {
|
||||||
|
showInfo(bufferType + ":Processing data")
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Start()
|
||||||
|
defer cmd.Wait()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
|
||||||
|
// Log Daten vom Prozess im Dubug Mode 1 anzeigen.
|
||||||
|
scanner := bufio.NewScanner(logOut)
|
||||||
|
scanner.Split(bufio.ScanLines)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
|
||||||
|
debug = fmt.Sprintf("%s log:%s", bufferType, strings.TrimSpace(scanner.Text()))
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-streamStatus:
|
||||||
|
showDebug(debug, 1)
|
||||||
|
default:
|
||||||
|
showInfo(debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Duration(10) * time.Millisecond)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
f, err = os.OpenFile(tmpFile, os.O_APPEND|os.O_WRONLY, 0600)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
buffer := make([]byte, 1024*4)
|
||||||
|
|
||||||
|
reader := bufio.NewReader(stdOut)
|
||||||
|
|
||||||
|
t := make(chan int)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
|
||||||
|
var timeout = 0
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Duration(1000) * time.Millisecond)
|
||||||
|
timeout++
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-t:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
t <- timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
|
||||||
|
select {
|
||||||
|
case timeout := <-t:
|
||||||
|
if timeout >= 20 && tmpSegment == 1 {
|
||||||
|
cmd.Process.Kill()
|
||||||
|
err = errors.New("Timout")
|
||||||
|
ShowError(err, 4006)
|
||||||
|
addErrorToStream(err)
|
||||||
|
cmd.Wait()
|
||||||
|
f.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if fileSize == 0 && stream.Status == false {
|
||||||
|
showInfo("Streaming Status:Receive data from " + bufferType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if clientConnection(stream) == false {
|
||||||
|
cmd.Process.Kill()
|
||||||
|
f.Close()
|
||||||
|
cmd.Wait()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := reader.Read(buffer)
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
fileSize = fileSize + len(buffer[:n])
|
||||||
|
|
||||||
|
if _, err := f.Write(buffer[:n]); err != nil {
|
||||||
|
cmd.Process.Kill()
|
||||||
|
ShowError(err, 0)
|
||||||
|
addErrorToStream(err)
|
||||||
|
cmd.Wait()
|
||||||
|
f.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if fileSize >= bufferSize/2 {
|
||||||
|
|
||||||
|
if tmpSegment == 1 && stream.Status == false {
|
||||||
|
close(t)
|
||||||
|
close(streamStatus)
|
||||||
|
showInfo(fmt.Sprintf("Streaming Status:Buffering data from %s", bufferType))
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Close()
|
||||||
|
tmpSegment++
|
||||||
|
|
||||||
|
if stream.Status == false {
|
||||||
|
Lock.Lock()
|
||||||
|
stream.Status = true
|
||||||
|
playlist.Streams[streamID] = stream
|
||||||
|
BufferInformation.Store(playlistID, playlist)
|
||||||
|
Lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpFile = fmt.Sprintf("%s%d.ts", tmpFolder, tmpSegment)
|
||||||
|
|
||||||
|
fileSize = 0
|
||||||
|
|
||||||
|
var errCreate, errOpen error
|
||||||
|
f, errCreate = os.Create(tmpFile)
|
||||||
|
f, errOpen = os.OpenFile(tmpFile, os.O_APPEND|os.O_WRONLY, 0600)
|
||||||
|
if errCreate != nil || errOpen != nil {
|
||||||
|
cmd.Process.Kill()
|
||||||
|
ShowError(err, 0)
|
||||||
|
addErrorToStream(err)
|
||||||
|
cmd.Wait()
|
||||||
|
f.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Process.Kill()
|
||||||
|
cmd.Wait()
|
||||||
|
|
||||||
|
err = errors.New(bufferType + " error")
|
||||||
|
addErrorToStream(err)
|
||||||
|
ShowError(err, 1204)
|
||||||
|
|
||||||
|
time.Sleep(time.Duration(500) * time.Millisecond)
|
||||||
|
clientConnection(stream)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func getTuner(id, playlistType string) (tuner int) {
|
func getTuner(id, playlistType string) (tuner int) {
|
||||||
|
|
||||||
switch Settings.Buffer {
|
switch Settings.Buffer {
|
||||||
|
|
||||||
case false:
|
case "-":
|
||||||
tuner = Settings.Tuner
|
tuner = Settings.Tuner
|
||||||
|
|
||||||
case true:
|
case "xteve", "ffmpeg", "vlc":
|
||||||
|
|
||||||
i, err := strconv.Atoi(getProviderParameter(id, playlistType, "tuner"))
|
i, err := strconv.Atoi(getProviderParameter(id, playlistType, "tuner"))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
@@ -146,3 +146,20 @@ func extractGZIP(gzipBody []byte, fileSource string) (body []byte, err error) {
|
|||||||
body = resB.Bytes()
|
body = resB.Bytes()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func compressGZIP(data *[]byte, file string) (err error) {
|
||||||
|
|
||||||
|
if len(file) != 0 {
|
||||||
|
|
||||||
|
f, err := os.Create(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := gzip.NewWriter(f)
|
||||||
|
w.Write(*data)
|
||||||
|
w.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ var System SystemStruct
|
|||||||
var WebScreenLog WebScreenLogStruct
|
var WebScreenLog WebScreenLogStruct
|
||||||
|
|
||||||
// Settings : Inhalt der settings.json
|
// Settings : Inhalt der settings.json
|
||||||
var Settings SettingsStrcut
|
var Settings SettingsStruct
|
||||||
|
|
||||||
// Data : Alle Daten werden hier abgelegt. (Lineup, XMLTV)
|
// Data : Alle Daten werden hier abgelegt. (Lineup, XMLTV)
|
||||||
var Data DataStruct
|
var Data DataStruct
|
||||||
@@ -29,6 +29,9 @@ var BufferInformation sync.Map
|
|||||||
// BufferClients : Anzahl der Clients die einen Stream über den Buffer abspielen
|
// BufferClients : Anzahl der Clients die einen Stream über den Buffer abspielen
|
||||||
var BufferClients sync.Map
|
var BufferClients sync.Map
|
||||||
|
|
||||||
|
// Lock : Lock Map
|
||||||
|
var Lock = sync.RWMutex{}
|
||||||
|
|
||||||
// Init : Systeminitialisierung
|
// Init : Systeminitialisierung
|
||||||
func Init() (err error) {
|
func Init() (err error) {
|
||||||
|
|
||||||
@@ -43,9 +46,14 @@ func Init() (err error) {
|
|||||||
System.ServerProtocol.M3U = "http"
|
System.ServerProtocol.M3U = "http"
|
||||||
System.ServerProtocol.WEB = "http"
|
System.ServerProtocol.WEB = "http"
|
||||||
System.ServerProtocol.XML = "http"
|
System.ServerProtocol.XML = "http"
|
||||||
System.DVRLimit = 480
|
System.PlexChannelLimit = 480
|
||||||
|
System.UnfilteredChannelLimit = 480
|
||||||
System.Compatibility = "1.4.4"
|
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)
|
// 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
|
Settings.LogEntriesRAM = 500
|
||||||
|
|
||||||
@@ -83,9 +91,16 @@ func Init() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(System.Flag.Restore) > 0 {
|
||||||
|
// Einstellungen werden über CLI wiederhergestellt. Weitere Initialisierung ist nicht notwendig.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
System.File.XML = getPlatformFile(fmt.Sprintf("%s%s.xml", System.Folder.Data, System.AppName))
|
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.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()
|
err = activatedSystemAuthentication()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -104,7 +119,7 @@ func Init() (err error) {
|
|||||||
|
|
||||||
// Überprüfen ob xTeVe als root läuft
|
// Überprüfen ob xTeVe als root läuft
|
||||||
if os.Geteuid() == 0 {
|
if os.Geteuid() == 0 {
|
||||||
showWarning(2010)
|
showWarning(2110)
|
||||||
}
|
}
|
||||||
|
|
||||||
if System.Flag.Debug > 0 {
|
if System.Flag.Debug > 0 {
|
||||||
@@ -113,6 +128,7 @@ func Init() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showInfo(fmt.Sprintf("Version:%s Build: %s", System.Version, System.Build))
|
showInfo(fmt.Sprintf("Version:%s Build: %s", System.Version, System.Build))
|
||||||
|
showInfo(fmt.Sprintf("Database Version:%s", System.DBVersion))
|
||||||
showInfo(fmt.Sprintf("System IP Addresses:IPv4: %d | IPv6: %d", len(System.IPAddressesV4), len(System.IPAddressesV6)))
|
showInfo(fmt.Sprintf("System IP Addresses:IPv4: %d | IPv6: %d", len(System.IPAddressesV4), len(System.IPAddressesV6)))
|
||||||
showInfo("Hostname:" + System.Hostname)
|
showInfo("Hostname:" + System.Hostname)
|
||||||
showInfo(fmt.Sprintf("System Folder:%s", getPlatformPath(System.Folder.Config)))
|
showInfo(fmt.Sprintf("System Folder:%s", getPlatformPath(System.Folder.Config)))
|
||||||
@@ -127,7 +143,6 @@ func Init() (err error) {
|
|||||||
// Bedingte Update Änderungen durchführen
|
// Bedingte Update Änderungen durchführen
|
||||||
err = conditionalUpdateChanges()
|
err = conditionalUpdateChanges()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ShowError(err, 0)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,9 +175,6 @@ func Init() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DLNA Server starten
|
|
||||||
go SSDP()
|
|
||||||
|
|
||||||
// Branch festlegen
|
// Branch festlegen
|
||||||
System.Branch = Settings.Branch
|
System.Branch = Settings.Branch
|
||||||
|
|
||||||
@@ -193,6 +205,13 @@ func Init() (err error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DLNA Server starten
|
||||||
|
err = SSDP()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML Datein laden
|
||||||
loadHTMLMap()
|
loadHTMLMap()
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -211,7 +230,8 @@ func StartSystem(updateProviderFiles bool) (err error) {
|
|||||||
showInfo(fmt.Sprintf("UUID:%s", Settings.UUID))
|
showInfo(fmt.Sprintf("UUID:%s", Settings.UUID))
|
||||||
showInfo(fmt.Sprintf("Tuner (Plex / Emby):%d", Settings.Tuner))
|
showInfo(fmt.Sprintf("Tuner (Plex / Emby):%d", Settings.Tuner))
|
||||||
showInfo(fmt.Sprintf("EPG Source:%s", Settings.EpgSource))
|
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
|
// Providerdaten aktualisieren
|
||||||
if len(Settings.Files.M3U) > 0 && Settings.FilesUpdate == true || updateProviderFiles == true {
|
if len(Settings.Files.M3U) > 0 && Settings.FilesUpdate == true || updateProviderFiles == true {
|
||||||
|
|||||||
188
src/data.go
188
src/data.go
@@ -11,11 +11,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"../src/internal/authentication"
|
"xteve/src/internal/authentication"
|
||||||
|
"xteve/src/internal/imgcache"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Einstellungen ändern (WebUI)
|
// 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 oldSettings = jsonToMap(mapToJSON(Settings))
|
||||||
var newSettings = jsonToMap(mapToJSON(request.Settings))
|
var newSettings = jsonToMap(mapToJSON(request.Settings))
|
||||||
@@ -24,6 +25,8 @@ func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err e
|
|||||||
var createXEPGFiles = false
|
var createXEPGFiles = false
|
||||||
var debug string
|
var debug string
|
||||||
|
|
||||||
|
// -vvv [URL] --sout '#transcode{vcodec=mp4v, acodec=mpga} :standard{access=http, mux=ogg}'
|
||||||
|
|
||||||
for key, value := range newSettings {
|
for key, value := range newSettings {
|
||||||
|
|
||||||
if _, ok := oldSettings[key]; ok {
|
if _, ok := oldSettings[key]; ok {
|
||||||
@@ -37,17 +40,29 @@ func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err e
|
|||||||
reloadData = true
|
reloadData = true
|
||||||
|
|
||||||
case "update":
|
case "update":
|
||||||
// Die Formatierung der Uhrzeit überprüfen (0000 - 2359)
|
// Leerzeichen aus den Werten entfernen und Formatierung der Uhrzeit überprüfen (0000 - 2359)
|
||||||
for _, i := range newSettings[key].([]interface{}) {
|
var newUpdateTimes = make([]string, 0)
|
||||||
|
|
||||||
_, err := time.Parse("1504", i.(string))
|
for _, v := range value.([]interface{}) {
|
||||||
|
|
||||||
|
v = strings.Replace(v.(string), " ", "", -1)
|
||||||
|
|
||||||
|
_, err := time.Parse("1504", v.(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ShowError(err, 1012)
|
ShowError(err, 1012)
|
||||||
return Settings, err
|
return Settings, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newUpdateTimes = append(newUpdateTimes, v.(string))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(newUpdateTimes) == 0 {
|
||||||
|
//newUpdateTimes = append(newUpdateTimes, "0000")
|
||||||
|
}
|
||||||
|
|
||||||
|
value = newUpdateTimes
|
||||||
|
|
||||||
case "cache.images":
|
case "cache.images":
|
||||||
cacheImages = true
|
cacheImages = true
|
||||||
|
|
||||||
@@ -86,6 +101,20 @@ func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err e
|
|||||||
return
|
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
|
oldSettings[key] = value
|
||||||
@@ -130,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)
|
err = saveSettings(Settings)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
||||||
@@ -148,30 +204,43 @@ func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err e
|
|||||||
|
|
||||||
if cacheImages == true {
|
if cacheImages == true {
|
||||||
|
|
||||||
if Settings.EpgSource == "XEPG" {
|
if Settings.EpgSource == "XEPG" && System.ImageCachingInProgress == 0 {
|
||||||
|
|
||||||
go func() {
|
|
||||||
|
|
||||||
if Settings.CacheImages == true {
|
|
||||||
|
|
||||||
createXMLTVFile()
|
|
||||||
cachingImages()
|
|
||||||
createXMLTVFile()
|
|
||||||
createM3UFile()
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
createXMLTVFile()
|
|
||||||
createM3UFile()
|
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch Settings.CacheImages {
|
||||||
|
|
||||||
|
case false:
|
||||||
|
createXMLTVFile()
|
||||||
|
createM3UFile()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if createXEPGFiles == true {
|
if createXEPGFiles == true {
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -353,11 +422,12 @@ func deleteLocalProviderFiles(dataID, fileType string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Filtereinstellungen speichern (WebUI)
|
// 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 filterMap = make(map[int64]interface{})
|
||||||
var newData = make(map[int64]interface{})
|
var newData = make(map[int64]interface{})
|
||||||
var defaultFilter FilterStruct
|
var defaultFilter FilterStruct
|
||||||
|
var newFilter = false
|
||||||
|
|
||||||
defaultFilter.Active = true
|
defaultFilter.Active = true
|
||||||
defaultFilter.CaseSensitive = false
|
defaultFilter.CaseSensitive = false
|
||||||
@@ -381,6 +451,7 @@ func saveFilter(request RequestStruct) (settings SettingsStrcut, err error) {
|
|||||||
if dataID == -1 {
|
if dataID == -1 {
|
||||||
|
|
||||||
// Neuer Filter
|
// Neuer Filter
|
||||||
|
newFilter = true
|
||||||
dataID = createNewID()
|
dataID = createNewID()
|
||||||
filterMap[dataID] = jsonToMap(mapToJSON(defaultFilter))
|
filterMap[dataID] = jsonToMap(mapToJSON(defaultFilter))
|
||||||
|
|
||||||
@@ -389,15 +460,28 @@ func saveFilter(request RequestStruct) (settings SettingsStrcut, err error) {
|
|||||||
// Filter aktualisieren / löschen
|
// Filter aktualisieren / löschen
|
||||||
for key, value := range data.(map[string]interface{}) {
|
for key, value := range data.(map[string]interface{}) {
|
||||||
|
|
||||||
var oldData = filterMap[dataID].(map[string]interface{})
|
|
||||||
oldData[key] = value
|
|
||||||
|
|
||||||
// Filter löschen
|
// Filter löschen
|
||||||
if _, ok := data.(map[string]interface{})["delete"]; ok {
|
if _, ok := data.(map[string]interface{})["delete"]; ok {
|
||||||
|
|
||||||
delete(filterMap, dataID)
|
delete(filterMap, dataID)
|
||||||
break
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -426,6 +510,11 @@ func saveXEpgMapping(request RequestStruct) (err error) {
|
|||||||
|
|
||||||
var tmp = Data.XEPG
|
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)
|
err = json.Unmarshal([]byte(mapToJSON(request.EpgMapping)), &tmp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -438,8 +527,44 @@ func saveXEpgMapping(request RequestStruct) (err error) {
|
|||||||
|
|
||||||
Data.XEPG.Channels = request.EpgMapping
|
Data.XEPG.Channels = request.EpgMapping
|
||||||
|
|
||||||
|
if System.ScanInProgress == 0 {
|
||||||
|
|
||||||
|
System.ScanInProgress = 1
|
||||||
cleanupXEPG()
|
cleanupXEPG()
|
||||||
|
System.ScanInProgress = 0
|
||||||
buildXEPG(true)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -604,6 +729,7 @@ func saveWizard(request RequestStruct) (nextStep int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildXEPG(false)
|
buildXEPG(false)
|
||||||
|
System.ScanInProgress = 0
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -671,9 +797,9 @@ func buildDatabaseDVR() (err error) {
|
|||||||
|
|
||||||
System.ScanInProgress = 1
|
System.ScanInProgress = 1
|
||||||
|
|
||||||
Data.Streams.All = make([]interface{}, 0)
|
Data.Streams.All = make([]interface{}, 0, System.UnfilteredChannelLimit)
|
||||||
Data.Streams.Active = make([]interface{}, 0)
|
Data.Streams.Active = make([]interface{}, 0, System.UnfilteredChannelLimit)
|
||||||
Data.Streams.Inactive = make([]interface{}, 0)
|
Data.Streams.Inactive = make([]interface{}, 0, System.UnfilteredChannelLimit)
|
||||||
Data.Playlist.M3U.Groups.Text = []string{}
|
Data.Playlist.M3U.Groups.Text = []string{}
|
||||||
Data.Playlist.M3U.Groups.Value = []string{}
|
Data.Playlist.M3U.Groups.Value = []string{}
|
||||||
Data.StreamPreviewUI.Active = []string{}
|
Data.StreamPreviewUI.Active = []string{}
|
||||||
@@ -829,7 +955,7 @@ func buildDatabaseDVR() (err error) {
|
|||||||
sort.Strings(Data.Playlist.M3U.Groups.Text)
|
sort.Strings(Data.Playlist.M3U.Groups.Text)
|
||||||
sort.Strings(Data.Playlist.M3U.Groups.Value)
|
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.Active = Data.Streams.All
|
||||||
Data.Streams.Inactive = make([]interface{}, 0)
|
Data.Streams.Inactive = make([]interface{}, 0)
|
||||||
|
|
||||||
@@ -838,11 +964,11 @@ func buildDatabaseDVR() (err error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(Data.Streams.Active) > System.DVRLimit {
|
if len(Data.Streams.Active) > System.PlexChannelLimit {
|
||||||
showWarning(2000)
|
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)
|
showWarning(2001)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ func getLineupStatus() (jsonContent []byte, err error) {
|
|||||||
lineupStatus.ScanInProgress = System.ScanInProgress
|
lineupStatus.ScanInProgress = System.ScanInProgress
|
||||||
lineupStatus.ScanPossible = 0
|
lineupStatus.ScanPossible = 0
|
||||||
lineupStatus.Source = "Cable"
|
lineupStatus.Source = "Cable"
|
||||||
lineupStatus.SourceList = []string{"IPTV", "Cable"}
|
lineupStatus.SourceList = []string{"Cable"}
|
||||||
|
|
||||||
jsonContent, err = json.MarshalIndent(lineupStatus, "", " ")
|
jsonContent, err = json.MarshalIndent(lineupStatus, "", " ")
|
||||||
|
|
||||||
|
|||||||
125
src/images.go
125
src/images.go
@@ -3,138 +3,15 @@ package src
|
|||||||
import (
|
import (
|
||||||
b64 "encoding/base64"
|
b64 "encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getCacheImageURL(url string) (cacheImageURL string) {
|
|
||||||
|
|
||||||
url = strings.Trim(url, "\r\n")
|
|
||||||
|
|
||||||
var urlMD5 = getMD5(url)
|
|
||||||
var fileExtension = filepath.Ext(url)
|
|
||||||
|
|
||||||
if indexOfString(urlMD5+fileExtension, Data.Cache.ImagesFiles) == -1 {
|
|
||||||
Data.Cache.ImagesFiles = append(Data.Cache.ImagesFiles, urlMD5+fileExtension)
|
|
||||||
}
|
|
||||||
|
|
||||||
if Settings.CacheImages == false || System.ImageCachingInProgress == 1 {
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
|
|
||||||
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(url, System.Domain+"/images/") == false {
|
|
||||||
|
|
||||||
if indexOfString(url, Data.Cache.ImagesURLS) == -1 {
|
|
||||||
Data.Cache.ImagesURLS = append(Data.Cache.ImagesURLS, url)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheImageURL = url
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func cachingImages() {
|
|
||||||
|
|
||||||
if Settings.CacheImages == false || System.ImageCachingInProgress == 1 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
System.ImageCachingInProgress = 1
|
|
||||||
|
|
||||||
showInfo("Image Caching:Images are cached")
|
|
||||||
|
|
||||||
for _, url := range Data.Cache.ImagesURLS {
|
|
||||||
|
|
||||||
if len(url) > 0 {
|
|
||||||
cacheImage(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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(url string) {
|
|
||||||
|
|
||||||
var debug string
|
|
||||||
var urlMD5 = getMD5(url)
|
|
||||||
var fileExtension = filepath.Ext(url)
|
|
||||||
|
|
||||||
debug = fmt.Sprintf("Image Caching:File: %s Download: %s", urlMD5+fileExtension, url)
|
|
||||||
showDebug(debug, 1)
|
|
||||||
|
|
||||||
resp, err := http.Get(url)
|
|
||||||
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) {
|
func uploadLogo(input, filename string) (logoURL string, err error) {
|
||||||
|
|
||||||
b64data := input[strings.IndexByte(input, ',')+1:]
|
b64data := input[strings.IndexByte(input, ',')+1:]
|
||||||
|
|
||||||
// BAse64 in bytes umwandeln un speichern
|
// BAse64 in bytes umwandeln un speichern
|
||||||
sDec, err := b64.StdEncoding.DecodeString(b64data)
|
sDec, err := b64.StdEncoding.DecodeString(b64data)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -146,7 +23,7 @@ func uploadLogo(input, filename string) (logoURL string, err error) {
|
|||||||
return
|
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
|
return
|
||||||
|
|
||||||
|
|||||||
100
src/info.go
Normal file
100
src/info.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package src
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ShowSystemInfo : Systeminformationen anzeigen
|
||||||
|
func ShowSystemInfo() {
|
||||||
|
|
||||||
|
fmt.Print("Creating the information takes a moment...")
|
||||||
|
err := buildDatabaseDVR()
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buildXEPG(false)
|
||||||
|
|
||||||
|
fmt.Println("OK")
|
||||||
|
println()
|
||||||
|
|
||||||
|
fmt.Println(fmt.Sprintf("Version: %s %s.%s", System.Name, System.Version, System.Build))
|
||||||
|
fmt.Println(fmt.Sprintf("Branch: %s", System.Branch))
|
||||||
|
fmt.Println(fmt.Sprintf("GitHub: %s/%s | Git update = %t", System.GitHub.User, System.GitHub.Repo, System.GitHub.Update))
|
||||||
|
fmt.Println(fmt.Sprintf("Folder (config): %s", System.Folder.Config))
|
||||||
|
|
||||||
|
fmt.Println(fmt.Sprintf("Streams: %d / %d", len(Data.Streams.Active), len(Data.Streams.All)))
|
||||||
|
fmt.Println(fmt.Sprintf("Filter: %d", len(Data.Filter)))
|
||||||
|
fmt.Println(fmt.Sprintf("XEPG Chanels: %d", int(Data.XEPG.XEPGCount)))
|
||||||
|
|
||||||
|
println()
|
||||||
|
fmt.Println(fmt.Sprintf("IPv4 Addresses:"))
|
||||||
|
|
||||||
|
for i, ipv4 := range System.IPAddressesV4 {
|
||||||
|
|
||||||
|
switch count := i; {
|
||||||
|
|
||||||
|
case count < 10:
|
||||||
|
fmt.Println(fmt.Sprintf(" %d. %s", count, ipv4))
|
||||||
|
break
|
||||||
|
case count < 100:
|
||||||
|
fmt.Println(fmt.Sprintf(" %d. %s", count, ipv4))
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
println()
|
||||||
|
fmt.Println(fmt.Sprintf("IPv6 Addresses:"))
|
||||||
|
|
||||||
|
for i, ipv4 := range System.IPAddressesV6 {
|
||||||
|
|
||||||
|
switch count := i; {
|
||||||
|
|
||||||
|
case count < 10:
|
||||||
|
fmt.Println(fmt.Sprintf(" %d. %s", count, ipv4))
|
||||||
|
break
|
||||||
|
case count < 100:
|
||||||
|
fmt.Println(fmt.Sprintf(" %d. %s", count, ipv4))
|
||||||
|
break
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
println("---")
|
||||||
|
|
||||||
|
fmt.Println("Settings [General]")
|
||||||
|
fmt.Println(fmt.Sprintf("xTeVe Update: %t", Settings.XteveAutoUpdate))
|
||||||
|
fmt.Println(fmt.Sprintf("UUID: %s", Settings.UUID))
|
||||||
|
fmt.Println(fmt.Sprintf("Tuner (Plex / Emby): %d", Settings.Tuner))
|
||||||
|
fmt.Println(fmt.Sprintf("EPG Source: %s", Settings.EpgSource))
|
||||||
|
|
||||||
|
println("---")
|
||||||
|
|
||||||
|
fmt.Println("Settings [Files]")
|
||||||
|
fmt.Println(fmt.Sprintf("Schedule: %s", strings.Join(Settings.Update, ",")))
|
||||||
|
fmt.Println(fmt.Sprintf("Files Update: %t", Settings.FilesUpdate))
|
||||||
|
fmt.Println(fmt.Sprintf("Folder (tmp): %s", Settings.TempPath))
|
||||||
|
fmt.Println(fmt.Sprintf("Image Chaching: %t", Settings.CacheImages))
|
||||||
|
fmt.Println(fmt.Sprintf("Replace EPG Image: %t", Settings.XepgReplaceMissingImages))
|
||||||
|
|
||||||
|
println("---")
|
||||||
|
|
||||||
|
fmt.Println("Settings [Streaming]")
|
||||||
|
fmt.Println(fmt.Sprintf("Buffer: %s", Settings.Buffer))
|
||||||
|
fmt.Println(fmt.Sprintf("UDPxy: %s", Settings.UDPxy))
|
||||||
|
fmt.Println(fmt.Sprintf("Buffer Size: %d KB", Settings.BufferSize))
|
||||||
|
fmt.Println(fmt.Sprintf("Timeout: %d ms", int(Settings.BufferTimeout)))
|
||||||
|
fmt.Println(fmt.Sprintf("User Agent: %s", Settings.UserAgent))
|
||||||
|
|
||||||
|
println("---")
|
||||||
|
|
||||||
|
fmt.Println("Settings [Backup]")
|
||||||
|
fmt.Println(fmt.Sprintf("Folder (backup): %s", Settings.BackupPath))
|
||||||
|
fmt.Println(fmt.Sprintf("Backup Keep: %d", Settings.BackupKeep))
|
||||||
|
|
||||||
|
}
|
||||||
178
src/internal/imgcache/cache.go
Normal file
178
src/internal/imgcache/cache.go
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
package imgcache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cache : Cache strcut
|
||||||
|
type Cache struct {
|
||||||
|
path string
|
||||||
|
cacheURL string
|
||||||
|
caching bool
|
||||||
|
images map[string]string
|
||||||
|
Queue []string
|
||||||
|
Cache []string
|
||||||
|
Image imageFunc
|
||||||
|
sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
type imageFunc struct {
|
||||||
|
GetURL func(string) string
|
||||||
|
Caching func()
|
||||||
|
Remove func()
|
||||||
|
}
|
||||||
|
|
||||||
|
// New : New cahce
|
||||||
|
func New(path, chacheURL string, caching bool) (c *Cache, err error) {
|
||||||
|
|
||||||
|
c = &Cache{}
|
||||||
|
|
||||||
|
c.images = make(map[string]string)
|
||||||
|
c.path = path
|
||||||
|
c.cacheURL = chacheURL
|
||||||
|
c.caching = caching
|
||||||
|
c.Queue = []string{}
|
||||||
|
c.Cache = []string{}
|
||||||
|
|
||||||
|
var queue []string
|
||||||
|
|
||||||
|
c.Image.GetURL = func(src string) (cacheURL string) {
|
||||||
|
|
||||||
|
c.Lock()
|
||||||
|
defer c.Unlock()
|
||||||
|
|
||||||
|
src = strings.Trim(src, "\r\n")
|
||||||
|
|
||||||
|
if c.caching == false {
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(src)
|
||||||
|
if err != nil || len(filepath.Ext(u.Path)) == 0 {
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
||||||
|
var filename = fmt.Sprintf("%s%s", strToMD5(src), filepath.Ext(u.Path))
|
||||||
|
if cacheURL, ok := c.images[fmt.Sprintf("%s%s", strToMD5(src), filepath.Ext(u.Path))]; ok {
|
||||||
|
return cacheURL
|
||||||
|
}
|
||||||
|
|
||||||
|
if indexOfString(filename, c.Cache) == -1 {
|
||||||
|
|
||||||
|
if indexOfString(src, c.Queue) == -1 {
|
||||||
|
c.Queue = append(c.Queue, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
c.images[filename] = c.cacheURL + filename
|
||||||
|
src = c.cacheURL + filename
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
if _, err := os.Stat(c.path + filename); err != nil {
|
||||||
|
//c.images[filename] = c.cacheURL + filename
|
||||||
|
if indexOfString(src, c.Queue) == -1 {
|
||||||
|
c.Queue = append(c.Queue, src)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.images[filename] = c.cacheURL + filename
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Image.Caching = func() {
|
||||||
|
|
||||||
|
c.Lock()
|
||||||
|
defer c.Unlock()
|
||||||
|
|
||||||
|
var filename string
|
||||||
|
|
||||||
|
for _, src := range c.Queue {
|
||||||
|
|
||||||
|
resp, err := http.Get(src)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
filename = fmt.Sprintf("%s%s%s%s", c.path, string(os.PathSeparator), strToMD5(src), filepath.Ext(src))
|
||||||
|
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(file, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(src)
|
||||||
|
if err == nil {
|
||||||
|
c.images[fmt.Sprintf("%s%s", strToMD5(src), filepath.Ext(u.Path))] = c.cacheURL + filename
|
||||||
|
}
|
||||||
|
|
||||||
|
queue = append(queue, src)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, q := range queue {
|
||||||
|
c.Queue = removeStringFromSlice(q, c.Queue)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Image.Remove = func() {
|
||||||
|
|
||||||
|
c.Lock()
|
||||||
|
defer c.Unlock()
|
||||||
|
|
||||||
|
files, err := ioutil.ReadDir(c.path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
|
||||||
|
switch c.caching {
|
||||||
|
|
||||||
|
case true:
|
||||||
|
if _, ok := c.images[file.Name()]; !ok {
|
||||||
|
os.RemoveAll(c.path + file.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
case false:
|
||||||
|
os.RemoveAll(c.path + file.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
files, err := ioutil.ReadDir(c.path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
c.Cache = append(c.Cache, file.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
34
src/internal/imgcache/tools.go
Normal file
34
src/internal/imgcache/tools.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package imgcache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
)
|
||||||
|
|
||||||
|
func strToMD5(str string) string {
|
||||||
|
md5Hasher := md5.New()
|
||||||
|
md5Hasher.Write([]byte(str))
|
||||||
|
return hex.EncodeToString(md5Hasher.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func indexOfString(str string, slice []string) int {
|
||||||
|
|
||||||
|
for i, v := range slice {
|
||||||
|
if str == v {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeStringFromSlice(str string, slice []string) []string {
|
||||||
|
|
||||||
|
var i = indexOfString(str, slice)
|
||||||
|
|
||||||
|
if i != -1 {
|
||||||
|
slice = append(slice[:i], slice[i+1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return slice
|
||||||
|
}
|
||||||
@@ -5,3 +5,9 @@ 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
|
#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
|
#123
|
||||||
http://example.com/stream/2
|
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
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package m3u
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -12,6 +14,7 @@ func MakeInterfaceFromM3U(byteStream []byte) (allChannels []interface{}, err err
|
|||||||
|
|
||||||
var content = string(byteStream)
|
var content = string(byteStream)
|
||||||
var channelName string
|
var channelName string
|
||||||
|
var uuids []string
|
||||||
|
|
||||||
var parseMetaData = func(channel string) (stream map[string]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)
|
line = strings.Replace(line, p, "", 1)
|
||||||
|
|
||||||
p = strings.Replace(p, `"`, "", -1)
|
p = strings.Replace(p, `"`, "", -1)
|
||||||
var parameter = strings.Split(p, "=")
|
var parameter = strings.SplitN(p, "=", 2)
|
||||||
|
|
||||||
if len(parameter) == 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 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.key"] = key
|
||||||
stream["_uuid.value"] = value
|
stream["_uuid.value"] = value
|
||||||
//os.Exit(0)
|
|
||||||
break
|
break
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -135,6 +144,10 @@ func MakeInterfaceFromM3U(byteStream []byte) (allChannels []interface{}, err err
|
|||||||
}
|
}
|
||||||
|
|
||||||
//fmt.Println(content)
|
//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") {
|
if strings.Contains(content, "#EXTM3U") {
|
||||||
|
|
||||||
@@ -154,114 +167,20 @@ func MakeInterfaceFromM3U(byteStream []byte) (allChannels []interface{}, err err
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
err = errors.New("No valid m3u file")
|
err = errors.New("Invalid M3U file, an extended M3U file is required.")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeInterfaceFromM3U2 :
|
func indexOfString(element string, data []string) int {
|
||||||
func MakeInterfaceFromM3U2(byteStream []byte) (allChannels []interface{}, err error) {
|
|
||||||
var content = string(byteStream)
|
|
||||||
//var allChannels = make([]interface{}, 0)
|
|
||||||
|
|
||||||
var channels = strings.Split(content, "#EXTINF")
|
for k, v := range data {
|
||||||
|
if element == v {
|
||||||
var parseMetaData = func(metaData string) map[string]string {
|
return k
|
||||||
var values string // Save all values in a key
|
|
||||||
var channel = make(map[string]string)
|
|
||||||
|
|
||||||
var exceptForParameter = `[a-z-A-Z=]*(".*?")`
|
|
||||||
//var exceptForChannelName = `(,[^.$\n]*|,[^.$\r]*)`
|
|
||||||
var exceptForChannelName = `(,[^\n]*|,[^\r]*)`
|
|
||||||
|
|
||||||
var exceptForStreamingURL = `(\n.*?\n|\r.*?\r|\n.*?\z|\r.*?\z)`
|
|
||||||
//var exceptForStreamingURL = `^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?`
|
|
||||||
|
|
||||||
// Parse all parameters
|
|
||||||
p := regexp.MustCompile(exceptForParameter)
|
|
||||||
var parameter = p.FindAllString(metaData, -1)
|
|
||||||
//fmt.Println(parameter)
|
|
||||||
for _, i := range parameter {
|
|
||||||
var remove = i
|
|
||||||
i = strings.Replace(i, `"`, "", -1)
|
|
||||||
if strings.Contains(i, "=") {
|
|
||||||
var item = strings.Split(i, "=")
|
|
||||||
switch strings.Contains(item[0], "tvg") {
|
|
||||||
case true:
|
|
||||||
channel[strings.ToLower(item[0])] = item[1]
|
|
||||||
case false:
|
|
||||||
channel[item[0]] = item[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
switch strings.Contains(item[1], "://") {
|
|
||||||
case false:
|
|
||||||
values = values + item[1] + " "
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
metaData = strings.Replace(metaData, remove, "", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse channel name (after the comma)
|
|
||||||
n := regexp.MustCompile(exceptForChannelName)
|
|
||||||
var name = n.FindAllString(metaData, 1)
|
|
||||||
//name[len(name) - 1] = strings.Replace(name[len(name) - 1], `\r`, "", -1)
|
|
||||||
|
|
||||||
var channelName string
|
|
||||||
if len(name) == 0 {
|
|
||||||
if v, ok := channel["tvg-name"]; ok {
|
|
||||||
channelName = v
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
channelName = name[len(name)-1][1:len(name[len(name)-1])]
|
|
||||||
}
|
|
||||||
|
|
||||||
channelName = strings.Replace(channelName, `"`, "", -1)
|
|
||||||
|
|
||||||
var replacer = strings.NewReplacer("\n", "", "\r", "")
|
|
||||||
channel["name"] = replacer.Replace(channelName)
|
|
||||||
|
|
||||||
values = values + channelName + " "
|
|
||||||
|
|
||||||
// Parse streaming URL
|
|
||||||
u := regexp.MustCompile(exceptForStreamingURL)
|
|
||||||
var streamingURL = u.FindAllString(metaData, -1)
|
|
||||||
var url = strings.Replace(streamingURL[0], "\n", "", -1)
|
|
||||||
url = strings.Replace(url, "\r", "", -1)
|
|
||||||
url = strings.Trim(url, "\r\n")
|
|
||||||
channel["url"] = url
|
|
||||||
|
|
||||||
channel["_values"] = values
|
|
||||||
|
|
||||||
// Search for a unique ID
|
|
||||||
|
|
||||||
for key, value := range channel {
|
|
||||||
if !strings.Contains(strings.ToLower(key), "tvg-id") {
|
|
||||||
if strings.Contains(strings.ToLower(key), "id") {
|
|
||||||
channel["_uuid.key"] = key
|
|
||||||
channel["_uuid.value"] = value
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return channel
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(channels[0], "#EXTM3U") {
|
|
||||||
|
|
||||||
for _, thisStream := range channels {
|
|
||||||
if !strings.Contains(thisStream, "#EXTM3U") {
|
|
||||||
var channel = parseMetaData(thisStream)
|
|
||||||
allChannels = append(allChannels, channel)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
return -1
|
||||||
err = errors.New("No valid m3u file")
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/m3u.go
10
src/m3u.go
@@ -9,7 +9,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
m3u "../src/internal/m3u-parser"
|
m3u "xteve/src/internal/m3u-parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Playlisten parsen
|
// Playlisten parsen
|
||||||
@@ -43,6 +43,10 @@ func filterThisStream(s interface{}) (status bool) {
|
|||||||
|
|
||||||
for _, filter := range Data.Filter {
|
for _, filter := range Data.Filter {
|
||||||
|
|
||||||
|
if filter.Rule == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
var group, name, search string
|
var group, name, search string
|
||||||
var exclude, include string
|
var exclude, include string
|
||||||
var match = false
|
var match = false
|
||||||
@@ -177,6 +181,7 @@ func checkConditions(streamValues, conditions, coType string) (status bool) {
|
|||||||
// xTeVe M3U Datei erstellen
|
// xTeVe M3U Datei erstellen
|
||||||
func buildM3U(groups []string) (m3u string, err error) {
|
func buildM3U(groups []string) (m3u string, err error) {
|
||||||
|
|
||||||
|
var imgc = Data.Cache.Images
|
||||||
var m3uChannels = make(map[float64]XEPGChannelStruct)
|
var m3uChannels = make(map[float64]XEPGChannelStruct)
|
||||||
var channelNumbers []float64
|
var channelNumbers []float64
|
||||||
|
|
||||||
@@ -219,7 +224,8 @@ func buildM3U(groups []string) (m3u string, err error) {
|
|||||||
for _, channelNumber := range channelNumbers {
|
for _, channelNumber := range channelNumbers {
|
||||||
|
|
||||||
var channel = m3uChannels[channelNumber]
|
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)
|
var stream, err = createStreamingURL("M3U", channel.FileM3UID, channel.XChannelID, channel.XName, channel.URL)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
m3u = m3u + parameter + stream + "\n"
|
m3u = m3u + parameter + stream + "\n"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"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)
|
// fileType: Welcher Dateityp soll aktualisiert werden (m3u, hdhr, xml) | fileID: Update einer bestimmten Datei (Provider ID)
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ import (
|
|||||||
|
|
||||||
func showInfo(str string) {
|
func showInfo(str string) {
|
||||||
|
|
||||||
var max = 22
|
if System.Flag.Info == true {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var max = 23
|
||||||
var msg = strings.SplitN(str, ":", 2)
|
var msg = strings.SplitN(str, ":", 2)
|
||||||
var length = len(msg[0])
|
var length = len(msg[0])
|
||||||
var space string
|
var space string
|
||||||
@@ -44,7 +48,7 @@ func showDebug(str string, level int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var max = 22
|
var max = 23
|
||||||
var msg = strings.SplitN(str, ":", 2)
|
var msg = strings.SplitN(str, ":", 2)
|
||||||
var length = len(msg[0])
|
var length = len(msg[0])
|
||||||
var space string
|
var space string
|
||||||
@@ -74,7 +78,7 @@ func showDebug(str string, level int) {
|
|||||||
|
|
||||||
func showHighlight(str string) {
|
func showHighlight(str string) {
|
||||||
|
|
||||||
var max = 22
|
var max = 23
|
||||||
var msg = strings.SplitN(str, ":", 2)
|
var msg = strings.SplitN(str, ":", 2)
|
||||||
var length = len(msg[0])
|
var length = len(msg[0])
|
||||||
var space string
|
var space string
|
||||||
@@ -228,7 +232,7 @@ func getErrMsg(errCode int) (errMsg string) {
|
|||||||
case 1004:
|
case 1004:
|
||||||
errMsg = fmt.Sprintf("File not found")
|
errMsg = fmt.Sprintf("File not found")
|
||||||
case 1005:
|
case 1005:
|
||||||
errMsg = fmt.Sprintf("Invalide m3u")
|
errMsg = fmt.Sprintf("Invalid M3U file, an extended M3U file is required.")
|
||||||
case 1006:
|
case 1006:
|
||||||
errMsg = fmt.Sprintf("No playlist!")
|
errMsg = fmt.Sprintf("No playlist!")
|
||||||
case 1007:
|
case 1007:
|
||||||
@@ -240,7 +244,9 @@ func getErrMsg(errCode int) (errMsg string) {
|
|||||||
case 1012:
|
case 1012:
|
||||||
errMsg = fmt.Sprintf("Invalid formatting of the time")
|
errMsg = fmt.Sprintf("Invalid formatting of the time")
|
||||||
case 1013:
|
case 1013:
|
||||||
errMsg = fmt.Sprintf("Invalid settings file (%s), file must be at least version %s", System.File.Settings, System.Compatibility)
|
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:
|
case 1020:
|
||||||
errMsg = fmt.Sprintf("Data could not be saved, invalid keyword")
|
errMsg = fmt.Sprintf("Data could not be saved, invalid keyword")
|
||||||
@@ -248,12 +254,13 @@ func getErrMsg(errCode int) (errMsg string) {
|
|||||||
// Datenbank Update
|
// Datenbank Update
|
||||||
case 1030:
|
case 1030:
|
||||||
errMsg = fmt.Sprintf("Invalid settings file (%s)", System.File.Settings)
|
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
|
// M3U Parser
|
||||||
case 1050:
|
case 1050:
|
||||||
errMsg = fmt.Sprintf("Invalid duration specification in the M3U8 playlist.")
|
errMsg = fmt.Sprintf("Invalid duration specification in the M3U8 playlist.")
|
||||||
|
|
||||||
// M3U Parser
|
|
||||||
case 1060:
|
case 1060:
|
||||||
errMsg = fmt.Sprintf("Invalid characters found in the tvg parameters, streams with invalid parameters were skipped.")
|
errMsg = fmt.Sprintf("Invalid characters found in the tvg parameters, streams with invalid parameters were skipped.")
|
||||||
|
|
||||||
@@ -262,6 +269,8 @@ func getErrMsg(errCode int) (errMsg string) {
|
|||||||
errMsg = fmt.Sprintf("Folder could not be created.")
|
errMsg = fmt.Sprintf("Folder could not be created.")
|
||||||
case 1071:
|
case 1071:
|
||||||
errMsg = fmt.Sprintf("File could not be created")
|
errMsg = fmt.Sprintf("File could not be created")
|
||||||
|
case 1072:
|
||||||
|
errMsg = fmt.Sprintf("File not found")
|
||||||
|
|
||||||
// Backup
|
// Backup
|
||||||
case 1090:
|
case 1090:
|
||||||
@@ -286,12 +295,14 @@ func getErrMsg(errCode int) (errMsg string) {
|
|||||||
errMsg = fmt.Sprintf("Steaming URL could not be found in any playlist")
|
errMsg = fmt.Sprintf("Steaming URL could not be found in any playlist")
|
||||||
case 1203:
|
case 1203:
|
||||||
errMsg = fmt.Sprintf("Steaming URL could not be found in any playlist")
|
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
|
// Warnings
|
||||||
case 2000:
|
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:
|
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:
|
case 2002:
|
||||||
errMsg = fmt.Sprintf("PMS can not play m3u8 streams")
|
errMsg = fmt.Sprintf("PMS can not play m3u8 streams")
|
||||||
case 2003:
|
case 2003:
|
||||||
@@ -302,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.")
|
errMsg = fmt.Sprintf("There are no channels mapped, use the mapping menu to assign EPG data to the channels.")
|
||||||
case 2010:
|
case 2010:
|
||||||
errMsg = fmt.Sprintf("No valid streaming URL")
|
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:
|
case 2099:
|
||||||
errMsg = fmt.Sprintf("Updates have been disabled by the developer")
|
errMsg = fmt.Sprintf("Updates have been disabled by the developer")
|
||||||
@@ -341,6 +356,8 @@ func getErrMsg(errCode int) (errMsg string) {
|
|||||||
errMsg = fmt.Sprintf("This error message comes from the provider")
|
errMsg = fmt.Sprintf("This error message comes from the provider")
|
||||||
case 4005:
|
case 4005:
|
||||||
errMsg = fmt.Sprintf("Temporary buffer files could not be deleted")
|
errMsg = fmt.Sprintf("Temporary buffer files could not be deleted")
|
||||||
|
case 4006:
|
||||||
|
errMsg = fmt.Sprintf("Server connection timeout")
|
||||||
|
|
||||||
// Buffer (M3U8)
|
// Buffer (M3U8)
|
||||||
case 4050:
|
case 4050:
|
||||||
@@ -351,6 +368,8 @@ func getErrMsg(errCode int) (errMsg string) {
|
|||||||
// Caching
|
// Caching
|
||||||
case 4100:
|
case 4100:
|
||||||
errMsg = fmt.Sprintf("Unknown content type for downloaded image")
|
errMsg = fmt.Sprintf("Unknown content type for downloaded image")
|
||||||
|
case 4101:
|
||||||
|
errMsg = fmt.Sprintf("Invalid URL, original URL is used for this image")
|
||||||
|
|
||||||
// API
|
// API
|
||||||
case 5000:
|
case 5000:
|
||||||
@@ -362,7 +381,7 @@ func getErrMsg(errCode int) (errMsg string) {
|
|||||||
case 6002:
|
case 6002:
|
||||||
errMsg = fmt.Sprintf("Update failed")
|
errMsg = fmt.Sprintf("Update failed")
|
||||||
case 6003:
|
case 6003:
|
||||||
errMsg = fmt.Sprintf("Server not available")
|
errMsg = fmt.Sprintf("Update server not available")
|
||||||
case 6004:
|
case 6004:
|
||||||
errMsg = fmt.Sprintf("xTeVe update available")
|
errMsg = fmt.Sprintf("xTeVe update available")
|
||||||
|
|
||||||
|
|||||||
49
src/ssdp.go
49
src/ssdp.go
@@ -11,24 +11,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// SSDP : SSPD / DLNA Server
|
// SSDP : SSPD / DLNA Server
|
||||||
func SSDP() {
|
func SSDP() (err error) {
|
||||||
|
|
||||||
showInfo(fmt.Sprintf("SSDP / DLNA:%t", Settings.SSDP))
|
if Settings.SSDP == false || System.Flag.Info == true {
|
||||||
|
|
||||||
if Settings.SSDP == false {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(10 * time.Second)
|
showInfo(fmt.Sprintf("SSDP / DLNA:%t", Settings.SSDP))
|
||||||
|
|
||||||
|
quit := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(quit, os.Interrupt)
|
||||||
|
|
||||||
ad, err := ssdp.Advertise(
|
ad, err := ssdp.Advertise(
|
||||||
"upnp:"+System.AppName, // send as "ST"
|
fmt.Sprintf("upnp:rootdevice"), // send as "ST"
|
||||||
System.DeviceID+"::upnp:"+System.AppName, // send as "USN"
|
fmt.Sprintf("uuid:%s::upnp:rootdevice", System.DeviceID), // send as "USN"
|
||||||
System.URLBase+"/device.xml", // send as "LOCATION"
|
fmt.Sprintf("%s/device.xml", System.URLBase), // send as "LOCATION"
|
||||||
System.AppName, // send as "SERVER"
|
System.AppName, // send as "SERVER"
|
||||||
1800) // send as "maxAge" in "CACHE-CONTROL"
|
1800) // send as "maxAge" in "CACHE-CONTROL"
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ShowError(err, 000)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug SSDP
|
// Debug SSDP
|
||||||
@@ -36,27 +38,27 @@ func SSDP() {
|
|||||||
ssdp.Logger = log.New(os.Stderr, "[SSDP] ", log.LstdFlags)
|
ssdp.Logger = log.New(os.Stderr, "[SSDP] ", log.LstdFlags)
|
||||||
}
|
}
|
||||||
|
|
||||||
var aliveTick <-chan time.Time
|
go func(adv *ssdp.Advertiser) {
|
||||||
var ai = 10
|
|
||||||
|
|
||||||
if ai > 0 {
|
aliveTick := time.Tick(300 * time.Second)
|
||||||
aliveTick = time.Tick(time.Duration(ai) * time.Second)
|
|
||||||
} else {
|
|
||||||
aliveTick = make(chan time.Time)
|
|
||||||
}
|
|
||||||
|
|
||||||
quit := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(quit, os.Interrupt)
|
|
||||||
|
|
||||||
loop:
|
loop:
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
|
||||||
case <-aliveTick:
|
case <-aliveTick:
|
||||||
ad.Alive()
|
err = adv.Alive()
|
||||||
|
if err != nil {
|
||||||
|
ShowError(err, 0)
|
||||||
|
adv.Bye()
|
||||||
|
adv.Close()
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
|
||||||
case <-quit:
|
case <-quit:
|
||||||
|
adv.Bye()
|
||||||
|
adv.Close()
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
break loop
|
break loop
|
||||||
|
|
||||||
@@ -64,6 +66,7 @@ loop:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ad.Bye()
|
}(ad)
|
||||||
ad.Close()
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ type ThisStream struct {
|
|||||||
type Segment struct {
|
type Segment struct {
|
||||||
Duration float64
|
Duration float64
|
||||||
Info bool
|
Info bool
|
||||||
|
PlaylistType string
|
||||||
Sequence int64
|
Sequence int64
|
||||||
URL string
|
URL string
|
||||||
Version int
|
Version int
|
||||||
@@ -107,3 +108,147 @@ type BandwidthCalculation struct {
|
|||||||
Stop time.Time
|
Stop time.Time
|
||||||
TimeDiff float64
|
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)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package src
|
package src
|
||||||
|
|
||||||
|
import "xteve/src/internal/imgcache"
|
||||||
|
|
||||||
// SystemStruct : Beinhaltet alle Systeminformationen
|
// SystemStruct : Beinhaltet alle Systeminformationen
|
||||||
type SystemStruct struct {
|
type SystemStruct struct {
|
||||||
Addresses struct {
|
Addresses struct {
|
||||||
@@ -11,14 +13,27 @@ type SystemStruct struct {
|
|||||||
APIVersion string
|
APIVersion string
|
||||||
AppName string
|
AppName string
|
||||||
ARCH string
|
ARCH string
|
||||||
|
BackgroundProcess bool
|
||||||
Branch string
|
Branch string
|
||||||
Build string
|
Build string
|
||||||
Compatibility string
|
Compatibility string
|
||||||
ConfigurationWizard bool
|
ConfigurationWizard bool
|
||||||
|
DBVersion string
|
||||||
Dev bool
|
Dev bool
|
||||||
DeviceID string
|
DeviceID string
|
||||||
Domain string
|
Domain string
|
||||||
DVRLimit int
|
PlexChannelLimit int
|
||||||
|
UnfilteredChannelLimit int
|
||||||
|
|
||||||
|
FFmpeg struct {
|
||||||
|
DefaultOptions string
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
VLC struct {
|
||||||
|
DefaultOptions string
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
File struct {
|
File struct {
|
||||||
Authentication string
|
Authentication string
|
||||||
@@ -30,10 +45,16 @@ type SystemStruct struct {
|
|||||||
XML string
|
XML string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Compressed struct {
|
||||||
|
GZxml string
|
||||||
|
}
|
||||||
|
|
||||||
Flag struct {
|
Flag struct {
|
||||||
Branch string
|
Branch string
|
||||||
Debug int
|
Debug int
|
||||||
|
Info bool
|
||||||
Port string
|
Port string
|
||||||
|
Restore string
|
||||||
SSDP bool
|
SSDP bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,6 +102,7 @@ type SystemStruct struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
URLBase string
|
URLBase string
|
||||||
|
UDPxy string
|
||||||
Version string
|
Version string
|
||||||
WEB struct {
|
WEB struct {
|
||||||
Menu []string
|
Menu []string
|
||||||
@@ -96,6 +118,7 @@ type GitStruct struct {
|
|||||||
// DataStruct : Alle Daten werden hier abgelegt. (Lineup, XMLTV)
|
// DataStruct : Alle Daten werden hier abgelegt. (Lineup, XMLTV)
|
||||||
type DataStruct struct {
|
type DataStruct struct {
|
||||||
Cache struct {
|
Cache struct {
|
||||||
|
Images *imgcache.Cache
|
||||||
ImagesCache []string
|
ImagesCache []string
|
||||||
ImagesFiles []string
|
ImagesFiles []string
|
||||||
ImagesURLS []string
|
ImagesURLS []string
|
||||||
@@ -173,6 +196,7 @@ type XEPGChannelStruct struct {
|
|||||||
XName string `json:"x-name,required"`
|
XName string `json:"x-name,required"`
|
||||||
XUpdateChannelIcon bool `json:"x-update-channel-icon,required"`
|
XUpdateChannelIcon bool `json:"x-update-channel-icon,required"`
|
||||||
XUpdateChannelName bool `json:"x-update-channel-name,required"`
|
XUpdateChannelName bool `json:"x-update-channel-name,required"`
|
||||||
|
XDescription string `json:"x-description,required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// M3UChannelStructXEPG : M3U Struktur für XEPG
|
// M3UChannelStructXEPG : M3U Struktur für XEPG
|
||||||
@@ -227,8 +251,8 @@ type Notification struct {
|
|||||||
Type string `json:"type,required"`
|
Type string `json:"type,required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SettingsStrcut : Inhalt der settings.json
|
// SettingsStruct : Inhalt der settings.json
|
||||||
type SettingsStrcut struct {
|
type SettingsStruct struct {
|
||||||
API bool `json:"api"`
|
API bool `json:"api"`
|
||||||
AuthenticationAPI bool `json:"authentication.api"`
|
AuthenticationAPI bool `json:"authentication.api"`
|
||||||
AuthenticationM3U bool `json:"authentication.m3u"`
|
AuthenticationM3U bool `json:"authentication.m3u"`
|
||||||
@@ -238,11 +262,15 @@ type SettingsStrcut struct {
|
|||||||
BackupKeep int `json:"backup.keep"`
|
BackupKeep int `json:"backup.keep"`
|
||||||
BackupPath string `json:"backup.path"`
|
BackupPath string `json:"backup.path"`
|
||||||
Branch string `json:"git.branch,omitempty"`
|
Branch string `json:"git.branch,omitempty"`
|
||||||
Buffer bool `json:"buffer"`
|
Buffer string `json:"buffer"`
|
||||||
BufferSize int `json:"buffer.size.kb"`
|
BufferSize int `json:"buffer.size.kb"`
|
||||||
BufferTimeout float64 `json:"buffer.timeout"`
|
BufferTimeout float64 `json:"buffer.timeout"`
|
||||||
CacheImages bool `json:"cache.images"`
|
CacheImages bool `json:"cache.images"`
|
||||||
EpgSource string `json:"epgSource"`
|
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
|
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)
|
FileXMLTV []string `json:"xmltv,omitempty"` // Altes Speichersystem der Provider XML Datei Slice (Wird für die Umwandlung auf das neue benötigt)
|
||||||
|
|
||||||
@@ -267,6 +295,7 @@ type SettingsStrcut struct {
|
|||||||
UpdateURL string `json:"update.url,omitempty"`
|
UpdateURL string `json:"update.url,omitempty"`
|
||||||
UserAgent string `json:"user.agent"`
|
UserAgent string `json:"user.agent"`
|
||||||
UUID string `json:"uuid"`
|
UUID string `json:"uuid"`
|
||||||
|
UDPxy string `json:"udpxy"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
XepgReplaceMissingImages bool `json:"xepg.replace.missing.images"`
|
XepgReplaceMissingImages bool `json:"xepg.replace.missing.images"`
|
||||||
XteveAutoUpdate bool `json:"xteveAutoUpdate"`
|
XteveAutoUpdate bool `json:"xteveAutoUpdate"`
|
||||||
|
|||||||
@@ -25,18 +25,25 @@ type RequestStruct struct {
|
|||||||
AuthenticationXML *bool `json:"authentication.xml,omitempty"`
|
AuthenticationXML *bool `json:"authentication.xml,omitempty"`
|
||||||
BackupKeep *int `json:"backup.keep,omitempty"`
|
BackupKeep *int `json:"backup.keep,omitempty"`
|
||||||
BackupPath *string `json:"backup.path,omitempty"`
|
BackupPath *string `json:"backup.path,omitempty"`
|
||||||
Buffer *bool `json:"buffer,omitempty"`
|
Buffer *string `json:"buffer,omitempty"`
|
||||||
BufferSize *int `json:"buffer.size.kb, omitempty"`
|
BufferSize *int `json:"buffer.size.kb, omitempty"`
|
||||||
BufferTimeout *float64 `json:"buffer.timeout,omitempty"`
|
BufferTimeout *float64 `json:"buffer.timeout,omitempty"`
|
||||||
CacheImages *bool `json:"cache.images,omitempty"`
|
CacheImages *bool `json:"cache.images,omitempty"`
|
||||||
EpgSource *string `json:"epgSource,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"`
|
FilesUpdate *bool `json:"files.update,omitempty"`
|
||||||
TempPath *string `json:"temp.path,omitempty"`
|
TempPath *string `json:"temp.path,omitempty"`
|
||||||
Tuner *int `json:"tuner,omitempty"`
|
Tuner *int `json:"tuner,omitempty"`
|
||||||
|
UDPxy *string `json:"udpxy,omitempty"`
|
||||||
Update *[]string `json:"update,omitempty"`
|
Update *[]string `json:"update,omitempty"`
|
||||||
UserAgent *string `json:"user.agent,omitempty"`
|
UserAgent *string `json:"user.agent,omitempty"`
|
||||||
XepgReplaceMissingImages *bool `json:"xepg.replace.missing.images,omitempty"`
|
XepgReplaceMissingImages *bool `json:"xepg.replace.missing.images,omitempty"`
|
||||||
XteveAutoUpdate *bool `json:"xteveAutoUpdate,omitempty"`
|
XteveAutoUpdate *bool `json:"xteveAutoUpdate,omitempty"`
|
||||||
|
SchemeM3U *string `json:"scheme.m3u,omitempty"`
|
||||||
|
SchemeXML *string `json:"scheme.xml,omitempty"`
|
||||||
} `json:"settings,omitempty"`
|
} `json:"settings,omitempty"`
|
||||||
|
|
||||||
// Upload Logo
|
// Upload Logo
|
||||||
@@ -103,7 +110,7 @@ type ResponseStruct struct {
|
|||||||
OpenLink string `json:"openLink,omitempty"`
|
OpenLink string `json:"openLink,omitempty"`
|
||||||
OpenMenu string `json:"openMenu,omitempty"`
|
OpenMenu string `json:"openMenu,omitempty"`
|
||||||
Reload bool `json:"reload,omitempty"`
|
Reload bool `json:"reload,omitempty"`
|
||||||
Settings SettingsStrcut `json:"settings,required"`
|
Settings SettingsStruct `json:"settings,required"`
|
||||||
Status bool `json:"status,required"`
|
Status bool `json:"status,required"`
|
||||||
Token string `json:"token,omitempty"`
|
Token string `json:"token,omitempty"`
|
||||||
Users map[string]interface{} `json:"users,omitempty"`
|
Users map[string]interface{} `json:"users,omitempty"`
|
||||||
|
|||||||
@@ -42,12 +42,16 @@ type Program struct {
|
|||||||
Country []*Country `xml:"country"`
|
Country []*Country `xml:"country"`
|
||||||
EpisodeNum []*EpisodeNum `xml:"episode-num"`
|
EpisodeNum []*EpisodeNum `xml:"episode-num"`
|
||||||
Poster []Poster `xml:"icon"`
|
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"`
|
Language []*Language `xml:"language"`
|
||||||
Video Video `xml:"video"`
|
Video Video `xml:"video"`
|
||||||
Date string `xml:"date"`
|
Date string `xml:"date"`
|
||||||
PreviouslyShown *PreviouslyShown `xml:"previously-shown"`
|
PreviouslyShown *PreviouslyShown `xml:"previously-shown"`
|
||||||
New *New `xml:"new"`
|
New *New `xml:"new"`
|
||||||
Live *Live `xml:"live"`
|
Live *Live `xml:"live"`
|
||||||
|
Premiere *Live `xml:"premiere"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Title : Programmtitel
|
// Title : Programmtitel
|
||||||
@@ -74,6 +78,19 @@ type Category struct {
|
|||||||
Value string `xml:",chardata"`
|
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
|
// Language : Sprachen
|
||||||
type Language struct {
|
type Language struct {
|
||||||
Value string `xml:",chardata"`
|
Value string `xml:",chardata"`
|
||||||
@@ -99,6 +116,41 @@ type Poster struct {
|
|||||||
Width string `xml:"width,attr"`
|
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
|
// Video : Video Metadaten
|
||||||
type Video struct {
|
type Video struct {
|
||||||
Aspect string `xml:"aspect,omitempty"`
|
Aspect string `xml:"aspect,omitempty"`
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ func createSystemFiles() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Einstellungen laden und default Werte setzen (xTeVe)
|
// 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)
|
settingsMap, err := loadJSONFileToMap(System.File.Settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -113,11 +113,13 @@ func loadSettings() (settings SettingsStrcut, err error) {
|
|||||||
defaults["authentication.xml"] = false
|
defaults["authentication.xml"] = false
|
||||||
defaults["backup.keep"] = 10
|
defaults["backup.keep"] = 10
|
||||||
defaults["backup.path"] = System.Folder.Backup
|
defaults["backup.path"] = System.Folder.Backup
|
||||||
defaults["buffer"] = false
|
defaults["buffer"] = "-"
|
||||||
defaults["buffer.size.kb"] = 1024
|
defaults["buffer.size.kb"] = 1024
|
||||||
defaults["buffer.timeout"] = 500
|
defaults["buffer.timeout"] = 500
|
||||||
defaults["cache.images"] = false
|
defaults["cache.images"] = false
|
||||||
defaults["epgSource"] = "XEPG"
|
defaults["epgSource"] = "PMS"
|
||||||
|
defaults["ffmpeg.options"] = System.FFmpeg.DefaultOptions
|
||||||
|
defaults["vlc.options"] = System.VLC.DefaultOptions
|
||||||
defaults["files"] = dataMap
|
defaults["files"] = dataMap
|
||||||
defaults["files.update"] = true
|
defaults["files.update"] = true
|
||||||
defaults["filter"] = make(map[string]interface{})
|
defaults["filter"] = make(map[string]interface{})
|
||||||
@@ -133,7 +135,8 @@ func loadSettings() (settings SettingsStrcut, err error) {
|
|||||||
defaults["update"] = []string{"0000"}
|
defaults["update"] = []string{"0000"}
|
||||||
defaults["user.agent"] = System.Name
|
defaults["user.agent"] = System.Name
|
||||||
defaults["uuid"] = createUUID()
|
defaults["uuid"] = createUUID()
|
||||||
defaults["version"] = System.Version
|
defaults["udpxy"] = ""
|
||||||
|
defaults["version"] = System.DBVersion
|
||||||
defaults["xteveAutoUpdate"] = true
|
defaults["xteveAutoUpdate"] = true
|
||||||
defaults["temp.path"] = System.Folder.Temp
|
defaults["temp.path"] = System.Folder.Temp
|
||||||
|
|
||||||
@@ -159,13 +162,32 @@ func loadSettings() (settings SettingsStrcut, err error) {
|
|||||||
showInfo(fmt.Sprintf("Git Branch:Switching Git Branch to -> %s", settings.Branch))
|
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)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Einstellungen speichern (xTeVe)
|
// Einstellungen speichern (xTeVe)
|
||||||
func saveSettings(settings SettingsStrcut) (err error) {
|
func saveSettings(settings SettingsStruct) (err error) {
|
||||||
|
|
||||||
if settings.BackupKeep == 0 {
|
if settings.BackupKeep == 0 {
|
||||||
settings.BackupKeep = 10
|
settings.BackupKeep = 10
|
||||||
|
|||||||
@@ -10,8 +10,11 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -42,13 +45,25 @@ func checkFolder(path string) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prüft ob die datei im Dateisystem existiert
|
// Prüft ob die Datei im Dateisystem existiert
|
||||||
func checkFile(filename string) (err error) {
|
func checkFile(filename string) (err error) {
|
||||||
|
|
||||||
var file = getPlatformFile(filename)
|
var file = getPlatformFile(filename)
|
||||||
|
|
||||||
if _, err = os.Stat(file); os.IsNotExist(err) {
|
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
|
return
|
||||||
@@ -77,6 +92,7 @@ func GetUserHomeDirectory() (userHomeDirectory string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prüft Dateiberechtigung
|
||||||
func checkFilePermission(dir string) (err error) {
|
func checkFilePermission(dir string) (err error) {
|
||||||
|
|
||||||
var filename = dir + "permission.test"
|
var filename = dir + "permission.test"
|
||||||
@@ -115,6 +131,34 @@ func removeOldSystemData() {
|
|||||||
os.RemoveAll(System.Folder.Temp)
|
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 {
|
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()
|
System.Hostname, err = os.Hostname()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
up2date "../src/internal/up2date/client"
|
up2date "xteve/src/internal/up2date/client"
|
||||||
|
|
||||||
"reflect"
|
"reflect"
|
||||||
)
|
)
|
||||||
@@ -41,8 +41,8 @@ func BinaryUpdate() (err error) {
|
|||||||
|
|
||||||
resp, err := http.Get(gitInfo)
|
resp, err := http.Get(gitInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ShowError(err, 0)
|
ShowError(err, 6003)
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
@@ -169,6 +169,13 @@ checkVersion:
|
|||||||
|
|
||||||
if settingsVersion, ok := settingsMap["version"].(string); ok {
|
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)
|
// Letzte Kompatible Version (1.4.4)
|
||||||
if settingsVersion < System.Compatibility {
|
if settingsVersion < System.Compatibility {
|
||||||
err = errors.New(getErrMsg(1013))
|
err = errors.New(getErrMsg(1013))
|
||||||
@@ -189,7 +196,7 @@ checkVersion:
|
|||||||
var newFilterMap = convertToNewFilter(oldFilter)
|
var newFilterMap = convertToNewFilter(oldFilter)
|
||||||
settingsMap["filter"] = newFilterMap
|
settingsMap["filter"] = newFilterMap
|
||||||
|
|
||||||
settingsMap["version"] = "1.9.0"
|
settingsMap["version"] = "2.0.0"
|
||||||
|
|
||||||
err = saveMapToJSONFile(System.File.Settings, settingsMap)
|
err = saveMapToJSONFile(System.File.Settings, settingsMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -203,11 +210,38 @@ checkVersion:
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
case "1.9.0":
|
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
|
// Falls es in einem späteren Update Änderungen an der Datenbank gibt, geht es hier weiter
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
79
src/webUI.go
79
src/webUI.go
File diff suppressed because one or more lines are too long
@@ -10,7 +10,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"../src/internal/authentication"
|
"xteve/src/internal/authentication"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
@@ -30,12 +30,25 @@ func StartWebserver() (err error) {
|
|||||||
http.HandleFunc("/api/", API)
|
http.HandleFunc("/api/", API)
|
||||||
http.HandleFunc("/images/", Images)
|
http.HandleFunc("/images/", Images)
|
||||||
http.HandleFunc("/data_images/", DataImages)
|
http.HandleFunc("/data_images/", DataImages)
|
||||||
|
|
||||||
//http.HandleFunc("/auto/", Auto)
|
//http.HandleFunc("/auto/", Auto)
|
||||||
|
|
||||||
showInfo("DVR IP:" + System.IPAddress + ":" + Settings.Port)
|
showInfo("DVR IP:" + System.IPAddress + ":" + Settings.Port)
|
||||||
|
|
||||||
|
var ips = len(System.IPAddressesV4) + len(System.IPAddressesV6) - 1
|
||||||
|
switch ips {
|
||||||
|
|
||||||
|
case 0:
|
||||||
showHighlight(fmt.Sprintf("Web Interface:%s://%s:%s/web/", System.ServerProtocol.WEB, System.IPAddress, Settings.Port))
|
showHighlight(fmt.Sprintf("Web Interface:%s://%s:%s/web/", System.ServerProtocol.WEB, System.IPAddress, Settings.Port))
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
showHighlight(fmt.Sprintf("Web Interface:%s://%s:%s/web/ | xTeVe is also available via the other %d IP.", System.ServerProtocol.WEB, System.IPAddress, Settings.Port, ips))
|
||||||
|
|
||||||
|
default:
|
||||||
|
showHighlight(fmt.Sprintf("Web Interface:%s://%s:%s/web/ | xTeVe is also available via the other %d IP's.", System.ServerProtocol.WEB, System.IPAddress, Settings.Port, len(System.IPAddressesV4)+len(System.IPAddressesV6)-1))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if err = http.ListenAndServe(":"+port, nil); err != nil {
|
if err = http.ListenAndServe(":"+port, nil); err != nil {
|
||||||
ShowError(err, 1001)
|
ShowError(err, 1001)
|
||||||
return
|
return
|
||||||
@@ -117,6 +130,18 @@ func Stream(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If an UDPxy host is set, and the stream URL is multicast (i.e. starts with 'udp://@'),
|
||||||
|
// then streamInfo.URL needs to be rewritten to point to UDPxy.
|
||||||
|
if Settings.UDPxy != "" && strings.HasPrefix(streamInfo.URL, "udp://@") {
|
||||||
|
streamInfo.URL = fmt.Sprintf("http://%s/udp/%s/", Settings.UDPxy, strings.TrimPrefix(streamInfo.URL, "udp://@"))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch Settings.Buffer {
|
||||||
|
|
||||||
|
case "-":
|
||||||
|
showInfo(fmt.Sprintf("Buffer:false [%s]", Settings.Buffer))
|
||||||
|
|
||||||
|
case "xteve":
|
||||||
if strings.Index(streamInfo.URL, "rtsp://") != -1 || strings.Index(streamInfo.URL, "rtp://") != -1 {
|
if strings.Index(streamInfo.URL, "rtsp://") != -1 || strings.Index(streamInfo.URL, "rtp://") != -1 {
|
||||||
err = errors.New("RTSP and RTP streams are not supported")
|
err = errors.New("RTSP and RTP streams are not supported")
|
||||||
ShowError(err, 2004)
|
ShowError(err, 2004)
|
||||||
@@ -128,9 +153,14 @@ func Stream(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
showInfo(fmt.Sprintf("Buffer:%t", Settings.Buffer))
|
showInfo(fmt.Sprintf("Buffer:true [%s]", Settings.Buffer))
|
||||||
|
|
||||||
if Settings.Buffer == true {
|
default:
|
||||||
|
showInfo(fmt.Sprintf("Buffer:true [%s]", Settings.Buffer))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if Settings.Buffer != "-" {
|
||||||
showInfo(fmt.Sprintf("Buffer Size:%d KB", Settings.BufferSize))
|
showInfo(fmt.Sprintf("Buffer Size:%d KB", Settings.BufferSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,14 +170,15 @@ func Stream(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Prüfen ob der Buffer verwendet werden soll
|
// Prüfen ob der Buffer verwendet werden soll
|
||||||
switch Settings.Buffer {
|
switch Settings.Buffer {
|
||||||
|
|
||||||
case true:
|
case "-":
|
||||||
bufferingStream(streamInfo.PlaylistID, streamInfo.URL, streamInfo.Name, w, r)
|
|
||||||
|
|
||||||
case false:
|
|
||||||
showInfo("Streaming URL:" + streamInfo.URL)
|
showInfo("Streaming URL:" + streamInfo.URL)
|
||||||
http.Redirect(w, r, streamInfo.URL, 302)
|
http.Redirect(w, r, streamInfo.URL, 302)
|
||||||
|
|
||||||
showInfo("Streaming Info:URL was passed to the client")
|
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)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +213,7 @@ func Auto(w http.ResponseWriter, r *http.Request) {
|
|||||||
// xTeVe : Web Server /xmltv/ und /m3u/
|
// xTeVe : Web Server /xmltv/ und /m3u/
|
||||||
func xTeVe(w http.ResponseWriter, r *http.Request) {
|
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 err error
|
||||||
var path = strings.TrimPrefix(r.URL.Path, "/")
|
var path = strings.TrimPrefix(r.URL.Path, "/")
|
||||||
var groups = []string{}
|
var groups = []string{}
|
||||||
@@ -235,6 +266,13 @@ func xTeVe(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
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 {
|
if err == nil {
|
||||||
w.Write([]byte(content))
|
w.Write([]byte(content))
|
||||||
}
|
}
|
||||||
@@ -291,17 +329,22 @@ func WS(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
var newToken string
|
var newToken string
|
||||||
|
|
||||||
|
/*
|
||||||
if r.Header.Get("Origin") != "http://"+r.Host {
|
if r.Header.Get("Origin") != "http://"+r.Host {
|
||||||
httpStatusError(w, r, 403)
|
httpStatusError(w, r, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
conn, err := websocket.Upgrade(w, r, w.Header(), 1024, 1024)
|
conn, err := websocket.Upgrade(w, r, w.Header(), 1024, 1024)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ShowError(err, 0)
|
ShowError(err, 0)
|
||||||
http.Error(w, "Could not open websocket connection", http.StatusBadRequest)
|
http.Error(w, "Could not open websocket connection", http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setGlobalDomain(r.Host)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|
||||||
err = conn.ReadJSON(&request)
|
err = conn.ReadJSON(&request)
|
||||||
@@ -447,7 +490,7 @@ func WS(w http.ResponseWriter, r *http.Request) {
|
|||||||
file, errNew := xteveBackup()
|
file, errNew := xteveBackup()
|
||||||
err = errNew
|
err = errNew
|
||||||
if err == nil {
|
if err == nil {
|
||||||
response.OpenLink = System.URLBase + "/download/" + file
|
response.OpenLink = fmt.Sprintf("%s://%s/download/%s", System.ServerProtocol.WEB, System.Domain, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
case "xteveRestore":
|
case "xteveRestore":
|
||||||
@@ -457,9 +500,10 @@ func WS(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if len(request.Base64) > 0 {
|
if len(request.Base64) > 0 {
|
||||||
|
|
||||||
newWebURL, err := xteveRestore(request.Base64)
|
newWebURL, err := xteveRestoreFromWeb(request.Base64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ShowError(err, 000)
|
ShowError(err, 000)
|
||||||
|
response.Alert = err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -528,7 +572,6 @@ func WS(w http.ResponseWriter, r *http.Request) {
|
|||||||
response.Settings = Settings
|
response.Settings = Settings
|
||||||
}
|
}
|
||||||
|
|
||||||
setGlobalDomain(r.Host)
|
|
||||||
response = setDefaultResponseData(response, true)
|
response = setDefaultResponseData(response, true)
|
||||||
if System.ConfigurationWizard == true {
|
if System.ConfigurationWizard == true {
|
||||||
response.ConfigurationWizard = System.ConfigurationWizard
|
response.ConfigurationWizard = System.ConfigurationWizard
|
||||||
@@ -556,6 +599,8 @@ func Web(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
var language LanguageUI
|
var language LanguageUI
|
||||||
|
|
||||||
|
setGlobalDomain(r.Host)
|
||||||
|
|
||||||
if System.Dev == true {
|
if System.Dev == true {
|
||||||
|
|
||||||
lang, err = loadJSONFileToMap(fmt.Sprintf("html/lang/%s.json", Settings.Language))
|
lang, err = loadJSONFileToMap(fmt.Sprintf("html/lang/%s.json", Settings.Language))
|
||||||
@@ -582,10 +627,14 @@ func Web(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if getFilenameFromPath(requestFile) == "html" {
|
if getFilenameFromPath(requestFile) == "html" {
|
||||||
|
|
||||||
if len(Data.Streams.All) == 0 && System.ScanInProgress == 0 {
|
if System.ScanInProgress == 0 {
|
||||||
|
|
||||||
|
if len(Settings.Files.M3U) == 0 && len(Settings.Files.HDHR) == 0 {
|
||||||
System.ConfigurationWizard = true
|
System.ConfigurationWizard = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
switch System.ConfigurationWizard {
|
switch System.ConfigurationWizard {
|
||||||
|
|
||||||
case true:
|
case true:
|
||||||
@@ -867,8 +916,6 @@ func API(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
case "status":
|
case "status":
|
||||||
|
|
||||||
fmt.Println("-----------------------------")
|
|
||||||
os.Exit(0)
|
|
||||||
response.VersionXteve = System.Version
|
response.VersionXteve = System.Version
|
||||||
response.VersionAPI = System.APIVersion
|
response.VersionAPI = System.APIVersion
|
||||||
response.StreamsActive = int64(len(Data.Streams.Active))
|
response.StreamsActive = int64(len(Data.Streams.Active))
|
||||||
|
|||||||
238
src/xepg.go
238
src/xepg.go
@@ -8,10 +8,15 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"xteve/src/internal/imgcache"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provider XMLTV Datei überprüfen
|
// Provider XMLTV Datei überprüfen
|
||||||
@@ -42,6 +47,13 @@ func buildXEPG(background bool) {
|
|||||||
|
|
||||||
System.ScanInProgress = 1
|
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" {
|
if Settings.EpgSource == "XEPG" {
|
||||||
|
|
||||||
switch background {
|
switch background {
|
||||||
@@ -56,10 +68,29 @@ func buildXEPG(background bool) {
|
|||||||
cleanupXEPG()
|
cleanupXEPG()
|
||||||
createXMLTVFile()
|
createXMLTVFile()
|
||||||
createM3UFile()
|
createM3UFile()
|
||||||
go cachingImages()
|
|
||||||
|
|
||||||
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
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
|
System.ScanInProgress = 0
|
||||||
|
|
||||||
// Cache löschen
|
// Cache löschen
|
||||||
@@ -82,7 +113,27 @@ func buildXEPG(background bool) {
|
|||||||
|
|
||||||
createXMLTVFile()
|
createXMLTVFile()
|
||||||
createM3UFile()
|
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"))
|
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
||||||
|
|
||||||
System.ScanInProgress = 0
|
System.ScanInProgress = 0
|
||||||
@@ -174,9 +225,7 @@ func createXEPGMapping() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(Data.XMLTV.Files) == 0 {
|
if len(Data.XMLTV.Files) > 0 {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := len(Data.XMLTV.Files) - 1; i >= 0; i-- {
|
for i := len(Data.XMLTV.Files) - 1; i >= 0; i-- {
|
||||||
|
|
||||||
@@ -221,9 +270,20 @@ func createXEPGMapping() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Data.XMLTV.Mapping = tmpMap
|
||||||
|
tmpMap = make(map[string]interface{})
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if System.ConfigurationWizard == false {
|
||||||
|
showWarning(1007)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Auswahl für den Dummy erstellen
|
// Auswahl für den Dummy erstellen
|
||||||
var dummy = make(map[string]interface{})
|
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 {
|
for _, i := range times {
|
||||||
|
|
||||||
@@ -236,19 +296,17 @@ func createXEPGMapping() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Data.XMLTV.Mapping = tmpMap
|
|
||||||
Data.XMLTV.Mapping["xTeVe Dummy"] = dummy
|
Data.XMLTV.Mapping["xTeVe Dummy"] = dummy
|
||||||
|
|
||||||
tmpMap = make(map[string]interface{})
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// XEPG Datenbank erstellen / aktualisieren
|
// XEPG Datenbank erstellen / aktualisieren
|
||||||
func createXEPGDatabase() (err error) {
|
func createXEPGDatabase() (err error) {
|
||||||
|
|
||||||
var allChannelNumbers []float64
|
var allChannelNumbers = make([]float64, 0, System.UnfilteredChannelLimit)
|
||||||
Data.Cache.Streams.Active = []string{}
|
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)
|
Data.XEPG.Channels, err = loadJSONFileToMap(System.File.XEPG)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -273,24 +331,33 @@ func createXEPGDatabase() (err error) {
|
|||||||
|
|
||||||
var getFreeChannelNumber = func() (xChannelID string) {
|
var getFreeChannelNumber = func() (xChannelID string) {
|
||||||
|
|
||||||
|
sort.Float64s(allChannelNumbers)
|
||||||
|
|
||||||
var firstFreeNumber float64 = Settings.MappingFirstChannel
|
var firstFreeNumber float64 = Settings.MappingFirstChannel
|
||||||
|
|
||||||
newNumber:
|
for {
|
||||||
|
|
||||||
if indexOfFloat64(firstFreeNumber, allChannelNumbers) == -1 {
|
if indexOfFloat64(firstFreeNumber, allChannelNumbers) == -1 {
|
||||||
xChannelID = fmt.Sprintf("%g", firstFreeNumber)
|
xChannelID = fmt.Sprintf("%g", firstFreeNumber)
|
||||||
allChannelNumbers = append(allChannelNumbers, firstFreeNumber)
|
allChannelNumbers = append(allChannelNumbers, firstFreeNumber)
|
||||||
} else {
|
return
|
||||||
|
}
|
||||||
|
|
||||||
firstFreeNumber++
|
firstFreeNumber++
|
||||||
goto newNumber
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
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")
|
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 {
|
for id, dxc := range Data.XEPG.Channels {
|
||||||
|
|
||||||
var xepgChannel XEPGChannelStruct
|
var xepgChannel XEPGChannelStruct
|
||||||
@@ -300,7 +367,6 @@ func createXEPGDatabase() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(xepgChannel.XChannelID) == 0 {
|
if len(xepgChannel.XChannelID) == 0 {
|
||||||
fmt.Println(mapToJSON(xepgChannel))
|
|
||||||
delete(Data.XEPG.Channels, id)
|
delete(Data.XEPG.Channels, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,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 {
|
for _, dsa := range Data.Streams.Active {
|
||||||
|
|
||||||
var channelExists = false // Entscheidet ob ein Kanal neu zu Datenbank hinzugefügt werden soll.
|
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
|
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
|
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
|
var m3uChannel M3UChannelStructXEPG
|
||||||
|
|
||||||
err = json.Unmarshal([]byte(mapToJSON(dsa)), &m3uChannel)
|
err = json.Unmarshal([]byte(mapToJSON(dsa)), &m3uChannel)
|
||||||
@@ -322,35 +401,43 @@ func createXEPGDatabase() (err error) {
|
|||||||
return
|
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.
|
// Try to find the channel based on matching all known values. If that fails, then move to full channel scan
|
||||||
for xepg, dxc := range Data.XEPG.Channels {
|
m3uChannelHash := generateHashForChannel(m3uChannel.FileM3UID, m3uChannel.GroupTitle, m3uChannel.TvgID, m3uChannel.TvgName, m3uChannel.UUIDKey, m3uChannel.UUIDValue)
|
||||||
|
if val, ok := xepgChannelsValuesMap[m3uChannelHash]; ok {
|
||||||
var xepgChannel XEPGChannelStruct
|
channelExists = true
|
||||||
err = json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
currentXEPGID = val.XEPG
|
||||||
if err != nil {
|
if len(m3uChannel.UUIDValue) > 0 {
|
||||||
return
|
channelHasUUID = true
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
// Vergleichen des Streams anhand einer UUID in der M3U mit dem Kanal in der Databank
|
// XEPG Datenbank durchlaufen um nach dem Kanal zu suchen. Run through the XEPG database to search for the channel (full scan)
|
||||||
if len(xepgChannel.UUIDValue) > 0 && len(m3uChannel.UUIDValue) > 0 {
|
for _, dxc := range xepgChannelsValuesMap {
|
||||||
|
|
||||||
if xepgChannel.UUIDValue == m3uChannel.UUIDValue && xepgChannel.UUIDKey == m3uChannel.UUIDKey {
|
if m3uChannel.FileM3UID == dxc.FileM3UID {
|
||||||
|
|
||||||
|
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
|
channelExists = true
|
||||||
channelHasUUID = true
|
channelHasUUID = true
|
||||||
currentXEPGID = xepg
|
currentXEPGID = dxc.XEPG
|
||||||
break
|
break
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Vergleichen des Streams mit dem Kanal in der Databank anhand des Kanalnamens
|
// 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
|
||||||
//fmt.Println(xepgChannel.Name, xepgChannel.UUIDKey, xepgChannel.UUIDValue)
|
if dxc.Name == m3uChannel.Name {
|
||||||
if xepgChannel.Name == m3uChannel.Name {
|
|
||||||
channelExists = true
|
channelExists = true
|
||||||
currentXEPGID = xepg
|
currentXEPGID = dxc.XEPG
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,9 +445,11 @@ func createXEPGDatabase() (err error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//os.Exit(0)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch channelExists {
|
switch channelExists {
|
||||||
|
|
||||||
case true:
|
case true:
|
||||||
// Bereits vorhandener Kanal
|
// Bereits vorhandener Kanal
|
||||||
var xepgChannel XEPGChannelStruct
|
var xepgChannel XEPGChannelStruct
|
||||||
@@ -423,7 +512,7 @@ func createXEPGDatabase() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
showInfo("XEPG:" + "Save DB file")
|
||||||
err = saveMapToJSONFile(System.File.XEPG, Data.XEPG.Channels)
|
err = saveMapToJSONFile(System.File.XEPG, Data.XEPG.Channels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -556,6 +645,10 @@ func mapping() (err error) {
|
|||||||
// XMLTV Datei erstellen
|
// XMLTV Datei erstellen
|
||||||
func createXMLTVFile() (err error) {
|
func createXMLTVFile() (err error) {
|
||||||
|
|
||||||
|
// Image Cache
|
||||||
|
// 4edd81ab7c368208cc6448b615051b37.jpg
|
||||||
|
var imgc = Data.Cache.Images
|
||||||
|
|
||||||
Data.Cache.ImagesFiles = []string{}
|
Data.Cache.ImagesFiles = []string{}
|
||||||
Data.Cache.ImagesURLS = []string{}
|
Data.Cache.ImagesURLS = []string{}
|
||||||
Data.Cache.ImagesCache = []string{}
|
Data.Cache.ImagesCache = []string{}
|
||||||
@@ -583,7 +676,12 @@ func createXMLTVFile() (err error) {
|
|||||||
var xepgXML XMLTV
|
var xepgXML XMLTV
|
||||||
|
|
||||||
xepgXML.Generator = System.Name
|
xepgXML.Generator = System.Name
|
||||||
|
|
||||||
|
if System.Branch == "master" {
|
||||||
xepgXML.Source = fmt.Sprintf("%s - %s", System.Name, System.Version)
|
xepgXML.Source = fmt.Sprintf("%s - %s", System.Name, System.Version)
|
||||||
|
} else {
|
||||||
|
xepgXML.Source = fmt.Sprintf("%s - %s.%s", System.Name, System.Version, System.Build)
|
||||||
|
}
|
||||||
|
|
||||||
var tmpProgram = &XMLTV{}
|
var tmpProgram = &XMLTV{}
|
||||||
|
|
||||||
@@ -598,7 +696,7 @@ func createXMLTVFile() (err error) {
|
|||||||
// Kanäle
|
// Kanäle
|
||||||
var channel Channel
|
var channel Channel
|
||||||
channel.ID = xepgChannel.XChannelID
|
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})
|
channel.DisplayName = append(channel.DisplayName, DisplayName{Value: xepgChannel.XName})
|
||||||
|
|
||||||
xepgXML.Channel = append(xepgXML.Channel, &channel)
|
xepgXML.Channel = append(xepgXML.Channel, &channel)
|
||||||
@@ -624,9 +722,10 @@ func createXMLTVFile() (err error) {
|
|||||||
var xmlOutput = []byte(xml.Header + string(content))
|
var xmlOutput = []byte(xml.Header + string(content))
|
||||||
writeByteToFile(System.File.XML, xmlOutput)
|
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
|
return
|
||||||
}
|
}
|
||||||
@@ -673,6 +772,15 @@ func getProgramData(xepgChannel XEPGChannelStruct) (xepgXML XMLTV, err error) {
|
|||||||
// Category (Kategorie)
|
// Category (Kategorie)
|
||||||
getCategory(program, xmltvProgram, xepgChannel)
|
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)
|
// Country (Länder)
|
||||||
program.Country = xmltvProgram.Country
|
program.Country = xmltvProgram.Country
|
||||||
|
|
||||||
@@ -700,6 +808,9 @@ func getProgramData(xepgChannel XEPGChannelStruct) (xepgXML XMLTV, err error) {
|
|||||||
// Live
|
// Live
|
||||||
program.Live = xmltvProgram.Live
|
program.Live = xmltvProgram.Live
|
||||||
|
|
||||||
|
// Premiere
|
||||||
|
program.Premiere = xmltvProgram.Premiere
|
||||||
|
|
||||||
xepgXML.Program = append(xepgXML.Program, program)
|
xepgXML.Program = append(xepgXML.Program, program)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -712,6 +823,7 @@ func getProgramData(xepgChannel XEPGChannelStruct) (xepgXML XMLTV, err error) {
|
|||||||
// Dummy Daten erstellen (createXMLTVFile)
|
// Dummy Daten erstellen (createXMLTVFile)
|
||||||
func createDummyProgram(xepgChannel XEPGChannelStruct) (dummyXMLTV XMLTV) {
|
func createDummyProgram(xepgChannel XEPGChannelStruct) (dummyXMLTV XMLTV) {
|
||||||
|
|
||||||
|
var imgc = Data.Cache.Images
|
||||||
var currentTime = time.Now()
|
var currentTime = time.Now()
|
||||||
var dateArray = strings.Fields(currentTime.String())
|
var dateArray = strings.Fields(currentTime.String())
|
||||||
var offset = " " + dateArray[2]
|
var offset = " " + dateArray[2]
|
||||||
@@ -742,14 +854,21 @@ func createDummyProgram(xepgChannel XEPGChannelStruct) (dummyXMLTV XMLTV) {
|
|||||||
epg.Start = epgStartTime.Format("20060102150405") + offset
|
epg.Start = epgStartTime.Format("20060102150405") + offset
|
||||||
epg.Stop = epgStopTime.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.Title = append(epg.Title, &Title{Value: xepgChannel.XName + " (" + epgStartTime.Weekday().String()[0:2] + ". " + 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"})
|
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 {
|
if Settings.XepgReplaceMissingImages == true {
|
||||||
poster.Src = getCacheImageURL(xepgChannel.TvgLogo)
|
poster.Src = imgc.Image.GetURL(xepgChannel.TvgLogo)
|
||||||
epg.Poster = append(epg.Poster, poster)
|
epg.Poster = append(epg.Poster, poster)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if xepgChannel.XCategory != "Movie" {
|
||||||
epg.EpisodeNum = append(epg.EpisodeNum, &EpisodeNum{Value: epgStartTime.Format("2006-01-02 15:04:05"), System: "original-air-date"})
|
epg.EpisodeNum = append(epg.EpisodeNum, &EpisodeNum{Value: epgStartTime.Format("2006-01-02 15:04:05"), System: "original-air-date"})
|
||||||
|
}
|
||||||
|
|
||||||
epg.New = &New{Value: ""}
|
epg.New = &New{Value: ""}
|
||||||
|
|
||||||
@@ -790,8 +909,10 @@ func getCategory(program *Program, xmltvProgram *Program, xepgChannel XEPGChanne
|
|||||||
// Programm Poster Cover aus der XMLTV Datei laden
|
// Programm Poster Cover aus der XMLTV Datei laden
|
||||||
func getPoster(program *Program, xmltvProgram *Program, xepgChannel XEPGChannelStruct) {
|
func getPoster(program *Program, xmltvProgram *Program, xepgChannel XEPGChannelStruct) {
|
||||||
|
|
||||||
|
var imgc = Data.Cache.Images
|
||||||
|
|
||||||
for _, poster := range xmltvProgram.Poster {
|
for _, poster := range xmltvProgram.Poster {
|
||||||
poster.Src = getCacheImageURL(poster.Src)
|
poster.Src = imgc.Image.GetURL(poster.Src)
|
||||||
program.Poster = append(program.Poster, poster)
|
program.Poster = append(program.Poster, poster)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -799,7 +920,7 @@ func getPoster(program *Program, xmltvProgram *Program, xepgChannel XEPGChannelS
|
|||||||
|
|
||||||
if len(xmltvProgram.Poster) == 0 {
|
if len(xmltvProgram.Poster) == 0 {
|
||||||
var poster Poster
|
var poster Poster
|
||||||
poster.Src = getCacheImageURL(xepgChannel.TvgLogo)
|
poster.Src = imgc.Image.GetURL(poster.Src)
|
||||||
program.Poster = append(program.Poster, poster)
|
program.Poster = append(program.Poster, poster)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -812,10 +933,19 @@ func getEpisodeNum(program *Program, xmltvProgram *Program, xepgChannel XEPGChan
|
|||||||
|
|
||||||
program.EpisodeNum = xmltvProgram.EpisodeNum
|
program.EpisodeNum = xmltvProgram.EpisodeNum
|
||||||
|
|
||||||
if len(xepgChannel.XCategory) > 0 {
|
if len(xepgChannel.XCategory) > 0 && xepgChannel.XCategory != "Movie" {
|
||||||
|
|
||||||
if len(xmltvProgram.EpisodeNum) == 0 {
|
if len(xmltvProgram.EpisodeNum) == 0 {
|
||||||
program.EpisodeNum = append(program.EpisodeNum, &EpisodeNum{Value: time.Now().Format("2006-01-02"), System: "original-air-date"})
|
|
||||||
|
var timeLayout = "20060102150405"
|
||||||
|
|
||||||
|
t, err := time.Parse(timeLayout, strings.Split(xmltvProgram.Start, " ")[0])
|
||||||
|
if err == nil {
|
||||||
|
program.EpisodeNum = append(program.EpisodeNum, &EpisodeNum{Value: t.Format("2006-01-02 15:04:05"), System: "original-air-date"})
|
||||||
|
} else {
|
||||||
|
ShowError(err, 0)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -901,6 +1031,18 @@ func createM3UFile() {
|
|||||||
// XEPG Datenbank bereinigen
|
// XEPG Datenbank bereinigen
|
||||||
func cleanupXEPG() {
|
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"))
|
showInfo("XEPG:" + fmt.Sprintf("Cleanup database"))
|
||||||
Data.XEPG.XEPGCount = 0
|
Data.XEPG.XEPGCount = 0
|
||||||
|
|
||||||
@@ -910,7 +1052,7 @@ func cleanupXEPG() {
|
|||||||
err := json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
err := json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
||||||
if err == nil {
|
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)
|
delete(Data.XEPG.Channels, id)
|
||||||
} else {
|
} else {
|
||||||
if xepgChannel.XActive == true {
|
if xepgChannel.XActive == true {
|
||||||
@@ -918,6 +1060,10 @@ func cleanupXEPG() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if indexOfString(xepgChannel.FileM3UID, sourceIDs) == -1 {
|
||||||
|
delete(Data.XEPG.Channels, id)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,8 @@ menuItems.push(new MainMenuItem("logout", "{{.mainMenu.item.logout}}", "logout.p
|
|||||||
|
|
||||||
// Kategorien für die Einstellungen
|
// Kategorien für die Einstellungen
|
||||||
var settingsCategory = new Array()
|
var settingsCategory = new Array()
|
||||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.general}}", "xteveAutoUpdate,tuner,epgSource,api"))
|
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.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.streaming}}", "buffer,buffer.size.kb,buffer.timeout,user.agent"))
|
|
||||||
settingsCategory.push(new SettingsCategoryItem("{{.settings.category.backup}}", "backup.path,backup.keep"))
|
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"))
|
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 data = SERVER["xepg"]["epgMapping"]
|
||||||
var channels = getObjKeys(data)
|
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 => {
|
channels.forEach(id => {
|
||||||
|
|
||||||
@@ -331,7 +330,17 @@ function createSearchObj() {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
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] + " "
|
SEARCH_MAPPING[id] = SEARCH_MAPPING[id] + data[id][key] + " "
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ class WizardItem extends WizardCategory {
|
|||||||
|
|
||||||
case "m3u":
|
case "m3u":
|
||||||
var input = content.createInput("text", key, "")
|
var input = content.createInput("text", key, "")
|
||||||
|
input.setAttribute("placeholder", "{{.wizard.m3u.placeholder}}")
|
||||||
input.setAttribute("class", "wizard")
|
input.setAttribute("class", "wizard")
|
||||||
input.id = key
|
input.id = key
|
||||||
doc.appendChild(input)
|
doc.appendChild(input)
|
||||||
@@ -72,6 +73,7 @@ class WizardItem extends WizardCategory {
|
|||||||
|
|
||||||
case "xmltv":
|
case "xmltv":
|
||||||
var input = content.createInput("text", key, "")
|
var input = content.createInput("text", key, "")
|
||||||
|
input.setAttribute("placeholder", "{{.wizard.xmltv.placeholder}}")
|
||||||
input.setAttribute("class", "wizard")
|
input.setAttribute("class", "wizard")
|
||||||
input.id = key
|
input.id = key
|
||||||
doc.appendChild(input)
|
doc.appendChild(input)
|
||||||
@@ -140,6 +142,12 @@ function saveWizard() {
|
|||||||
name = (config[i] as HTMLInputElement).name
|
name = (config[i] as HTMLInputElement).name
|
||||||
value = (config[i] as HTMLInputElement).value
|
value = (config[i] as HTMLInputElement).value
|
||||||
|
|
||||||
|
if (value.length == 0) {
|
||||||
|
var msg = name.toUpperCase() + ": " + "{{.alert.missingInput}}"
|
||||||
|
alert(msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
wizard[name] = value
|
wizard[name] = value
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ class Content {
|
|||||||
var cell:Cell = new Cell()
|
var cell:Cell = new Cell()
|
||||||
cell.child = true
|
cell.child = true
|
||||||
cell.childType = "P"
|
cell.childType = "P"
|
||||||
if (SERVER["settings"]["buffer"] == true) {
|
if (SERVER["settings"]["buffer"] != "-") {
|
||||||
cell.value = data[key]["tuner"]
|
cell.value = data[key]["tuner"]
|
||||||
} else {
|
} else {
|
||||||
cell.value = "-"
|
cell.value = "-"
|
||||||
@@ -714,7 +714,7 @@ class ShowContent extends Content {
|
|||||||
break
|
break
|
||||||
|
|
||||||
case "log":
|
case "log":
|
||||||
var input = this.createInput("button", menuKey, "{{.button.resetlogs}}")
|
var input = this.createInput("button", menuKey, "{{.button.resetLogs}}")
|
||||||
input.setAttribute("onclick", 'javascript: resetLogs();')
|
input.setAttribute("onclick", 'javascript: resetLogs();')
|
||||||
interaction.appendChild(input)
|
interaction.appendChild(input)
|
||||||
|
|
||||||
@@ -1113,7 +1113,7 @@ function openPopUp(dataType, element) {
|
|||||||
content.appendRow("{{.playlist.fileM3U.title}}", input)
|
content.appendRow("{{.playlist.fileM3U.title}}", input)
|
||||||
|
|
||||||
// Tuner
|
// Tuner
|
||||||
if (SERVER["settings"]["buffer"] == true) {
|
if (SERVER["settings"]["buffer"] != "-") {
|
||||||
var text:string[] = new Array()
|
var text:string[] = new Array()
|
||||||
var values:string[] = new Array()
|
var values:string[] = new Array()
|
||||||
|
|
||||||
@@ -1192,7 +1192,7 @@ function openPopUp(dataType, element) {
|
|||||||
content.appendRow("{{.playlist.fileHDHR.title}}", input)
|
content.appendRow("{{.playlist.fileHDHR.title}}", input)
|
||||||
|
|
||||||
// Tuner
|
// Tuner
|
||||||
if (SERVER["settings"]["buffer"] == true) {
|
if (SERVER["settings"]["buffer"] != "-") {
|
||||||
var text:string[] = new Array()
|
var text:string[] = new Array()
|
||||||
var values:string[] = new Array()
|
var values:string[] = new Array()
|
||||||
|
|
||||||
@@ -1530,6 +1530,15 @@ function openPopUp(dataType, element) {
|
|||||||
}
|
}
|
||||||
content.appendRow("{{.mapping.channelName.title}}", input)
|
content.appendRow("{{.mapping.channelName.title}}", input)
|
||||||
|
|
||||||
|
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
|
// Aktualisierung des Kanalnamens
|
||||||
if (data.hasOwnProperty("_uuid.key")) {
|
if (data.hasOwnProperty("_uuid.key")) {
|
||||||
if (data["_uuid.key"] != "") {
|
if (data["_uuid.key"] != "") {
|
||||||
@@ -1559,7 +1568,7 @@ function openPopUp(dataType, element) {
|
|||||||
// Erweitern der EPG Kategorie
|
// Erweitern der EPG Kategorie
|
||||||
var dbKey:string = "x-category"
|
var dbKey:string = "x-category"
|
||||||
var text:string[] = ["-", "Kids (Emby only)", "News", "Movie", "Series", "Sports"]
|
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)
|
var select = content.createSelect(text, values, data[dbKey], dbKey)
|
||||||
select.setAttribute("onchange", "javascript: this.className = 'changed'")
|
select.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
content.appendRow("{{.mapping.epgCategory.title}}", select)
|
content.appendRow("{{.mapping.epgCategory.title}}", select)
|
||||||
@@ -1570,6 +1579,10 @@ function openPopUp(dataType, element) {
|
|||||||
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
input.setAttribute("onchange", "javascript: this.className = 'changed'")
|
||||||
content.appendRow("{{.mapping.m3uGroupTitle.title}}", input)
|
content.appendRow("{{.mapping.m3uGroupTitle.title}}", input)
|
||||||
|
|
||||||
|
if (data["group-title"] != undefined) {
|
||||||
|
content.description(data["group-title"])
|
||||||
|
}
|
||||||
|
|
||||||
// XMLTV Datei
|
// XMLTV Datei
|
||||||
var dbKey:string = "x-xmltv-file"
|
var dbKey:string = "x-xmltv-file"
|
||||||
var xmlFile = data[dbKey]
|
var xmlFile = data[dbKey]
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class Server {
|
|||||||
case "http:":
|
case "http:":
|
||||||
this.protocol = "ws://"
|
this.protocol = "ws://"
|
||||||
break
|
break
|
||||||
case "https://":
|
case "https:":
|
||||||
this.protocol = "wss://"
|
this.protocol = "wss://"
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,62 @@ class SettingsCategory {
|
|||||||
setting.appendChild(tdRight)
|
setting.appendChild(tdRight)
|
||||||
break
|
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
|
// Checkboxen
|
||||||
case "authentication.web":
|
case "authentication.web":
|
||||||
var tdLeft = document.createElement("TD")
|
var tdLeft = document.createElement("TD")
|
||||||
@@ -216,20 +272,6 @@ class SettingsCategory {
|
|||||||
setting.appendChild(tdRight)
|
setting.appendChild(tdRight)
|
||||||
break
|
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":
|
case "api":
|
||||||
var tdLeft = document.createElement("TD")
|
var tdLeft = document.createElement("TD")
|
||||||
tdLeft.innerHTML = "{{.settings.api.title}}" + ":"
|
tdLeft.innerHTML = "{{.settings.api.title}}" + ":"
|
||||||
@@ -314,6 +356,37 @@ class SettingsCategory {
|
|||||||
setting.appendChild(tdRight)
|
setting.appendChild(tdRight)
|
||||||
break
|
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
|
return setting
|
||||||
@@ -381,6 +454,22 @@ class SettingsCategory {
|
|||||||
text = "{{.settings.userAgent.description}}"
|
text = "{{.settings.userAgent.description}}"
|
||||||
break
|
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":
|
case "epgSource":
|
||||||
text = "{{.settings.epgSource.description}}"
|
text = "{{.settings.epgSource.description}}"
|
||||||
break
|
break
|
||||||
@@ -409,6 +498,10 @@ class SettingsCategory {
|
|||||||
text = "{{.settings.replaceEmptyImages.description}}"
|
text = "{{.settings.replaceEmptyImages.description}}"
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case "udpxy":
|
||||||
|
text = "{{.settings.udpxy.description}}"
|
||||||
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
text = ""
|
text = ""
|
||||||
break
|
break
|
||||||
|
|||||||
58
xteve.go
58
xteve.go
@@ -13,7 +13,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"./src"
|
"xteve/src"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GitHubStruct : GitHub Account. Über diesen Account werden die Updates veröffentlicht
|
// GitHubStruct : GitHub Account. Über diesen Account werden die Updates veröffentlicht
|
||||||
@@ -35,28 +35,34 @@ var GitHub = GitHubStruct{Branch: "master", User: "xteve-project", Repo: "xTeVe-
|
|||||||
Update: Automatic updates from the GitHub repository [true|false]
|
Update: Automatic updates from the GitHub repository [true|false]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Name : Programname
|
// Name : Programmname
|
||||||
const Name = "xTeVe"
|
const Name = "xTeVe"
|
||||||
|
|
||||||
// Version : Version, die Build Nummer wird in der main func geparst.
|
// Version : Version, die Build Nummer wird in der main func geparst.
|
||||||
const Version = "2.0.0.0000"
|
const Version = "2.2.0.0200"
|
||||||
|
|
||||||
|
// DBVersion : Datanbank Version
|
||||||
|
const DBVersion = "2.1.0"
|
||||||
|
|
||||||
// APIVersion : API Version
|
// APIVersion : API Version
|
||||||
const APIVersion = "1.1.0"
|
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 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 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))
|
||||||
|
|
||||||
var configFolder = flag.String("config", "", ": Config Folder ["+samplePath+"] (default: "+homeDirectory+")")
|
var configFolder = flag.String("config", "", ": Config Folder ["+samplePath+"] (default: "+homeDirectory+")")
|
||||||
var port = flag.String("port", "", ": Server port [34400] (default: 34400)")
|
var port = flag.String("port", "", ": Server port [34400] (default: 34400)")
|
||||||
|
var restore = flag.String("restore", "", ": Restore from backup ["+sampleRestore+"xteve_backup.zip]")
|
||||||
|
|
||||||
var gitBranch = flag.String("branch", "", ": Git Branch [master|beta] (default: master)")
|
var gitBranch = flag.String("branch", "", ": Git Branch [master|beta] (default: master)")
|
||||||
var debug = flag.Int("debug", 0, ": Debug level [0 - 3] (default: 0)")
|
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")
|
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() {
|
func main() {
|
||||||
|
|
||||||
// Build-Nummer von der Versionsnummer trennen
|
// Build-Nummer von der Versionsnummer trennen
|
||||||
@@ -66,7 +72,7 @@ func main() {
|
|||||||
system.APIVersion = APIVersion
|
system.APIVersion = APIVersion
|
||||||
system.Branch = GitHub.Branch
|
system.Branch = GitHub.Branch
|
||||||
system.Build = build[len(build)-1:][0]
|
system.Build = build[len(build)-1:][0]
|
||||||
system.Dev = Dev
|
system.DBVersion = DBVersion
|
||||||
system.GitHub = GitHub
|
system.GitHub = GitHub
|
||||||
system.Name = Name
|
system.Name = Name
|
||||||
system.Version = strings.Join(build[0:len(build)-1], ".")
|
system.Version = strings.Join(build[0:len(build)-1], ".")
|
||||||
@@ -115,6 +121,24 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
system.Dev = *dev
|
||||||
|
|
||||||
|
// Systeminformationen anzeigen
|
||||||
|
if *info {
|
||||||
|
|
||||||
|
system.Flag.Info = true
|
||||||
|
|
||||||
|
err := src.Init()
|
||||||
|
if err != nil {
|
||||||
|
src.ShowError(err, 0)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
src.ShowSystemInfo()
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Webserver Port
|
// Webserver Port
|
||||||
if len(*port) > 0 {
|
if len(*port) > 0 {
|
||||||
system.Flag.Port = *port
|
system.Flag.Port = *port
|
||||||
@@ -138,6 +162,25 @@ func main() {
|
|||||||
system.Folder.Config = *configFolder
|
system.Folder.Config = *configFolder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Backup wiederherstellen
|
||||||
|
if len(*restore) > 0 {
|
||||||
|
|
||||||
|
system.Flag.Restore = *restore
|
||||||
|
|
||||||
|
err := src.Init()
|
||||||
|
if err != nil {
|
||||||
|
src.ShowError(err, 0)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = src.XteveRestoreFromCLI(*restore)
|
||||||
|
if err != nil {
|
||||||
|
src.ShowError(err, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
err := src.Init()
|
err := src.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
src.ShowError(err, 0)
|
src.ShowError(err, 0)
|
||||||
@@ -147,7 +190,6 @@ func main() {
|
|||||||
err = src.BinaryUpdate()
|
err = src.BinaryUpdate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
src.ShowError(err, 0)
|
src.ShowError(err, 0)
|
||||||
os.Exit(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = src.StartSystem(false)
|
err = src.StartSystem(false)
|
||||||
|
|||||||
Reference in New Issue
Block a user