mirror of
https://github.com/fHDHR/fHDHR_NextPVR.git
synced 2025-12-06 09:26:57 -05:00
Compare commits
186 Commits
2020-12-08
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e40543552a | ||
|
|
c376cfcaa9 | ||
|
|
6ecf54a8d0 | ||
|
|
0d185e6704 | ||
|
|
09ddda62fc | ||
|
|
b1d4e13c31 | ||
|
|
eaf26457f0 | ||
|
|
b92247c810 | ||
|
|
0fb454016d | ||
|
|
ab51ea02a1 | ||
|
|
884d4b6e27 | ||
|
|
9c72f30a99 | ||
|
|
72627510aa | ||
|
|
9caeac2f41 | ||
|
|
d03e575f0b | ||
|
|
54e1e72104 | ||
|
|
b16fcf3b51 | ||
|
|
e86290e9fe | ||
|
|
0de184c242 | ||
|
|
5f4092bdc8 | ||
|
|
d9cda8b1d4 | ||
|
|
a7c854bcd4 | ||
|
|
13faf0845e | ||
|
|
1cf2a7acce | ||
|
|
36712e7ba0 | ||
|
|
02e825978b | ||
|
|
9642feecae | ||
|
|
b8ce4f4e8a | ||
|
|
22955ce11f | ||
|
|
1aa35b66f0 | ||
|
|
cbe8deb965 | ||
|
|
d118ef7807 | ||
|
|
c9b20743fd | ||
|
|
1b13aedc5e | ||
|
|
ddcb04892b | ||
|
|
6076011d1c | ||
|
|
751eaebee9 | ||
|
|
53dc0e127d | ||
|
|
acf72ad109 | ||
|
|
630b8dbf2b | ||
|
|
e8aa5bd3f4 | ||
|
|
03927ec495 | ||
|
|
6a924cbca2 | ||
|
|
4bd2ff971e | ||
|
|
63685f4a0e | ||
|
|
70fe2f3814 | ||
|
|
c0ff51a6db | ||
|
|
8a3f8d919d | ||
|
|
e09bb5d83b | ||
|
|
b72a26c96c | ||
|
|
35e6bb707d | ||
|
|
fc0708d888 | ||
|
|
9d25b63f99 | ||
|
|
af9fbe97b9 | ||
|
|
a36b9e8143 | ||
|
|
ef82a3b5e7 | ||
|
|
6bf4319316 | ||
|
|
9ad3f00482 | ||
|
|
31f7213006 | ||
|
|
ccd99a7008 | ||
|
|
56d7a74ee4 | ||
|
|
e3d8f64c5c | ||
|
|
28383d89ec | ||
|
|
d915e4cbed | ||
|
|
51b9a85597 | ||
|
|
051fb87add | ||
|
|
796a5e9eec | ||
|
|
7348101eea | ||
|
|
fb1f5f2324 | ||
|
|
887b60b80d | ||
|
|
698e407c38 | ||
|
|
8c80c51c2a | ||
|
|
1eca980ae5 | ||
|
|
308c56da09 | ||
|
|
404f44c22d | ||
|
|
cd24c5a4fe | ||
|
|
db49d28de5 | ||
|
|
1f23425be5 | ||
|
|
4efdded7e1 | ||
|
|
b4d8ed6e4d | ||
|
|
cdbe545df6 | ||
|
|
d1038ab46a | ||
|
|
8f9208c2cb | ||
|
|
93e07fd771 | ||
|
|
f5967e718a | ||
|
|
32252e34c9 | ||
|
|
7e2accd2d2 | ||
|
|
244472792e | ||
|
|
418b23e96b | ||
|
|
d9e0cc13dd | ||
|
|
c444d3123c | ||
|
|
e003d502c2 | ||
|
|
a1e9e28e64 | ||
|
|
4093a8c135 | ||
|
|
e7282522b5 | ||
|
|
602e74f565 | ||
|
|
46a6043e62 | ||
|
|
d87ef97494 | ||
|
|
038bd03b42 | ||
|
|
cd47fa0a3f | ||
|
|
cf64aecf7b | ||
|
|
b6ef8b13ae | ||
|
|
90fb90a92e | ||
|
|
0441e731d4 | ||
|
|
e87af73215 | ||
|
|
f1dab94210 | ||
|
|
75a8492dbe | ||
|
|
c7c5efdc4e | ||
|
|
51d9728d2a | ||
|
|
27a8045fc1 | ||
|
|
8a475f154a | ||
|
|
64a6e4a635 | ||
|
|
e9e0e40d78 | ||
|
|
c0acaa736e | ||
|
|
f733b8c9df | ||
|
|
3f314f3863 | ||
|
|
0c09e1dca0 | ||
|
|
5c66f2594e | ||
|
|
1199489cc3 | ||
|
|
1ab992fa76 | ||
|
|
2cf0f3d891 | ||
|
|
fb44ee3bde | ||
|
|
e7e4ddcade | ||
|
|
dace8fa650 | ||
|
|
8fc69ba973 | ||
|
|
87633c356e | ||
|
|
341d905ea2 | ||
|
|
c05ae6ac71 | ||
|
|
aec09eade1 | ||
|
|
28a9886960 | ||
|
|
adae4d77c7 | ||
|
|
cebce2f1ba | ||
|
|
2cf4f4249b | ||
|
|
fa6e3bdd50 | ||
|
|
a981d8d845 | ||
|
|
39649a11f8 | ||
|
|
2858016ad7 | ||
|
|
77f941c08a | ||
|
|
9f37dfa6b3 | ||
|
|
e3a264f24e | ||
|
|
2cd30a38ca | ||
|
|
b12996b8bb | ||
|
|
7e4eea1d76 | ||
|
|
b8be38db68 | ||
|
|
5b1c9b303b | ||
|
|
0b8a5104a9 | ||
|
|
0aea878ebe | ||
|
|
86c14c2d9b | ||
|
|
eaf5e89113 | ||
|
|
ca96d4de34 | ||
|
|
9c481d4103 | ||
|
|
1d595d8261 | ||
|
|
845efb0719 | ||
|
|
91926a2dcf | ||
|
|
0cc13306ab | ||
|
|
d47ecee009 | ||
|
|
60455b6a84 | ||
|
|
886b257228 | ||
|
|
3f8ff15e97 | ||
|
|
baf4cf461c | ||
|
|
73c5f23ed7 | ||
|
|
61779991ca | ||
|
|
1143b158da | ||
|
|
40971388ce | ||
|
|
7f6d80fd3e | ||
|
|
55ca03b389 | ||
|
|
d101717e8f | ||
|
|
a8972371c2 | ||
|
|
d5c7a1ea47 | ||
|
|
afed209051 | ||
|
|
328320192a | ||
|
|
71da17fa45 | ||
|
|
295a7259ac | ||
|
|
e00cec1ed6 | ||
|
|
8b870430ad | ||
|
|
01c7b7fd99 | ||
|
|
6786f10812 | ||
|
|
5ff0cdf93c | ||
|
|
105e1a34a5 | ||
|
|
eb55403bee | ||
|
|
142b9fd3ab | ||
|
|
bfdbeded3a | ||
|
|
295599ac55 | ||
|
|
b75d4284d0 | ||
|
|
f9da9d8a72 | ||
|
|
10311a767e |
@ -16,5 +16,7 @@ fHDHR is labeled as beta until we reach v1.0.0
|
|||||||
|
|
||||||
Join us in `#fHDHR <irc://irc.freenode.net/#fHDHR>`_ on Freenode.
|
Join us in `#fHDHR <irc://irc.freenode.net/#fHDHR>`_ on Freenode.
|
||||||
|
|
||||||
|
# !!NOTICE!!
|
||||||
|
|
||||||
Due to multiple issues, I'm dropping official support for Windows.
|
To reduce code duplication between variants, I am moving to a plugin system.
|
||||||
|
The normal variant repos will stay active during the transition.
|
||||||
|
|||||||
@ -1,2 +0,0 @@
|
|||||||
# pylama:ignore=W0401,W0611
|
|
||||||
from .zap2it import *
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
[main]
|
|
||||||
# uuid =
|
|
||||||
# cache_dir =
|
|
||||||
# servicename = NextPVR
|
|
||||||
# reponame = fHDHR_NextPVR
|
|
||||||
|
|
||||||
[fhdhr]
|
|
||||||
# address = 0.0.0.0
|
|
||||||
# discovery_address = 0.0.0.0
|
|
||||||
# port = 5004
|
|
||||||
# stream_type = direct
|
|
||||||
# tuner_count = 4
|
|
||||||
# friendlyname = fHDHR-NextPVR
|
|
||||||
# reporting_firmware_name = fHDHR_NextPVR
|
|
||||||
# reporting_manufacturer = BoronDust
|
|
||||||
# reporting_model = fHDHR
|
|
||||||
# reporting_firmware_ver = 20201001
|
|
||||||
# reporting_tuner_type = Antenna
|
|
||||||
# device_auth = fHDHR
|
|
||||||
|
|
||||||
[epg]
|
|
||||||
# images = pass
|
|
||||||
# method = origin
|
|
||||||
# update_frequency = 43200
|
|
||||||
|
|
||||||
[ffmpeg]
|
|
||||||
# path = ffmpeg
|
|
||||||
# bytes_per_read = 1152000
|
|
||||||
|
|
||||||
[vlc]
|
|
||||||
# path = cvlc
|
|
||||||
# bytes_per_read = 1152000
|
|
||||||
|
|
||||||
[direct_stream]
|
|
||||||
# chunksize = 1048576
|
|
||||||
|
|
||||||
[logging]
|
|
||||||
# level = WARNING
|
|
||||||
|
|
||||||
[database]
|
|
||||||
# type = sqlite
|
|
||||||
# driver = None
|
|
||||||
|
|
||||||
[nextpvr]
|
|
||||||
# address = localhost
|
|
||||||
# port = 8866
|
|
||||||
# ssl =
|
|
||||||
# pin =
|
|
||||||
|
|
||||||
[zap2it]
|
|
||||||
# delay = 5
|
|
||||||
# postalcode = None
|
|
||||||
# affiliate_id = gapzap
|
|
||||||
# country = USA
|
|
||||||
# device = -
|
|
||||||
# headendid = lineupId
|
|
||||||
# isoverride = True
|
|
||||||
# languagecode = en
|
|
||||||
# pref =
|
|
||||||
# timespan = 6
|
|
||||||
# timezone =
|
|
||||||
# userid = -
|
|
||||||
@ -6,12 +6,33 @@
|
|||||||
"config_web": true
|
"config_web": true
|
||||||
},
|
},
|
||||||
"method":{
|
"method":{
|
||||||
"value": "blocks",
|
"value": "none",
|
||||||
"config_file": true,
|
"config_file": true,
|
||||||
"config_web": true
|
"config_web": true
|
||||||
},
|
},
|
||||||
"update_frequency":{
|
"update_frequency":{
|
||||||
"value": 14400,
|
"value": 43200,
|
||||||
|
"config_file": true,
|
||||||
|
"config_web": true
|
||||||
|
},
|
||||||
|
"reverse_days": {
|
||||||
|
"value": -1,
|
||||||
|
"config_file": true,
|
||||||
|
"config_web": true
|
||||||
|
},
|
||||||
|
"forward_days": {
|
||||||
|
"value": 7,
|
||||||
|
"config_file": true,
|
||||||
|
"config_web": true
|
||||||
|
},
|
||||||
|
"block_size": {
|
||||||
|
"value": 1800,
|
||||||
|
"config_file": true,
|
||||||
|
"config_web": true
|
||||||
|
}
|
||||||
|
,
|
||||||
|
"xmltv_offset": {
|
||||||
|
"value": "+0000",
|
||||||
"config_file": true,
|
"config_file": true,
|
||||||
"config_web": true
|
"config_web": true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,26 +15,6 @@
|
|||||||
"config_file": true,
|
"config_file": true,
|
||||||
"config_web": true
|
"config_web": true
|
||||||
},
|
},
|
||||||
"reporting_manufacturer":{
|
|
||||||
"value": "BoronDust",
|
|
||||||
"config_file": true,
|
|
||||||
"config_web": true
|
|
||||||
},
|
|
||||||
"reporting_model":{
|
|
||||||
"value": "fHDHR",
|
|
||||||
"config_file": true,
|
|
||||||
"config_web": true
|
|
||||||
},
|
|
||||||
"reporting_firmware_ver":{
|
|
||||||
"value": "20201001",
|
|
||||||
"config_file": true,
|
|
||||||
"config_web": true
|
|
||||||
},
|
|
||||||
"reporting_tuner_type":{
|
|
||||||
"value": "Antenna",
|
|
||||||
"config_file": true,
|
|
||||||
"config_web": true
|
|
||||||
},
|
|
||||||
"device_auth":{
|
"device_auth":{
|
||||||
"value": "fHDHR",
|
"value": "fHDHR",
|
||||||
"config_file": true,
|
"config_file": true,
|
||||||
@ -54,21 +34,6 @@
|
|||||||
"value": "fHDHR",
|
"value": "fHDHR",
|
||||||
"config_file": true,
|
"config_file": true,
|
||||||
"config_web": true
|
"config_web": true
|
||||||
},
|
|
||||||
"stream_type":{
|
|
||||||
"value": "direct",
|
|
||||||
"config_file": true,
|
|
||||||
"config_web": true
|
|
||||||
},
|
|
||||||
"tuner_count":{
|
|
||||||
"value": 4,
|
|
||||||
"config_file": true,
|
|
||||||
"config_web": true
|
|
||||||
},
|
|
||||||
"reporting_firmware_name":{
|
|
||||||
"value": "fHDHR",
|
|
||||||
"config_file": true,
|
|
||||||
"config_web": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"logging":{
|
"logging":{
|
||||||
"level":{
|
"level":{
|
||||||
"value": "WARNING",
|
"value": "INFO",
|
||||||
"config_file": true,
|
"config_file": true,
|
||||||
"config_web": true
|
"config_web": true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,35 +10,15 @@
|
|||||||
"config_file": true,
|
"config_file": true,
|
||||||
"config_web": true
|
"config_web": true
|
||||||
},
|
},
|
||||||
"thread_method":{
|
|
||||||
"value": "multiprocessing",
|
|
||||||
"config_file": true,
|
|
||||||
"config_web": true
|
|
||||||
},
|
|
||||||
"servicename":{
|
"servicename":{
|
||||||
"value": "fHDHR",
|
"value": "fHDHR",
|
||||||
"config_file": false,
|
"config_file": false,
|
||||||
"config_web": false
|
"config_web": false
|
||||||
},
|
},
|
||||||
"dictpopname":{
|
|
||||||
"value": "fHDHR",
|
|
||||||
"config_file": false,
|
|
||||||
"config_web": false
|
|
||||||
},
|
|
||||||
"reponame":{
|
"reponame":{
|
||||||
"value": "fHDHR",
|
"value": "fHDHR",
|
||||||
"config_file": false,
|
"config_file": false,
|
||||||
"config_web": false
|
"config_web": false
|
||||||
},
|
|
||||||
"valid_epg_methods":{
|
|
||||||
"value": "None,blocks",
|
|
||||||
"config_file": false,
|
|
||||||
"config_web": false
|
|
||||||
},
|
|
||||||
"required":{
|
|
||||||
"value": "none",
|
|
||||||
"config_file": false,
|
|
||||||
"config_web": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"rmg":{
|
|
||||||
"enabled":{
|
|
||||||
"value": true,
|
|
||||||
"config_file": true,
|
|
||||||
"config_web": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,31 +1,22 @@
|
|||||||
{
|
{
|
||||||
"ffmpeg":{
|
"streaming":{
|
||||||
"path":{
|
|
||||||
"value": "ffmpeg",
|
|
||||||
"config_file": true,
|
|
||||||
"config_web": true
|
|
||||||
},
|
|
||||||
"bytes_per_read": {
|
"bytes_per_read": {
|
||||||
"value": 1152000,
|
"value": 1152000,
|
||||||
"config_file": true,
|
"config_file": true,
|
||||||
"config_web": true
|
"config_web": true
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"vlc":{
|
"origin_quality": {
|
||||||
"path":{
|
"value": "none",
|
||||||
"value": "cvlc",
|
|
||||||
"config_file": true,
|
"config_file": true,
|
||||||
"config_web": true
|
"config_web": true
|
||||||
},
|
},
|
||||||
"bytes_per_read":{
|
"transcode_quality": {
|
||||||
"value": 1152000,
|
"value": "none",
|
||||||
"config_file": true,
|
"config_file": true,
|
||||||
"config_web": true
|
"config_web": true
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"direct_stream":{
|
"method": {
|
||||||
"chunksize":{
|
"value": "direct",
|
||||||
"value": 1048576,
|
|
||||||
"config_file": true,
|
"config_file": true,
|
||||||
"config_web": true
|
"config_web": true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,14 +28,33 @@ Here's the `main` section.
|
|||||||
# cache_dir =
|
# cache_dir =
|
||||||
````
|
````
|
||||||
|
|
||||||
|
## streaming
|
||||||
|
|
||||||
|
* `method` can be set to `ffmpeg`, `vlc` or `direct`.
|
||||||
|
* `bytes_per_read` determines how many bytes of the stream to read before sending the data to your client. Increasing this value may cause longer load times, and lowering it may effect `stuttering`.
|
||||||
|
* `origin_quality` can be set to high,medium,low for most variants. Variants that make use of m3u8 will Autoselect High for the direct method if not set. ffmpeg/vlc will determine the best stream on their own. Some Variants can allow alternative values.
|
||||||
|
* `transcode_quality` works with ffmpeg/vlc to use fHDHR for handling quality instead of the origin. Valid settings include: heavy,mobile,internet720,internet480,internet360,internet240
|
||||||
|
|
||||||
|
|
||||||
|
````
|
||||||
|
[streaming]
|
||||||
|
# method = direct
|
||||||
|
# bytes_per_read = 1152000
|
||||||
|
# origin_quality = None
|
||||||
|
# transcode_quality = None
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
## fhdhr
|
## fhdhr
|
||||||
|
|
||||||
The `fhdhr` contains all the configuration options for interfacing between this script and your media platform.
|
The `fhdhr` contains all the configuration options for interfacing between this script and your media platform.
|
||||||
* `address` and `port` are what we will allow the script to listen on. `0.0.0.0` is the default, and will respond to all.
|
* `address` and `port` are what we will allow the script to listen on. `0.0.0.0` is the default, and will respond to all.
|
||||||
* `discovery_address` may be helpful for making SSDP work properly. If `address` is not `0.0.0.0`, we will use that. If this is not set to a real IP, we won't run SSDP. SSDP is only really helpful for discovering in Plex/Emby. It's a wasted resource since you can manually add the `ip:port` of the script to Plex.
|
* `discovery_address` may be helpful for making SSDP work properly. If `address` is not `0.0.0.0`, we will use that. If this is not set to a real IP, we won't run SSDP. SSDP is only really helpful for discovering in Plex/Emby. It's a wasted resource since you can manually add the `ip:port` of the script to Plex.
|
||||||
* `tuner_count` is a limit of devices able to stream from the script.
|
* `tuner_count` is a limit of devices able to stream from the script. The default is 3, as per Locast's documentation. A 4th is possible, but is not reccomended.
|
||||||
* `friendlyname` is to set the name that Plex sees the script as.
|
* `friendlyname` is to set the name that Plex sees the script as.
|
||||||
* `stream_type` can be set to `ffmpeg`, `vlc` or `direct`.
|
* `reporting_*` are settings that show how the script projects itself as a hardware device.
|
||||||
|
* `device_auth` and `require_auth` are for an unimplemented Authentication feature.
|
||||||
|
* `chanscan_on_start` Scans Origin for new channels at startup.
|
||||||
|
|
||||||
|
|
||||||
````
|
````
|
||||||
@ -43,61 +62,56 @@ The `fhdhr` contains all the configuration options for interfacing between this
|
|||||||
# address = 0.0.0.0
|
# address = 0.0.0.0
|
||||||
# discovery_address = 0.0.0.0
|
# discovery_address = 0.0.0.0
|
||||||
# port = 5004
|
# port = 5004
|
||||||
# stream_type = direct
|
|
||||||
# tuner_count = 4
|
# tuner_count = 4
|
||||||
# friendlyname = fHDHR-NextPVR
|
# friendlyname = fHDHR-Locast
|
||||||
# reporting_firmware_name = fHDHR_NextPVR
|
# reporting_firmware_name = fHDHR_Locast
|
||||||
# reporting_manufacturer = BoronDust
|
# reporting_manufacturer = BoronDust
|
||||||
# reporting_model = fHDHR
|
# reporting_model = fHDHR
|
||||||
# reporting_firmware_ver = 20201001
|
# reporting_firmware_ver = 20201001
|
||||||
# reporting_tuner_type = Antenna
|
# reporting_tuner_type = Antenna
|
||||||
# device_auth = fHDHR
|
# device_auth = fHDHR
|
||||||
|
# require_auth = False
|
||||||
|
# chanscan_on_start = True
|
||||||
````
|
````
|
||||||
|
|
||||||
# EPG
|
# EPG
|
||||||
* `images` can be set to `proxy` or `pass`. If you choose `proxy`, images will be reverse proxied through fHDHR.
|
* `images` can be set to `proxy` or `pass`. If you choose `proxy`, images will be reverse proxied through fHDHR.
|
||||||
* `method` defaults to `origin` and will pull the xmltv data from NextPVR. Other Options include `blocks` which is an hourly schedule with minimal channel information. Another option is `zap2it`, which is another source of EPG information. Channel Numbers may need to be manually mapped.
|
* `method` defaults to `origin` and will pull the xmltv data from Locast. Other Options include `blocks` which is an hourly schedule with minimal channel information. Another option is `zap2it`, which is another source of EPG information. Channel Numbers may need to be manually mapped.
|
||||||
* `update_frequency` * `epg_update_frequency` determines how often we check for new scheduling information. In Seconds.
|
* `update_frequency` determines how often we check for new scheduling information. In Seconds.
|
||||||
|
* `reverse_days` allows Blocks of EPG data to be created prior to the start of the EPG Source data.
|
||||||
|
* `forward_days` allows Blocks of EPG data to be created after the end of the EPG Source data.
|
||||||
|
* `block_size` in seconds, sets the default block size for data before, after and missing timeslots.
|
||||||
|
* `xmltv_offset` allows the final xmltv file to have an offset for users with timezone issues.
|
||||||
|
|
||||||
````
|
````
|
||||||
[epg]
|
[epg]
|
||||||
# images = pass
|
# images = pass
|
||||||
# method = origin
|
# method = origin
|
||||||
# update_frequency = 43200
|
# update_frequency = 43200
|
||||||
|
# reverse_days = -1
|
||||||
|
# forward_days = 7
|
||||||
|
# block_size = 1800
|
||||||
|
# xmltv_offset = +0000
|
||||||
````
|
````
|
||||||
|
|
||||||
## ffmpeg
|
## ffmpeg
|
||||||
|
|
||||||
The `ffmpeg` section includes:
|
The `ffmpeg` section includes:
|
||||||
* `path` is useful if ffmpeg is not in your systems PATH, or you want to manually specify.
|
* `path` is useful if ffmpeg is not in your systems PATH, or you want to manually specify.
|
||||||
* `bytes_per_read` determines how many bytes of the stream to read before sending the data to your client. Increasing this value may cause longer load times, and lowering it may effect `stuttering`.
|
|
||||||
|
|
||||||
````
|
````
|
||||||
[ffmpeg]
|
[ffmpeg]
|
||||||
# path = ffmpeg
|
# path = ffmpeg
|
||||||
# bytes_per_read = 1152000
|
|
||||||
````
|
````
|
||||||
|
|
||||||
## vlc
|
## vlc
|
||||||
|
|
||||||
The `vlc` section includes:
|
The `vlc` section includes:
|
||||||
* `path` is useful if ffmpeg is not in your systems PATH, or you want to manually specify.
|
* `path` is useful if ffmpeg is not in your systems PATH, or you want to manually specify.
|
||||||
* `bytes_per_read` determines how many bytes of the stream to read before sending the data to your client. Increasing this value may cause longer load times, and lowering it may effect `stuttering`.
|
|
||||||
|
|
||||||
````
|
````
|
||||||
[vlc]
|
[vlc]
|
||||||
# path = ffmpeg
|
# path = cvlc
|
||||||
# bytes_per_read = 1152000
|
|
||||||
````
|
|
||||||
|
|
||||||
## direct_stream
|
|
||||||
|
|
||||||
The `direct_stream` section is for when you set the `[fhdhr]stream_type` to `direct`
|
|
||||||
* `chunksize` is how much data to read at a time.
|
|
||||||
|
|
||||||
````
|
|
||||||
[direct_stream]
|
|
||||||
# chunksize = 1024*1024
|
|
||||||
````
|
````
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
@ -117,6 +131,27 @@ TODO: improve documentation here.
|
|||||||
[database]
|
[database]
|
||||||
# type = sqlite
|
# type = sqlite
|
||||||
# driver = None
|
# driver = None
|
||||||
|
user = None
|
||||||
|
pass = None
|
||||||
|
host = None
|
||||||
|
port = None
|
||||||
|
name = None
|
||||||
|
````
|
||||||
|
|
||||||
|
## RMG
|
||||||
|
|
||||||
|
````
|
||||||
|
# enabled = True
|
||||||
|
````
|
||||||
|
|
||||||
|
## SSDP
|
||||||
|
|
||||||
|
````
|
||||||
|
# enabled = True
|
||||||
|
# max_age = 1800
|
||||||
|
# proto = ipv6
|
||||||
|
# iface = None
|
||||||
|
# multicast_address = None
|
||||||
````
|
````
|
||||||
|
|
||||||
## NextPVR
|
## NextPVR
|
||||||
|
|||||||
@ -1,37 +1,42 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
|
|
||||||
from .originwrapper import OriginServiceWrapper
|
|
||||||
from .device import fHDHR_Device
|
from .device import fHDHR_Device
|
||||||
from .api import fHDHR_API_URLs
|
from .api import fHDHR_API_URLs
|
||||||
|
|
||||||
import fHDHR.tools
|
import fHDHR.tools
|
||||||
|
fHDHR_VERSION = "v0.6.0-beta"
|
||||||
fHDHR_VERSION = "v0.4.6-beta"
|
|
||||||
|
|
||||||
|
|
||||||
class fHDHR_INT_OBJ():
|
class fHDHR_INT_OBJ():
|
||||||
|
|
||||||
def __init__(self, settings, logger, db):
|
def __init__(self, settings, logger, db, plugins):
|
||||||
self.version = fHDHR_VERSION
|
self.version = fHDHR_VERSION
|
||||||
self.config = settings
|
self.config = settings
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.db = db
|
self.db = db
|
||||||
|
self.plugins = plugins
|
||||||
|
|
||||||
self.web = fHDHR.tools.WebReq()
|
self.web = fHDHR.tools.WebReq()
|
||||||
|
for plugin_name in list(self.plugins.plugins.keys()):
|
||||||
|
self.plugins.plugins[plugin_name].plugin_utils.web = self.web
|
||||||
|
|
||||||
self.api = fHDHR_API_URLs(settings)
|
self.api = fHDHR_API_URLs(settings, self.web)
|
||||||
|
for plugin_name in list(self.plugins.plugins.keys()):
|
||||||
|
self.plugins.plugins[plugin_name].plugin_utils.api = self.api
|
||||||
|
|
||||||
|
self.threads = {}
|
||||||
|
|
||||||
|
|
||||||
class fHDHR_OBJ():
|
class fHDHR_OBJ():
|
||||||
|
|
||||||
def __init__(self, settings, logger, db, origin, alternative_epg):
|
def __init__(self, settings, logger, db, plugins):
|
||||||
self.fhdhr = fHDHR_INT_OBJ(settings, logger, db)
|
self.fhdhr = fHDHR_INT_OBJ(settings, logger, db, plugins)
|
||||||
|
|
||||||
self.originwrapper = OriginServiceWrapper(self.fhdhr, origin)
|
self.fhdhr.origins = fHDHR.origins.Origins(self.fhdhr)
|
||||||
|
|
||||||
self.device = fHDHR_Device(self.fhdhr, self.originwrapper, alternative_epg)
|
self.device = fHDHR_Device(self.fhdhr, self.fhdhr.origins)
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
''' will only get called for undefined attributes '''
|
''' will only get called for undefined attributes '''
|
||||||
if hasattr(self.fhdhr, name):
|
if hasattr(self.fhdhr, name):
|
||||||
return eval("self.fhdhr." + name)
|
return eval("self.fhdhr.%s" % name)
|
||||||
|
|||||||
@ -1,15 +1,61 @@
|
|||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
|
|
||||||
|
class Fillin_Client():
|
||||||
|
|
||||||
|
def __init__(self, settings, web):
|
||||||
|
self.config = settings
|
||||||
|
self.web = web
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
''' will only get called for undefined attributes '''
|
||||||
|
if hasattr(self.web.session, name):
|
||||||
|
return eval("self.web.session.%s" % name)
|
||||||
|
|
||||||
|
|
||||||
class fHDHR_API_URLs():
|
class fHDHR_API_URLs():
|
||||||
|
|
||||||
def __init__(self, settings):
|
def __init__(self, settings, web):
|
||||||
self.config = settings
|
self.config = settings
|
||||||
|
self.web = web
|
||||||
|
|
||||||
|
self.headers = {'User-Agent': "fHDHR/%s" % self.config.internal["versions"]["fHDHR"]}
|
||||||
|
|
||||||
|
# Replaced later
|
||||||
|
self.client = Fillin_Client(settings, web)
|
||||||
|
|
||||||
self.address = self.config.dict["fhdhr"]["address"]
|
self.address = self.config.dict["fhdhr"]["address"]
|
||||||
self.discovery_address = self.config.dict["fhdhr"]["discovery_address"]
|
self.discovery_address = self.config.dict["fhdhr"]["discovery_address"]
|
||||||
self.port = self.config.dict["fhdhr"]["port"]
|
self.port = self.config.dict["fhdhr"]["port"]
|
||||||
|
|
||||||
|
def get(self, url, *args):
|
||||||
|
|
||||||
|
req_method = type(self.client).__name__
|
||||||
|
|
||||||
|
if not url.startswith("http"):
|
||||||
|
if not url.startswith("/"):
|
||||||
|
url = "/%s" % url
|
||||||
|
url = "%s%s" % (self.base, url)
|
||||||
|
|
||||||
|
if req_method == "FlaskClient":
|
||||||
|
self.client.get(url, headers=self.headers, *args)
|
||||||
|
else:
|
||||||
|
self.client.get(url, headers=self.headers, *args)
|
||||||
|
|
||||||
|
def post(self, url, *args):
|
||||||
|
|
||||||
|
req_method = type(self.client).__name__
|
||||||
|
|
||||||
|
if not url.startswith("http"):
|
||||||
|
if not url.startswith("/"):
|
||||||
|
url = "/%s" % url
|
||||||
|
url = "%s%s" % (self.base, url)
|
||||||
|
|
||||||
|
if req_method == "FlaskClient":
|
||||||
|
self.client.post(url, headers=self.headers, *args)
|
||||||
|
else:
|
||||||
|
self.client.post(url, headers=self.headers, *args)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def base(self):
|
def base(self):
|
||||||
if self.discovery_address:
|
if self.discovery_address:
|
||||||
|
|||||||
@ -2,13 +2,13 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
import time
|
import time
|
||||||
import multiprocessing
|
|
||||||
import threading
|
|
||||||
import platform
|
|
||||||
|
|
||||||
from fHDHR import fHDHR_VERSION, fHDHR_OBJ
|
from fHDHR import fHDHR_VERSION, fHDHR_OBJ
|
||||||
import fHDHR.exceptions
|
import fHDHR.exceptions
|
||||||
import fHDHR.config
|
import fHDHR.config
|
||||||
|
import fHDHR.logger
|
||||||
|
import fHDHR.plugins
|
||||||
|
import fHDHR.origins
|
||||||
from fHDHR.db import fHDHRdb
|
from fHDHR.db import fHDHRdb
|
||||||
|
|
||||||
ERR_CODE = 1
|
ERR_CODE = 1
|
||||||
@ -19,10 +19,6 @@ if sys.version_info.major == 2 or sys.version_info < (3, 7):
|
|||||||
print('Error: fHDHR requires python 3.7+.')
|
print('Error: fHDHR requires python 3.7+.')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
opersystem = platform.system()
|
|
||||||
if opersystem in ["Windows"]:
|
|
||||||
print("WARNING: This script may fail on Windows. Try Setting the `thread_method` to `threading`")
|
|
||||||
|
|
||||||
|
|
||||||
def build_args_parser():
|
def build_args_parser():
|
||||||
"""Build argument parser for fHDHR"""
|
"""Build argument parser for fHDHR"""
|
||||||
@ -31,48 +27,38 @@ def build_args_parser():
|
|||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
def get_configuration(args, script_dir, origin, fHDHR_web):
|
def get_configuration(args, script_dir, fHDHR_web):
|
||||||
if not os.path.isfile(args.cfg):
|
if not os.path.isfile(args.cfg):
|
||||||
raise fHDHR.exceptions.ConfigurationNotFound(filename=args.cfg)
|
raise fHDHR.exceptions.ConfigurationNotFound(filename=args.cfg)
|
||||||
return fHDHR.config.Config(args.cfg, script_dir, origin, fHDHR_web)
|
return fHDHR.config.Config(args.cfg, script_dir, fHDHR_web)
|
||||||
|
|
||||||
|
|
||||||
def run(settings, logger, db, script_dir, fHDHR_web, origin, alternative_epg):
|
def run(settings, logger, db, script_dir, fHDHR_web, plugins):
|
||||||
|
|
||||||
fhdhr = fHDHR_OBJ(settings, logger, db, origin, alternative_epg)
|
fhdhr = fHDHR_OBJ(settings, logger, db, plugins)
|
||||||
fhdhrweb = fHDHR_web.fHDHR_HTTP_Server(fhdhr)
|
fhdhrweb = fHDHR_web.fHDHR_HTTP_Server(fhdhr)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
print("HTTP Server Starting")
|
# Start Flask Thread
|
||||||
if settings.dict["main"]["thread_method"] in ["multiprocessing"]:
|
fhdhrweb.start()
|
||||||
fhdhr_web = multiprocessing.Process(target=fhdhrweb.run)
|
|
||||||
elif settings.dict["main"]["thread_method"] in ["threading"]:
|
|
||||||
fhdhr_web = threading.Thread(target=fhdhrweb.run)
|
|
||||||
if settings.dict["main"]["thread_method"] in ["multiprocessing", "threading"]:
|
|
||||||
fhdhr_web.start()
|
|
||||||
|
|
||||||
|
# Start SSDP Thread
|
||||||
if settings.dict["fhdhr"]["discovery_address"]:
|
if settings.dict["fhdhr"]["discovery_address"]:
|
||||||
print("SSDP Server Starting")
|
fhdhr.device.ssdp.start()
|
||||||
if settings.dict["main"]["thread_method"] in ["multiprocessing"]:
|
|
||||||
fhdhr_ssdp = multiprocessing.Process(target=fhdhr.device.ssdp.run)
|
|
||||||
elif settings.dict["main"]["thread_method"] in ["threading"]:
|
|
||||||
fhdhr_ssdp = threading.Thread(target=fhdhr.device.ssdp.run)
|
|
||||||
if settings.dict["main"]["thread_method"] in ["multiprocessing", "threading"]:
|
|
||||||
fhdhr_ssdp.start()
|
|
||||||
|
|
||||||
|
# Start EPG Thread
|
||||||
if settings.dict["epg"]["method"]:
|
if settings.dict["epg"]["method"]:
|
||||||
print("EPG Update Starting")
|
fhdhr.device.epg.start()
|
||||||
if settings.dict["main"]["thread_method"] in ["multiprocessing"]:
|
|
||||||
fhdhr_epg = multiprocessing.Process(target=fhdhr.device.epg.run)
|
# Perform some actions now that HTTP Server is running
|
||||||
elif settings.dict["main"]["thread_method"] in ["threading"]:
|
fhdhr.api.get("/api/startup_tasks")
|
||||||
fhdhr_epg = threading.Thread(target=fhdhr.device.epg.run)
|
|
||||||
if settings.dict["main"]["thread_method"] in ["multiprocessing", "threading"]:
|
|
||||||
fhdhr_epg.start()
|
|
||||||
|
|
||||||
# wait forever
|
# wait forever
|
||||||
while True:
|
restart_code = "restart"
|
||||||
time.sleep(3600)
|
while fhdhr.threads["flask"].is_alive():
|
||||||
|
time.sleep(1)
|
||||||
|
return restart_code
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
return ERR_CODE_NO_RESTART
|
return ERR_CODE_NO_RESTART
|
||||||
@ -80,32 +66,52 @@ def run(settings, logger, db, script_dir, fHDHR_web, origin, alternative_epg):
|
|||||||
return ERR_CODE
|
return ERR_CODE
|
||||||
|
|
||||||
|
|
||||||
def start(args, script_dir, fHDHR_web, origin, alternative_epg):
|
def start(args, script_dir, fHDHR_web):
|
||||||
"""Get Configuration for fHDHR and start"""
|
"""Get Configuration for fHDHR and start"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
settings = get_configuration(args, script_dir, origin, fHDHR_web)
|
settings = get_configuration(args, script_dir, fHDHR_web)
|
||||||
except fHDHR.exceptions.ConfigurationError as e:
|
except fHDHR.exceptions.ConfigurationError as e:
|
||||||
print(e)
|
print(e)
|
||||||
return ERR_CODE_NO_RESTART
|
return ERR_CODE_NO_RESTART
|
||||||
|
|
||||||
logger = settings.logging_setup()
|
# Find Plugins and import their default configs
|
||||||
|
plugins = fHDHR.plugins.PluginsHandler(settings)
|
||||||
|
|
||||||
|
# Apply User Configuration
|
||||||
|
settings.user_config()
|
||||||
|
settings.config_verification()
|
||||||
|
|
||||||
|
# Setup Logging
|
||||||
|
logger = fHDHR.logger.Logger(settings)
|
||||||
|
|
||||||
|
# Setup Database
|
||||||
db = fHDHRdb(settings)
|
db = fHDHRdb(settings)
|
||||||
|
|
||||||
return run(settings, logger, db, script_dir, fHDHR_web, origin, alternative_epg)
|
# Setup Plugins
|
||||||
|
plugins.load_plugins(logger, db)
|
||||||
|
plugins.setup()
|
||||||
|
settings.config_verification_plugins()
|
||||||
|
|
||||||
|
if not len([x for x in list(plugins.plugins.keys()) if plugins.plugins[x].type == "origin"]):
|
||||||
|
print("No Origin Plugins found.")
|
||||||
|
return ERR_CODE
|
||||||
|
|
||||||
|
return run(settings, logger, db, script_dir, fHDHR_web, plugins)
|
||||||
|
|
||||||
|
|
||||||
def main(script_dir, fHDHR_web, origin, alternative_epg):
|
def main(script_dir, fHDHR_web):
|
||||||
"""fHDHR run script entry point"""
|
"""fHDHR run script entry point"""
|
||||||
|
|
||||||
print("Loading fHDHR %s" % fHDHR_VERSION)
|
print("Loading fHDHR %s" % fHDHR_VERSION)
|
||||||
print("Loading fHDHR_web %s" % fHDHR_web.fHDHR_web_VERSION)
|
print("Loading fHDHR_web %s" % fHDHR_web.fHDHR_web_VERSION)
|
||||||
print("Loading Origin Service: %s %s" % (origin.ORIGIN_NAME, origin.ORIGIN_VERSION))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
args = build_args_parser()
|
args = build_args_parser()
|
||||||
return start(args, script_dir, fHDHR_web, origin, alternative_epg)
|
while True:
|
||||||
|
returned_code = start(args, script_dir, fHDHR_web)
|
||||||
|
if returned_code not in ["restart"]:
|
||||||
|
return returned_code
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\n\nInterrupted")
|
print("\n\nInterrupted")
|
||||||
return ERR_CODE
|
return ERR_CODE
|
||||||
|
|||||||
@ -3,8 +3,6 @@ import sys
|
|||||||
import random
|
import random
|
||||||
import configparser
|
import configparser
|
||||||
import pathlib
|
import pathlib
|
||||||
import logging
|
|
||||||
import subprocess
|
|
||||||
import platform
|
import platform
|
||||||
import json
|
import json
|
||||||
|
|
||||||
@ -15,31 +13,31 @@ from fHDHR.tools import isint, isfloat, is_arithmetic, is_docker
|
|||||||
|
|
||||||
class Config():
|
class Config():
|
||||||
|
|
||||||
def __init__(self, filename, script_dir, origin, fHDHR_web):
|
def __init__(self, filename, script_dir, fHDHR_web):
|
||||||
self.origin = origin
|
|
||||||
self.fHDHR_web = fHDHR_web
|
self.fHDHR_web = fHDHR_web
|
||||||
|
|
||||||
self.internal = {}
|
self.internal = {}
|
||||||
self.conf_default = {}
|
self.conf_default = {}
|
||||||
self.dict = {}
|
self.dict = {}
|
||||||
|
self.internal["versions"] = {}
|
||||||
self.config_file = filename
|
self.config_file = filename
|
||||||
|
|
||||||
self.initial_load(script_dir)
|
self.core_setup(script_dir)
|
||||||
self.config_verification()
|
|
||||||
|
|
||||||
def initial_load(self, script_dir):
|
def core_setup(self, script_dir):
|
||||||
|
|
||||||
data_dir = pathlib.Path(script_dir).joinpath('data')
|
data_dir = pathlib.Path(script_dir).joinpath('data')
|
||||||
|
internal_plugins_dir = pathlib.Path(script_dir).joinpath('plugins')
|
||||||
fHDHR_web_dir = pathlib.Path(script_dir).joinpath('fHDHR_web')
|
fHDHR_web_dir = pathlib.Path(script_dir).joinpath('fHDHR_web')
|
||||||
www_dir = pathlib.Path(fHDHR_web_dir).joinpath('www_dir')
|
www_dir = pathlib.Path(fHDHR_web_dir).joinpath('www_dir')
|
||||||
|
|
||||||
self.internal["paths"] = {
|
self.internal["paths"] = {
|
||||||
"script_dir": script_dir,
|
"script_dir": script_dir,
|
||||||
"data_dir": data_dir,
|
"data_dir": data_dir,
|
||||||
"alternative_epg": pathlib.Path(script_dir).joinpath('alternative_epg'),
|
"plugins_dir": [internal_plugins_dir],
|
||||||
"origin": pathlib.Path(script_dir).joinpath('origin'),
|
|
||||||
"cache_dir": pathlib.Path(data_dir).joinpath('cache'),
|
"cache_dir": pathlib.Path(data_dir).joinpath('cache'),
|
||||||
"internal_config": pathlib.Path(data_dir).joinpath('internal_config'),
|
"internal_config": pathlib.Path(data_dir).joinpath('internal_config'),
|
||||||
|
"fHDHR_web_dir": fHDHR_web_dir,
|
||||||
"www_dir": www_dir,
|
"www_dir": www_dir,
|
||||||
"www_templates_dir": pathlib.Path(fHDHR_web_dir).joinpath('templates'),
|
"www_templates_dir": pathlib.Path(fHDHR_web_dir).joinpath('templates'),
|
||||||
"font": pathlib.Path(data_dir).joinpath('garamond.ttf'),
|
"font": pathlib.Path(data_dir).joinpath('garamond.ttf'),
|
||||||
@ -50,32 +48,66 @@ class Config():
|
|||||||
if str(conffilepath).endswith(".json"):
|
if str(conffilepath).endswith(".json"):
|
||||||
self.read_json_config(conffilepath)
|
self.read_json_config(conffilepath)
|
||||||
|
|
||||||
for dir_type in ["alternative_epg", "origin"]:
|
for file_item in os.listdir(self.internal["paths"]["fHDHR_web_dir"]):
|
||||||
|
file_item_path = pathlib.Path(self.internal["paths"]["fHDHR_web_dir"]).joinpath(file_item)
|
||||||
for file_item in os.listdir(self.internal["paths"][dir_type]):
|
|
||||||
file_item_path = os.path.join(self.internal["paths"][dir_type], file_item)
|
|
||||||
if str(file_item_path).endswith("_conf.json"):
|
if str(file_item_path).endswith("_conf.json"):
|
||||||
self.read_json_config(file_item_path)
|
self.read_json_config(file_item_path)
|
||||||
|
|
||||||
print("Loading Configuration File: " + str(self.config_file))
|
self.dict["epg"]["valid_methods"] = {None: {}}
|
||||||
self.read_ini_config(self.config_file)
|
self.dict["origins"] = {}
|
||||||
|
self.dict["origins"]["valid_methods"] = {}
|
||||||
|
self.dict["streaming"]["valid_methods"] = {"direct": {}}
|
||||||
|
self.dict["plugin_web_paths"] = {}
|
||||||
|
|
||||||
self.load_versions()
|
self.load_versions()
|
||||||
|
|
||||||
|
def register_web_path(self, name, path, plugin_dict_name):
|
||||||
|
self.dict["plugin_web_paths"][name.lower()] = {
|
||||||
|
"name": name,
|
||||||
|
"namespace": name.lower(),
|
||||||
|
"path": path,
|
||||||
|
"plugin": plugin_dict_name
|
||||||
|
}
|
||||||
|
|
||||||
|
def register_valid_origin_method(self, method_item):
|
||||||
|
self.dict["origins"]["valid_methods"][method_item.lower()] = {
|
||||||
|
"name": method_item,
|
||||||
|
"namespace": method_item.lower(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def register_valid_streaming_method(self, method_item, plugin_dict_name):
|
||||||
|
self.dict["streaming"]["valid_methods"][method_item.lower()] = {
|
||||||
|
"name": method_item,
|
||||||
|
"namespace": method_item.lower(),
|
||||||
|
"plugin": plugin_dict_name
|
||||||
|
}
|
||||||
|
|
||||||
|
def register_valid_epg_method(self, method_item, plugin_dict_name):
|
||||||
|
self.dict["epg"]["valid_methods"][method_item.lower()] = {
|
||||||
|
"name": method_item,
|
||||||
|
"namespace": method_item.lower(),
|
||||||
|
"plugin": plugin_dict_name
|
||||||
|
}
|
||||||
|
|
||||||
|
def register_version(self, item_name, item_version, item_type):
|
||||||
|
self.internal["versions"][item_name] = {
|
||||||
|
"name": item_name,
|
||||||
|
"version": item_version,
|
||||||
|
"type": item_type
|
||||||
|
}
|
||||||
|
|
||||||
|
def import_conf_json(self, file_item_path):
|
||||||
|
self.read_json_config(file_item_path)
|
||||||
|
|
||||||
def load_versions(self):
|
def load_versions(self):
|
||||||
|
|
||||||
self.internal["versions"] = {}
|
self.register_version("fHDHR", fHDHR_VERSION, "fHDHR")
|
||||||
|
self.register_version("fHDHR_web", self.fHDHR_web.fHDHR_web_VERSION, "fHDHR")
|
||||||
|
|
||||||
self.internal["versions"]["fHDHR"] = fHDHR_VERSION
|
self.register_version("Python", sys.version, "env")
|
||||||
|
|
||||||
self.internal["versions"]["fHDHR_web"] = self.fHDHR_web.fHDHR_web_VERSION
|
|
||||||
|
|
||||||
self.internal["versions"][self.origin.ORIGIN_NAME] = self.origin.ORIGIN_VERSION
|
|
||||||
|
|
||||||
self.internal["versions"]["Python"] = sys.version
|
|
||||||
|
|
||||||
opersystem = platform.system()
|
opersystem = platform.system()
|
||||||
self.internal["versions"]["Operating System"] = opersystem
|
self.register_version("Operating System", opersystem, "env")
|
||||||
if opersystem in ["Linux", "Darwin"]:
|
if opersystem in ["Linux", "Darwin"]:
|
||||||
# Linux/Mac
|
# Linux/Mac
|
||||||
if os.getuid() == 0 or os.geteuid() == 0:
|
if os.getuid() == 0 or os.geteuid() == 0:
|
||||||
@ -88,41 +120,90 @@ class Config():
|
|||||||
print("Uncommon Operating System, use at your own risk.")
|
print("Uncommon Operating System, use at your own risk.")
|
||||||
|
|
||||||
isdocker = is_docker()
|
isdocker = is_docker()
|
||||||
self.internal["versions"]["Docker"] = isdocker
|
self.register_version("Docker", isdocker, "env")
|
||||||
|
|
||||||
if self.dict["fhdhr"]["stream_type"] == "ffmpeg":
|
def user_config(self):
|
||||||
try:
|
print("Loading Configuration File: %s" % self.config_file)
|
||||||
ffmpeg_command = [self.dict["ffmpeg"]["path"],
|
self.read_ini_config(self.config_file)
|
||||||
"-version",
|
|
||||||
"pipe:stdout"
|
|
||||||
]
|
|
||||||
|
|
||||||
ffmpeg_proc = subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE)
|
def config_verification_plugins(self):
|
||||||
ffmpeg_version = ffmpeg_proc.stdout.read()
|
required_missing = {}
|
||||||
ffmpeg_proc.terminate()
|
# create dict and combine items
|
||||||
ffmpeg_proc.communicate()
|
for config_section in list(self.conf_default.keys()):
|
||||||
ffmpeg_version = ffmpeg_version.decode().split("version ")[1].split(" ")[0]
|
for config_item in list(self.conf_default[config_section].keys()):
|
||||||
except FileNotFoundError:
|
if self.conf_default[config_section][config_item]["required"]:
|
||||||
ffmpeg_version = "Missing"
|
if not self.dict[config_section][config_item]:
|
||||||
print("Failed to find ffmpeg.")
|
if config_section not in list(required_missing.keys()):
|
||||||
self.internal["versions"]["ffmpeg"] = ffmpeg_version
|
required_missing[config_section] = []
|
||||||
|
required_missing[config_section].append(config_item)
|
||||||
|
for config_section in list(required_missing.keys()):
|
||||||
|
print("Warning! Required configuration options missing: [%s]%s" % (config_section, ", ".join(required_missing[config_section])))
|
||||||
|
|
||||||
if self.dict["fhdhr"]["stream_type"] == "vlc":
|
if self.dict["epg"]["method"] and self.dict["epg"]["method"] not in ["None"]:
|
||||||
try:
|
if isinstance(self.dict["epg"]["method"], str):
|
||||||
vlc_command = [self.dict["vlc"]["path"],
|
self.dict["epg"]["method"] = [self.dict["epg"]["method"]]
|
||||||
"--version",
|
epg_methods = []
|
||||||
"pipe:stdout"
|
for epg_method in self.dict["epg"]["method"]:
|
||||||
]
|
if epg_method in list(self.dict["epg"]["valid_methods"].keys()):
|
||||||
|
epg_methods.append(epg_method)
|
||||||
|
elif epg_method in list(self.dict["origins"]["valid_methods"].keys()):
|
||||||
|
epg_methods.append(epg_method)
|
||||||
|
else:
|
||||||
|
raise fHDHR.exceptions.ConfigurationError("Invalid EPG Method. Exiting...")
|
||||||
|
if self.dict["epg"]["method"]:
|
||||||
|
self.dict["epg"]["def_method"] = self.dict["epg"]["method"][0]
|
||||||
|
else:
|
||||||
|
self.dict["epg"]["def_method"] = None
|
||||||
|
|
||||||
vlc_proc = subprocess.Popen(vlc_command, stdout=subprocess.PIPE)
|
if self.dict["streaming"]["method"] not in self.dict["streaming"]["valid_methods"]:
|
||||||
vlc_version = vlc_proc.stdout.read()
|
raise fHDHR.exceptions.ConfigurationError("Invalid stream type. Exiting...")
|
||||||
vlc_proc.terminate()
|
|
||||||
vlc_proc.communicate()
|
def config_verification(self):
|
||||||
vlc_version = vlc_version.decode().split("version ")[1].split('\n')[0]
|
|
||||||
except FileNotFoundError:
|
if not self.dict["main"]["uuid"]:
|
||||||
vlc_version = "Missing"
|
self.dict["main"]["uuid"] = ''.join(random.choice("hijklmnopqrstuvwxyz") for i in range(8))
|
||||||
print("Failed to find vlc.")
|
self.write('uuid', self.dict["main"]["uuid"], 'main')
|
||||||
self.internal["versions"]["vlc"] = vlc_version
|
|
||||||
|
if self.dict["main"]["cache_dir"]:
|
||||||
|
if not pathlib.Path(self.dict["main"]["cache_dir"]).is_dir():
|
||||||
|
raise fHDHR.exceptions.ConfigurationError("Invalid Cache Directory. Exiting...")
|
||||||
|
self.internal["paths"]["cache_dir"] = pathlib.Path(self.dict["main"]["cache_dir"])
|
||||||
|
cache_dir = self.internal["paths"]["cache_dir"]
|
||||||
|
|
||||||
|
logs_dir = pathlib.Path(cache_dir).joinpath('logs')
|
||||||
|
self.internal["paths"]["logs_dir"] = logs_dir
|
||||||
|
if not logs_dir.is_dir():
|
||||||
|
logs_dir.mkdir()
|
||||||
|
|
||||||
|
self.dict["database"]["path"] = pathlib.Path(cache_dir).joinpath('fhdhr.db')
|
||||||
|
|
||||||
|
if not self.dict["fhdhr"]["discovery_address"] and self.dict["fhdhr"]["address"] != "0.0.0.0":
|
||||||
|
self.dict["fhdhr"]["discovery_address"] = self.dict["fhdhr"]["address"]
|
||||||
|
if not self.dict["fhdhr"]["discovery_address"] or self.dict["fhdhr"]["discovery_address"] == "0.0.0.0":
|
||||||
|
self.dict["fhdhr"]["discovery_address"] = None
|
||||||
|
|
||||||
|
def get_real_conf_value(self, key, confvalue):
|
||||||
|
if not confvalue:
|
||||||
|
confvalue = None
|
||||||
|
elif key == "xmltv_offset":
|
||||||
|
confvalue = str(confvalue)
|
||||||
|
elif str(confvalue) in ["0"]:
|
||||||
|
confvalue = 0
|
||||||
|
elif isint(confvalue):
|
||||||
|
confvalue = int(confvalue)
|
||||||
|
elif isfloat(confvalue):
|
||||||
|
confvalue = float(confvalue)
|
||||||
|
elif is_arithmetic(confvalue):
|
||||||
|
confvalue = eval(confvalue)
|
||||||
|
elif "," in confvalue:
|
||||||
|
confvalue = confvalue.split(",")
|
||||||
|
elif str(confvalue).lower() in ["none", ""]:
|
||||||
|
confvalue = None
|
||||||
|
elif str(confvalue).lower() in ["false"]:
|
||||||
|
confvalue = False
|
||||||
|
elif str(confvalue).lower() in ["true"]:
|
||||||
|
confvalue = True
|
||||||
|
return confvalue
|
||||||
|
|
||||||
def read_json_config(self, conffilepath):
|
def read_json_config(self, conffilepath):
|
||||||
with open(conffilepath, 'r') as jsonconf:
|
with open(conffilepath, 'r') as jsonconf:
|
||||||
@ -140,27 +221,13 @@ class Config():
|
|||||||
if key not in list(self.conf_default[section].keys()):
|
if key not in list(self.conf_default[section].keys()):
|
||||||
self.conf_default[section][key] = {}
|
self.conf_default[section][key] = {}
|
||||||
|
|
||||||
confvalue = confimport[section][key]["value"]
|
confvalue = self.get_real_conf_value(key, confimport[section][key]["value"])
|
||||||
if isint(confvalue):
|
|
||||||
confvalue = int(confvalue)
|
|
||||||
elif isfloat(confvalue):
|
|
||||||
confvalue = float(confvalue)
|
|
||||||
elif is_arithmetic(confvalue):
|
|
||||||
confvalue = eval(confvalue)
|
|
||||||
elif "," in confvalue:
|
|
||||||
confvalue = confvalue.split(",")
|
|
||||||
elif str(confvalue).lower() in ["none"]:
|
|
||||||
confvalue = None
|
|
||||||
elif str(confvalue).lower() in ["false"]:
|
|
||||||
confvalue = False
|
|
||||||
elif str(confvalue).lower() in ["true"]:
|
|
||||||
confvalue = True
|
|
||||||
|
|
||||||
self.dict[section][key] = confvalue
|
self.dict[section][key] = confvalue
|
||||||
|
|
||||||
self.conf_default[section][key]["value"] = confvalue
|
self.conf_default[section][key]["value"] = confvalue
|
||||||
|
|
||||||
for config_option in ["config_web_hidden", "config_file", "config_web"]:
|
for config_option in ["config_web_hidden", "config_file", "config_web", "required"]:
|
||||||
if config_option not in list(confimport[section][key].keys()):
|
if config_option not in list(confimport[section][key].keys()):
|
||||||
config_option_value = False
|
config_option_value = False
|
||||||
else:
|
else:
|
||||||
@ -180,22 +247,7 @@ class Config():
|
|||||||
if each_section.lower() not in list(self.dict.keys()):
|
if each_section.lower() not in list(self.dict.keys()):
|
||||||
self.dict[each_section.lower()] = {}
|
self.dict[each_section.lower()] = {}
|
||||||
for (each_key, each_val) in config_handler.items(each_section):
|
for (each_key, each_val) in config_handler.items(each_section):
|
||||||
if not each_val:
|
each_val = self.get_real_conf_value(each_key, each_val)
|
||||||
each_val = None
|
|
||||||
elif each_val.lower() in ["none"]:
|
|
||||||
each_val = None
|
|
||||||
elif each_val.lower() in ["false"]:
|
|
||||||
each_val = False
|
|
||||||
elif each_val.lower() in ["true"]:
|
|
||||||
each_val = True
|
|
||||||
elif isint(each_val):
|
|
||||||
each_val = int(each_val)
|
|
||||||
elif isfloat(each_val):
|
|
||||||
each_val = float(each_val)
|
|
||||||
elif is_arithmetic(each_val):
|
|
||||||
each_val = eval(each_val)
|
|
||||||
elif "," in each_val:
|
|
||||||
each_val = each_val.split(",")
|
|
||||||
|
|
||||||
import_val = True
|
import_val = True
|
||||||
if each_section in list(self.conf_default.keys()):
|
if each_section in list(self.conf_default.keys()):
|
||||||
@ -206,10 +258,23 @@ class Config():
|
|||||||
if import_val:
|
if import_val:
|
||||||
self.dict[each_section.lower()][each_key.lower()] = each_val
|
self.dict[each_section.lower()][each_key.lower()] = each_val
|
||||||
|
|
||||||
def write(self, section, key, value):
|
def write(self, key, value, section):
|
||||||
if section == self.dict["main"]["dictpopname"]:
|
|
||||||
self.dict["origin"][key] = value
|
if not value:
|
||||||
else:
|
value = None
|
||||||
|
if value.lower() in ["none"]:
|
||||||
|
value = None
|
||||||
|
elif value.lower() in ["false"]:
|
||||||
|
value = False
|
||||||
|
elif value.lower() in ["true"]:
|
||||||
|
value = True
|
||||||
|
elif isint(value):
|
||||||
|
value = int(value)
|
||||||
|
elif isfloat(value):
|
||||||
|
value = float(value)
|
||||||
|
elif isinstance(value, list):
|
||||||
|
",".join(value)
|
||||||
|
|
||||||
self.dict[section][key] = value
|
self.dict[section][key] = value
|
||||||
|
|
||||||
config_handler = configparser.ConfigParser()
|
config_handler = configparser.ConfigParser()
|
||||||
@ -218,100 +283,11 @@ class Config():
|
|||||||
if not config_handler.has_section(section):
|
if not config_handler.has_section(section):
|
||||||
config_handler.add_section(section)
|
config_handler.add_section(section)
|
||||||
|
|
||||||
config_handler.set(section, key, value)
|
config_handler.set(section, key, str(value))
|
||||||
|
|
||||||
with open(self.config_file, 'w') as config_file:
|
with open(self.config_file, 'w') as config_file:
|
||||||
config_handler.write(config_file)
|
config_handler.write(config_file)
|
||||||
|
|
||||||
def config_verification(self):
|
|
||||||
|
|
||||||
if self.dict["main"]["thread_method"] not in ["threading", "multiprocessing"]:
|
|
||||||
raise fHDHR.exceptions.ConfigurationError("Invalid Threading Method. Exiting...")
|
|
||||||
|
|
||||||
if self.dict["main"]["required"]:
|
|
||||||
required_missing = []
|
|
||||||
if isinstance(self.dict["main"]["required"], str):
|
|
||||||
self.dict["main"]["required"] = [self.dict["main"]["required"]]
|
|
||||||
if len(self.dict["main"]["required"]):
|
|
||||||
for req_item in self.dict["main"]["required"]:
|
|
||||||
req_section = req_item.split("/")[0]
|
|
||||||
req_key = req_item.split("/")[1]
|
|
||||||
if not self.dict[req_section][req_key]:
|
|
||||||
required_missing.append(req_item)
|
|
||||||
if len(required_missing):
|
|
||||||
raise fHDHR.exceptions.ConfigurationError("Required configuration options missing: " + ", ".join(required_missing))
|
|
||||||
|
|
||||||
self.dict["origin"] = self.dict.pop(self.dict["main"]["dictpopname"])
|
|
||||||
|
|
||||||
if isinstance(self.dict["main"]["valid_epg_methods"], str):
|
|
||||||
self.dict["main"]["valid_epg_methods"] = [self.dict["main"]["valid_epg_methods"]]
|
|
||||||
|
|
||||||
if self.dict["epg"]["method"] and self.dict["epg"]["method"] not in ["None"]:
|
|
||||||
if isinstance(self.dict["epg"]["method"], str):
|
|
||||||
self.dict["epg"]["method"] = [self.dict["epg"]["method"]]
|
|
||||||
epg_methods = []
|
|
||||||
for epg_method in self.dict["epg"]["method"]:
|
|
||||||
if epg_method == self.dict["main"]["dictpopname"] or epg_method == "origin":
|
|
||||||
epg_methods.append("origin")
|
|
||||||
elif epg_method in ["None"]:
|
|
||||||
raise fHDHR.exceptions.ConfigurationError("Invalid EPG Method. Exiting...")
|
|
||||||
elif epg_method in self.dict["main"]["valid_epg_methods"]:
|
|
||||||
epg_methods.append(epg_method)
|
|
||||||
else:
|
|
||||||
raise fHDHR.exceptions.ConfigurationError("Invalid EPG Method. Exiting...")
|
|
||||||
self.dict["epg"]["def_method"] = self.dict["epg"]["method"][0]
|
|
||||||
|
|
||||||
if not self.dict["main"]["uuid"]:
|
|
||||||
self.dict["main"]["uuid"] = ''.join(random.choice("hijklmnopqrstuvwxyz") for i in range(8))
|
|
||||||
self.write('main', 'uuid', self.dict["main"]["uuid"])
|
|
||||||
|
|
||||||
if self.dict["main"]["cache_dir"]:
|
|
||||||
if not pathlib.Path(self.dict["main"]["cache_dir"]).is_dir():
|
|
||||||
raise fHDHR.exceptions.ConfigurationError("Invalid Cache Directory. Exiting...")
|
|
||||||
self.internal["paths"]["cache_dir"] = pathlib.Path(self.dict["main"]["cache_dir"])
|
|
||||||
cache_dir = self.internal["paths"]["cache_dir"]
|
|
||||||
|
|
||||||
logs_dir = pathlib.Path(cache_dir).joinpath('logs')
|
|
||||||
self.internal["paths"]["logs_dir"] = logs_dir
|
|
||||||
if not logs_dir.is_dir():
|
|
||||||
logs_dir.mkdir()
|
|
||||||
|
|
||||||
self.dict["database"]["path"] = pathlib.Path(cache_dir).joinpath('fhdhr.db')
|
|
||||||
|
|
||||||
if self.dict["fhdhr"]["stream_type"] not in ["direct", "ffmpeg", "vlc"]:
|
|
||||||
raise fHDHR.exceptions.ConfigurationError("Invalid stream type. Exiting...")
|
|
||||||
|
|
||||||
if not self.dict["fhdhr"]["discovery_address"] and self.dict["fhdhr"]["address"] != "0.0.0.0":
|
|
||||||
self.dict["fhdhr"]["discovery_address"] = self.dict["fhdhr"]["address"]
|
|
||||||
if not self.dict["fhdhr"]["discovery_address"] or self.dict["fhdhr"]["discovery_address"] == "0.0.0.0":
|
|
||||||
self.dict["fhdhr"]["discovery_address"] = None
|
|
||||||
|
|
||||||
def logging_setup(self):
|
|
||||||
|
|
||||||
log_level = self.dict["logging"]["level"].upper()
|
|
||||||
|
|
||||||
# Create a custom logger
|
|
||||||
logging.basicConfig(format='%(name)s - %(levelname)s - %(message)s', level=log_level)
|
|
||||||
logger = logging.getLogger('fHDHR')
|
|
||||||
log_file = os.path.join(self.internal["paths"]["logs_dir"], 'fHDHR.log')
|
|
||||||
|
|
||||||
# Create handlers
|
|
||||||
# c_handler = logging.StreamHandler()
|
|
||||||
f_handler = logging.FileHandler(log_file)
|
|
||||||
# c_handler.setLevel(log_level)
|
|
||||||
f_handler.setLevel(log_level)
|
|
||||||
|
|
||||||
# Create formatters and add it to handlers
|
|
||||||
# c_format = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
|
|
||||||
f_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
||||||
# c_handler.setFormatter(c_format)
|
|
||||||
f_handler.setFormatter(f_format)
|
|
||||||
|
|
||||||
# Add handlers to the logger
|
|
||||||
# logger.addHandler(c_handler)
|
|
||||||
logger.addHandler(f_handler)
|
|
||||||
return logger
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
''' will only get called for undefined attributes '''
|
''' will only get called for undefined attributes '''
|
||||||
if name in list(self.dict.keys()):
|
if name in list(self.dict.keys()):
|
||||||
|
|||||||
@ -32,28 +32,10 @@ MYSQL_TABLE_ARGS = {'mysql_engine': 'InnoDB',
|
|||||||
'mysql_collate': 'utf8mb4_unicode_ci'}
|
'mysql_collate': 'utf8mb4_unicode_ci'}
|
||||||
|
|
||||||
|
|
||||||
class ChannelValues(BASE):
|
class PluginValues(BASE):
|
||||||
__tablename__ = 'channel_values'
|
__tablename__ = 'plugin_values'
|
||||||
__table_args__ = MYSQL_TABLE_ARGS
|
__table_args__ = MYSQL_TABLE_ARGS
|
||||||
channel = Column(String(255), primary_key=True)
|
pluginitem = Column(String(255), primary_key=True)
|
||||||
namespace = Column(String(255), primary_key=True)
|
|
||||||
key = Column(String(255), primary_key=True)
|
|
||||||
value = Column(Text())
|
|
||||||
|
|
||||||
|
|
||||||
class ProgramValues(BASE):
|
|
||||||
__tablename__ = 'program_values'
|
|
||||||
__table_args__ = MYSQL_TABLE_ARGS
|
|
||||||
program = Column(String(255), primary_key=True)
|
|
||||||
namespace = Column(String(255), primary_key=True)
|
|
||||||
key = Column(String(255), primary_key=True)
|
|
||||||
value = Column(Text())
|
|
||||||
|
|
||||||
|
|
||||||
class CacheValues(BASE):
|
|
||||||
__tablename__ = 'cache_values'
|
|
||||||
__table_args__ = MYSQL_TABLE_ARGS
|
|
||||||
cacheitem = Column(String(255), primary_key=True)
|
|
||||||
namespace = Column(String(255), primary_key=True)
|
namespace = Column(String(255), primary_key=True)
|
||||||
key = Column(String(255), primary_key=True)
|
key = Column(String(255), primary_key=True)
|
||||||
value = Column(Text())
|
value = Column(Text())
|
||||||
@ -148,198 +130,6 @@ class fHDHRdb(object):
|
|||||||
def get_uri(self):
|
def get_uri(self):
|
||||||
return self.url
|
return self.url
|
||||||
|
|
||||||
# Channel Values
|
|
||||||
|
|
||||||
def set_channel_value(self, channel, key, value, namespace='default'):
|
|
||||||
channel = channel.lower()
|
|
||||||
value = json.dumps(value, ensure_ascii=False)
|
|
||||||
session = self.ssession()
|
|
||||||
try:
|
|
||||||
result = session.query(ChannelValues) \
|
|
||||||
.filter(ChannelValues.channel == channel)\
|
|
||||||
.filter(ChannelValues.namespace == namespace)\
|
|
||||||
.filter(ChannelValues.key == key) \
|
|
||||||
.one_or_none()
|
|
||||||
# ChannelValues exists, update
|
|
||||||
if result:
|
|
||||||
result.value = value
|
|
||||||
session.commit()
|
|
||||||
# DNE - Insert
|
|
||||||
else:
|
|
||||||
new_channelvalue = ChannelValues(channel=channel, namespace=namespace, key=key, value=value)
|
|
||||||
session.add(new_channelvalue)
|
|
||||||
session.commit()
|
|
||||||
except SQLAlchemyError:
|
|
||||||
session.rollback()
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
def get_channel_value(self, channel, key, namespace='default'):
|
|
||||||
channel = channel.lower()
|
|
||||||
session = self.ssession()
|
|
||||||
try:
|
|
||||||
result = session.query(ChannelValues) \
|
|
||||||
.filter(ChannelValues.channel == channel)\
|
|
||||||
.filter(ChannelValues.namespace == namespace)\
|
|
||||||
.filter(ChannelValues.key == key) \
|
|
||||||
.one_or_none()
|
|
||||||
if result is not None:
|
|
||||||
result = result.value
|
|
||||||
return _deserialize(result)
|
|
||||||
except SQLAlchemyError:
|
|
||||||
session.rollback()
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
def delete_channel_value(self, channel, key, namespace='default'):
|
|
||||||
channel = channel.lower()
|
|
||||||
session = self.ssession()
|
|
||||||
try:
|
|
||||||
result = session.query(ChannelValues) \
|
|
||||||
.filter(ChannelValues.channel == channel)\
|
|
||||||
.filter(ChannelValues.namespace == namespace)\
|
|
||||||
.filter(ChannelValues.key == key) \
|
|
||||||
.one_or_none()
|
|
||||||
# ChannelValues exists, delete
|
|
||||||
if result:
|
|
||||||
session.delete(result)
|
|
||||||
session.commit()
|
|
||||||
except SQLAlchemyError:
|
|
||||||
session.rollback()
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
# Program Values
|
|
||||||
|
|
||||||
def set_program_value(self, program, key, value, namespace='default'):
|
|
||||||
program = program.lower()
|
|
||||||
value = json.dumps(value, ensure_ascii=False)
|
|
||||||
session = self.ssession()
|
|
||||||
try:
|
|
||||||
result = session.query(ProgramValues) \
|
|
||||||
.filter(ProgramValues.program == program)\
|
|
||||||
.filter(ProgramValues.namespace == namespace)\
|
|
||||||
.filter(ProgramValues.key == key) \
|
|
||||||
.one_or_none()
|
|
||||||
# ProgramValue exists, update
|
|
||||||
if result:
|
|
||||||
result.value = value
|
|
||||||
session.commit()
|
|
||||||
# DNE - Insert
|
|
||||||
else:
|
|
||||||
new_programvalue = ProgramValues(program=program, namespace=namespace, key=key, value=value)
|
|
||||||
session.add(new_programvalue)
|
|
||||||
session.commit()
|
|
||||||
except SQLAlchemyError:
|
|
||||||
session.rollback()
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
def get_program_value(self, program, key, namespace='default'):
|
|
||||||
program = program.lower()
|
|
||||||
session = self.ssession()
|
|
||||||
try:
|
|
||||||
result = session.query(ProgramValues) \
|
|
||||||
.filter(ProgramValues.program == program)\
|
|
||||||
.filter(ProgramValues.namespace == namespace)\
|
|
||||||
.filter(ProgramValues.key == key) \
|
|
||||||
.one_or_none()
|
|
||||||
if result is not None:
|
|
||||||
result = result.value
|
|
||||||
return _deserialize(result)
|
|
||||||
except SQLAlchemyError:
|
|
||||||
session.rollback()
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
def delete_program_value(self, program, key, namespace='default'):
|
|
||||||
program = program.lower()
|
|
||||||
session = self.ssession()
|
|
||||||
try:
|
|
||||||
result = session.query(ProgramValues) \
|
|
||||||
.filter(ProgramValues.program == program)\
|
|
||||||
.filter(ProgramValues.namespace == namespace)\
|
|
||||||
.filter(ProgramValues.key == key) \
|
|
||||||
.one_or_none()
|
|
||||||
# ProgramValue exists, delete
|
|
||||||
if result:
|
|
||||||
session.delete(result)
|
|
||||||
session.commit()
|
|
||||||
except SQLAlchemyError:
|
|
||||||
session.rollback()
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
# Cache Values
|
|
||||||
|
|
||||||
def set_cacheitem_value(self, cacheitem, key, value, namespace='default'):
|
|
||||||
cacheitem = cacheitem.lower()
|
|
||||||
value = json.dumps(value, ensure_ascii=False)
|
|
||||||
session = self.ssession()
|
|
||||||
try:
|
|
||||||
result = session.query(CacheValues) \
|
|
||||||
.filter(CacheValues.cacheitem == cacheitem)\
|
|
||||||
.filter(CacheValues.namespace == namespace)\
|
|
||||||
.filter(CacheValues.key == key) \
|
|
||||||
.one_or_none()
|
|
||||||
# ProgramValue exists, update
|
|
||||||
if result:
|
|
||||||
result.value = value
|
|
||||||
session.commit()
|
|
||||||
# DNE - Insert
|
|
||||||
else:
|
|
||||||
new_cacheitemvalue = CacheValues(cacheitem=cacheitem, namespace=namespace, key=key, value=value)
|
|
||||||
session.add(new_cacheitemvalue)
|
|
||||||
session.commit()
|
|
||||||
except SQLAlchemyError:
|
|
||||||
session.rollback()
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
def get_cacheitem_value(self, cacheitem, key, namespace='default'):
|
|
||||||
cacheitem = cacheitem.lower()
|
|
||||||
session = self.ssession()
|
|
||||||
try:
|
|
||||||
result = session.query(CacheValues) \
|
|
||||||
.filter(CacheValues.cacheitem == cacheitem)\
|
|
||||||
.filter(CacheValues.namespace == namespace)\
|
|
||||||
.filter(CacheValues.key == key) \
|
|
||||||
.one_or_none()
|
|
||||||
if result is not None:
|
|
||||||
result = result.value
|
|
||||||
return _deserialize(result)
|
|
||||||
except SQLAlchemyError:
|
|
||||||
session.rollback()
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
def delete_cacheitem_value(self, cacheitem, key, namespace='default'):
|
|
||||||
cacheitem = cacheitem.lower()
|
|
||||||
session = self.ssession()
|
|
||||||
try:
|
|
||||||
result = session.query(CacheValues) \
|
|
||||||
.filter(CacheValues.cacheitem == cacheitem)\
|
|
||||||
.filter(CacheValues.namespace == namespace)\
|
|
||||||
.filter(CacheValues.key == key) \
|
|
||||||
.one_or_none()
|
|
||||||
# ProgramValue exists, delete
|
|
||||||
if result:
|
|
||||||
session.delete(result)
|
|
||||||
session.commit()
|
|
||||||
except SQLAlchemyError:
|
|
||||||
session.rollback()
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
# fHDHR Values
|
# fHDHR Values
|
||||||
|
|
||||||
def set_fhdhr_value(self, item, key, value, namespace='default'):
|
def set_fhdhr_value(self, item, key, value, namespace='default'):
|
||||||
@ -358,8 +148,8 @@ class fHDHRdb(object):
|
|||||||
session.commit()
|
session.commit()
|
||||||
# DNE - Insert
|
# DNE - Insert
|
||||||
else:
|
else:
|
||||||
new_cacheitemvalue = fHDHRValues(item=item, namespace=namespace, key=key, value=value)
|
new_pluginitemvalue = fHDHRValues(item=item, namespace=namespace, key=key, value=value)
|
||||||
session.add(new_cacheitemvalue)
|
session.add(new_pluginitemvalue)
|
||||||
session.commit()
|
session.commit()
|
||||||
except SQLAlchemyError:
|
except SQLAlchemyError:
|
||||||
session.rollback()
|
session.rollback()
|
||||||
@ -403,3 +193,67 @@ class fHDHRdb(object):
|
|||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
|
# Plugin Values
|
||||||
|
|
||||||
|
def set_plugin_value(self, pluginitem, key, value, namespace='default'):
|
||||||
|
pluginitem = pluginitem.lower()
|
||||||
|
value = json.dumps(value, ensure_ascii=False)
|
||||||
|
session = self.ssession()
|
||||||
|
try:
|
||||||
|
result = session.query(PluginValues) \
|
||||||
|
.filter(PluginValues.pluginitem == pluginitem)\
|
||||||
|
.filter(PluginValues.namespace == namespace)\
|
||||||
|
.filter(PluginValues.key == key) \
|
||||||
|
.one_or_none()
|
||||||
|
# ProgramValue exists, update
|
||||||
|
if result:
|
||||||
|
result.value = value
|
||||||
|
session.commit()
|
||||||
|
# DNE - Insert
|
||||||
|
else:
|
||||||
|
new_pluginitemvalue = PluginValues(pluginitem=pluginitem, namespace=namespace, key=key, value=value)
|
||||||
|
session.add(new_pluginitemvalue)
|
||||||
|
session.commit()
|
||||||
|
except SQLAlchemyError:
|
||||||
|
session.rollback()
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
def get_plugin_value(self, pluginitem, key, namespace='default'):
|
||||||
|
pluginitem = pluginitem.lower()
|
||||||
|
session = self.ssession()
|
||||||
|
try:
|
||||||
|
result = session.query(PluginValues) \
|
||||||
|
.filter(PluginValues.pluginitem == pluginitem)\
|
||||||
|
.filter(PluginValues.namespace == namespace)\
|
||||||
|
.filter(PluginValues.key == key) \
|
||||||
|
.one_or_none()
|
||||||
|
if result is not None:
|
||||||
|
result = result.value
|
||||||
|
return _deserialize(result)
|
||||||
|
except SQLAlchemyError:
|
||||||
|
session.rollback()
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
def delete_plugin_value(self, pluginitem, key, namespace='default'):
|
||||||
|
pluginitem = pluginitem.lower()
|
||||||
|
session = self.ssession()
|
||||||
|
try:
|
||||||
|
result = session.query(PluginValues) \
|
||||||
|
.filter(PluginValues.pluginitem == pluginitem)\
|
||||||
|
.filter(PluginValues.namespace == namespace)\
|
||||||
|
.filter(PluginValues.key == key) \
|
||||||
|
.one_or_none()
|
||||||
|
# ProgramValue exists, delete
|
||||||
|
if result:
|
||||||
|
session.delete(result)
|
||||||
|
session.commit()
|
||||||
|
except SQLAlchemyError:
|
||||||
|
session.rollback()
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
session.close()
|
||||||
|
|||||||
@ -3,16 +3,16 @@ from .epg import EPG
|
|||||||
from .tuners import Tuners
|
from .tuners import Tuners
|
||||||
from .images import imageHandler
|
from .images import imageHandler
|
||||||
from .ssdp import SSDPServer
|
from .ssdp import SSDPServer
|
||||||
from .cluster import fHDHR_Cluster
|
|
||||||
|
|
||||||
|
|
||||||
class fHDHR_Device():
|
class fHDHR_Device():
|
||||||
|
|
||||||
def __init__(self, fhdhr, originwrapper, alternative_epg):
|
def __init__(self, fhdhr, origins):
|
||||||
|
self.fhdhr = fhdhr
|
||||||
|
|
||||||
self.channels = Channels(fhdhr, originwrapper)
|
self.channels = Channels(fhdhr, origins)
|
||||||
|
|
||||||
self.epg = EPG(fhdhr, self.channels, originwrapper, alternative_epg)
|
self.epg = EPG(fhdhr, self.channels, origins)
|
||||||
|
|
||||||
self.tuners = Tuners(fhdhr, self.epg, self.channels)
|
self.tuners = Tuners(fhdhr, self.epg, self.channels)
|
||||||
|
|
||||||
@ -20,4 +20,15 @@ class fHDHR_Device():
|
|||||||
|
|
||||||
self.ssdp = SSDPServer(fhdhr)
|
self.ssdp = SSDPServer(fhdhr)
|
||||||
|
|
||||||
self.cluster = fHDHR_Cluster(fhdhr, self.ssdp)
|
self.interfaces = {}
|
||||||
|
|
||||||
|
for plugin_name in list(self.fhdhr.plugins.plugins.keys()):
|
||||||
|
if self.fhdhr.plugins.plugins[plugin_name].manifest["type"] == "interface":
|
||||||
|
method = self.fhdhr.plugins.plugins[plugin_name].name.lower()
|
||||||
|
plugin_utils = self.fhdhr.plugins.plugins[plugin_name].plugin_utils
|
||||||
|
plugin_utils.channels = self.channels
|
||||||
|
plugin_utils.epg = self.epg
|
||||||
|
plugin_utils.tuners = self.tuners
|
||||||
|
plugin_utils.images = self.images
|
||||||
|
plugin_utils.ssdp = self.ssdp
|
||||||
|
self.interfaces[method] = self.fhdhr.plugins.plugins[plugin_name].Plugin_OBJ(fhdhr, plugin_utils)
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import datetime
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from fHDHR.tools import hours_between_datetime
|
from fHDHR.tools import humanized_time
|
||||||
|
|
||||||
from .channel import Channel
|
from .channel import Channel
|
||||||
from .chan_ident import Channel_IDs
|
from .chan_ident import Channel_IDs
|
||||||
@ -9,51 +8,111 @@ from .chan_ident import Channel_IDs
|
|||||||
|
|
||||||
class Channels():
|
class Channels():
|
||||||
|
|
||||||
def __init__(self, fhdhr, originwrapper):
|
def __init__(self, fhdhr, origins):
|
||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
|
|
||||||
self.origin = originwrapper
|
self.origins = origins
|
||||||
|
|
||||||
self.id_system = Channel_IDs(fhdhr)
|
self.id_system = Channel_IDs(fhdhr, origins)
|
||||||
|
|
||||||
self.list = {}
|
self.list = {}
|
||||||
self.list_update_time = None
|
for origin in list(self.origins.origins_dict.keys()):
|
||||||
|
self.list[origin] = {}
|
||||||
|
|
||||||
self.get_db_channels()
|
self.get_db_channels()
|
||||||
haseverscanned = self.fhdhr.db.get_fhdhr_value("channels", "scanned_time")
|
|
||||||
if (self.fhdhr.config.dict["fhdhr"]["chanscan_on_start"] or not haseverscanned):
|
|
||||||
self.get_channels()
|
|
||||||
|
|
||||||
def get_channel_obj(self, keyfind, valfind):
|
def get_channel_obj(self, keyfind, valfind, origin=None):
|
||||||
return next(self.list[fhdhr_id] for fhdhr_id in list(self.list.keys()) if self.list[fhdhr_id].dict[keyfind] == valfind)
|
if origin:
|
||||||
|
origin = origin.lower()
|
||||||
|
if keyfind == "number":
|
||||||
|
matches = [self.list[origin][x].dict["id"] for x in list(self.list[origin].keys()) if self.list[origin][x].number == valfind]
|
||||||
|
else:
|
||||||
|
matches = [self.list[origin][x].dict["id"] for x in list(self.list[origin].keys()) if self.list[origin][x].dict[keyfind] == valfind]
|
||||||
|
if len(matches):
|
||||||
|
return self.list[origin][matches[0]]
|
||||||
|
else:
|
||||||
|
matches = []
|
||||||
|
for origin in list(self.list.keys()):
|
||||||
|
if keyfind == "number":
|
||||||
|
matches = [self.list[origin][x].dict["id"] for x in list(self.list[origin].keys()) if self.list[origin][x].number == valfind]
|
||||||
|
else:
|
||||||
|
matches = [self.list[origin][x].dict["id"] for x in list(self.list[origin].keys()) if self.list[origin][x].dict[keyfind] == valfind]
|
||||||
|
if len(matches):
|
||||||
|
return self.list[origin][matches[0]]
|
||||||
|
if len(matches):
|
||||||
|
return self.list[origin][matches[0]]
|
||||||
|
return None
|
||||||
|
|
||||||
def get_channel_list(self, keyfind):
|
def get_channel_list(self, keyfind, origin=None):
|
||||||
return [self.list[x].dict[keyfind] for x in list(self.list.keys())]
|
if origin:
|
||||||
|
if keyfind == "number":
|
||||||
|
return [self.list[origin][x].number for x in [x["id"] for x in self.get_channels(origin)]]
|
||||||
|
else:
|
||||||
|
return [self.list[origin][x].dict[keyfind] for x in [x["id"] for x in self.get_channels(origin)]]
|
||||||
|
else:
|
||||||
|
matches = []
|
||||||
|
for origin in list(self.list.keys()):
|
||||||
|
if keyfind == "number":
|
||||||
|
next_match = [self.list[origin][x].number for x in [x["id"] for x in self.get_channels(origin)]]
|
||||||
|
else:
|
||||||
|
next_match = [self.list[origin][x].dict[keyfind] for x in [x["id"] for x in self.get_channels(origin)]]
|
||||||
|
if len(next_match):
|
||||||
|
matches.append(next_match)
|
||||||
|
return matches[0]
|
||||||
|
|
||||||
def set_channel_status(self, keyfind, valfind, updatedict):
|
def get_channel_dict(self, keyfind, valfind, origin=None):
|
||||||
self.get_channel_obj(keyfind, valfind).set_status(updatedict)
|
chan_obj = self.get_channel_obj(keyfind, valfind, origin)
|
||||||
|
if chan_obj:
|
||||||
|
return chan_obj.dict
|
||||||
|
return None
|
||||||
|
|
||||||
def set_channel_enablement_all(self, enablement):
|
def set_channel_status(self, keyfind, valfind, updatedict, origin):
|
||||||
for fhdhr_id in list(self.list.keys()):
|
self.get_channel_obj(keyfind, valfind, origin).set_status(updatedict)
|
||||||
self.list[fhdhr_id].set_enablement(enablement)
|
|
||||||
|
|
||||||
def set_channel_enablement(self, keyfind, valfind, enablement):
|
def set_channel_enablement_all(self, enablement, origin):
|
||||||
self.get_channel_obj(keyfind, valfind).set_enablement(enablement)
|
for fhdhr_id in [x["id"] for x in self.get_channels(origin)]:
|
||||||
|
self.list[fhdhr_id].set_enablement(enablement, origin)
|
||||||
|
|
||||||
def set_channel_favorite(self, keyfind, valfind, enablement):
|
def set_channel_enablement(self, keyfind, valfind, enablement, origin):
|
||||||
self.get_channel_obj(keyfind, valfind).set_favorite(enablement)
|
self.get_channel_obj(keyfind, valfind, origin).set_enablement(enablement)
|
||||||
|
|
||||||
def get_db_channels(self):
|
def set_channel_favorite(self, keyfind, valfind, enablement, origin):
|
||||||
self.fhdhr.logger.info("Checking for Channel information stored in the database.")
|
self.get_channel_obj(keyfind, valfind, origin).set_favorite(enablement)
|
||||||
channel_ids = self.fhdhr.db.get_fhdhr_value("channels", "list") or []
|
|
||||||
|
def get_db_channels(self, origin=None):
|
||||||
|
|
||||||
|
if not origin:
|
||||||
|
origins_list = list(self.list.keys())
|
||||||
|
else:
|
||||||
|
origins_list = origin.lower()
|
||||||
|
|
||||||
|
if isinstance(origins_list, str):
|
||||||
|
origins_list = [origins_list]
|
||||||
|
|
||||||
|
for origin in origins_list:
|
||||||
|
self.fhdhr.logger.info("Checking for %s Channel information stored in the database." % origin)
|
||||||
|
channel_ids = self.fhdhr.db.get_fhdhr_value("channels", "list", origin) or []
|
||||||
if len(channel_ids):
|
if len(channel_ids):
|
||||||
self.fhdhr.logger.info("Found %s existing channels in the database." % str(len(channel_ids)))
|
self.fhdhr.logger.info("Found %s existing channels in the database." % str(len(channel_ids)))
|
||||||
for channel_id in channel_ids:
|
for channel_id in channel_ids:
|
||||||
channel_obj = Channel(self.fhdhr, self.id_system, channel_id=channel_id)
|
channel_obj = Channel(self.fhdhr, self.id_system, origin=origin, channel_id=channel_id)
|
||||||
channel_id = channel_obj.dict["id"]
|
channel_id = channel_obj.dict["id"]
|
||||||
self.list[channel_id] = channel_obj
|
self.list[origin][channel_id] = channel_obj
|
||||||
|
|
||||||
def get_channels(self, forceupdate=False):
|
def save_db_channels(self, origin=None):
|
||||||
|
if not origin:
|
||||||
|
origins_list = list(self.list.keys())
|
||||||
|
else:
|
||||||
|
origins_list = origin.lower()
|
||||||
|
|
||||||
|
if isinstance(origins_list, str):
|
||||||
|
origins_list = [origins_list]
|
||||||
|
|
||||||
|
for origin in origins_list:
|
||||||
|
channel_ids = [self.list[origin][x].dict["id"] for x in list(self.list[origin].keys())]
|
||||||
|
self.fhdhr.db.set_fhdhr_value("channels", "list", channel_ids, origin)
|
||||||
|
|
||||||
|
def get_channels(self, origin=None, forceupdate=False):
|
||||||
"""Pull Channels from origin.
|
"""Pull Channels from origin.
|
||||||
|
|
||||||
Output a list.
|
Output a list.
|
||||||
@ -61,54 +120,64 @@ class Channels():
|
|||||||
Don't pull more often than 12 hours.
|
Don't pull more often than 12 hours.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
updatelist = False
|
if not origin:
|
||||||
if not self.list_update_time:
|
origins_list = list(self.list.keys())
|
||||||
updatelist = True
|
else:
|
||||||
elif hours_between_datetime(self.list_update_time, datetime.datetime.now()) > 12:
|
origins_list = origin.lower().lower()
|
||||||
updatelist = True
|
|
||||||
elif forceupdate:
|
|
||||||
updatelist = True
|
|
||||||
|
|
||||||
if updatelist:
|
if isinstance(origins_list, str):
|
||||||
channel_origin_id_list = [str(self.list[x].dict["origin_id"]) for x in list(self.list.keys())]
|
origins_list = [origins_list]
|
||||||
|
|
||||||
self.fhdhr.logger.info("Performing Channel Scan.")
|
return_chan_list = []
|
||||||
|
for origin in origins_list:
|
||||||
|
|
||||||
channel_dict_list = self.origin.get_channels()
|
if not len(list(self.list[origin].keys())):
|
||||||
self.fhdhr.logger.info("Found %s channels for %s." % (len(channel_dict_list), self.fhdhr.config.dict["main"]["servicename"]))
|
self.get_db_channels(origin=origin)
|
||||||
|
|
||||||
|
if not forceupdate:
|
||||||
|
return_chan_list.extend([self.list[origin][x].dict for x in list(self.list[origin].keys())])
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
channel_origin_id_list = [str(self.list[origin][x].dict["origin_id"]) for x in list(self.list[origin].keys())]
|
||||||
|
|
||||||
|
self.fhdhr.logger.info("Performing Channel Scan for %s." % origin)
|
||||||
|
|
||||||
|
channel_dict_list = self.origins.origins_dict[origin].get_channels()
|
||||||
|
self.fhdhr.logger.info("Found %s channels for %s." % (len(channel_dict_list), origin))
|
||||||
|
|
||||||
|
self.fhdhr.logger.info("Performing Channel Import, This can take some time, Please wait.")
|
||||||
|
|
||||||
newchan = 0
|
newchan = 0
|
||||||
|
chan_scan_start = time.time()
|
||||||
for channel_info in channel_dict_list:
|
for channel_info in channel_dict_list:
|
||||||
|
|
||||||
chan_existing = False
|
chan_existing = str(channel_info["id"]) in channel_origin_id_list
|
||||||
if str(channel_info["id"]) in channel_origin_id_list:
|
|
||||||
chan_existing = True
|
if chan_existing:
|
||||||
channel_obj = self.get_channel_obj("origin_id", channel_info["id"])
|
channel_obj = self.get_channel_obj("origin_id", channel_info["id"], origin)
|
||||||
else:
|
else:
|
||||||
channel_obj = Channel(self.fhdhr, self.id_system, origin_id=channel_info["id"])
|
channel_obj = Channel(self.fhdhr, self.id_system, origin, origin_id=channel_info["id"])
|
||||||
|
|
||||||
channel_id = channel_obj.dict["id"]
|
channel_id = channel_obj.dict["id"]
|
||||||
channel_obj.basics(channel_info)
|
channel_obj.basics(channel_info)
|
||||||
if not chan_existing:
|
if not chan_existing:
|
||||||
self.list[channel_id] = channel_obj
|
self.list[origin][channel_id] = channel_obj
|
||||||
newchan += 1
|
newchan += 1
|
||||||
|
|
||||||
|
self.fhdhr.logger.info("%s Channel Import took %s" % (origin, humanized_time(time.time() - chan_scan_start)))
|
||||||
|
|
||||||
if not newchan:
|
if not newchan:
|
||||||
newchan = "no"
|
newchan = "no"
|
||||||
self.fhdhr.logger.info("Found %s NEW channels." % newchan)
|
self.fhdhr.logger.info("Found %s NEW channels for %s." % (newchan, origin))
|
||||||
|
|
||||||
self.fhdhr.logger.info("Total Channel Count: %s" % len(self.list.keys()))
|
self.fhdhr.logger.info("Total %s Channel Count: %s" % (origin, len(self.list[origin].keys())))
|
||||||
|
self.save_db_channels(origin=origin)
|
||||||
|
|
||||||
self.list_update_time = datetime.datetime.now()
|
self.fhdhr.db.set_fhdhr_value("channels", "scanned_time", time.time(), origin)
|
||||||
self.fhdhr.db.set_fhdhr_value("channels", "scanned_time", time.time())
|
return_chan_list.extend([self.list[origin][x].dict for x in list(self.list[origin].keys())])
|
||||||
|
|
||||||
channel_list = []
|
return return_chan_list
|
||||||
for chan_obj in list(self.list.keys()):
|
|
||||||
channel_list.append(self.list[chan_obj].dict)
|
|
||||||
return channel_list
|
|
||||||
|
|
||||||
def get_channel_stream(self, channel_number):
|
def get_channel_stream(self, stream_args, origin):
|
||||||
return self.origin.get_channel_stream(self.get_channel_dict("number", channel_number))
|
return self.origins.origins_dict[origin].get_channel_stream(self.get_channel_dict("number", stream_args["channel"]), stream_args)
|
||||||
|
|
||||||
def get_channel_dict(self, keyfind, valfind):
|
|
||||||
return self.get_channel_obj(keyfind, valfind).dict
|
|
||||||
|
|||||||
@ -2,36 +2,44 @@ import uuid
|
|||||||
|
|
||||||
|
|
||||||
class Channel_IDs():
|
class Channel_IDs():
|
||||||
def __init__(self, fhdhr):
|
def __init__(self, fhdhr, origins):
|
||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
|
self.origins = origins
|
||||||
|
|
||||||
def get(self, origin_id):
|
def get(self, origin_id, origin):
|
||||||
existing_ids = self.fhdhr.db.get_fhdhr_value("channels", "list") or []
|
existing_ids = self.fhdhr.db.get_fhdhr_value("channels", "list", origin) or []
|
||||||
existing_channel_info = [self.fhdhr.db.get_channel_value(channel_id, "dict") or {} for channel_id in existing_ids]
|
existing_channel_info = [self.fhdhr.db.get_fhdhr_value(channel_id, "dict", origin) or {} for channel_id in existing_ids]
|
||||||
for existing_channel in existing_channel_info:
|
for existing_channel in existing_channel_info:
|
||||||
if existing_channel["origin_id"] == origin_id:
|
if existing_channel["origin_id"] == origin_id:
|
||||||
return existing_channel["id"]
|
return existing_channel["id"]
|
||||||
return self.assign()
|
return self.assign(origin)
|
||||||
|
|
||||||
def assign(self):
|
def assign(self, origin):
|
||||||
existing_ids = self.fhdhr.db.get_fhdhr_value("channels", "list") or []
|
existing_ids = self.fhdhr.db.get_fhdhr_value("channels", "list", origin) or []
|
||||||
channel_id = None
|
channel_id = None
|
||||||
while not channel_id:
|
while not channel_id:
|
||||||
unique_id = str(uuid.uuid4())
|
unique_id = str(uuid.uuid4())
|
||||||
if str(unique_id) not in existing_ids:
|
if str(unique_id) not in existing_ids:
|
||||||
channel_id = str(unique_id)
|
channel_id = str(unique_id)
|
||||||
existing_ids.append(channel_id)
|
existing_ids.append(channel_id)
|
||||||
self.fhdhr.db.set_fhdhr_value("channels", "list", existing_ids)
|
self.fhdhr.db.set_fhdhr_value("channels", "list", existing_ids, origin)
|
||||||
return channel_id
|
return channel_id
|
||||||
|
|
||||||
def get_number(self, channel_id):
|
def get_number(self, channel_id, origin):
|
||||||
existing_ids = self.fhdhr.db.get_fhdhr_value("channels", "list") or []
|
existing_ids = self.fhdhr.db.get_fhdhr_value("channels", "list", origin) or []
|
||||||
existing_channel_info = [self.fhdhr.db.get_channel_value(channel_id, "dict") or {} for channel_id in existing_ids]
|
existing_channel_info = [self.fhdhr.db.get_fhdhr_value(channel_id, "dict", origin) or {} for channel_id in existing_ids]
|
||||||
cnumber = [existing_channel["number"] for existing_channel in existing_channel_info if existing_channel["id"] == channel_id] or None
|
cnumber = [existing_channel["number"] for existing_channel in existing_channel_info if existing_channel["id"] == channel_id] or None
|
||||||
if cnumber:
|
if cnumber:
|
||||||
return cnumber
|
return cnumber
|
||||||
|
|
||||||
used_numbers = [existing_channel["number"] for existing_channel in existing_channel_info]
|
used_numbers = []
|
||||||
|
for existing_channel in existing_channel_info:
|
||||||
|
if existing_channel["subnumber"]:
|
||||||
|
number = "%s.%s" % (existing_channel["number"], existing_channel["subnumber"])
|
||||||
|
else:
|
||||||
|
number = existing_channel["number"]
|
||||||
|
used_numbers.append(number)
|
||||||
|
|
||||||
for i in range(1000, 2000):
|
for i in range(1000, 2000):
|
||||||
if str(float(i)) not in used_numbers:
|
if str(float(i)) not in used_numbers:
|
||||||
break
|
break
|
||||||
|
|||||||
@ -3,21 +3,30 @@ import time
|
|||||||
|
|
||||||
class Channel():
|
class Channel():
|
||||||
|
|
||||||
def __init__(self, fhdhr, id_system, origin_id=None, channel_id=None):
|
def __init__(self, fhdhr, id_system, origin, origin_id=None, channel_id=None):
|
||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
|
self.origin = origin
|
||||||
|
|
||||||
self.id_system = id_system
|
self.id_system = id_system
|
||||||
|
|
||||||
if not channel_id:
|
if not channel_id:
|
||||||
if origin_id:
|
if origin_id:
|
||||||
channel_id = id_system.get(origin_id)
|
channel_id = id_system.get(origin_id, origin)
|
||||||
else:
|
else:
|
||||||
channel_id = id_system.assign()
|
channel_id = id_system.assign(origin)
|
||||||
self.channel_id = channel_id
|
self.channel_id = channel_id
|
||||||
self.dict = self.fhdhr.db.get_channel_value(str(channel_id), "dict") or self.default_dict
|
|
||||||
|
self.dict = self.fhdhr.db.get_fhdhr_value(str(channel_id), "dict", self.origin) or self.default_dict
|
||||||
self.verify_dict()
|
self.verify_dict()
|
||||||
|
|
||||||
self.fhdhr.db.set_channel_value(self.dict["id"], "dict", self.dict)
|
self.fhdhr.db.set_fhdhr_value(self.dict["id"], "dict", self.dict, self.origin)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def number(self):
|
||||||
|
if self.dict["subnumber"]:
|
||||||
|
return "%s.%s" % (self.dict["number"], self.dict["subnumber"])
|
||||||
|
else:
|
||||||
|
return self.dict["number"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def thumbnail(self):
|
def thumbnail(self):
|
||||||
@ -35,9 +44,9 @@ class Channel():
|
|||||||
return {
|
return {
|
||||||
"callsign": self.dict["callsign"],
|
"callsign": self.dict["callsign"],
|
||||||
"name": self.dict["name"],
|
"name": self.dict["name"],
|
||||||
"number": self.dict["number"],
|
"number": self.number,
|
||||||
"id": self.dict["origin_id"],
|
"id": self.dict["origin_id"],
|
||||||
"thumbnail": self.dict["thumbnail"],
|
"thumbnail": self.thumbnail,
|
||||||
"listing": [],
|
"listing": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,37 +58,59 @@ class Channel():
|
|||||||
for key in list(default_dict.keys()):
|
for key in list(default_dict.keys()):
|
||||||
if key not in list(self.dict.keys()):
|
if key not in list(self.dict.keys()):
|
||||||
self.dict[key] = default_dict[key]
|
self.dict[key] = default_dict[key]
|
||||||
|
if self.dict["number"]:
|
||||||
|
if "." in self.dict["number"]:
|
||||||
|
self.dict["subnumber"] = self.dict["number"].split(".")[1]
|
||||||
|
self.dict["number"] = self.dict["number"].split(".")[0]
|
||||||
|
|
||||||
def basics(self, channel_info):
|
def basics(self, channel_info):
|
||||||
"""Some Channel Information is Critical"""
|
"""Some Channel Information is Critical"""
|
||||||
|
|
||||||
if "name" not in list(channel_info.keys()):
|
if "name" not in list(channel_info.keys()):
|
||||||
channel_info["name"] = self.dict["id"]
|
channel_info["name"] = self.dict["id"]
|
||||||
|
elif not channel_info["name"]:
|
||||||
|
channel_info["name"] = self.dict["id"]
|
||||||
self.dict["origin_name"] = channel_info["name"]
|
self.dict["origin_name"] = channel_info["name"]
|
||||||
if not self.dict["name"]:
|
if not self.dict["name"]:
|
||||||
self.dict["name"] = self.dict["origin_name"]
|
self.dict["name"] = self.dict["origin_name"]
|
||||||
|
|
||||||
if "id" not in list(channel_info.keys()):
|
if "id" not in list(channel_info.keys()):
|
||||||
channel_info["id"] = channel_info["name"]
|
channel_info["id"] = channel_info["name"]
|
||||||
|
elif not channel_info["id"]:
|
||||||
|
channel_info["id"] = channel_info["name"]
|
||||||
self.dict["origin_id"] = channel_info["id"]
|
self.dict["origin_id"] = channel_info["id"]
|
||||||
|
|
||||||
if "callsign" not in list(channel_info.keys()):
|
if "callsign" not in list(channel_info.keys()):
|
||||||
channel_info["callsign"] = channel_info["name"]
|
channel_info["callsign"] = channel_info["name"]
|
||||||
|
elif not channel_info["callsign"]:
|
||||||
|
channel_info["callsign"] = channel_info["name"]
|
||||||
self.dict["origin_callsign"] = channel_info["callsign"]
|
self.dict["origin_callsign"] = channel_info["callsign"]
|
||||||
if not self.dict["callsign"]:
|
if not self.dict["callsign"]:
|
||||||
self.dict["callsign"] = self.dict["origin_callsign"]
|
self.dict["callsign"] = self.dict["origin_callsign"]
|
||||||
|
|
||||||
if "tags" not in list(channel_info.keys()):
|
if "tags" not in list(channel_info.keys()):
|
||||||
channel_info["tags"] = []
|
channel_info["tags"] = []
|
||||||
|
elif not channel_info["tags"]:
|
||||||
|
channel_info["tags"] = []
|
||||||
self.dict["origin_tags"] = channel_info["tags"]
|
self.dict["origin_tags"] = channel_info["tags"]
|
||||||
if not self.dict["tags"]:
|
if not self.dict["tags"]:
|
||||||
self.dict["tags"] = self.dict["origin_tags"]
|
self.dict["tags"] = self.dict["origin_tags"]
|
||||||
|
|
||||||
if "number" not in list(channel_info.keys()):
|
if "number" not in list(channel_info.keys()):
|
||||||
channel_info["number"] = self.id_system.get_number(channel_info["id"])
|
channel_info["number"] = self.id_system.get_number(channel_info["id"], self.origin)
|
||||||
self.dict["origin_number"] = str(float(channel_info["number"]))
|
elif not channel_info["number"]:
|
||||||
|
channel_info["number"] = self.id_system.get_number(channel_info["id"], self.origin)
|
||||||
|
self.dict["origin_number"] = str(channel_info["number"])
|
||||||
if not self.dict["number"]:
|
if not self.dict["number"]:
|
||||||
self.dict["number"] = self.dict["origin_number"]
|
self.dict["number"] = self.dict["origin_number"].split(".")[0]
|
||||||
|
try:
|
||||||
|
self.dict["subnumber"] = self.dict["origin_number"].split(".")[1]
|
||||||
|
except IndexError:
|
||||||
|
self.dict["subnumber"] = None
|
||||||
|
else:
|
||||||
|
if "." in self.dict["number"]:
|
||||||
|
self.dict["subnumber"] = self.dict["number"].split(".")[1]
|
||||||
|
self.dict["number"] = self.dict["number"].split(".")[0]
|
||||||
|
|
||||||
if "thumbnail" not in list(channel_info.keys()):
|
if "thumbnail" not in list(channel_info.keys()):
|
||||||
channel_info["thumbnail"] = None
|
channel_info["thumbnail"] = None
|
||||||
@ -98,7 +129,7 @@ class Channel():
|
|||||||
if "created" not in list(self.dict.keys()):
|
if "created" not in list(self.dict.keys()):
|
||||||
self.dict["created"] = time.time()
|
self.dict["created"] = time.time()
|
||||||
|
|
||||||
self.fhdhr.db.set_channel_value(self.dict["id"], "dict", self.dict)
|
self.fhdhr.db.set_fhdhr_value(self.dict["id"], "dict", self.dict, self.origin)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def default_dict(self):
|
def default_dict(self):
|
||||||
@ -106,7 +137,7 @@ class Channel():
|
|||||||
"id": str(self.channel_id), "origin_id": None,
|
"id": str(self.channel_id), "origin_id": None,
|
||||||
"name": None, "origin_name": None,
|
"name": None, "origin_name": None,
|
||||||
"callsign": None, "origin_callsign": None,
|
"callsign": None, "origin_callsign": None,
|
||||||
"number": None, "origin_number": None,
|
"number": None, "subnumber": None, "origin_number": None,
|
||||||
"tags": [], "origin_tags": [],
|
"tags": [], "origin_tags": [],
|
||||||
"thumbnail": None, "origin_thumbnail": None,
|
"thumbnail": None, "origin_thumbnail": None,
|
||||||
"enabled": True, "favorite": 0,
|
"enabled": True, "favorite": 0,
|
||||||
@ -114,48 +145,37 @@ class Channel():
|
|||||||
}
|
}
|
||||||
|
|
||||||
def destroy(self):
|
def destroy(self):
|
||||||
self.fhdhr.db.delete_channel_value(self.dict["id"], "dict")
|
self.fhdhr.db.delete_fhdhr_value(self.dict["id"], "dict", self.origin)
|
||||||
channel_ids = self.fhdhr.db.get_fhdhr_value("channels", "list") or []
|
channel_ids = self.fhdhr.db.get_fhdhr_value("channels", "list") or []
|
||||||
if self.dict["id"] in channel_ids:
|
if self.dict["id"] in channel_ids:
|
||||||
channel_ids.remove(self.dict["id"])
|
channel_ids.remove(self.dict["id"])
|
||||||
self.fhdhr.db.set_fhdhr_value("channels", "list", channel_ids)
|
self.fhdhr.db.set_fhdhr_value("channels", "list", channel_ids, self.origin)
|
||||||
|
|
||||||
def set_status(self, updatedict):
|
def set_status(self, updatedict):
|
||||||
for key in list(updatedict.keys()):
|
for key in list(updatedict.keys()):
|
||||||
if key == "number":
|
if key == "number":
|
||||||
updatedict[key] = str(float(updatedict[key]))
|
updatedict[key] = str(updatedict[key])
|
||||||
self.dict[key] = updatedict[key]
|
self.dict[key] = updatedict[key]
|
||||||
self.fhdhr.db.set_channel_value(self.dict["id"], "dict", self.dict)
|
self.fhdhr.db.set_fhdhr_value(self.dict["id"], "dict", self.dict, self.origin)
|
||||||
|
|
||||||
@property
|
|
||||||
def lineup_dict(self):
|
|
||||||
return {
|
|
||||||
'GuideNumber': self.dict['number'],
|
|
||||||
'GuideName': self.dict['name'],
|
|
||||||
'Tags': ",".join(self.dict['tags']),
|
|
||||||
'URL': self.stream_url,
|
|
||||||
'HD': self.dict["HD"],
|
|
||||||
"Favorite": self.dict["favorite"],
|
|
||||||
}
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def generic_image_url(self):
|
def generic_image_url(self):
|
||||||
return "/api/images?method=generate&type=channel&message=%s" % self.dict["number"]
|
return "/api/images?method=generate&type=channel&message=%s" % self.number
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def stream_url(self):
|
def api_stream_url(self):
|
||||||
return '/auto/v%s' % self.dict['number']
|
return '/api/tuners?method=stream&stream_method=%s&channel=%s&origin=%s' % (self.fhdhr.origins.origins_dict[self.origin].stream_method, self.dict["id"], self.origin)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def play_url(self):
|
def api_m3u_url(self):
|
||||||
return '/api/m3u?method=get&channel=%s' % self.dict['number']
|
return '/api/m3u?method=get&channel=%s&origin=%s' % (self.dict["id"], self.origin)
|
||||||
|
|
||||||
def set_favorite(self, enablement):
|
def set_favorite(self, enablement):
|
||||||
if enablement == "+":
|
if enablement == "+":
|
||||||
self.dict["favorite"] = 1
|
self.dict["favorite"] = 1
|
||||||
elif enablement == "+":
|
elif enablement == "-":
|
||||||
self.dict["favorite"] = 0
|
self.dict["favorite"] = 0
|
||||||
self.fhdhr.db.set_channel_value(self.dict["id"], "info", self.dict)
|
self.fhdhr.db.set_fhdhr_value(self.dict["id"], "info", self.dict, self.origin)
|
||||||
|
|
||||||
def set_enablement(self, enablement):
|
def set_enablement(self, enablement):
|
||||||
if enablement == "disable":
|
if enablement == "disable":
|
||||||
@ -167,7 +187,7 @@ class Channel():
|
|||||||
self.dict["enabled"] = False
|
self.dict["enabled"] = False
|
||||||
else:
|
else:
|
||||||
self.dict["enabled"] = True
|
self.dict["enabled"] = True
|
||||||
self.fhdhr.db.set_channel_value(self.dict["id"], "info", self.dict)
|
self.fhdhr.db.set_fhdhr_value(self.dict["id"], "info", self.dict, self.origin)
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
''' will only get called for undefined attributes '''
|
''' will only get called for undefined attributes '''
|
||||||
|
|||||||
@ -1,158 +0,0 @@
|
|||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
|
|
||||||
class fHDHR_Cluster():
|
|
||||||
|
|
||||||
def __init__(self, fhdhr, ssdp):
|
|
||||||
self.fhdhr = fhdhr
|
|
||||||
|
|
||||||
self.ssdp = ssdp
|
|
||||||
|
|
||||||
self.friendlyname = self.fhdhr.config.dict["fhdhr"]["friendlyname"]
|
|
||||||
|
|
||||||
if self.fhdhr.config.dict["fhdhr"]["discovery_address"]:
|
|
||||||
self.startup_sync()
|
|
||||||
|
|
||||||
def cluster(self):
|
|
||||||
return self.fhdhr.db.get_fhdhr_value("cluster", "dict") or self.default_cluster()
|
|
||||||
|
|
||||||
def get_cluster_dicts_web(self):
|
|
||||||
fhdhr_list = self.cluster()
|
|
||||||
locations = []
|
|
||||||
for location in list(fhdhr_list.keys()):
|
|
||||||
item_dict = {
|
|
||||||
"base_url": fhdhr_list[location]["base_url"],
|
|
||||||
"name": fhdhr_list[location]["name"]
|
|
||||||
}
|
|
||||||
if item_dict["base_url"] != self.fhdhr.api.base:
|
|
||||||
locations.append(item_dict)
|
|
||||||
if len(locations):
|
|
||||||
locations = sorted(locations, key=lambda i: i['name'])
|
|
||||||
return locations
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_list(self):
|
|
||||||
cluster = self.fhdhr.db.get_fhdhr_value("cluster", "dict") or self.default_cluster()
|
|
||||||
return_dict = {}
|
|
||||||
for location in list(cluster.keys()):
|
|
||||||
if location != self.fhdhr.api.base:
|
|
||||||
return_dict[location] = {
|
|
||||||
"Joined": True
|
|
||||||
}
|
|
||||||
|
|
||||||
detected_list = self.ssdp.detect_method.get()
|
|
||||||
for location in detected_list:
|
|
||||||
if location not in list(cluster.keys()):
|
|
||||||
return_dict[location] = {
|
|
||||||
"Joined": False
|
|
||||||
}
|
|
||||||
return_dict = OrderedDict(sorted(return_dict.items()))
|
|
||||||
return return_dict
|
|
||||||
|
|
||||||
def default_cluster(self):
|
|
||||||
defdict = {}
|
|
||||||
defdict[self.fhdhr.api.base] = {
|
|
||||||
"base_url": self.fhdhr.api.base,
|
|
||||||
"name": self.friendlyname
|
|
||||||
}
|
|
||||||
return defdict
|
|
||||||
|
|
||||||
def startup_sync(self):
|
|
||||||
self.fhdhr.logger.info("Syncronizing with Cluster.")
|
|
||||||
cluster = self.fhdhr.db.get_fhdhr_value("cluster", "dict") or self.default_cluster()
|
|
||||||
if not len(list(cluster.keys())):
|
|
||||||
self.fhdhr.logger.info("No Cluster Found.")
|
|
||||||
else:
|
|
||||||
self.fhdhr.logger.info("Found %s clustered services." % str(len(list(cluster.keys()))))
|
|
||||||
for location in list(cluster.keys()):
|
|
||||||
if location != self.fhdhr.api.base:
|
|
||||||
self.fhdhr.logger.info("Checking Cluster Syncronization information from %s." % location)
|
|
||||||
sync_url = location + "/api/cluster?method=get"
|
|
||||||
try:
|
|
||||||
sync_open = self.fhdhr.web.session.get(sync_url)
|
|
||||||
retrieved_cluster = sync_open.json()
|
|
||||||
if self.fhdhr.api.base not in list(retrieved_cluster.keys()):
|
|
||||||
return self.leave()
|
|
||||||
except self.fhdhr.web.exceptions.ConnectionError:
|
|
||||||
self.fhdhr.logger.error("Unreachable: " + location)
|
|
||||||
|
|
||||||
def leave(self):
|
|
||||||
self.fhdhr.logger.info("Leaving cluster.")
|
|
||||||
self.fhdhr.db.set_fhdhr_value("cluster", "dict", self.default_cluster())
|
|
||||||
|
|
||||||
def disconnect(self):
|
|
||||||
cluster = self.fhdhr.db.get_fhdhr_value("cluster", "dict") or self.default_cluster()
|
|
||||||
for location in list(cluster.keys()):
|
|
||||||
if location != self.fhdhr.api.base:
|
|
||||||
self.fhdhr.logger.info("Informing %s that I am departing the Cluster." % location)
|
|
||||||
sync_url = location + "/api/cluster?method=del&location=" + self.fhdhr.api.base
|
|
||||||
try:
|
|
||||||
self.fhdhr.web.session.get(sync_url)
|
|
||||||
except self.fhdhr.web.exceptions.ConnectionError:
|
|
||||||
self.fhdhr.logger.error("Unreachable: " + location)
|
|
||||||
self.leave()
|
|
||||||
|
|
||||||
def sync(self, location):
|
|
||||||
sync_url = location + "/api/cluster?method=get"
|
|
||||||
try:
|
|
||||||
sync_open = self.fhdhr.web.session.get(sync_url)
|
|
||||||
self.fhdhr.db.set_fhdhr_value("cluster", "dict", sync_open.json())
|
|
||||||
except self.fhdhr.web.exceptions.ConnectionError:
|
|
||||||
self.fhdhr.logger.error("Unreachable: " + location)
|
|
||||||
|
|
||||||
def push_sync(self):
|
|
||||||
cluster = self.fhdhr.db.get_fhdhr_value("cluster", "dict") or self.default_cluster()
|
|
||||||
for location in list(cluster.keys()):
|
|
||||||
if location != self.fhdhr.api.base:
|
|
||||||
sync_url = location + "/api/cluster?method=sync&location=" + self.fhdhr.api.base_quoted
|
|
||||||
try:
|
|
||||||
self.fhdhr.web.session.get(sync_url)
|
|
||||||
except self.fhdhr.web.exceptions.ConnectionError:
|
|
||||||
self.fhdhr.logger.error("Unreachable: " + location)
|
|
||||||
|
|
||||||
def add(self, location):
|
|
||||||
cluster = self.fhdhr.db.get_fhdhr_value("cluster", "dict") or self.default_cluster()
|
|
||||||
if location not in list(cluster.keys()):
|
|
||||||
self.fhdhr.logger.info("Adding %s to cluster." % location)
|
|
||||||
cluster[location] = {"base_url": location}
|
|
||||||
|
|
||||||
location_info_url = "%s/hdhr/discover.json" % location
|
|
||||||
try:
|
|
||||||
location_info_req = self.fhdhr.web.session.get(location_info_url)
|
|
||||||
except self.fhdhr.web.exceptions.ConnectionError:
|
|
||||||
self.fhdhr.logger.error("Unreachable: " + location)
|
|
||||||
del cluster[location]
|
|
||||||
self.fhdhr.db.set_fhdhr_value("cluster", "dict", cluster)
|
|
||||||
return
|
|
||||||
location_info = location_info_req.json()
|
|
||||||
cluster[location]["name"] = location_info["FriendlyName"]
|
|
||||||
|
|
||||||
cluster_info_url = location + "/api/cluster?method=get"
|
|
||||||
try:
|
|
||||||
cluster_info_req = self.fhdhr.web.session.get(cluster_info_url)
|
|
||||||
except self.fhdhr.web.exceptions.ConnectionError:
|
|
||||||
self.fhdhr.logger.error("Unreachable: " + location)
|
|
||||||
del cluster[location]
|
|
||||||
self.fhdhr.db.set_fhdhr_value("cluster", "dict", cluster)
|
|
||||||
return
|
|
||||||
cluster_info = cluster_info_req.json()
|
|
||||||
for cluster_key in list(cluster_info.keys()):
|
|
||||||
if cluster_key not in list(cluster.keys()):
|
|
||||||
cluster[cluster_key] = cluster_info[cluster_key]
|
|
||||||
|
|
||||||
self.fhdhr.db.set_fhdhr_value("cluster", "dict", cluster)
|
|
||||||
self.push_sync()
|
|
||||||
|
|
||||||
def remove(self, location):
|
|
||||||
cluster = self.fhdhr.db.get_fhdhr_value("cluster", "dict") or self.default_cluster()
|
|
||||||
if location in list(cluster.keys()):
|
|
||||||
self.fhdhr.logger.info("Removing %s from cluster." % location)
|
|
||||||
del cluster[location]
|
|
||||||
sync_url = location + "/api/cluster?method=leave"
|
|
||||||
try:
|
|
||||||
self.fhdhr.web.session.get(sync_url)
|
|
||||||
except self.fhdhr.web.exceptions.ConnectionError:
|
|
||||||
self.fhdhr.logger.error("Unreachable: " + location)
|
|
||||||
self.push_sync()
|
|
||||||
self.fhdhr.db.set_fhdhr_value("cluster", "dict", cluster)
|
|
||||||
@ -1,30 +1,27 @@
|
|||||||
import os
|
|
||||||
import time
|
import time
|
||||||
import datetime
|
import datetime
|
||||||
from collections import OrderedDict
|
import threading
|
||||||
|
|
||||||
|
from fHDHR.tools import channel_sort
|
||||||
|
|
||||||
from .blocks import blocksEPG
|
from .blocks import blocksEPG
|
||||||
|
|
||||||
|
|
||||||
class EPG():
|
class EPG():
|
||||||
|
|
||||||
def __init__(self, fhdhr, channels, originwrapper, alternative_epg):
|
def __init__(self, fhdhr, channels, origins):
|
||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
|
|
||||||
self.origin = originwrapper
|
self.origins = origins
|
||||||
self.channels = channels
|
self.channels = channels
|
||||||
self.alternative_epg = alternative_epg
|
|
||||||
|
|
||||||
self.epgdict = {}
|
self.epgdict = {}
|
||||||
|
|
||||||
self.epg_methods = self.fhdhr.config.dict["epg"]["method"]
|
self.epg_methods = self.fhdhr.config.dict["epg"]["method"] or []
|
||||||
self.valid_epg_methods = [x for x in self.fhdhr.config.dict["main"]["valid_epg_methods"] if x and x not in [None, "None"]]
|
self.valid_epg_methods = [x for x in list(self.fhdhr.config.dict["epg"]["valid_methods"].keys()) if x and x not in [None, "None"]]
|
||||||
|
|
||||||
self.blocks = blocksEPG(self.fhdhr, self.channels)
|
self.blocks = blocksEPG(self.fhdhr, self.channels, self.origins, None)
|
||||||
self.epg_handling = {
|
self.epg_handling = {}
|
||||||
"origin": self.origin,
|
|
||||||
"blocks": self.blocks,
|
|
||||||
}
|
|
||||||
self.epg_method_selfadd()
|
self.epg_method_selfadd()
|
||||||
|
|
||||||
self.def_method = self.fhdhr.config.dict["epg"]["def_method"]
|
self.def_method = self.fhdhr.config.dict["epg"]["def_method"]
|
||||||
@ -36,21 +33,21 @@ class EPG():
|
|||||||
if epg_method not in list(self.sleeptime.keys()):
|
if epg_method not in list(self.sleeptime.keys()):
|
||||||
self.sleeptime[epg_method] = self.fhdhr.config.dict["epg"]["update_frequency"]
|
self.sleeptime[epg_method] = self.fhdhr.config.dict["epg"]["update_frequency"]
|
||||||
|
|
||||||
self.epg_update_url = "%s/api/epg?method=update" % (self.fhdhr.api.base)
|
self.epg_update_url = "/api/epg?method=update"
|
||||||
|
|
||||||
|
self.fhdhr.threads["epg"] = threading.Thread(target=self.run)
|
||||||
|
|
||||||
def clear_epg_cache(self, method=None):
|
def clear_epg_cache(self, method=None):
|
||||||
|
|
||||||
if not method:
|
if not method:
|
||||||
|
if not self.def_method:
|
||||||
|
return
|
||||||
|
if method not in self.valid_epg_methods:
|
||||||
|
if not self.def_method:
|
||||||
|
return
|
||||||
method = self.def_method
|
method = self.def_method
|
||||||
if (method == self.fhdhr.config.dict["main"]["dictpopname"] or
|
|
||||||
method not in self.fhdhr.config.dict["main"]["valid_epg_methods"]):
|
|
||||||
method = "origin"
|
|
||||||
|
|
||||||
epgtypename = method
|
self.fhdhr.logger.info("Clearing %s EPG cache." % method)
|
||||||
if method in [self.fhdhr.config.dict["main"]["dictpopname"], "origin"]:
|
|
||||||
epgtypename = self.fhdhr.config.dict["main"]["dictpopname"]
|
|
||||||
|
|
||||||
self.fhdhr.logger.info("Clearing " + epgtypename + " EPG cache.")
|
|
||||||
|
|
||||||
if hasattr(self.epg_handling[method], 'clear_cache'):
|
if hasattr(self.epg_handling[method], 'clear_cache'):
|
||||||
self.epg_handling[method].clear_cache()
|
self.epg_handling[method].clear_cache()
|
||||||
@ -60,54 +57,84 @@ class EPG():
|
|||||||
|
|
||||||
self.fhdhr.db.delete_fhdhr_value("epg_dict", method)
|
self.fhdhr.db.delete_fhdhr_value("epg_dict", method)
|
||||||
|
|
||||||
def whats_on_now(self, channel, method=None):
|
def whats_on_now(self, channel_number, method=None, chan_obj=None, chan_dict=None):
|
||||||
|
nowtime = time.time()
|
||||||
epgdict = self.get_epg(method)
|
epgdict = self.get_epg(method)
|
||||||
listings = epgdict[channel]["listing"]
|
if channel_number not in list(epgdict.keys()):
|
||||||
for listing in listings:
|
epgdict[channel_number] = {
|
||||||
nowtime = datetime.datetime.utcnow()
|
"callsign": "",
|
||||||
start_time = datetime.datetime.strptime(listing["time_start"], '%Y%m%d%H%M%S +0000')
|
"name": "",
|
||||||
end_time = datetime.datetime.strptime(listing["time_end"], '%Y%m%d%H%M%S +0000')
|
"number": str(channel_number),
|
||||||
if start_time <= nowtime <= end_time:
|
"id": "",
|
||||||
epgitem = epgdict[channel].copy()
|
"thumbnail": "",
|
||||||
|
"listing": []
|
||||||
|
}
|
||||||
|
|
||||||
|
for listing in epgdict[channel_number]["listing"]:
|
||||||
|
for time_item in ["time_start", "time_end"]:
|
||||||
|
time_value = listing[time_item]
|
||||||
|
if str(time_value).endswith("+00:00"):
|
||||||
|
listing[time_item] = datetime.datetime.strptime(time_value, '%Y%m%d%H%M%S +00:00').timestamp()
|
||||||
|
elif str(time_value).endswith("+0000"):
|
||||||
|
listing[time_item] = datetime.datetime.strptime(time_value, '%Y%m%d%H%M%S +0000').timestamp()
|
||||||
|
else:
|
||||||
|
listing[time_item] = int(time_value)
|
||||||
|
if int(listing["time_start"]) <= nowtime <= int(listing["time_end"]):
|
||||||
|
epgitem = epgdict[channel_number].copy()
|
||||||
epgitem["listing"] = [listing]
|
epgitem["listing"] = [listing]
|
||||||
return epgitem
|
return epgitem
|
||||||
return None
|
epgitem = epgdict[channel_number].copy()
|
||||||
|
epgitem["listing"] = [self.blocks.empty_listing(chan_obj=None, chan_dict=None)]
|
||||||
|
return epgitem
|
||||||
|
|
||||||
def whats_on_allchans(self, method=None):
|
def whats_on_allchans(self, method=None):
|
||||||
|
|
||||||
if not method:
|
if not method:
|
||||||
|
if not self.def_method:
|
||||||
|
return
|
||||||
|
method = self.def_method
|
||||||
|
if method not in self.valid_epg_methods:
|
||||||
|
if not self.def_method:
|
||||||
|
return
|
||||||
method = self.def_method
|
method = self.def_method
|
||||||
if (method == self.fhdhr.config.dict["main"]["dictpopname"] or
|
|
||||||
method not in self.fhdhr.config.dict["main"]["valid_epg_methods"]):
|
|
||||||
method = "origin"
|
|
||||||
|
|
||||||
channel_guide_list = []
|
channel_guide_dict = {}
|
||||||
epgdict = self.get_epg(method)
|
epgdict = self.get_epg(method)
|
||||||
channels = list(epgdict.keys())
|
epgdict = epgdict.copy()
|
||||||
for channel in channels:
|
for c in list(epgdict.keys()):
|
||||||
whatson = self.whats_on_now(epgdict[channel]["number"], method)
|
if method in [origin for origin in list(self.origins.origins_dict.keys())]:
|
||||||
|
chan_obj = self.channels.get_channel_obj("origin_id", epgdict[c]["id"])
|
||||||
|
channel_number = chan_obj.number
|
||||||
|
epgdict[channel_number] = epgdict.pop(c)
|
||||||
|
epgdict[channel_number]["name"] = chan_obj.dict["name"]
|
||||||
|
epgdict[channel_number]["callsign"] = chan_obj.dict["callsign"]
|
||||||
|
epgdict[channel_number]["number"] = chan_obj.number
|
||||||
|
epgdict[channel_number]["id"] = chan_obj.dict["origin_id"]
|
||||||
|
epgdict[channel_number]["thumbnail"] = chan_obj.thumbnail
|
||||||
|
else:
|
||||||
|
chan_obj = None
|
||||||
|
channel_number = c
|
||||||
|
whatson = self.whats_on_now(channel_number, method, chan_dict=epgdict, chan_obj=chan_obj)
|
||||||
if whatson:
|
if whatson:
|
||||||
channel_guide_list.append(whatson)
|
channel_guide_dict[channel_number] = whatson
|
||||||
return channel_guide_list
|
return channel_guide_dict
|
||||||
|
|
||||||
def get_epg(self, method=None):
|
def get_epg(self, method=None):
|
||||||
|
|
||||||
if not method:
|
if not method:
|
||||||
|
if not self.def_method:
|
||||||
|
return
|
||||||
|
method = self.def_method
|
||||||
|
if method not in self.valid_epg_methods:
|
||||||
|
if not self.def_method:
|
||||||
|
return
|
||||||
method = self.def_method
|
method = self.def_method
|
||||||
if (method == self.fhdhr.config.dict["main"]["dictpopname"] or
|
|
||||||
method not in self.fhdhr.config.dict["main"]["valid_epg_methods"]):
|
|
||||||
method = "origin"
|
|
||||||
|
|
||||||
if method not in list(self.epgdict.keys()):
|
if method in list(self.epgdict.keys()):
|
||||||
|
return self.epgdict[method]
|
||||||
|
|
||||||
epgdict = self.fhdhr.db.get_fhdhr_value("epg_dict", method) or None
|
|
||||||
if not epgdict:
|
|
||||||
self.update(method)
|
self.update(method)
|
||||||
self.epgdict[method] = self.fhdhr.db.get_fhdhr_value("epg_dict", method) or {}
|
self.epgdict[method] = self.fhdhr.db.get_fhdhr_value("epg_dict", method) or {}
|
||||||
else:
|
|
||||||
self.epgdict[method] = epgdict
|
|
||||||
return self.epgdict[method]
|
|
||||||
else:
|
|
||||||
return self.epgdict[method]
|
return self.epgdict[method]
|
||||||
|
|
||||||
def get_thumbnail(self, itemtype, itemid):
|
def get_thumbnail(self, itemtype, itemid):
|
||||||
@ -121,71 +148,171 @@ class EPG():
|
|||||||
|
|
||||||
def find_channel_dict(self, channel_id):
|
def find_channel_dict(self, channel_id):
|
||||||
epgdict = self.get_epg()
|
epgdict = self.get_epg()
|
||||||
channel_list = []
|
channel_list = [epgdict[x] for x in list(epgdict.keys())]
|
||||||
for channel in list(epgdict.keys()):
|
return next(item for item in channel_list if item["id"] == channel_id) or None
|
||||||
channel_list.append(epgdict[channel])
|
|
||||||
return next(item for item in channel_list if item["id"] == channel_id)
|
|
||||||
|
|
||||||
def find_program_dict(self, event_id):
|
def find_program_dict(self, event_id):
|
||||||
epgdict = self.get_epg()
|
epgdict = self.get_epg()
|
||||||
event_list = []
|
event_list = []
|
||||||
for channel in list(epgdict.keys()):
|
for channel in list(epgdict.keys()):
|
||||||
event_list.extend(epgdict[channel]["listing"])
|
event_list.extend(epgdict[channel]["listing"])
|
||||||
return next(item for item in event_list if item["id"] == event_id)
|
return next(item for item in event_list if item["id"] == event_id) or None
|
||||||
|
|
||||||
def epg_method_selfadd(self):
|
def epg_method_selfadd(self):
|
||||||
self.fhdhr.logger.info("Checking for Alternative EPG methods.")
|
for plugin_name in list(self.fhdhr.plugins.plugins.keys()):
|
||||||
new_epgtype_list = []
|
if self.fhdhr.plugins.plugins[plugin_name].type == "alt_epg":
|
||||||
for entry in os.scandir(self.fhdhr.config.internal["paths"]["alternative_epg"]):
|
method = self.fhdhr.plugins.plugins[plugin_name].name.lower()
|
||||||
if entry.is_file():
|
self.epg_handling[method] = self.fhdhr.plugins.plugins[plugin_name].Plugin_OBJ(self.channels, self.fhdhr.plugins.plugins[plugin_name].plugin_utils)
|
||||||
if entry.name[0] != '_' and entry.name.endswith(".py"):
|
for origin in list(self.origins.origins_dict.keys()):
|
||||||
new_epgtype_list.append(str(entry.name[:-3]))
|
if origin.lower() not in list(self.epg_handling.keys()):
|
||||||
for method in new_epgtype_list:
|
self.epg_handling[origin.lower()] = blocksEPG(self.fhdhr, self.channels, self.origins, origin)
|
||||||
self.fhdhr.logger.info("Found %s EPG method." % method)
|
self.fhdhr.config.register_valid_epg_method(origin, "Blocks")
|
||||||
self.epg_handling[method] = eval("self.alternative_epg.%s.%sEPG(self.fhdhr, self.channels)" % (method, method))
|
self.valid_epg_methods.append(origin.lower())
|
||||||
|
|
||||||
def update(self, method=None):
|
def update(self, method=None):
|
||||||
|
|
||||||
if (not method or
|
if not method:
|
||||||
method not in self.fhdhr.config.dict["main"]["valid_epg_methods"]):
|
if not self.def_method:
|
||||||
|
return
|
||||||
|
method = self.def_method
|
||||||
|
if method not in self.valid_epg_methods:
|
||||||
|
if not self.def_method:
|
||||||
|
return
|
||||||
method = self.def_method
|
method = self.def_method
|
||||||
|
|
||||||
if method == self.fhdhr.config.dict["main"]["dictpopname"]:
|
self.fhdhr.logger.info("Updating %s EPG cache." % method)
|
||||||
method = "origin"
|
|
||||||
|
|
||||||
epgtypename = method
|
|
||||||
if method in [self.fhdhr.config.dict["main"]["dictpopname"], "origin"]:
|
|
||||||
epgtypename = self.fhdhr.config.dict["main"]["dictpopname"]
|
|
||||||
|
|
||||||
self.fhdhr.logger.info("Updating " + epgtypename + " EPG cache.")
|
|
||||||
if method == 'origin':
|
|
||||||
programguide = self.epg_handling['origin'].update_epg(self.channels)
|
|
||||||
else:
|
|
||||||
programguide = self.epg_handling[method].update_epg()
|
programguide = self.epg_handling[method].update_epg()
|
||||||
|
|
||||||
for chan in list(programguide.keys()):
|
# sort the channel listings by time stamp
|
||||||
floatnum = str(float(chan))
|
for cnum in list(programguide.keys()):
|
||||||
programguide[floatnum] = programguide.pop(chan)
|
|
||||||
programguide[floatnum]["number"] = floatnum
|
|
||||||
|
|
||||||
programguide = OrderedDict(sorted(programguide.items()))
|
|
||||||
|
|
||||||
for cnum in programguide:
|
|
||||||
programguide[cnum]["listing"] = sorted(programguide[cnum]["listing"], key=lambda i: i['time_start'])
|
programguide[cnum]["listing"] = sorted(programguide[cnum]["listing"], key=lambda i: i['time_start'])
|
||||||
|
|
||||||
self.epgdict = programguide
|
# Gernate Block periods for between EPG data, if missing
|
||||||
|
clean_prog_guide = {}
|
||||||
|
desired_start_time = (datetime.datetime.today() + datetime.timedelta(days=self.fhdhr.config.dict["epg"]["reverse_days"])).timestamp()
|
||||||
|
desired_end_time = (datetime.datetime.today() + datetime.timedelta(days=self.fhdhr.config.dict["epg"]["forward_days"])).timestamp()
|
||||||
|
for cnum in list(programguide.keys()):
|
||||||
|
|
||||||
|
if cnum not in list(clean_prog_guide.keys()):
|
||||||
|
clean_prog_guide[cnum] = programguide[cnum].copy()
|
||||||
|
clean_prog_guide[cnum]["listing"] = []
|
||||||
|
|
||||||
|
if method in [origin for origin in list(self.origins.origins_dict.keys())]:
|
||||||
|
chan_obj = self.channels.get_channel_obj("origin_id", programguide[cnum]["id"])
|
||||||
|
else:
|
||||||
|
chan_obj = None
|
||||||
|
|
||||||
|
# Generate Blocks for Channels containing No Lisiings
|
||||||
|
if not len(programguide[cnum]["listing"]):
|
||||||
|
timestamps = self.blocks.timestamps_between(desired_start_time, desired_end_time)
|
||||||
|
clean_prog_dicts = self.blocks.empty_channel_epg(timestamps, chan_dict=programguide[cnum], chan_obj=chan_obj)
|
||||||
|
clean_prog_guide[cnum]["listing"].extend(clean_prog_dicts)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
# Clean Timetamps from old xmltv method to timestamps
|
||||||
|
progindex = 0
|
||||||
|
for program_item in programguide[cnum]["listing"]:
|
||||||
|
for time_item in ["time_start", "time_end"]:
|
||||||
|
time_value = programguide[cnum]["listing"][progindex][time_item]
|
||||||
|
if str(time_value).endswith("+00:00"):
|
||||||
|
programguide[cnum]["listing"][progindex][time_item] = datetime.datetime.strptime(time_value, '%Y%m%d%H%M%S +00:00').timestamp()
|
||||||
|
elif str(time_value).endswith("+0000"):
|
||||||
|
programguide[cnum]["listing"][progindex][time_item] = datetime.datetime.strptime(time_value, '%Y%m%d%H%M%S +0000').timestamp()
|
||||||
|
else:
|
||||||
|
programguide[cnum]["listing"][progindex][time_item] = int(time_value)
|
||||||
|
progindex += 1
|
||||||
|
|
||||||
|
# Generate time before the listing actually starts
|
||||||
|
first_prog_time = programguide[cnum]["listing"][0]['time_start']
|
||||||
|
if desired_start_time < first_prog_time:
|
||||||
|
timestamps = self.blocks.timestamps_between(desired_start_time, first_prog_time)
|
||||||
|
clean_prog_dicts = self.blocks.empty_channel_epg(timestamps, chan_dict=programguide[cnum], chan_obj=chan_obj)
|
||||||
|
clean_prog_guide[cnum]["listing"].extend(clean_prog_dicts)
|
||||||
|
|
||||||
|
# Generate time blocks between events if chunks of time are missing
|
||||||
|
progindex = 0
|
||||||
|
for program_item in programguide[cnum]["listing"]:
|
||||||
|
try:
|
||||||
|
nextprog_dict = programguide[cnum]["listing"][progindex + 1]
|
||||||
|
except IndexError:
|
||||||
|
nextprog_dict = None
|
||||||
|
if not nextprog_dict:
|
||||||
|
clean_prog_guide[cnum]["listing"].append(program_item)
|
||||||
|
else:
|
||||||
|
if nextprog_dict['time_start'] > program_item['time_end']:
|
||||||
|
timestamps = self.blocks.timestamps_between(program_item['time_end'], nextprog_dict['time_start'])
|
||||||
|
clean_prog_dicts = self.blocks.empty_channel_epg(timestamps, chan_dict=programguide[cnum], chan_obj=chan_obj)
|
||||||
|
clean_prog_guide[cnum]["listing"].extend(clean_prog_dicts)
|
||||||
|
else:
|
||||||
|
clean_prog_guide[cnum]["listing"].append(program_item)
|
||||||
|
progindex += 1
|
||||||
|
|
||||||
|
# Generate time after the listing actually ends
|
||||||
|
end_prog_time = programguide[cnum]["listing"][progindex]['time_end']
|
||||||
|
if desired_end_time > end_prog_time:
|
||||||
|
timestamps = self.blocks.timestamps_between(end_prog_time, desired_end_time)
|
||||||
|
clean_prog_dicts = self.blocks.empty_channel_epg(timestamps, chan_dict=programguide[cnum], chan_obj=chan_obj)
|
||||||
|
clean_prog_guide[cnum]["listing"].extend(clean_prog_dicts)
|
||||||
|
|
||||||
|
programguide = clean_prog_guide.copy()
|
||||||
|
|
||||||
|
# if a stock method, generate Blocks EPG for missing channels
|
||||||
|
if method in [origin for origin in list(self.origins.origins_dict.keys())]:
|
||||||
|
timestamps = self.blocks.timestamps
|
||||||
|
for fhdhr_id in [x["id"] for x in self.channels.get_channels(method)]:
|
||||||
|
chan_obj = self.channels.get_channel_obj("id", fhdhr_id, method)
|
||||||
|
if str(chan_obj.number) not in list(programguide.keys()):
|
||||||
|
programguide[str(chan_obj.number)] = chan_obj.epgdict
|
||||||
|
clean_prog_dicts = self.blocks.empty_channel_epg(timestamps, chan_obj=chan_obj)
|
||||||
|
programguide[str(chan_obj.number)]["listing"].extend(clean_prog_dicts)
|
||||||
|
|
||||||
|
# Make Thumbnails for missing thumbnails
|
||||||
|
for cnum in list(programguide.keys()):
|
||||||
|
if not programguide[cnum]["thumbnail"]:
|
||||||
|
programguide[cnum]["thumbnail"] = "/api/images?method=generate&type=channel&message=%s" % programguide[cnum]["number"]
|
||||||
|
programguide[cnum]["listing"] = sorted(programguide[cnum]["listing"], key=lambda i: i['time_start'])
|
||||||
|
prog_index = 0
|
||||||
|
for program_item in programguide[cnum]["listing"]:
|
||||||
|
if not programguide[cnum]["listing"][prog_index]["thumbnail"]:
|
||||||
|
programguide[cnum]["listing"][prog_index]["thumbnail"] = programguide[cnum]["thumbnail"]
|
||||||
|
prog_index += 1
|
||||||
|
|
||||||
|
# Get Totals
|
||||||
|
total_channels = len(list(programguide.keys()))
|
||||||
|
total_programs = 0
|
||||||
|
|
||||||
|
# Sort the channels
|
||||||
|
sorted_channel_list = channel_sort(list(programguide.keys()))
|
||||||
|
sorted_chan_guide = {}
|
||||||
|
for channel in sorted_channel_list:
|
||||||
|
total_programs += len(programguide[cnum]["listing"])
|
||||||
|
sorted_chan_guide[channel] = programguide[channel]
|
||||||
|
|
||||||
|
self.epgdict[method] = sorted_chan_guide
|
||||||
self.fhdhr.db.set_fhdhr_value("epg_dict", method, programguide)
|
self.fhdhr.db.set_fhdhr_value("epg_dict", method, programguide)
|
||||||
self.fhdhr.db.set_fhdhr_value("update_time", method, time.time())
|
self.fhdhr.db.set_fhdhr_value("update_time", method, time.time())
|
||||||
self.fhdhr.logger.info("Wrote " + epgtypename + " EPG cache.")
|
self.fhdhr.logger.info("Wrote %s EPG cache. %s Programs for %s Channels" % (method, total_programs, total_channels))
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.fhdhr.logger.info("EPG Update Thread Starting")
|
||||||
|
self.fhdhr.threads["epg"].start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.fhdhr.logger.info("EPG Update Thread Stopping")
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
for epg_method in self.epg_methods:
|
time.sleep(1800)
|
||||||
self.fhdhr.web.session.get(self.epg_update_url)
|
|
||||||
try:
|
|
||||||
while True:
|
while True:
|
||||||
for epg_method in self.epg_methods:
|
for epg_method in self.epg_methods:
|
||||||
if time.time() >= (self.fhdhr.db.get_fhdhr_value("update_time", epg_method) + self.sleeptime[epg_method]):
|
last_update_time = self.fhdhr.db.get_fhdhr_value("update_time", epg_method)
|
||||||
self.fhdhr.web.session.get(self.epg_update_url)
|
updatetheepg = False
|
||||||
time.sleep(360)
|
if not last_update_time:
|
||||||
except KeyboardInterrupt:
|
updatetheepg = True
|
||||||
pass
|
elif time.time() >= (last_update_time + self.sleeptime[epg_method]):
|
||||||
|
updatetheepg = True
|
||||||
|
if updatetheepg:
|
||||||
|
self.fhdhr.api.get("%s&source=%s" % (self.epg_update_url, epg_method))
|
||||||
|
time.sleep(1800)
|
||||||
|
|
||||||
|
self.stop()
|
||||||
|
|||||||
@ -3,61 +3,65 @@ import datetime
|
|||||||
|
|
||||||
class blocksEPG():
|
class blocksEPG():
|
||||||
|
|
||||||
def __init__(self, fhdhr, channels):
|
def __init__(self, fhdhr, channels, origins, origin):
|
||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
|
|
||||||
self.channels = channels
|
self.channels = channels
|
||||||
|
self.origins = origins
|
||||||
|
self.origin = origin
|
||||||
|
|
||||||
def update_epg(self):
|
def update_epg(self):
|
||||||
programguide = {}
|
programguide = {}
|
||||||
|
|
||||||
timestamps = self.timestamps
|
timestamps = self.timestamps
|
||||||
|
|
||||||
for fhdhr_id in list(self.channels.list.keys()):
|
for fhdhr_id in [x["id"] for x in self.channels.get_channels(self.origin)]:
|
||||||
chan_obj = self.channels.list[fhdhr_id]
|
chan_obj = self.channels.get_channel_obj("id", fhdhr_id, self.origin)
|
||||||
|
|
||||||
if str(chan_obj.dict["number"]) not in list(programguide.keys()):
|
if str(chan_obj.number) not in list(programguide.keys()):
|
||||||
programguide[str(chan_obj.dict["number"])] = chan_obj.epgdict
|
programguide[str(chan_obj.number)] = chan_obj.epgdict
|
||||||
|
|
||||||
clean_prog_dicts = self.empty_channel_epg(timestamps, chan_obj)
|
clean_prog_dicts = self.empty_channel_epg(timestamps, chan_obj=chan_obj)
|
||||||
for clean_prog_dict in clean_prog_dicts:
|
for clean_prog_dict in clean_prog_dicts:
|
||||||
programguide[str(chan_obj.dict["number"])]["listing"].append(clean_prog_dict)
|
programguide[str(chan_obj.number)]["listing"].append(clean_prog_dict)
|
||||||
|
|
||||||
return programguide
|
return programguide
|
||||||
|
|
||||||
def get_content_thumbnail(self, content_id):
|
|
||||||
return "/api/images?method=generate&type=content&message=%s" % content_id
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def timestamps(self):
|
def timestamps(self):
|
||||||
timestamps = []
|
desired_start_time = (datetime.datetime.today() + datetime.timedelta(days=self.fhdhr.config.dict["epg"]["reverse_days"])).timestamp()
|
||||||
todaydate = datetime.date.today()
|
desired_end_time = (datetime.datetime.today() + datetime.timedelta(days=self.fhdhr.config.dict["epg"]["forward_days"])).timestamp()
|
||||||
for x in range(0, 6):
|
return self.timestamps_between(desired_start_time, desired_end_time)
|
||||||
xdate = todaydate + datetime.timedelta(days=x)
|
|
||||||
xtdate = xdate + datetime.timedelta(days=1)
|
|
||||||
|
|
||||||
for hour in range(0, 24):
|
def timestamps_between(self, starttime, endtime):
|
||||||
time_start = datetime.datetime.combine(xdate, datetime.time(hour, 0))
|
timestamps = []
|
||||||
if hour + 1 < 24:
|
desired_blocksize = self.fhdhr.config.dict["epg"]["block_size"]
|
||||||
time_end = datetime.datetime.combine(xdate, datetime.time(hour + 1, 0))
|
current_time = starttime
|
||||||
else:
|
while (current_time + desired_blocksize) <= endtime:
|
||||||
time_end = datetime.datetime.combine(xtdate, datetime.time(0, 0))
|
|
||||||
timestampdict = {
|
timestampdict = {
|
||||||
"time_start": str(time_start.strftime('%Y%m%d%H%M%S')) + " +0000",
|
"time_start": current_time,
|
||||||
"time_end": str(time_end.strftime('%Y%m%d%H%M%S')) + " +0000",
|
"time_end": current_time + desired_blocksize,
|
||||||
|
}
|
||||||
|
timestamps.append(timestampdict)
|
||||||
|
current_time += desired_blocksize
|
||||||
|
if current_time < endtime:
|
||||||
|
timestampdict = {
|
||||||
|
"time_start": current_time,
|
||||||
|
"time_end": endtime
|
||||||
}
|
}
|
||||||
timestamps.append(timestampdict)
|
timestamps.append(timestampdict)
|
||||||
return timestamps
|
return timestamps
|
||||||
|
|
||||||
def empty_channel_epg(self, timestamps, chan_obj):
|
def single_channel_epg(self, timestampdict, chan_obj=None, chan_dict=None):
|
||||||
clean_prog_dicts = []
|
|
||||||
for timestamp in timestamps:
|
if chan_obj:
|
||||||
content_id = "%s_%s" % (chan_obj.dict["origin_id"], str(timestamp['time_start']).split(" ")[0])
|
content_id = "%s_%s" % (chan_obj.dict["origin_id"], timestampdict['time_start'])
|
||||||
|
elif chan_dict:
|
||||||
|
content_id = "%s_%s" % (chan_dict["id"], timestampdict['time_start'])
|
||||||
|
|
||||||
clean_prog_dict = {
|
clean_prog_dict = {
|
||||||
"time_start": timestamp['time_start'],
|
"time_start": timestampdict['time_start'],
|
||||||
"time_end": timestamp['time_end'],
|
"time_end": timestampdict['time_end'],
|
||||||
"duration_minutes": 60,
|
"duration_minutes": (timestampdict['time_end'] - timestampdict['time_start']) / 60,
|
||||||
"thumbnail": chan_obj.dict["thumbnail"] or self.get_content_thumbnail(content_id),
|
|
||||||
"title": "Unavailable",
|
"title": "Unavailable",
|
||||||
"sub-title": "Unavailable",
|
"sub-title": "Unavailable",
|
||||||
"description": "Unavailable",
|
"description": "Unavailable",
|
||||||
@ -70,5 +74,47 @@ class blocksEPG():
|
|||||||
"isnew": False,
|
"isnew": False,
|
||||||
"id": content_id,
|
"id": content_id,
|
||||||
}
|
}
|
||||||
|
if chan_obj:
|
||||||
|
clean_prog_dict["thumbnail"] = chan_obj.thumbnail
|
||||||
|
elif chan_dict:
|
||||||
|
clean_prog_dict["thumbnail"] = chan_dict["thumbnail"]
|
||||||
|
if not clean_prog_dict["thumbnail"]:
|
||||||
|
clean_prog_dict["thumbnail"] = "/api/images?method=generate&type=content&message=%s" % content_id
|
||||||
|
|
||||||
|
return clean_prog_dict
|
||||||
|
|
||||||
|
def empty_channel_epg(self, timestamps, chan_obj=None, chan_dict=None):
|
||||||
|
clean_prog_dicts = []
|
||||||
|
for timestampdict in timestamps:
|
||||||
|
clean_prog_dict = self.single_channel_epg(timestampdict, chan_obj=chan_obj, chan_dict=chan_dict)
|
||||||
clean_prog_dicts.append(clean_prog_dict)
|
clean_prog_dicts.append(clean_prog_dict)
|
||||||
return clean_prog_dicts
|
return clean_prog_dicts
|
||||||
|
|
||||||
|
def empty_listing(self, chan_obj=None, chan_dict=None):
|
||||||
|
clean_prog_dict = {
|
||||||
|
"time_start": None,
|
||||||
|
"time_end": None,
|
||||||
|
"duration_minutes": None,
|
||||||
|
"title": "Unavailable",
|
||||||
|
"sub-title": "Unavailable",
|
||||||
|
"description": "Unavailable",
|
||||||
|
"rating": "N/A",
|
||||||
|
"episodetitle": None,
|
||||||
|
"releaseyear": None,
|
||||||
|
"genres": [],
|
||||||
|
"seasonnumber": None,
|
||||||
|
"episodenumber": None,
|
||||||
|
"isnew": False,
|
||||||
|
"id": "Unavailable",
|
||||||
|
}
|
||||||
|
|
||||||
|
if chan_obj:
|
||||||
|
clean_prog_dict["thumbnail"] = chan_obj.thumbnail
|
||||||
|
elif chan_dict:
|
||||||
|
clean_prog_dict["thumbnail"] = chan_dict["thumbnail"]
|
||||||
|
else:
|
||||||
|
clean_prog_dict["thumbnail"] = None
|
||||||
|
if not clean_prog_dict["thumbnail"]:
|
||||||
|
clean_prog_dict["thumbnail"] = "/api/images?method=generate&type=content&message=Unavailable"
|
||||||
|
|
||||||
|
return clean_prog_dict
|
||||||
|
|||||||
@ -2,10 +2,7 @@
|
|||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
import time
|
import time
|
||||||
|
import threading
|
||||||
from .ssdp_detect import fHDHR_Detect
|
|
||||||
from .rmg_ssdp import RMG_SSDP
|
|
||||||
from .hdhr_ssdp import HDHR_SSDP
|
|
||||||
|
|
||||||
|
|
||||||
class SSDPServer():
|
class SSDPServer():
|
||||||
@ -13,10 +10,14 @@ class SSDPServer():
|
|||||||
def __init__(self, fhdhr):
|
def __init__(self, fhdhr):
|
||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
|
|
||||||
self.detect_method = fHDHR_Detect(fhdhr)
|
self.ssdp_handling = {}
|
||||||
|
self.methods = [x for x in list(self.fhdhr.plugins.plugins.keys()) if self.fhdhr.plugins.plugins[x].type == "ssdp"]
|
||||||
|
|
||||||
if (self.fhdhr.config.dict["fhdhr"]["discovery_address"] and
|
if (self.fhdhr.config.dict["fhdhr"]["discovery_address"] and
|
||||||
self.fhdhr.config.dict["ssdp"]["enabled"]):
|
self.fhdhr.config.dict["ssdp"]["enabled"] and
|
||||||
|
len(self.methods)):
|
||||||
|
|
||||||
|
self.fhdhr.threads["ssdp"] = threading.Thread(target=self.run)
|
||||||
self.setup_ssdp()
|
self.setup_ssdp()
|
||||||
|
|
||||||
self.sock.bind((self.bind_address, 1900))
|
self.sock.bind((self.bind_address, 1900))
|
||||||
@ -26,12 +27,33 @@ class SSDPServer():
|
|||||||
self.max_age = int(fhdhr.config.dict["ssdp"]["max_age"])
|
self.max_age = int(fhdhr.config.dict["ssdp"]["max_age"])
|
||||||
self.age_time = None
|
self.age_time = None
|
||||||
|
|
||||||
self.rmg_ssdp = RMG_SSDP(fhdhr, self.broadcast_ip, self.max_age)
|
self.ssdp_method_selfadd()
|
||||||
self.hdhr_ssdp = HDHR_SSDP(fhdhr, self.broadcast_ip, self.max_age)
|
|
||||||
|
|
||||||
self.do_alive()
|
self.do_alive()
|
||||||
self.m_search()
|
self.m_search()
|
||||||
|
|
||||||
|
def ssdp_method_selfadd(self):
|
||||||
|
for plugin_name in list(self.fhdhr.plugins.plugins.keys()):
|
||||||
|
if self.fhdhr.plugins.plugins[plugin_name].type == "ssdp":
|
||||||
|
method = self.fhdhr.plugins.plugins[plugin_name].name.lower()
|
||||||
|
plugin_utils = self.fhdhr.plugins.plugins[plugin_name].plugin_utils
|
||||||
|
self.ssdp_handling[method] = self.fhdhr.plugins.plugins[plugin_name].Plugin_OBJ(self.fhdhr, plugin_utils, self.broadcast_ip, self.max_age)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.fhdhr.logger.info("SSDP Server Starting")
|
||||||
|
self.fhdhr.threads["ssdp"].start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.fhdhr.logger.info("SSDP Server Stopping")
|
||||||
|
self.sock.close()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while True:
|
||||||
|
data, address = self.sock.recvfrom(1024)
|
||||||
|
self.on_recv(data, address)
|
||||||
|
self.do_alive()
|
||||||
|
self.stop()
|
||||||
|
|
||||||
def do_alive(self, forcealive=False):
|
def do_alive(self, forcealive=False):
|
||||||
|
|
||||||
send_alive = False
|
send_alive = False
|
||||||
@ -44,21 +66,22 @@ class SSDPServer():
|
|||||||
|
|
||||||
if send_alive:
|
if send_alive:
|
||||||
self.fhdhr.logger.info("Sending Alive message to network.")
|
self.fhdhr.logger.info("Sending Alive message to network.")
|
||||||
self.do_notify(self.broadcase_address_tuple)
|
self.do_notify(self.broadcast_address_tuple)
|
||||||
self.age_time = time.time()
|
self.age_time = time.time()
|
||||||
|
|
||||||
def do_notify(self, address):
|
def do_notify(self, address):
|
||||||
|
|
||||||
notify_list = []
|
notify_list = []
|
||||||
|
for ssdp_handler in list(self.ssdp_handling.keys()):
|
||||||
hdhr_notify = self.hdhr_ssdp.get()
|
if self.ssdp_handling[ssdp_handler].enabled and hasattr(self.ssdp_handling[ssdp_handler], 'notify'):
|
||||||
notify_list.append(hdhr_notify)
|
notify_data = self.ssdp_handling[ssdp_handler].notify
|
||||||
|
if isinstance(notify_data, list):
|
||||||
if self.fhdhr.config.dict["rmg"]["enabled"]:
|
notify_list.extend(notify_data)
|
||||||
rmg_notify = self.rmg_ssdp.get()
|
else:
|
||||||
notify_list.append(rmg_notify)
|
notify_list.append(notify_data)
|
||||||
|
|
||||||
for notifydata in notify_list:
|
for notifydata in notify_list:
|
||||||
|
notifydata = notifydata.encode("utf-8")
|
||||||
|
|
||||||
self.fhdhr.logger.debug("Created {}".format(notifydata))
|
self.fhdhr.logger.debug("Created {}".format(notifydata))
|
||||||
try:
|
try:
|
||||||
@ -85,6 +108,10 @@ class SSDPServer():
|
|||||||
headers = [x.split(':', 1) for x in lines]
|
headers = [x.split(':', 1) for x in lines]
|
||||||
headers = dict(map(lambda x: (x[0].lower(), x[1]), headers))
|
headers = dict(map(lambda x: (x[0].lower(), x[1]), headers))
|
||||||
|
|
||||||
|
for ssdp_handler in list(self.ssdp_handling.keys()):
|
||||||
|
if self.ssdp_handling[ssdp_handler].enabled and hasattr(self.ssdp_handling[ssdp_handler], 'on_recv'):
|
||||||
|
self.ssdp_handling[ssdp_handler].on_recv(headers, cmd, list(self.ssdp_handling.keys()))
|
||||||
|
|
||||||
if cmd[0] == 'M-SEARCH' and cmd[1] == '*':
|
if cmd[0] == 'M-SEARCH' and cmd[1] == '*':
|
||||||
# SSDP discovery
|
# SSDP discovery
|
||||||
self.fhdhr.logger.debug("Received qualifying M-SEARCH from {}".format(address))
|
self.fhdhr.logger.debug("Received qualifying M-SEARCH from {}".format(address))
|
||||||
@ -92,26 +119,14 @@ class SSDPServer():
|
|||||||
|
|
||||||
self.do_notify(address)
|
self.do_notify(address)
|
||||||
|
|
||||||
elif cmd[0] == 'NOTIFY' and cmd[1] == '*':
|
if cmd[0] == 'NOTIFY' and cmd[1] == '*':
|
||||||
# SSDP presence
|
|
||||||
self.fhdhr.logger.debug("NOTIFY data: {}".format(headers))
|
self.fhdhr.logger.debug("NOTIFY data: {}".format(headers))
|
||||||
try:
|
|
||||||
if headers["server"].startswith("fHDHR"):
|
|
||||||
savelocation = headers["location"].split("/device.xml")[0]
|
|
||||||
if savelocation.endswith("/hdhr"):
|
|
||||||
savelocation = savelocation.replace("/hdhr", '')
|
|
||||||
elif savelocation.endswith("/rmg"):
|
|
||||||
savelocation = savelocation.replace("/rmg", '')
|
|
||||||
if savelocation != self.fhdhr.api.base:
|
|
||||||
self.detect_method.set(savelocation)
|
|
||||||
except KeyError:
|
|
||||||
return
|
|
||||||
else:
|
else:
|
||||||
self.fhdhr.logger.debug('Unknown SSDP command %s %s' % (cmd[0], cmd[1]))
|
self.fhdhr.logger.debug('Unknown SSDP command %s %s' % (cmd[0], cmd[1]))
|
||||||
|
|
||||||
def m_search(self):
|
def m_search(self):
|
||||||
data = self.msearch_payload
|
data = self.msearch_payload
|
||||||
self.sock.sendto(data, self.broadcase_address_tuple)
|
self.sock.sendto(data, self.broadcast_address_tuple)
|
||||||
|
|
||||||
def create_msearch_payload(self):
|
def create_msearch_payload(self):
|
||||||
|
|
||||||
@ -132,15 +147,6 @@ class SSDPServer():
|
|||||||
|
|
||||||
return data.encode("utf-8")
|
return data.encode("utf-8")
|
||||||
|
|
||||||
def run(self):
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
data, address = self.sock.recvfrom(1024)
|
|
||||||
self.on_recv(data, address)
|
|
||||||
self.do_alive()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
self.sock.close()
|
|
||||||
|
|
||||||
def setup_ssdp(self):
|
def setup_ssdp(self):
|
||||||
self.sock = None
|
self.sock = None
|
||||||
|
|
||||||
@ -167,7 +173,7 @@ class SSDPServer():
|
|||||||
if self.proto == "ipv4":
|
if self.proto == "ipv4":
|
||||||
self.af_type = socket.AF_INET
|
self.af_type = socket.AF_INET
|
||||||
self.broadcast_ip = "239.255.255.250"
|
self.broadcast_ip = "239.255.255.250"
|
||||||
self.broadcase_address_tuple = (self.broadcast_ip, 1900)
|
self.broadcast_address_tuple = (self.broadcast_ip, 1900)
|
||||||
self.bind_address = "0.0.0.0"
|
self.bind_address = "0.0.0.0"
|
||||||
elif self.proto == "ipv6":
|
elif self.proto == "ipv6":
|
||||||
self.af_type = socket.AF_INET6
|
self.af_type = socket.AF_INET6
|
||||||
|
|||||||
@ -1,39 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
class HDHR_SSDP():
|
|
||||||
|
|
||||||
def __init__(self, fhdhr, broadcast_ip, max_age):
|
|
||||||
self.fhdhr = fhdhr
|
|
||||||
|
|
||||||
self.ssdp_content = None
|
|
||||||
|
|
||||||
self.broadcast_ip = broadcast_ip
|
|
||||||
self.device_xml_path = '/device.xml'
|
|
||||||
|
|
||||||
self.max_age = max_age
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
if self.ssdp_content:
|
|
||||||
return self.ssdp_content.encode("utf-8")
|
|
||||||
|
|
||||||
data = ''
|
|
||||||
data_command = "NOTIFY * HTTP/1.1"
|
|
||||||
|
|
||||||
data_dict = {
|
|
||||||
"HOST": "%s:%s" % (self.broadcast_ip, 1900),
|
|
||||||
"NT": 'urn:schemas-upnp-org:device:MediaServer:1',
|
|
||||||
"NTS": "ssdp:alive",
|
|
||||||
"USN": 'uuid:%s::%s' % (self.fhdhr.config.dict["main"]["uuid"], 'urn:schemas-upnp-org:device:MediaServer:1'),
|
|
||||||
"SERVER": 'fHDHR/%s UPnP/1.0' % self.fhdhr.version,
|
|
||||||
"LOCATION": "%s%s" % (self.fhdhr.api.base, self.device_xml_path),
|
|
||||||
"AL": "%s%s" % (self.fhdhr.api.base, self.device_xml_path),
|
|
||||||
"Cache-Control:max-age=": self.max_age
|
|
||||||
}
|
|
||||||
|
|
||||||
data += "%s\r\n" % data_command
|
|
||||||
for data_key in list(data_dict.keys()):
|
|
||||||
data += "%s:%s\r\n" % (data_key, data_dict[data_key])
|
|
||||||
data += "\r\n"
|
|
||||||
|
|
||||||
self.ssdp_content = data
|
|
||||||
return data.encode("utf-8")
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
class RMG_SSDP():
|
|
||||||
|
|
||||||
def __init__(self, fhdhr, broadcast_ip, max_age):
|
|
||||||
self.fhdhr = fhdhr
|
|
||||||
|
|
||||||
self.ssdp_content = None
|
|
||||||
|
|
||||||
self.broadcast_ip = broadcast_ip
|
|
||||||
self.device_xml_path = '/device.xml'
|
|
||||||
|
|
||||||
self.max_age = max_age
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
if self.ssdp_content:
|
|
||||||
return self.ssdp_content.encode("utf-8")
|
|
||||||
|
|
||||||
data = ''
|
|
||||||
data_command = "NOTIFY * HTTP/1.1"
|
|
||||||
|
|
||||||
data_dict = {
|
|
||||||
"HOST": "%s:%s" % (self.broadcast_ip, 1900),
|
|
||||||
"NT": 'urn:schemas-upnp-org:device-1-0',
|
|
||||||
"NTS": "ssdp:alive",
|
|
||||||
"USN": 'uuid:%s::%s' % (self.fhdhr.config.dict["main"]["uuid"], 'urn:schemas-upnp-org:device-1-0'),
|
|
||||||
"SERVER": 'fHDHR/%s UPnP/1.0' % self.fhdhr.version,
|
|
||||||
"LOCATION": "%s%s" % (self.fhdhr.api.base, self.device_xml_path),
|
|
||||||
"AL": "%s%s" % (self.fhdhr.api.base, self.device_xml_path),
|
|
||||||
"Cache-Control:max-age=": self.max_age
|
|
||||||
}
|
|
||||||
|
|
||||||
data += "%s\r\n" % data_command
|
|
||||||
for data_key in list(data_dict.keys()):
|
|
||||||
data += "%s:%s\r\n" % (data_key, data_dict[data_key])
|
|
||||||
data += "\r\n"
|
|
||||||
|
|
||||||
self.ssdp_content = data
|
|
||||||
return data.encode("utf-8")
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
class fHDHR_Detect():
|
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
|
||||||
self.fhdhr = fhdhr
|
|
||||||
self.fhdhr.db.delete_fhdhr_value("ssdp_detect", "list")
|
|
||||||
|
|
||||||
def set(self, location):
|
|
||||||
detect_list = self.fhdhr.db.get_fhdhr_value("ssdp_detect", "list") or []
|
|
||||||
if location not in detect_list:
|
|
||||||
detect_list.append(location)
|
|
||||||
self.fhdhr.db.set_fhdhr_value("ssdp_detect", "list", detect_list)
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
return self.fhdhr.db.get_fhdhr_value("ssdp_detect", "list") or []
|
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import m3u8
|
||||||
|
|
||||||
from fHDHR.exceptions import TunerError
|
from fHDHR.exceptions import TunerError
|
||||||
|
|
||||||
@ -11,96 +12,218 @@ class Tuners():
|
|||||||
self.channels = channels
|
self.channels = channels
|
||||||
|
|
||||||
self.epg = epg
|
self.epg = epg
|
||||||
self.max_tuners = int(self.fhdhr.config.dict["fhdhr"]["tuner_count"])
|
|
||||||
|
|
||||||
self.tuners = {}
|
self.tuners = {}
|
||||||
|
for origin in list(self.fhdhr.origins.origins_dict.keys()):
|
||||||
|
self.tuners[origin] = {}
|
||||||
|
|
||||||
self.fhdhr.logger.info("Creating %s tuners." % str(self.max_tuners))
|
max_tuners = int(self.fhdhr.origins.origins_dict[origin].tuners)
|
||||||
|
|
||||||
for i in range(0, self.max_tuners):
|
self.fhdhr.logger.info("Creating %s tuners for %s." % (max_tuners, origin))
|
||||||
self.tuners[str(i)] = Tuner(fhdhr, i, epg)
|
|
||||||
|
|
||||||
def get_available_tuner(self):
|
for i in range(0, max_tuners):
|
||||||
return next(tunernum for tunernum in list(self.tuners.keys()) if not self.tuners[tunernum].tuner_lock.locked()) or None
|
self.tuners[origin][str(i)] = Tuner(fhdhr, i, epg, origin)
|
||||||
|
|
||||||
def get_scanning_tuner(self):
|
self.alt_stream_handlers = {}
|
||||||
return next(tunernum for tunernum in list(self.tuners.keys()) if self.tuners[tunernum].status["status"] == "Scanning") or None
|
|
||||||
|
|
||||||
def stop_tuner_scan(self):
|
def alt_stream_methods_selfadd(self):
|
||||||
tunernum = self.get_scanning_tuner()
|
for plugin_name in list(self.fhdhr.plugins.plugins.keys()):
|
||||||
|
if self.fhdhr.plugins.plugins[plugin_name].type == "alt_stream":
|
||||||
|
method = self.fhdhr.plugins.plugins[plugin_name].name
|
||||||
|
self.alt_stream_handlers[method] = self.fhdhr.plugins.plugins[plugin_name]
|
||||||
|
|
||||||
|
def get_available_tuner(self, origin):
|
||||||
|
return next(tunernum for tunernum in list(self.tuners[origin].keys()) if not self.tuners[origin][tunernum].tuner_lock.locked()) or None
|
||||||
|
|
||||||
|
def get_scanning_tuner(self, origin):
|
||||||
|
return next(tunernum for tunernum in list(self.tuners[origin].keys()) if self.tuners[origin][tunernum].status["status"] == "Scanning") or None
|
||||||
|
|
||||||
|
def stop_tuner_scan(self, origin):
|
||||||
|
tunernum = self.get_scanning_tuner(origin)
|
||||||
if tunernum:
|
if tunernum:
|
||||||
self.tuners[str(tunernum)].close()
|
self.tuners[origin][str(tunernum)].close()
|
||||||
|
|
||||||
def tuner_scan(self):
|
def tuner_scan(self, origin="all"):
|
||||||
"""Temporarily use a tuner for a scan"""
|
"""Temporarily use a tuner for a scan"""
|
||||||
if not self.available_tuner_count():
|
|
||||||
|
if origin == "all":
|
||||||
|
origins = list(self.tuners.keys())
|
||||||
|
else:
|
||||||
|
origins = [origin]
|
||||||
|
|
||||||
|
for origin in origins:
|
||||||
|
|
||||||
|
if not self.available_tuner_count(origin):
|
||||||
raise TunerError("805 - All Tuners In Use")
|
raise TunerError("805 - All Tuners In Use")
|
||||||
|
|
||||||
tunernumber = self.get_available_tuner()
|
tunernumber = self.get_available_tuner(origin)
|
||||||
self.tuners[str(tunernumber)].channel_scan()
|
self.tuners[origin][str(tunernumber)].channel_scan(origin)
|
||||||
|
|
||||||
if not tunernumber:
|
if not tunernumber:
|
||||||
raise TunerError("805 - All Tuners In Use")
|
raise TunerError("805 - All Tuners In Use")
|
||||||
|
|
||||||
def tuner_grab(self, tuner_number, channel_number):
|
def tuner_grab(self, tuner_number, origin, channel_number):
|
||||||
|
|
||||||
if str(tuner_number) not in list(self.tuners.keys()):
|
if str(tuner_number) not in list(self.tuners[origin].keys()):
|
||||||
self.fhdhr.logger.error("Tuner %s does not exist." % str(tuner_number))
|
self.fhdhr.logger.error("Tuner %s does not exist for %s." % (tuner_number, origin))
|
||||||
raise TunerError("806 - Tune Failed")
|
raise TunerError("806 - Tune Failed")
|
||||||
|
|
||||||
# TunerError will raise if unavailable
|
# TunerError will raise if unavailable
|
||||||
self.tuners[str(tuner_number)].grab(channel_number)
|
self.tuners[origin][str(tuner_number)].grab(origin, channel_number)
|
||||||
|
|
||||||
return tuner_number
|
return tuner_number
|
||||||
|
|
||||||
def first_available(self, channel_number):
|
def first_available(self, origin, channel_number, dograb=True):
|
||||||
|
|
||||||
if not self.available_tuner_count():
|
if not self.available_tuner_count(origin):
|
||||||
raise TunerError("805 - All Tuners In Use")
|
raise TunerError("805 - All Tuners In Use")
|
||||||
|
|
||||||
tunernumber = self.get_available_tuner()
|
tunernumber = self.get_available_tuner(origin)
|
||||||
|
|
||||||
if not tunernumber:
|
if not tunernumber:
|
||||||
raise TunerError("805 - All Tuners In Use")
|
raise TunerError("805 - All Tuners In Use")
|
||||||
else:
|
else:
|
||||||
self.tuners[str(tunernumber)].grab(channel_number)
|
self.tuners[origin][str(tunernumber)].grab(origin, channel_number)
|
||||||
return tunernumber
|
return tunernumber
|
||||||
|
|
||||||
def tuner_close(self, tunernum):
|
def tuner_close(self, tunernum, origin):
|
||||||
self.tuners[str(tunernum)].close()
|
self.tuners[origin][str(tunernum)].close()
|
||||||
|
|
||||||
def status(self):
|
def status(self, origin=None):
|
||||||
all_status = {}
|
all_status = {}
|
||||||
for tunernum in list(self.tuners.keys()):
|
if origin:
|
||||||
all_status[tunernum] = self.tuners[str(tunernum)].get_status()
|
for tunernum in list(self.tuners[origin].keys()):
|
||||||
|
all_status[tunernum] = self.tuners[origin][str(tunernum)].get_status()
|
||||||
|
else:
|
||||||
|
for origin in list(self.tuners.keys()):
|
||||||
|
all_status[origin] = {}
|
||||||
|
for tunernum in list(self.tuners[origin].keys()):
|
||||||
|
all_status[origin][tunernum] = self.tuners[origin][str(tunernum)].get_status()
|
||||||
return all_status
|
return all_status
|
||||||
|
|
||||||
def available_tuner_count(self):
|
def available_tuner_count(self, origin):
|
||||||
available_tuners = 0
|
available_tuners = 0
|
||||||
for tunernum in list(self.tuners.keys()):
|
for tunernum in list(self.tuners[origin].keys()):
|
||||||
if not self.tuners[str(tunernum)].tuner_lock.locked():
|
if not self.tuners[origin][str(tunernum)].tuner_lock.locked():
|
||||||
available_tuners += 1
|
available_tuners += 1
|
||||||
return available_tuners
|
return available_tuners
|
||||||
|
|
||||||
def inuse_tuner_count(self):
|
def inuse_tuner_count(self, origin):
|
||||||
inuse_tuners = 0
|
inuse_tuners = 0
|
||||||
for tunernum in list(self.tuners.keys()):
|
for tunernum in list(self.tuners[origin].keys()):
|
||||||
if self.tuners[str(tunernum)].tuner_lock.locked():
|
if self.tuners[origin][str(tunernum)].tuner_lock.locked():
|
||||||
inuse_tuners += 1
|
inuse_tuners += 1
|
||||||
return inuse_tuners
|
return inuse_tuners
|
||||||
|
|
||||||
def get_stream_info(self, stream_args):
|
def get_stream_info(self, stream_args):
|
||||||
|
|
||||||
stream_args["channelUri"] = self.channels.get_channel_stream(str(stream_args["channel"]))
|
stream_info = self.channels.get_channel_stream(stream_args, stream_args["origin"])
|
||||||
if not stream_args["channelUri"]:
|
if not stream_info:
|
||||||
raise TunerError("806 - Tune Failed")
|
raise TunerError("806 - Tune Failed")
|
||||||
|
|
||||||
channelUri_headers = self.fhdhr.web.session.head(stream_args["channelUri"]).headers
|
if isinstance(stream_info, str):
|
||||||
stream_args["true_content_type"] = channelUri_headers['Content-Type']
|
stream_info = {"url": stream_info, "headers": None}
|
||||||
|
stream_args["stream_info"] = stream_info
|
||||||
|
|
||||||
|
if not stream_args["stream_info"]["url"]:
|
||||||
|
raise TunerError("806 - Tune Failed")
|
||||||
|
|
||||||
|
if "headers" not in list(stream_args["stream_info"].keys()):
|
||||||
|
stream_args["stream_info"]["headers"] = None
|
||||||
|
|
||||||
|
if stream_args["stream_info"]["url"].startswith("udp://"):
|
||||||
|
stream_args["true_content_type"] = "video/mpeg"
|
||||||
|
stream_args["content_type"] = "video/mpeg"
|
||||||
|
else:
|
||||||
|
|
||||||
|
channel_stream_url_headers = self.fhdhr.web.session.head(stream_args["stream_info"]["url"]).headers
|
||||||
|
stream_args["true_content_type"] = channel_stream_url_headers['Content-Type']
|
||||||
|
|
||||||
if stream_args["true_content_type"].startswith(tuple(["application/", "text/"])):
|
if stream_args["true_content_type"].startswith(tuple(["application/", "text/"])):
|
||||||
stream_args["content_type"] = "video/mpeg"
|
stream_args["content_type"] = "video/mpeg"
|
||||||
|
if stream_args["origin_quality"] != -1:
|
||||||
|
stream_args["stream_info"]["url"] = self.m3u8_quality(stream_args)
|
||||||
else:
|
else:
|
||||||
stream_args["content_type"] = stream_args["true_content_type"]
|
stream_args["content_type"] = stream_args["true_content_type"]
|
||||||
|
|
||||||
return stream_args
|
return stream_args
|
||||||
|
|
||||||
|
def m3u8_quality(self, stream_args):
|
||||||
|
|
||||||
|
m3u8_url = stream_args["stream_info"]["url"]
|
||||||
|
quality_profile = stream_args["origin_quality"]
|
||||||
|
|
||||||
|
if not quality_profile:
|
||||||
|
if stream_args["method"] == "direct":
|
||||||
|
quality_profile = "high"
|
||||||
|
self.fhdhr.logger.info("Origin Quality not set in config. Direct Method set and will default to Highest Quality")
|
||||||
|
else:
|
||||||
|
self.fhdhr.logger.info("Origin Quality not set in config. %s Method will select the Quality Automatically" % stream_args["method"])
|
||||||
|
return m3u8_url
|
||||||
|
else:
|
||||||
|
quality_profile = quality_profile.lower()
|
||||||
|
self.fhdhr.logger.info("Origin Quality set in config to %s" % (quality_profile))
|
||||||
|
|
||||||
|
while True:
|
||||||
|
self.fhdhr.logger.info("Opening m3u8 for reading %s" % m3u8_url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if stream_args["stream_info"]["headers"]:
|
||||||
|
videoUrlM3u = m3u8.load(m3u8_url, headers=stream_args["stream_info"]["headers"])
|
||||||
|
else:
|
||||||
|
videoUrlM3u = m3u8.load(m3u8_url)
|
||||||
|
except Exception as e:
|
||||||
|
self.fhdhr.logger.info("m3u8 load error: %s" % e)
|
||||||
|
return m3u8_url
|
||||||
|
|
||||||
|
if len(videoUrlM3u.playlists):
|
||||||
|
self.fhdhr.logger.info("%s m3u8 varients found" % len(videoUrlM3u.playlists))
|
||||||
|
|
||||||
|
# Create list of dicts
|
||||||
|
playlists, playlist_index = {}, 0
|
||||||
|
for playlist_item in videoUrlM3u.playlists:
|
||||||
|
playlist_index += 1
|
||||||
|
playlist_dict = {
|
||||||
|
"url": playlist_item.absolute_uri,
|
||||||
|
"bandwidth": playlist_item.stream_info.bandwidth,
|
||||||
|
}
|
||||||
|
|
||||||
|
if not playlist_item.stream_info.resolution:
|
||||||
|
playlist_dict["width"] = None
|
||||||
|
playlist_dict["height"] = None
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
playlist_dict["width"] = playlist_item.stream_info.resolution[0]
|
||||||
|
playlist_dict["height"] = playlist_item.stream_info.resolution[1]
|
||||||
|
except TypeError:
|
||||||
|
playlist_dict["width"] = None
|
||||||
|
playlist_dict["height"] = None
|
||||||
|
|
||||||
|
playlists[playlist_index] = playlist_dict
|
||||||
|
|
||||||
|
sorted_playlists = sorted(playlists, key=lambda i: (
|
||||||
|
int(playlists[i]['bandwidth']),
|
||||||
|
int(playlists[i]['width'] or 0),
|
||||||
|
int(playlists[i]['height'] or 0)
|
||||||
|
))
|
||||||
|
sorted_playlists = [playlists[x] for x in sorted_playlists]
|
||||||
|
|
||||||
|
if not quality_profile or quality_profile == "high":
|
||||||
|
selected_index = -1
|
||||||
|
elif quality_profile == "medium":
|
||||||
|
selected_index = int((len(sorted_playlists) - 1)/2)
|
||||||
|
elif quality_profile == "low":
|
||||||
|
selected_index = 0
|
||||||
|
|
||||||
|
m3u8_stats = ",".join(
|
||||||
|
["%s %s" % (x, sorted_playlists[selected_index][x])
|
||||||
|
for x in list(sorted_playlists[selected_index].keys())
|
||||||
|
if x != "url" and sorted_playlists[selected_index][x]])
|
||||||
|
self.fhdhr.logger.info("Selected m3u8 details: %s" % m3u8_stats)
|
||||||
|
m3u8_url = sorted_playlists[selected_index]["url"]
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.fhdhr.logger.info("No m3u8 varients found")
|
||||||
|
break
|
||||||
|
|
||||||
|
return m3u8_url
|
||||||
|
|||||||
@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
from .direct_stream import Direct_Stream
|
from .direct_stream import Direct_Stream
|
||||||
from .direct_m3u8_stream import Direct_M3U8_Stream
|
from .direct_m3u8_stream import Direct_M3U8_Stream
|
||||||
from .ffmpeg_stream import FFMPEG_Stream
|
|
||||||
from .vlc_stream import VLC_Stream
|
|
||||||
|
|
||||||
|
|
||||||
class Stream():
|
class Stream():
|
||||||
@ -12,16 +10,14 @@ class Stream():
|
|||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
self.stream_args = stream_args
|
self.stream_args = stream_args
|
||||||
|
|
||||||
if stream_args["method"] == "ffmpeg":
|
if stream_args["method"] == "direct":
|
||||||
self.method = FFMPEG_Stream(fhdhr, stream_args, tuner)
|
if self.stream_args["true_content_type"].startswith(tuple(["application/", "text/"])):
|
||||||
if stream_args["method"] == "vlc":
|
|
||||||
self.method = VLC_Stream(fhdhr, stream_args, tuner)
|
|
||||||
elif (stream_args["method"] == "direct" and
|
|
||||||
not self.stream_args["true_content_type"].startswith(tuple(["application/", "text/"]))):
|
|
||||||
self.method = Direct_Stream(fhdhr, stream_args, tuner)
|
|
||||||
elif (stream_args["method"] == "direct" and
|
|
||||||
self.stream_args["true_content_type"].startswith(tuple(["application/", "text/"]))):
|
|
||||||
self.method = Direct_M3U8_Stream(fhdhr, stream_args, tuner)
|
self.method = Direct_M3U8_Stream(fhdhr, stream_args, tuner)
|
||||||
|
else:
|
||||||
|
self.method = Direct_Stream(fhdhr, stream_args, tuner)
|
||||||
|
else:
|
||||||
|
plugin_name = self.fhdhr.config.dict["streaming"]["valid_methods"][stream_args["method"]]["plugin"]
|
||||||
|
self.method = self.fhdhr.plugins.plugins[plugin_name].Plugin_OBJ(fhdhr, self.fhdhr.plugins.plugins[plugin_name].plugin_utils, stream_args, tuner)
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
return self.method.get()
|
return self.method.get()
|
||||||
|
|||||||
@ -14,25 +14,17 @@ class Direct_M3U8_Stream():
|
|||||||
self.stream_args = stream_args
|
self.stream_args = stream_args
|
||||||
self.tuner = tuner
|
self.tuner = tuner
|
||||||
|
|
||||||
self.chunksize = int(self.fhdhr.config.dict["direct_stream"]['chunksize'])
|
self.bytes_per_read = int(self.fhdhr.config.dict["streaming"]["bytes_per_read"])
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
|
|
||||||
if not self.stream_args["duration"] == 0:
|
if not self.stream_args["duration"] == 0:
|
||||||
self.stream_args["time_end"] = self.stream_args["duration"] + time.time()
|
self.stream_args["time_end"] = self.stream_args["duration"] + time.time()
|
||||||
|
|
||||||
self.fhdhr.logger.info("Detected stream URL is m3u8: %s" % self.stream_args["true_content_type"])
|
self.fhdhr.logger.info("Detected stream of m3u8 URL: %s" % self.stream_args["stream_info"]["url"])
|
||||||
|
|
||||||
channelUri = self.stream_args["channelUri"]
|
if self.stream_args["transcode_quality"]:
|
||||||
while True:
|
self.fhdhr.logger.info("Client requested a %s transcode for stream. Direct Method cannot transcode." % self.stream_args["transcode_quality"])
|
||||||
|
|
||||||
self.fhdhr.logger.info("Opening m3u8 for reading %s" % channelUri)
|
|
||||||
videoUrlM3u = m3u8.load(channelUri)
|
|
||||||
if len(videoUrlM3u.playlists):
|
|
||||||
self.fhdhr.logger.info("%s m3u8 varients found" % len(videoUrlM3u.playlists))
|
|
||||||
channelUri = videoUrlM3u.playlists[0].absolute_uri
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
def generate():
|
def generate():
|
||||||
|
|
||||||
@ -42,7 +34,16 @@ class Direct_M3U8_Stream():
|
|||||||
|
|
||||||
while self.tuner.tuner_lock.locked():
|
while self.tuner.tuner_lock.locked():
|
||||||
|
|
||||||
playlist = m3u8.load(channelUri)
|
try:
|
||||||
|
if self.stream_args["stream_info"]["headers"]:
|
||||||
|
playlist = m3u8.load(self.stream_args["stream_info"]["url"], headers=self.stream_args["stream_info"]["headers"])
|
||||||
|
else:
|
||||||
|
playlist = m3u8.load(self.stream_args["stream_info"]["url"])
|
||||||
|
except Exception as e:
|
||||||
|
self.fhdhr.logger.info("Connection Closed: %s" % e)
|
||||||
|
self.tuner.close()
|
||||||
|
return None
|
||||||
|
|
||||||
segments = playlist.segments
|
segments = playlist.segments
|
||||||
|
|
||||||
if len(played_chunk_urls):
|
if len(played_chunk_urls):
|
||||||
@ -70,12 +71,18 @@ class Direct_M3U8_Stream():
|
|||||||
self.fhdhr.logger.info("Requested Duration Expired.")
|
self.fhdhr.logger.info("Requested Duration Expired.")
|
||||||
self.tuner.close()
|
self.tuner.close()
|
||||||
|
|
||||||
|
if self.stream_args["stream_info"]["headers"]:
|
||||||
|
chunk = self.fhdhr.web.session.get(chunkurl, headers=self.stream_args["stream_info"]["headers"]).content
|
||||||
|
else:
|
||||||
chunk = self.fhdhr.web.session.get(chunkurl).content
|
chunk = self.fhdhr.web.session.get(chunkurl).content
|
||||||
if not chunk:
|
if not chunk:
|
||||||
break
|
break
|
||||||
# raise TunerError("807 - No Video Data")
|
# raise TunerError("807 - No Video Data")
|
||||||
if key:
|
if key:
|
||||||
if key["url"]:
|
if key["url"]:
|
||||||
|
if self.stream_args["stream_info"]["headers"]:
|
||||||
|
keyfile = self.fhdhr.web.session.get(key["url"], headers=self.stream_args["stream_info"]["headers"]).content
|
||||||
|
else:
|
||||||
keyfile = self.fhdhr.web.session.get(key["url"]).content
|
keyfile = self.fhdhr.web.session.get(key["url"]).content
|
||||||
cryptor = AES.new(keyfile, AES.MODE_CBC, keyfile)
|
cryptor = AES.new(keyfile, AES.MODE_CBC, keyfile)
|
||||||
self.fhdhr.logger.info("Decrypting Chunk #%s with key: %s" % (len(played_chunk_urls), key["url"]))
|
self.fhdhr.logger.info("Decrypting Chunk #%s with key: %s" % (len(played_chunk_urls), key["url"]))
|
||||||
@ -91,8 +98,11 @@ class Direct_M3U8_Stream():
|
|||||||
except GeneratorExit:
|
except GeneratorExit:
|
||||||
self.fhdhr.logger.info("Connection Closed.")
|
self.fhdhr.logger.info("Connection Closed.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.fhdhr.logger.info("Connection Closed: " + str(e))
|
self.fhdhr.logger.info("Connection Closed: %s" % e)
|
||||||
finally:
|
finally:
|
||||||
|
self.fhdhr.logger.info("Connection Closed: Tuner Lock Removed")
|
||||||
|
if hasattr(self.fhdhr.origins.origins_dict[self.tuner.origin], "close_stream"):
|
||||||
|
self.fhdhr.origins.origins_dict[self.tuner.origin].close_stream(self.tuner.number, self.stream_args)
|
||||||
self.tuner.close()
|
self.tuner.close()
|
||||||
# raise TunerError("806 - Tune Failed")
|
# raise TunerError("806 - Tune Failed")
|
||||||
|
|
||||||
|
|||||||
@ -11,16 +11,22 @@ class Direct_Stream():
|
|||||||
self.stream_args = stream_args
|
self.stream_args = stream_args
|
||||||
self.tuner = tuner
|
self.tuner = tuner
|
||||||
|
|
||||||
self.chunksize = int(self.fhdhr.config.dict["direct_stream"]['chunksize'])
|
self.bytes_per_read = int(self.fhdhr.config.dict["streaming"]["bytes_per_read"])
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
|
|
||||||
if not self.stream_args["duration"] == 0:
|
if not self.stream_args["duration"] == 0:
|
||||||
self.stream_args["time_end"] = self.stream_args["duration"] + time.time()
|
self.stream_args["time_end"] = self.stream_args["duration"] + time.time()
|
||||||
|
|
||||||
self.fhdhr.logger.info("Direct Stream of %s URL: %s" % (self.stream_args["true_content_type"], self.stream_args["channelUri"]))
|
self.fhdhr.logger.info("Direct Stream of %s URL: %s" % (self.stream_args["true_content_type"], self.stream_args["stream_info"]["url"]))
|
||||||
|
|
||||||
req = self.fhdhr.web.session.get(self.stream_args["channelUri"], stream=True)
|
if self.stream_args["transcode_quality"]:
|
||||||
|
self.fhdhr.logger.info("Client requested a %s transcode for stream. Direct Method cannot transcode." % self.stream_args["transcode_quality"])
|
||||||
|
|
||||||
|
if self.stream_args["stream_info"]["headers"]:
|
||||||
|
req = self.fhdhr.web.session.get(self.stream_args["stream_info"]["url"], stream=True, headers=self.stream_args["stream_info"]["headers"])
|
||||||
|
else:
|
||||||
|
req = self.fhdhr.web.session.get(self.stream_args["stream_info"]["url"], stream=True)
|
||||||
|
|
||||||
def generate():
|
def generate():
|
||||||
|
|
||||||
@ -30,7 +36,7 @@ class Direct_Stream():
|
|||||||
|
|
||||||
while self.tuner.tuner_lock.locked():
|
while self.tuner.tuner_lock.locked():
|
||||||
|
|
||||||
for chunk in req.iter_content(chunk_size=self.chunksize):
|
for chunk in req.iter_content(chunk_size=self.bytes_per_read):
|
||||||
|
|
||||||
if (not self.stream_args["duration"] == 0 and
|
if (not self.stream_args["duration"] == 0 and
|
||||||
not time.time() < self.stream_args["time_end"]):
|
not time.time() < self.stream_args["time_end"]):
|
||||||
@ -54,9 +60,12 @@ class Direct_Stream():
|
|||||||
except GeneratorExit:
|
except GeneratorExit:
|
||||||
self.fhdhr.logger.info("Connection Closed.")
|
self.fhdhr.logger.info("Connection Closed.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.fhdhr.logger.info("Connection Closed: " + str(e))
|
self.fhdhr.logger.info("Connection Closed: %s" % e)
|
||||||
finally:
|
finally:
|
||||||
req.close()
|
req.close()
|
||||||
|
self.fhdhr.logger.info("Connection Closed: Tuner Lock Removed")
|
||||||
|
if hasattr(self.fhdhr.origins.origins_dict[self.tuner.origin], "close_stream"):
|
||||||
|
self.fhdhr.origins.origins_dict[self.tuner.origin].close_stream(self.tuner.number, self.stream_args)
|
||||||
self.tuner.close()
|
self.tuner.close()
|
||||||
# raise TunerError("806 - Tune Failed")
|
# raise TunerError("806 - Tune Failed")
|
||||||
|
|
||||||
|
|||||||
@ -1,130 +0,0 @@
|
|||||||
import sys
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
# from fHDHR.exceptions import TunerError
|
|
||||||
|
|
||||||
|
|
||||||
class FFMPEG_Stream():
|
|
||||||
|
|
||||||
def __init__(self, fhdhr, stream_args, tuner):
|
|
||||||
self.fhdhr = fhdhr
|
|
||||||
self.stream_args = stream_args
|
|
||||||
self.tuner = tuner
|
|
||||||
|
|
||||||
self.bytes_per_read = int(self.fhdhr.config.dict["ffmpeg"]["bytes_per_read"])
|
|
||||||
self.ffmpeg_command = self.ffmpeg_command_assemble(stream_args)
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
|
|
||||||
ffmpeg_proc = subprocess.Popen(self.ffmpeg_command, stdout=subprocess.PIPE)
|
|
||||||
|
|
||||||
def generate():
|
|
||||||
try:
|
|
||||||
while self.tuner.tuner_lock.locked():
|
|
||||||
|
|
||||||
chunk = ffmpeg_proc.stdout.read(self.bytes_per_read)
|
|
||||||
if not chunk:
|
|
||||||
break
|
|
||||||
# raise TunerError("807 - No Video Data")
|
|
||||||
yield chunk
|
|
||||||
chunk_size = int(sys.getsizeof(chunk))
|
|
||||||
self.tuner.add_downloaded_size(chunk_size)
|
|
||||||
self.fhdhr.logger.info("Connection Closed: Tuner Lock Removed")
|
|
||||||
|
|
||||||
except GeneratorExit:
|
|
||||||
self.fhdhr.logger.info("Connection Closed.")
|
|
||||||
except Exception as e:
|
|
||||||
self.fhdhr.logger.info("Connection Closed: " + str(e))
|
|
||||||
finally:
|
|
||||||
ffmpeg_proc.terminate()
|
|
||||||
ffmpeg_proc.communicate()
|
|
||||||
self.tuner.close()
|
|
||||||
# raise TunerError("806 - Tune Failed")
|
|
||||||
|
|
||||||
return generate()
|
|
||||||
|
|
||||||
def ffmpeg_command_assemble(self, stream_args):
|
|
||||||
ffmpeg_command = [
|
|
||||||
self.fhdhr.config.dict["ffmpeg"]["path"],
|
|
||||||
"-i", stream_args["channelUri"],
|
|
||||||
]
|
|
||||||
ffmpeg_command.extend(self.ffmpeg_duration(stream_args))
|
|
||||||
ffmpeg_command.extend(self.transcode_profiles(stream_args))
|
|
||||||
ffmpeg_command.extend(self.ffmpeg_loglevel())
|
|
||||||
ffmpeg_command.extend(["pipe:stdout"])
|
|
||||||
return ffmpeg_command
|
|
||||||
|
|
||||||
def ffmpeg_duration(self, stream_args):
|
|
||||||
ffmpeg_command = []
|
|
||||||
if stream_args["duration"]:
|
|
||||||
ffmpeg_command.extend(["-t", str(stream_args["duration"])])
|
|
||||||
else:
|
|
||||||
ffmpeg_command.extend(
|
|
||||||
[
|
|
||||||
"-reconnect", "1",
|
|
||||||
"-reconnect_at_eof", "1",
|
|
||||||
"-reconnect_streamed", "1",
|
|
||||||
"-reconnect_delay_max", "2",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
return ffmpeg_command
|
|
||||||
|
|
||||||
def ffmpeg_loglevel(self):
|
|
||||||
ffmpeg_command = []
|
|
||||||
log_level = self.fhdhr.config.dict["logging"]["level"].lower()
|
|
||||||
|
|
||||||
loglevel_dict = {
|
|
||||||
"debug": "debug",
|
|
||||||
"info": "info",
|
|
||||||
"error": "error",
|
|
||||||
"warning": "warning",
|
|
||||||
"critical": "fatal",
|
|
||||||
}
|
|
||||||
if log_level not in ["info", "debug"]:
|
|
||||||
ffmpeg_command.extend(["-nostats", "-hide_banner"])
|
|
||||||
ffmpeg_command.extend(["-loglevel", loglevel_dict[log_level]])
|
|
||||||
return ffmpeg_command
|
|
||||||
|
|
||||||
def transcode_profiles(self, stream_args):
|
|
||||||
# TODO implement actual profiles here
|
|
||||||
"""
|
|
||||||
• heavy: transcode to AVC with the same resolution, frame-rate, and interlacing as the
|
|
||||||
original stream. For example 1080i60 AVC 1080i60, 720p60 AVC 720p60. → →
|
|
||||||
• mobile: trancode to AVC progressive not exceeding 1280x720 30fps.
|
|
||||||
• internet720: transcode to low bitrate AVC progressive not exceeding 1280x720 30fps.
|
|
||||||
• internet480: transcode to low bitrate AVC progressive not exceeding 848x480 30fps for
|
|
||||||
16:9 content, not exceeding 640x480 30fps for 4:3 content.
|
|
||||||
• internet360: transcode to low bitrate AVC progressive not exceeding 640x360 30fps for
|
|
||||||
16:9 content, not exceeding 480x360 30fps for 4:3 content.
|
|
||||||
• internet240: transcode to low bitrate AVC progressive not exceeding 432x240 30fps for
|
|
||||||
16:9 content, not exceeding 320x240 30fps for 4:3 content
|
|
||||||
"""
|
|
||||||
|
|
||||||
if stream_args["transcode"]:
|
|
||||||
self.fhdhr.logger.info("Client requested a " + stream_args["transcode"] + " transcode for stream.")
|
|
||||||
stream_args["transcode"] = None
|
|
||||||
|
|
||||||
ffmpeg_command = []
|
|
||||||
|
|
||||||
if not stream_args["transcode"]:
|
|
||||||
ffmpeg_command.extend(
|
|
||||||
[
|
|
||||||
"-c", "copy",
|
|
||||||
"-f", "mpegts",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
elif stream_args["transcode"] == "heavy":
|
|
||||||
ffmpeg_command.extend([])
|
|
||||||
elif stream_args["transcode"] == "mobile":
|
|
||||||
ffmpeg_command.extend([])
|
|
||||||
elif stream_args["transcode"] == "internet720":
|
|
||||||
ffmpeg_command.extend([])
|
|
||||||
elif stream_args["transcode"] == "internet480":
|
|
||||||
ffmpeg_command.extend([])
|
|
||||||
elif stream_args["transcode"] == "internet360":
|
|
||||||
ffmpeg_command.extend([])
|
|
||||||
elif stream_args["transcode"] == "internet240":
|
|
||||||
ffmpeg_command.extend([])
|
|
||||||
|
|
||||||
return ffmpeg_command
|
|
||||||
@ -1,121 +0,0 @@
|
|||||||
import sys
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
# from fHDHR.exceptions import TunerError
|
|
||||||
|
|
||||||
|
|
||||||
class VLC_Stream():
|
|
||||||
|
|
||||||
def __init__(self, fhdhr, stream_args, tuner):
|
|
||||||
self.fhdhr = fhdhr
|
|
||||||
self.stream_args = stream_args
|
|
||||||
self.tuner = tuner
|
|
||||||
|
|
||||||
self.bytes_per_read = int(self.fhdhr.config.dict["vlc"]["bytes_per_read"])
|
|
||||||
self.vlc_command = self.vlc_command_assemble(stream_args)
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
|
|
||||||
vlc_proc = subprocess.Popen(self.vlc_command, stdout=subprocess.PIPE)
|
|
||||||
|
|
||||||
def generate():
|
|
||||||
try:
|
|
||||||
|
|
||||||
while self.tuner.tuner_lock.locked():
|
|
||||||
|
|
||||||
chunk = vlc_proc.stdout.read(self.bytes_per_read)
|
|
||||||
if not chunk:
|
|
||||||
break
|
|
||||||
# raise TunerError("807 - No Video Data")
|
|
||||||
yield chunk
|
|
||||||
chunk_size = int(sys.getsizeof(chunk))
|
|
||||||
self.tuner.add_downloaded_size(chunk_size)
|
|
||||||
self.fhdhr.logger.info("Connection Closed: Tuner Lock Removed")
|
|
||||||
|
|
||||||
except GeneratorExit:
|
|
||||||
self.fhdhr.logger.info("Connection Closed.")
|
|
||||||
except Exception as e:
|
|
||||||
self.fhdhr.logger.info("Connection Closed: " + str(e))
|
|
||||||
finally:
|
|
||||||
vlc_proc.terminate()
|
|
||||||
vlc_proc.communicate()
|
|
||||||
self.fhdhr.logger.info("Connection Closed: Tuner Lock Removed")
|
|
||||||
self.tuner.close()
|
|
||||||
# raise TunerError("806 - Tune Failed")
|
|
||||||
|
|
||||||
return generate()
|
|
||||||
|
|
||||||
def vlc_command_assemble(self, stream_args):
|
|
||||||
vlc_command = [
|
|
||||||
self.fhdhr.config.dict["vlc"]["path"],
|
|
||||||
"-I", "dummy", stream_args["channelUri"],
|
|
||||||
]
|
|
||||||
vlc_command.extend(self.vlc_duration(stream_args))
|
|
||||||
vlc_command.extend(self.vlc_loglevel())
|
|
||||||
vlc_command.extend(["--sout"])
|
|
||||||
vlc_command.extend(self.transcode_profiles(stream_args))
|
|
||||||
return vlc_command
|
|
||||||
|
|
||||||
def vlc_duration(self, stream_args):
|
|
||||||
vlc_command = []
|
|
||||||
if stream_args["duration"]:
|
|
||||||
vlc_command.extend(["--run-time=%s" % str(stream_args["duration"])])
|
|
||||||
return vlc_command
|
|
||||||
|
|
||||||
def vlc_loglevel(self):
|
|
||||||
vlc_command = []
|
|
||||||
log_level = self.fhdhr.config.dict["logging"]["level"].lower()
|
|
||||||
|
|
||||||
loglevel_dict = {
|
|
||||||
"debug": "3",
|
|
||||||
"info": "0",
|
|
||||||
"error": "1",
|
|
||||||
"warning": "2",
|
|
||||||
"critical": "1",
|
|
||||||
}
|
|
||||||
vlc_command.extend(["--log-verbose=", loglevel_dict[log_level]])
|
|
||||||
if log_level not in ["info", "debug"]:
|
|
||||||
vlc_command.extend(["--quiet"])
|
|
||||||
return vlc_command
|
|
||||||
|
|
||||||
def transcode_profiles(self, stream_args):
|
|
||||||
# TODO implement actual profiles here
|
|
||||||
"""
|
|
||||||
• heavy: transcode to AVC with the same resolution, frame-rate, and interlacing as the
|
|
||||||
original stream. For example 1080i60 AVC 1080i60, 720p60 AVC 720p60. → →
|
|
||||||
• mobile: trancode to AVC progressive not exceeding 1280x720 30fps.
|
|
||||||
• internet720: transcode to low bitrate AVC progressive not exceeding 1280x720 30fps.
|
|
||||||
• internet480: transcode to low bitrate AVC progressive not exceeding 848x480 30fps for
|
|
||||||
16:9 content, not exceeding 640x480 30fps for 4:3 content.
|
|
||||||
• internet360: transcode to low bitrate AVC progressive not exceeding 640x360 30fps for
|
|
||||||
16:9 content, not exceeding 480x360 30fps for 4:3 content.
|
|
||||||
• internet240: transcode to low bitrate AVC progressive not exceeding 432x240 30fps for
|
|
||||||
16:9 content, not exceeding 320x240 30fps for 4:3 content
|
|
||||||
"""
|
|
||||||
vlc_command = []
|
|
||||||
|
|
||||||
if stream_args["transcode"]:
|
|
||||||
self.fhdhr.logger.info("Client requested a " + stream_args["transcode"] + " transcode for stream.")
|
|
||||||
stream_args["transcode"] = None
|
|
||||||
|
|
||||||
vlc_transcode_string = "#std{mux=ts,access=file,dst=-}"
|
|
||||||
return [vlc_transcode_string]
|
|
||||||
|
|
||||||
'#transcode{vcodec=mp2v,vb=4096,acodec=mp2a,ab=192,scale=1,channels=2,deinterlace}:std{access=file,mux=ts,dst=-"}'
|
|
||||||
|
|
||||||
if not stream_args["transcode"]:
|
|
||||||
vlc_command.extend([])
|
|
||||||
elif stream_args["transcode"] == "heavy":
|
|
||||||
vlc_command.extend([])
|
|
||||||
elif stream_args["transcode"] == "mobile":
|
|
||||||
vlc_command.extend([])
|
|
||||||
elif stream_args["transcode"] == "internet720":
|
|
||||||
vlc_command.extend([])
|
|
||||||
elif stream_args["transcode"] == "internet480":
|
|
||||||
vlc_command.extend([])
|
|
||||||
elif stream_args["transcode"] == "internet360":
|
|
||||||
vlc_command.extend([])
|
|
||||||
elif stream_args["transcode"] == "internet240":
|
|
||||||
vlc_command.extend([])
|
|
||||||
|
|
||||||
return vlc_command
|
|
||||||
@ -1,4 +1,3 @@
|
|||||||
import multiprocessing
|
|
||||||
import threading
|
import threading
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
@ -9,70 +8,76 @@ from .stream import Stream
|
|||||||
|
|
||||||
|
|
||||||
class Tuner():
|
class Tuner():
|
||||||
def __init__(self, fhdhr, inum, epg):
|
def __init__(self, fhdhr, inum, epg, origin):
|
||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
|
|
||||||
self.number = inum
|
self.number = inum
|
||||||
|
self.origin = origin
|
||||||
self.epg = epg
|
self.epg = epg
|
||||||
|
|
||||||
self.tuner_lock = threading.Lock()
|
self.tuner_lock = threading.Lock()
|
||||||
self.set_off_status()
|
self.set_off_status()
|
||||||
|
|
||||||
self.chanscan_url = "%s/api/channels?method=scan" % (self.fhdhr.api.base)
|
self.chanscan_url = "/api/channels?method=scan"
|
||||||
self.close_url = "%s/api/tuners?method=close&tuner=%s" % (self.fhdhr.api.base, str(self.number))
|
self.close_url = "/api/tuners?method=close&tuner=%s&origin=%s" % (self.number, self.origin)
|
||||||
|
|
||||||
def channel_scan(self):
|
def channel_scan(self, origin, grabbed=False):
|
||||||
if self.tuner_lock.locked():
|
if self.tuner_lock.locked() and not grabbed:
|
||||||
self.fhdhr.logger.error("Tuner #%s is not available." % str(self.number))
|
self.fhdhr.logger.error("%s Tuner #%s is not available." % (self.origin, self.number))
|
||||||
raise TunerError("804 - Tuner In Use")
|
raise TunerError("804 - Tuner In Use")
|
||||||
|
|
||||||
if self.status["status"] == "Scanning":
|
if self.status["status"] == "Scanning":
|
||||||
self.fhdhr.logger.info("Channel Scan Already In Progress!")
|
self.fhdhr.logger.info("Channel Scan Already In Progress!")
|
||||||
else:
|
else:
|
||||||
|
|
||||||
|
if not grabbed:
|
||||||
self.tuner_lock.acquire()
|
self.tuner_lock.acquire()
|
||||||
self.status["status"] = "Scanning"
|
self.status["status"] = "Scanning"
|
||||||
self.fhdhr.logger.info("Tuner #%s Performing Channel Scan." % str(self.number))
|
self.status["origin"] = origin
|
||||||
|
self.status["time_start"] = datetime.datetime.utcnow()
|
||||||
|
self.fhdhr.logger.info("Tuner #%s Performing Channel Scan for %s origin." % (self.number, origin))
|
||||||
|
|
||||||
if self.fhdhr.config.dict["main"]["thread_method"] in ["multiprocessing"]:
|
chanscan = threading.Thread(target=self.runscan, args=(origin,))
|
||||||
chanscan = multiprocessing.Process(target=self.runscan)
|
|
||||||
elif self.fhdhr.config.dict["main"]["thread_method"] in ["threading"]:
|
|
||||||
chanscan = threading.Thread(target=self.runscan)
|
|
||||||
if self.fhdhr.config.dict["main"]["thread_method"] in ["multiprocessing", "threading"]:
|
|
||||||
chanscan.start()
|
chanscan.start()
|
||||||
|
|
||||||
def runscan(self):
|
def runscan(self, origin):
|
||||||
self.fhdhr.web.session.get(self.chanscan_url)
|
self.fhdhr.api.get("%s&origin=%s" % (self.chanscan_url, origin))
|
||||||
self.fhdhr.logger.info("Requested Channel Scan Complete.")
|
self.fhdhr.logger.info("Requested Channel Scan for %s origin Complete." % origin)
|
||||||
self.fhdhr.web.session.get(self.close_url)
|
self.close()
|
||||||
|
self.fhdhr.api.get(self.close_url)
|
||||||
|
|
||||||
def add_downloaded_size(self, bytes_count):
|
def add_downloaded_size(self, bytes_count):
|
||||||
if "downloaded" in list(self.status.keys()):
|
if "downloaded" in list(self.status.keys()):
|
||||||
self.status["downloaded"] += bytes_count
|
self.status["downloaded"] += bytes_count
|
||||||
|
|
||||||
def grab(self, channel_number):
|
def grab(self, origin, channel_number):
|
||||||
if self.tuner_lock.locked():
|
if self.tuner_lock.locked():
|
||||||
self.fhdhr.logger.error("Tuner #" + str(self.number) + " is not available.")
|
self.fhdhr.logger.error("Tuner #%s is not available." % self.number)
|
||||||
raise TunerError("804 - Tuner In Use")
|
raise TunerError("804 - Tuner In Use")
|
||||||
self.tuner_lock.acquire()
|
self.tuner_lock.acquire()
|
||||||
self.status["status"] = "Acquired"
|
self.status["status"] = "Acquired"
|
||||||
|
self.status["origin"] = origin
|
||||||
self.status["channel"] = channel_number
|
self.status["channel"] = channel_number
|
||||||
|
self.status["time_start"] = datetime.datetime.utcnow()
|
||||||
self.fhdhr.logger.info("Tuner #%s Acquired." % str(self.number))
|
self.fhdhr.logger.info("Tuner #%s Acquired." % str(self.number))
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self.set_off_status()
|
self.set_off_status()
|
||||||
if self.tuner_lock.locked():
|
if self.tuner_lock.locked():
|
||||||
self.tuner_lock.release()
|
self.tuner_lock.release()
|
||||||
self.fhdhr.logger.info("Tuner #" + str(self.number) + " Released.")
|
self.fhdhr.logger.info("Tuner #%s Released." % self.number)
|
||||||
|
|
||||||
def get_status(self):
|
def get_status(self):
|
||||||
current_status = self.status.copy()
|
current_status = self.status.copy()
|
||||||
if current_status["status"] == "Active":
|
current_status["epg"] = {}
|
||||||
current_status["Play Time"] = str(
|
if current_status["status"] in ["Acquired", "Active", "Scanning"]:
|
||||||
|
current_status["running_time"] = str(
|
||||||
humanized_time(
|
humanized_time(
|
||||||
int((datetime.datetime.utcnow() - current_status["time_start"]).total_seconds())))
|
int((datetime.datetime.utcnow() - current_status["time_start"]).total_seconds())))
|
||||||
current_status["time_start"] = str(current_status["time_start"])
|
current_status["time_start"] = str(current_status["time_start"])
|
||||||
current_status["epg"] = self.epg.whats_on_now(current_status["channel"])
|
if current_status["status"] in ["Active"]:
|
||||||
|
if current_status["origin"] in self.epg.epg_methods:
|
||||||
|
current_status["epg"] = self.epg.whats_on_now(current_status["channel"], method=current_status["origin"])
|
||||||
return current_status
|
return current_status
|
||||||
|
|
||||||
def set_off_status(self):
|
def set_off_status(self):
|
||||||
@ -80,7 +85,7 @@ class Tuner():
|
|||||||
|
|
||||||
def get_stream(self, stream_args, tuner):
|
def get_stream(self, stream_args, tuner):
|
||||||
stream = Stream(self.fhdhr, stream_args, tuner)
|
stream = Stream(self.fhdhr, stream_args, tuner)
|
||||||
return stream.get()
|
return stream
|
||||||
|
|
||||||
def set_status(self, stream_args):
|
def set_status(self, stream_args):
|
||||||
if self.status["status"] != "Active":
|
if self.status["status"] != "Active":
|
||||||
@ -90,8 +95,9 @@ class Tuner():
|
|||||||
"clients_id": [],
|
"clients_id": [],
|
||||||
"method": stream_args["method"],
|
"method": stream_args["method"],
|
||||||
"accessed": [stream_args["accessed"]],
|
"accessed": [stream_args["accessed"]],
|
||||||
|
"origin": stream_args["origin"],
|
||||||
"channel": stream_args["channel"],
|
"channel": stream_args["channel"],
|
||||||
"proxied_url": stream_args["channelUri"],
|
"proxied_url": stream_args["stream_info"]["url"],
|
||||||
"time_start": datetime.datetime.utcnow(),
|
"time_start": datetime.datetime.utcnow(),
|
||||||
"downloaded": 0
|
"downloaded": 0
|
||||||
}
|
}
|
||||||
|
|||||||
36
fHDHR/logger/__init__.py
Normal file
36
fHDHR/logger/__init__.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
class Logger():
|
||||||
|
|
||||||
|
def __init__(self, settings):
|
||||||
|
self.config = settings
|
||||||
|
|
||||||
|
log_level = self.config.dict["logging"]["level"].upper()
|
||||||
|
|
||||||
|
# Create a custom logger
|
||||||
|
logging.basicConfig(format='%(name)s - %(levelname)s - %(message)s', level=log_level)
|
||||||
|
self.logger = logging.getLogger('fHDHR')
|
||||||
|
log_file = os.path.join(self.config.internal["paths"]["logs_dir"], 'fHDHR.log')
|
||||||
|
|
||||||
|
# Create handlers
|
||||||
|
# c_handler = logging.StreamHandler()
|
||||||
|
f_handler = logging.FileHandler(log_file)
|
||||||
|
# c_handler.setLevel(log_level)
|
||||||
|
f_handler.setLevel(log_level)
|
||||||
|
|
||||||
|
# Create formatters and add it to handlers
|
||||||
|
# c_format = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
|
||||||
|
f_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
# c_handler.setFormatter(c_format)
|
||||||
|
f_handler.setFormatter(f_format)
|
||||||
|
|
||||||
|
# Add handlers to the logger
|
||||||
|
# logger.addHandler(c_handler)
|
||||||
|
self.logger.addHandler(f_handler)
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
''' will only get called for undefined attributes '''
|
||||||
|
if hasattr(self.logger, name):
|
||||||
|
return eval("self.logger.%s" % name)
|
||||||
48
fHDHR/origins/__init__.py
Normal file
48
fHDHR/origins/__init__.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
|
||||||
|
import fHDHR.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
class Origin_StandIN():
|
||||||
|
def __init__(self):
|
||||||
|
self.setup_success = False
|
||||||
|
|
||||||
|
def get_channels(self):
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_channel_stream(self, chandict, stream_args):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class Origins():
|
||||||
|
|
||||||
|
def __init__(self, fhdhr):
|
||||||
|
self.fhdhr = fhdhr
|
||||||
|
|
||||||
|
self.origins_dict = {}
|
||||||
|
self.origin_selfadd()
|
||||||
|
for plugin_name in list(self.fhdhr.plugins.plugins.keys()):
|
||||||
|
if self.fhdhr.plugins.plugins[plugin_name].manifest["tagged_mod"] and self.fhdhr.plugins.plugins[plugin_name].manifest["tagged_mod_type"] == "origin":
|
||||||
|
self.fhdhr.plugins.plugins[plugin_name].plugin_utils.origin = self.origins_dict[self.fhdhr.plugins.plugins[plugin_name].manifest["tagged_mod"].lower()]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def valid_origins(self):
|
||||||
|
return [origin for origin in list(self.origins_dict.keys())]
|
||||||
|
|
||||||
|
def origin_selfadd(self):
|
||||||
|
for plugin_name in list(self.fhdhr.plugins.plugins.keys()):
|
||||||
|
if self.fhdhr.plugins.plugins[plugin_name].type == "origin":
|
||||||
|
method = self.fhdhr.plugins.plugins[plugin_name].name.lower()
|
||||||
|
try:
|
||||||
|
plugin_utils = self.fhdhr.plugins.plugins[plugin_name].plugin_utils
|
||||||
|
self.origins_dict[method] = self.fhdhr.plugins.plugins[plugin_name].Plugin_OBJ(plugin_utils)
|
||||||
|
self.fhdhr.logger.info("%s Setup Success" % method)
|
||||||
|
self.origins_dict[method].setup_success = True
|
||||||
|
except fHDHR.exceptions.OriginSetupError as e:
|
||||||
|
self.fhdhr.logger.error(e)
|
||||||
|
self.origins_dict[method] = Origin_StandIN()
|
||||||
|
|
||||||
|
if not hasattr(self.origins_dict[method], 'tuners'):
|
||||||
|
self.origins_dict[method].tuners = 4
|
||||||
|
|
||||||
|
if not hasattr(self.origins_dict[method], 'stream_method'):
|
||||||
|
self.origins_dict[method].stream_method = self.fhdhr.config.dict["streaming"]["method"]
|
||||||
@ -1,92 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
import fHDHR.exceptions
|
|
||||||
|
|
||||||
|
|
||||||
class OriginEPG_StandIN():
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def update_epg(self, channels):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
class OriginChannels_StandIN():
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_channels(self):
|
|
||||||
return []
|
|
||||||
|
|
||||||
def get_channel_stream(self, chandict):
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class OriginServiceWrapper():
|
|
||||||
|
|
||||||
def __init__(self, fhdhr, origin):
|
|
||||||
self.fhdhr = fhdhr
|
|
||||||
self.origin = origin
|
|
||||||
|
|
||||||
self.servicename = fhdhr.config.dict["main"]["servicename"]
|
|
||||||
|
|
||||||
self.setup_success = None
|
|
||||||
self.setup()
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.originservice = self.origin.OriginService(self.fhdhr)
|
|
||||||
self.setup_success = True
|
|
||||||
self.fhdhr.logger.info("%s Setup Success" % self.servicename)
|
|
||||||
except fHDHR.exceptions.OriginSetupError as e:
|
|
||||||
self.fhdhr.logger.error(e)
|
|
||||||
self.setup_success = False
|
|
||||||
|
|
||||||
if self.setup_success:
|
|
||||||
self.channels = self.origin.OriginChannels(self.fhdhr, self.originservice)
|
|
||||||
self.epg = self.origin.OriginEPG(self.fhdhr)
|
|
||||||
else:
|
|
||||||
self.channels = OriginChannels_StandIN()
|
|
||||||
self.epg = OriginEPG_StandIN()
|
|
||||||
|
|
||||||
def get_channels(self):
|
|
||||||
return self.channels.get_channels()
|
|
||||||
|
|
||||||
def get_channel_stream(self, chandict):
|
|
||||||
return self.channels.get_channel_stream(chandict)
|
|
||||||
|
|
||||||
def update_epg(self, channels):
|
|
||||||
return self.epg.update_epg(channels)
|
|
||||||
|
|
||||||
def get_status_dict(self):
|
|
||||||
|
|
||||||
if self.setup_success:
|
|
||||||
status_dict = {
|
|
||||||
"Setup": "Success",
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
full_status_dict = self.origin.get_status_dict()
|
|
||||||
for status_key in list(full_status_dict.keys()):
|
|
||||||
status_dict[status_key] = full_status_dict[status_key]
|
|
||||||
return status_dict
|
|
||||||
except AttributeError:
|
|
||||||
return status_dict
|
|
||||||
else:
|
|
||||||
return {
|
|
||||||
"Setup": "Failed",
|
|
||||||
}
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
''' will only get called for undefined attributes '''
|
|
||||||
if hasattr(self.fhdhr, name):
|
|
||||||
return eval("self.fhdhr." + name)
|
|
||||||
if hasattr(self.originservice, name):
|
|
||||||
return eval("self.originservice." + name)
|
|
||||||
elif hasattr(self.channels, name):
|
|
||||||
return eval("self.channels." + name)
|
|
||||||
elif hasattr(self.epg, name):
|
|
||||||
return eval("self.epg." + name)
|
|
||||||
else:
|
|
||||||
raise AttributeError(name)
|
|
||||||
250
fHDHR/plugins/__init__.py
Normal file
250
fHDHR/plugins/__init__.py
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
import os
|
||||||
|
import imp
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class Plugin_DB():
|
||||||
|
def __init__(self, db, name):
|
||||||
|
self._db = db
|
||||||
|
self.name = name
|
||||||
|
self.namespace = name.lower()
|
||||||
|
|
||||||
|
# fhdhr
|
||||||
|
def set_fhdhr_value(self, pluginitem, key, value, namespace="default"):
|
||||||
|
print("%s plugin is not allowed write access to fhdhr db namespaces." % self.name)
|
||||||
|
return
|
||||||
|
|
||||||
|
def get_fhdhr_value(self, pluginitem, key, namespace="default"):
|
||||||
|
return self._db.get_fhdhr_value(pluginitem, key, namespace=namespace.lower())
|
||||||
|
|
||||||
|
def delete_fhdhr_value(self, pluginitem, key, namespace="default"):
|
||||||
|
print("%s plugin is not allowed write access to fhdhr db namespaces." % self.name)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Plugin
|
||||||
|
def set_plugin_value(self, pluginitem, key, value, namespace=None):
|
||||||
|
if not namespace:
|
||||||
|
namespace = self.namespace
|
||||||
|
elif namespace.lower() != self.namespace:
|
||||||
|
print("%s plugin is not allowed write access to %s db namespace." % (self.name, namespace))
|
||||||
|
return
|
||||||
|
return self._db.set_plugin_value(pluginitem, key, value, namespace=self.namespace)
|
||||||
|
|
||||||
|
def get_plugin_value(self, pluginitem, key, namespace=None):
|
||||||
|
if not namespace:
|
||||||
|
namespace = self.namespace
|
||||||
|
return self._db.get_plugin_value(pluginitem, key, namespace=namespace.lower())
|
||||||
|
|
||||||
|
def delete_plugin_value(self, pluginitem, key, namespace=None):
|
||||||
|
if not namespace:
|
||||||
|
namespace = self.namespace
|
||||||
|
elif namespace.lower() != self.namespace:
|
||||||
|
print("%s plugin is not allowed write access to %s db namespace." % (self.name, namespace))
|
||||||
|
return
|
||||||
|
return self._db.delete_plugin_value(pluginitem, key, namespace=self.namespace)
|
||||||
|
|
||||||
|
|
||||||
|
class Plugin_Config():
|
||||||
|
def __init__(self, config, name):
|
||||||
|
self._config = config
|
||||||
|
self.name = name
|
||||||
|
self.namespace = name.lower()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dict(self):
|
||||||
|
return self._config.dict.copy()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def internal(self):
|
||||||
|
return self._config.internal.copy()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def conf_default(self):
|
||||||
|
return self._config.conf_default.copy()
|
||||||
|
|
||||||
|
def write(self, key, value, namespace=None):
|
||||||
|
if not namespace:
|
||||||
|
namespace = self.namespace
|
||||||
|
elif str(namespace).lower() != self.namespace:
|
||||||
|
print("%s plugin is not allowed write access to fhdhr config namespaces." % self.name)
|
||||||
|
return
|
||||||
|
return self._config.write(key, value, self.namespace)
|
||||||
|
|
||||||
|
|
||||||
|
class Plugin_Utils():
|
||||||
|
|
||||||
|
def __init__(self, config, logger, db, plugin_name, plugin_manifest, modname):
|
||||||
|
self.config = Plugin_Config(config, plugin_manifest["name"])
|
||||||
|
self.db = Plugin_DB(db, plugin_manifest["name"])
|
||||||
|
self.logger = logger
|
||||||
|
self.namespace = plugin_manifest["name"].lower()
|
||||||
|
self.plugin_name = plugin_name
|
||||||
|
self.plugin_manifest = plugin_manifest
|
||||||
|
self.origin = None
|
||||||
|
|
||||||
|
|
||||||
|
class Plugin():
|
||||||
|
|
||||||
|
def __init__(self, config, logger, db, plugin_name, plugin_path, plugin_conf, plugin_manifest):
|
||||||
|
self.config = config
|
||||||
|
self.db = db
|
||||||
|
self.logger = logger
|
||||||
|
|
||||||
|
# Gather Info about Plugin
|
||||||
|
self.plugin_name = plugin_name
|
||||||
|
self.modname = os.path.basename(plugin_path)
|
||||||
|
self.path = plugin_path
|
||||||
|
self.module_type = imp.PKG_DIRECTORY
|
||||||
|
self.multi_plugin = (self.plugin_name != self.modname)
|
||||||
|
self.default_conf = plugin_conf
|
||||||
|
self.manifest = plugin_manifest
|
||||||
|
|
||||||
|
if self.multi_plugin:
|
||||||
|
self.plugin_dict_name = "%s_%s" % (plugin_name, self.modname)
|
||||||
|
else:
|
||||||
|
self.plugin_dict_name = plugin_name
|
||||||
|
|
||||||
|
self.plugin_utils = Plugin_Utils(config, logger, db, plugin_name, plugin_manifest, self.modname)
|
||||||
|
|
||||||
|
# Load the module
|
||||||
|
self._module = self._load()
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
|
||||||
|
if self.type == "alt_epg":
|
||||||
|
self.config.register_valid_epg_method(self.name, self.plugin_dict_name)
|
||||||
|
elif self.type == "alt_stream":
|
||||||
|
self.config.register_valid_streaming_method(self.name, self.plugin_dict_name)
|
||||||
|
elif self.type == "web":
|
||||||
|
self.config.register_web_path(self.manifest["name"], self.path, self.plugin_dict_name)
|
||||||
|
|
||||||
|
if self.has_setup():
|
||||||
|
self._module.setup(self)
|
||||||
|
|
||||||
|
def has_setup(self):
|
||||||
|
return hasattr(self._module, 'setup')
|
||||||
|
|
||||||
|
def _load(self):
|
||||||
|
description = ('', '', self.module_type)
|
||||||
|
mod = imp.load_module(self.plugin_dict_name, None, self.path, description)
|
||||||
|
return mod
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self.manifest["name"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def version(self):
|
||||||
|
return self.manifest["version"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type(self):
|
||||||
|
return self.manifest["type"]
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
''' will only get called for undefined attributes '''
|
||||||
|
if name == "Plugin_OBJ":
|
||||||
|
return self._module.Plugin_OBJ
|
||||||
|
|
||||||
|
|
||||||
|
class PluginsHandler():
|
||||||
|
|
||||||
|
def __init__(self, settings):
|
||||||
|
self.config = settings
|
||||||
|
|
||||||
|
self.plugins = {}
|
||||||
|
|
||||||
|
self.found_plugins = []
|
||||||
|
self.found_plugins_conf = []
|
||||||
|
self.list_plugins()
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
for plugin_name in list(self.plugins.keys()):
|
||||||
|
self.plugins[plugin_name].setup()
|
||||||
|
|
||||||
|
def load_plugin_configs(self):
|
||||||
|
for file_item_path in self.found_plugins_conf:
|
||||||
|
self.config.import_conf_json(file_item_path)
|
||||||
|
|
||||||
|
def list_plugins(self):
|
||||||
|
for directory in self.config.internal["paths"]["plugins_dir"]:
|
||||||
|
|
||||||
|
base = os.path.abspath(directory)
|
||||||
|
for filename in os.listdir(base):
|
||||||
|
abspath = os.path.join(base, filename)
|
||||||
|
|
||||||
|
if os.path.isdir(abspath):
|
||||||
|
|
||||||
|
plugin_conf = []
|
||||||
|
for subfilename in os.listdir(abspath):
|
||||||
|
subabspath = os.path.join(abspath, subfilename)
|
||||||
|
if subfilename.endswith("_conf.json"):
|
||||||
|
plugin_conf.append(subabspath)
|
||||||
|
self.found_plugins_conf.append(subabspath)
|
||||||
|
|
||||||
|
# Plugin/multi-plugin must have a basic manifest json
|
||||||
|
conffilepath = os.path.join(abspath, 'plugin.json')
|
||||||
|
if os.path.isfile(conffilepath):
|
||||||
|
plugin_manifest = json.load(open(conffilepath, 'r'))
|
||||||
|
|
||||||
|
for plugin_man_item in ["name", "version", "type"]:
|
||||||
|
if plugin_man_item not in list(plugin_manifest.keys()):
|
||||||
|
plugin_manifest[plugin_man_item] = None
|
||||||
|
|
||||||
|
self.config.register_version(os.path.basename(filename), plugin_manifest["version"], "plugin")
|
||||||
|
|
||||||
|
if plugin_manifest["type"] == "origin":
|
||||||
|
self.config.register_valid_origin_method(plugin_manifest["name"])
|
||||||
|
|
||||||
|
plugin_import_print_string = "Found %s type plugin: %s %s. " % (plugin_manifest["type"], plugin_manifest["name"], plugin_manifest["version"])
|
||||||
|
|
||||||
|
# Warn for multiple origins
|
||||||
|
if plugin_manifest["type"] == "origin" and len([plugin_name for plugin_name, plugin_path, plugin_conf, plugin_manifest in self.found_plugins if plugin_manifest["type"] == "origin"]):
|
||||||
|
plugin_import_print_string += " ImportWarning: Only one Origin Allowed."
|
||||||
|
|
||||||
|
if not any(plugin_manifest[plugin_item] for plugin_item in ["name", "version", "type"]):
|
||||||
|
plugin_import_print_string += " ImportWarning: Missing PLUGIN_* Value."
|
||||||
|
else:
|
||||||
|
|
||||||
|
# Single Plugin
|
||||||
|
if os.path.isfile(os.path.join(abspath, '__init__.py')):
|
||||||
|
plugin_manifest["tagged_mod"] = None
|
||||||
|
plugin_manifest["tagged_mod_type"] = None
|
||||||
|
self.found_plugins.append((os.path.basename(filename), abspath, plugin_conf, plugin_manifest))
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
# Multi-Plugin
|
||||||
|
for subfilename in os.listdir(abspath):
|
||||||
|
subabspath = os.path.join(abspath, subfilename)
|
||||||
|
|
||||||
|
if os.path.isdir(subabspath):
|
||||||
|
|
||||||
|
subconffilepath = os.path.join(subabspath, 'plugin.json')
|
||||||
|
if os.path.isfile(subconffilepath):
|
||||||
|
subplugin_manifest = json.load(open(subconffilepath, 'r'))
|
||||||
|
|
||||||
|
for subplugin_man_item in ["name", "version", "type"]:
|
||||||
|
if subplugin_man_item not in list(subplugin_manifest.keys()):
|
||||||
|
subplugin_manifest[subplugin_man_item] = plugin_manifest[subplugin_man_item]
|
||||||
|
else:
|
||||||
|
subplugin_manifest = plugin_manifest
|
||||||
|
|
||||||
|
subplugin_manifest["tagged_mod"] = None
|
||||||
|
subplugin_manifest["tagged_mod_type"] = None
|
||||||
|
if plugin_manifest["type"] != subplugin_manifest["type"]:
|
||||||
|
subplugin_manifest["tagged_mod"] = plugin_manifest["name"]
|
||||||
|
subplugin_manifest["tagged_mod_type"] = plugin_manifest["type"]
|
||||||
|
|
||||||
|
if os.path.isfile(os.path.join(subabspath, '__init__.py')):
|
||||||
|
self.found_plugins.append((os.path.basename(filename), subabspath, plugin_conf, subplugin_manifest))
|
||||||
|
|
||||||
|
print(plugin_import_print_string)
|
||||||
|
self.load_plugin_configs()
|
||||||
|
|
||||||
|
def load_plugins(self, logger, db):
|
||||||
|
self.logger = logger
|
||||||
|
self.db = db
|
||||||
|
for plugin_name, plugin_path, plugin_conf, plugin_manifest in self.found_plugins:
|
||||||
|
plugin_item = Plugin(self.config, self.logger, self.db, plugin_name, plugin_path, plugin_conf, plugin_manifest)
|
||||||
|
self.plugins[plugin_item.plugin_dict_name] = plugin_item
|
||||||
@ -8,6 +8,19 @@ UNARY_OPS = (ast.UAdd, ast.USub)
|
|||||||
BINARY_OPS = (ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Mod)
|
BINARY_OPS = (ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Mod)
|
||||||
|
|
||||||
|
|
||||||
|
def channel_sort(channel_list):
|
||||||
|
"""Take a list of channel number strings and sort the Numbers and SubNumbers"""
|
||||||
|
chan_dict_list_split = {}
|
||||||
|
for number in channel_list:
|
||||||
|
try:
|
||||||
|
subnumber = number.split(".")[1]
|
||||||
|
except IndexError:
|
||||||
|
subnumber = None
|
||||||
|
prinumber = number.split(".")[0]
|
||||||
|
chan_dict_list_split[number] = {"number": prinumber, "subnumber": subnumber}
|
||||||
|
return sorted(chan_dict_list_split, key=lambda i: (int(chan_dict_list_split[i]['number']), int(chan_dict_list_split[i]['subnumber'] or 0)))
|
||||||
|
|
||||||
|
|
||||||
def is_docker():
|
def is_docker():
|
||||||
path = "/proc/self/cgroup"
|
path = "/proc/self/cgroup"
|
||||||
if not os.path.isfile(path):
|
if not os.path.isfile(path):
|
||||||
@ -120,9 +133,9 @@ def humanized_time(countdownseconds):
|
|||||||
if currenttimevar > 1:
|
if currenttimevar > 1:
|
||||||
timetype = str(x+"s")
|
timetype = str(x+"s")
|
||||||
if displaymsg:
|
if displaymsg:
|
||||||
displaymsg = str(displaymsg + " " + str(int(currenttimevar)) + " " + timetype)
|
displaymsg = "%s %s %s" % (displaymsg, int(currenttimevar), timetype)
|
||||||
else:
|
else:
|
||||||
displaymsg = str(str(int(currenttimevar)) + " " + timetype)
|
displaymsg = "%s %s" % (int(currenttimevar), timetype)
|
||||||
if not displaymsg:
|
if not displaymsg:
|
||||||
return "just now"
|
return "just now"
|
||||||
return displaymsg
|
return displaymsg
|
||||||
@ -134,3 +147,8 @@ class WebReq():
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.session = requests.Session()
|
self.session = requests.Session()
|
||||||
self.exceptions = requests.exceptions
|
self.exceptions = requests.exceptions
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
''' will only get called for undefined attributes '''
|
||||||
|
if hasattr(self.session, name):
|
||||||
|
return eval("self.session.%s" % name)
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
from gevent.pywsgi import WSGIServer
|
from gevent.pywsgi import WSGIServer
|
||||||
from flask import Flask, request
|
from flask import Flask, request, session
|
||||||
|
import threading
|
||||||
|
import uuid
|
||||||
|
|
||||||
from .pages import fHDHR_Pages
|
from .pages import fHDHR_Pages
|
||||||
from .files import fHDHR_Files
|
from .files import fHDHR_Files
|
||||||
from .hdhr import fHDHR_HDHR
|
from .brython import fHDHR_Brython
|
||||||
from .rmg import fHDHR_RMG
|
|
||||||
from .api import fHDHR_API
|
from .api import fHDHR_API
|
||||||
|
|
||||||
|
|
||||||
fHDHR_web_VERSION = "v0.4.0-beta"
|
fHDHR_web_VERSION = "v0.8.1-beta"
|
||||||
|
|
||||||
|
|
||||||
class fHDHR_HTTP_Server():
|
class fHDHR_HTTP_Server():
|
||||||
@ -21,55 +22,182 @@ class fHDHR_HTTP_Server():
|
|||||||
|
|
||||||
self.fhdhr.logger.info("Loading Flask.")
|
self.fhdhr.logger.info("Loading Flask.")
|
||||||
|
|
||||||
self.app = Flask("fHDHR", template_folder=self.template_folder)
|
self.fhdhr.app = Flask("fHDHR", template_folder=self.template_folder)
|
||||||
|
self.instance_id = str(uuid.uuid4())
|
||||||
|
|
||||||
self.fhdhr.logger.info("Loading HTTP Pages Endpoints.")
|
# Allow Internal API Usage
|
||||||
self.pages = fHDHR_Pages(fhdhr)
|
self.fhdhr.app.testing = True
|
||||||
self.add_endpoints(self.pages, "pages")
|
self.fhdhr.api.client = self.fhdhr.app.test_client()
|
||||||
|
|
||||||
self.fhdhr.logger.info("Loading HTTP Files Endpoints.")
|
# Set Secret Key For Sessions
|
||||||
self.files = fHDHR_Files(fhdhr)
|
self.fhdhr.app.secret_key = self.fhdhr.config.dict["fhdhr"]["friendlyname"]
|
||||||
self.add_endpoints(self.files, "files")
|
|
||||||
|
|
||||||
self.fhdhr.logger.info("Loading HTTP HDHR Endpoints.")
|
self.route_list = {}
|
||||||
self.hdhr = fHDHR_HDHR(fhdhr)
|
|
||||||
self.add_endpoints(self.hdhr, "hdhr")
|
|
||||||
|
|
||||||
self.fhdhr.logger.info("Loading HTTP RMG Endpoints.")
|
self.endpoints_obj = {}
|
||||||
self.rmg = fHDHR_RMG(fhdhr)
|
self.endpoints_obj["pages"] = fHDHR_Pages(fhdhr)
|
||||||
self.add_endpoints(self.rmg, "rmg")
|
self.endpoints_obj["files"] = fHDHR_Files(fhdhr)
|
||||||
|
self.endpoints_obj["brython"] = fHDHR_Brython(fhdhr)
|
||||||
|
self.endpoints_obj["api"] = fHDHR_API(fhdhr)
|
||||||
|
|
||||||
self.fhdhr.logger.info("Loading HTTP API Endpoints.")
|
self.selfadd_web_plugins()
|
||||||
self.api = fHDHR_API(fhdhr)
|
for endpoint_type in list(self.endpoints_obj.keys()):
|
||||||
self.add_endpoints(self.api, "api")
|
self.fhdhr.logger.info("Loading HTTP %s Endpoints." % endpoint_type)
|
||||||
|
self.add_endpoints(endpoint_type)
|
||||||
|
|
||||||
self.app.before_request(self.before_request)
|
self.fhdhr.app.before_request(self.before_request)
|
||||||
self.app.after_request(self.after_request)
|
self.fhdhr.app.after_request(self.after_request)
|
||||||
self.app.before_first_request(self.before_first_request)
|
self.fhdhr.app.before_first_request(self.before_first_request)
|
||||||
|
|
||||||
|
self.fhdhr.threads["flask"] = threading.Thread(target=self.run)
|
||||||
|
|
||||||
|
def selfadd_web_plugins(self):
|
||||||
|
for plugin_name in list(self.fhdhr.plugins.plugins.keys()):
|
||||||
|
if self.fhdhr.plugins.plugins[plugin_name].type == "web":
|
||||||
|
method = self.fhdhr.plugins.plugins[plugin_name].name.lower()
|
||||||
|
plugin_utils = self.fhdhr.plugins.plugins[plugin_name].plugin_utils
|
||||||
|
try:
|
||||||
|
self.endpoints_obj[method] = self.fhdhr.plugins.plugins[plugin_name].Plugin_OBJ(self.fhdhr, plugin_utils)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.fhdhr.logger.info("Flask HTTP Thread Starting")
|
||||||
|
self.fhdhr.threads["flask"].start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.fhdhr.logger.info("Flask HTTP Thread Stopping")
|
||||||
|
self.http.stop()
|
||||||
|
|
||||||
def before_first_request(self):
|
def before_first_request(self):
|
||||||
self.fhdhr.logger.info("HTTP Server Online.")
|
self.fhdhr.logger.info("HTTP Server Online.")
|
||||||
|
|
||||||
def before_request(self):
|
def before_request(self):
|
||||||
|
|
||||||
|
session["session_id"] = str(uuid.uuid4())
|
||||||
|
session["instance_id"] = self.instance_id
|
||||||
|
session["route_list"] = self.route_list
|
||||||
|
|
||||||
|
session["user_agent"] = request.headers.get('User-Agent')
|
||||||
|
|
||||||
|
session["is_internal_api"] = self.detect_internal_api(request)
|
||||||
|
if session["is_internal_api"]:
|
||||||
|
self.fhdhr.logger.debug("Client is using internal API call.")
|
||||||
|
|
||||||
|
session["is_mobile"] = self.detect_mobile(request)
|
||||||
|
if session["is_mobile"]:
|
||||||
|
self.fhdhr.logger.debug("Client is a mobile device.")
|
||||||
|
|
||||||
|
session["is_plexmediaserver"] = self.detect_plexmediaserver(request)
|
||||||
|
if session["is_plexmediaserver"]:
|
||||||
|
self.fhdhr.logger.debug("Client is a Plex Media Server.")
|
||||||
|
|
||||||
|
session["deviceauth"] = self.detect_plexmediaserver(request)
|
||||||
|
|
||||||
|
session["tuner_used"] = None
|
||||||
|
|
||||||
|
session["restart"] = False
|
||||||
|
|
||||||
self.fhdhr.logger.debug("Client %s requested %s Opening" % (request.method, request.path))
|
self.fhdhr.logger.debug("Client %s requested %s Opening" % (request.method, request.path))
|
||||||
|
|
||||||
def after_request(self, response):
|
def after_request(self, response):
|
||||||
self.fhdhr.logger.debug("Client %s requested %s Closing" % (request.method, request.path))
|
|
||||||
return response
|
|
||||||
|
|
||||||
def add_endpoints(self, index_list, index_name):
|
# Close Tuner if it was in use, and did not close already
|
||||||
item_list = [x for x in dir(index_list) if self.isapath(x)]
|
# if session["tuner_used"] is not None:
|
||||||
|
# tuner = self.fhdhr.device.tuners.tuners[str(session["tuner_used"])]
|
||||||
|
# if tuner.tuner_lock.locked():
|
||||||
|
# self.fhdhr.logger.info("Shutting down Tuner #%s after Request." % session["tuner_used"])
|
||||||
|
# tuner.close()
|
||||||
|
|
||||||
|
self.fhdhr.logger.debug("Client %s requested %s Closing" % (request.method, request.path))
|
||||||
|
if not session["restart"]:
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
return self.stop()
|
||||||
|
|
||||||
|
def detect_internal_api(self, request):
|
||||||
|
user_agent = request.headers.get('User-Agent')
|
||||||
|
if not user_agent:
|
||||||
|
return False
|
||||||
|
elif str(user_agent).lower().startswith("fhdhr"):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def detect_deviceauth(self, request):
|
||||||
|
return request.args.get('DeviceAuth', default=None, type=str)
|
||||||
|
|
||||||
|
def detect_mobile(self, request):
|
||||||
|
user_agent = request.headers.get('User-Agent')
|
||||||
|
phones = ["iphone", "android", "blackberry"]
|
||||||
|
if not user_agent:
|
||||||
|
return False
|
||||||
|
elif any(phone in user_agent.lower() for phone in phones):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def detect_plexmediaserver(self, request):
|
||||||
|
user_agent = request.headers.get('User-Agent')
|
||||||
|
if not user_agent:
|
||||||
|
return False
|
||||||
|
elif str(user_agent).lower().startswith("plexmediaserver"):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def add_endpoints(self, index_name):
|
||||||
|
|
||||||
|
item_list = [x for x in dir(self.endpoints_obj[index_name]) if self.isapath(x)]
|
||||||
|
endpoint_main = self.endpoints_obj[index_name]
|
||||||
|
endpoint_main.fhdhr.version # dummy line
|
||||||
for item in item_list:
|
for item in item_list:
|
||||||
endpoints = eval("self." + str(index_name) + "." + str(item) + ".endpoints")
|
endpoints = eval("endpoint_main.%s.%s" % (item, "endpoints"))
|
||||||
if isinstance(endpoints, str):
|
if isinstance(endpoints, str):
|
||||||
endpoints = [endpoints]
|
endpoints = [endpoints]
|
||||||
handler = eval("self." + str(index_name) + "." + str(item))
|
handler = eval("endpoint_main.%s" % item)
|
||||||
endpoint_name = eval("self." + str(index_name) + "." + str(item) + ".endpoint_name")
|
endpoint_name = eval("endpoint_main.%s.%s" % (item, "endpoint_name"))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
endpoint_methods = eval("self." + str(index_name) + "." + str(item) + ".endpoint_methods")
|
endpoint_methods = eval("endpoint_main.%s.%s" % (item, "endpoint_methods"))
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
endpoint_methods = ['GET']
|
endpoint_methods = ['GET']
|
||||||
self.fhdhr.logger.info("Adding endpoint %s available at %s with %s methods." % (endpoint_name, ",".join(endpoints), ",".join(endpoint_methods)))
|
|
||||||
|
try:
|
||||||
|
endpoint_access_level = eval("endpoint_main.%s.%s" % (item, "endpoint_access_level"))
|
||||||
|
except AttributeError:
|
||||||
|
endpoint_access_level = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
pretty_name = eval("endpoint_main.%s.%s" % (item, "pretty_name"))
|
||||||
|
except AttributeError:
|
||||||
|
pretty_name = endpoint_name
|
||||||
|
|
||||||
|
try:
|
||||||
|
endpoint_category = eval("endpoint_main.%s.%s" % (item, "endpoint_category"))
|
||||||
|
except AttributeError:
|
||||||
|
endpoint_category = index_name
|
||||||
|
|
||||||
|
try:
|
||||||
|
endpoint_default_parameters = eval("endpoint_main.%s.%s" % (item, "endpoint_default_parameters"))
|
||||||
|
except AttributeError:
|
||||||
|
endpoint_default_parameters = {}
|
||||||
|
|
||||||
|
self.fhdhr.logger.debug("Adding endpoint %s available at %s with %s methods." % (endpoint_name, ",".join(endpoints), ",".join(endpoint_methods)))
|
||||||
|
|
||||||
|
if endpoint_category not in list(self.route_list.keys()):
|
||||||
|
self.route_list[endpoint_category] = {}
|
||||||
|
|
||||||
|
if endpoint_name not in list(self.route_list[endpoint_category].keys()):
|
||||||
|
self.route_list[endpoint_category][endpoint_name] = {}
|
||||||
|
self.route_list[endpoint_category][endpoint_name]["name"] = endpoint_name
|
||||||
|
self.route_list[endpoint_category][endpoint_name]["endpoints"] = endpoints
|
||||||
|
self.route_list[endpoint_category][endpoint_name]["endpoint_methods"] = endpoint_methods
|
||||||
|
self.route_list[endpoint_category][endpoint_name]["endpoint_access_level"] = endpoint_access_level
|
||||||
|
self.route_list[endpoint_category][endpoint_name]["endpoint_default_parameters"] = endpoint_default_parameters
|
||||||
|
self.route_list[endpoint_category][endpoint_name]["pretty_name"] = pretty_name
|
||||||
|
self.route_list[endpoint_category][endpoint_name]["endpoint_category"] = endpoint_category
|
||||||
|
|
||||||
for endpoint in endpoints:
|
for endpoint in endpoints:
|
||||||
self.add_endpoint(endpoint=endpoint,
|
self.add_endpoint(endpoint=endpoint,
|
||||||
endpoint_name=endpoint_name,
|
endpoint_name=endpoint_name,
|
||||||
@ -77,7 +205,7 @@ class fHDHR_HTTP_Server():
|
|||||||
methods=endpoint_methods)
|
methods=endpoint_methods)
|
||||||
|
|
||||||
def isapath(self, item):
|
def isapath(self, item):
|
||||||
not_a_page_list = ["fhdhr", "htmlerror", "page_elements"]
|
not_a_page_list = ["fhdhr", "plugin_utils"]
|
||||||
if item in not_a_page_list:
|
if item in not_a_page_list:
|
||||||
return False
|
return False
|
||||||
elif item.startswith("__") and item.endswith("__"):
|
elif item.startswith("__") and item.endswith("__"):
|
||||||
@ -86,15 +214,16 @@ class fHDHR_HTTP_Server():
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def add_endpoint(self, endpoint=None, endpoint_name=None, handler=None, methods=['GET']):
|
def add_endpoint(self, endpoint=None, endpoint_name=None, handler=None, methods=['GET']):
|
||||||
self.app.add_url_rule(endpoint, endpoint_name, handler, methods=methods)
|
self.fhdhr.app.add_url_rule(endpoint, endpoint_name, handler, methods=methods)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
self.http = WSGIServer(self.fhdhr.api.address_tuple,
|
self.http = WSGIServer(self.fhdhr.api.address_tuple,
|
||||||
self.app.wsgi_app,
|
self.fhdhr.app.wsgi_app,
|
||||||
log=self.fhdhr.logger)
|
log=self.fhdhr.logger.logger,
|
||||||
|
error_log=self.fhdhr.logger.logger)
|
||||||
try:
|
try:
|
||||||
self.http.serve_forever()
|
self.http.serve_forever()
|
||||||
except KeyboardInterrupt:
|
self.stop()
|
||||||
self.http.stop()
|
except AttributeError:
|
||||||
|
self.fhdhr.logger.info("HTTP Server Offline")
|
||||||
|
|||||||
@ -1,14 +1,18 @@
|
|||||||
|
|
||||||
from .root_url import Root_URL
|
from .root_url import Root_URL
|
||||||
|
from .startup_tasks import Startup_Tasks
|
||||||
|
|
||||||
from .cluster import Cluster
|
|
||||||
from .settings import Settings
|
from .settings import Settings
|
||||||
from .channels import Channels
|
from .channels import Channels
|
||||||
from .xmltv import xmlTV
|
from .xmltv import xmlTV
|
||||||
from .m3u import M3U
|
from .m3u import M3U
|
||||||
|
from .w3u import W3U
|
||||||
from .epg import EPG
|
from .epg import EPG
|
||||||
from .tuners import Tuners
|
from .tuners import Tuners
|
||||||
from .debug import Debug_JSON
|
from .debug import Debug_JSON
|
||||||
|
from .plugins import Plugins_JSON
|
||||||
|
|
||||||
|
from .route_list import Route_List
|
||||||
|
|
||||||
from .images import Images
|
from .images import Images
|
||||||
|
|
||||||
@ -19,14 +23,18 @@ class fHDHR_API():
|
|||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
|
|
||||||
self.root_url = Root_URL(fhdhr)
|
self.root_url = Root_URL(fhdhr)
|
||||||
|
self.startup_tasks = Startup_Tasks(fhdhr)
|
||||||
|
|
||||||
self.cluster = Cluster(fhdhr)
|
|
||||||
self.settings = Settings(fhdhr)
|
self.settings = Settings(fhdhr)
|
||||||
self.channels = Channels(fhdhr)
|
self.channels = Channels(fhdhr)
|
||||||
self.xmltv = xmlTV(fhdhr)
|
self.xmltv = xmlTV(fhdhr)
|
||||||
self.m3u = M3U(fhdhr)
|
self.m3u = M3U(fhdhr)
|
||||||
|
self.w3u = W3U(fhdhr)
|
||||||
self.epg = EPG(fhdhr)
|
self.epg = EPG(fhdhr)
|
||||||
self.tuners = Tuners(fhdhr)
|
self.tuners = Tuners(fhdhr)
|
||||||
self.debug = Debug_JSON(fhdhr)
|
self.debug = Debug_JSON(fhdhr)
|
||||||
|
self.plugins = Plugins_JSON(fhdhr)
|
||||||
|
|
||||||
|
self.route_list = Route_List(fhdhr)
|
||||||
|
|
||||||
self.images = Images(fhdhr)
|
self.images = Images(fhdhr)
|
||||||
|
|||||||
@ -2,11 +2,16 @@ from flask import request, redirect, Response, abort
|
|||||||
import urllib.parse
|
import urllib.parse
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from fHDHR.tools import channel_sort
|
||||||
|
|
||||||
|
|
||||||
class Channels():
|
class Channels():
|
||||||
endpoints = ["/api/channels"]
|
endpoints = ["/api/channels"]
|
||||||
endpoint_name = "api_channels"
|
endpoint_name = "api_channels"
|
||||||
endpoint_methods = ["GET", "POST"]
|
endpoint_methods = ["GET", "POST"]
|
||||||
|
endpoint_default_parameters = {
|
||||||
|
"method": "get"
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
def __init__(self, fhdhr):
|
||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
@ -19,14 +24,37 @@ class Channels():
|
|||||||
method = request.args.get('method', default=None, type=str)
|
method = request.args.get('method', default=None, type=str)
|
||||||
redirect_url = request.args.get('redirect', default=None, type=str)
|
redirect_url = request.args.get('redirect', default=None, type=str)
|
||||||
|
|
||||||
|
origin_methods = self.fhdhr.origins.valid_origins
|
||||||
|
origin = request.args.get('origin', default=None, type=str)
|
||||||
|
if origin and origin not in origin_methods:
|
||||||
|
return "%s Invalid channels origin" % origin
|
||||||
|
|
||||||
if method == "get":
|
if method == "get":
|
||||||
channels_info = []
|
channels_info = {}
|
||||||
for fhdhr_id in list(self.fhdhr.device.channels.list.keys()):
|
if not origin:
|
||||||
channel_obj = self.fhdhr.device.channels.list[fhdhr_id]
|
origin_list = origin_methods
|
||||||
|
else:
|
||||||
|
origin_list = [origin]
|
||||||
|
|
||||||
|
for origin_item in origin_list:
|
||||||
|
|
||||||
|
channels_info[origin_item] = {}
|
||||||
|
|
||||||
|
for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels(origin=origin_item)]:
|
||||||
|
channel_obj = self.fhdhr.device.channels.list[origin_item][fhdhr_id]
|
||||||
channel_dict = channel_obj.dict.copy()
|
channel_dict = channel_obj.dict.copy()
|
||||||
channel_dict["play_url"] = channel_obj.play_url
|
channel_dict["m3u_url"] = channel_obj.api_m3u_url
|
||||||
channel_dict["stream_url"] = channel_obj.stream_url
|
channel_dict["stream_url"] = channel_obj.api_stream_url
|
||||||
channels_info.append(channel_dict)
|
channels_info[origin_item][channel_obj.number] = channel_dict
|
||||||
|
|
||||||
|
# Sort the channels
|
||||||
|
sorted_channel_list = channel_sort(list(channels_info[origin_item].keys()))
|
||||||
|
sorted_chan_guide = []
|
||||||
|
for channel in sorted_channel_list:
|
||||||
|
sorted_chan_guide.append(channels_info[origin_item][channel])
|
||||||
|
|
||||||
|
channels_info[origin_item] = sorted_chan_guide
|
||||||
|
|
||||||
channels_info_json = json.dumps(channels_info, indent=4)
|
channels_info_json = json.dumps(channels_info, indent=4)
|
||||||
|
|
||||||
return Response(status=200,
|
return Response(status=200,
|
||||||
@ -38,7 +66,7 @@ class Channels():
|
|||||||
channel = request.args.get('channel', default=None, type=str)
|
channel = request.args.get('channel', default=None, type=str)
|
||||||
if not channel:
|
if not channel:
|
||||||
if redirect_url:
|
if redirect_url:
|
||||||
return redirect(redirect_url + "?retmessage=" + urllib.parse.quote("%s Failed" % method))
|
return redirect("%s?retmessage=%s" % (redirect_url, urllib.parse.quote("%s Failed" % method)))
|
||||||
else:
|
else:
|
||||||
return "%s Falied" % method
|
return "%s Falied" % method
|
||||||
|
|
||||||
@ -47,34 +75,34 @@ class Channels():
|
|||||||
channel_method = channel[0]
|
channel_method = channel[0]
|
||||||
channel_number = channel[1:]
|
channel_number = channel[1:]
|
||||||
|
|
||||||
if str(channel_number) not in [str(x) for x in self.fhdhr.device.channels.get_channel_list("number")]:
|
if str(channel_number) not in [str(x) for x in self.fhdhr.device.channels.get_channel_list("number", origin)]:
|
||||||
response = Response("Not Found", status=404)
|
response = Response("Not Found", status=404)
|
||||||
response.headers["X-fHDHR-Error"] = "801 - Unknown Channel"
|
response.headers["X-fHDHR-Error"] = "801 - Unknown Channel"
|
||||||
self.fhdhr.logger.error(response.headers["X-fHDHR-Error"])
|
self.fhdhr.logger.error(response.headers["X-fHDHR-Error"])
|
||||||
abort(response)
|
abort(response)
|
||||||
|
|
||||||
if channel_method == "+":
|
if channel_method == "+":
|
||||||
self.fhdhr.device.channels.set_channel_enablement("number", channel_number, channel_method)
|
self.fhdhr.device.channels.set_channel_enablement("number", channel_number, channel_method, origin)
|
||||||
elif channel_method == "-":
|
elif channel_method == "-":
|
||||||
self.fhdhr.device.channels.set_channel_enablement("number", channel_number, channel_method)
|
self.fhdhr.device.channels.set_channel_enablement("number", channel_number, channel_method, origin)
|
||||||
elif channel_method == "x":
|
elif channel_method == "x":
|
||||||
self.fhdhr.device.channels.set_channel_enablement("number", channel_number, "toggle")
|
self.fhdhr.device.channels.set_channel_enablement("number", channel_number, "toggle", origin)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.fhdhr.logger.warning("Unknown favorite command " + request.args['favorite'])
|
self.fhdhr.logger.warning("Unknown favorite command %s" % request.args['favorite'])
|
||||||
return abort(200, "Not a valid favorite command")
|
return abort(200, "Not a valid favorite command")
|
||||||
|
|
||||||
elif method in ["enable", "disable"]:
|
elif method in ["enable", "disable"]:
|
||||||
channel = request.args.get('channel', default=None, type=str)
|
channel = request.args.get('channel', default=None, type=str)
|
||||||
if channel == "all":
|
if channel == "all":
|
||||||
self.fhdhr.device.channels.set_channel_enablement_all(method)
|
self.fhdhr.device.channels.set_channel_enablement_all(method, origin)
|
||||||
elif not channel or str(channel) not in [str(x) for x in self.fhdhr.device.channels.get_channel_list("number")]:
|
elif not channel or str(channel) not in [str(x) for x in self.fhdhr.device.channels.get_channel_list("number", origin)]:
|
||||||
if redirect_url:
|
if redirect_url:
|
||||||
return redirect(redirect_url + "?retmessage=" + urllib.parse.quote("%s Failed" % method))
|
return redirect("%s?retmessage=%s" % (redirect_url, urllib.parse.quote("%s Failed" % method)))
|
||||||
else:
|
else:
|
||||||
return "%s Falied" % method
|
return "%s Falied" % method
|
||||||
else:
|
else:
|
||||||
self.fhdhr.device.channels.set_channel_enablement("number", channel, method)
|
self.fhdhr.device.channels.set_channel_enablement("number", channel, method, origin)
|
||||||
|
|
||||||
elif method == "update":
|
elif method == "update":
|
||||||
channel_id = request.form.get('id', None)
|
channel_id = request.form.get('id', None)
|
||||||
@ -84,7 +112,12 @@ class Channels():
|
|||||||
if key in ["name", "callsign", "thumbnail"]:
|
if key in ["name", "callsign", "thumbnail"]:
|
||||||
updatedict[key] = str(request.form.get(key))
|
updatedict[key] = str(request.form.get(key))
|
||||||
elif key in ["number"]:
|
elif key in ["number"]:
|
||||||
updatedict[key] = float(request.form.get(key))
|
number = str(request.form.get(key))
|
||||||
|
if "." in number:
|
||||||
|
updatedict["subnumber"] = number.split(".")[1]
|
||||||
|
updatedict["number"] = number.split(".")[0]
|
||||||
|
else:
|
||||||
|
updatedict["number"] = number
|
||||||
elif key in ["enabled"]:
|
elif key in ["enabled"]:
|
||||||
confvalue = request.form.get(key)
|
confvalue = request.form.get(key)
|
||||||
if str(confvalue).lower() in ["false"]:
|
if str(confvalue).lower() in ["false"]:
|
||||||
@ -94,16 +127,44 @@ class Channels():
|
|||||||
updatedict[key] = confvalue
|
updatedict[key] = confvalue
|
||||||
elif key in ["favorite", "HD"]:
|
elif key in ["favorite", "HD"]:
|
||||||
updatedict[key] = int(request.form.get(key))
|
updatedict[key] = int(request.form.get(key))
|
||||||
self.fhdhr.device.channels.set_channel_status("id", channel_id, updatedict)
|
self.fhdhr.device.channels.set_channel_status("id", channel_id, updatedict, origin)
|
||||||
|
|
||||||
|
elif method == "modify":
|
||||||
|
channels_list = json.loads(request.form.get('channels', []))
|
||||||
|
for channel in channels_list:
|
||||||
|
updatedict = {}
|
||||||
|
for key in list(channel.keys()):
|
||||||
|
if key != "id":
|
||||||
|
if key in ["name", "callsign", "thumbnail"]:
|
||||||
|
updatedict[key] = str(channel[key])
|
||||||
|
elif key in ["number"]:
|
||||||
|
number = str(channel[key])
|
||||||
|
if "." in number:
|
||||||
|
updatedict["subnumber"] = number.split(".")[1]
|
||||||
|
updatedict["number"] = number.split(".")[0]
|
||||||
|
else:
|
||||||
|
updatedict["number"] = number
|
||||||
|
elif key in ["enabled"]:
|
||||||
|
confvalue = channel[key]
|
||||||
|
if str(confvalue).lower() in ["false"]:
|
||||||
|
confvalue = False
|
||||||
|
elif str(confvalue).lower() in ["true"]:
|
||||||
|
confvalue = True
|
||||||
|
updatedict[key] = confvalue
|
||||||
|
elif key in ["favorite", "HD"]:
|
||||||
|
updatedict[key] = int(channel[key])
|
||||||
|
else:
|
||||||
|
channel_id = str(channel[key])
|
||||||
|
self.fhdhr.device.channels.set_channel_status("id", channel_id, updatedict, origin)
|
||||||
|
|
||||||
elif method == "scan":
|
elif method == "scan":
|
||||||
self.fhdhr.device.channels.get_channels(forceupdate=True)
|
self.fhdhr.device.channels.get_channels(forceupdate=True, origin=origin)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return "Invalid Method"
|
return "Invalid Method"
|
||||||
|
|
||||||
if redirect_url:
|
if redirect_url:
|
||||||
return redirect(redirect_url + "?retmessage=" + urllib.parse.quote("%s Success" % method))
|
return redirect("%s?retmessage=%s" % (redirect_url, urllib.parse.quote("%s Success" % method)))
|
||||||
else:
|
else:
|
||||||
if method == "scan":
|
if method == "scan":
|
||||||
return redirect('/lineup_status.json')
|
return redirect('/lineup_status.json')
|
||||||
|
|||||||
@ -1,56 +0,0 @@
|
|||||||
from flask import request, redirect, Response
|
|
||||||
import urllib.parse
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
class Cluster():
|
|
||||||
endpoints = ["/api/cluster"]
|
|
||||||
endpoint_name = "api_cluster"
|
|
||||||
endpoint_methods = ["GET", "POST"]
|
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
|
||||||
self.fhdhr = fhdhr
|
|
||||||
|
|
||||||
def __call__(self, *args):
|
|
||||||
return self.get(*args)
|
|
||||||
|
|
||||||
def get(self, *args):
|
|
||||||
|
|
||||||
method = request.args.get('method', default="get", type=str)
|
|
||||||
location = request.args.get("location", default=None, type=str)
|
|
||||||
redirect_url = request.args.get('redirect', default=None, type=str)
|
|
||||||
|
|
||||||
if method == "get":
|
|
||||||
jsoncluster = self.fhdhr.device.cluster.cluster()
|
|
||||||
cluster_json = json.dumps(jsoncluster, indent=4)
|
|
||||||
|
|
||||||
return Response(status=200,
|
|
||||||
response=cluster_json,
|
|
||||||
mimetype='application/json')
|
|
||||||
|
|
||||||
elif method == "scan":
|
|
||||||
self.fhdhr.device.ssdp.m_search()
|
|
||||||
|
|
||||||
elif method == 'add':
|
|
||||||
self.fhdhr.device.cluster.add(location)
|
|
||||||
elif method == 'del':
|
|
||||||
self.fhdhr.device.cluster.remove(location)
|
|
||||||
|
|
||||||
elif method == 'sync':
|
|
||||||
self.fhdhr.device.cluster.sync(location)
|
|
||||||
|
|
||||||
elif method == 'leave':
|
|
||||||
self.fhdhr.device.cluster.leave()
|
|
||||||
elif method == 'disconnect':
|
|
||||||
self.fhdhr.device.cluster.disconnect()
|
|
||||||
|
|
||||||
elif method == 'alive':
|
|
||||||
self.fhdhr.device.ssdp.do_alive(forcealive=True)
|
|
||||||
|
|
||||||
else:
|
|
||||||
return "Invalid Method"
|
|
||||||
|
|
||||||
if redirect_url:
|
|
||||||
return redirect(redirect_url + "?retmessage=" + urllib.parse.quote("%s Success" % method))
|
|
||||||
else:
|
|
||||||
return "%s Success" % method
|
|
||||||
@ -19,11 +19,16 @@ class Debug_JSON():
|
|||||||
|
|
||||||
debugjson = {
|
debugjson = {
|
||||||
"base_url": base_url,
|
"base_url": base_url,
|
||||||
"total channels": len(self.fhdhr.device.channels.list),
|
|
||||||
"tuner status": self.fhdhr.device.tuners.status(),
|
|
||||||
}
|
}
|
||||||
cluster_json = json.dumps(debugjson, indent=4)
|
|
||||||
|
for origin in list(self.fhdhr.origins.origins_dict.keys()):
|
||||||
|
debugjson[origin] = {
|
||||||
|
"tuner status": self.fhdhr.device.tuners.status(origin),
|
||||||
|
"total channels": len(list(self.fhdhr.device.channels.list[origin].keys()))
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_json = json.dumps(debugjson, indent=4)
|
||||||
|
|
||||||
return Response(status=200,
|
return Response(status=200,
|
||||||
response=cluster_json,
|
response=debug_json,
|
||||||
mimetype='application/json')
|
mimetype='application/json')
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
from flask import Response, request, redirect
|
from flask import Response, request, redirect
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import json
|
import json
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from fHDHR.tools import humanized_time, channel_sort
|
||||||
|
|
||||||
|
|
||||||
class EPG():
|
class EPG():
|
||||||
@ -20,25 +23,95 @@ class EPG():
|
|||||||
method = request.args.get('method', default="get", type=str)
|
method = request.args.get('method', default="get", type=str)
|
||||||
|
|
||||||
source = request.args.get('source', default=self.fhdhr.config.dict["epg"]["def_method"], type=str)
|
source = request.args.get('source', default=self.fhdhr.config.dict["epg"]["def_method"], type=str)
|
||||||
if source not in self.fhdhr.config.dict["main"]["valid_epg_methods"]:
|
if source not in list(self.fhdhr.config.dict["epg"]["valid_methods"].keys()):
|
||||||
return "%s Invalid xmltv method" % source
|
return "%s Invalid epg method" % source
|
||||||
|
|
||||||
redirect_url = request.args.get('redirect', default=None, type=str)
|
redirect_url = request.args.get('redirect', default=None, type=str)
|
||||||
|
|
||||||
if method == "get":
|
if method == "get":
|
||||||
|
|
||||||
epgdict = self.fhdhr.device.epg.get_epg(source)
|
epgdict = self.fhdhr.device.epg.get_epg(source)
|
||||||
|
if source in self.fhdhr.origins.valid_origins:
|
||||||
epgdict = epgdict.copy()
|
epgdict = epgdict.copy()
|
||||||
for c in list(epgdict.keys()):
|
for c in list(epgdict.keys()):
|
||||||
chan_obj = self.fhdhr.device.channels.get_channel_obj("origin_id", epgdict[c]["id"])
|
chan_obj = self.fhdhr.device.channels.get_channel_obj("origin_id", epgdict[c]["id"], source)
|
||||||
epgdict[chan_obj.dict["number"]] = epgdict.pop(c)
|
epgdict[chan_obj.number] = epgdict.pop(c)
|
||||||
epgdict[chan_obj.dict["number"]]["name"] = chan_obj.dict["name"]
|
epgdict[chan_obj.number]["name"] = chan_obj.dict["name"]
|
||||||
epgdict[chan_obj.dict["number"]]["callsign"] = chan_obj.dict["callsign"]
|
epgdict[chan_obj.number]["callsign"] = chan_obj.dict["callsign"]
|
||||||
epgdict[chan_obj.dict["number"]]["number"] = chan_obj.dict["number"]
|
epgdict[chan_obj.number]["number"] = chan_obj.number
|
||||||
epgdict[chan_obj.dict["number"]]["id"] = chan_obj.dict["origin_id"]
|
epgdict[chan_obj.number]["id"] = chan_obj.dict["origin_id"]
|
||||||
epgdict[chan_obj.dict["number"]]["thumbnail"] = chan_obj.thumbnail
|
epgdict[chan_obj.number]["thumbnail"] = chan_obj.thumbnail
|
||||||
|
|
||||||
epg_json = json.dumps(epgdict, indent=4)
|
# Sort the channels
|
||||||
|
sorted_channel_list = channel_sort(list(epgdict.keys()))
|
||||||
|
sorted_chan_guide = {}
|
||||||
|
for channel in sorted_channel_list:
|
||||||
|
sorted_chan_guide[channel] = epgdict[channel]
|
||||||
|
|
||||||
|
epg_json = json.dumps(sorted_chan_guide, indent=4)
|
||||||
|
|
||||||
|
return Response(status=200,
|
||||||
|
response=epg_json,
|
||||||
|
mimetype='application/json')
|
||||||
|
|
||||||
|
elif method == "current":
|
||||||
|
|
||||||
|
nowtime = datetime.datetime.utcnow().timestamp()
|
||||||
|
|
||||||
|
chan_guide_list = []
|
||||||
|
|
||||||
|
whatson = self.fhdhr.device.epg.whats_on_allchans(source)
|
||||||
|
|
||||||
|
# Sort the channels
|
||||||
|
sorted_channel_list = channel_sort(list(whatson.keys()))
|
||||||
|
sorted_chan_guide = {}
|
||||||
|
for channel in sorted_channel_list:
|
||||||
|
sorted_chan_guide[channel] = whatson[channel]
|
||||||
|
|
||||||
|
for channel in list(sorted_chan_guide.keys()):
|
||||||
|
if sorted_chan_guide[channel]["listing"][0]["time_end"]:
|
||||||
|
remaining_time = humanized_time(sorted_chan_guide[channel]["listing"][0]["time_end"] - nowtime)
|
||||||
|
else:
|
||||||
|
remaining_time = "N/A"
|
||||||
|
|
||||||
|
chan_dict = {
|
||||||
|
"name": sorted_chan_guide[channel]["name"],
|
||||||
|
"number": sorted_chan_guide[channel]["number"],
|
||||||
|
"chan_thumbnail": sorted_chan_guide[channel]["thumbnail"],
|
||||||
|
"listing_title": sorted_chan_guide[channel]["listing"][0]["title"],
|
||||||
|
"listing_thumbnail": sorted_chan_guide[channel]["listing"][0]["thumbnail"],
|
||||||
|
"listing_description": sorted_chan_guide[channel]["listing"][0]["description"],
|
||||||
|
"listing_remaining_time": str(remaining_time)
|
||||||
|
}
|
||||||
|
|
||||||
|
for time_item in ["time_start", "time_end"]:
|
||||||
|
|
||||||
|
if not sorted_chan_guide[channel]["listing"][0][time_item]:
|
||||||
|
chan_dict["listing_%s" % time_item] = "N/A"
|
||||||
|
elif str(sorted_chan_guide[channel]["listing"][0][time_item]).endswith(tuple(["+0000", "+00:00"])):
|
||||||
|
chan_dict["listing_%s" % time_item] = str(sorted_chan_guide[channel]["listing"][0][time_item])
|
||||||
|
else:
|
||||||
|
chan_dict["listing_%s" % time_item] = str(datetime.datetime.fromtimestamp(sorted_chan_guide[channel]["listing"][0][time_item]))
|
||||||
|
|
||||||
|
if source in self.fhdhr.origins.valid_origins:
|
||||||
|
chan_obj = self.fhdhr.device.channels.get_channel_obj("origin_id", sorted_chan_guide[channel]["id"], source)
|
||||||
|
|
||||||
|
chan_dict["name"] = chan_obj.dict["name"]
|
||||||
|
chan_dict["number"] = chan_obj.number
|
||||||
|
chan_dict["chan_thumbnail"] = chan_obj.thumbnail
|
||||||
|
chan_dict["enabled"] = chan_obj.dict["enabled"]
|
||||||
|
chan_dict["m3u_url"] = chan_obj.api_m3u_url
|
||||||
|
|
||||||
|
chan_dict["listing_thumbnail"] = chan_dict["listing_thumbnail"] or chan_obj.thumbnail
|
||||||
|
else:
|
||||||
|
if not chan_dict["listing_thumbnail"]:
|
||||||
|
chan_dict["listing_thumbnail"] = chan_dict["chan_thumbnail"]
|
||||||
|
if not chan_dict["listing_thumbnail"]:
|
||||||
|
chan_dict["listing_thumbnail"] = "/api/images?method=generate&type=channel&message=%s" % chan_dict["number"]
|
||||||
|
|
||||||
|
chan_guide_list.append(chan_dict)
|
||||||
|
|
||||||
|
epg_json = json.dumps(chan_guide_list, indent=4)
|
||||||
|
|
||||||
return Response(status=200,
|
return Response(status=200,
|
||||||
response=epg_json,
|
response=epg_json,
|
||||||
@ -54,6 +127,6 @@ class EPG():
|
|||||||
return "%s Invalid Method" % method
|
return "%s Invalid Method" % method
|
||||||
|
|
||||||
if redirect_url:
|
if redirect_url:
|
||||||
return redirect(redirect_url + "?retmessage=" + urllib.parse.quote("%s Success" % method))
|
return redirect("%s?retmessage=%s" % (redirect_url, urllib.parse.quote("%s Success" % method)))
|
||||||
else:
|
else:
|
||||||
return "%s Success" % method
|
return "%s Success" % method
|
||||||
|
|||||||
@ -5,6 +5,11 @@ class Images():
|
|||||||
endpoints = ["/api/images"]
|
endpoints = ["/api/images"]
|
||||||
endpoint_name = "api_images"
|
endpoint_name = "api_images"
|
||||||
endpoint_methods = ["GET", "POST"]
|
endpoint_methods = ["GET", "POST"]
|
||||||
|
endpoint_default_parameters = {
|
||||||
|
"method": "generate",
|
||||||
|
"type": "content",
|
||||||
|
"message": "Internal Image Handling"
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
def __init__(self, fhdhr):
|
||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
@ -26,7 +31,7 @@ class Images():
|
|||||||
|
|
||||||
elif method == "get":
|
elif method == "get":
|
||||||
source = request.args.get('source', default=self.fhdhr.config.dict["epg"]["method"], type=str)
|
source = request.args.get('source', default=self.fhdhr.config.dict["epg"]["method"], type=str)
|
||||||
if source in self.fhdhr.config.dict["main"]["valid_epg_methods"]:
|
if source in list(self.fhdhr.config.dict["epg"]["valid_methods"].keys()):
|
||||||
image_type = request.args.get('type', default="content", type=str)
|
image_type = request.args.get('type', default="content", type=str)
|
||||||
if image_type in ["content", "channel"]:
|
if image_type in ["content", "channel"]:
|
||||||
image_id = request.args.get('id', default=None, type=str)
|
image_id = request.args.get('id', default=None, type=str)
|
||||||
|
|||||||
@ -2,6 +2,8 @@ from flask import Response, request, redirect
|
|||||||
import urllib.parse
|
import urllib.parse
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
|
||||||
|
from fHDHR.tools import channel_sort
|
||||||
|
|
||||||
|
|
||||||
class M3U():
|
class M3U():
|
||||||
endpoints = ["/api/m3u", "/api/channels.m3u"]
|
endpoints = ["/api/m3u", "/api/channels.m3u"]
|
||||||
@ -24,6 +26,11 @@ class M3U():
|
|||||||
|
|
||||||
if method == "get":
|
if method == "get":
|
||||||
|
|
||||||
|
origin_methods = self.fhdhr.origins.valid_origins
|
||||||
|
origin = request.args.get('origin', default=None, type=str)
|
||||||
|
if origin and origin not in origin_methods:
|
||||||
|
return "%s Invalid channels origin" % origin
|
||||||
|
|
||||||
FORMAT_DESCRIPTOR = "#EXTM3U"
|
FORMAT_DESCRIPTOR = "#EXTM3U"
|
||||||
RECORD_MARKER = "#EXTINF"
|
RECORD_MARKER = "#EXTINF"
|
||||||
|
|
||||||
@ -31,24 +38,41 @@ class M3U():
|
|||||||
|
|
||||||
xmltvurl = ('%s/api/xmltv' % base_url)
|
xmltvurl = ('%s/api/xmltv' % base_url)
|
||||||
|
|
||||||
fakefile.write(
|
fakefile.write("%s url-tvg=\"%s\" x-tvg-url=\"%s\"\n" % (FORMAT_DESCRIPTOR, xmltvurl, xmltvurl))
|
||||||
"%s\n" % (
|
|
||||||
FORMAT_DESCRIPTOR + " " +
|
|
||||||
"url-tvg=\"" + xmltvurl + "\"" + " " +
|
|
||||||
"x-tvg-url=\"" + xmltvurl + "\"")
|
|
||||||
)
|
|
||||||
|
|
||||||
channel_items = []
|
channel_items = []
|
||||||
|
|
||||||
|
if origin:
|
||||||
if channel == "all":
|
if channel == "all":
|
||||||
fileName = "channels.m3u"
|
fileName = "channels.m3u"
|
||||||
for fhdhr_id in list(self.fhdhr.device.channels.list.keys()):
|
for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels(origin)]:
|
||||||
channel_obj = self.fhdhr.device.channels.list[fhdhr_id]
|
channel_obj = self.fhdhr.device.channels.get_channel_obj("id", fhdhr_id, origin)
|
||||||
if channel_obj.enabled:
|
if channel_obj.enabled:
|
||||||
channel_items.append(channel_obj)
|
channel_items.append(channel_obj)
|
||||||
elif str(channel) in [str(x) for x in self.fhdhr.device.channels.get_channel_list("number")]:
|
elif str(channel) in [str(x) for x in self.fhdhr.device.channels.get_channel_list("number", origin)]:
|
||||||
channel_obj = self.fhdhr.device.channels.get_channel_obj("number", channel)
|
channel_obj = self.fhdhr.device.channels.get_channel_obj("number", channel, origin)
|
||||||
fileName = str(channel_obj.number) + ".m3u"
|
fileName = "%s.m3u" % channel_obj.number
|
||||||
|
if channel_obj.enabled:
|
||||||
|
channel_items.append(channel_obj)
|
||||||
|
else:
|
||||||
|
return "Channel Disabled"
|
||||||
|
elif channel != "all" and str(channel) in [str(x) for x in self.fhdhr.device.channels.get_channel_list("id", origin)]:
|
||||||
|
channel_obj = self.fhdhr.device.channels.get_channel_obj("id", channel, origin)
|
||||||
|
fileName = "%s.m3u" % channel_obj.number
|
||||||
|
if channel_obj.enabled:
|
||||||
|
channel_items.append(channel_obj)
|
||||||
|
else:
|
||||||
|
return "Channel Disabled"
|
||||||
|
elif not origin and channel == "all":
|
||||||
|
fileName = "channels.m3u"
|
||||||
|
for origin in list(self.fhdhr.origins.origins_dict.keys()):
|
||||||
|
for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels(origin)]:
|
||||||
|
channel_obj = self.fhdhr.device.channels.get_channel_obj("id", fhdhr_id, origin)
|
||||||
|
if channel_obj.enabled:
|
||||||
|
channel_items.append(channel_obj)
|
||||||
|
elif not origin and channel != "all" and str(channel) in [str(x) for x in self.fhdhr.device.channels.get_channel_list("id")]:
|
||||||
|
channel_obj = self.fhdhr.device.channels.get_channel_obj("id", channel)
|
||||||
|
fileName = "%s.m3u" % channel_obj.number
|
||||||
if channel_obj.enabled:
|
if channel_obj.enabled:
|
||||||
channel_items.append(channel_obj)
|
channel_items.append(channel_obj)
|
||||||
else:
|
else:
|
||||||
@ -56,6 +80,7 @@ class M3U():
|
|||||||
else:
|
else:
|
||||||
return "Invalid Channel"
|
return "Invalid Channel"
|
||||||
|
|
||||||
|
channels_info = {}
|
||||||
for channel_obj in channel_items:
|
for channel_obj in channel_items:
|
||||||
|
|
||||||
if self.fhdhr.config.dict["epg"]["images"] == "proxy" or not channel_obj.thumbnail:
|
if self.fhdhr.config.dict["epg"]["images"] == "proxy" or not channel_obj.thumbnail:
|
||||||
@ -64,26 +89,39 @@ class M3U():
|
|||||||
else:
|
else:
|
||||||
logourl = channel_obj.thumbnail
|
logourl = channel_obj.thumbnail
|
||||||
|
|
||||||
fakefile.write(
|
channels_info[channel_obj.number] = {
|
||||||
"%s\n" % (
|
"channelID": str(channel_obj.dict['origin_id']),
|
||||||
RECORD_MARKER + ":0" + " " +
|
"tvg-chno": str(channel_obj.number),
|
||||||
"channelID=\"" + str(channel_obj.dict['origin_id']) + "\" " +
|
"tvg-name": str(channel_obj.dict['name']),
|
||||||
"tvg-chno=\"" + str(channel_obj.dict['number']) + "\" " +
|
"tvg-id": str(channel_obj.number),
|
||||||
"tvg-name=\"" + str(channel_obj.dict['name']) + "\" " +
|
"tvg-logo": logourl,
|
||||||
"tvg-id=\"" + str(channel_obj.dict['number']) + "\" " +
|
"group-title": channel_obj.origin,
|
||||||
"tvg-logo=\"" + logourl + "\" " +
|
"group-titleb": str(channel_obj.dict['name']),
|
||||||
"group-title=\"" + self.fhdhr.config.dict["fhdhr"]["friendlyname"] + "\"," + str(channel_obj.dict['name']))
|
"stream_url": "%s%s" % (base_url, channel_obj.api_stream_url)
|
||||||
)
|
}
|
||||||
|
|
||||||
fakefile.write("%s%s\n" % (base_url, channel_obj.stream_url))
|
# Sort the channels
|
||||||
|
sorted_channel_list = channel_sort(list(channels_info.keys()))
|
||||||
|
sorted_chan_guide = []
|
||||||
|
for channel in sorted_channel_list:
|
||||||
|
sorted_chan_guide.append(channels_info[channel])
|
||||||
|
|
||||||
|
for channel_item_dict in sorted_chan_guide:
|
||||||
|
m3ustring = "%s:0 " % (RECORD_MARKER)
|
||||||
|
for chan_key in list(channel_item_dict.keys()):
|
||||||
|
if not chan_key.startswith(tuple(["group-title", "stream_url"])):
|
||||||
|
m3ustring += "%s=\"%s\" " % (chan_key, channel_item_dict[chan_key])
|
||||||
|
m3ustring += "group-title=\"%s\",%s\n" % (channel_item_dict["group-title"], channel_item_dict["group-titleb"])
|
||||||
|
m3ustring += "%s\n" % channel_item_dict["stream_url"]
|
||||||
|
fakefile.write(m3ustring)
|
||||||
|
|
||||||
channels_m3u = fakefile.getvalue()
|
channels_m3u = fakefile.getvalue()
|
||||||
|
|
||||||
resp = Response(status=200, response=channels_m3u, mimetype='audio/x-mpegurl')
|
resp = Response(status=200, response=channels_m3u, mimetype='audio/x-mpegurl')
|
||||||
resp.headers["content-disposition"] = "attachment; filename=" + fileName
|
resp.headers["content-disposition"] = "attachment; filename=%s" % fileName
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
if redirect_url:
|
if redirect_url:
|
||||||
return redirect(redirect_url + "?retmessage=" + urllib.parse.quote("%s Success" % method))
|
return redirect("%s?retmessage=%s" % (redirect_url, urllib.parse.quote("%s Success" % method)))
|
||||||
else:
|
else:
|
||||||
return "%s Success" % method
|
return "%s Success" % method
|
||||||
|
|||||||
30
fHDHR_web/api/plugins.py
Normal file
30
fHDHR_web/api/plugins.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from flask import Response
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class Plugins_JSON():
|
||||||
|
endpoints = ["/api/plugins"]
|
||||||
|
endpoint_name = "api_plugins"
|
||||||
|
endpoint_methods = ["GET", "POST"]
|
||||||
|
|
||||||
|
def __init__(self, fhdhr):
|
||||||
|
self.fhdhr = fhdhr
|
||||||
|
|
||||||
|
def __call__(self, *args):
|
||||||
|
return self.get(*args)
|
||||||
|
|
||||||
|
def get(self, *args):
|
||||||
|
|
||||||
|
pluginsjson = {}
|
||||||
|
|
||||||
|
for plugin in list(self.fhdhr.plugins.plugins.keys()):
|
||||||
|
pluginsjson[plugin] = {
|
||||||
|
"name": plugin,
|
||||||
|
"manifest": self.fhdhr.plugins.plugins[plugin].manifest
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins_json = json.dumps(pluginsjson, indent=4)
|
||||||
|
|
||||||
|
return Response(status=200,
|
||||||
|
response=plugins_json,
|
||||||
|
mimetype='application/json')
|
||||||
@ -1,4 +1,4 @@
|
|||||||
from flask import redirect, request
|
from flask import redirect
|
||||||
|
|
||||||
|
|
||||||
class Root_URL():
|
class Root_URL():
|
||||||
@ -13,20 +13,4 @@ class Root_URL():
|
|||||||
return self.get(*args)
|
return self.get(*args)
|
||||||
|
|
||||||
def get(self, *args):
|
def get(self, *args):
|
||||||
|
|
||||||
user_agent = request.headers.get('User-Agent')
|
|
||||||
|
|
||||||
# Client Devices Discovering Device Information
|
|
||||||
if not user_agent or str(user_agent).lower().startswith("plexmediaserver"):
|
|
||||||
|
|
||||||
# Plex Remote Media Grabber redirect
|
|
||||||
if self.fhdhr.config.dict["rmg"]["enabled"] and str(user_agent).lower().startswith("plexmediaserver"):
|
|
||||||
return redirect("/rmg")
|
|
||||||
|
|
||||||
# Client Device is looking for HDHR type device
|
|
||||||
else:
|
|
||||||
return redirect("/hdhr/device.xml")
|
|
||||||
|
|
||||||
# Anything Else is likely a Web Browser
|
|
||||||
else:
|
|
||||||
return redirect("/index")
|
return redirect("/index")
|
||||||
|
|||||||
37
fHDHR_web/api/route_list.py
Normal file
37
fHDHR_web/api/route_list.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from flask import Response, request, redirect, session
|
||||||
|
import urllib.parse
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class Route_List():
|
||||||
|
endpoints = ["/api/routes"]
|
||||||
|
endpoint_name = "api_routes"
|
||||||
|
endpoint_methods = ["GET", "POST"]
|
||||||
|
|
||||||
|
def __init__(self, fhdhr):
|
||||||
|
self.fhdhr = fhdhr
|
||||||
|
|
||||||
|
def __call__(self, *args):
|
||||||
|
return self.get(*args)
|
||||||
|
|
||||||
|
def get(self, *args):
|
||||||
|
|
||||||
|
method = request.args.get('method', default="get", type=str)
|
||||||
|
|
||||||
|
redirect_url = request.args.get('redirect', default=None, type=str)
|
||||||
|
|
||||||
|
if method == "get":
|
||||||
|
|
||||||
|
return_json = json.dumps(session["route_list"], indent=4)
|
||||||
|
|
||||||
|
return Response(status=200,
|
||||||
|
response=return_json,
|
||||||
|
mimetype='application/json')
|
||||||
|
|
||||||
|
else:
|
||||||
|
return "%s Invalid Method" % method
|
||||||
|
|
||||||
|
if redirect_url:
|
||||||
|
return redirect("%s?retmessage=%s" % (redirect_url, urllib.parse.quote("%s Success" % method)))
|
||||||
|
else:
|
||||||
|
return "%s Success" % method
|
||||||
@ -1,5 +1,8 @@
|
|||||||
from flask import request, redirect
|
from flask import request, redirect, Response, session
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
class Settings():
|
class Settings():
|
||||||
@ -10,6 +13,9 @@ class Settings():
|
|||||||
def __init__(self, fhdhr):
|
def __init__(self, fhdhr):
|
||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
|
|
||||||
|
self.restart_url = "/api/settings?method=restart_actual"
|
||||||
|
self.restart_sleep = 5
|
||||||
|
|
||||||
def __call__(self, *args):
|
def __call__(self, *args):
|
||||||
return self.get(*args)
|
return self.get(*args)
|
||||||
|
|
||||||
@ -18,23 +24,50 @@ class Settings():
|
|||||||
method = request.args.get('method', default="get", type=str)
|
method = request.args.get('method', default="get", type=str)
|
||||||
redirect_url = request.args.get('redirect', default=None, type=str)
|
redirect_url = request.args.get('redirect', default=None, type=str)
|
||||||
|
|
||||||
if method == "update":
|
if method == "get":
|
||||||
|
web_settings_dict = {}
|
||||||
|
for config_section in list(self.fhdhr.config.conf_default.keys()):
|
||||||
|
web_settings_dict[config_section] = {}
|
||||||
|
|
||||||
|
for config_item in list(self.fhdhr.config.conf_default[config_section].keys()):
|
||||||
|
web_settings_dict[config_section][config_item] = {
|
||||||
|
"value": self.fhdhr.config.dict[config_section][config_item],
|
||||||
|
}
|
||||||
|
if self.fhdhr.config.conf_default[config_section][config_item]["config_web_hidden"]:
|
||||||
|
web_settings_dict[config_section][config_item]["value"] = "***********"
|
||||||
|
|
||||||
|
return_json = json.dumps(web_settings_dict, indent=4)
|
||||||
|
|
||||||
|
return Response(status=200,
|
||||||
|
response=return_json,
|
||||||
|
mimetype='application/json')
|
||||||
|
|
||||||
|
elif method == "update":
|
||||||
config_section = request.form.get('config_section', None)
|
config_section = request.form.get('config_section', None)
|
||||||
config_name = request.form.get('config_name', None)
|
config_name = request.form.get('config_name', None)
|
||||||
config_value = request.form.get('config_value', None)
|
config_value = request.form.get('config_value', None)
|
||||||
|
|
||||||
if not config_section or not config_name or not config_value:
|
if not config_section or not config_name or not config_value:
|
||||||
if redirect_url:
|
if redirect_url:
|
||||||
return redirect(redirect_url + "?retmessage=" + urllib.parse.quote("%s Failed" % method))
|
return redirect("%s?retmessage=%s" % (redirect_url, urllib.parse.quote("%s Failed" % method)))
|
||||||
else:
|
else:
|
||||||
return "%s Falied" % method
|
return "%s Falied" % method
|
||||||
|
|
||||||
if config_section == "origin":
|
self.fhdhr.config.write(config_name, config_value, config_section)
|
||||||
config_section = self.fhdhr.config.dict["main"]["dictpopname"]
|
|
||||||
|
|
||||||
self.fhdhr.config.write(config_section, config_name, config_value)
|
elif method == "restart":
|
||||||
|
restart_thread = threading.Thread(target=self.restart_thread)
|
||||||
|
restart_thread.start()
|
||||||
|
return redirect("%s?retmessage=%s" % (redirect_url, urllib.parse.quote("Restarting in %s seconds" % self.restart_sleep)))
|
||||||
|
|
||||||
|
elif method == "restart_actual":
|
||||||
|
session["restart"] = True
|
||||||
|
|
||||||
if redirect_url:
|
if redirect_url:
|
||||||
return redirect(redirect_url + "?retmessage=" + urllib.parse.quote("%s Success" % method))
|
return redirect("%s?retmessage=%s" % (redirect_url, urllib.parse.quote("%s Success" % method)))
|
||||||
else:
|
else:
|
||||||
return "%s Success" % method
|
return "%s Success" % method
|
||||||
|
|
||||||
|
def restart_thread(self):
|
||||||
|
time.sleep(self.restart_sleep)
|
||||||
|
self.fhdhr.api.get(self.restart_url)
|
||||||
|
|||||||
39
fHDHR_web/api/startup_tasks.py
Normal file
39
fHDHR_web/api/startup_tasks.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
|
||||||
|
class Startup_Tasks():
|
||||||
|
endpoints = ["/api/startup_tasks"]
|
||||||
|
endpoint_name = "api_startup_tasks"
|
||||||
|
endpoint_methods = ["GET", "POST"]
|
||||||
|
|
||||||
|
def __init__(self, fhdhr):
|
||||||
|
self.fhdhr = fhdhr
|
||||||
|
|
||||||
|
self.epg_update_url = "/api/epg?method=update"
|
||||||
|
self.channel_update_url = "/api/channels?method=scan"
|
||||||
|
|
||||||
|
def __call__(self, *args):
|
||||||
|
return self.get(*args)
|
||||||
|
|
||||||
|
def get(self, *args):
|
||||||
|
|
||||||
|
self.fhdhr.logger.info("Running Startup Tasks.")
|
||||||
|
|
||||||
|
# Hit Channel Update API
|
||||||
|
haseverscanned = self.fhdhr.db.get_fhdhr_value("channels", "scanned_time")
|
||||||
|
updatechannels = False
|
||||||
|
if not haseverscanned:
|
||||||
|
updatechannels = True
|
||||||
|
elif self.fhdhr.config.dict["fhdhr"]["chanscan_on_start"]:
|
||||||
|
updatechannels = True
|
||||||
|
|
||||||
|
if updatechannels:
|
||||||
|
for origin in list(self.fhdhr.origins.origins_dict.keys()):
|
||||||
|
self.fhdhr.api.get("%s&origin=%s" % (self.channel_update_url, origin))
|
||||||
|
|
||||||
|
# Hit EPG Update API
|
||||||
|
for epg_method in self.fhdhr.device.epg.epg_methods:
|
||||||
|
self.fhdhr.api.get("%s&source=%s" % (self.epg_update_url, epg_method))
|
||||||
|
|
||||||
|
self.fhdhr.logger.info("Startup Tasks Complete.")
|
||||||
|
|
||||||
|
return "Success"
|
||||||
@ -1,6 +1,6 @@
|
|||||||
from flask import Response, request, redirect, abort, stream_with_context
|
from flask import Response, request, redirect, abort, stream_with_context, session
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import uuid
|
import json
|
||||||
|
|
||||||
from fHDHR.exceptions import TunerError
|
from fHDHR.exceptions import TunerError
|
||||||
|
|
||||||
@ -9,6 +9,9 @@ class Tuners():
|
|||||||
endpoints = ["/api/tuners"]
|
endpoints = ["/api/tuners"]
|
||||||
endpoint_name = "api_tuners"
|
endpoint_name = "api_tuners"
|
||||||
endpoint_methods = ["GET", "POST"]
|
endpoint_methods = ["GET", "POST"]
|
||||||
|
endpoint_default_parameters = {
|
||||||
|
"method": "status"
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
def __init__(self, fhdhr):
|
||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
@ -22,36 +25,66 @@ class Tuners():
|
|||||||
|
|
||||||
accessed_url = request.args.get('accessed', default=request.url, type=str)
|
accessed_url = request.args.get('accessed', default=request.url, type=str)
|
||||||
|
|
||||||
method = request.args.get('method', default=self.fhdhr.config.dict["fhdhr"]["stream_type"], type=str)
|
method = request.args.get('method', default=self.fhdhr.config.dict["streaming"]["method"], type=str)
|
||||||
|
|
||||||
tuner_number = request.args.get('tuner', None, type=str)
|
tuner_number = request.args.get('tuner', default=None, type=str)
|
||||||
|
|
||||||
redirect_url = request.args.get('redirect', default=None, type=str)
|
redirect_url = request.args.get('redirect', default=None, type=str)
|
||||||
|
|
||||||
if method in ["direct", "ffmpeg", "vlc"]:
|
origin_methods = self.fhdhr.origins.valid_origins
|
||||||
|
origin = request.args.get('origin', default=None, type=str)
|
||||||
|
if origin and origin not in origin_methods:
|
||||||
|
return "%s Invalid channels origin" % origin
|
||||||
|
|
||||||
|
if method == "stream":
|
||||||
|
|
||||||
channel_number = request.args.get('channel', None, type=str)
|
channel_number = request.args.get('channel', None, type=str)
|
||||||
if not channel_number:
|
if not channel_number:
|
||||||
return "Missing Channel"
|
return "Missing Channel"
|
||||||
|
|
||||||
if str(channel_number) not in [str(x) for x in self.fhdhr.device.channels.get_channel_list("number")]:
|
if origin:
|
||||||
|
|
||||||
|
if str(channel_number) in [str(x) for x in self.fhdhr.device.channels.get_channel_list("number", origin)]:
|
||||||
|
chan_obj = self.fhdhr.device.channels.get_channel_obj("number", channel_number, origin)
|
||||||
|
elif str(channel_number) in [str(x) for x in self.fhdhr.device.channels.get_channel_list("id", origin)]:
|
||||||
|
chan_obj = self.fhdhr.device.channels.get_channel_obj("id", channel_number, origin)
|
||||||
|
else:
|
||||||
response = Response("Not Found", status=404)
|
response = Response("Not Found", status=404)
|
||||||
response.headers["X-fHDHR-Error"] = "801 - Unknown Channel"
|
response.headers["X-fHDHR-Error"] = "801 - Unknown Channel"
|
||||||
self.fhdhr.logger.error(response.headers["X-fHDHR-Error"])
|
self.fhdhr.logger.error(response.headers["X-fHDHR-Error"])
|
||||||
abort(response)
|
abort(response)
|
||||||
|
|
||||||
channel_dict = self.fhdhr.device.channels.get_channel_dict("number", channel_number)
|
else:
|
||||||
if not channel_dict["enabled"]:
|
|
||||||
|
if str(channel_number) in [str(x) for x in self.fhdhr.device.channels.get_channel_list("id")]:
|
||||||
|
chan_obj = self.fhdhr.device.channels.get_channel_obj("id", channel_number)
|
||||||
|
else:
|
||||||
|
response = Response("Not Found", status=404)
|
||||||
|
response.headers["X-fHDHR-Error"] = "801 - Unknown Channel"
|
||||||
|
self.fhdhr.logger.error(response.headers["X-fHDHR-Error"])
|
||||||
|
abort(response)
|
||||||
|
|
||||||
|
if not chan_obj.dict["enabled"]:
|
||||||
response = Response("Service Unavailable", status=503)
|
response = Response("Service Unavailable", status=503)
|
||||||
response.headers["X-fHDHR-Error"] = str("806 - Tune Failed")
|
response.headers["X-fHDHR-Error"] = str("806 - Tune Failed")
|
||||||
self.fhdhr.logger.error(response.headers["X-fHDHR-Error"])
|
self.fhdhr.logger.error(response.headers["X-fHDHR-Error"])
|
||||||
abort(response)
|
abort(response)
|
||||||
|
|
||||||
|
origin = chan_obj.origin
|
||||||
|
channel_number = chan_obj.number
|
||||||
|
|
||||||
|
stream_method = request.args.get('stream_method', default=self.fhdhr.origins.origins_dict[origin].stream_method, type=str)
|
||||||
|
if stream_method not in list(self.fhdhr.config.dict["streaming"]["valid_methods"].keys()):
|
||||||
|
response = Response("Service Unavailable", status=503)
|
||||||
|
response = Response("Service Unavailable", status=503)
|
||||||
|
response.headers["X-fHDHR-Error"] = str("806 - Tune Failed")
|
||||||
|
abort(response)
|
||||||
|
|
||||||
duration = request.args.get('duration', default=0, type=int)
|
duration = request.args.get('duration', default=0, type=int)
|
||||||
|
|
||||||
transcode = request.args.get('transcode', default=None, type=str)
|
transcode_quality = request.args.get('transcode', default=None, type=str)
|
||||||
valid_transcode_types = [None, "heavy", "mobile", "internet720", "internet480", "internet360", "internet240"]
|
valid_transcode_types = [None, "heavy", "mobile", "internet720", "internet480", "internet360", "internet240"]
|
||||||
if transcode not in valid_transcode_types:
|
if transcode_quality not in valid_transcode_types:
|
||||||
response = Response("Service Unavailable", status=503)
|
response = Response("Service Unavailable", status=503)
|
||||||
response.headers["X-fHDHR-Error"] = "802 - Unknown Transcode Profile"
|
response.headers["X-fHDHR-Error"] = "802 - Unknown Transcode Profile"
|
||||||
self.fhdhr.logger.error(response.headers["X-fHDHR-Error"])
|
self.fhdhr.logger.error(response.headers["X-fHDHR-Error"])
|
||||||
@ -59,19 +92,21 @@ class Tuners():
|
|||||||
|
|
||||||
stream_args = {
|
stream_args = {
|
||||||
"channel": channel_number,
|
"channel": channel_number,
|
||||||
"method": method,
|
"origin": origin,
|
||||||
|
"method": stream_method,
|
||||||
"duration": duration,
|
"duration": duration,
|
||||||
"transcode": transcode,
|
"origin_quality": self.fhdhr.config.dict["streaming"]["origin_quality"],
|
||||||
|
"transcode_quality": transcode_quality or self.fhdhr.config.dict["streaming"]["transcode_quality"],
|
||||||
"accessed": accessed_url,
|
"accessed": accessed_url,
|
||||||
"client": client_address,
|
"client": client_address,
|
||||||
"client_id": str(client_address) + "_" + str(uuid.uuid4())
|
"client_id": session["session_id"]
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not tuner_number:
|
if not tuner_number:
|
||||||
tunernum = self.fhdhr.device.tuners.first_available(channel_number)
|
tunernum = self.fhdhr.device.tuners.first_available(origin, channel_number)
|
||||||
else:
|
else:
|
||||||
tunernum = self.fhdhr.device.tuners.tuner_grab(tuner_number, channel_number)
|
tunernum = self.fhdhr.device.tuners.tuner_grab(tuner_number, origin, channel_number)
|
||||||
except TunerError as e:
|
except TunerError as e:
|
||||||
self.fhdhr.logger.info("A %s stream request for channel %s was rejected due to %s"
|
self.fhdhr.logger.info("A %s stream request for channel %s was rejected due to %s"
|
||||||
% (stream_args["method"], str(stream_args["channel"]), str(e)))
|
% (stream_args["method"], str(stream_args["channel"]), str(e)))
|
||||||
@ -79,47 +114,91 @@ class Tuners():
|
|||||||
response.headers["X-fHDHR-Error"] = str(e)
|
response.headers["X-fHDHR-Error"] = str(e)
|
||||||
self.fhdhr.logger.error(response.headers["X-fHDHR-Error"])
|
self.fhdhr.logger.error(response.headers["X-fHDHR-Error"])
|
||||||
abort(response)
|
abort(response)
|
||||||
tuner = self.fhdhr.device.tuners.tuners[str(tunernum)]
|
|
||||||
|
tuner = self.fhdhr.device.tuners.tuners[origin][str(tunernum)]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stream_args = self.fhdhr.device.tuners.get_stream_info(stream_args)
|
stream_args = self.fhdhr.device.tuners.get_stream_info(stream_args)
|
||||||
except TunerError as e:
|
except TunerError as e:
|
||||||
self.fhdhr.logger.info("A %s stream request for channel %s was rejected due to %s"
|
self.fhdhr.logger.info("A %s stream request for %s channel %s was rejected due to %s"
|
||||||
% (stream_args["method"], str(stream_args["channel"]), str(e)))
|
% (origin, stream_args["method"], str(stream_args["channel"]), str(e)))
|
||||||
response = Response("Service Unavailable", status=503)
|
response = Response("Service Unavailable", status=503)
|
||||||
response.headers["X-fHDHR-Error"] = str(e)
|
response.headers["X-fHDHR-Error"] = str(e)
|
||||||
self.fhdhr.logger.error(response.headers["X-fHDHR-Error"])
|
self.fhdhr.logger.error(response.headers["X-fHDHR-Error"])
|
||||||
tuner.close()
|
tuner.close()
|
||||||
abort(response)
|
abort(response)
|
||||||
|
|
||||||
self.fhdhr.logger.info("Tuner #" + str(tunernum) + " to be used for stream.")
|
self.fhdhr.logger.info("%s Tuner #%s to be used for stream." % (origin, tunernum))
|
||||||
tuner.set_status(stream_args)
|
tuner.set_status(stream_args)
|
||||||
|
session["tuner_used"] = tunernum
|
||||||
|
|
||||||
if stream_args["method"] == "direct":
|
try:
|
||||||
return Response(tuner.get_stream(stream_args, tuner), content_type=stream_args["content_type"], direct_passthrough=True)
|
stream = tuner.get_stream(stream_args, tuner)
|
||||||
elif stream_args["method"] in ["ffmpeg", "vlc"]:
|
except TunerError as e:
|
||||||
return Response(stream_with_context(tuner.get_stream(stream_args, tuner)), mimetype=stream_args["content_type"])
|
response.headers["X-fHDHR-Error"] = str(e)
|
||||||
|
self.fhdhr.logger.error(response.headers["X-fHDHR-Error"])
|
||||||
|
tuner.close()
|
||||||
|
abort(response)
|
||||||
|
|
||||||
|
return Response(stream_with_context(stream.get()), mimetype=stream_args["content_type"])
|
||||||
|
|
||||||
elif method == "close":
|
elif method == "close":
|
||||||
|
|
||||||
if not tuner_number or str(tuner_number) not in list(self.fhdhr.device.tuners.tuners.keys()):
|
if not origin:
|
||||||
|
return "Missing Origin"
|
||||||
|
|
||||||
|
if not tuner_number or str(tuner_number) not in list(self.fhdhr.device.tuners.tuners[origin].keys()):
|
||||||
return "%s Invalid tuner" % str(tuner_number)
|
return "%s Invalid tuner" % str(tuner_number)
|
||||||
|
|
||||||
tuner = self.fhdhr.device.tuners.tuners[str(tuner_number)]
|
session["tuner_used"] = tuner_number
|
||||||
|
|
||||||
|
tuner = self.fhdhr.device.tuners.tuners[origin][str(tuner_number)]
|
||||||
tuner.close()
|
tuner.close()
|
||||||
|
|
||||||
elif method == "scan":
|
elif method == "scan":
|
||||||
|
|
||||||
|
if not origin:
|
||||||
|
for origin in list(self.fhdhr.device.tuners.tuners.keys()):
|
||||||
if not tuner_number:
|
if not tuner_number:
|
||||||
self.fhdhr.device.tuners.tuner_scan()
|
tunernum = self.fhdhr.device.tuners.first_available(origin, None)
|
||||||
else:
|
else:
|
||||||
tuner = self.fhdhr.device.tuners.tuners[str(tuner_number)]
|
tunernum = self.fhdhr.device.tuners.tuner_grab(tuner_number, origin, None)
|
||||||
tuner.channel_scan()
|
tuner = self.fhdhr.device.tuners.tuners[origin][str(tunernum)]
|
||||||
|
tuner.channel_scan(origin=origin, grabbed=False)
|
||||||
|
else:
|
||||||
|
if not tuner_number:
|
||||||
|
tunernum = self.fhdhr.device.tuners.first_available(origin, None)
|
||||||
|
else:
|
||||||
|
tunernum = self.fhdhr.device.tuners.tuner_grab(tuner_number, origin, None)
|
||||||
|
tuner = self.fhdhr.device.tuners.tuners[origin][str(tunernum)]
|
||||||
|
tuner.channel_scan(origin=origin, grabbed=True)
|
||||||
|
|
||||||
|
elif method == "status":
|
||||||
|
|
||||||
|
if not origin:
|
||||||
|
if not tuner_number:
|
||||||
|
tuner_status = self.fhdhr.device.tuners.status()
|
||||||
|
else:
|
||||||
|
tuner_status = ["Invalid Tuner %s" % tuner_number]
|
||||||
|
else:
|
||||||
|
|
||||||
|
if not tuner_number:
|
||||||
|
tuner_status = self.fhdhr.device.tuners.status(origin)
|
||||||
|
elif str(tuner_number) in list(self.fhdhr.device.tuners.tuners[origin].keys()):
|
||||||
|
tuner_status = self.fhdhr.device.tuners.tuners[origin][str(tuner_number)].get_status()
|
||||||
|
else:
|
||||||
|
tuner_status = ["Invalid Tuner %s" % tuner_number]
|
||||||
|
|
||||||
|
tuner_status_json = json.dumps(tuner_status, indent=4)
|
||||||
|
|
||||||
|
return Response(status=200,
|
||||||
|
response=tuner_status_json,
|
||||||
|
mimetype='application/json')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return "%s Invalid Method" % method
|
return "%s Invalid Method" % method
|
||||||
|
|
||||||
if redirect_url:
|
if redirect_url:
|
||||||
return redirect(redirect_url + "?retmessage=" + urllib.parse.quote("%s Success" % method))
|
return redirect("%s?retmessage=%s" % (redirect_url, urllib.parse.quote("%s Success" % method)))
|
||||||
else:
|
else:
|
||||||
return "%s Success" % method
|
return "%s Success" % method
|
||||||
|
|||||||
116
fHDHR_web/api/w3u.py
Normal file
116
fHDHR_web/api/w3u.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
from flask import Response, request, redirect
|
||||||
|
import urllib.parse
|
||||||
|
import json
|
||||||
|
|
||||||
|
from fHDHR.tools import channel_sort
|
||||||
|
|
||||||
|
|
||||||
|
class W3U():
|
||||||
|
endpoints = ["/api/w3u"]
|
||||||
|
endpoint_name = "api_w3u"
|
||||||
|
endpoint_methods = ["GET", "POST"]
|
||||||
|
|
||||||
|
def __init__(self, fhdhr):
|
||||||
|
self.fhdhr = fhdhr
|
||||||
|
|
||||||
|
def __call__(self, *args):
|
||||||
|
return self.get(*args)
|
||||||
|
|
||||||
|
def get(self, *args):
|
||||||
|
|
||||||
|
base_url = request.url_root[:-1]
|
||||||
|
|
||||||
|
method = request.args.get('method', default="get", type=str)
|
||||||
|
channel = request.args.get('channel', default="all", type=str)
|
||||||
|
redirect_url = request.args.get('redirect', default=None, type=str)
|
||||||
|
|
||||||
|
if method == "get":
|
||||||
|
|
||||||
|
origin_methods = self.fhdhr.origins.valid_origins
|
||||||
|
origin = request.args.get('origin', default=None, type=str)
|
||||||
|
if origin and origin not in origin_methods:
|
||||||
|
return "%s Invalid channels origin" % origin
|
||||||
|
|
||||||
|
channel_info_w3u = {
|
||||||
|
"name": self.fhdhr.config.dict["fhdhr"]["friendlyname"],
|
||||||
|
"image": '%s/favicon.ico' % base_url,
|
||||||
|
"epg": '%s/api/xmltv' % base_url,
|
||||||
|
"stations": []
|
||||||
|
}
|
||||||
|
|
||||||
|
channel_items = []
|
||||||
|
|
||||||
|
if origin:
|
||||||
|
if channel == "all":
|
||||||
|
fileName = "channels.w3u"
|
||||||
|
for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels(origin)]:
|
||||||
|
channel_obj = self.fhdhr.device.channels.get_channel_obj("id", fhdhr_id, origin)
|
||||||
|
if channel_obj.enabled:
|
||||||
|
channel_items.append(channel_obj)
|
||||||
|
elif str(channel) in [str(x) for x in self.fhdhr.device.channels.get_channel_list("number", origin)]:
|
||||||
|
channel_obj = self.fhdhr.device.channels.get_channel_obj("number", channel, origin)
|
||||||
|
fileName = "%s.w3u" % channel_obj.number
|
||||||
|
if channel_obj.enabled:
|
||||||
|
channel_items.append(channel_obj)
|
||||||
|
else:
|
||||||
|
return "Channel Disabled"
|
||||||
|
elif channel != "all" and str(channel) in [str(x) for x in self.fhdhr.device.channels.get_channel_list("id", origin)]:
|
||||||
|
channel_obj = self.fhdhr.device.channels.get_channel_obj("id", channel, origin)
|
||||||
|
fileName = "%s.w3u" % channel_obj.number
|
||||||
|
if channel_obj.enabled:
|
||||||
|
channel_items.append(channel_obj)
|
||||||
|
else:
|
||||||
|
return "Channel Disabled"
|
||||||
|
elif not origin and channel == "all":
|
||||||
|
fileName = "channels.w3u"
|
||||||
|
for origin in list(self.fhdhr.origins.origins_dict.keys()):
|
||||||
|
for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels(origin)]:
|
||||||
|
channel_obj = self.fhdhr.device.channels.get_channel_obj("id", fhdhr_id, origin)
|
||||||
|
if channel_obj.enabled:
|
||||||
|
channel_items.append(channel_obj)
|
||||||
|
elif not origin and channel != "all" and str(channel) in [str(x) for x in self.fhdhr.device.channels.get_channel_list("id")]:
|
||||||
|
channel_obj = self.fhdhr.device.channels.get_channel_obj("id", channel)
|
||||||
|
fileName = "%s.w3u" % channel_obj.number
|
||||||
|
if channel_obj.enabled:
|
||||||
|
channel_items.append(channel_obj)
|
||||||
|
else:
|
||||||
|
return "Channel Disabled"
|
||||||
|
else:
|
||||||
|
return "Invalid Channel"
|
||||||
|
|
||||||
|
channels_info = {}
|
||||||
|
|
||||||
|
for channel_obj in channel_items:
|
||||||
|
|
||||||
|
if self.fhdhr.config.dict["epg"]["images"] == "proxy" or not channel_obj.thumbnail:
|
||||||
|
logourl = ('%s/api/images?method=get&type=channel&id=%s' %
|
||||||
|
(base_url, str(channel_obj.dict['origin_id'])))
|
||||||
|
else:
|
||||||
|
logourl = channel_obj.thumbnail
|
||||||
|
|
||||||
|
channels_info[channel_obj.number] = {
|
||||||
|
"name": str(channel_obj.dict['name']),
|
||||||
|
"url": "%s%s" % (base_url, channel_obj.api_stream_url),
|
||||||
|
"epgId": str(channel_obj.dict['origin_id']),
|
||||||
|
"image": logourl,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sort the channels
|
||||||
|
sorted_channel_list = channel_sort(list(channels_info.keys()))
|
||||||
|
for channel in sorted_channel_list:
|
||||||
|
channel_info_w3u["stations"].append(channels_info[channel])
|
||||||
|
|
||||||
|
channels_info_json = json.dumps(channel_info_w3u, indent=4)
|
||||||
|
|
||||||
|
resp = Response(status=200, response=channels_info_json, mimetype='application/json')
|
||||||
|
resp.headers["content-disposition"] = "attachment; filename=%s" % fileName
|
||||||
|
return resp
|
||||||
|
|
||||||
|
return Response(status=200,
|
||||||
|
response=channels_info_json,
|
||||||
|
mimetype='application/json')
|
||||||
|
|
||||||
|
if redirect_url:
|
||||||
|
return redirect("%s?retmessage=%s" % (redirect_url, urllib.parse.quote("%s Success" % method)))
|
||||||
|
else:
|
||||||
|
return "%s Success" % method
|
||||||
@ -1,7 +1,8 @@
|
|||||||
from flask import Response, request, redirect
|
from flask import Response, request, redirect, session
|
||||||
import xml.etree.ElementTree
|
import xml.etree.ElementTree
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
import datetime
|
||||||
|
|
||||||
from fHDHR.tools import sub_el
|
from fHDHR.tools import sub_el
|
||||||
|
|
||||||
@ -15,14 +16,21 @@ class xmlTV():
|
|||||||
def __init__(self, fhdhr):
|
def __init__(self, fhdhr):
|
||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
|
|
||||||
|
self.xmltv_offset = {}
|
||||||
|
for epg_method in list(self.fhdhr.device.epg.epg_handling.keys()):
|
||||||
|
if epg_method in list(self.fhdhr.config.dict.keys()):
|
||||||
|
if "xmltv_offset" in list(self.fhdhr.config.dict[epg_method].keys()):
|
||||||
|
self.xmltv_offset[epg_method] = self.fhdhr.config.dict[epg_method]["xmltv_offset"]
|
||||||
|
if epg_method not in list(self.xmltv_offset.keys()):
|
||||||
|
self.xmltv_offset[epg_method] = self.fhdhr.config.dict["epg"]["xmltv_offset"]
|
||||||
|
|
||||||
def __call__(self, *args):
|
def __call__(self, *args):
|
||||||
return self.get(*args)
|
return self.get(*args)
|
||||||
|
|
||||||
def get(self, *args):
|
def get(self, *args):
|
||||||
|
|
||||||
if self.fhdhr.config.dict["fhdhr"]["require_auth"]:
|
if self.fhdhr.config.dict["fhdhr"]["require_auth"]:
|
||||||
DeviceAuth = request.args.get('DeviceAuth', default=None, type=str)
|
if session["deviceauth"] != self.fhdhr.config.dict["fhdhr"]["device_auth"]:
|
||||||
if DeviceAuth != self.fhdhr.config.dict["fhdhr"]["device_auth"]:
|
|
||||||
return "not subscribed"
|
return "not subscribed"
|
||||||
|
|
||||||
base_url = request.url_root[:-1]
|
base_url = request.url_root[:-1]
|
||||||
@ -30,7 +38,7 @@ class xmlTV():
|
|||||||
method = request.args.get('method', default="get", type=str)
|
method = request.args.get('method', default="get", type=str)
|
||||||
|
|
||||||
source = request.args.get('source', default=self.fhdhr.config.dict["epg"]["def_method"], type=str)
|
source = request.args.get('source', default=self.fhdhr.config.dict["epg"]["def_method"], type=str)
|
||||||
if source not in self.fhdhr.config.dict["main"]["valid_epg_methods"]:
|
if source not in list(self.fhdhr.config.dict["epg"]["valid_methods"].keys()):
|
||||||
return "%s Invalid xmltv method" % source
|
return "%s Invalid xmltv method" % source
|
||||||
|
|
||||||
redirect_url = request.args.get('redirect', default=None, type=str)
|
redirect_url = request.args.get('redirect', default=None, type=str)
|
||||||
@ -38,6 +46,18 @@ class xmlTV():
|
|||||||
if method == "get":
|
if method == "get":
|
||||||
|
|
||||||
epgdict = self.fhdhr.device.epg.get_epg(source)
|
epgdict = self.fhdhr.device.epg.get_epg(source)
|
||||||
|
|
||||||
|
if source in self.fhdhr.origins.valid_origins:
|
||||||
|
epgdict = epgdict.copy()
|
||||||
|
for c in list(epgdict.keys()):
|
||||||
|
chan_obj = self.fhdhr.device.channels.get_channel_obj("origin_id", epgdict[c]["id"], source)
|
||||||
|
epgdict[chan_obj.number] = epgdict.pop(c)
|
||||||
|
epgdict[chan_obj.number]["name"] = chan_obj.dict["name"]
|
||||||
|
epgdict[chan_obj.number]["callsign"] = chan_obj.dict["callsign"]
|
||||||
|
epgdict[chan_obj.number]["number"] = chan_obj.number
|
||||||
|
epgdict[chan_obj.number]["id"] = chan_obj.dict["origin_id"]
|
||||||
|
epgdict[chan_obj.number]["thumbnail"] = chan_obj.thumbnail
|
||||||
|
|
||||||
xmltv_xml = self.create_xmltv(base_url, epgdict, source)
|
xmltv_xml = self.create_xmltv(base_url, epgdict, source)
|
||||||
|
|
||||||
return Response(status=200,
|
return Response(status=200,
|
||||||
@ -54,7 +74,7 @@ class xmlTV():
|
|||||||
return "%s Invalid Method" % method
|
return "%s Invalid Method" % method
|
||||||
|
|
||||||
if redirect_url:
|
if redirect_url:
|
||||||
return redirect(redirect_url + "?retmessage=" + urllib.parse.quote("%s Success" % method))
|
return redirect("%s?retmessage=%s" % (redirect_url, urllib.parse.quote("%s Success" % method)))
|
||||||
else:
|
else:
|
||||||
return "%s Success" % method
|
return "%s Success" % method
|
||||||
|
|
||||||
@ -64,7 +84,7 @@ class xmlTV():
|
|||||||
xmltvgen.set('source-info-url', self.fhdhr.config.dict["fhdhr"]["friendlyname"])
|
xmltvgen.set('source-info-url', self.fhdhr.config.dict["fhdhr"]["friendlyname"])
|
||||||
xmltvgen.set('source-info-name', self.fhdhr.config.dict["main"]["servicename"])
|
xmltvgen.set('source-info-name', self.fhdhr.config.dict["main"]["servicename"])
|
||||||
xmltvgen.set('generator-info-name', 'fHDHR')
|
xmltvgen.set('generator-info-name', 'fHDHR')
|
||||||
xmltvgen.set('generator-info-url', 'fHDHR/' + self.fhdhr.config.dict["main"]["reponame"])
|
xmltvgen.set('generator-info-url', 'fHDHR/%s' % self.fhdhr.config.dict["main"]["reponame"])
|
||||||
return xmltvgen
|
return xmltvgen
|
||||||
|
|
||||||
def xmltv_file(self, xmltvgen):
|
def xmltv_file(self, xmltvgen):
|
||||||
@ -78,6 +98,14 @@ class xmlTV():
|
|||||||
"""This method is called when creation of a full xmltv is not possible"""
|
"""This method is called when creation of a full xmltv is not possible"""
|
||||||
return self.xmltv_file(self.xmltv_headers())
|
return self.xmltv_file(self.xmltv_headers())
|
||||||
|
|
||||||
|
def timestamp_to_datetime(self, time_start, time_end, source):
|
||||||
|
xmltvtimetamps = {}
|
||||||
|
source_offset = self.xmltv_offset[source]
|
||||||
|
for time_item, time_value in zip(["time_start", "time_end"], [time_start, time_end]):
|
||||||
|
timestampval = datetime.datetime.fromtimestamp(time_value).strftime('%Y%m%d%H%M%S')
|
||||||
|
xmltvtimetamps[time_item] = "%s %s" % (timestampval, source_offset)
|
||||||
|
return xmltvtimetamps
|
||||||
|
|
||||||
def create_xmltv(self, base_url, epgdict, source):
|
def create_xmltv(self, base_url, epgdict, source):
|
||||||
if not epgdict:
|
if not epgdict:
|
||||||
return self.xmltv_empty()
|
return self.xmltv_empty()
|
||||||
@ -85,15 +113,15 @@ class xmlTV():
|
|||||||
|
|
||||||
out = self.xmltv_headers()
|
out = self.xmltv_headers()
|
||||||
|
|
||||||
if source in ["origin", "blocks", self.fhdhr.config.dict["main"]["dictpopname"]]:
|
if source in self.fhdhr.origins.valid_origins:
|
||||||
for c in list(epgdict.keys()):
|
for c in list(epgdict.keys()):
|
||||||
chan_obj = self.fhdhr.device.channels.get_channel_obj("origin_id", epgdict[c]["id"])
|
chan_obj = self.fhdhr.device.channels.get_channel_obj("origin_id", epgdict[c]["id"], source)
|
||||||
epgdict[chan_obj.dict["number"]] = epgdict.pop(c)
|
epgdict[chan_obj.number] = epgdict.pop(c)
|
||||||
epgdict[chan_obj.dict["number"]]["name"] = chan_obj.dict["name"]
|
epgdict[chan_obj.number]["name"] = chan_obj.dict["name"]
|
||||||
epgdict[chan_obj.dict["number"]]["callsign"] = chan_obj.dict["callsign"]
|
epgdict[chan_obj.number]["callsign"] = chan_obj.dict["callsign"]
|
||||||
epgdict[chan_obj.dict["number"]]["number"] = chan_obj.dict["number"]
|
epgdict[chan_obj.number]["number"] = chan_obj.number
|
||||||
epgdict[chan_obj.dict["number"]]["id"] = chan_obj.dict["origin_id"]
|
epgdict[chan_obj.number]["id"] = chan_obj.dict["origin_id"]
|
||||||
epgdict[chan_obj.dict["number"]]["thumbnail"] = chan_obj.thumbnail
|
epgdict[chan_obj.number]["thumbnail"] = chan_obj.thumbnail
|
||||||
|
|
||||||
for c in list(epgdict.keys()):
|
for c in list(epgdict.keys()):
|
||||||
|
|
||||||
@ -107,7 +135,7 @@ class xmlTV():
|
|||||||
sub_el(c_out, 'display-name', text=epgdict[c]['name'])
|
sub_el(c_out, 'display-name', text=epgdict[c]['name'])
|
||||||
|
|
||||||
if self.fhdhr.config.dict["epg"]["images"] == "proxy":
|
if self.fhdhr.config.dict["epg"]["images"] == "proxy":
|
||||||
sub_el(c_out, 'icon', src=(str(base_url) + "/api/images?method=get&type=channel&id=" + str(epgdict[c]['id'])))
|
sub_el(c_out, 'icon', src=("%s/api/images?method=get&type=channel&id=%s" % (base_url, epgdict[c]['id'])))
|
||||||
else:
|
else:
|
||||||
sub_el(c_out, 'icon', src=(epgdict[c]["thumbnail"]))
|
sub_el(c_out, 'icon', src=(epgdict[c]["thumbnail"]))
|
||||||
|
|
||||||
@ -117,16 +145,18 @@ class xmlTV():
|
|||||||
|
|
||||||
for program in channel_listing:
|
for program in channel_listing:
|
||||||
|
|
||||||
|
xmltvtimetamps = self.timestamp_to_datetime(program['time_start'], program['time_end'], source)
|
||||||
|
|
||||||
prog_out = sub_el(out, 'programme',
|
prog_out = sub_el(out, 'programme',
|
||||||
start=program['time_start'],
|
start=xmltvtimetamps['time_start'],
|
||||||
stop=program['time_end'],
|
stop=xmltvtimetamps['time_end'],
|
||||||
channel=str(channelnum))
|
channel=str(channelnum))
|
||||||
|
|
||||||
sub_el(prog_out, 'title', lang='en', text=program['title'])
|
sub_el(prog_out, 'title', lang='en', text=program['title'])
|
||||||
|
|
||||||
sub_el(prog_out, 'desc', lang='en', text=program['description'])
|
sub_el(prog_out, 'desc', lang='en', text=program['description'])
|
||||||
|
|
||||||
sub_el(prog_out, 'sub-title', lang='en', text='Movie: ' + program['sub-title'])
|
sub_el(prog_out, 'sub-title', lang='en', text='Movie: %s' % program['sub-title'])
|
||||||
|
|
||||||
sub_el(prog_out, 'length', units='minutes', text=str(int(program['duration_minutes'])))
|
sub_el(prog_out, 'length', units='minutes', text=str(int(program['duration_minutes'])))
|
||||||
|
|
||||||
@ -148,11 +178,11 @@ class xmlTV():
|
|||||||
|
|
||||||
if program["thumbnail"]:
|
if program["thumbnail"]:
|
||||||
if self.fhdhr.config.dict["epg"]["images"] == "proxy":
|
if self.fhdhr.config.dict["epg"]["images"] == "proxy":
|
||||||
sub_el(prog_out, 'icon', src=(str(base_url) + "/api/images?method=get&type=content&id=" + str(program['id'])))
|
sub_el(prog_out, 'icon', src=("%s/api/images?method=get&type=content&id=%s" % (base_url, program['id'])))
|
||||||
else:
|
else:
|
||||||
sub_el(prog_out, 'icon', src=(program["thumbnail"]))
|
sub_el(prog_out, 'icon', src=(program["thumbnail"]))
|
||||||
else:
|
else:
|
||||||
sub_el(prog_out, 'icon', src=(str(base_url) + "/api/images?method=generate&type=content&message=" + urllib.parse.quote(program['title'])))
|
sub_el(prog_out, 'icon', src=("%s/api/images?method=generate&type=content&message=%s" % (base_url, urllib.parse.quote(program['title']))))
|
||||||
|
|
||||||
if program['rating']:
|
if program['rating']:
|
||||||
rating_out = sub_el(prog_out, 'rating', system="MPAA")
|
rating_out = sub_el(prog_out, 'rating', system="MPAA")
|
||||||
|
|||||||
17
fHDHR_web/brython/__init__.py
Normal file
17
fHDHR_web/brython/__init__.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
|
||||||
|
from .brython import Brython
|
||||||
|
from .brython_stdlib import Brython_stdlib
|
||||||
|
|
||||||
|
from .brython_bry import Brython_bry
|
||||||
|
|
||||||
|
|
||||||
|
class fHDHR_Brython():
|
||||||
|
|
||||||
|
def __init__(self, fhdhr):
|
||||||
|
self.fhdhr = fhdhr
|
||||||
|
|
||||||
|
self.brython = Brython(fhdhr)
|
||||||
|
self.brython_stdlib = Brython_stdlib(fhdhr)
|
||||||
|
|
||||||
|
self.brython_bry = Brython_bry(fhdhr)
|
||||||
14160
fHDHR_web/brython/brython.js
Normal file
14160
fHDHR_web/brython/brython.js
Normal file
File diff suppressed because one or more lines are too long
21
fHDHR_web/brython/brython.py
Normal file
21
fHDHR_web/brython/brython.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from flask import send_from_directory
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
|
||||||
|
class Brython():
|
||||||
|
endpoints = ["/brython.js"]
|
||||||
|
endpoint_name = "file_brython_js"
|
||||||
|
|
||||||
|
def __init__(self, fhdhr):
|
||||||
|
self.fhdhr = fhdhr
|
||||||
|
self.brython_path = pathlib.Path(self.fhdhr.config.internal["paths"]["fHDHR_web_dir"]).joinpath('brython')
|
||||||
|
|
||||||
|
def __call__(self, *args):
|
||||||
|
return self.get(*args)
|
||||||
|
|
||||||
|
def get(self, *args):
|
||||||
|
|
||||||
|
return send_from_directory(self.brython_path,
|
||||||
|
'brython.js',
|
||||||
|
mimetype='text/javascript')
|
||||||
19
fHDHR_web/brython/brython_bry.py
Normal file
19
fHDHR_web/brython/brython_bry.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from flask import send_from_directory
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
|
||||||
|
class Brython_bry():
|
||||||
|
endpoints = ["/brython.bry"]
|
||||||
|
endpoint_name = "file_brython_bry"
|
||||||
|
|
||||||
|
def __init__(self, fhdhr):
|
||||||
|
self.fhdhr = fhdhr
|
||||||
|
self.brython_path = pathlib.Path(self.fhdhr.config.internal["paths"]["fHDHR_web_dir"]).joinpath('brython')
|
||||||
|
|
||||||
|
def __call__(self, *args):
|
||||||
|
return self.get(*args)
|
||||||
|
|
||||||
|
def get(self, *args):
|
||||||
|
|
||||||
|
return send_from_directory(self.brython_path, 'brython_code.py')
|
||||||
104
fHDHR_web/brython/brython_code.py
Normal file
104
fHDHR_web/brython/brython_code.py
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
from browser import document, bind # alert, window
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
def chan_edit_data(items, channel_id):
|
||||||
|
|
||||||
|
chanlist = []
|
||||||
|
chandict = {}
|
||||||
|
|
||||||
|
for element in items:
|
||||||
|
if element.name == "id":
|
||||||
|
if len(chandict.keys()) >= 2 and "id" in list(chandict.keys()):
|
||||||
|
chanlist.append(chandict)
|
||||||
|
chandict = {"id": element.value}
|
||||||
|
if element.type == "checkbox":
|
||||||
|
if element.name in ["enabled"]:
|
||||||
|
save_val = element.checked
|
||||||
|
else:
|
||||||
|
save_val = int(element.checked)
|
||||||
|
else:
|
||||||
|
save_val = element.value
|
||||||
|
if element.name != "id":
|
||||||
|
cur_value = element.placeholder
|
||||||
|
if element.type == "checkbox":
|
||||||
|
if element.name in ["enabled"]:
|
||||||
|
cur_value = element.placeholder
|
||||||
|
else:
|
||||||
|
cur_value = int(element.placeholder)
|
||||||
|
if str(save_val) != str(cur_value):
|
||||||
|
chandict[element.name] = save_val
|
||||||
|
|
||||||
|
if channel_id != "all":
|
||||||
|
chanlist == [x for x in chanlist if x["id"] == channel_id]
|
||||||
|
|
||||||
|
return chanlist
|
||||||
|
|
||||||
|
|
||||||
|
def chan_edit_postform(chanlist):
|
||||||
|
origin = document["origin"].value
|
||||||
|
postForm = document.createElement('form')
|
||||||
|
postForm.method = "POST"
|
||||||
|
postForm.action = "/api/channels?method=modify&origin=%s&redirect=/channels_editor&origin=%s" % (origin, origin)
|
||||||
|
postForm.setRequestHeader = "('Content-Type', 'application/json')"
|
||||||
|
|
||||||
|
postData = document.createElement('input')
|
||||||
|
postData.type = 'hidden'
|
||||||
|
postData.name = "channels"
|
||||||
|
postData.value = json.dumps(chanlist)
|
||||||
|
|
||||||
|
postForm.appendChild(postData)
|
||||||
|
document.body.appendChild(postForm)
|
||||||
|
return postForm
|
||||||
|
|
||||||
|
|
||||||
|
@bind("#Chan_Edit_Reset", "submit")
|
||||||
|
def chan_edit_reset(evt):
|
||||||
|
chanlist = chan_edit_data(
|
||||||
|
document.select(".reset"),
|
||||||
|
str(evt.currentTarget.children[0].id).replace("reset_", ""))
|
||||||
|
postForm = chan_edit_postform(chanlist)
|
||||||
|
postForm.submit()
|
||||||
|
evt.preventDefault()
|
||||||
|
|
||||||
|
|
||||||
|
@bind("#Chan_Edit_Modify", "submit")
|
||||||
|
def chan_edit_modify(evt):
|
||||||
|
chanlist = chan_edit_data(
|
||||||
|
document.select(".channels"),
|
||||||
|
str(evt.currentTarget.children[0].id).replace("update_", ""))
|
||||||
|
postForm = chan_edit_postform(chanlist)
|
||||||
|
postForm.submit()
|
||||||
|
evt.preventDefault()
|
||||||
|
|
||||||
|
|
||||||
|
@bind("#Chan_Edit_Enable_Toggle", "click")
|
||||||
|
def chan_edit_enable(event):
|
||||||
|
enable_bool = bool(int(document["enable_button"].value))
|
||||||
|
for element in document.get(selector='input[type="checkbox"]'):
|
||||||
|
if element.name == "enabled":
|
||||||
|
element.checked = enable_bool
|
||||||
|
element.value = enable_bool
|
||||||
|
|
||||||
|
if not enable_bool:
|
||||||
|
document["enable_button"].value = "1"
|
||||||
|
document["enable_button"].text = "Enable All"
|
||||||
|
else:
|
||||||
|
document["enable_button"].value = "0"
|
||||||
|
document["enable_button"].text = "Disable All"
|
||||||
|
|
||||||
|
|
||||||
|
@bind("#Chan_Edit_Favorite_Toggle", "click")
|
||||||
|
def chan_edit_favorite(event):
|
||||||
|
enable_bool = bool(int(document["favorite_button"].value))
|
||||||
|
for element in document.get(selector='input[type="checkbox"]'):
|
||||||
|
if element.name == "favorite":
|
||||||
|
element.checked = enable_bool
|
||||||
|
element.value = int(enable_bool)
|
||||||
|
|
||||||
|
if not enable_bool:
|
||||||
|
document["favorite_button"].value = "1"
|
||||||
|
document["favorite_button"].text = "Favorite All"
|
||||||
|
else:
|
||||||
|
document["favorite_button"].value = "0"
|
||||||
|
document["favorite_button"].text = "Unfavorite All"
|
||||||
3
fHDHR_web/brython/brython_stdlib.js
Normal file
3
fHDHR_web/brython/brython_stdlib.js
Normal file
File diff suppressed because one or more lines are too long
22
fHDHR_web/brython/brython_stdlib.py
Normal file
22
fHDHR_web/brython/brython_stdlib.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from flask import send_from_directory
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
|
||||||
|
class Brython_stdlib():
|
||||||
|
endpoints = ["/brython_stdlib.js"]
|
||||||
|
endpoint_name = "file_brython_stdlib_js"
|
||||||
|
|
||||||
|
def __init__(self, fhdhr):
|
||||||
|
self.fhdhr = fhdhr
|
||||||
|
|
||||||
|
self.brython_path = pathlib.Path(self.fhdhr.config.internal["paths"]["fHDHR_web_dir"]).joinpath('brython')
|
||||||
|
|
||||||
|
def __call__(self, *args):
|
||||||
|
return self.get(*args)
|
||||||
|
|
||||||
|
def get(self, *args):
|
||||||
|
|
||||||
|
return send_from_directory(self.brython_path,
|
||||||
|
'brython_stdlib.js',
|
||||||
|
mimetype='text/javascript')
|
||||||
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
from .favicon_ico import Favicon_ICO
|
from .favicon_ico import Favicon_ICO
|
||||||
from .style_css import Style_CSS
|
from .style_css import Style_CSS
|
||||||
from .device_xml import Device_XML
|
|
||||||
|
|
||||||
|
|
||||||
class fHDHR_Files():
|
class fHDHR_Files():
|
||||||
@ -12,4 +11,3 @@ class fHDHR_Files():
|
|||||||
|
|
||||||
self.favicon = Favicon_ICO(fhdhr)
|
self.favicon = Favicon_ICO(fhdhr)
|
||||||
self.style = Style_CSS(fhdhr)
|
self.style = Style_CSS(fhdhr)
|
||||||
self.device_xml = Device_XML(fhdhr)
|
|
||||||
|
|||||||
@ -1,21 +0,0 @@
|
|||||||
from flask import request, redirect
|
|
||||||
|
|
||||||
|
|
||||||
class Device_XML():
|
|
||||||
endpoints = ["/device.xml"]
|
|
||||||
endpoint_name = "file_device_xml"
|
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
|
||||||
self.fhdhr = fhdhr
|
|
||||||
|
|
||||||
def __call__(self, *args):
|
|
||||||
return self.get(*args)
|
|
||||||
|
|
||||||
def get(self, *args):
|
|
||||||
|
|
||||||
user_agent = request.headers.get('User-Agent')
|
|
||||||
if (self.fhdhr.config.dict["rmg"]["enabled"] and
|
|
||||||
str(user_agent).lower().startswith("plexmediaserver")):
|
|
||||||
return redirect("/rmg/device.xml")
|
|
||||||
else:
|
|
||||||
return redirect("/hdhr/device.xml")
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
from flask import Response, request
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
class Lineup_JSON():
|
|
||||||
endpoints = ["/lineup.json", "/hdhr/lineup.json"]
|
|
||||||
endpoint_name = "hdhr_lineup_json"
|
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
|
||||||
self.fhdhr = fhdhr
|
|
||||||
|
|
||||||
def __call__(self, *args):
|
|
||||||
return self.get(*args)
|
|
||||||
|
|
||||||
def get(self, *args):
|
|
||||||
|
|
||||||
base_url = request.url_root[:-1]
|
|
||||||
|
|
||||||
show = request.args.get('show', default="all", type=str)
|
|
||||||
|
|
||||||
jsonlineup = []
|
|
||||||
for fhdhr_id in list(self.fhdhr.device.channels.list.keys()):
|
|
||||||
channel_obj = self.fhdhr.device.channels.list[fhdhr_id]
|
|
||||||
if channel_obj.enabled or show == "found":
|
|
||||||
lineup_dict = channel_obj.lineup_dict
|
|
||||||
lineup_dict["URL"] = "%s%s" % (base_url, lineup_dict["URL"])
|
|
||||||
if show == "found" and channel_obj.enabled:
|
|
||||||
lineup_dict["Enabled"] = 1
|
|
||||||
elif show == "found" and not channel_obj.enabled:
|
|
||||||
lineup_dict["Enabled"] = 0
|
|
||||||
jsonlineup.append(lineup_dict)
|
|
||||||
|
|
||||||
lineup_json = json.dumps(jsonlineup, indent=4)
|
|
||||||
|
|
||||||
return Response(status=200,
|
|
||||||
response=lineup_json,
|
|
||||||
mimetype='application/json')
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
from flask import Response, request
|
|
||||||
from io import BytesIO
|
|
||||||
import xml.etree.ElementTree
|
|
||||||
|
|
||||||
from fHDHR.tools import sub_el
|
|
||||||
|
|
||||||
|
|
||||||
class Lineup_XML():
|
|
||||||
endpoints = ["/lineup.xml", "/hdhr/lineup.xml"]
|
|
||||||
endpoint_name = "hdhr_lineup_xml"
|
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
|
||||||
self.fhdhr = fhdhr
|
|
||||||
|
|
||||||
def __call__(self, *args):
|
|
||||||
return self.get(*args)
|
|
||||||
|
|
||||||
def get(self, *args):
|
|
||||||
|
|
||||||
base_url = request.url_root[:-1]
|
|
||||||
|
|
||||||
show = request.args.get('show', default="all", type=str)
|
|
||||||
|
|
||||||
out = xml.etree.ElementTree.Element('Lineup')
|
|
||||||
for fhdhr_id in list(self.fhdhr.device.channels.list.keys()):
|
|
||||||
channel_obj = self.fhdhr.device.channels.list[fhdhr_id]
|
|
||||||
if channel_obj.enabled or show == "found":
|
|
||||||
program_out = sub_el(out, 'Program')
|
|
||||||
lineup_dict = channel_obj.lineup_dict
|
|
||||||
lineup_dict["URL"] = base_url + lineup_dict["URL"]
|
|
||||||
if show == "found" and channel_obj.enabled:
|
|
||||||
lineup_dict["Enabled"] = 1
|
|
||||||
elif show == "found" and not channel_obj.enabled:
|
|
||||||
lineup_dict["Enabled"] = 0
|
|
||||||
for key in list(lineup_dict.keys()):
|
|
||||||
sub_el(program_out, str(key), str(lineup_dict[key]))
|
|
||||||
|
|
||||||
fakefile = BytesIO()
|
|
||||||
fakefile.write(b'<?xml version="1.0" encoding="UTF-8"?>\n')
|
|
||||||
fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8'))
|
|
||||||
lineup_xml = fakefile.getvalue()
|
|
||||||
|
|
||||||
return Response(status=200,
|
|
||||||
response=lineup_xml,
|
|
||||||
mimetype='application/xml')
|
|
||||||
@ -1,16 +1,14 @@
|
|||||||
|
|
||||||
|
|
||||||
from .index_html import Index_HTML
|
from .index_html import Index_HTML
|
||||||
from .origin_html import Origin_HTML
|
|
||||||
from .channels_html import Channels_HTML
|
from .channels_html import Channels_HTML
|
||||||
from .guide_html import Guide_HTML
|
from .guide_html import Guide_HTML
|
||||||
from .cluster_html import Cluster_HTML
|
|
||||||
from .tuners_html import Tuners_HTML
|
from .tuners_html import Tuners_HTML
|
||||||
from .xmltv_html import xmlTV_HTML
|
from .xmltv_html import xmlTV_HTML
|
||||||
from .version_html import Version_HTML
|
from .version_html import Version_HTML
|
||||||
from .diagnostics_html import Diagnostics_HTML
|
from .diagnostics_html import Diagnostics_HTML
|
||||||
from .settings_html import Settings_HTML
|
from .settings_html import Settings_HTML
|
||||||
from .channels_editor import Channels_Editor_HTML
|
from .channels_editor_html import Channels_Editor_HTML
|
||||||
|
|
||||||
|
|
||||||
class fHDHR_Pages():
|
class fHDHR_Pages():
|
||||||
@ -19,11 +17,9 @@ class fHDHR_Pages():
|
|||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
|
|
||||||
self.index_html = Index_HTML(fhdhr)
|
self.index_html = Index_HTML(fhdhr)
|
||||||
self.origin_html = Origin_HTML(fhdhr)
|
|
||||||
self.channels_html = Channels_HTML(fhdhr)
|
self.channels_html = Channels_HTML(fhdhr)
|
||||||
self.channels_editor = Channels_Editor_HTML(fhdhr)
|
self.channels_editor_html = Channels_Editor_HTML(fhdhr)
|
||||||
self.guide_html = Guide_HTML(fhdhr)
|
self.guide_html = Guide_HTML(fhdhr)
|
||||||
self.cluster_html = Cluster_HTML(fhdhr)
|
|
||||||
self.tuners_html = Tuners_HTML(fhdhr)
|
self.tuners_html = Tuners_HTML(fhdhr)
|
||||||
self.xmltv_html = xmlTV_HTML(fhdhr)
|
self.xmltv_html = xmlTV_HTML(fhdhr)
|
||||||
self.version_html = Version_HTML(fhdhr)
|
self.version_html = Version_HTML(fhdhr)
|
||||||
|
|||||||
@ -1,23 +0,0 @@
|
|||||||
from flask import request, render_template
|
|
||||||
|
|
||||||
|
|
||||||
class Channels_Editor_HTML():
|
|
||||||
endpoints = ["/channels_editor", "/channels_editor.html"]
|
|
||||||
endpoint_name = "page_channels_editor_html"
|
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
|
||||||
self.fhdhr = fhdhr
|
|
||||||
|
|
||||||
def __call__(self, *args):
|
|
||||||
return self.get(*args)
|
|
||||||
|
|
||||||
def get(self, *args):
|
|
||||||
|
|
||||||
channelslist = []
|
|
||||||
for fhdhr_id in list(self.fhdhr.device.channels.list.keys()):
|
|
||||||
channel_obj = self.fhdhr.device.channels.list[fhdhr_id]
|
|
||||||
channel_dict = channel_obj.dict.copy()
|
|
||||||
channel_dict["play_url"] = channel_obj.play_url
|
|
||||||
channelslist.append(channel_dict)
|
|
||||||
|
|
||||||
return render_template('channels_editor.html', request=request, fhdhr=self.fhdhr, channelslist=channelslist)
|
|
||||||
43
fHDHR_web/pages/channels_editor_html.py
Normal file
43
fHDHR_web/pages/channels_editor_html.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
from flask import request, render_template, session
|
||||||
|
|
||||||
|
from fHDHR.tools import channel_sort
|
||||||
|
|
||||||
|
|
||||||
|
class Channels_Editor_HTML():
|
||||||
|
endpoints = ["/channels_editor", "/channels_editor.html"]
|
||||||
|
endpoint_name = "page_channels_editor_html"
|
||||||
|
endpoint_access_level = 2
|
||||||
|
endpoint_category = "tool_pages"
|
||||||
|
pretty_name = "Channels Editor"
|
||||||
|
|
||||||
|
def __init__(self, fhdhr):
|
||||||
|
self.fhdhr = fhdhr
|
||||||
|
|
||||||
|
def __call__(self, *args):
|
||||||
|
return self.get(*args)
|
||||||
|
|
||||||
|
def get(self, *args):
|
||||||
|
|
||||||
|
origin = request.args.get('origin', default=self.fhdhr.device.epg.def_method, type=str)
|
||||||
|
origin_methods = self.fhdhr.origins.valid_origins
|
||||||
|
if origin not in origin_methods:
|
||||||
|
origin = origin_methods[0]
|
||||||
|
|
||||||
|
channelslist = {}
|
||||||
|
for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels(origin)]:
|
||||||
|
channel_obj = self.fhdhr.device.channels.get_channel_obj("id", fhdhr_id, origin)
|
||||||
|
channel_dict = channel_obj.dict.copy()
|
||||||
|
|
||||||
|
channel_dict["number"] = channel_obj.number
|
||||||
|
channel_dict["chan_thumbnail"] = channel_obj.thumbnail
|
||||||
|
channel_dict["m3u_url"] = channel_obj.api_m3u_url
|
||||||
|
|
||||||
|
channelslist[channel_dict["number"]] = channel_dict
|
||||||
|
|
||||||
|
# Sort the channels
|
||||||
|
sorted_channel_list = channel_sort(list(channelslist.keys()))
|
||||||
|
sorted_chan_guide = []
|
||||||
|
for channel in sorted_channel_list:
|
||||||
|
sorted_chan_guide.append(channelslist[channel])
|
||||||
|
|
||||||
|
return render_template('channels_editor.html', request=request, session=session, fhdhr=self.fhdhr, channelslist=sorted_chan_guide, origin=origin, origin_methods=origin_methods, list=list)
|
||||||
@ -1,9 +1,13 @@
|
|||||||
from flask import request, render_template
|
from flask import request, render_template, session
|
||||||
|
|
||||||
|
from fHDHR.tools import channel_sort
|
||||||
|
|
||||||
|
|
||||||
class Channels_HTML():
|
class Channels_HTML():
|
||||||
endpoints = ["/channels", "/channels.html"]
|
endpoints = ["/channels", "/channels.html"]
|
||||||
endpoint_name = "page_channels_html"
|
endpoint_name = "page_channels_html"
|
||||||
|
endpoint_access_level = 0
|
||||||
|
pretty_name = "Channels"
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
def __init__(self, fhdhr):
|
||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
@ -13,18 +17,33 @@ class Channels_HTML():
|
|||||||
|
|
||||||
def get(self, *args):
|
def get(self, *args):
|
||||||
|
|
||||||
|
origin = request.args.get('origin', default=self.fhdhr.device.epg.def_method, type=str)
|
||||||
|
origin_methods = self.fhdhr.origins.valid_origins
|
||||||
|
if origin not in origin_methods:
|
||||||
|
origin = origin_methods[0]
|
||||||
|
|
||||||
channels_dict = {
|
channels_dict = {
|
||||||
"Total Channels": len(list(self.fhdhr.device.channels.list.keys())),
|
"Total Channels": len(self.fhdhr.device.channels.get_channels(origin)),
|
||||||
"Enabled": 0,
|
"Enabled": 0
|
||||||
}
|
}
|
||||||
|
|
||||||
channelslist = []
|
channelslist = {}
|
||||||
for fhdhr_id in list(self.fhdhr.device.channels.list.keys()):
|
for fhdhr_id in [x["id"] for x in self.fhdhr.device.channels.get_channels(origin)]:
|
||||||
channel_obj = self.fhdhr.device.channels.list[fhdhr_id]
|
channel_obj = self.fhdhr.device.channels.get_channel_obj("id", fhdhr_id, origin)
|
||||||
channel_dict = channel_obj.dict.copy()
|
channel_dict = channel_obj.dict.copy()
|
||||||
channel_dict["play_url"] = channel_obj.play_url
|
|
||||||
channelslist.append(channel_dict)
|
channel_dict["number"] = channel_obj.number
|
||||||
|
channel_dict["chan_thumbnail"] = channel_obj.thumbnail
|
||||||
|
channel_dict["m3u_url"] = channel_obj.api_m3u_url
|
||||||
|
|
||||||
|
channelslist[channel_dict["number"]] = channel_dict
|
||||||
if channel_dict["enabled"]:
|
if channel_dict["enabled"]:
|
||||||
channels_dict["Enabled"] += 1
|
channels_dict["Enabled"] += 1
|
||||||
|
|
||||||
return render_template('channels.html', request=request, fhdhr=self.fhdhr, channelslist=channelslist, channels_dict=channels_dict, list=list)
|
# Sort the channels
|
||||||
|
sorted_channel_list = channel_sort(list(channelslist.keys()))
|
||||||
|
sorted_chan_guide = []
|
||||||
|
for channel in sorted_channel_list:
|
||||||
|
sorted_chan_guide.append(channelslist[channel])
|
||||||
|
|
||||||
|
return render_template('channels.html', request=request, session=session, fhdhr=self.fhdhr, channelslist=sorted_chan_guide, channels_dict=channels_dict, origin=origin, origin_methods=origin_methods, list=list)
|
||||||
|
|||||||
@ -1,50 +0,0 @@
|
|||||||
from flask import request, render_template
|
|
||||||
import urllib.parse
|
|
||||||
|
|
||||||
|
|
||||||
class Cluster_HTML():
|
|
||||||
endpoints = ["/cluster", "/cluster.html"]
|
|
||||||
endpoint_name = "page_cluster_html"
|
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
|
||||||
self.fhdhr = fhdhr
|
|
||||||
self.location_dict = {
|
|
||||||
"name": self.fhdhr.config.dict["fhdhr"]["friendlyname"],
|
|
||||||
"location": self.fhdhr.api.base,
|
|
||||||
"joined": "N/A",
|
|
||||||
"url_query": self.fhdhr.api.base_quoted
|
|
||||||
}
|
|
||||||
|
|
||||||
def __call__(self, *args):
|
|
||||||
return self.get(*args)
|
|
||||||
|
|
||||||
def get(self, *args):
|
|
||||||
|
|
||||||
locations_list = []
|
|
||||||
|
|
||||||
if self.fhdhr.config.dict["fhdhr"]["discovery_address"]:
|
|
||||||
|
|
||||||
locations_list.append(self.location_dict)
|
|
||||||
|
|
||||||
fhdhr_list = self.fhdhr.device.cluster.get_list()
|
|
||||||
for location in list(fhdhr_list.keys()):
|
|
||||||
|
|
||||||
if location in list(self.fhdhr.device.cluster.cluster().keys()):
|
|
||||||
location_name = self.fhdhr.device.cluster.cluster()[location]["name"]
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
location_info_url = location + "/discover.json"
|
|
||||||
location_info_req = self.fhdhr.web.session.get(location_info_url)
|
|
||||||
location_info = location_info_req.json()
|
|
||||||
location_name = location_info["FriendlyName"]
|
|
||||||
except self.fhdhr.web.exceptions.ConnectionError:
|
|
||||||
self.fhdhr.logger.error("Unreachable: " + location)
|
|
||||||
location_dict = {
|
|
||||||
"name": location_name,
|
|
||||||
"location": location,
|
|
||||||
"joined": str(fhdhr_list[location]["Joined"]),
|
|
||||||
"url_query": urllib.parse.quote(location)
|
|
||||||
}
|
|
||||||
locations_list.append(location_dict)
|
|
||||||
|
|
||||||
return render_template('cluster.html', request=request, fhdhr=self.fhdhr, locations_list=locations_list)
|
|
||||||
@ -1,9 +1,12 @@
|
|||||||
from flask import request, render_template
|
from flask import request, render_template, session
|
||||||
|
|
||||||
|
|
||||||
class Diagnostics_HTML():
|
class Diagnostics_HTML():
|
||||||
endpoints = ["/diagnostics", "/diagnostics.html"]
|
endpoints = ["/diagnostics", "/diagnostics.html"]
|
||||||
endpoint_name = "page_diagnostics_html"
|
endpoint_name = "page_diagnostics_html"
|
||||||
|
endpoint_access_level = 2
|
||||||
|
endpoint_category = "tool_pages"
|
||||||
|
pretty_name = "Diagnostics"
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
def __init__(self, fhdhr):
|
||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
@ -15,111 +18,33 @@ class Diagnostics_HTML():
|
|||||||
|
|
||||||
base_url = request.url_root[:-1]
|
base_url = request.url_root[:-1]
|
||||||
|
|
||||||
button_list = []
|
button_dict = {}
|
||||||
|
|
||||||
button_list.append({
|
for route_group in list(session["route_list"].keys()):
|
||||||
"label": "Debug Json",
|
if route_group not in ["pages", "brython", "files", "tool_pages"]:
|
||||||
"hdhr": None,
|
button_dict[route_group] = []
|
||||||
"rmg": None,
|
for route_item in list(session["route_list"][route_group].keys()):
|
||||||
"other": "/api/debug",
|
if not session["route_list"][route_group][route_item]["name"].startswith("page_"):
|
||||||
})
|
button_link = session["route_list"][route_group][route_item]["endpoints"][0]
|
||||||
|
parameter_index = 0
|
||||||
|
for parameter in list(session["route_list"][route_group][route_item]["endpoint_default_parameters"].keys()):
|
||||||
|
parameter_val = session["route_list"][route_group][route_item]["endpoint_default_parameters"][parameter]
|
||||||
|
if not parameter_index:
|
||||||
|
button_link += "?"
|
||||||
|
else:
|
||||||
|
button_link += "&"
|
||||||
|
button_link += "%s=%s" % (parameter, parameter_val)
|
||||||
|
button_link = button_link.replace("<devicekey>", self.fhdhr.config.dict["main"]["uuid"])
|
||||||
|
button_link = button_link.replace("<base_url>", base_url)
|
||||||
|
curr_button_dict = {
|
||||||
|
"label": session["route_list"][route_group][route_item]["pretty_name"],
|
||||||
|
"link": button_link,
|
||||||
|
"methods": ",".join(session["route_list"][route_group][route_item]["endpoint_methods"]),
|
||||||
|
"button": True
|
||||||
|
}
|
||||||
|
if ("GET" not in session["route_list"][route_group][route_item]["endpoint_methods"]
|
||||||
|
or "<tuner_number>" in button_link or "<channel>" in button_link):
|
||||||
|
curr_button_dict["button"] = False
|
||||||
|
button_dict[route_group].append(curr_button_dict)
|
||||||
|
|
||||||
button_list.append({
|
return render_template('diagnostics.html', request=request, session=session, fhdhr=self.fhdhr, button_dict=button_dict, list=list)
|
||||||
"label": "Cluster Json",
|
|
||||||
"hdhr": None,
|
|
||||||
"rmg": None,
|
|
||||||
"other": "/api/cluster?method=get",
|
|
||||||
})
|
|
||||||
|
|
||||||
button_list.append({
|
|
||||||
"label": "Lineup XML",
|
|
||||||
"hdhr": "/lineup.xml",
|
|
||||||
"rmg": None,
|
|
||||||
"other": None,
|
|
||||||
})
|
|
||||||
|
|
||||||
button_list.append({
|
|
||||||
"label": "Lineup JSON",
|
|
||||||
"hdhr": "/hdhr/lineup.json",
|
|
||||||
"rmg": None,
|
|
||||||
"other": None,
|
|
||||||
})
|
|
||||||
|
|
||||||
button_list.append({
|
|
||||||
"label": "Lineup Status",
|
|
||||||
"hdhr": "/hdhr/lineup_status.json",
|
|
||||||
"rmg": None,
|
|
||||||
"other": None,
|
|
||||||
})
|
|
||||||
|
|
||||||
button_list.append({
|
|
||||||
"label": "Discover Json",
|
|
||||||
"hdhr": "/hdhr/discover.json",
|
|
||||||
"rmg": None,
|
|
||||||
"other": None,
|
|
||||||
})
|
|
||||||
|
|
||||||
button_list.append({
|
|
||||||
"label": "Device XML",
|
|
||||||
"hdhr": "/hdhr/device.xml",
|
|
||||||
"rmg": "/rmg/device.xml",
|
|
||||||
"other": None,
|
|
||||||
})
|
|
||||||
|
|
||||||
button_list.append({
|
|
||||||
"label": "RMG Identification XML",
|
|
||||||
"hdhr": "",
|
|
||||||
"rmg": "/rmg",
|
|
||||||
"other": None,
|
|
||||||
})
|
|
||||||
|
|
||||||
button_list.append({
|
|
||||||
"label": "RMG Devices Discover",
|
|
||||||
"hdhr": "",
|
|
||||||
"rmg": "/rmg/devices/discover",
|
|
||||||
"other": None,
|
|
||||||
})
|
|
||||||
|
|
||||||
button_list.append({
|
|
||||||
"label": "RMG Devices Probe",
|
|
||||||
"hdhr": "",
|
|
||||||
"rmg": "/rmg/devices/probe?uri=%s" % base_url,
|
|
||||||
"other": None,
|
|
||||||
})
|
|
||||||
|
|
||||||
button_list.append({
|
|
||||||
"label": "RMG Devices by DeviceKey",
|
|
||||||
"hdhr": "",
|
|
||||||
"rmg": "/rmg/devices/%s" % self.fhdhr.config.dict["main"]["uuid"],
|
|
||||||
"other": None,
|
|
||||||
})
|
|
||||||
|
|
||||||
button_list.append({
|
|
||||||
"label": "RMG Channels by DeviceKey",
|
|
||||||
"hdhr": "",
|
|
||||||
"rmg": "/rmg/devices/%s/channels" % self.fhdhr.config.dict["main"]["uuid"],
|
|
||||||
"other": None,
|
|
||||||
})
|
|
||||||
|
|
||||||
button_list.append({
|
|
||||||
"label": "RMG Scanners by DeviceKey",
|
|
||||||
"hdhr": "",
|
|
||||||
"rmg": "/rmg/devices/%s/scanners" % self.fhdhr.config.dict["main"]["uuid"],
|
|
||||||
"other": None,
|
|
||||||
})
|
|
||||||
|
|
||||||
button_list.append({
|
|
||||||
"label": "RMG Networks by DeviceKey",
|
|
||||||
"hdhr": "",
|
|
||||||
"rmg": "/rmg/devices/%s/networks" % self.fhdhr.config.dict["main"]["uuid"],
|
|
||||||
"other": None,
|
|
||||||
})
|
|
||||||
|
|
||||||
button_list.append({
|
|
||||||
"label": "RMG Scan by DeviceKey",
|
|
||||||
"hdhr": "",
|
|
||||||
"rmg": "/rmg/devices/%s/scan" % self.fhdhr.config.dict["main"]["uuid"],
|
|
||||||
"other": None,
|
|
||||||
})
|
|
||||||
|
|
||||||
return render_template('diagnostics.html', request=request, fhdhr=self.fhdhr, button_list=button_list)
|
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
from flask import request, render_template
|
from flask import request, render_template, session
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from fHDHR.tools import humanized_time
|
from fHDHR.tools import humanized_time, channel_sort
|
||||||
|
|
||||||
|
|
||||||
class Guide_HTML():
|
class Guide_HTML():
|
||||||
endpoints = ["/guide", "/guide.html"]
|
endpoints = ["/guide", "/guide.html"]
|
||||||
endpoint_name = "page_guide_html"
|
endpoint_name = "page_guide_html"
|
||||||
|
endpoint_access_level = 0
|
||||||
|
pretty_name = "Guide"
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
def __init__(self, fhdhr):
|
||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
@ -16,7 +18,7 @@ class Guide_HTML():
|
|||||||
|
|
||||||
def get(self, *args):
|
def get(self, *args):
|
||||||
|
|
||||||
nowtime = datetime.datetime.utcnow()
|
nowtime = datetime.datetime.utcnow().timestamp()
|
||||||
|
|
||||||
chan_guide_list = []
|
chan_guide_list = []
|
||||||
|
|
||||||
@ -25,19 +27,58 @@ class Guide_HTML():
|
|||||||
if source not in epg_methods:
|
if source not in epg_methods:
|
||||||
source = self.fhdhr.device.epg.def_method
|
source = self.fhdhr.device.epg.def_method
|
||||||
|
|
||||||
for channel in self.fhdhr.device.epg.whats_on_allchans(source):
|
if not source:
|
||||||
end_time = datetime.datetime.strptime(channel["listing"][0]["time_end"], '%Y%m%d%H%M%S +0000')
|
return render_template('guide.html', request=request, session=session, fhdhr=self.fhdhr, chan_guide_list=chan_guide_list, epg_methods=epg_methods, source=source, list=list)
|
||||||
remaining_time = humanized_time(int((end_time - nowtime).total_seconds()))
|
|
||||||
|
whatson = self.fhdhr.device.epg.whats_on_allchans(source)
|
||||||
|
|
||||||
|
# Sort the channels
|
||||||
|
sorted_channel_list = channel_sort(list(whatson.keys()))
|
||||||
|
sorted_chan_guide = {}
|
||||||
|
for channel in sorted_channel_list:
|
||||||
|
sorted_chan_guide[channel] = whatson[channel]
|
||||||
|
|
||||||
|
for channel in list(sorted_chan_guide.keys()):
|
||||||
|
if sorted_chan_guide[channel]["listing"][0]["time_end"]:
|
||||||
|
remaining_time = humanized_time(sorted_chan_guide[channel]["listing"][0]["time_end"] - nowtime)
|
||||||
|
else:
|
||||||
|
remaining_time = "N/A"
|
||||||
|
|
||||||
chan_dict = {
|
chan_dict = {
|
||||||
"name": channel["name"],
|
"name": sorted_chan_guide[channel]["name"],
|
||||||
"number": channel["number"],
|
"number": sorted_chan_guide[channel]["number"],
|
||||||
"chan_thumbnail": channel["thumbnail"],
|
"chan_thumbnail": sorted_chan_guide[channel]["thumbnail"],
|
||||||
"listing_title": channel["listing"][0]["title"],
|
"listing_title": sorted_chan_guide[channel]["listing"][0]["title"],
|
||||||
"listing_thumbnail": channel["listing"][0]["thumbnail"],
|
"listing_thumbnail": sorted_chan_guide[channel]["listing"][0]["thumbnail"],
|
||||||
"listing_description": channel["listing"][0]["description"],
|
"listing_description": sorted_chan_guide[channel]["listing"][0]["description"],
|
||||||
"remaining_time": str(remaining_time)
|
"listing_remaining_time": str(remaining_time)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for time_item in ["time_start", "time_end"]:
|
||||||
|
|
||||||
|
if not sorted_chan_guide[channel]["listing"][0][time_item]:
|
||||||
|
chan_dict["listing_%s" % time_item] = "N/A"
|
||||||
|
elif str(sorted_chan_guide[channel]["listing"][0][time_item]).endswith(tuple(["+0000", "+00:00"])):
|
||||||
|
chan_dict["listing_%s" % time_item] = str(sorted_chan_guide[channel]["listing"][0][time_item])
|
||||||
|
else:
|
||||||
|
chan_dict["listing_%s" % time_item] = str(datetime.datetime.fromtimestamp(sorted_chan_guide[channel]["listing"][0][time_item]))
|
||||||
|
|
||||||
|
if source in self.fhdhr.origins.valid_origins:
|
||||||
|
chan_obj = self.fhdhr.device.channels.get_channel_obj("origin_id", sorted_chan_guide[channel]["id"], source)
|
||||||
|
|
||||||
|
chan_dict["name"] = chan_obj.dict["name"]
|
||||||
|
chan_dict["number"] = chan_obj.number
|
||||||
|
chan_dict["chan_thumbnail"] = chan_obj.thumbnail
|
||||||
|
chan_dict["enabled"] = chan_obj.dict["enabled"]
|
||||||
|
chan_dict["m3u_url"] = chan_obj.api_m3u_url
|
||||||
|
|
||||||
|
chan_dict["listing_thumbnail"] = chan_dict["listing_thumbnail"] or chan_obj.thumbnail
|
||||||
|
else:
|
||||||
|
if not chan_dict["listing_thumbnail"]:
|
||||||
|
chan_dict["listing_thumbnail"] = chan_dict["chan_thumbnail"]
|
||||||
|
if not chan_dict["listing_thumbnail"]:
|
||||||
|
chan_dict["listing_thumbnail"] = "/api/images?method=generate&type=channel&message=%s" % chan_dict["number"]
|
||||||
|
|
||||||
chan_guide_list.append(chan_dict)
|
chan_guide_list.append(chan_dict)
|
||||||
|
|
||||||
return render_template('guide.html', request=request, fhdhr=self.fhdhr, chan_guide_list=chan_guide_list, epg_methods=epg_methods)
|
return render_template('guide.html', request=request, session=session, fhdhr=self.fhdhr, chan_guide_list=chan_guide_list, epg_methods=epg_methods, source=source, list=list)
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
from flask import request, render_template
|
from flask import request, render_template, session
|
||||||
|
|
||||||
|
|
||||||
class Index_HTML():
|
class Index_HTML():
|
||||||
endpoints = ["/index", "/index.html"]
|
endpoints = ["/index", "/index.html"]
|
||||||
endpoint_name = "page_index_html"
|
endpoint_name = "page_index_html"
|
||||||
|
endpoint_access_level = 0
|
||||||
|
pretty_name = "Index"
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
def __init__(self, fhdhr):
|
||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
@ -13,15 +15,13 @@ class Index_HTML():
|
|||||||
|
|
||||||
def get(self, *args):
|
def get(self, *args):
|
||||||
|
|
||||||
tuners_in_use = self.fhdhr.device.tuners.inuse_tuner_count()
|
origin = self.fhdhr.origins.valid_origins[0]
|
||||||
max_tuners = self.fhdhr.device.tuners.max_tuners
|
|
||||||
|
|
||||||
fhdhr_status_dict = {
|
fhdhr_status_dict = {
|
||||||
"Script Directory": str(self.fhdhr.config.internal["paths"]["script_dir"]),
|
"Script Directory": str(self.fhdhr.config.internal["paths"]["script_dir"]),
|
||||||
"Config File": str(self.fhdhr.config.config_file),
|
"Config File": str(self.fhdhr.config.config_file),
|
||||||
"Cache Path": str(self.fhdhr.config.internal["paths"]["cache_dir"]),
|
"Cache Path": str(self.fhdhr.config.internal["paths"]["cache_dir"]),
|
||||||
"Total Channels": len(self.fhdhr.device.channels.list),
|
"Total Channels": len(list(self.fhdhr.device.channels.list[origin].keys())),
|
||||||
"Tuner Usage": ("%s/%s" % (str(tuners_in_use), str(max_tuners))),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return render_template('index.html', request=request, fhdhr=self.fhdhr, fhdhr_status_dict=fhdhr_status_dict, list=list)
|
return render_template('index.html', request=request, session=session, fhdhr=self.fhdhr, fhdhr_status_dict=fhdhr_status_dict, list=list)
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
from flask import request, render_template
|
|
||||||
|
|
||||||
|
|
||||||
class Origin_HTML():
|
|
||||||
endpoints = ["/origin", "/origin.html"]
|
|
||||||
endpoint_name = "page_origin_html"
|
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
|
||||||
self.fhdhr = fhdhr
|
|
||||||
|
|
||||||
def __call__(self, *args):
|
|
||||||
return self.get(*args)
|
|
||||||
|
|
||||||
def get(self, *args):
|
|
||||||
|
|
||||||
origin_status_dict = self.fhdhr.originwrapper.get_status_dict()
|
|
||||||
origin_status_dict["Total Channels"] = len(self.fhdhr.device.channels.list)
|
|
||||||
return render_template('origin.html', request=request, fhdhr=self.fhdhr, origin_status_dict=origin_status_dict, list=list)
|
|
||||||
@ -1,9 +1,12 @@
|
|||||||
from flask import request, render_template
|
from flask import request, render_template, session
|
||||||
|
|
||||||
|
|
||||||
class Settings_HTML():
|
class Settings_HTML():
|
||||||
endpoints = ["/settings", "/settings.html"]
|
endpoints = ["/settings", "/settings.html"]
|
||||||
endpoint_name = "page_settings_html"
|
endpoint_name = "page_settings_html"
|
||||||
|
endpoint_access_level = 1
|
||||||
|
endpoint_category = "tool_pages"
|
||||||
|
pretty_name = "Settings"
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
def __init__(self, fhdhr):
|
||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
@ -19,15 +22,12 @@ class Settings_HTML():
|
|||||||
|
|
||||||
for config_item in list(self.fhdhr.config.conf_default[config_section].keys()):
|
for config_item in list(self.fhdhr.config.conf_default[config_section].keys()):
|
||||||
if self.fhdhr.config.conf_default[config_section][config_item]["config_web"]:
|
if self.fhdhr.config.conf_default[config_section][config_item]["config_web"]:
|
||||||
real_config_section = config_section
|
|
||||||
if config_section == self.fhdhr.config.dict["main"]["dictpopname"]:
|
|
||||||
real_config_section = "origin"
|
|
||||||
web_settings_dict[config_section][config_item] = {
|
web_settings_dict[config_section][config_item] = {
|
||||||
"value": self.fhdhr.config.dict[real_config_section][config_item],
|
"value": self.fhdhr.config.dict[config_section][config_item],
|
||||||
"value_default": self.fhdhr.config.conf_default[config_section][config_item]["value"],
|
"value_default": self.fhdhr.config.conf_default[config_section][config_item]["value"],
|
||||||
"hide": self.fhdhr.config.conf_default[config_section][config_item]["config_web_hidden"]
|
"hide": self.fhdhr.config.conf_default[config_section][config_item]["config_web_hidden"]
|
||||||
}
|
}
|
||||||
if not len(web_settings_dict[config_section].keys()):
|
if not len(web_settings_dict[config_section].keys()):
|
||||||
del web_settings_dict[config_section]
|
del web_settings_dict[config_section]
|
||||||
|
|
||||||
return render_template('settings.html', request=request, fhdhr=self.fhdhr, web_settings_dict=web_settings_dict, list=list)
|
return render_template('settings.html', request=request, session=session, fhdhr=self.fhdhr, web_settings_dict=web_settings_dict, list=list)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from flask import request, render_template
|
from flask import request, render_template, session
|
||||||
|
|
||||||
from fHDHR.tools import humanized_filesize
|
from fHDHR.tools import humanized_filesize
|
||||||
|
|
||||||
@ -6,6 +6,9 @@ from fHDHR.tools import humanized_filesize
|
|||||||
class Tuners_HTML():
|
class Tuners_HTML():
|
||||||
endpoints = ["/tuners", "/tuners.html"]
|
endpoints = ["/tuners", "/tuners.html"]
|
||||||
endpoint_name = "page_streams_html"
|
endpoint_name = "page_streams_html"
|
||||||
|
endpoint_access_level = 0
|
||||||
|
endpoint_category = "tool_pages"
|
||||||
|
pretty_name = "Tuners"
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
def __init__(self, fhdhr):
|
||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
@ -15,22 +18,36 @@ class Tuners_HTML():
|
|||||||
|
|
||||||
def get(self, *args):
|
def get(self, *args):
|
||||||
|
|
||||||
tuner_list = []
|
tuner_status_dict = {}
|
||||||
|
|
||||||
tuner_status = self.fhdhr.device.tuners.status()
|
tuner_status = self.fhdhr.device.tuners.status()
|
||||||
tuner_scanning = 0
|
for origin in list(tuner_status.keys()):
|
||||||
for tuner in list(tuner_status.keys()):
|
tuner_status_dict[origin] = {}
|
||||||
|
tuner_status_dict[origin]["scan_count"] = 0
|
||||||
|
tuner_status_dict[origin]["status_list"] = []
|
||||||
|
for tuner in list(tuner_status[origin].keys()):
|
||||||
|
if tuner_status[origin][tuner]["status"] == "Scanning":
|
||||||
|
tuner_status_dict[origin]["scan_count"] += 1
|
||||||
|
|
||||||
tuner_dict = {
|
tuner_dict = {
|
||||||
"number": str(tuner),
|
"number": str(tuner),
|
||||||
"status": str(tuner_status[tuner]["status"]),
|
"status": str(tuner_status[origin][tuner]["status"]),
|
||||||
|
"origin": "N/A",
|
||||||
|
"channel_number": "N/A",
|
||||||
|
"method": "N/A",
|
||||||
|
"running_time": "N/A",
|
||||||
|
"downloaded": "N/A",
|
||||||
}
|
}
|
||||||
if tuner_status[tuner]["status"] == "Active":
|
|
||||||
tuner_dict["channel_number"] = tuner_status[tuner]["channel"]
|
|
||||||
tuner_dict["method"] = tuner_status[tuner]["method"]
|
|
||||||
tuner_dict["play_duration"] = str(tuner_status[tuner]["Play Time"])
|
|
||||||
tuner_dict["downloaded"] = humanized_filesize(tuner_status[tuner]["downloaded"])
|
|
||||||
elif tuner_status[tuner]["status"] == "Scanning":
|
|
||||||
tuner_scanning += 1
|
|
||||||
|
|
||||||
tuner_list.append(tuner_dict)
|
if tuner_status[origin][tuner]["status"] in ["Active", "Acquired", "Scanning"]:
|
||||||
|
tuner_dict["origin"] = tuner_status[origin][tuner]["origin"]
|
||||||
|
tuner_dict["channel_number"] = tuner_status[origin][tuner]["channel"] or "N/A"
|
||||||
|
tuner_dict["running_time"] = str(tuner_status[origin][tuner]["running_time"])
|
||||||
|
|
||||||
return render_template('tuners.html', request=request, fhdhr=self.fhdhr, tuner_list=tuner_list, tuner_scanning=tuner_scanning)
|
if tuner_status[origin][tuner]["status"] in "Active":
|
||||||
|
tuner_dict["method"] = tuner_status[origin][tuner]["method"]
|
||||||
|
tuner_dict["downloaded"] = humanized_filesize(tuner_status[origin][tuner]["downloaded"])
|
||||||
|
|
||||||
|
tuner_status_dict[origin]["status_list"].append(tuner_dict)
|
||||||
|
|
||||||
|
return render_template('tuners.html', request=request, session=session, fhdhr=self.fhdhr, tuner_status_dict=tuner_status_dict, list=list)
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
from flask import request, render_template
|
from flask import request, render_template, session
|
||||||
|
|
||||||
|
|
||||||
class Version_HTML():
|
class Version_HTML():
|
||||||
endpoints = ["/version", "/version.html"]
|
endpoints = ["/version", "/version.html"]
|
||||||
endpoint_name = "page_version_html"
|
endpoint_name = "page_version_html"
|
||||||
|
endpoint_access_level = 1
|
||||||
|
endpoint_category = "tool_pages"
|
||||||
|
pretty_name = "Version"
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
def __init__(self, fhdhr):
|
||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
@ -12,7 +15,19 @@ class Version_HTML():
|
|||||||
return self.get(*args)
|
return self.get(*args)
|
||||||
|
|
||||||
def get(self, *args):
|
def get(self, *args):
|
||||||
|
|
||||||
version_dict = {}
|
version_dict = {}
|
||||||
for key in list(self.fhdhr.config.internal["versions"].keys()):
|
for key in list(self.fhdhr.config.internal["versions"].keys()):
|
||||||
version_dict[key] = self.fhdhr.config.internal["versions"][key]
|
version_dict[key] = self.fhdhr.config.internal["versions"][key]
|
||||||
return render_template('version.html', request=request, fhdhr=self.fhdhr, version_dict=version_dict, list=list)
|
|
||||||
|
# Sort the Version Info
|
||||||
|
sorted_version_list = sorted(version_dict, key=lambda i: (version_dict[i]['type'], version_dict[i]['name']))
|
||||||
|
sorted_version_dict = {
|
||||||
|
"fHDHR": version_dict["fHDHR"],
|
||||||
|
"fHDHR_web": version_dict["fHDHR_web"]
|
||||||
|
}
|
||||||
|
for version_item in sorted_version_list:
|
||||||
|
if version_item not in ["fHDHR", "fHDHR_web"]:
|
||||||
|
sorted_version_dict[version_item] = version_dict[version_item]
|
||||||
|
|
||||||
|
return render_template('version.html', request=request, session=session, fhdhr=self.fhdhr, version_dict=sorted_version_dict, list=list)
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
from flask import request, render_template
|
from flask import request, render_template, session
|
||||||
|
|
||||||
|
|
||||||
class xmlTV_HTML():
|
class xmlTV_HTML():
|
||||||
endpoints = ["/xmltv", "/xmltv.html"]
|
endpoints = ["/xmltv", "/xmltv.html"]
|
||||||
endpoint_name = "page_xmltv_html"
|
endpoint_name = "page_xmltv_html"
|
||||||
|
endpoint_access_level = 1
|
||||||
|
endpoint_category = "tool_pages"
|
||||||
|
pretty_name = "xmltv"
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
def __init__(self, fhdhr):
|
||||||
self.fhdhr = fhdhr
|
self.fhdhr = fhdhr
|
||||||
@ -13,4 +16,4 @@ class xmlTV_HTML():
|
|||||||
|
|
||||||
def get(self, *args):
|
def get(self, *args):
|
||||||
|
|
||||||
return render_template('xmltv.html', request=request, fhdhr=self.fhdhr)
|
return render_template('xmltv.html', request=request, session=session, fhdhr=self.fhdhr, list=list)
|
||||||
|
|||||||
@ -1,58 +0,0 @@
|
|||||||
from flask import Response, request
|
|
||||||
from io import BytesIO
|
|
||||||
import xml.etree.ElementTree
|
|
||||||
|
|
||||||
from fHDHR.tools import sub_el
|
|
||||||
|
|
||||||
|
|
||||||
class RMG_Device_XML():
|
|
||||||
endpoints = ["/rmg/device.xml"]
|
|
||||||
endpoint_name = "rmg_device_xml"
|
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
|
||||||
self.fhdhr = fhdhr
|
|
||||||
|
|
||||||
def __call__(self, *args):
|
|
||||||
return self.get(*args)
|
|
||||||
|
|
||||||
def get(self, *args):
|
|
||||||
"""Device.xml referenced from SSDP"""
|
|
||||||
|
|
||||||
base_url = request.url_root[:-1]
|
|
||||||
|
|
||||||
out = xml.etree.ElementTree.Element('root')
|
|
||||||
out.set('xmlns', "urn:schemas-upnp-org:device-1-0")
|
|
||||||
|
|
||||||
specVersion_out = sub_el(out, 'specVersion')
|
|
||||||
sub_el(specVersion_out, 'major', "1")
|
|
||||||
sub_el(specVersion_out, 'minor', "0")
|
|
||||||
|
|
||||||
device_out = sub_el(out, 'device')
|
|
||||||
|
|
||||||
sub_el(device_out, 'deviceType', "urn:plex-tv:device:Media:1")
|
|
||||||
|
|
||||||
sub_el(device_out, 'friendlyName', self.fhdhr.config.dict["fhdhr"]["friendlyname"])
|
|
||||||
sub_el(device_out, 'manufacturer', self.fhdhr.config.dict["fhdhr"]["reporting_manufacturer"])
|
|
||||||
sub_el(device_out, 'manufacturerURL', "https://github.com/fHDHR/%s" % self.fhdhr.config.dict["main"]["reponame"])
|
|
||||||
sub_el(device_out, 'modelName', self.fhdhr.config.dict["fhdhr"]["reporting_model"])
|
|
||||||
sub_el(device_out, 'modelNumber', self.fhdhr.config.internal["versions"]["fHDHR"])
|
|
||||||
|
|
||||||
sub_el(device_out, 'modelDescription', self.fhdhr.config.dict["fhdhr"]["friendlyname"])
|
|
||||||
sub_el(device_out, 'modelURL', "https://github.com/fHDHR/%s" % self.fhdhr.config.dict["main"]["reponame"])
|
|
||||||
|
|
||||||
serviceList_out = sub_el(device_out, 'serviceList')
|
|
||||||
service_out = sub_el(serviceList_out, 'service')
|
|
||||||
sub_el(out, 'URLBase', "%s" % base_url)
|
|
||||||
sub_el(service_out, 'serviceType', "urn:plex-tv:service:MediaGrabber:1")
|
|
||||||
sub_el(service_out, 'serviceId', "urn:plex-tv:serviceId:MediaGrabber")
|
|
||||||
|
|
||||||
sub_el(device_out, 'UDN', "uuid:%s" % self.fhdhr.config.dict["main"]["uuid"])
|
|
||||||
|
|
||||||
fakefile = BytesIO()
|
|
||||||
fakefile.write(b'<?xml version="1.0" encoding="UTF-8"?>\n')
|
|
||||||
fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8'))
|
|
||||||
device_xml = fakefile.getvalue()
|
|
||||||
|
|
||||||
return Response(status=200,
|
|
||||||
response=device_xml,
|
|
||||||
mimetype='application/xml')
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
from flask import Response
|
|
||||||
from io import BytesIO
|
|
||||||
import xml.etree.ElementTree
|
|
||||||
|
|
||||||
from fHDHR.tools import sub_el
|
|
||||||
|
|
||||||
|
|
||||||
class RMG_Devices_DeviceKey_Channels():
|
|
||||||
endpoints = ["/devices/<devicekey>/channels", "/rmg/devices/<devicekey>/channels"]
|
|
||||||
endpoint_name = "rmg_devices_devicekey_channels"
|
|
||||||
endpoint_methods = ["GET"]
|
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
|
||||||
self.fhdhr = fhdhr
|
|
||||||
|
|
||||||
def __call__(self, devicekey, *args):
|
|
||||||
return self.get(devicekey, *args)
|
|
||||||
|
|
||||||
def get(self, devicekey, *args):
|
|
||||||
"""Returns the current channels."""
|
|
||||||
|
|
||||||
out = xml.etree.ElementTree.Element('MediaContainer')
|
|
||||||
if devicekey == self.fhdhr.config.dict["main"]["uuid"]:
|
|
||||||
out.set('size', str(len(self.fhdhr.device.channels.list)))
|
|
||||||
for fhdhr_id in list(self.fhdhr.device.channels.list.keys()):
|
|
||||||
channel_obj = self.fhdhr.device.channels.list[fhdhr_id]
|
|
||||||
if channel_obj.enabled:
|
|
||||||
sub_el(out, 'Channel',
|
|
||||||
drm="0",
|
|
||||||
channelIdentifier="id://%s" % channel_obj.dict["number"],
|
|
||||||
name=channel_obj.dict["name"],
|
|
||||||
origin=channel_obj.dict["callsign"],
|
|
||||||
number=str(channel_obj.dict["number"]),
|
|
||||||
type="tv",
|
|
||||||
# TODO param
|
|
||||||
signalStrength="100",
|
|
||||||
signalQuality="100",
|
|
||||||
)
|
|
||||||
|
|
||||||
fakefile = BytesIO()
|
|
||||||
fakefile.write(b'<?xml version="1.0" encoding="UTF-8"?>\n')
|
|
||||||
fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8'))
|
|
||||||
device_xml = fakefile.getvalue()
|
|
||||||
|
|
||||||
return Response(status=200,
|
|
||||||
response=device_xml,
|
|
||||||
mimetype='application/xml')
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
from flask import request, redirect
|
|
||||||
import urllib.parse
|
|
||||||
|
|
||||||
|
|
||||||
class RMG_Devices_DeviceKey_Media():
|
|
||||||
endpoints = ["/devices/<devicekey>/media/<channel>", "/rmg/devices/<devicekey>/media/<channel>"]
|
|
||||||
endpoint_name = "rmg_devices_devicekey_media"
|
|
||||||
endpoint_methods = ["GET"]
|
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
|
||||||
self.fhdhr = fhdhr
|
|
||||||
|
|
||||||
def __call__(self, devicekey, channel, *args):
|
|
||||||
return self.get(devicekey, channel, *args)
|
|
||||||
|
|
||||||
def get(self, devicekey, channel, *args):
|
|
||||||
|
|
||||||
param = request.args.get('method', default=None, type=str)
|
|
||||||
self.fhdhr.logger.debug("param:%s" % param)
|
|
||||||
|
|
||||||
method = self.fhdhr.config.dict["fhdhr"]["stream_type"]
|
|
||||||
|
|
||||||
redirect_url = "/api/tuners?method=%s" % (method)
|
|
||||||
|
|
||||||
if str(channel).startswith('id://'):
|
|
||||||
channel = str(channel).replace('id://', '')
|
|
||||||
redirect_url += "&channel=%s" % str(channel)
|
|
||||||
|
|
||||||
redirect_url += "&accessed=%s" % urllib.parse.quote(request.url)
|
|
||||||
|
|
||||||
return redirect(redirect_url)
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
from flask import Response, request
|
|
||||||
from io import BytesIO
|
|
||||||
import xml.etree.ElementTree
|
|
||||||
|
|
||||||
from fHDHR.tools import sub_el
|
|
||||||
|
|
||||||
|
|
||||||
class RMG_Devices_Discover():
|
|
||||||
endpoints = ["/devices/discover", "/rmg/devices/discover"]
|
|
||||||
endpoint_name = "rmg_devices_discover"
|
|
||||||
endpoint_methods = ["GET", "POST"]
|
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
|
||||||
self.fhdhr = fhdhr
|
|
||||||
|
|
||||||
def __call__(self, *args):
|
|
||||||
return self.get(*args)
|
|
||||||
|
|
||||||
def get(self, *args):
|
|
||||||
"""This endpoint requests the grabber attempt to discover any devices it can, and it returns zero or more devices."""
|
|
||||||
|
|
||||||
base_url = request.url_root[:-1]
|
|
||||||
|
|
||||||
out = xml.etree.ElementTree.Element('MediaContainer')
|
|
||||||
out.set('size', "1")
|
|
||||||
sub_el(out, 'Device',
|
|
||||||
key=self.fhdhr.config.dict["main"]["uuid"],
|
|
||||||
make=self.fhdhr.config.dict["fhdhr"]["reporting_manufacturer"],
|
|
||||||
model=self.fhdhr.config.dict["fhdhr"]["reporting_model"],
|
|
||||||
modelNumber=self.fhdhr.config.internal["versions"]["fHDHR"],
|
|
||||||
protocol="livetv",
|
|
||||||
status="alive",
|
|
||||||
title=self.fhdhr.config.dict["fhdhr"]["friendlyname"],
|
|
||||||
tuners=str(self.fhdhr.config.dict["fhdhr"]["tuner_count"]),
|
|
||||||
uri=base_url,
|
|
||||||
uuid="device://tv.plex.grabbers.fHDHR/%s" % self.fhdhr.config.dict["main"]["uuid"],
|
|
||||||
thumb="favicon.ico",
|
|
||||||
interface='network'
|
|
||||||
# TODO add preferences
|
|
||||||
)
|
|
||||||
|
|
||||||
fakefile = BytesIO()
|
|
||||||
fakefile.write(b'<?xml version="1.0" encoding="UTF-8"?>\n')
|
|
||||||
fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8'))
|
|
||||||
device_xml = fakefile.getvalue()
|
|
||||||
|
|
||||||
return Response(status=200,
|
|
||||||
response=device_xml,
|
|
||||||
mimetype='application/xml')
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
from flask import Response, request
|
|
||||||
from io import BytesIO
|
|
||||||
import xml.etree.ElementTree
|
|
||||||
|
|
||||||
from fHDHR.tools import sub_el
|
|
||||||
|
|
||||||
|
|
||||||
class RMG_Devices_Probe():
|
|
||||||
endpoints = ["/devices/probe", "/rmg/devices/probe"]
|
|
||||||
endpoint_name = "rmg_devices_probe"
|
|
||||||
endpoint_methods = ["GET", "POST"]
|
|
||||||
|
|
||||||
def __init__(self, fhdhr):
|
|
||||||
self.fhdhr = fhdhr
|
|
||||||
|
|
||||||
def __call__(self, *args):
|
|
||||||
return self.get(*args)
|
|
||||||
|
|
||||||
def get(self, *args):
|
|
||||||
"""Probes a specific URI for a network device, and returns a device, if it exists at the given URI."""
|
|
||||||
|
|
||||||
base_url = request.url_root[:-1]
|
|
||||||
|
|
||||||
uri = request.args.get('uri', default=None, type=str)
|
|
||||||
|
|
||||||
out = xml.etree.ElementTree.Element('MediaContainer')
|
|
||||||
out.set('size', "1")
|
|
||||||
if uri == base_url:
|
|
||||||
sub_el(out, 'Device',
|
|
||||||
key=self.fhdhr.config.dict["main"]["uuid"],
|
|
||||||
make=self.fhdhr.config.dict["fhdhr"]["reporting_manufacturer"],
|
|
||||||
model=self.fhdhr.config.dict["fhdhr"]["reporting_model"],
|
|
||||||
modelNumber=self.fhdhr.config.internal["versions"]["fHDHR"],
|
|
||||||
protocol="livetv",
|
|
||||||
status="alive",
|
|
||||||
title=self.fhdhr.config.dict["fhdhr"]["friendlyname"],
|
|
||||||
tuners=str(self.fhdhr.config.dict["fhdhr"]["tuner_count"]),
|
|
||||||
uri=base_url,
|
|
||||||
uuid="device://tv.plex.grabbers.fHDHR/%s" % self.fhdhr.config.dict["main"]["uuid"],
|
|
||||||
thumb="favicon.ico",
|
|
||||||
interface='network'
|
|
||||||
)
|
|
||||||
|
|
||||||
fakefile = BytesIO()
|
|
||||||
fakefile.write(b'<?xml version="1.0" encoding="UTF-8"?>\n')
|
|
||||||
fakefile.write(xml.etree.ElementTree.tostring(out, encoding='UTF-8'))
|
|
||||||
device_xml = fakefile.getvalue()
|
|
||||||
|
|
||||||
return Response(status=200,
|
|
||||||
response=device_xml,
|
|
||||||
mimetype='application/xml')
|
|
||||||
@ -1,57 +1,74 @@
|
|||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>{{ fhdhr.config.dict["fhdhr"]["friendlyname"] }}</title>
|
<title>{{ fhdhr.config.dict["fhdhr"]["friendlyname"] }}</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<style>
|
|
||||||
table, th, td {border: 1px solid black;}
|
|
||||||
</style>
|
|
||||||
<link href="style.css" rel="stylesheet">
|
<link href="style.css" rel="stylesheet">
|
||||||
|
|
||||||
|
<script type="text/javascript" src="/brython.js"></script>
|
||||||
|
<script type="text/javascript" src="/brython_stdlib.js"></script>
|
||||||
|
<script type="text/python" src="brython.bry"></script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
|
||||||
<h1 style="text-align: center;">
|
<body onload="brython({debug: 1, indexedDB: false})">
|
||||||
|
|
||||||
|
<h1 class="center" style="text-align:center">
|
||||||
<span style="text-decoration: underline;"><strong><em>{{ fhdhr.config.dict["fhdhr"]["friendlyname"] }}</em></strong>
|
<span style="text-decoration: underline;"><strong><em>{{ fhdhr.config.dict["fhdhr"]["friendlyname"] }}</em></strong>
|
||||||
</span>
|
</span>
|
||||||
<img class="pull-left" src="/favicon.ico" alt="fHDHR Logo" width="100" height="100">
|
<img class="pull-left" src="/favicon.ico" alt="fHDHR Logo" width="100" height="100">
|
||||||
</h1>
|
</h1>
|
||||||
|
{% set retmessage = request.args.get('retmessage', default=None) %}
|
||||||
|
{% if retmessage %}
|
||||||
|
<p></p>
|
||||||
|
<p class="pull-left" style="font-size:1em">{{ retmessage }}</p>
|
||||||
|
{% endif %}
|
||||||
<br><br>
|
<br><br>
|
||||||
<p></p>
|
<p></p>
|
||||||
<div>
|
|
||||||
|
|
||||||
<button class="pull-left" onclick="OpenLink('/')">fHDHR</a></button>
|
<div>
|
||||||
<button class="pull-left" onclick="OpenLink('/origin')">{{ fhdhr.config.dict["main"]["servicename"] }}</a></button>
|
<button onclick="location.href='/index'" type="button">fHDHR</button>
|
||||||
<button class="pull-left" onclick="OpenLink('/channels')">Channels</a></button>
|
|
||||||
<button class="pull-left" onclick="OpenLink('/guide')">Guide</a></button>
|
{% for page_dict in session["route_list"]["pages"] %}
|
||||||
<button class="pull-left" onclick="OpenLink('/cluster')">Cluster/SSDP</a></button>
|
{% if session["route_list"]["pages"][page_dict]["name"] != "page_index_html" and fhdhr.config.dict["web_ui"]["access_level"] >= session["route_list"]["pages"][page_dict]["endpoint_access_level"] %}
|
||||||
<button class="pull-left" onclick="OpenLink('/tuners')">Tuners</a></button>
|
<button onclick="location.href='{{ session["route_list"]["pages"][page_dict]["endpoints"][0] }}'" type="button">{{ session["route_list"]["pages"][page_dict]["pretty_name"] }}</button>
|
||||||
<button class="pull-left" onclick="OpenLink('/xmltv')">xmltv</a></button>
|
{% endif %}
|
||||||
<button class="pull-left" onclick="OpenLink('/version')">Version</a></button>
|
{% endfor %}
|
||||||
<button class="pull-left" onclick="OpenLink('/diagnostics')">Diagnostics</a></button>
|
|
||||||
<button class="pull-left" onclick="OpenLink('/settings')">Settings</a></button>
|
|
||||||
|
|
||||||
<a class="pull-right" style="padding: 5px;" href="/api/xmltv?method=get&source={{ fhdhr.device.epg.def_method }}">xmltv</a>
|
<a class="pull-right" style="padding: 5px;" href="/api/xmltv?method=get&source={{ fhdhr.device.epg.def_method }}">xmltv</a>
|
||||||
<a class="pull-right" style="padding: 5px;" href="/api/m3u?method=get&channel=all">m3u</a>
|
<a class="pull-right" style="padding: 5px;" href="/api/m3u?method=get&channel=all">m3u</a>
|
||||||
|
<a class="pull-right" style="padding: 5px;" href="/api/w3u?method=get&channel=all">w3u</a>
|
||||||
|
|
||||||
</div>
|
<form class="pull-right" style="padding: 5px;" method="post" action="/api/settings?method=update&redirect={{ request.path }}">
|
||||||
|
<input type="hidden" name="config_section" value="web_ui">
|
||||||
|
<input type="hidden" name="config_name" value="access_level">
|
||||||
|
|
||||||
|
{% if fhdhr.config.dict["web_ui"]["access_level"] == 2 %}
|
||||||
|
<input type="hidden" name="config_value" value=0>
|
||||||
|
{% elif fhdhr.config.dict["web_ui"]["access_level"] == 1 %}
|
||||||
|
<input type="hidden" name="config_value" value=2>
|
||||||
|
{% else %}
|
||||||
|
<input type="hidden" name="config_value" value=1>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<input type="hidden" name="config_default" value=0>
|
||||||
|
{% if fhdhr.config.dict["web_ui"]["access_level"] == 2 %}
|
||||||
|
<a><input type="submit" value="Extra Advanced"></a>
|
||||||
|
{% elif fhdhr.config.dict["web_ui"]["access_level"] == 1 %}
|
||||||
|
<a><input type="submit" value="Advanced"></a>
|
||||||
|
{% else %}
|
||||||
|
<a><input type="submit" value="Basic"></a>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
<hr align="center" width="100%">
|
<hr align="center" width="100%">
|
||||||
|
</div>
|
||||||
|
|
||||||
{% set locations = fhdhr.device.cluster.get_cluster_dicts_web() %}
|
{% for page_dict in session["route_list"]["tool_pages"] %}
|
||||||
{% if locations %}
|
{% if session["route_list"]["tool_pages"][page_dict]["name"] != "page_index_html" and fhdhr.config.dict["web_ui"]["access_level"] >= session["route_list"]["tool_pages"][page_dict]["endpoint_access_level"] %}
|
||||||
<div>
|
<button onclick="location.href='{{ session["route_list"]["tool_pages"][page_dict]["endpoints"][0] }}'" type="button">{{ session["route_list"]["tool_pages"][page_dict]["pretty_name"] }}</button>
|
||||||
{% for location in locations %}
|
{% endif %}
|
||||||
<button class="pull-left" onclick="OpenLink('{{ location["base_url"] }}')">{{ location["name"] }}</a></button>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
|
||||||
<hr align="center" width="100%">
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% set retmessage = request.args.get('retmessage', default=None) %}
|
|
||||||
{% if retmessage %}
|
|
||||||
<p>{{ retmessage }}</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
<script>
|
|
||||||
function OpenLink(NewURL) {window.open(NewURL, "_self");}
|
|
||||||
</script>
|
|
||||||
|
|||||||
@ -4,30 +4,40 @@
|
|||||||
|
|
||||||
<h4 style="text-align: center;">{{ fhdhr.config.dict["fhdhr"]["friendlyname"] }} Channels</h4>
|
<h4 style="text-align: center;">{{ fhdhr.config.dict["fhdhr"]["friendlyname"] }} Channels</h4>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{% for origin in origin_methods %}
|
||||||
|
<button onclick="location.href='/channels?origin={{ origin }}'" type="button">{{ origin }}</button>
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
|
||||||
<div style="text-align: center;">
|
<div style="text-align: center;">
|
||||||
<button onclick="OpenLink('/api/tuners?method=scan&redirect=%2Fchannels')">Force Channel Update</a></button><p> Note: This may take some time.</p>
|
<button onclick="location.href='/api/tuners?method=scan&origin={{ origin }}&redirect=/channels?origin={{ origin }}'" type="button">Force Channel Update</button>
|
||||||
|
<p> Note: This may take some time.</p>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<table class="center" style="width:50%">
|
<div class="container">
|
||||||
<tr>
|
<table class="table-small center">
|
||||||
<th></th>
|
<tbody>
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{% for key in list(channels_dict.keys()) %}
|
{% for key in list(channels_dict.keys()) %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ key }}</td>
|
<td>{{ key }}</td>
|
||||||
<td>{{ channels_dict[key] }}</td>
|
<td>{{ channels_dict[key] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br>
|
||||||
<div style="text-align: center;">
|
<div style="text-align: center;">
|
||||||
<button onclick="OpenLink('/channels_editor')">Edit Channels</a></button>
|
<button onclick="location.href='/channels_editor?origin={{ origin }}'" type="button">Edit Channels</button>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<table class="center" style="width:100%">
|
<div class="container">
|
||||||
|
<table class="table-scroll center small-first-col">
|
||||||
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Play</th>
|
<th>Play</th>
|
||||||
<th>Channel Name</th>
|
<th>Channel Name</th>
|
||||||
@ -37,14 +47,17 @@
|
|||||||
<th>Enabled</th>
|
<th>Enabled</th>
|
||||||
<th>Favorite</th>
|
<th>Favorite</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody class="body-half-screen">
|
||||||
{% for chan_dict in channelslist %}
|
{% for chan_dict in channelslist %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{% if chan_dict["enabled"] %}
|
{% if chan_dict["enabled"] %}
|
||||||
<a href="{{ chan_dict["play_url"] }}">Play</a>
|
<a href="{{ chan_dict["m3u_url"] }}">Play</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ chan_dict["m3u_url"] }}" style="visibility:hidden">Play</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>{{ chan_dict["name"] }}</td>
|
<td>{{ chan_dict["name"] }}</td>
|
||||||
@ -68,7 +81,9 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<td>No</td>
|
<td>No</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -3,13 +3,39 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<h4 style="text-align: center;">{{ fhdhr.config.dict["fhdhr"]["friendlyname"] }} Channels Editor</h4>
|
<h4 style="text-align: center;">{{ fhdhr.config.dict["fhdhr"]["friendlyname"] }} Channels Editor</h4>
|
||||||
|
<input type="hidden" id="origin" value="{{ origin }}">
|
||||||
|
|
||||||
<div style="text-align: center;">
|
<div class="container">
|
||||||
<button onclick="OpenLink('/api/channels?method=enable&channel=all&redirect=%2Fchannels_editor')">Enable All</a></button>
|
<table class="table-medium center">
|
||||||
<button onclick="OpenLink('/api/channels?method=disable&channel=all&redirect=%2Fchannels_editor')">Disable All</a></button>
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<form id="Chan_Edit_Enable_Toggle" method="post">
|
||||||
|
<button type="button" id="enable_button" value="0">Disable All</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<form id="Chan_Edit_Favorite_Toggle" method="post">
|
||||||
|
<button type="button" id="favorite_button" value="0">Unfavorite All</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<form id="Chan_Edit_Modify" method="post">
|
||||||
|
<button type="Submit" id="modify_all">Modify All</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form id="Chan_Edit_Reset" method="post">
|
||||||
|
<button type="Submit" id="reset_all">Reset All</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="center" style="width:100%">
|
<div class="container">
|
||||||
|
<table class="table-scroll center text-edit-cols">
|
||||||
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Channel Name</th>
|
<th>Channel Name</th>
|
||||||
<th>Channel CallSign</th>
|
<th>Channel CallSign</th>
|
||||||
@ -17,55 +43,58 @@
|
|||||||
<th>Channel Thumbnail</th>
|
<th>Channel Thumbnail</th>
|
||||||
<th>Enabled</th>
|
<th>Enabled</th>
|
||||||
<th>Favorite</th>
|
<th>Favorite</th>
|
||||||
<th>Update</th>
|
<th>Actions</th>
|
||||||
<th>Reset</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody class="body-half-screen">
|
||||||
|
|
||||||
{% for chan_dict in channelslist %}
|
{% for chan_dict in channelslist %}
|
||||||
|
<tr>
|
||||||
|
<input type="hidden" name="id" class="channels" value="{{ chan_dict["id"] }}">
|
||||||
|
<input type="hidden" class="reset" name="id" value="{{ chan_dict["id"] }}">
|
||||||
|
|
||||||
|
<td><input type="text" class="channels" name="name" value="{{ chan_dict["name"] }}" placeholder="{{ chan_dict["name"] }}"></td>
|
||||||
|
<input type="hidden" class="reset" name="name" value="{{ chan_dict["origin_name"] }}" placeholder="{{ chan_dict["name"] }}">
|
||||||
|
|
||||||
|
<td><input type="text" class="channels" name="callsign" value="{{ chan_dict["callsign"] }}" placeholder="{{ chan_dict["callsign"] }}"></td>
|
||||||
|
<input type="hidden" class="reset" name="callsign" value="{{ chan_dict["origin_callsign"] }}" placeholder="{{ chan_dict["callsign"] }}">
|
||||||
|
|
||||||
|
<td><input type="text" class="channels" name="number" value="{{ chan_dict["number"] }}" placeholder="{{ chan_dict["number"] }}"></td>
|
||||||
|
<input type="hidden" class="reset" name="number" value="{{ chan_dict["origin_number"] }}" placeholder="{{ chan_dict["number"] }}">
|
||||||
|
|
||||||
|
<td><input type="text" class="channels" name="thumbnail" value="{{ chan_dict["thumbnail"] }}" placeholder="{{ chan_dict["thumbnail"] }}"></td>
|
||||||
|
<input type="hidden" class="reset" name="thumbnail" value="{{ chan_dict["origin_thumbnail"] }}" placeholder="{{ chan_dict["thumbnail"] }}">
|
||||||
|
|
||||||
<form method="post" action="/api/channels?method=update&redirect=%2Fchannels_editor">
|
|
||||||
<input type="hidden" name="id" value={{ chan_dict["id"] }}>
|
|
||||||
<td data-th="Channel Name"><input type="text" name="name" value={{ chan_dict["name"] }}></td>
|
|
||||||
<td data-th="Channel Calsign"><input type="text" name="callsign" value={{ chan_dict["callsign"] }}></td>
|
|
||||||
<td data-th="Channel Number"><input type="text" name="number" value={{ chan_dict["number"] }}></td>
|
|
||||||
<td data-th="Channel Thumbnail"><input type="text" name="thumbnail" value={{ chan_dict["thumbnail"] }}></td>
|
|
||||||
<td>
|
|
||||||
<select name="enabled">
|
|
||||||
{% if chan_dict["enabled"] %}
|
{% if chan_dict["enabled"] %}
|
||||||
<option value=True selected>Enabled</option>
|
<td><input type="checkbox" class="channels" name="enabled" value=True checked placeholder="{{ chan_dict["enabled"] }}"></td>
|
||||||
<option value=False>Disabled</option>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<option value=True>Enabled</option>
|
<td><input type="checkbox" class="channels" name="enabled" value=True placeholder="{{ chan_dict["enabled"] }}"></td>
|
||||||
<option value=False selected>Disabled</option>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</select>
|
<input type="hidden" class="reset" name="enabled" value=True placeholder="{{ chan_dict["enabled"] }}">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<select name="favorite">
|
|
||||||
{% if chan_dict["favorite"] %}
|
{% if chan_dict["favorite"] %}
|
||||||
<option value=1 selected>Yes</option>
|
<td><input type="checkbox" class="channels" name="favorite" value=1 checked placeholder="{{ chan_dict["favorite"] }}"></td>
|
||||||
<option value=0>No</option>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<option value=1>Yes</option>
|
<td><input type="checkbox" class="channels" name="favorite" value=1 placeholder="{{ chan_dict["favorite"] }}"></td>
|
||||||
<option value=0 selected>No</option>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</select>
|
<input type="hidden" class="reset" name="favorite" value="0" placeholder="{{ chan_dict["favorite"] }}">
|
||||||
</td>
|
|
||||||
<td data-th="Update"><input type="submit" value="Update"></td>
|
<td>
|
||||||
|
<form id="Chan_Edit_Modify" method="post">
|
||||||
|
<button type="Submit" id="modify_{{ chan_dict["id"] }}">Modify</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<form method="post" action="/api/channels?method=update&redirect=%2Fchannels_editor">
|
<form id="Chan_Edit_Reset" method="post">
|
||||||
<input type="hidden" name="id" value={{ chan_dict["id"] }}>
|
<button type="Submit" id="reset_{{ chan_dict["id"] }}">Reset</button>
|
||||||
<input type="hidden" name="name" value={{ chan_dict["origin_name"] }}>
|
|
||||||
<input type="hidden" name="callsign" value={{ chan_dict["origin_callsign"] }}>
|
|
||||||
<input type="hidden" name="number" value={{ chan_dict["origin_number"] }}>
|
|
||||||
<input type="hidden" name="thumbnail" value={{ chan_dict["origin_thumbnail"] }}>
|
|
||||||
<input type="hidden" name="enabled" value=True>
|
|
||||||
<td data-th="Reset"><input type="submit" value="Reset"></td>
|
|
||||||
</form>
|
</form>
|
||||||
|
</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<input type="hidden" name="id" class="channels" value="dummy">
|
||||||
|
<input type="hidden" class="reset" name="id" value="dummy">
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,51 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<h4 style="text-align: center;">Cluster/SSDP</h4>
|
|
||||||
{% if not fhdhr.config.dict["fhdhr"]["discovery_address"] %}
|
|
||||||
<p style="text-align: center;">Discovery Address must be set for Cluster/SSDP</p>
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
<div style="text-align: center;">
|
|
||||||
<button onclick="OpenLink('/api/cluster?method=scan&redirect=%2Fcluster')">Force Scan</a></button>
|
|
||||||
<button onclick="OpenLink('/api/cluster?method=alive&redirect=%2Fcluster')">Send Alive</a></button>
|
|
||||||
<button onclick="OpenLink('/api/cluster?method=disconnect&redirect=%2Fcluster')">Disconnect</a></button>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
|
|
||||||
<table class="center" style="width:50%">
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Location</th>
|
|
||||||
<th>Joined</th>
|
|
||||||
<th>Options</th>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{% for location in locations_list %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ location["name"] }}</td>
|
|
||||||
<td>{{ location["location"] }}</td>
|
|
||||||
<td>{{ location["joined"] }}</td>
|
|
||||||
|
|
||||||
<td>
|
|
||||||
<div>
|
|
||||||
|
|
||||||
{% if location["joined"] in ["True", "False"] %}
|
|
||||||
<button onclick="OpenLink('{{ location["location"] }}')">Visit</a></button>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if location["joined"] == "True" %}
|
|
||||||
<button onclick="OpenLink('/api/cluster?method=del&location={{ location["url_query"] }}&redirect=%2Fcluster')">Remove</a></button>
|
|
||||||
{% elif location["joined"] == "False" %}
|
|
||||||
<button onclick="OpenLink('/api/cluster?method=add&location={{ location["url_query"] }}&redirect=%2Fcluster')">Add</a></button>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
@ -4,33 +4,35 @@
|
|||||||
|
|
||||||
<h4 style="text-align: center;">fHDHR Diagnostic Links</h4>
|
<h4 style="text-align: center;">fHDHR Diagnostic Links</h4>
|
||||||
|
|
||||||
<table class="center" style="width:100%">
|
{% for route_group in list(button_dict.keys()) %}
|
||||||
<tr>
|
|
||||||
<th>Item</th>
|
|
||||||
<th>HDHR</th>
|
|
||||||
<th>RMG</th>
|
|
||||||
<th>Non-Specific</th>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{% for button_item in button_list %}
|
<h4 style="text-align: center;">{{ route_group }}</h4>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<table class="table-settings center action-col text-edit-cols">
|
||||||
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ button_item["label"] }}</td>
|
<th>Link</th>
|
||||||
{% if button_item["hdhr"] %}
|
<th>Methods</th>
|
||||||
<td><button onclick="OpenLink('{{ button_item["hdhr"] }}')">{{ button_item["label"] }}</a></button></td>
|
|
||||||
{% else %}
|
|
||||||
<td></td>
|
|
||||||
{% endif %}
|
|
||||||
{% if button_item["rmg"] %}
|
|
||||||
<td><button onclick="OpenLink('{{ button_item["rmg"] }}')">{{ button_item["label"] }}</a></button></td>
|
|
||||||
{% else %}
|
|
||||||
<td></td>
|
|
||||||
{% endif %}
|
|
||||||
{% if button_item["other"] %}
|
|
||||||
<td><button onclick="OpenLink('{{ button_item["other"] }}')">{{ button_item["label"] }}</a></button></td>
|
|
||||||
{% else %}
|
|
||||||
<td></td>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for button_item in button_dict[route_group] %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{% if button_item["button"] %}
|
||||||
|
<button onclick="location.href='{{ button_item["link"] }}'" type="button">{{ button_item["label"] }}</button>
|
||||||
|
{% else %}
|
||||||
|
<a>{{ button_item["link"] }}</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td><a>{{ button_item["methods"] }}</a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -6,31 +6,54 @@
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
{% for epg_method in epg_methods %}
|
{% for epg_method in epg_methods %}
|
||||||
<button onclick="OpenLink('/guide?source={{ epg_method }}')">{{ epg_method }}</a></button>
|
<button onclick="location.href='/guide?source={{ epg_method }}'" type="button">{{ epg_method }}</button>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<table class="center" style="width:100%">
|
<div class="container">
|
||||||
|
<table class="table-scroll">
|
||||||
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
{% if source in fhdhr.origins.valid_origins %}
|
||||||
|
<th>Play</th>
|
||||||
|
{% endif %}
|
||||||
<th>Channel Name</th>
|
<th>Channel Name</th>
|
||||||
<th>Channel Number</th>
|
<th>Channel Number</th>
|
||||||
<th>Channel Thumbnail</th>
|
<th>Channel Thumbnail</th>
|
||||||
<th>Content Title</th>
|
<th>Content Title</th>
|
||||||
<th>Content Thumbnail</th>
|
<th>Content Thumbnail</th>
|
||||||
<th>Content Description</th>
|
<th>Content Description</th>
|
||||||
|
<th>Start Time (UTC)</th>
|
||||||
|
<th>End Time (UTC)</th>
|
||||||
<th>Content Remaining Time</th>
|
<th>Content Remaining Time</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody class="body-half-screen">
|
||||||
{% for chan_dict in chan_guide_list %}
|
{% for chan_dict in chan_guide_list %}
|
||||||
<tr>
|
<tr>
|
||||||
|
{% if source in fhdhr.origins.valid_origins %}
|
||||||
|
<td>
|
||||||
|
{% if chan_dict["enabled"] %}
|
||||||
|
<a href="{{ chan_dict["m3u_url"] }}">Play</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ chan_dict["m3u_url"] }}" style="visibility:hidden">Play</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
<td>{{ chan_dict["name"] }}</td>
|
<td>{{ chan_dict["name"] }}</td>
|
||||||
<td>{{ chan_dict["number"] }}</td>
|
<td>{{ chan_dict["number"] }}</td>
|
||||||
<td><img src="{{ chan_dict["chan_thumbnail"] }}" alt="{{ chan_dict["name"] }}" width="100" height="100"></td>
|
<td><img src="{{ chan_dict["chan_thumbnail"] }}" alt="{{ chan_dict["name"] }}" width="100" height="100"></td>
|
||||||
<td>{{ chan_dict["listing_title"] }}</td>
|
<td>{{ chan_dict["listing_title"] }}</td>
|
||||||
<td><img src="{{ chan_dict["listing_thumbnail"] }}" alt="{{ chan_dict["listing_title"] }}" width="100" height="100"></td>
|
<td><img src="{{ chan_dict["listing_thumbnail"] }}" alt="{{ chan_dict["listing_title"] }}" width="100" height="100"></td>
|
||||||
<td>{{ chan_dict["listing_description"] }}</td>
|
<td>{{ chan_dict["listing_description"] }}</td>
|
||||||
<td>{{ chan_dict["remaining_time"] }}</td>
|
<td>{{ chan_dict["listing_time_start"] }}</td>
|
||||||
|
<td>{{ chan_dict["listing_time_end"] }}</td>
|
||||||
|
<td>{{ chan_dict["listing_remaining_time"] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -4,17 +4,19 @@
|
|||||||
|
|
||||||
<h4 style="text-align: center;">fHDHR Status</h4>
|
<h4 style="text-align: center;">fHDHR Status</h4>
|
||||||
|
|
||||||
<table class="center" style="width:50%">
|
|
||||||
<tr>
|
<div class="container">
|
||||||
<th></th>
|
<table class="table-medium center">
|
||||||
<th></th>
|
<tbody>
|
||||||
</tr>
|
|
||||||
|
|
||||||
{% for key in list(fhdhr_status_dict.keys()) %}
|
{% for key in list(fhdhr_status_dict.keys()) %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ key }}</td>
|
<td class="rTableCell">{{ key }}</td>
|
||||||
<td>{{ fhdhr_status_dict[key] }}</td>
|
<td class="rTableCell">{{ fhdhr_status_dict[key] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<h4 style="text-align: center;">{{ fhdhr.config.dict["main"]["servicename"] }} Status</h4>
|
|
||||||
|
|
||||||
<table class="center" style="width:50%">
|
|
||||||
<tr>
|
|
||||||
<th></th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{% for key in list(origin_status_dict.keys()) %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ key }}</td>
|
|
||||||
<td>{{ origin_status_dict[key] }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
@ -4,56 +4,64 @@
|
|||||||
|
|
||||||
<h4 style="text-align: center;">fHDHR Settings</h4>
|
<h4 style="text-align: center;">fHDHR Settings</h4>
|
||||||
|
|
||||||
<h4 style="text-align: center;">Settings will require a manual restart.</h4>
|
<h4 style="text-align: center;">Some Settings will require a manual restart.</h4>
|
||||||
|
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<button onclick="location.href='/api/settings?method=restart&redirect=/settings'" type="button">Restart fHDHR</button>
|
||||||
|
<p> Note: This may take some time, and you will have to refresh your page.</p>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
|
||||||
{% for config_section in list(web_settings_dict.keys()) %}
|
{% for config_section in list(web_settings_dict.keys()) %}
|
||||||
|
|
||||||
{% if config_section == "origin" %}
|
|
||||||
<h4 style="text-align: center;">{{ fhdhr.config.dict["main"]["dictpopname"] }}</h4>
|
|
||||||
{% else %}
|
|
||||||
<h4 style="text-align: center;">{{ config_section }}</h4>
|
<h4 style="text-align: center;">{{ config_section }}</h4>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<table class="center" style="width:100%">
|
|
||||||
|
<div class="container">
|
||||||
|
<table class="table-settings center action-col text-edit-cols">
|
||||||
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Config Name</th>
|
<th>Config Name</th>
|
||||||
<th>Config Default Value</th>
|
<th>Config Default Value</th>
|
||||||
<th>Config Value</th>
|
<th>Config Value</th>
|
||||||
<th>Update</th>
|
<th>Action</th>
|
||||||
<th>Reset</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
{% for config_item in list(web_settings_dict[config_section].keys()) %}
|
{% for config_item in list(web_settings_dict[config_section].keys()) %}
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td data-th="Config Name">{{ config_item }}</td>
|
<td>{{ config_item }}</td>
|
||||||
|
<td>{{ web_settings_dict[config_section][config_item]["value_default"] }}</td>
|
||||||
<td data-th="Config Default Value">{{ web_settings_dict[config_section][config_item]["value_default"] }}</td>
|
<td>
|
||||||
|
<form method="post" action="/api/settings?method=update&redirect=/settings">
|
||||||
<form method="post" action="/api/settings?method=update&redirect=%2Fsettings">
|
<input type="hidden" name="config_section" value="{{ config_section }}">
|
||||||
<input type="hidden" name="config_section" value={{ config_section }}>
|
<input type="hidden" name="config_name" value="{{ config_item }}">
|
||||||
<input type="hidden" name="config_name" value={{ config_item }}>
|
<input type="hidden" name="config_default" value="{{ web_settings_dict[config_section][config_item]["value_default"] }}">
|
||||||
<input type="hidden" name="config_default" value={{ web_settings_dict[config_section][config_item]["value_default"] }}>
|
|
||||||
{% if web_settings_dict[config_section][config_item]["hide"] %}
|
{% if web_settings_dict[config_section][config_item]["hide"] %}
|
||||||
<td data-th="Config Value"><input type="text" size="50" name="config_value" value=**************></td>
|
<input type="text" size="25" name="config_value" value="**************">
|
||||||
{% else %}
|
{% else %}
|
||||||
<td data-th="Config Value"><input type="text" size="50" name="config_value" value={{ web_settings_dict[config_section][config_item]["value"] }}></td>
|
<input type="text" size="25" name="config_value" value="{{ web_settings_dict[config_section][config_item]["value"] }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td data-th="Update"><input type="submit" value="Update"></td>
|
</td>
|
||||||
|
<td style="display:flex;">
|
||||||
|
<span style="margin:auto">
|
||||||
|
<input type="submit" value="Update">
|
||||||
</form>
|
</form>
|
||||||
|
</span>
|
||||||
<form method="post" action="/api/settings?method=update&redirect=%2Fsettings">
|
<form style="margin:auto">
|
||||||
<input type="hidden" name="config_section" value={{ config_section }}>
|
<input type="hidden" name="config_section" value="{{ config_section }}">
|
||||||
<input type="hidden" name="config_name" value={{ config_item }}>
|
<input type="hidden" name="config_name" value="{{ config_item }}">
|
||||||
<input type="hidden" name="config_value" value={{ web_settings_dict[config_section][config_item]["value_default"] }}>
|
<input type="hidden" name="config_value" value="{{ web_settings_dict[config_section][config_item]["value_default"] }}">
|
||||||
<input type="hidden" name="config_default" value={{ web_settings_dict[config_section][config_item]["value_default"] }}>
|
<input type="hidden" name="config_default" value="{{ web_settings_dict[config_section][config_item]["value_default"] }}">
|
||||||
<td data-th="Reset"><input type="submit" value="Reset"></td>
|
<input type="submit" value="Reset">
|
||||||
</form>
|
</form>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
|||||||
@ -4,46 +4,48 @@
|
|||||||
|
|
||||||
<h4 style="text-align: center;">fHDHR Streams</h4>
|
<h4 style="text-align: center;">fHDHR Streams</h4>
|
||||||
|
|
||||||
<table class="center" style="width:100%">
|
{% for origin in list(tuner_status_dict.keys()) %}
|
||||||
|
|
||||||
|
<h4 style="text-align: center;">{{ origin }}</h4>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<table class="table-medium center action-col">
|
||||||
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Tuner</th>
|
<th>Tuner</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
|
<th>Origin</th>
|
||||||
<th>Channel</th>
|
<th>Channel</th>
|
||||||
<th>Method</th>
|
<th>Method</th>
|
||||||
<th>Time Active</th>
|
<th>Time Active</th>
|
||||||
<th>Total Downloaded</th>
|
<th>Total Downloaded</th>
|
||||||
<th>Options</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% for tuner_dict in tuner_list %}
|
{% for tuner_dict in tuner_status_dict[origin]["status_list"] %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ tuner_dict["number"] }}</td>
|
<td>{{ tuner_dict["number"] }}</td>
|
||||||
<td>{{ tuner_dict["status"] }}</td>
|
<td>{{ tuner_dict["status"] }}</td>
|
||||||
{% if tuner_dict["status"] in ["Active", "Acquired"] %}
|
<td>{{ tuner_dict["origin"] }}</td>
|
||||||
<td>{{ tuner_dict["channel_number"] }}</td>
|
<td>{{ tuner_dict["channel_number"] }}</td>
|
||||||
{% else %}
|
|
||||||
<td>N/A</td>
|
|
||||||
{% endif %}
|
|
||||||
{% if tuner_dict["status"] == "Active" %}
|
|
||||||
<td>{{ tuner_dict["method"] }}</td>
|
<td>{{ tuner_dict["method"] }}</td>
|
||||||
<td>{{ tuner_dict["play_duration"] }}</td>
|
<td>{{ tuner_dict["running_time"] }}</td>
|
||||||
<td>{{ tuner_dict["downloaded"] }}</td>
|
<td>{{ tuner_dict["downloaded"] }}</td>
|
||||||
{% else %}
|
|
||||||
<td>N/A</td>
|
|
||||||
<td>N/A</td>
|
|
||||||
<td>N/A</td>
|
|
||||||
{% endif %}
|
|
||||||
<td>
|
<td>
|
||||||
<div>
|
|
||||||
{% if tuner_dict["status"] != "Inactive" %}
|
{% if tuner_dict["status"] != "Inactive" %}
|
||||||
<button onclick="OpenLink('/api/tuners?method=close&tuner={{ tuner_dict["number"] }}&redirect=%2Ftuners')">Close</a></button>
|
<button onclick="location.href='/api/tuners?method=close&tuner={{ tuner_dict["number"] }}&origin={{ origin }}&redirect=/tuners'" type="button">Close</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if not tuner_scanning and tuner_dict["status"] == "Inactive" %}
|
{% if not tuner_status_dict[origin]["scan_count"] and tuner_dict["status"] == "Inactive" %}
|
||||||
<button onclick="OpenLink('/api/tuners?method=scan&tuner={{ tuner_dict["number"] }}&redirect=%2Ftuners')">Channel Scan</a></button>
|
<button onclick="location.href='/api/tuners?method=scan&tuner={{ tuner_dict["number"] }}&origin={{ origin }}&redirect=/tuners'" type="button">Channel Scan</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -4,17 +4,19 @@
|
|||||||
|
|
||||||
<h4 style="text-align: center;">fHDHR Version Information</h4>
|
<h4 style="text-align: center;">fHDHR Version Information</h4>
|
||||||
|
|
||||||
<table class="center" style="width:50%">
|
|
||||||
<tr>
|
|
||||||
<th></th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<table class="table-medium center">
|
||||||
|
<tbody>
|
||||||
{% for key in list(version_dict.keys()) %}
|
{% for key in list(version_dict.keys()) %}
|
||||||
<tr>
|
<tr>
|
||||||
|
<td>{{ version_dict[key]["type"] }}</td>
|
||||||
<td>{{ key }}</td>
|
<td>{{ key }}</td>
|
||||||
<td>{{ version_dict[key] }}</td>
|
<td>{{ version_dict[key]["version"] }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -4,35 +4,30 @@
|
|||||||
|
|
||||||
<h4 style="text-align: center;">xmltv</h4>
|
<h4 style="text-align: center;">xmltv</h4>
|
||||||
|
|
||||||
<table class="center" style="width:50%">
|
<div class="container">
|
||||||
|
<table class="table-medium center action-col">
|
||||||
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Version</th>
|
<th>Version</th>
|
||||||
<th>XMLTV Link</th>
|
<th>XMLTV Link</th>
|
||||||
<th>EPG Link</th>
|
<th>EPG Link</th>
|
||||||
<th>Options</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% for epg_method in fhdhr.config.dict["main"]["valid_epg_methods"] %}
|
{% for epg_method in list(fhdhr.config.dict["epg"]["valid_methods"].keys()) %}
|
||||||
{% if epg_method not in [None, "None"] %}
|
{% if epg_method not in [None, "None"] %}
|
||||||
{% set epg_method_name = epg_method %}
|
|
||||||
{% if epg_method == "origin" %}
|
|
||||||
{% set epg_method_name = fhdhr.config.dict["main"]["dictpopname"] %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ epg_method_name }}</td>
|
<td> {{ epg_method }}</td>
|
||||||
<td><a href="/api/xmltv?method=get&source={{ epg_method }}">{{ epg_method_name }}</a></td>
|
<td><a href="/api/xmltv?method=get&source={{ epg_method }}">{{ epg_method }}</a></td>
|
||||||
<td><a href="/api/epg?method=get&source={{ epg_method }}">{{ epg_method_name }}</a></td>
|
<td><a href="/api/epg?method=get&source={{ epg_method }}">{{ epg_method }}</a></td>
|
||||||
<td>
|
<td>
|
||||||
<div>
|
<button onclick="location.href='/api/xmltv?method=update&source={{ epg_method }}&redirect=/xmltv'" type="button">Update</button>
|
||||||
<button onclick="OpenLink('/api/xmltv?method=update&source={{ epg_method }}&redirect=%2Fxmltv')">Update</a></button>
|
<button onclick="location.href='/api/xmltv?method=clearcache&source={{ epg_method }}&redirect=/xmltv'" type="button">Clear Cache</button>
|
||||||
<button onclick="OpenLink('/api/xmltv?method=clearcache&source={{ epg_method }}&redirect=%2Fxmltv')">Clear Cache</a></button>
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -4,6 +4,11 @@
|
|||||||
"value": "none",
|
"value": "none",
|
||||||
"config_file": true,
|
"config_file": true,
|
||||||
"config_web": true
|
"config_web": true
|
||||||
|
},
|
||||||
|
"access_level":{
|
||||||
|
"value": "0",
|
||||||
|
"config_file": true,
|
||||||
|
"config_web": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,3 +1,7 @@
|
|||||||
|
table {border: 1px solid black;}
|
||||||
|
th {border: 1px solid black;}
|
||||||
|
td {border: 1px solid black;}
|
||||||
|
|
||||||
.pull-right { float: right; }
|
.pull-right { float: right; }
|
||||||
|
|
||||||
.pull-left { float: left; }
|
.pull-left { float: left; }
|
||||||
@ -6,3 +10,170 @@
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rTable {
|
||||||
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.rTableRow {
|
||||||
|
display: table-row;
|
||||||
|
}
|
||||||
|
.rTableRowAlternate {
|
||||||
|
display: table-row;
|
||||||
|
background-color: #33FFFB;
|
||||||
|
}
|
||||||
|
.rTableHeading {
|
||||||
|
display: table-header-group;
|
||||||
|
background-color: #ddd;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.rTableCell, .rTableHead {
|
||||||
|
display: table-cell;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border: 1px solid #999999;
|
||||||
|
}
|
||||||
|
.rTableFoot {
|
||||||
|
display: table-footer-group;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
.rTableBody {
|
||||||
|
display: table-row-group;
|
||||||
|
max-height: 450px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container{
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 1rem;
|
||||||
|
}
|
||||||
|
.table-scroll{
|
||||||
|
/*width:100%; */
|
||||||
|
display: block;
|
||||||
|
empty-cells: show;
|
||||||
|
|
||||||
|
/* Decoration */
|
||||||
|
border-spacing: 0;
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-small{
|
||||||
|
width: 20%;
|
||||||
|
border-spacing: 0;
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-medium{
|
||||||
|
width: 50%;
|
||||||
|
border-spacing: 0;
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-large{
|
||||||
|
width: 75%;
|
||||||
|
border-spacing: 0;
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-settings{
|
||||||
|
width: 75%;
|
||||||
|
border-spacing: 0;
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-settings thead{
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
position:relative;
|
||||||
|
display: block;
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-settings tbody{
|
||||||
|
/* Position */
|
||||||
|
display: block; position:relative;
|
||||||
|
width:100%;
|
||||||
|
/* Decoration */
|
||||||
|
border-top: 1px solid rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-settings tr{
|
||||||
|
width: 100%;
|
||||||
|
display:flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-settings td,.table-settings th{
|
||||||
|
flex: 1 1 0;
|
||||||
|
display: block;
|
||||||
|
padding: .25rem;
|
||||||
|
}
|
||||||
|
.table-scroll thead{
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
position:relative;
|
||||||
|
display: block;
|
||||||
|
width:100%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-scroll tbody{
|
||||||
|
/* Position */
|
||||||
|
display: block; position:relative;
|
||||||
|
width:100%; overflow-y:scroll;
|
||||||
|
/* Decoration */
|
||||||
|
border-top: 1px solid rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-scroll tr{
|
||||||
|
width: 100%;
|
||||||
|
display:flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-scroll td,.table-scroll th{
|
||||||
|
flex: 1 1 0;
|
||||||
|
display: block;
|
||||||
|
padding: .25rem;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Other options */
|
||||||
|
|
||||||
|
.table-scroll.text-edit-cols td:nth-of-type(-n+4),
|
||||||
|
.table-scroll.text-edit-cols th:nth-of-type(-n+4){
|
||||||
|
flex: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-settings.text-edit-cols td:nth-of-type(-n+2),
|
||||||
|
.table-settings.text-edit-cols th:nth-of-type(-n+2){
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-scroll.small-first-col td:first-child,
|
||||||
|
.table-scroll.small-first-col th:first-child{
|
||||||
|
width: 20%;
|
||||||
|
flex: 0 1 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-col td:last-child{
|
||||||
|
flex:1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-small tbody tr:nth-child(2n){
|
||||||
|
background-color: rgba(130,130,170,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-medium tbody tr:nth-child(2n){
|
||||||
|
background-color: rgba(130,130,170,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-large tbody tr:nth-child(2n){
|
||||||
|
background-color: rgba(130,130,170,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-scroll tbody tr:nth-child(2n){
|
||||||
|
background-color: rgba(130,130,170,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.body-half-screen{
|
||||||
|
max-height: 50vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-col{flex-basis:10%;}
|
||||||
|
|||||||
6
main.py
6
main.py
@ -6,15 +6,11 @@ monkey.patch_all()
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import pathlib
|
import pathlib
|
||||||
from multiprocessing import freeze_support
|
|
||||||
|
|
||||||
from fHDHR.cli import run
|
from fHDHR.cli import run
|
||||||
import fHDHR_web
|
import fHDHR_web
|
||||||
import alternative_epg
|
|
||||||
import origin
|
|
||||||
|
|
||||||
SCRIPT_DIR = pathlib.Path(os.path.dirname(os.path.abspath(__file__)))
|
SCRIPT_DIR = pathlib.Path(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
freeze_support()
|
sys.exit(run.main(SCRIPT_DIR, fHDHR_web))
|
||||||
sys.exit(run.main(SCRIPT_DIR, fHDHR_web, origin, alternative_epg))
|
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
# pylama:ignore=W0401,W0611
|
|
||||||
from .origin_service import *
|
|
||||||
from .origin_channels import *
|
|
||||||
from .origin_epg import *
|
|
||||||
|
|
||||||
ORIGIN_NAME = "fHDHR_NextPVR"
|
|
||||||
ORIGIN_VERSION = "v0.5.0-beta"
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
import xmltodict
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
class OriginChannels():
|
|
||||||
|
|
||||||
def __init__(self, fhdhr, origin):
|
|
||||||
self.fhdhr = fhdhr
|
|
||||||
self.origin = origin
|
|
||||||
|
|
||||||
def get_channel_thumbnail(self, channel_id):
|
|
||||||
channel_thumb_url = ("%s%s:%s/service?method=channel.icon&channel_id=%s" %
|
|
||||||
("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://",
|
|
||||||
self.fhdhr.config.dict["origin"]["address"],
|
|
||||||
str(self.fhdhr.config.dict["origin"]["port"]),
|
|
||||||
str(channel_id)
|
|
||||||
))
|
|
||||||
return channel_thumb_url
|
|
||||||
|
|
||||||
def get_channels(self):
|
|
||||||
|
|
||||||
data_url = ('%s%s:%s/service?method=channel.list&sid=%s' %
|
|
||||||
("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://",
|
|
||||||
self.fhdhr.config.dict["origin"]["address"],
|
|
||||||
str(self.fhdhr.config.dict["origin"]["port"]),
|
|
||||||
self.origin.sid
|
|
||||||
))
|
|
||||||
|
|
||||||
data_req = self.fhdhr.web.session.get(data_url)
|
|
||||||
data_dict = xmltodict.parse(data_req.content)
|
|
||||||
|
|
||||||
if 'channels' not in list(data_dict['rsp'].keys()):
|
|
||||||
self.fhdhr.logger.error("Could not retrieve channel list")
|
|
||||||
return []
|
|
||||||
|
|
||||||
channel_o_list = data_dict['rsp']['channels']['channel']
|
|
||||||
|
|
||||||
channel_list = []
|
|
||||||
for c in channel_o_list:
|
|
||||||
dString = json.dumps(c)
|
|
||||||
channel_dict = eval(dString)
|
|
||||||
|
|
||||||
clean_station_item = {
|
|
||||||
"name": channel_dict["name"],
|
|
||||||
"callsign": channel_dict["name"],
|
|
||||||
"number": channel_dict["formatted-number"],
|
|
||||||
"id": channel_dict["id"],
|
|
||||||
"thumbnail": self.get_channel_thumbnail(channel_dict["id"])
|
|
||||||
}
|
|
||||||
channel_list.append(clean_station_item)
|
|
||||||
return channel_list
|
|
||||||
|
|
||||||
def get_channel_stream(self, chandict):
|
|
||||||
streamurl = ('%s%s:%s/live?channel_id=%s&client=%s' %
|
|
||||||
("https://" if self.fhdhr.config.dict["origin"]["ssl"] else "http://",
|
|
||||||
self.fhdhr.config.dict["origin"]["address"],
|
|
||||||
str(self.fhdhr.config.dict["origin"]["port"]),
|
|
||||||
str(chandict["origin_id"]),
|
|
||||||
"fhdhr_" + str(chandict["origin_number"]),
|
|
||||||
))
|
|
||||||
return streamurl
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user