attempt merge

This commit is contained in:
deathbybandaid 2020-12-14 14:28:05 -05:00
parent e7a47b751b
commit b55380d705
22 changed files with 584 additions and 70 deletions

View File

@ -1,7 +1,7 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2020 Sam Zick <Sam@deathbybandaid.net>
Copyright (C) 2017 Sam Zick <Sam@deathbybandaid.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long

View File

@ -1,4 +1,4 @@
<p align="center">fHDHR_TEMPLATE <img src="docs/images/logo.ico" alt="Logo"/></p>
<p align="center">fHDHR_Locast <img src="docs/images/logo.ico" alt="Logo"/></p>
Welcome to the world of streaming content as a DVR device! We use some fancy python here to achieve a system of:

View File

@ -1,8 +1,8 @@
[main]
# uuid =
# cache_dir =
# servicename = TEMPLATE
# reponame = fHDHR_TEMPLATE
# servicename = Locast
# reponame = fHDHR_Locast
[fhdhr]
# address = 0.0.0.0
@ -10,8 +10,8 @@
# port = 5004
# stream_type = direct
# tuner_count = 4
# friendlyname = fHDHR-TEMPLATE
# reporting_firmware_name = fHDHR_TEMPLATE
# friendlyname = fHDHR-Locast
# reporting_firmware_name = fHDHR_Locast
# reporting_manufacturer = BoronDust
# reporting_model = fHDHR
# reporting_firmware_ver = 20201001
@ -24,7 +24,7 @@
# update_frequency = 43200
[ffmpeg]
# path = ffmpeg
# ffmpeg_path = ffmpeg
# bytes_per_read = 1152000
[vlc]
@ -41,5 +41,24 @@
# type = sqlite
# driver = None
[template]
[locast]
# username =
# password =
# override_zipcode = None
# override_latitude =
# override_longitude =
# force_best = False
[zap2it]
# delay = 5
# postalcode = None
# affiliate_id = gapzap
# country = USA
# device = -
# headendid = lineupId
# isoverride = True
# languagecode = en
# pref =
# timespan = 6
# timezone =
# userid = -

View File

@ -1,3 +1,7 @@
[locast]
# username =
# password =
[fhdhr]
# address = 0.0.0.0
# port = 5004

39
config.ini Normal file
View File

@ -0,0 +1,39 @@
[main]
uuid = dymunyqo
[fhdhr]
address = 199.254.167.1
port = 5006
stream_type = ffmpeg
tuner_count = 4
friendlyname = fHDHR-Ceton
reporting_firmware_name = fHDHR_Ceton
reporting_manufacturer = BoronDust
reporting_model = fHDHR
reporting_firmware_ver = 20201001
reporting_tuner_type = Antenna
device_auth = fHDHR
chanscan_on_start = False
[epg]
method = blocks
[ffmpeg]
[vlc]
[direct_stream]
[logging]
level = info
[database]
[ceton]
ceton_ip = 199.254.167.17
[zap2it]
[web_ui]
advanced = False

View File

@ -1,7 +1,7 @@
<p align="center">fHDHR <img src="images/logo.ico" alt="Logo"/></p>
---
[Main](README.md) | [Setup and Usage](Usage.md) | [template](Origin.md) | [Credits/Related Projects](Related-Projects.md)
[Main](README.md) | [Setup and Usage](Usage.md) | [Locast](Origin.md) | [Credits/Related Projects](Related-Projects.md)
---
**f**un
**H**ome
@ -33,7 +33,7 @@ Here's the `main` section.
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.
* `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.
* `stream_type` can be set to `ffmpeg`, `vlc` or `direct`.
@ -45,8 +45,8 @@ The `fhdhr` contains all the configuration options for interfacing between this
# port = 5004
# stream_type = direct
# tuner_count = 4
# friendlyname = fHDHR-template
# reporting_firmware_name = fHDHR_TEMPLATE
# friendlyname = fHDHR-Locast
# reporting_firmware_name = fHDHR_Locast
# reporting_manufacturer = BoronDust
# reporting_model = fHDHR
# reporting_firmware_ver = 20201001
@ -56,7 +56,7 @@ The `fhdhr` contains all the configuration options for interfacing between this
# EPG
* `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 template. 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.
````
@ -119,11 +119,40 @@ TODO: improve documentation here.
# driver = None
````
## template
The `template` section
## Locast
The `locast` section
* requires `username` and `password`. The script will not run without these.
* `override_zipcode` is useful for if your DMA is not being picked up correctly.
* `override_latitude` and `override_longitude` are helpful for "mocking" your location.
* `force_best` will force select the best stream available.
````
[template]
[locast]
# username =
# password =
# override_zipcode = None
# mock_location = None
# force_best = False
````
## zap2it
`zap2it` contains a ton of configuration options, and defaults to options that in my experience don't need to be adjusted.
* `postalcode` is a value of importance, and is helpful. If not set, the script will attempt to retrieve your postalcode automatically.
````
[zap2it]
# delay = 5
# postalcode = None
# affiliate_id = gapzap
# country = USA
# device = -
# headendid = lineupId
# isoverride = True
# languagecode = en
# pref =
# timespan = 6
# timezone =
# userid = -
````

