Compare commits
87 Commits
v2.0.0.000
...
2.1.2.0120
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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.
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
demo
|
||||||
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.12.4 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
|
||||||
|
```
|
||||||
@@ -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,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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"] != "") {
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,18 @@ 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;
|
||||||
}
|
}
|
||||||
return setting;
|
return setting;
|
||||||
};
|
};
|
||||||
@@ -308,6 +353,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;
|
||||||
|
|||||||
@@ -27,7 +27,8 @@
|
|||||||
},
|
},
|
||||||
"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",
|
||||||
@@ -44,7 +45,7 @@
|
|||||||
"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": {
|
||||||
@@ -189,6 +190,11 @@
|
|||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
|
"description": {
|
||||||
|
"title": "Channel Description",
|
||||||
|
"placeholder": "Used by the Dummy as an XML description",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
"updateChannelName": {
|
"updateChannelName": {
|
||||||
"title": "Update Channel Name",
|
"title": "Update Channel Name",
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
@@ -242,7 +248,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"title": "Password",
|
"title": "Password",
|
||||||
"placeholder": "Passoword",
|
"placeholder": "Password",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
@@ -306,7 +312,7 @@
|
|||||||
"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": {
|
||||||
@@ -319,7 +325,32 @@
|
|||||||
},
|
},
|
||||||
"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"
|
||||||
|
|
||||||
|
},
|
||||||
|
"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": {
|
"bufferSize": {
|
||||||
"title": "Buffer Size",
|
"title": "Buffer Size",
|
||||||
@@ -331,8 +362,8 @@
|
|||||||
"placeholder": "100"
|
"placeholder": "100"
|
||||||
},
|
},
|
||||||
"userAgent": {
|
"userAgent": {
|
||||||
"title": "User agent",
|
"title": "User Agent",
|
||||||
"description": "User Agent for HTTP requests",
|
"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": {
|
||||||
@@ -381,10 +412,12 @@
|
|||||||
},
|
},
|
||||||
"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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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="">
|
||||||
|
|||||||
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":
|
||||||
|
|
||||||
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|
||||||
@@ -46,6 +49,10 @@ func Init() (err error) {
|
|||||||
System.DVRLimit = 480
|
System.DVRLimit = 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 +90,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 +118,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 +127,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 +142,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 +174,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 +204,13 @@ func Init() (err error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DLNA Server starten
|
||||||
|
err = SSDP()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML Datein laden
|
||||||
loadHTMLMap()
|
loadHTMLMap()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
136
src/data.go
136
src/data.go
@@ -24,6 +24,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 +39,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 []string
|
||||||
|
|
||||||
_, 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 +100,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 +158,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 {
|
||||||
|
|
||||||
@@ -358,6 +413,7 @@ func saveFilter(request RequestStruct) (settings SettingsStrcut, 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 +437,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 +446,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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -438,8 +508,59 @@ 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()
|
||||||
buildXEPG(true)
|
buildXEPG(true)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
|
||||||
|
createXMLTVFile()
|
||||||
|
createM3UFile()
|
||||||
|
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
||||||
|
go cachingImages()
|
||||||
|
|
||||||
|
System.ScanInProgress = 0
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Wenn während des erstellen der Datanbank das Mapping erneut gespeichert wird, wird die Datenbank erst später erneut aktualisiert.
|
||||||
|
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()
|
||||||
|
buildXEPG(false)
|
||||||
|
createXMLTVFile()
|
||||||
|
createM3UFile()
|
||||||
|
showInfo("XEPG:" + fmt.Sprintf("Ready to use"))
|
||||||
|
go cachingImages()
|
||||||
|
|
||||||
|
System.ScanInProgress = 0
|
||||||
|
|
||||||
|
System.BackgroundProcess = false
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -604,6 +725,7 @@ func saveWizard(request RequestStruct) (nextStep int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildXEPG(false)
|
buildXEPG(false)
|
||||||
|
System.ScanInProgress = 0
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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, "", " ")
|
||||||
|
|
||||||
|
|||||||
@@ -6,41 +6,58 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getCacheImageURL(url string) (cacheImageURL string) {
|
func getCacheImageURL(imageURL string) (cacheImageURL string) {
|
||||||
|
|
||||||
url = strings.Trim(url, "\r\n")
|
if Settings.CacheImages == false {
|
||||||
|
return imageURL
|
||||||
|
}
|
||||||
|
|
||||||
var urlMD5 = getMD5(url)
|
imageURL = strings.Trim(imageURL, "\r\n")
|
||||||
var fileExtension = filepath.Ext(url)
|
|
||||||
|
p, err := url.Parse(imageURL)
|
||||||
|
if err != nil {
|
||||||
|
// URL konnte nicht geparst werden, die ursprüngliche image url wird zurückgegeben
|
||||||
|
showInfo(fmt.Sprintf("Image Caching:Image URL: %s", imageURL))
|
||||||
|
showWarning(4101)
|
||||||
|
return imageURL
|
||||||
|
}
|
||||||
|
var urlMD5 = getMD5(imageURL)
|
||||||
|
var fileExtension = filepath.Ext(p.Path)
|
||||||
|
|
||||||
|
if len(fileExtension) == 0 {
|
||||||
|
// Keine Dateierweiterung vorhanden, die ursprüngliche image url wird zurückgegeben
|
||||||
|
return imageURL
|
||||||
|
}
|
||||||
|
|
||||||
if indexOfString(urlMD5+fileExtension, Data.Cache.ImagesFiles) == -1 {
|
if indexOfString(urlMD5+fileExtension, Data.Cache.ImagesFiles) == -1 {
|
||||||
Data.Cache.ImagesFiles = append(Data.Cache.ImagesFiles, urlMD5+fileExtension)
|
Data.Cache.ImagesFiles = append(Data.Cache.ImagesFiles, urlMD5+fileExtension)
|
||||||
}
|
}
|
||||||
|
|
||||||
if Settings.CacheImages == false || System.ImageCachingInProgress == 1 {
|
if System.ImageCachingInProgress == 1 {
|
||||||
return url
|
return imageURL
|
||||||
}
|
}
|
||||||
|
|
||||||
if indexOfString(urlMD5+fileExtension, Data.Cache.ImagesCache) != -1 {
|
if indexOfString(urlMD5+fileExtension, Data.Cache.ImagesCache) != -1 {
|
||||||
|
|
||||||
cacheImageURL = fmt.Sprintf("%s://%s/images/%s%s", System.ServerProtocol.WEB, System.Domain, urlMD5, fileExtension)
|
cacheImageURL = fmt.Sprintf("%s://%s/images/%s%s", System.ServerProtocol.XML, System.Domain, urlMD5, fileExtension)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if strings.Contains(url, System.Domain+"/images/") == false {
|
if strings.Contains(imageURL, System.Domain+"/images/") == false {
|
||||||
|
|
||||||
if indexOfString(url, Data.Cache.ImagesURLS) == -1 {
|
if indexOfString(imageURL, Data.Cache.ImagesURLS) == -1 {
|
||||||
Data.Cache.ImagesURLS = append(Data.Cache.ImagesURLS, url)
|
Data.Cache.ImagesURLS = append(Data.Cache.ImagesURLS, imageURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheImageURL = url
|
cacheImageURL = imageURL
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,10 +74,10 @@ func cachingImages() {
|
|||||||
|
|
||||||
showInfo("Image Caching:Images are cached")
|
showInfo("Image Caching:Images are cached")
|
||||||
|
|
||||||
for _, url := range Data.Cache.ImagesURLS {
|
for _, imageURL := range Data.Cache.ImagesURLS {
|
||||||
|
|
||||||
if len(url) > 0 {
|
if len(imageURL) > 0 {
|
||||||
cacheImage(url)
|
cacheImage(imageURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -94,16 +111,16 @@ func cachingImages() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func cacheImage(url string) {
|
func cacheImage(imageURL string) {
|
||||||
|
|
||||||
var debug string
|
var debug string
|
||||||
var urlMD5 = getMD5(url)
|
var urlMD5 = getMD5(imageURL)
|
||||||
var fileExtension = filepath.Ext(url)
|
var fileExtension = filepath.Ext(imageURL)
|
||||||
|
|
||||||
debug = fmt.Sprintf("Image Caching:File: %s Download: %s", urlMD5+fileExtension, url)
|
debug = fmt.Sprintf("Image Caching:File: %s Download: %s", urlMD5+fileExtension, imageURL)
|
||||||
showDebug(debug, 1)
|
showDebug(debug, 1)
|
||||||
|
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(imageURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -146,7 +163,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
|
||||||
|
|
||||||
|
|||||||
99
src/info.go
Normal file
99
src/info.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
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("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))
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -161,107 +170,13 @@ func MakeInterfaceFromM3U(byteStream []byte) (allChannels []interface{}, err err
|
|||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ import (
|
|||||||
|
|
||||||
func showInfo(str string) {
|
func showInfo(str string) {
|
||||||
|
|
||||||
|
if System.Flag.Info == true {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var max = 22
|
var max = 22
|
||||||
var msg = strings.SplitN(str, ":", 2)
|
var msg = strings.SplitN(str, ":", 2)
|
||||||
var length = len(msg[0])
|
var length = len(msg[0])
|
||||||
@@ -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,6 +295,8 @@ 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:
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|||||||
@@ -11,15 +11,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
|
DVRLimit int
|
||||||
|
|
||||||
|
FFmpeg struct {
|
||||||
|
DefaultOptions string
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
VLC struct {
|
||||||
|
DefaultOptions string
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
File struct {
|
File struct {
|
||||||
Authentication string
|
Authentication string
|
||||||
M3U string
|
M3U string
|
||||||
@@ -30,10 +42,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +191,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
|
||||||
@@ -238,11 +257,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)
|
||||||
|
|
||||||
|
|||||||
@@ -25,11 +25,15 @@ 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"`
|
||||||
@@ -37,6 +41,8 @@ type RequestStruct struct {
|
|||||||
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
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ type Program struct {
|
|||||||
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
|
||||||
|
|||||||
@@ -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,7 @@ 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["version"] = System.DBVersion
|
||||||
defaults["xteveAutoUpdate"] = true
|
defaults["xteveAutoUpdate"] = true
|
||||||
defaults["temp.path"] = System.Folder.Temp
|
defaults["temp.path"] = System.Folder.Temp
|
||||||
|
|
||||||
@@ -159,8 +161,27 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
73
src/webUI.go
73
src/webUI.go
File diff suppressed because one or more lines are too long
@@ -34,8 +34,20 @@ func StartWebserver() (err error) {
|
|||||||
|
|
||||||
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 +129,12 @@ func Stream(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 +146,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 +163,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 +206,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 +259,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,10 +322,12 @@ 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 {
|
||||||
@@ -302,6 +335,8 @@ func WS(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, "Could not open websocket connection", http.StatusBadRequest)
|
http.Error(w, "Could not open websocket connection", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setGlobalDomain(r.Host)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|
||||||
err = conn.ReadJSON(&request)
|
err = conn.ReadJSON(&request)
|
||||||
@@ -447,7 +482,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 +492,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 +564,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 +591,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 +619,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 +908,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))
|
||||||
|
|||||||
61
src/xepg.go
61
src/xepg.go
@@ -174,9 +174,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,6 +219,17 @@ 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"}
|
||||||
@@ -236,11 +245,8 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +306,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,6 +315,12 @@ func createXEPGDatabase() (err error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var xepgChannels = make(map[string]interface{})
|
||||||
|
|
||||||
|
for k, v := range Data.XEPG.Channels {
|
||||||
|
xepgChannels[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
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.
|
||||||
@@ -325,7 +336,7 @@ func createXEPGDatabase() (err error) {
|
|||||||
Data.Cache.Streams.Active = append(Data.Cache.Streams.Active, m3uChannel.Name)
|
Data.Cache.Streams.Active = append(Data.Cache.Streams.Active, m3uChannel.Name)
|
||||||
|
|
||||||
// XEPG Datenbank durchlaufen um nach dem Kanal zu suchen.
|
// XEPG Datenbank durchlaufen um nach dem Kanal zu suchen.
|
||||||
for xepg, dxc := range Data.XEPG.Channels {
|
for xepg, dxc := range xepgChannels {
|
||||||
|
|
||||||
var xepgChannel XEPGChannelStruct
|
var xepgChannel XEPGChannelStruct
|
||||||
err = json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
err = json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel)
|
||||||
@@ -361,6 +372,7 @@ func createXEPGDatabase() (err error) {
|
|||||||
//os.Exit(0)
|
//os.Exit(0)
|
||||||
|
|
||||||
switch channelExists {
|
switch channelExists {
|
||||||
|
|
||||||
case true:
|
case true:
|
||||||
// Bereits vorhandener Kanal
|
// Bereits vorhandener Kanal
|
||||||
var xepgChannel XEPGChannelStruct
|
var xepgChannel XEPGChannelStruct
|
||||||
@@ -583,7 +595,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{}
|
||||||
|
|
||||||
@@ -624,9 +641,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
|
||||||
}
|
}
|
||||||
@@ -700,6 +718,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)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -742,14 +763,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 = getCacheImageURL(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: ""}
|
||||||
|
|
||||||
@@ -812,10 +840,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)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,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"))
|
||||||
|
|
||||||
|
|||||||
@@ -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"] != "") {
|
||||||
@@ -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,22 @@ 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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return setting
|
return setting
|
||||||
@@ -381,6 +439,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
|
||||||
|
|||||||
46
xteve.go
46
xteve.go
@@ -35,11 +35,14 @@ 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.1.2.0120"
|
||||||
|
|
||||||
|
// DBVersion : Datanbank Version
|
||||||
|
const DBVersion = "2.1.0"
|
||||||
|
|
||||||
// APIVersion : API Version
|
// APIVersion : API Version
|
||||||
const APIVersion = "1.1.0"
|
const APIVersion = "1.1.0"
|
||||||
@@ -49,12 +52,15 @@ 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")
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -66,6 +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.DBVersion = DBVersion
|
||||||
system.Dev = Dev
|
system.Dev = Dev
|
||||||
system.GitHub = GitHub
|
system.GitHub = GitHub
|
||||||
system.Name = Name
|
system.Name = Name
|
||||||
@@ -115,6 +122,22 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 +161,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)
|
||||||
|
|||||||
Reference in New Issue
Block a user