def list_streams(): """List all streams""" streams = StreamModel.query() form = StreamForm() if form.is_submitted(): if form.validate(): stream = StreamModel(stream_name=form.stream_name.data, stream_type=form.stream_type.data, stream_hostname=form.stream_hostname.data, stream_port=form.stream_port.data, stream_sid=form.stream_sid.data, stream_mount=form.stream_mount.data, added_by=users.get_current_user()) try: stream.put() stream_id = stream.key.id() flash(u'Stream %s successfully saved.' % stream_id, 'success') return redirect(url_for('list_streams')) except CapabilityDisabledError: flash(u'App Engine Datastore is currently in read-only mode.', 'info') return redirect(url_for('list_streams')) else: flash(u'Error validating stream. Please check your input.', 'error') return render_template('list_streams.html', streams=streams, form=form)
def list_streams(): """List all streams""" streams = StreamModel.query() form = StreamForm() if form.is_submitted(): if form.validate(): stream = StreamModel( stream_name = form.stream_name.data, stream_type = form.stream_type.data, stream_hostname = form.stream_hostname.data, stream_port = form.stream_port.data, stream_sid = form.stream_sid.data, stream_mount = form.stream_mount.data, added_by = users.get_current_user() ) try: stream.put() stream_id = stream.key.id() flash(u'Stream %s successfully saved.' % stream_id, 'success') return redirect(url_for('list_streams')) except CapabilityDisabledError: flash(u'App Engine Datastore is currently in read-only mode.', 'info') return redirect(url_for('list_streams')) else: flash(u'Error validating stream. Please check your input.', 'error') return render_template('list_streams.html', streams=streams, form=form)
def __init__(self): super(ApplicationWindow, self).__init__(None) self.setup_ui() # Connect threading signals self.add_frame.connect(self.setup_videoframe) self.fail_add_stream.connect(self.on_fail_add_stream) self.model = StreamModel(self.grid)
def check(): """Check streams cron job""" streams = StreamModel.query() for stream in streams: stream_id = stream.key.id() queue = taskqueue.Queue('check') task = taskqueue.Task(url='/stream/' + str(stream_id) + '/check', method='GET') queue.add(task) return ''
def delete_stream(stream_id): """Delete a stream object""" stream = StreamModel.get_by_id(stream_id) try: stream.key.delete() flash(u'Stream %s successfully deleted.' % stream_id, 'success') return redirect(url_for('list_streams')) except CapabilityDisabledError: flash(u'App Engine Datastore is currently in read-only mode.', 'info') return redirect(url_for('list_streams'))
def edit_stream(stream_id): stream = StreamModel.get_by_id(stream_id) form = StreamForm(obj=stream) if form.validate_on_submit(): stream.stream_name = form.data.get('stream_name') stream.stream_type = form.data.get('stream_type') stream.stream_hostname = form.data.get('stream_hostname') stream.stream_port = form.data.get('stream_port') stream.stream_sid = form.data.get('stream_sid') stream.stream_mount = form.data.get('stream_mount') stream.put() flash(u'Stream %s successfully saved.' % stream_id, 'success') return redirect(url_for('list_streams')) return render_template('edit_stream.html', stream=stream, form=form)
def show_stream(stream_id): """Show stream object""" start_date_str = request.values.get('s', None) end_date_str = request.values.get('e', None) if start_date_str is not None: start_date = datetime.fromtimestamp(int(start_date_str) / 1000) else: start_date = datetime.utcnow() start_date += timedelta(days=-1) if end_date_str is not None: end_date = datetime.fromtimestamp(int(end_date_str) / 1000) else: end_date = datetime.utcnow() stream = StreamModel.get_by_id(stream_id) stream_checks_query = StreamCheckModel.query().filter( StreamCheckModel.stream == stream.key, StreamCheckModel.timestamp >= start_date, StreamCheckModel.timestamp <= end_date).order( StreamCheckModel.timestamp) stream_checks = stream_checks_query.fetch(1000) server_uptime_sum = 0 stream_uptime_sum = 0 average_listeners_sum = 0 average_listeners_count = 0 average_listen_time_count = 0 average_listen_time_sum = 0 for stream_check in stream_checks: if stream_check.server_status > 0: server_uptime_sum += 1 if stream_check.stream_status > 0: stream_uptime_sum += 1 if stream_check.current_listeners is not None: average_listeners_sum += stream_check.current_listeners average_listeners_count += 1 if stream_check.average_listen_time is not None: average_listen_time_sum += stream_check.average_listen_time average_listen_time_count += 1 status = 'Up' if len(stream_checks) > 0: server_uptime = round(((server_uptime_sum / len(stream_checks)) * 100), 2) stream_uptime = round(((stream_uptime_sum / len(stream_checks)) * 100), 2) server_uptime_moving_average = get_server_uptime_moving_average( stream_checks, 144) stream_uptime_moving_average = get_stream_uptime_moving_average( stream_checks, 144) else: server_uptime = 0.0 stream_uptime = 0.0 server_uptime_moving_average = [] stream_uptime_moving_average = [] if average_listeners_count > 0: average_listeners = round( (average_listeners_sum / average_listeners_count), 2) else: average_listeners = 0 if average_listen_time_count > 0: average_listen_time = str( timedelta(seconds=int(average_listen_time_sum / average_listen_time_count))) else: average_listen_time = 0 return render_template( 'show_stream.html', stream=stream, stream_checks=stream_checks, status=status, server_uptime=server_uptime, stream_uptime=stream_uptime, average_listeners=average_listeners, server_uptime_moving_average=server_uptime_moving_average, stream_uptime_moving_average=stream_uptime_moving_average, average_listen_time=average_listen_time, start_date=start_date.strftime("%B %d, %Y %H:%M:%S"), end_date=end_date.strftime("%B %d, %Y %H:%M:%S"))
def show_stream(stream_id): """Show stream object""" start_date_str = request.values.get('s', None) end_date_str = request.values.get('e', None) if start_date_str is not None: start_date = datetime.fromtimestamp(int(start_date_str) / 1000) else: start_date = datetime.utcnow() start_date += timedelta(days=-1) if end_date_str is not None: end_date = datetime.fromtimestamp(int(end_date_str) / 1000) else: end_date = datetime.utcnow() stream = StreamModel.get_by_id(stream_id) stream_checks_query = StreamCheckModel.query().filter(StreamCheckModel.stream == stream.key, StreamCheckModel.timestamp >= start_date, StreamCheckModel.timestamp <= end_date).order(StreamCheckModel.timestamp) stream_checks = stream_checks_query.fetch(1000) server_uptime_sum = 0 stream_uptime_sum = 0 average_listeners_sum = 0 average_listeners_count = 0 average_listen_time_count = 0 average_listen_time_sum = 0 for stream_check in stream_checks: if stream_check.server_status > 0: server_uptime_sum += 1 if stream_check.stream_status > 0: stream_uptime_sum += 1 if stream_check.current_listeners is not None: average_listeners_sum += stream_check.current_listeners average_listeners_count += 1 if stream_check.average_listen_time is not None: average_listen_time_sum += stream_check.average_listen_time average_listen_time_count += 1 status = 'Up' if len(stream_checks) > 0: server_uptime = round(((server_uptime_sum / len(stream_checks)) * 100), 2) stream_uptime = round(((stream_uptime_sum / len(stream_checks)) * 100), 2) server_uptime_moving_average = get_server_uptime_moving_average(stream_checks, 144) stream_uptime_moving_average = get_stream_uptime_moving_average(stream_checks, 144) else: server_uptime = 0.0 stream_uptime = 0.0 server_uptime_moving_average = [] stream_uptime_moving_average = [] if average_listeners_count > 0: average_listeners = round((average_listeners_sum / average_listeners_count), 2) else: average_listeners = 0 if average_listen_time_count > 0: average_listen_time = str(timedelta(seconds=int(average_listen_time_sum / average_listen_time_count))) else: average_listen_time = 0 return render_template( 'show_stream.html', stream=stream, stream_checks=stream_checks, status=status, server_uptime=server_uptime, stream_uptime=stream_uptime, average_listeners=average_listeners, server_uptime_moving_average=server_uptime_moving_average, stream_uptime_moving_average=stream_uptime_moving_average, average_listen_time=average_listen_time, start_date=start_date.strftime("%B %d, %Y %H:%M:%S"), end_date=end_date.strftime("%B %d, %Y %H:%M:%S"))
class ApplicationWindow(QtWidgets.QMainWindow): """The main GUI window.""" # Define a new signal, used to add_frames from the separate thread add_frame = QtCore.pyqtSignal(str, dict, str, VideoFrameCoordinates) # Used when the add stream thread fails fail_add_stream = QtCore.pyqtSignal(AddStreamError, tuple) def __init__(self): super(ApplicationWindow, self).__init__(None) self.setup_ui() # Connect threading signals self.add_frame.connect(self.setup_videoframe) self.fail_add_stream.connect(self.on_fail_add_stream) self.model = StreamModel(self.grid) def setup_ui(self): """Loads the main.ui file and sets up the window and grid.""" self.ui = uic.loadUi("ui/main.ui", self) with open("ui/styles.qss", "r") as f: self.setStyleSheet(f.read()) self.grid = VideoFrameGrid(self) self.container = self.ui.findChild(QtCore.QObject, "container") self.container.addLayout(self.grid) self.menubar = self.ui.findChild(QtCore.QObject, "MenuBar") # Connect up all actions. self.actions = {} self.actions[MUTE_CHECKBOX] = self.ui.findChild( QtCore.QObject, MUTE_ALL_STREAMS) self.__bind_view_to_action(MUTE_ALL_STREAMS, self.mute_all_streams, toggled=True) self.__bind_view_to_action(EXPORT_STREAMS_TO_CLIPBOARD, self.export_streams_to_clipboard) self.__bind_view_to_action(ADD_NEW_STREAM, self.add_new_stream) self.__bind_view_to_action(ADD_NEW_SCHEDULED_STREAM, self.add_new_scheduled_stream) self.__bind_view_to_action(IMPORT_STREAMS_FROM_CLIPBOARD, self.import_streams_from_clipboard) self.__bind_view_to_action(SETTINGS_MENU, self.show_settings) self.__bind_view_to_action(LOAD_STREAM_HISTORY, self.stream_history) self.recent_menu = self.ui.findChild(QtCore.QObject, "menuRecent") # Create the loading gear but dont add it to anywhere, just save it self.setup_loading_gif() self.ui.show() def setup_videoframe(self, stream_url, stream_options, stream_quality): """Sets up a videoframe and with the provided stream information.""" self.model.add_new_videoframe(stream_url, stream_options, stream_quality) # Remove the loading feedback self.hide_loading_gif() # Update recent meny option self.update_recent() def setup_loading_gif(self): """Creates the loading gear as QMovie and its label.""" self.movie = QtGui.QMovie(self) self.movie.setFileName("ui/res/loading.gif") self.movie.setCacheMode(QtGui.QMovie.CacheAll) self.movie.start() self.loading = QtWidgets.QLabel(self) self.loading.setAlignment(QtCore.Qt.AlignCenter) self.loading.setMovie(self.movie) self.loading.hide() def show_loading_gif(self): """Shows the loading gif on the next frame location""" self.model.add_widget(self.loading, self.model.grid.coordinates.x, self.model.grid.coordinates.y) self.loading.show() def hide_loading_gif(self): """Removes the loading gif from the next frame location""" self.model.remove_widget(self.loading) self.loading.hide() def mute_all_streams(self): """Toggles the audio of all the players.""" is_mute_checked = self.actions[MUTE_CHECKBOX].isChecked() self.model.mute_all_streams(is_mute_checked) def export_streams_to_clipboard(self): """Exports all streams to the users clipboard.""" text = self.model.export_streams_to_clipboard() clipboard = QtWidgets.QApplication.clipboard() clipboard.clear(mode=clipboard.Clipboard) clipboard.setText(text, mode=clipboard.Clipboard) # Give some feedback to the user QtWidgets.QMessageBox.information( self, "Export streams", "Succesfully exported the following streams to your clipboard:\n" + text) def show_settings(self): """Shows a dialog containing settings for DSW""" self.dialog = QDialog(self) self.dialog.ui = uic.loadUi(SETTINGS_UI_FILE, self.dialog) self.dialog.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.dialog.findChild(QtCore.QObject, BUTTONBOX) \ .accepted.connect(self.generate_conf) if cfg[CONFIG_BUFFER_STREAM]: self.dialog.findChild(QtCore.QObject, RECORD_SETTINGS).setChecked(True) if cfg[CONFIG_MUTE]: self.dialog.findChild(QtCore.QObject, MUTE_SETTINGS).setChecked(True) self.dialog.findChild(QtCore.QObject, QUALITY_SETTINGS) \ .setText(CONFIG_QUALITY_DELIMITER_JOIN.join(cfg[CONFIG_QUALITY])) self.dialog.findChild(QtCore.QObject, BUFFER_SIZE).setValue(cfg[CONFIG_BUFFER_SIZE]) self.dialog.show() def generate_conf(self): """Generate a config from the provided settings.""" # Quality options should be provided as a comma-separated string. quality_text = self.dialog.findChild(QtCore.QObject, QUALITY_SETTINGS).text() quality_options = [ quality.strip() for quality in quality_text.split(CONFIG_QUALITY_DELIMITER_SPLIT) ] # Set new cfg values cfg[CONFIG_QUALITY] = quality_options cfg[CONFIG_BUFFER_STREAM] = self.dialog.findChild( QtCore.QObject, RECORD_SETTINGS).isChecked() cfg[CONFIG_MUTE] = self.dialog.findChild(QtCore.QObject, MUTE_SETTINGS).isChecked() cfg[CONFIG_BUFFER_SIZE] = self.dialog.findChild( QtCore.QObject, BUFFER_SIZE).value() QtWidgets.QMessageBox.critical( self, "Caution", "Some of your changes may require a restart in order to take effect." ) try: cfg.dump() except IOError: print("Could not dump config file.") def update_recent(self): """Updates the recent menu option.""" self.recent_menu.clear() # Only the 30 recently added streams are shown for url in self.grid.url_list[:30]: action = QtWidgets.QAction(url, parent=self) action.triggered.connect( self.add_stream_from_history(action.text())) self.recent_menu.addAction(action) def add_stream_from_history(self, stream_url): def func(): self.add_new_stream(stream_url) return func def import_streams_from_clipboard(self): """Imports all streams from the users clipboard.""" streams = QtWidgets.QApplication.clipboard().text().rsplit("\n") for stream in streams: self.add_new_stream(stream_url=stream) def add_new_stream(self, stream_url=None, stream_qualities=None): """Adds a new player for the specified stream in the grid.""" if not stream_url: stream_url, ok = QtWidgets.QInputDialog.getText( self, "Stream input", "Enter the stream URL:") if not ok: return # Use default quality if not specified if not stream_qualities: stream_qualities = cfg[CONFIG_QUALITY] # Lower case the stream url for easier handling in future cases stream_url = self.model.parse_url(stream_url) # Give some feedback to the user self.show_loading_gif() # Run the rest on a separate thread to be able to show the loading feedback # Also helps a lot with lag threading.Thread(target=self._add_new_stream, args=(stream_url, stream_qualities)).start() def add_new_scheduled_stream(self, stream_url=None, stream_qualities=None): """Schedules a new stream at given time""" if not stream_url: stream_url, ok1 = QtWidgets.QInputDialog.getText( self, "Schedule stream", "Enter the stream URL:") if not ok1: return # Use default quality if not specified if not stream_qualities: stream_qualities = cfg[CONFIG_QUALITY] inputTime, ok2 = QtWidgets.QInputDialog.getText( self, "Schedule stream", "Time (HH.MM)") if not ok2: return # Add stream after certain delay (factory function) def schedule_stream(): self.model.save_stream_to_history(stream_url) self._add_new_stream(stream_url, stream_qualities) try: h, m = inputTime.split(".") now = datetime.now() runtime = now.replace(hour=int(h), minute=int(m), second=0, microsecond=0) if not runtime > now: raise ValueError delay = (runtime - now).total_seconds() * 1000 QtCore.QTimer(self).singleShot(delay, schedule_stream) QtWidgets.QMessageBox.information( self, "Schedule stream", "Succesfully scheduled stream at " + h + "." + m) except ValueError: QtWidgets.QMessageBox().warning(self, "Error!", "Not a valid time") def _add_new_stream(self, stream_url, stream_qualities): """Fetches qualities and if possible adds a frame to the main window.""" try: stream_options = self.model.get_stream_options(stream_url) # If the stream is not currently broadcasting, 'stream_options' # will be an empty list. if not stream_options: raise streamlink.exceptions.NoStreamsError(stream_url) # Quality options that are both available and set as default values. available_qualities = [ q for q in stream_qualities if q in stream_options ] if not available_qualities: self.fail_add_stream.emit( AddStreamError.DEFAULT_QUALITY_MISSING, (stream_options, stream_url)) return # Take the first available quality since that has the highest priority. self.add_frame.emit(stream_url, stream_options, available_qualities[0], self.model.grid.coordinates) # Save url to stream history self.model.save_stream_to_history(stream_url) except streamlink.exceptions.NoPluginError: self.fail_add_stream.emit( AddStreamError.URL_NOT_SUPPORTED, ("Error", "Could not open stream: The provided URL is not supported")) except streamlink.exceptions.PluginError: self.fail_add_stream.emit(AddStreamError.OTHER, ("Error", textwrap.dedent(""" Could not open stream! The provided URL is supported, but could not be opened. Check for spelling mistakes! """))) except streamlink.exceptions.NoStreamsError: self.fail_add_stream.emit(AddStreamError.OTHER, ("Error", textwrap.dedent(""" Could not open stream: No streams was found. Is the stream running? """))) except streamlink.exceptions.StreamError: self.fail_add_stream.emit(AddStreamError.OTHER, ("Error", "Could not open stream.")) except streamlink.exceptions.StreamlinkError: self.fail_add_stream.emit(AddStreamError.OTHER, ("Error", "Could not open stream.")) def on_fail_add_stream(self, err, args): # Remove the loading feedback self.hide_loading_gif() if err == AddStreamError.URL_NOT_SUPPORTED: (title, text) = args QtWidgets.QMessageBox().warning(self, title, text) elif err == AddStreamError.DEFAULT_QUALITY_MISSING: (stream_options, url) = args quality, ok = self._get_user_quality_preference(stream_options) if ok: self.add_new_stream(url, quality) return else: (title, text) = args QtWidgets.QMessageBox().warning(self, title, text) self.add_new_stream() def __bind_view_to_action(self, view, action, toggled=False): return self.ui.findChild( QtCore.QObject, view).toggled.connect(action) if toggled else self.ui.findChild( QtCore.QObject, view).triggered.connect(action) def _get_user_quality_preference(self, stream_options): """Prompts the user to select what quality they want on the stream.""" qualities = LiveStreamContainer.quality_options(stream_options) return QtWidgets.QInputDialog.getItem( self, "Stream Quality option", textwrap.dedent(""" The default stream quality option could not be used. Please select another one: """), reversed(qualities)) def stream_history(self): """Starts streaming all streams that were playing when last session was closed.""" if self.model.stream_history: for stream in self.model.stream_history: self.add_new_stream(stream)
def check_stream_service(stream_id): stream = StreamModel.get_by_id(stream_id) if stream.stream_type == 'sc2': check_sc2_stream(stream) elif stream.stream_type == 'ic2': check_ic2_stream(stream)