View File

@ -1,7 +1,7 @@
<p align="center">fHDHR <img src="images/logo.ico" alt="Logo"/></p>
---
[Main](README.md) | [Setup and Usage](Usage.md) | [template](Origin.md) | [Credits/Related Projects](Related-Projects.md)
[Main](README.md) | [Setup and Usage](Usage.md) | [Locast](Origin.md) | [Credits/Related Projects](Related-Projects.md)
---
**f**un
**H**ome
@ -30,12 +30,12 @@ Under `fhdhr`, you'll find 2 addresses listed. `0.0.0.0` works great for a liste
# discovery_address = 0.0.0.0
````
## template
## Locast
template requires signin credentials, so add those.
Locast requires signin credentials, so add those.
````
[template]
[locast]
# username =
# password =
````

View File

@ -1,7 +1,7 @@
<p align="center">fHDHR <img src="images/logo.ico" alt="Logo"/></p>
---
[Main](README.md) | [Setup and Usage](Usage.md) | [template](Origin.md) | [Credits/Related Projects](Related-Projects.md)
[Main](README.md) | [Setup and Usage](Usage.md) | [Locast](Origin.md) | [Credits/Related Projects](Related-Projects.md)
---
**f**un
**H**ome
@ -12,4 +12,16 @@
---
This varient of fHDHR connects to [template](https://template.com/about).
This varient of fHDHR connects to [Locast](https://www.locast.org/about).
Usage with fHDHR requires:
* an account with their service
* an active donation
* The service to be available in your DMA (“Designated Market Area”)
Do NOT use fHDHR to obtain channels outside of your DMA. You will recieve no support for doing so.
This includes but is not limited to:
* VPN services
* Cloud Server Hosting

View File

@ -1,7 +1,7 @@
<p align="center">fHDHR <img src="images/logo.ico" alt="Logo"/></p>
---
[Main](README.md) | [Setup and Usage](Usage.md) | [template](Origin.md) | [Credits/Related Projects](Related-Projects.md)
[Main](README.md) | [Setup and Usage](Usage.md) | [Locast](Origin.md) | [Credits/Related Projects](Related-Projects.md)
---
**f**un
**H**ome

View File

@ -1,7 +1,7 @@
<p align="center">fHDHR <img src="images/logo.ico" alt="Logo"/></p>
---
[Main](README.md) | [Setup and Usage](Usage.md) | [template](Origin.md) | [Credits/Related Projects](Related-Projects.md)
[Main](README.md) | [Setup and Usage](Usage.md) | [Locast](Origin.md) | [Credits/Related Projects](Related-Projects.md)
---
**f**un
**H**ome

View File

@ -1,7 +1,7 @@
<p align="center">fHDHR <img src="images/logo.ico" alt="Logo"/></p>
---
[Main](README.md) | [Setup and Usage](Usage.md) | [template](Origin.md) | [Credits/Related Projects](Related-Projects.md)
[Main](README.md) | [Setup and Usage](Usage.md) | [Locast](Origin.md) | [Credits/Related Projects](Related-Projects.md)
---
**f**un
**H**ome
@ -50,72 +50,72 @@ fHDHR uses direct connections with video sources by default. Alternatively, you
## Docker
This portion of the guide assumes you are using a Linux system with both docker and docker-compose installed. This (or some variation thereof) may work on Mac or Windows, but has not been tested.
* this guide assumes we wish to use the `~/fhdhr` directory for our install (you can use whatever directory you like, just make the appropriate changes elsewhere in this guide) and that we are installing for template support
* run the following commands to clone the repo into `~/fhdhr/fHDHR_TEMPLATE`
* this guide assumes we wish to use the `~/fhdhr` directory for our install (you can use whatever directory you like, just make the appropriate changes elsewhere in this guide) and that we are installing for Locast support
* run the following commands to clone the repo into `~/fhdhr/fHDHR_Locast`
```
cd ~/fhdhr
git clone https://github.com/fHDHR/fHDHR_TEMPLATE.git
git clone https://github.com/fHDHR/fHDHR_Locast.git
```
* create your config.ini file (as described earlier in this guide) in the `~/fhdhr/fHDHR_TEMPLATE` directory
* create your config.ini file (as described earlier in this guide) in the `~/fhdhr/fHDHR_Locast` directory
* while still in the `~/fhdhr` directory, create the following `docker-compose.yml` file
```
version: '3'
services:
template:
build: ./fHDHR_TEMPLATE
container_name: template
locast:
build: ./fHDHR_Locast
container_name: locast
network_mode: host
volumes:
- ./fHDHR_TEMPLATE/config.ini:/app/config/config.ini
- ./fHDHR_Locast/config.ini:/app/config/config.ini
```
* run the following command to build and launch the container
```
docker-compose up --build -d template
docker-compose up --build -d locast
```
After a short period of time (during which docker will build your new fHDHR container), you should now have a working build of fHDHR running inside a docker container.
As the code changes and new versions / bug fixes are released, at any point you can pull the latest version of the code and rebuild your container with the following commands:
```
cd ~/fhdhr/fHDHR_TEMPLATE
cd ~/fhdhr/fHDHR_Locast
git checkout master
git pull
cd ~/fhdhr
docker-compose up --build -d template
docker-compose up --build -d locast
```
<hr />
You can also run multiple instances of fHDHR to support additional sources by cloning the appropriate repo into your `~/fhdhr` directory and adding the necessary services to the docker-compose file we created above.
* for example, if we also wanted template support, you would clone the template repository:
* for example, if we also wanted PlutoTV support, you would clone the PlutoTV repository:
```
cd ~/fhdhr
git clone https://github.com/fHDHR/fHDHR_TEMPLATE.git
git clone https://github.com/fHDHR/fHDHR_PlutoTV.git
```
* **NOTE**: if you are running multiple services on the same machine, you must change the port in your config.ini file for each one. For example, if template was using the default port of 5004, template cannot also use that port. You must change the port in your template config.ini file to something else (5005, for example).
* add template as a service in your `docker-compose.yml` file
* **NOTE**: if you are running multiple services on the same machine, you must change the port in your config.ini file for each one. For example, if Locast was using the default port of 5004, PlutoTV cannot also use that port. You must change the port in your PlutoTV config.ini file to something else (5005, for example).
* add plutotv as a service in your `docker-compose.yml` file
```
version: '3'
services:
template:
build: ./fHDHR_TEMPLATE
container_name: template
locast:
build: ./fHDHR_Locast
container_name: locast
network_mode: host
volumes:
- ./fHDHR_TEMPLATE/config.ini:/app/config/config.ini
- ./fHDHR_Locast/config.ini:/app/config/config.ini
template:
build: ./fHDHR_TEMPLATE
container_name: template
plutotv:
build: ./fHDHR_PlutoTV
container_name: plutotv
network_mode: host
volumes:
- ./fHDHR_TEMPLATE/config.ini:/app/config/config.ini
- ./fHDHR_PlutoTV/config.ini:/app/config/config.ini
```
* run the following command to build and launch the container
```
docker-compose up --build -d template
docker-compose up --build -d plutotv
```
You can repeat these instructions for as many fHDHR containers as your system resources will allow.

View File

@ -1,7 +1,7 @@
<p align="center">fHDHR <img src="images/logo.ico" alt="Logo"/></p>
---
[Main](README.md) | [Setup and Usage](Usage.md) | [template](Origin.md) | [Credits/Related Projects](Related-Projects.md)
[Main](README.md) | [Setup and Usage](Usage.md) | [Locast](Origin.md) | [Credits/Related Projects](Related-Projects.md)
---
**f**un
**H**ome
@ -27,7 +27,7 @@ Below is the main landing page with basic information.
<img src="screenshots/webui_main.PNG" alt="Main Page"/>
# template
# Locast
Here you will have access to some basic information about the service we are proxying.

View File

@ -4,5 +4,5 @@ from .origin_channels import *
from .origin_epg import *
from .origin_web import *
ORIGIN_NAME = "fHDHR_Locast"
ORIGIN_NAME = "fHDHR_Ceton"
ORIGIN_VERSION = "v0.5.0-beta"

View File

@ -1,3 +1,7 @@
import xmltodict
import base64
from random import randint
class OriginChannels():
@ -8,10 +12,137 @@ class OriginChannels():
def get_channels(self):
channel_list = []
stations_url = ('http://' + self.fhdhr.config.dict["origin"]["ceton_ip"] + '/view_channel_map.cgi?page=0&xml=1')
url_headers = {'accept': 'application/xml;q=0.9, */*;q=0.8'}
return channel_list
try:
stationsReq = self.fhdhr.web.session.get(stations_url, headers=url_headers)
stationsReq.raise_for_status()
except self.fhdhr.web.exceptions.HTTPError as err:
self.fhdhr.logger.error('Error while getting stations: %s' % err)
return []
stationsRes = xmltodict.parse(stationsReq.content)
cleaned_channels = []
for station_item in stationsRes['channels']['channel']:
nameTmp = station_item["name"]
nameTmp_bytes = nameTmp.encode('ascii')
namebytes = base64.b64decode(nameTmp_bytes)
name = namebytes.decode('ascii')
clean_station_item = {
"name": name,
"callsign": name,
"number": station_item["number"],
"eia": station_item["eia"],
"id": station_item["sourceid"],
}
cleaned_channels.append(clean_station_item)
return cleaned_channels
def get_ceton_getvar(self, instance, query):
getVarUrl = ('http://' + self.fhdhr.config.dict["origin"]["ceton_ip"] + '/get_var?i=' + str(instance) + query)
try:
getVarUrlReq = self.fhdhr.web.session.get(getVarUrl)
getVarUrlReq.raise_for_status()
except self.fhdhr.web.exceptions.HTTPError as err:
self.fhdhr.logger.error('Error while getting tuner variable for: %s - %s' % (query, err))
return None
return
def get_ceton_tuner_status(self, chandict):
found = 0
count = int(self.fhdhr.config.dict["fhdhr"]["tuner_count"])
for instance in range(count):
getVarUrl = ('http://' + self.fhdhr.config.dict["origin"]["ceton_ip"] + '/get_var?i=' + str(instance) + '&s=av&v=TransportState')
try:
getVarUrlReq = self.fhdhr.web.session.get(getVarUrl)
getVarUrlReq.raise_for_status()
except self.fhdhr.web.exceptions.HTTPError as err:
self.fhdhr.logger.error('Error while getting tuner status: %s' % err)
return None
if "STOPPED" in getVarUrlReq.text:
self.fhdhr.logger.info('Tuner %s selected' % str(instance))
found = 1
break
return found, instance
def startstop_ceton_tuner(self, instance, startstop):
if not startstop:
port = 0
self.fhdhr.logger.info('Tuner %s to be stopped' % str(instance))
else:
port = randint(41001, 49999)
self.fhdhr.logger.info('Tuner %s to be started' % str(instance))
StartStopUrl = ('http://%s/stream_request.cgi' % self.fhdhr.config.dict["origin"]["ceton_ip"])
StartStop_data = {"instance_id": instance,
"dest_ip": self.fhdhr.config.dict["fhdhr"]["address"],
"dest_port": port,
"protocol": 0,
"start": startstop}
# StartStopUrl_headers = {
# 'Content-Type': 'application/json',
# 'User-Agent': "curl/7.64.1"}
try:
StartStopUrlReq = self.fhdhr.web.session.post(StartStopUrl, StartStop_data)
StartStopUrlReq.raise_for_status()
except self.fhdhr.web.exceptions.HTTPError as err:
self.fhdhr.logger.error('Error while setting station stream: %s' % err)
return None
return port
def set_ceton_tuner(self, chandict, instance):
tuneChannelUrl = ('http://%s/channel_request.cgi' % self.fhdhr.config.dict["origin"]["ceton_ip"])
tuneChannel_data = {"instance_id": 0,
"channel": chandict['number']}
try:
tuneChannelUrlReq = self.fhdhr.web.session.post(tuneChannelUrl, tuneChannel_data)
tuneChannelUrlReq.raise_for_status()
except self.fhdhr.web.exceptions.HTTPError as err:
self.fhdhr.logger.error('Error while tuning station URL: %s' % err)
return None
return 1
def get_channel_stream(self, chandict):
streamurl = ""
# streamurl = "rtsp://admin:password1@199.254.167.199:554"
# print (streamurl)
# return streamurl
found, instance = self.get_ceton_tuner_status(chandict)
# 1 to start or 0 to stop
if found:
port = self.startstop_ceton_tuner(instance, 1)
self.fhdhr.logger.error('No tuners available')
else:
port = 0
if port:
tuned = self.set_ceton_tuner(chandict, instance)
self.fhdhr.logger.error('Preparing tuner ' + str(instance) + ' on port:' + str(port))
else:
tuned = 0
self.get_ceton_getvar(instance, "&s=tuner&v=Frequency")
self.get_ceton_getvar(instance, "&s=mux&v=ProgramNumber")
self.get_ceton_getvar(instance, "&s=diag&v=CopyProtectionStatus")
if tuned:
self.fhdhr.logger.error('Initiate streaming from tuner ' + str(instance))
streamurl = "udp://127.0.0.1:" + str(port)
else:
streamurl = None
return streamurl

View File

@ -1,29 +1,39 @@
{
"main":{
"servicename":{
"value": "TEMPLATE",
"value": "Ceton",
"config_file": false,
"config_web": false
},
"dictpopname":{
"value": "template",
"value": "ceton",
"config_file": false,
"config_web": false
},
"reponame":{
"value": "fHDHR_TEMPLATE",
"value": "fHDHR_Ceton",
"config_file": false,
"config_web": false
},
"required":{
"value": "ceton/ceton_ip",
"config_file": false,
"config_web": false
}
},
"fhdhr":{
"friendlyname":{
"value": "fHDHR-TEMPLATE",
"value": "fHDHR-Ceton",
"config_file": true,
"config_web": true
},
"tuner_count":{
"value": 3,
"config_file": true,
"config_web": true
},
"reporting_firmware_name":{
"value": "fHDHR_TEMPLATE",
"value": "fHDHR_Ceton",
"config_file": true,
"config_web": true
}
@ -35,10 +45,16 @@
"config_web": true
},
"valid_epg_methods":{
"value": "None,blocks,origin",
"value": "None,blocks,origin,zap2it,tvtv",
"config_file": false,
"config_web": false
}
},
"template":{}
"ceton":{
"ceton_ip":{
"value": "none",
"config_file": true,
"config_web": true
}
}
}

View File

@ -8,10 +8,4 @@ class OriginEPG():
def update_epg(self, fhdhr_channels):
programguide = {}
for fhdhr_id in list(fhdhr_channels.list.keys()):
chan_obj = fhdhr_channels.list[fhdhr_id]
if str(chan_obj.dict["number"]) not in list(programguide.keys()):
programguide[str(chan_obj.dict["number"])] = chan_obj.epgdict
return programguide

187
origin/origin_rtp.py Normal file
View File

@ -0,0 +1,187 @@
import socket
# import re
import bitstring # if you don't have this from your linux distro, install with "pip install bitstring"
# ********* (2) The routine for handling the RTP stream ***********
def digestpacket(st):
""" This routine takes a UDP packet, i.e. a string of bytes and ..
(a) strips off the RTP header
(b) adds NAL "stamps" to the packets, so that they are recognized as NAL's
(c) Concantenates frames
(d) Returns a packet that can be written to disk as such and that is recognized by stock media players as h264 stream
"""
startbytes = "\x00\x00\x00\x01" # this is the sequence of four bytes that identifies a NAL packet.. must be in front of every NAL packet.
bt = bitstring.BitArray(bytes=st) # turn the whole string-of-bytes packet into a string of bits. Very unefficient, but hey, this is only for demoing.
lc = 12 # bytecounter
bc = 12 * 8 # bitcounter
version = bt[0:2].uint # version
p = bt[3] # P
x = bt[4] # X
cc = bt[4:8].uint # CC
m = bt[9] # M
pt = bt[9:16].uint # PT
sn = bt[16:32].uint # sequence number
timestamp = bt[32:64].uint # timestamp
ssrc = bt[64:96].uint # ssrc identifier
# The header format can be found from:
# https://en.wikipedia.org/wiki/Real-time_Transport_Protocol
lc = 12 # so, we have red twelve bytes
bc = 12 * 8 # .. and that many bits
print("version, p, x, cc, m, pt", version, p, x, cc, m, pt)
print("sequence number, timestamp", sn, timestamp)
print("sync. source identifier", ssrc)
# st=f.read(4*cc) # csrc identifiers, 32 bits (4 bytes) each
cids = []
for i in range(cc):
cids.append(bt[bc:bc+32].uint)
bc += 32
lc += 4
print("csrc identifiers:", cids)
if (x):
# this section haven't been tested.. might fail
hid = bt[bc:bc+16].uint
bc += 16
lc += 2
hlen = bt[bc:bc+16].uint
bc += 16
lc += 2
print("ext. header id, header len", hid, hlen)
# hst = bt[bc:bc+32*hlen]
bc += 32 * hlen
lc += 4 * hlen
# OK, now we enter the NAL packet, as described here:
#
# https://tools.ietf.org/html/rfc6184#section-1.3
#
# Some quotes from that document:
#
"""
5.3. NAL Unit Header Usage
The structure and semantics of the NAL unit header were introduced in
Section 1.3. For convenience, the format of the NAL unit header is
reprinted below:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
This section specifies the semantics of F and NRI according to this
specification.
"""
"""
Table 3. Summary of allowed NAL unit types for each packetization
mode (yes = allowed, no = disallowed, ig = ignore)
Payload Packet Single NAL Non-Interleaved Interleaved
Type Type Unit Mode Mode Mode
-------------------------------------------------------------
0 reserved ig ig ig
1-23 NAL unit yes yes no
24 STAP-A no yes no
25 STAP-B no no yes
26 MTAP16 no no yes
27 MTAP24 no no yes
28 FU-A no yes yes
29 FU-B no no yes
30-31 reserved ig ig ig
"""
# This was also very usefull:
# http://stackoverflow.com/questions/7665217/how-to-process-raw-udp-packets-so-that-they-can-be-decoded-by-a-decoder-filter-i
# A quote from that:
"""
First byte: [ 3 NAL UNIT BITS | 5 FRAGMENT TYPE BITS]
Second byte: [ START BIT | RESERVED BIT | END BIT | 5 NAL UNIT BITS]
Other bytes: [... VIDEO FRAGMENT DATA...]
"""
fb = bt[bc] # i.e. "F"
nri = bt[bc+1:bc+3].uint # "NRI"
# nlu0 = bt[bc:bc+3] # "3 NAL UNIT BITS" (i.e. [F | NRI])
typ = bt[bc+3:bc+8].uint # "Type"
print("F, NRI, Type :", fb, nri, typ)
print("first three bits together :", bt[bc:bc+3])
if (typ == 7 or typ == 8):
# this means we have either an SPS or a PPS packet
# they have the meta-info about resolution, etc.
# more reading for example here:
# http://www.cardinalpeak.com/blog/the-h-264-sequence-parameter-set/
if (typ == 7):
print(">>>>> SPS packet")
else:
print(">>>>> PPS packet")
return startbytes+st[lc:]
# .. notice here that we include the NAL starting sequence "startbytes" and the "First byte"
bc += 8
lc += 1 # let's go to "Second byte"
# ********* WE ARE AT THE "Second byte" ************
# The "Type" here is most likely 28, i.e. "FU-A"
start = bt[bc] # start bit
end = bt[bc+2] # end bit
# nlu1 = bt[bc+3:bc+8] # 5 nal unit bits
if (start): # OK, this is a first fragment in a movie frame
print(">>> first fragment found")
# nlu = nlu0 + nlu1 # Create "[3 NAL UNIT BITS | 5 NAL UNIT BITS]"
# head = startbytes+nlu.bytes # .. add the NAL starting sequence
lc += 1 # We skip the "Second byte"
if (start is False and end is False): # intermediate fragment in a sequence, just dump "VIDEO FRAGMENT DATA"
# head = ""
lc += 1 # We skip the "Second byte"
elif (end is True): # last fragment in a sequence, just dump "VIDEO FRAGMENT DATA"
# head = ""
print("<<<< last fragment found")
lc += 1 # We skip the "Second byte"
if (typ == 28): # This code only handles "Type" = 28, i.e. "FU-A"
print("missing code here?")
else:
raise(Exception, "unknown frame type for this piece of s***")
# *********** (3) THE MAIN PROGRAM STARTS HERE ****************
s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# unknown name `clientports`
# s1.bind(("", clientports[0])) # we open a port that is visible to the whole internet (the empty string "" takes care of that)
s1.settimeout(5) # if the socket is dead for 5 s., its thrown into trash
# further reading:
# https://wiki.python.org/moin/UdpCommunication
print("** STRIPPING RTP INFO AND DUMPING INTO FILE **")
# unknown name `fname`
# f = open(fname, 'w')
# unoknown name rn
# for i in range(rn):
# print
# print
# recst = s1.recv(4096)
# print("read", len(recst), "bytes")
# st = digestpacket(recst)
# print("dumping", len(st), "bytes")
# f.write(st)
# f.close()
s1.close()

View File

@ -1,3 +1,5 @@
# import os
# import bitstring
class OriginService():

View File

@ -1,5 +1,6 @@
from .origin_api import Origin_API
from .origin_api_tools import Origin_API_Tools
from .origin_html import Origin_HTML
@ -9,4 +10,5 @@ class fHDHR_Origin_Web():
self.fhdhr = fhdhr
self.origin_api = Origin_API(fhdhr)
self.origin_api_tools = Origin_API_Tools(fhdhr)
self.origin_html = Origin_HTML(fhdhr)

View File

@ -0,0 +1,78 @@
from flask import Response, request, redirect
import urllib.parse
import json
class Origin_API_Tools():
endpoints = ["/api/origin/tools"]
endpoint_name = "api_origin_tools"
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 == "channels":
dma = request.args.get('dma', default=self.fhdhr.originwrapper.originservice.location["DMA"], type=str)
stations_url = 'https://api.locastnet.org/api/watch/epg/%s' % dma
try:
stationsReq = self.fhdhr.web.session.get(stations_url)
stationsReq.raise_for_status()
stationsRes = stationsReq.json()
except self.fhdhr.web.exceptions.HTTPError as err:
self.fhdhr.logger.error('Error while getting stations: %s' % err)
stationsRes = [{"error": 'Error while getting stations: %s' % err, "listings": []}]
filtered_json = []
for station_item in stationsRes:
station_item["listings"] = []
filtered_json.append(station_item)
stations_json = json.dumps(filtered_json, indent=4)
return Response(status=200,
response=stations_json,
mimetype='application/json')
elif method == "zipcode":
zipcode = request.args.get('zipcode', default=None, type=str)
if not zipcode:
if redirect_url:
return redirect(redirect_url + "?retmessage=" + urllib.parse.quote("%s Success" % method))
else:
return "%s Success" % method
status_url = 'https://api.locastnet.org/api/watch/dma/zip/%s' % zipcode
try:
statusReq = self.fhdhr.web.session.get(status_url)
statusReq.raise_for_status()
statusRes = statusReq.json()
except self.fhdhr.web.exceptions.HTTPError as err:
self.fhdhr.logger.error('Error while getting zipcode status: %s' % err)
statusRes = [{"error": 'Error while getting zipcode status: %s' % err, "listings": []}]
status_json = json.dumps(statusRes, indent=4)
return Response(status=200,
response=status_json,
mimetype='application/json')
else:
return "%s Invalid Method" % method
if redirect_url:
return redirect(redirect_url + "?retmessage=" + urllib.parse.quote("%s Success" % method))
else:
return "%s Success" % method

View File

@ -1,6 +1,9 @@
from flask import request, render_template_string
import pathlib
from io import StringIO
import datetime
from fHDHR.tools import humanized_time
class Origin_HTML():
@ -21,7 +24,6 @@ class Origin_HTML():
if self.fhdhr.originwrapper.setup_success:
origin_status_dict = {"Setup": "Success"}
origin_status_dict["Total Channels"] = len(self.fhdhr.device.channels.list)
else:
origin_status_dict = {"Setup": "Failed"}
return render_template_string(self.template.getvalue(), request=request, fhdhr=self.fhdhr, origin_status_dict=origin_status_dict, list=list)

View File

@ -2,7 +2,6 @@ requests
gevent
flask
image
xmltodict
m3u8
sqlalchemy
pycryptodome