示例#1
0
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)
示例#2
0
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)
示例#3
0
    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)
示例#4
0
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 ''
示例#5
0
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'))
示例#6
0
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 ''
示例#7
0
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'))
示例#8
0
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)
示例#9
0
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)
示例#10
0
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"))
示例#11
0
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"))
示例#12
0
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)
示例#13
0
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)
示例#14
0
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)