1
0
Fork 0
mirror of https://github.com/fastogt/fastocloud_admin.git synced 2025-03-09 23:38:52 +00:00

Proxy stream

This commit is contained in:
topilski 2019-06-11 10:45:33 -04:00
parent 6203746a70
commit a2d932de68
15 changed files with 338 additions and 148 deletions

View file

@ -2,6 +2,7 @@
[Alexandr Topilski] [Alexandr Topilski]
- User agents - User agents
- Subscribers - Subscribers
- Proxy streams
1.1.0 / June 6, 2019 1.1.0 / June 6, 2019
[Alexandr Topilski] [Alexandr Topilski]

View file

@ -2,14 +2,15 @@ from enum import IntEnum
class StreamType(IntEnum): class StreamType(IntEnum):
RELAY = 0 PROXY = 0,
ENCODE = 1 RELAY = 1
TIMESHIFT_PLAYER = 2 ENCODE = 2
TIMESHIFT_RECORDER = 3 TIMESHIFT_PLAYER = 3
CATCHUP = 4 TIMESHIFT_RECORDER = 4
TEST_LIFE = 5, CATCHUP = 5
VOD_RELAY = 6, TEST_LIFE = 6,
VOD_ENCODE = 7 VOD_RELAY = 7,
VOD_ENCODE = 8
@classmethod @classmethod
def choices(cls): def choices(cls):
@ -69,11 +70,6 @@ DEFAULT_LOCALE = 'en'
AVAILABLE_LOCALES = DEFAULT_LOCALE, 'ru' AVAILABLE_LOCALES = DEFAULT_LOCALE, 'ru'
AVAILABLE_LOCALES_PAIRS = [(DEFAULT_LOCALE, 'English'), ('ru', 'Russian')] AVAILABLE_LOCALES_PAIRS = [(DEFAULT_LOCALE, 'English'), ('ru', 'Russian')]
AVAILABLE_STREAM_TYPES_PAIRS = [(StreamType.RELAY, 'relay'), (StreamType.ENCODE, 'encode'),
(StreamType.TIMESHIFT_PLAYER, 'timeshift_player'),
(StreamType.TIMESHIFT_RECORDER, 'timeshift_record'), (StreamType.CATCHUP, 'catchup'),
(StreamType.TEST_LIFE, 'test_life'), (StreamType.VOD_RELAY, 'vod_relay'),
(StreamType.VOD_ENCODE, 'vod_encode')]
AVAILABLE_LOG_LEVELS_PAIRS = [(StreamLogLevel.LOG_LEVEL_EMERG, 'EVERG'), (StreamLogLevel.LOG_LEVEL_ALERT, 'ALERT'), AVAILABLE_LOG_LEVELS_PAIRS = [(StreamLogLevel.LOG_LEVEL_EMERG, 'EVERG'), (StreamLogLevel.LOG_LEVEL_ALERT, 'ALERT'),
(StreamLogLevel.LOG_LEVEL_CRIT, 'CRITICAL'), (StreamLogLevel.LOG_LEVEL_CRIT, 'CRITICAL'),
(StreamLogLevel.LOG_LEVEL_ERR, 'ERROR'), (StreamLogLevel.LOG_LEVEL_ERR, 'ERROR'),

View file

@ -55,7 +55,8 @@ class ActivateForm(FlaskForm):
class UploadM3uForm(FlaskForm): class UploadM3uForm(FlaskForm):
AVAILABLE_STREAM_TYPES_FOR_UPLOAD = [(constants.StreamType.RELAY, 'Relay'), (constants.StreamType.ENCODE, 'Encode'), AVAILABLE_STREAM_TYPES_FOR_UPLOAD = [(constants.StreamType.PROXY, 'Proxy'), (constants.StreamType.RELAY, 'Relay'),
(constants.StreamType.ENCODE, 'Encode'),
(constants.StreamType.CATCHUP, 'Catchup'), (constants.StreamType.CATCHUP, 'Catchup'),
(constants.StreamType.TEST_LIFE, 'Test life'), (constants.StreamType.TEST_LIFE, 'Test life'),
(constants.StreamType.VOD_RELAY, 'Vod relay'), (constants.StreamType.VOD_RELAY, 'Vod relay'),

View file

@ -1,9 +1,8 @@
from bson.objectid import ObjectId from bson.objectid import ObjectId
from app.stream.stream_entry import Stream, EncodeStream, RelayStream, TimeshiftRecorderStream, CatchupStream, \ from app.stream.stream_entry import IStream, ProxyStream, EncodeStream, RelayStream, TimeshiftRecorderStream, \
TimeshiftPlayerStream, TestLifeStream, make_encode_stream, make_relay_stream, make_timeshift_recorder_stream, \ CatchupStream, \
make_catchup_stream, make_timeshift_player_stream, make_test_life_stream, make_vod_encode_stream, \ TimeshiftPlayerStream, TestLifeStream, VodRelayStream, VodEncodeStream
make_vod_relay_stream
from app.client.client_constants import ClientStatus from app.client.client_constants import ClientStatus
from app.service.service_entry import ServiceSettings from app.service.service_entry import ServiceSettings
@ -216,29 +215,32 @@ class Service(IStreamHandler):
ServiceFields.VERSION: self.version, ServiceFields.UPTIME: self._uptime, ServiceFields.VERSION: self.version, ServiceFields.UPTIME: self._uptime,
ServiceFields.TIMESTAMP: self._timestamp, ServiceFields.STATUS: self.status} ServiceFields.TIMESTAMP: self._timestamp, ServiceFields.STATUS: self.status}
def make_relay_stream(self) -> RelayStream: def make_proxy_stream(self) -> ProxyStream:
return make_relay_stream(self._settings) return ProxyStream.make_stream(self._settings)
def make_vod_relay_stream(self) -> RelayStream: def make_relay_stream(self) -> RelayStream:
return make_vod_relay_stream(self._settings) return RelayStream.make_stream(self._settings)
def make_vod_relay_stream(self) -> VodRelayStream:
return VodRelayStream.make_stream(self._settings)
def make_encode_stream(self) -> EncodeStream: def make_encode_stream(self) -> EncodeStream:
return make_encode_stream(self._settings) return EncodeStream.make_stream(self._settings)
def make_vod_encode_stream(self) -> EncodeStream: def make_vod_encode_stream(self) -> EncodeStream:
return make_vod_encode_stream(self._settings) return VodEncodeStream.make_stream(self._settings)
def make_timeshift_recorder_stream(self) -> TimeshiftRecorderStream: def make_timeshift_recorder_stream(self) -> TimeshiftRecorderStream:
return make_timeshift_recorder_stream(self._settings) return TimeshiftRecorderStream.make_stream(self._settings)
def make_catchup_stream(self) -> CatchupStream: def make_catchup_stream(self) -> CatchupStream:
return make_catchup_stream(self._settings) return CatchupStream.make_stream(self._settings)
def make_timeshift_player_stream(self) -> TimeshiftPlayerStream: def make_timeshift_player_stream(self) -> TimeshiftPlayerStream:
return make_timeshift_player_stream(self._settings) return TimeshiftPlayerStream.make_stream(self._settings)
def make_test_life_stream(self) -> TestLifeStream: def make_test_life_stream(self) -> TestLifeStream:
return make_test_life_stream(self._settings) return TestLifeStream.make_stream(self._settings)
# handler # handler
def on_stream_statistic_received(self, params: dict): def on_stream_statistic_received(self, params: dict):
@ -303,7 +305,7 @@ class Service(IStreamHandler):
self._uptime = stats[ServiceFields.UPTIME] self._uptime = stats[ServiceFields.UPTIME]
self._timestamp = stats[ServiceFields.TIMESTAMP] self._timestamp = stats[ServiceFields.TIMESTAMP]
def __init_stream_runtime_fields(self, stream: Stream): def __init_stream_runtime_fields(self, stream: IStream):
stream.set_server_settings(self._settings) stream.set_server_settings(self._settings)
def __reload_from_db(self): def __reload_from_db(self):

View file

@ -3,7 +3,7 @@ from mongoengine import Document, ListField, EmbeddedDocumentField, ReferenceFie
import app.constants as constants import app.constants as constants
from app.service.server_entry import ServerSettings from app.service.server_entry import ServerSettings
from app.stream.stream_entry import Stream from app.stream.stream_entry import IStream
# #EXTM3U # #EXTM3U
@ -18,7 +18,7 @@ class UserPair(EmbeddedDocument):
class ServiceSettings(Document, ServerSettings): class ServiceSettings(Document, ServerSettings):
meta = {'collection': 'services', 'auto_create_index': False} meta = {'collection': 'services', 'auto_create_index': False}
streams = ListField(EmbeddedDocumentField(Stream), default=[]) streams = ListField(EmbeddedDocumentField(IStream), default=[])
users = ListField(EmbeddedDocumentField(UserPair), default=[]) users = ListField(EmbeddedDocumentField(UserPair), default=[])
subscribers = ListField(ReferenceField('Subscriber'), default=[]) subscribers = ListField(ReferenceField('Subscriber'), default=[])

View file

@ -37,7 +37,9 @@ class ServiceView(FlaskView):
m3u_parser.parse() m3u_parser.parse()
for file in m3u_parser.files: for file in m3u_parser.files:
if stream_type == constants.StreamType.RELAY: if stream_type == constants.StreamType.PROXY:
stream = server.make_proxy_stream()
elif stream_type == constants.StreamType.RELAY:
stream = server.make_relay_stream() stream = server.make_relay_stream()
stream.output.urls[0] = stream.generate_http_link() stream.output.urls[0] = stream.generate_http_link()
elif stream_type == constants.StreamType.ENCODE: elif stream_type == constants.StreamType.ENCODE:

View file

@ -105,22 +105,66 @@ class ChannelInfo:
ChannelInfo.AUDIO_ENABLE_FIELD: self.have_audio} ChannelInfo.AUDIO_ENABLE_FIELD: self.have_audio}
class Stream(EmbeddedDocument): class IStream(EmbeddedDocument):
meta = {'allow_inheritance': True, 'auto_create_index': True} meta = {'allow_inheritance': True, 'auto_create_index': True}
id = ObjectIdField(required=True, default=ObjectId, id = ObjectIdField(required=True, default=ObjectId,
unique=True, primary_key=True) unique=True, primary_key=True) #
name = StringField(default=constants.DEFAULT_STREAM_NAME, max_length=constants.MAX_STREAM_NAME_LENGTH, name = StringField(default=constants.DEFAULT_STREAM_NAME, max_length=constants.MAX_STREAM_NAME_LENGTH,
min_length=constants.MIN_STREAM_NAME_LENGTH, required=True) min_length=constants.MIN_STREAM_NAME_LENGTH, required=True) #
icon = StringField(default=constants.DEFAULT_STREAM_ICON_URL, max_length=constants.MAX_URL_LENGTH, icon = StringField(default=constants.DEFAULT_STREAM_ICON_URL, max_length=constants.MAX_URL_LENGTH,
min_length=constants.MIN_URL_LENGTH, required=True) min_length=constants.MIN_URL_LENGTH, required=True) #
group = StringField(default=constants.DEFAULT_STREAM_GROUP_TITLE, min_length=constants.MIN_STREAM_GROUP_TITLE, group = StringField(default=constants.DEFAULT_STREAM_GROUP_TITLE, min_length=constants.MIN_STREAM_GROUP_TITLE,
max_length=constants.MAX_STREAM_GROUP_TITLE, required=False) max_length=constants.MAX_STREAM_GROUP_TITLE, required=False)
created_date = DateTimeField(default=datetime.now) # for inner use created_date = DateTimeField(default=datetime.now) # for inner use
output = EmbeddedDocumentField(OutputUrls, default=OutputUrls()) #
_settings = ServerSettings()
def __init__(self, *args, **kwargs):
super(IStream, self).__init__(*args, **kwargs)
def set_server_settings(self, settings: ServerSettings):
self._settings = settings
def get_type(self):
raise NotImplementedError('subclasses must override get_type()!')
def get_id(self) -> str:
return str(self.id)
def to_channel_info(self) -> [ChannelInfo]:
ch = []
for out in self.output.urls:
epg = EpgInfo(self.get_id(), out.uri, self.name, self.icon)
ch.append(ChannelInfo(epg))
return ch
def to_front(self) -> dict:
return {StreamFields.NAME: self.name, StreamFields.ID: self.get_id(), StreamFields.TYPE: self.get_type()}
def fixup_output_urls(self):
return
class ProxyStream(IStream):
def __init__(self, *args, **kwargs):
super(ProxyStream, self).__init__(*args, **kwargs)
def get_type(self):
return constants.StreamType.PROXY
@classmethod
def make_stream(cls, settings: ServerSettings):
stream = cls()
stream._settings = settings
return stream
class HardwareStream(IStream):
log_level = IntField(default=constants.StreamLogLevel.LOG_LEVEL_INFO, required=True) log_level = IntField(default=constants.StreamLogLevel.LOG_LEVEL_INFO, required=True)
input = EmbeddedDocumentField(InputUrls, default=InputUrls()) input = EmbeddedDocumentField(InputUrls, default=InputUrls())
output = EmbeddedDocumentField(OutputUrls, default=OutputUrls())
have_video = BooleanField(default=constants.DEFAULT_HAVE_VIDEO, required=True) have_video = BooleanField(default=constants.DEFAULT_HAVE_VIDEO, required=True)
have_audio = BooleanField(default=constants.DEFAULT_HAVE_AUDIO, required=True) have_audio = BooleanField(default=constants.DEFAULT_HAVE_AUDIO, required=True)
audio_select = IntField(default=constants.INVALID_AUDIO_SELECT, required=True) audio_select = IntField(default=constants.INVALID_AUDIO_SELECT, required=True)
@ -139,13 +183,12 @@ class Stream(EmbeddedDocument):
_start_time = 0 _start_time = 0
_input_streams = str() _input_streams = str()
_output_streams = str() _output_streams = str()
_settings = ServerSettings()
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Stream, self).__init__(*args, **kwargs) super(HardwareStream, self).__init__(*args, **kwargs)
def set_server_settings(self, settings: ServerSettings): def get_type(self):
self._settings = settings raise NotImplementedError('subclasses must override get_type()!')
def reset(self): def reset(self):
self._status = constants.StreamStatus.NEW self._status = constants.StreamStatus.NEW
@ -172,11 +215,17 @@ class Stream(EmbeddedDocument):
self._output_streams = params[StreamFields.OUTPUT_STREAMS] self._output_streams = params[StreamFields.OUTPUT_STREAMS]
def to_front(self) -> dict: def to_front(self) -> dict:
return {StreamFields.NAME: self.name, StreamFields.ID: self.get_id(), StreamFields.TYPE: self.get_type(), front = super(HardwareStream, self).to_front()
StreamFields.STATUS: self._status, StreamFields.CPU: self._cpu, StreamFields.TIMESTAMP: self._timestamp, front[StreamFields.STATUS] = self._status
StreamFields.RSS: self._rss, StreamFields.LOOP_START_TIME: self._loop_start_time, front[StreamFields.CPU] = self._cpu
StreamFields.RESTARTS: self._restarts, StreamFields.START_TIME: self._start_time, front[StreamFields.TIMESTAMP] = self._timestamp
StreamFields.INPUT_STREAMS: self._input_streams, StreamFields.OUTPUT_STREAMS: self._output_streams} front[StreamFields.RSS] = self._rss
front[StreamFields.LOOP_START_TIME] = self._loop_start_time
front[StreamFields.RESTARTS] = self._restarts
front[StreamFields.START_TIME] = self._start_time
front[StreamFields.INPUT_STREAMS] = self._input_streams
front[StreamFields.OUTPUT_STREAMS] = self._output_streams
return front
def config(self) -> dict: def config(self) -> dict:
conf = { conf = {
@ -199,13 +248,6 @@ class Stream(EmbeddedDocument):
conf[AUDIO_SELECT_FIELD] = audio_select conf[AUDIO_SELECT_FIELD] = audio_select
return conf return conf
def to_channel_info(self) -> [ChannelInfo]:
ch = []
for out in self.output.urls:
epg = EpgInfo(self.get_id(), out.uri, self.name, self.icon)
ch.append(ChannelInfo(epg))
return ch
def generate_feedback_dir(self): def generate_feedback_dir(self):
return '{0}/{1}/{2}'.format(self._settings.feedback_directory, self.get_type(), self.get_id()) return '{0}/{1}/{2}'.format(self._settings.feedback_directory, self.get_type(), self.get_id())
@ -233,12 +275,6 @@ class Stream(EmbeddedDocument):
def get_have_audio(self): def get_have_audio(self):
return self.have_audio return self.have_audio
def get_id(self) -> str:
return str(self.id)
def get_type(self):
raise NotImplementedError('subclasses must override get_type()!')
def get_loop(self): def get_loop(self):
return self.loop return self.loop
@ -251,9 +287,6 @@ class Stream(EmbeddedDocument):
def get_auto_exit_time(self): def get_auto_exit_time(self):
return self.auto_exit_time return self.auto_exit_time
def fixup_output_urls(self):
return
# private # private
def _generate_http_root_dir(self, oid: int): def _generate_http_root_dir(self, oid: int):
return '{0}/{1}/{2}/{3}'.format(self._settings.hls_directory, self.get_type(), self.get_id(), oid) return '{0}/{1}/{2}/{3}'.format(self._settings.hls_directory, self.get_type(), self.get_id(), oid)
@ -284,10 +317,9 @@ class Stream(EmbeddedDocument):
self.output.urls[idx] = self.generate_vod_link(filename) self.output.urls[idx] = self.generate_vod_link(filename)
class RelayStream(Stream): class RelayStream(HardwareStream):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(RelayStream, self).__init__(*args, **kwargs) super(RelayStream, self).__init__(*args, **kwargs)
# super(RelayStream, self).type = constants.StreamType.RELAY
video_parser = StringField(default=constants.DEFAULT_VIDEO_PARSER, required=True) video_parser = StringField(default=constants.DEFAULT_VIDEO_PARSER, required=True)
audio_parser = StringField(default=constants.DEFAULT_AUDIO_PARSER, required=True) audio_parser = StringField(default=constants.DEFAULT_AUDIO_PARSER, required=True)
@ -310,8 +342,16 @@ class RelayStream(Stream):
def fixup_output_urls(self): def fixup_output_urls(self):
return self._fixup_http_output_urls() return self._fixup_http_output_urls()
@classmethod
def make_stream(cls, settings: ServerSettings):
stream = cls()
stream._settings = settings
stream.input = InputUrls(urls=[InputUrl(id=InputUrl.generate_id())])
stream.output = OutputUrls(urls=[OutputUrl(id=OutputUrl.generate_id())])
return stream
class EncodeStream(Stream):
class EncodeStream(HardwareStream):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(EncodeStream, self).__init__(*args, **kwargs) super(EncodeStream, self).__init__(*args, **kwargs)
@ -395,6 +435,14 @@ class EncodeStream(Stream):
def fixup_output_urls(self): def fixup_output_urls(self):
return self._fixup_http_output_urls() return self._fixup_http_output_urls()
@classmethod
def make_stream(cls, settings: ServerSettings):
stream = cls()
stream._settings = settings
stream.input = InputUrls(urls=[InputUrl(id=InputUrl.generate_id())])
stream.output = OutputUrls(urls=[OutputUrl(id=OutputUrl.generate_id())])
return stream
class TimeshiftRecorderStream(RelayStream): class TimeshiftRecorderStream(RelayStream):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -422,6 +470,13 @@ class TimeshiftRecorderStream(RelayStream):
def fixup_output_urls(self): def fixup_output_urls(self):
return return
@classmethod
def make_stream(cls, settings: ServerSettings):
stream = cls()
stream._settings = settings
stream.input = InputUrls(urls=[InputUrl(id=InputUrl.generate_id())])
return stream
class CatchupStream(TimeshiftRecorderStream): class CatchupStream(TimeshiftRecorderStream):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -432,6 +487,13 @@ class CatchupStream(TimeshiftRecorderStream):
def get_type(self): def get_type(self):
return constants.StreamType.CATCHUP return constants.StreamType.CATCHUP
@classmethod
def make_stream(cls, settings: ServerSettings):
stream = cls()
stream._settings = settings
stream.input = InputUrls(urls=[InputUrl(id=InputUrl.generate_id())])
return stream
class TimeshiftPlayerStream(RelayStream): class TimeshiftPlayerStream(RelayStream):
timeshift_dir = StringField(required=True) # FIXME default timeshift_dir = StringField(required=True) # FIXME default
@ -449,6 +511,14 @@ class TimeshiftPlayerStream(RelayStream):
conf[TIMESHIFT_DELAY] = self.timeshift_delay conf[TIMESHIFT_DELAY] = self.timeshift_delay
return conf return conf
@classmethod
def make_stream(cls, settings: ServerSettings):
stream = cls()
stream._settings = settings
stream.input = InputUrls(urls=[InputUrl(id=InputUrl.generate_id())])
stream.output = OutputUrls(urls=[OutputUrl(id=OutputUrl.generate_id())])
return stream
class TestLifeStream(RelayStream): class TestLifeStream(RelayStream):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -464,6 +534,14 @@ class TestLifeStream(RelayStream):
def fixup_output_urls(self): def fixup_output_urls(self):
return return
@classmethod
def make_stream(cls, settings: ServerSettings):
stream = cls()
stream._settings = settings
stream.input = InputUrls(urls=[InputUrl(id=InputUrl.generate_id())])
stream.output = OutputUrls(urls=[OutputUrl(id=OutputUrl.generate_id(), uri=constants.DEFAULT_TEST_URL)])
return stream
class VodRelayStream(RelayStream): class VodRelayStream(RelayStream):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -481,6 +559,14 @@ class VodRelayStream(RelayStream):
def fixup_output_urls(self): def fixup_output_urls(self):
return self._fixup_vod_output_urls() return self._fixup_vod_output_urls()
@classmethod
def make_stream(cls, settings: ServerSettings):
stream = cls()
stream._settings = settings
stream.input = InputUrls(urls=[InputUrl(id=InputUrl.generate_id())])
stream.output = OutputUrls(urls=[OutputUrl(id=OutputUrl.generate_id())])
return stream
class VodEncodeStream(EncodeStream): class VodEncodeStream(EncodeStream):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -498,66 +584,10 @@ class VodEncodeStream(EncodeStream):
def fixup_output_urls(self): def fixup_output_urls(self):
return self._fixup_vod_output_urls() return self._fixup_vod_output_urls()
@classmethod
def make_relay_stream(settings: ServerSettings) -> RelayStream: def make_stream(cls, settings: ServerSettings):
stream = RelayStream() stream = cls()
stream._settings = settings stream._settings = settings
stream.input = InputUrls(urls=[InputUrl(id=InputUrl.generate_id())]) stream.input = InputUrls(urls=[InputUrl(id=InputUrl.generate_id())])
stream.output = OutputUrls(urls=[OutputUrl(id=OutputUrl.generate_id())]) stream.output = OutputUrls(urls=[OutputUrl(id=OutputUrl.generate_id())])
return stream return stream
# loop false, input file, output vod folder hls
def make_vod_relay_stream(settings: ServerSettings) -> VodRelayStream:
stream = VodRelayStream()
stream._settings = settings
stream.input = InputUrls(urls=[InputUrl(id=InputUrl.generate_id())])
stream.output = OutputUrls(urls=[OutputUrl(id=OutputUrl.generate_id())])
return stream
def make_encode_stream(settings: ServerSettings) -> EncodeStream:
stream = EncodeStream()
stream._settings = settings
stream.input = InputUrls(urls=[InputUrl(id=InputUrl.generate_id())])
stream.output = OutputUrls(urls=[OutputUrl(id=OutputUrl.generate_id())])
return stream
# loop false, input file, output vod folder hls
def make_vod_encode_stream(settings: ServerSettings) -> VodEncodeStream:
stream = VodEncodeStream()
stream._settings = settings
stream.input = InputUrls(urls=[InputUrl(id=InputUrl.generate_id())])
stream.output = OutputUrls(urls=[OutputUrl(id=OutputUrl.generate_id())])
return stream
def make_timeshift_recorder_stream(settings: ServerSettings) -> TimeshiftRecorderStream:
stream = TimeshiftRecorderStream()
stream._settings = settings
stream.input = InputUrls(urls=[InputUrl(id=InputUrl.generate_id())])
return stream
def make_catchup_stream(settings: ServerSettings) -> CatchupStream:
stream = CatchupStream()
stream._settings = settings
stream.input = InputUrls(urls=[InputUrl(id=InputUrl.generate_id())])
return stream
def make_timeshift_player_stream(settings: ServerSettings) -> TimeshiftPlayerStream:
stream = TimeshiftPlayerStream()
stream._settings = settings
stream.input = InputUrls(urls=[InputUrl(id=InputUrl.generate_id())])
stream.output = OutputUrls(urls=[OutputUrl(id=OutputUrl.generate_id())])
return stream
def make_test_life_stream(settings: ServerSettings) -> TestLifeStream:
stream = TestLifeStream()
stream._settings = settings
stream.input = InputUrls(urls=[InputUrl(id=InputUrl.generate_id())])
stream.output = OutputUrls(urls=[OutputUrl(id=OutputUrl.generate_id(), uri=constants.DEFAULT_TEST_URL)])
return stream

View file

@ -5,12 +5,12 @@ from wtforms.validators import InputRequired, Length, NumberRange
from wtforms.fields import StringField, SubmitField, SelectField, IntegerField, FormField, BooleanField, FloatField from wtforms.fields import StringField, SubmitField, SelectField, IntegerField, FormField, BooleanField, FloatField
import app.constants as constants import app.constants as constants
from app.stream.stream_entry import Stream, RelayStream, EncodeStream, TimeshiftRecorderStream, CatchupStream, \ from app.stream.stream_entry import ProxyStream, HardwareStream, RelayStream, EncodeStream, TimeshiftRecorderStream, \
TimeshiftPlayerStream, TestLifeStream, VodRelayStream, VodEncodeStream CatchupStream, TimeshiftPlayerStream, TestLifeStream, VodRelayStream, VodEncodeStream
from app.common_forms import InputUrlsForm, OutputUrlsForm, SizeForm, LogoForm, RationalForm from app.common_forms import InputUrlsForm, OutputUrlsForm, SizeForm, LogoForm, RationalForm
class StreamForm(FlaskForm): class IStreamForm(FlaskForm):
name = StringField(lazy_gettext(u'Name:'), name = StringField(lazy_gettext(u'Name:'),
validators=[InputRequired(), validators=[InputRequired(),
Length(min=constants.MIN_STREAM_NAME_LENGTH, max=constants.MAX_STREAM_NAME_LENGTH)]) Length(min=constants.MIN_STREAM_NAME_LENGTH, max=constants.MAX_STREAM_NAME_LENGTH)])
@ -18,8 +18,24 @@ class StreamForm(FlaskForm):
validators=[InputRequired(), Length(min=constants.MIN_URL_LENGTH, max=constants.MAX_URL_LENGTH)]) validators=[InputRequired(), Length(min=constants.MIN_URL_LENGTH, max=constants.MAX_URL_LENGTH)])
group = StringField(lazy_gettext(u'Group:'), group = StringField(lazy_gettext(u'Group:'),
validators=[Length(min=constants.MIN_STREAM_GROUP_TITLE, max=constants.MAX_STREAM_GROUP_TITLE)]) validators=[Length(min=constants.MIN_STREAM_GROUP_TITLE, max=constants.MAX_STREAM_GROUP_TITLE)])
input = FormField(InputUrlsForm, lazy_gettext(u'Input:'))
output = FormField(OutputUrlsForm, lazy_gettext(u'Output:')) output = FormField(OutputUrlsForm, lazy_gettext(u'Output:'))
submit = SubmitField(lazy_gettext(u'Confirm'))
class ProxyStreamForm(IStreamForm):
def make_entry(self):
return self.update_entry(ProxyStream())
def update_entry(self, entry: ProxyStream):
entry.name = self.name.data
entry.icon = self.icon.data
entry.group = self.group.data
entry.output = self.output.get_data()
return entry
class HardwareStreamForm(IStreamForm):
input = FormField(InputUrlsForm, lazy_gettext(u'Input:'))
log_level = SelectField(lazy_gettext(u'Log level:'), validators=[], log_level = SelectField(lazy_gettext(u'Log level:'), validators=[],
choices=constants.AVAILABLE_LOG_LEVELS_PAIRS, coerce=constants.StreamLogLevel.coerce) choices=constants.AVAILABLE_LOG_LEVELS_PAIRS, coerce=constants.StreamLogLevel.coerce)
audio_select = IntegerField(lazy_gettext(u'Audio select:'), audio_select = IntegerField(lazy_gettext(u'Audio select:'),
@ -31,15 +47,12 @@ class StreamForm(FlaskForm):
restart_attempts = IntegerField(lazy_gettext(u'Max restart attempts and frozen:'), restart_attempts = IntegerField(lazy_gettext(u'Max restart attempts and frozen:'),
validators=[NumberRange(1, 1000)]) validators=[NumberRange(1, 1000)])
auto_exit_time = IntegerField(lazy_gettext(u'Auto exit time:'), validators=[]) auto_exit_time = IntegerField(lazy_gettext(u'Auto exit time:'), validators=[])
submit = SubmitField(lazy_gettext(u'Confirm'))
def make_entry(self): def make_entry(self):
return self.update_entry(Stream()) return self.update_entry(HardwareStream())
def update_entry(self, entry: Stream): def update_entry(self, entry: HardwareStream):
entry.name = self.name.data
entry.input = self.input.get_data() entry.input = self.input.get_data()
entry.output = self.output.get_data()
entry.audio_select = self.audio_select.data entry.audio_select = self.audio_select.data
entry.have_video = self.have_video.data entry.have_video = self.have_video.data
@ -52,7 +65,7 @@ class StreamForm(FlaskForm):
return entry return entry
class RelayStreamForm(StreamForm): class RelayStreamForm(HardwareStreamForm):
video_parser = SelectField(lazy_gettext(u'Video parser:'), validators=[], video_parser = SelectField(lazy_gettext(u'Video parser:'), validators=[],
choices=constants.AVAILABLE_VIDEO_PARSERS) choices=constants.AVAILABLE_VIDEO_PARSERS)
audio_parser = SelectField(lazy_gettext(u'Audio parser:'), validators=[], audio_parser = SelectField(lazy_gettext(u'Audio parser:'), validators=[],
@ -67,7 +80,7 @@ class RelayStreamForm(StreamForm):
return super(RelayStreamForm, self).update_entry(entry) return super(RelayStreamForm, self).update_entry(entry)
class EncodeStreamForm(StreamForm): class EncodeStreamForm(HardwareStreamForm):
relay_video = BooleanField(lazy_gettext(u'Relay video:'), validators=[]) relay_video = BooleanField(lazy_gettext(u'Relay video:'), validators=[])
relay_audio = BooleanField(lazy_gettext(u'Relay audio:'), validators=[]) relay_audio = BooleanField(lazy_gettext(u'Relay audio:'), validators=[])
deinterlace = BooleanField(lazy_gettext(u'Deinterlace:'), validators=[]) deinterlace = BooleanField(lazy_gettext(u'Deinterlace:'), validators=[])

View file

@ -6,8 +6,8 @@ from flask_login import login_required, current_user
import app.constants as constants import app.constants as constants
from app import get_runtime_stream_folder from app import get_runtime_stream_folder
from app.stream.stream_forms import EncodeStreamForm, RelayStreamForm, TimeshiftRecorderStreamForm, CatchupStreamForm, \ from app.stream.stream_forms import ProxyStreamForm, EncodeStreamForm, RelayStreamForm, TimeshiftRecorderStreamForm, \
TimeshiftPlayerStreamForm, TestLifeStreamForm, VodEncodeStreamForm, VodRelayStreamForm CatchupStreamForm, TimeshiftPlayerStreamForm, TestLifeStreamForm, VodEncodeStreamForm, VodRelayStreamForm
# routes # routes
@ -100,6 +100,21 @@ class StreamView(FlaskView):
# broadcast routes # broadcast routes
@login_required
@route('/add/proxy', methods=['GET', 'POST'])
def add_proxy(self):
server = current_user.get_current_server()
if server:
stream = server.make_relay_stream()
form = ProxyStreamForm(obj=stream)
if request.method == 'POST' and form.validate_on_submit():
new_entry = form.make_entry()
server.add_stream(new_entry)
return jsonify(status='ok'), 200
return render_template('stream/proxy/add.html', form=form, feedback_dir=stream.generate_feedback_dir())
return jsonify(status='failed'), 404
@login_required @login_required
@route('/add/relay', methods=['GET', 'POST']) @route('/add/relay', methods=['GET', 'POST'])
def add_relay(self): def add_relay(self):
@ -234,7 +249,16 @@ class StreamView(FlaskView):
stream = server.find_stream_by_id(sid) stream = server.find_stream_by_id(sid)
if stream: if stream:
type = stream.get_type() type = stream.get_type()
if type == constants.StreamType.RELAY: if type == constants.StreamType.PROXY:
form = ProxyStreamForm(obj=stream)
if request.method == 'POST' and form.validate_on_submit():
stream = form.update_entry(stream)
server.update_stream(stream)
return jsonify(status='ok'), 200
return render_template('stream/proxy/edit.html', form=form)
elif type == constants.StreamType.RELAY:
form = RelayStreamForm(obj=stream) form = RelayStreamForm(obj=stream)
if request.method == 'POST' and form.validate_on_submit(): if request.method == 'POST' and form.validate_on_submit():

View file

@ -0,0 +1,4 @@
{% extends 'stream/proxy/base.html' %}
{% block title %}
Add proxy stream
{% endblock %}

View file

@ -0,0 +1,47 @@
{% from 'bootstrap/wtf.html' import form_field %}
{% macro render_bootstrap_field(field) %}
<div class="row">
<label class="col-md-4">{{ field.label }}</label>
<div class="col-md-8">
{{ field(class='form-control')|safe }}
</div>
</div>
{% endmacro %}
{% macro render_bootstrap_form(form) %}
<div class="row">
<label class="col-md-4">{{ form.label }}</label>
<div class="col-md-8">
{{ form() }}
</div>
</div>
{% endmacro %}
<form id="stream_entry_form" name="stream_entry_form" class="form" method="post">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">
{% block title %}
{% endblock %}
</h4>
</div>
<div class="modal-body">
{{ form.hidden_tag() }}
<br>
{{ render_bootstrap_field(form.name) }}
<br>
{{ render_bootstrap_field(form.icon) }}
<br>
{{ render_bootstrap_field(form.group) }}
<br>
{{ render_bootstrap_form(form.output) }}
</div>
<div class="modal-footer">
{% block footer %}
<button type="button" class="btn btn-danger" data-dismiss="modal">{% trans %}Cancel{% endtrans %}</button>
{{ form_field(form.submit, class="btn btn-success") }}
{% endblock %}
</div>
</form>

View file

@ -0,0 +1,4 @@
{% extends 'stream/proxy/base.html' %}
{% block title %}
Edit proxy stream
{% endblock %}

View file

@ -32,6 +32,10 @@
<br> <br>
{{ render_bootstrap_field(form.name) }} {{ render_bootstrap_field(form.name) }}
<br> <br>
{{ render_bootstrap_field(form.icon) }}
<br>
{{ render_bootstrap_field(form.group) }}
<br>
{{ render_bootstrap_field(form.audio_select) }} {{ render_bootstrap_field(form.audio_select) }}
<br> <br>
{{ render_bootstrap_field(form.have_video) }} {{ render_bootstrap_field(form.have_video) }}

View file

@ -44,6 +44,7 @@ Dashboard | {{ config['PUBLIC_CONFIG'].site.title }}
th.stream_actions { th.stream_actions {
width: 26%; width: 26%;
} }
</style> </style>
{{super()}} {{super()}}
{% endblock %} {% endblock %}
@ -246,6 +247,7 @@ Dashboard | {{ config['PUBLIC_CONFIG'].site.title }}
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<li class="active"><a href="#streams" data-toggle="tab">Streams</a></li> <li class="active"><a href="#streams" data-toggle="tab">Streams</a></li>
<li><a href="#vods" data-toggle="tab">Vods</a></li> <li><a href="#vods" data-toggle="tab">Vods</a></li>
<li><a href="#proxy" data-toggle="tab">Proxy</a></li>
</ul> </ul>
</div> </div>
<div class="panel-body"> <div class="panel-body">
@ -271,12 +273,13 @@ Dashboard | {{ config['PUBLIC_CONFIG'].site.title }}
</thead> </thead>
<tbody> <tbody>
{% for rev in streams %} {% for rev in streams %}
{% if (rev.type != 6 and rev.type != 7) %} {% if (rev.type != 7 and rev.type != 8 and rev.type != 0) %}
<tr id='{{ rev.id }}'> <tr id='{{ rev.id }}'>
<td>{{ loop.index }}</td> <td>{{ loop.index }}</td>
<td>{{ rev.name }}</td> <td>{{ rev.name }}</td>
<td> <td>
{{ ['RELAY', 'ENCODE', 'TIMESHIFT_PLAYER', 'TIMESHIFT_RECORDER', 'CATCHUP', {{ ['PROXY','RELAY', 'ENCODE', 'TIMESHIFT_PLAYER', 'TIMESHIFT_RECORDER',
'CATCHUP',
'TEST_LIFE', 'TEST_LIFE',
'VOD_RELAY', 'VOD_ENCODE'][rev.type] }} 'VOD_RELAY', 'VOD_ENCODE'][rev.type] }}
</td> </td>
@ -411,12 +414,13 @@ Dashboard | {{ config['PUBLIC_CONFIG'].site.title }}
</thead> </thead>
<tbody> <tbody>
{% for vod in streams %} {% for vod in streams %}
{% if (vod.type == 6 or vod.type == 7) %} {% if (vod.type == 7 or vod.type == 8) %}
<tr id='{{ vod.id }}'> <tr id='{{ vod.id }}'>
<td>{{ loop.index }}</td> <td>{{ loop.index }}</td>
<td>{{ vod.name }}</td> <td>{{ vod.name }}</td>
<td> <td>
{{ ['RELAY', 'ENCODE', 'TIMESHIFT_PLAYER', 'TIMESHIFT_RECORDER', 'CATCHUP', {{ ['PROXY','RELAY', 'ENCODE', 'TIMESHIFT_PLAYER', 'TIMESHIFT_RECORDER',
'CATCHUP',
'TEST_LIFE', 'TEST_LIFE',
'VOD_RELAY', 'VOD_ENCODE'][vod.type] }} 'VOD_RELAY', 'VOD_ENCODE'][vod.type] }}
</td> </td>
@ -482,6 +486,51 @@ Dashboard | {{ config['PUBLIC_CONFIG'].site.title }}
</button> </button>
</div> </div>
</div> </div>
<div class="tab-pane fade" id="proxy">
<div class="row">
<table id='proxy_table' class="table">
<thead>
<tr>
<th class="stream_number">#</th>
<th class="stream_name">{% trans %}Name{% endtrans %}</th>
<th class="stream_type">{% trans %}Type{% endtrans %}</th>
<th class="stream_actions">{% trans %}Actions{% endtrans %}</th>
</tr>
</thead>
<tbody>
{% for proxy in streams %}
{% if (proxy.type == 0) %}
<tr id='{{ proxy.id }}'>
<td>{{ loop.index }}</td>
<td>{{ proxy.name }}</td>
<td>
{{ ['PROXY','RELAY', 'ENCODE', 'TIMESHIFT_PLAYER', 'TIMESHIFT_RECORDER',
'CATCHUP',
'TEST_LIFE',
'VOD_RELAY', 'VOD_ENCODE'][proxy.type] }}
</td>
<td>
<button type="submit" class="btn btn-success btn-xs"
onclick="edit_stream('{{ proxy.id }}')">
{% trans %}Edit{% endtrans %}
</button>
<button type="submit" class="btn btn-danger btn-xs"
onclick="remove_stream('{{ proxy.id }}')">
{% trans %}Remove{% endtrans %}
</button>
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
<div class="row">
<button class="btn btn-warning btn-send col-md-12" onclick="add_proxy_stream()">
{% trans %}Add proxy{% endtrans %}
</button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -701,6 +750,18 @@ Dashboard | {{ config['PUBLIC_CONFIG'].site.title }}
}) })
}); });
} }
function add_proxy_stream() {
var url = "{{ url_for('StreamView:add_proxy') }}";
$.get(url, function(data) {
$('#stream_dialog .modal-content').html(data);
$('#stream_dialog').modal();
$('#submit').click(function(event) {
event.preventDefault();
add_stream_entry(url);
})
});
}
function edit_stream(sid) { function edit_stream(sid) {
var url = "/stream/edit/" + sid; var url = "/stream/edit/" + sid;
$.get(url, function(data) { $.get(url, function(data) {
@ -809,5 +870,6 @@ Dashboard | {{ config['PUBLIC_CONFIG'].site.title }}
} }
}); });
} }
</script> </script>
{% endblock %} {% endblock %}

View file

@ -6,7 +6,7 @@ from mongoengine import connect
sys.path.append(os.path.join(os.path.dirname(__file__), '..')) sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from app.stream.stream_entry import make_test_life_stream from app.stream.stream_entry import TestLifeStream
from app.service.service import ServiceSettings from app.service.service import ServiceSettings
from app.utils.m3u_parser import M3uParser from app.utils.m3u_parser import M3uParser
@ -26,7 +26,7 @@ if __name__ == '__main__':
m3u_parser.read_m3u(argv.uri) m3u_parser.read_m3u(argv.uri)
m3u_parser.parse() m3u_parser.parse()
for file in m3u_parser.files: for file in m3u_parser.files:
stream = make_test_life_stream(service_settings.feedback_directory) stream = TestLifeStream.make_stream(service_settings)
stream.input.urls[0].uri = file['link'] stream.input.urls[0].uri = file['link']
stream.name = '{0}({1})'.format(file['tvg-group'], file['title']) stream.name = '{0}({1})'.format(file['tvg-group'], file['title'])
service_settings.streams.append(stream) service_settings.streams.append(stream)