From 229ed6d84ea38397c3092c0e59df90c8988877c4 Mon Sep 17 00:00:00 2001 From: Sunny Dhoke Date: Wed, 9 Oct 2019 17:51:27 +0530 Subject: [PATCH 1/2] Converting to Python 3 Using 2to3. Creating .bak for backups --- .idea/.gitignore | 3 + .idea/freeseer.iml | 11 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + docs/source/conf.py | 12 +- docs/source/conf.py.bak | 221 +++++ src/freeseer/framework/config/core.py | 18 +- src/freeseer/framework/config/core.py.bak | 252 ++++++ .../framework/config/persist/configparser.py | 6 +- .../config/persist/configparser.py.bak | 58 ++ .../framework/config/persist/jsonstorage.py | 4 +- .../config/persist/jsonstorage.py.bak | 69 ++ src/freeseer/framework/config/profile.py | 2 +- src/freeseer/framework/config/profile.py.bak | 206 +++++ src/freeseer/framework/database.py | 68 +- src/freeseer/framework/database.py.bak | 605 +++++++++++++ src/freeseer/framework/qt_key_grabber.py | 14 +- src/freeseer/framework/qt_key_grabber.py.bak | 95 +++ src/freeseer/framework/util.py | 10 +- src/freeseer/framework/util.py.bak | 174 ++++ src/freeseer/framework/youtube.py | 16 +- src/freeseer/framework/youtube.py.bak | 220 +++++ src/freeseer/frontend/cli.py | 20 +- src/freeseer/frontend/cli.py.bak | 343 ++++++++ src/freeseer/frontend/configtool/AVWidget.py | 2 +- .../frontend/configtool/AVWidget.py.bak | 224 +++++ .../frontend/configtool/GeneralWidget.py | 2 +- .../frontend/configtool/GeneralWidget.py.bak | 127 +++ .../frontend/configtool/PluginWidget.py | 2 +- .../frontend/configtool/PluginWidget.py.bak | 151 ++++ .../frontend/configtool/configtool.py | 14 +- .../frontend/configtool/configtool.py.bak | 760 +++++++++++++++++ src/freeseer/frontend/controller/recording.py | 4 +- .../frontend/controller/recording.py.bak | 244 ++++++ src/freeseer/frontend/qtcommon/AboutDialog.py | 8 +- .../frontend/qtcommon/AboutDialog.py.bak | 79 ++ src/freeseer/frontend/qtcommon/AboutWidget.py | 30 +- .../frontend/qtcommon/AboutWidget.py.bak | 176 ++++ .../frontend/record/RecordingController.py | 10 +- .../record/RecordingController.py.bak | 110 +++ src/freeseer/frontend/record/record.py | 14 +- src/freeseer/frontend/record/record.py.bak | 762 +++++++++++++++++ .../frontend/reporteditor/reporteditor.py | 12 +- .../frontend/reporteditor/reporteditor.py.bak | 253 ++++++ .../frontend/talkeditor/talkeditor.py | 22 +- .../frontend/talkeditor/talkeditor.py.bak | 626 ++++++++++++++ src/freeseer/frontend/upload/youtube.py | 12 +- src/freeseer/frontend/upload/youtube.py.bak | 126 +++ .../audioinput/jackaudiosrc/__init__.py | 2 +- .../audioinput/jackaudiosrc/__init__.py.bak | 120 +++ .../plugins/audioinput/pulsesrc/__init__.py | 4 +- .../audioinput/pulsesrc/__init__.py.bak | 134 +++ .../audiomixer/audiopassthrough/__init__.py | 2 +- .../audiopassthrough/__init__.py.bak | 138 +++ .../plugins/audiomixer/multiaudio/__init__.py | 2 +- .../audiomixer/multiaudio/__init__.py.bak | 176 ++++ src/freeseer/plugins/importer/csv_importer.py | 20 +- .../plugins/importer/csv_importer.py.bak | 77 ++ .../importer/rss_feedparser/__init__.py | 6 +- .../importer/rss_feedparser/__init__.py.bak | 121 +++ .../plugins/output/audiofeedback/__init__.py | 2 +- .../output/audiofeedback/__init__.py.bak | 104 +++ .../plugins/output/ogg_icecast/__init__.py | 4 +- .../output/ogg_icecast/__init__.py.bak | 252 ++++++ .../plugins/output/ogg_output/__init__.py | 4 +- .../plugins/output/ogg_output/__init__.py.bak | 247 ++++++ .../plugins/output/rtmp_streaming/__init__.py | 15 +- .../output/rtmp_streaming/__init__.py.bak | 805 ++++++++++++++++++ .../plugins/output/videopreview/__init__.py | 2 +- .../output/videopreview/__init__.py.bak | 125 +++ .../plugins/output/webm_output/__init__.py | 2 +- .../output/webm_output/__init__.py.bak | 140 +++ .../plugins/videoinput/desktop/__init__.py | 2 +- .../videoinput/desktop/__init__.py.bak | 229 +++++ .../videoinput/firewiresrc/__init__.py | 2 +- .../videoinput/firewiresrc/__init__.py.bak | 151 ++++ .../plugins/videoinput/usbsrc/__init__.py | 6 +- .../plugins/videoinput/usbsrc/__init__.py.bak | 172 ++++ .../videoinput/videotestsrc/__init__.py | 2 +- .../videoinput/videotestsrc/__init__.py.bak | 116 +++ .../plugins/videomixer/pip/__init__.py | 6 +- .../plugins/videomixer/pip/__init__.py.bak | 257 ++++++ .../videomixer/videopassthrough/__init__.py | 4 +- .../videopassthrough/__init__.py.bak | 226 +++++ .../framework/config/options/test_choice.py | 4 +- .../config/options/test_choice.py.bak | 95 +++ .../framework/config/options/test_float.py | 6 +- .../config/options/test_float.py.bak | 72 ++ .../framework/config/options/test_folder.py | 4 +- .../config/options/test_folder.py.bak | 105 +++ .../framework/config/options/test_integer.py | 6 +- .../config/options/test_integer.py.bak | 72 ++ .../tests/framework/test_multimedia.py | 4 +- .../tests/framework/test_multimedia.py.bak | 77 ++ .../tests/framework/test_presentation.py | 2 +- .../tests/framework/test_presentation.py.bak | 60 ++ src/freeseer/tests/framework/test_youtube.py | 2 +- .../tests/framework/test_youtube.py.bak | 70 ++ .../frontend/configtool/test_config_tool.py | 18 +- .../configtool/test_config_tool.py.bak | 381 +++++++++ .../tests/frontend/controller/test_server.py | 6 +- .../frontend/controller/test_server.py.bak | 339 ++++++++ src/freeseer/tests/frontend/test_cli_talk.py | 16 +- .../tests/frontend/test_cli_talk.py.bak | 91 ++ .../tests/frontend/upload/test_youtube.py | 2 +- .../tests/frontend/upload/test_youtube.py.bak | 111 +++ 108 files changed, 11209 insertions(+), 232 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/freeseer.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 docs/source/conf.py.bak create mode 100644 src/freeseer/framework/config/core.py.bak create mode 100644 src/freeseer/framework/config/persist/configparser.py.bak create mode 100644 src/freeseer/framework/config/persist/jsonstorage.py.bak create mode 100644 src/freeseer/framework/config/profile.py.bak create mode 100644 src/freeseer/framework/database.py.bak create mode 100644 src/freeseer/framework/qt_key_grabber.py.bak create mode 100644 src/freeseer/framework/util.py.bak create mode 100644 src/freeseer/framework/youtube.py.bak create mode 100644 src/freeseer/frontend/cli.py.bak create mode 100644 src/freeseer/frontend/configtool/AVWidget.py.bak create mode 100644 src/freeseer/frontend/configtool/GeneralWidget.py.bak create mode 100644 src/freeseer/frontend/configtool/PluginWidget.py.bak create mode 100644 src/freeseer/frontend/configtool/configtool.py.bak create mode 100644 src/freeseer/frontend/controller/recording.py.bak create mode 100644 src/freeseer/frontend/qtcommon/AboutDialog.py.bak create mode 100644 src/freeseer/frontend/qtcommon/AboutWidget.py.bak create mode 100644 src/freeseer/frontend/record/RecordingController.py.bak create mode 100644 src/freeseer/frontend/record/record.py.bak create mode 100644 src/freeseer/frontend/reporteditor/reporteditor.py.bak create mode 100644 src/freeseer/frontend/talkeditor/talkeditor.py.bak create mode 100644 src/freeseer/frontend/upload/youtube.py.bak create mode 100644 src/freeseer/plugins/audioinput/jackaudiosrc/__init__.py.bak create mode 100644 src/freeseer/plugins/audioinput/pulsesrc/__init__.py.bak create mode 100644 src/freeseer/plugins/audiomixer/audiopassthrough/__init__.py.bak create mode 100644 src/freeseer/plugins/audiomixer/multiaudio/__init__.py.bak create mode 100644 src/freeseer/plugins/importer/csv_importer.py.bak create mode 100644 src/freeseer/plugins/importer/rss_feedparser/__init__.py.bak create mode 100644 src/freeseer/plugins/output/audiofeedback/__init__.py.bak create mode 100644 src/freeseer/plugins/output/ogg_icecast/__init__.py.bak create mode 100644 src/freeseer/plugins/output/ogg_output/__init__.py.bak create mode 100644 src/freeseer/plugins/output/rtmp_streaming/__init__.py.bak create mode 100644 src/freeseer/plugins/output/videopreview/__init__.py.bak create mode 100644 src/freeseer/plugins/output/webm_output/__init__.py.bak create mode 100644 src/freeseer/plugins/videoinput/desktop/__init__.py.bak create mode 100644 src/freeseer/plugins/videoinput/firewiresrc/__init__.py.bak create mode 100644 src/freeseer/plugins/videoinput/usbsrc/__init__.py.bak create mode 100644 src/freeseer/plugins/videoinput/videotestsrc/__init__.py.bak create mode 100644 src/freeseer/plugins/videomixer/pip/__init__.py.bak create mode 100644 src/freeseer/plugins/videomixer/videopassthrough/__init__.py.bak create mode 100644 src/freeseer/tests/framework/config/options/test_choice.py.bak create mode 100644 src/freeseer/tests/framework/config/options/test_float.py.bak create mode 100644 src/freeseer/tests/framework/config/options/test_folder.py.bak create mode 100644 src/freeseer/tests/framework/config/options/test_integer.py.bak create mode 100644 src/freeseer/tests/framework/test_multimedia.py.bak create mode 100644 src/freeseer/tests/framework/test_presentation.py.bak create mode 100644 src/freeseer/tests/framework/test_youtube.py.bak create mode 100644 src/freeseer/tests/frontend/configtool/test_config_tool.py.bak create mode 100644 src/freeseer/tests/frontend/controller/test_server.py.bak create mode 100644 src/freeseer/tests/frontend/test_cli_talk.py.bak create mode 100644 src/freeseer/tests/frontend/upload/test_youtube.py.bak diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..0e40fe8f --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ + +# Default ignored files +/workspace.xml \ No newline at end of file diff --git a/.idea/freeseer.iml b/.idea/freeseer.iml new file mode 100644 index 00000000..67116063 --- /dev/null +++ b/.idea/freeseer.iml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..105ce2da --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..a2e120dc --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..6a827896 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 1b01c570..e3f251ab 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -43,8 +43,8 @@ master_doc = 'index' # General information about the project. -project = u'Freeseer' -copyright = u'© 2011-2014 Free and Open Source Software Learning Centre' +project = 'Freeseer' +copyright = '© 2011-2014 Free and Open Source Software Learning Centre' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -182,8 +182,8 @@ latex_documents = [( 'index', # source start file 'Freeseer.tex', # target name - u'Freeseer Documentation', # title - u'Free and Open Source Software Learning Centre', # author + 'Freeseer Documentation', # title + 'Free and Open Source Software Learning Centre', # author 'manual' # documentclass [howto/manual] )] @@ -216,6 +216,6 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'freeseer', u'Freeseer Documentation', - [u'Free and Open Source Software Learning Centre'], 1) + ('index', 'freeseer', 'Freeseer Documentation', + ['Free and Open Source Software Learning Centre'], 1) ] diff --git a/docs/source/conf.py.bak b/docs/source/conf.py.bak new file mode 100644 index 00000000..1b01c570 --- /dev/null +++ b/docs/source/conf.py.bak @@ -0,0 +1,221 @@ +# -*- coding: utf-8 -*- +# +# Freeseer documentation build configuration file, created by +# sphinx-quickstart on Sun Sep 4 18:21:52 2011. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import os +import sys + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. +sys.path.append('../src') # Temporarily add freeseer/src to $PATH. + +# -- General configuration ---------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', + 'sphinx.ext.viewcode', 'sphinx.ext.todo'] + +# If True, the todo and todolist directives will produce output. +# http://sphinx.pocoo.org/ext/todo.html#module-sphinx.ext.todo +todo_include_todos = True + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Freeseer' +copyright = u'© 2011-2014 Free and Open Source Software Learning Centre' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '3.0' +# The full version, including alpha/beta/rc tags. +release = '3.0.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['contribute/packaging.rst'] + +# The reST default role (for text `like this`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +show_authors = True + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output -------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +html_theme_path = ['_themes', ] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = 'freeseer_logo.png' + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +html_favicon = 'favicon.ico' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +html_show_copyright = False + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Freeseerdoc' + + +# -- Options for LaTeX output ------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. Takes a list of tuples. +latex_documents = [( + 'index', # source start file + 'Freeseer.tex', # target name + u'Freeseer Documentation', # title + u'Free and Open Source Software Learning Centre', # author + 'manual' # documentclass [howto/manual] +)] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output ------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'freeseer', u'Freeseer Documentation', + [u'Free and Open Source Software Learning Centre'], 1) +] diff --git a/src/freeseer/framework/config/core.py b/src/freeseer/framework/config/core.py index 8ccdafff..83e5efd5 100644 --- a/src/freeseer/framework/config/core.py +++ b/src/freeseer/framework/config/core.py @@ -31,11 +31,9 @@ from freeseer.framework.config.exceptions import StorageNotSetError -class Option(object): +class Option(object, metaclass=abc.ABCMeta): """Represents a Config option.""" - __metaclass__ = abc.ABCMeta - class NotSpecified(object): pass @@ -98,7 +96,7 @@ def __new__(meta, name, bases, class_attributes): class_attributes, options = meta.find_options(class_attributes) class_attributes['options'] = options cls = super(ConfigBase, meta).__new__(meta, name, bases, class_attributes) - for opt_name, option in options.iteritems(): + for opt_name, option in options.items(): opt_get = functools.partial(cls.get_value, name=opt_name, option=option, presentation=True) opt_set = functools.partial(cls._set_value, name=opt_name, option=option) setattr(cls, opt_name, property(opt_get, opt_set)) @@ -118,7 +116,7 @@ def find_options(class_attributes): return new_attributes, options -class Config(object): +class Config(object, metaclass=ConfigBase): """Base class for all custom configs. To be useful, its body must contain some number of Option instances. @@ -128,8 +126,6 @@ class MyConfig(Config): test = StringOption('default_value') """ - __metaclass__ = ConfigBase - def __init__(self, storage=None, storage_args=None): """ Params: @@ -149,7 +145,7 @@ def _set_value(self, value, name, option): def set_defaults(self): """Sets the values of all options to their default value (if applicable).""" - for name, option in self.options.iteritems(): + for name, option in self.options.items(): if not option.is_required(): self.set_value(name, option, option.default) @@ -210,7 +206,7 @@ def schema(cls): 'properties': {}, } - for name, instance in cls.options.iteritems(): + for name, instance in cls.options.items(): schema['properties'][name] = instance.schema() if instance.is_required(): required.append(name) @@ -221,11 +217,9 @@ def schema(cls): return schema -class ConfigStorage(object): +class ConfigStorage(object, metaclass=abc.ABCMeta): """Defines an interface for loading and storing Config instances.""" - __metaclass__ = abc.ABCMeta - def __init__(self, filepath): """ Params: diff --git a/src/freeseer/framework/config/core.py.bak b/src/freeseer/framework/config/core.py.bak new file mode 100644 index 00000000..8ccdafff --- /dev/null +++ b/src/freeseer/framework/config/core.py.bak @@ -0,0 +1,252 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013, 2014 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +import abc +import collections +import functools + +from freeseer.framework.config.exceptions import InvalidOptionValueError +from freeseer.framework.config.exceptions import OptionValueNotSetError +from freeseer.framework.config.exceptions import StorageNotSetError + + +class Option(object): + """Represents a Config option.""" + + __metaclass__ = abc.ABCMeta + + class NotSpecified(object): + pass + + def __init__(self, default=NotSpecified): + self.default = default + + def is_required(self): + """Returns true iff this option is required.""" + return self.default == self.NotSpecified + + # Override these if you know what you're doing + + def pre_set(self, value): + """Do something before value is stored for this option.""" + return value + + def presentation(self, value): + """Returns a modified version of value that will not itself be persisted.""" + return value + + def schema(self): + """Returns the json schema for an Option.""" + schema = {'type': self.SCHEMA_TYPE} + if self.default != self.NotSpecified: + schema['default'] = self.default + return schema + + # Override these! + + @abc.abstractmethod + def is_valid(self, value): + """Checks if a value is valid for this option.""" + pass + + @abc.abstractmethod + def encode(self, value): + """Encodes value into a string. + + Should raise something if unable to encode. + """ + pass + + @abc.abstractmethod + def decode(self, value): + """Decodes value into a proper Option value. + + Should raise something if unable to decode. + """ + pass + + +class ConfigBase(abc.ABCMeta): + """Metaclass for Config subclasses. + + It does some transformations on the options you specify to let them be used as properties. + """ + + def __new__(meta, name, bases, class_attributes): + """Finds all Options delcared in the subclass and transform them into properties.""" + class_attributes, options = meta.find_options(class_attributes) + class_attributes['options'] = options + cls = super(ConfigBase, meta).__new__(meta, name, bases, class_attributes) + for opt_name, option in options.iteritems(): + opt_get = functools.partial(cls.get_value, name=opt_name, option=option, presentation=True) + opt_set = functools.partial(cls._set_value, name=opt_name, option=option) + setattr(cls, opt_name, property(opt_get, opt_set)) + return cls + + @staticmethod + def find_options(class_attributes): + """Find all Option subclasses within the class body.""" + new_attributes = {} + options = collections.OrderedDict() + for name in sorted(class_attributes.keys()): + attr = class_attributes[name] + if name.startswith('_') or not isinstance(attr, Option): + new_attributes[name] = attr + else: + options[name] = attr + return new_attributes, options + + +class Config(object): + """Base class for all custom configs. + + To be useful, its body must contain some number of Option instances. + + Example: + class MyConfig(Config): + test = StringOption('default_value') + """ + + __metaclass__ = ConfigBase + + def __init__(self, storage=None, storage_args=None): + """ + Params: + storage - an instance of a ConfigStorage + storage_args - an iterable of arguments that will be passed to + storage.load(...) + """ + self._storage = storage + self._storage_args = storage_args if storage_args else [] + + self.values = {} + self.set_defaults() + + def _set_value(self, value, name, option): + """This is just here to make the argument order more logical.""" + self.set_value(name, option, value) + + def set_defaults(self): + """Sets the values of all options to their default value (if applicable).""" + for name, option in self.options.iteritems(): + if not option.is_required(): + self.set_value(name, option, option.default) + + # You probably will not need to override these: + + def get_value(self, name, option, presentation=False): + """Gets the value of an option. + + Params: + name - the string name of the option instance + option - the option instance itself + presentation - boolean + + Returns: the value that the config has stored for this option + """ + if name in self.values: + value = self.values[name] + if presentation: + return option.presentation(value) + else: + return value + else: + raise OptionValueNotSetError(name, option) + + def set_value(self, name, option, value): + """Sets the value of an option. + + Params: + name - the string name of the option instance + option - the option instance itself + value - the value that will be set for the option + + Returns: nothing + """ + if option.is_valid(value): + mod_value = option.pre_set(value) + self.values[name] = mod_value + else: + raise InvalidOptionValueError(name, option) + + def save(self): + """Persist the Config instance. + + This only works if the storage stuff is passed to the Config's constructor. + """ + if self._storage: + self._storage.store(self, *self._storage_args) + else: + raise StorageNotSetError() + + @classmethod + def schema(cls): + """Returns the json schema for this Config instance.""" + required = [] + + schema = { + 'type': 'object', + 'properties': {}, + } + + for name, instance in cls.options.iteritems(): + schema['properties'][name] = instance.schema() + if instance.is_required(): + required.append(name) + + if required: + schema['required'] = required + + return schema + + +class ConfigStorage(object): + """Defines an interface for loading and storing Config instances.""" + + __metaclass__ = abc.ABCMeta + + def __init__(self, filepath): + """ + Params: + filepath - the path to file where the config will be persisted or loaded from + """ + self._filepath = filepath + + # Override these! + + @abc.abstractmethod + def load(self, config_instance): + """Populates the Config instance from somewhere. + + It should iterate over all options in self.options and determine the value to store by using option.decode(..). + """ + pass + + @abc.abstractmethod + def store(self, config_instance): + """Persists the Config to somewhere. + + It should iterate over all options in self.options and determine the value to persis by using option.encode(..). + """ + pass diff --git a/src/freeseer/framework/config/persist/configparser.py b/src/freeseer/framework/config/persist/configparser.py index 2c47b015..a06144cb 100644 --- a/src/freeseer/framework/config/persist/configparser.py +++ b/src/freeseer/framework/config/persist/configparser.py @@ -22,7 +22,7 @@ # For support, questions, suggestions or any other inquiries, visit: # http://wiki.github.com/Freeseer/freeseer/ -import ConfigParser +from . import ConfigParser from freeseer.framework.config.core import ConfigStorage @@ -34,7 +34,7 @@ def load(self, config_instance, section): parser = ConfigParser.ConfigParser() parser.read([self._filepath]) - for name, option in config_instance.options.iteritems(): + for name, option in config_instance.options.items(): if parser.has_option(section, name): raw = parser.get(section, name) clean = option.decode(raw) @@ -49,7 +49,7 @@ def store(self, config_instance, section): if not parser.has_section(section): parser.add_section(section) - for name, option in config_instance.options.iteritems(): + for name, option in config_instance.options.items(): raw = config_instance.get_value(name, option) clean = option.encode(raw) parser.set(section, name, clean) diff --git a/src/freeseer/framework/config/persist/configparser.py.bak b/src/freeseer/framework/config/persist/configparser.py.bak new file mode 100644 index 00000000..2c47b015 --- /dev/null +++ b/src/freeseer/framework/config/persist/configparser.py.bak @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +import ConfigParser + +from freeseer.framework.config.core import ConfigStorage + + +class ConfigParserStorage(ConfigStorage): + """Persists Configs to and from INI style config files.""" + + def load(self, config_instance, section): + parser = ConfigParser.ConfigParser() + parser.read([self._filepath]) + + for name, option in config_instance.options.iteritems(): + if parser.has_option(section, name): + raw = parser.get(section, name) + clean = option.decode(raw) + config_instance.set_value(name, option, clean) + + return config_instance + + def store(self, config_instance, section): + parser = ConfigParser.ConfigParser() + parser.read([self._filepath]) + + if not parser.has_section(section): + parser.add_section(section) + + for name, option in config_instance.options.iteritems(): + raw = config_instance.get_value(name, option) + clean = option.encode(raw) + parser.set(section, name, clean) + + with open(self._filepath, 'w') as config_fd: + parser.write(config_fd) diff --git a/src/freeseer/framework/config/persist/jsonstorage.py b/src/freeseer/framework/config/persist/jsonstorage.py index 32d88399..636c45ff 100644 --- a/src/freeseer/framework/config/persist/jsonstorage.py +++ b/src/freeseer/framework/config/persist/jsonstorage.py @@ -49,7 +49,7 @@ def load(self, config_instance, section): if section not in dict_: return config_instance - for name, option in config_instance.options.iteritems(): + for name, option in config_instance.options.items(): if name in dict_[section]: raw = dict_[section][name] clean = option.decode(raw) @@ -61,7 +61,7 @@ def store(self, config_instance, section): if section not in dict_: dict_[section] = {} - for name, option in config_instance.options.iteritems(): + for name, option in config_instance.options.items(): raw = config_instance.get_value(name, option) clean = option.encode(raw) dict_[section][name] = clean diff --git a/src/freeseer/framework/config/persist/jsonstorage.py.bak b/src/freeseer/framework/config/persist/jsonstorage.py.bak new file mode 100644 index 00000000..32d88399 --- /dev/null +++ b/src/freeseer/framework/config/persist/jsonstorage.py.bak @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +import json +import os + +from freeseer.framework.config.core import ConfigStorage + + +class JSONConfigStorage(ConfigStorage): + """Persists Configs to and from JSON formatted config files.""" + + def parse_json(self): + if os.path.isfile(self._filepath): + return json.load(open(self._filepath)) + else: + return {} + + def write_json(self, dict_): + with open(self._filepath, 'wc') as config_fd: + config_fd.write(json.dumps(dict_, + sort_keys=True, + indent=4, + separators=(',', ': '))) + + def load(self, config_instance, section): + dict_ = self.parse_json() + if section not in dict_: + return config_instance + + for name, option in config_instance.options.iteritems(): + if name in dict_[section]: + raw = dict_[section][name] + clean = option.decode(raw) + config_instance.set_value(name, option, clean) + return config_instance + + def store(self, config_instance, section): + dict_ = self.parse_json() + if section not in dict_: + dict_[section] = {} + + for name, option in config_instance.options.iteritems(): + raw = config_instance.get_value(name, option) + clean = option.encode(raw) + dict_[section][name] = clean + + self.write_json(dict_) diff --git a/src/freeseer/framework/config/profile.py b/src/freeseer/framework/config/profile.py index 5f417ee6..6fbc0a6e 100644 --- a/src/freeseer/framework/config/profile.py +++ b/src/freeseer/framework/config/profile.py @@ -153,7 +153,7 @@ def get_storage(self, name): It will also be cached for future invocations of this method. """ if name not in self._storages: - for suffix, engine in self.STORAGE_MAP.iteritems(): + for suffix, engine in self.STORAGE_MAP.items(): if name.endswith(suffix): self._storages[name] = engine(self.get_filepath(name)) break diff --git a/src/freeseer/framework/config/profile.py.bak b/src/freeseer/framework/config/profile.py.bak new file mode 100644 index 00000000..5f417ee6 --- /dev/null +++ b/src/freeseer/framework/config/profile.py.bak @@ -0,0 +1,206 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +import os +import shutil + +from freeseer.framework.config.persist import ConfigParserStorage +from freeseer.framework.config.persist import JSONConfigStorage +from freeseer.framework.database import QtDBConnector +from freeseer.framework.plugin import PluginManager + + +class ProfileManager(object): + """Manages Profile instances for the current system user.""" + + def __init__(self, base_folder): + self._base_folder = base_folder + self._cache = {} + self._create_if_needed(base_folder) + + def _create_if_needed(self, path): + try: + os.makedirs(path) + except OSError: + # This is thrown if path already exists. + pass + + def get(self, name='default', create_if_needed=True): + """ + Retrieve Profile instances by name. Profiles are cached for future gets. + + Args: + name: The name of the profile. + create_if_needed: When True, get creates a new profile instance + if profile for given name doesn't exist. + + Returns: + The instance of Profile for the given name. + + Raises: + ProfileDoesNotExist: If create_if_needed==False and + the given profile name doesn't exist. + """ + if name in self._cache: + return self._cache[name] + else: + full_path = os.path.join(self._base_folder, name) + if os.path.exists(full_path): + self._cache[name] = Profile(full_path, name) + return self._cache[name] + elif create_if_needed: + return self.create(name) + + raise ProfileDoesNotExist(name) + + def create(self, name): + """ + Creates a new Profile on file and adds it to the cache. + + Args: + name: The name of the profile to create. + + Returns: + The instance for the created Profile. + + Raises: + ProfileAlreadyExists: If a profile by the same name exists. + """ + path = os.path.join(self._base_folder, name) + try: + os.makedirs(path) + except OSError: + raise ProfileAlreadyExists(name) + + self._cache[name] = Profile(path, name) + return self._cache[name] + + def list_profiles(self): + """Returns a list of available profiles on file.""" + return os.listdir(self._base_folder) + + def delete(self, name): + """ + Deletes a profile and its configuration files from disk and cache. + + Args: + name: The name of the profile to delete. + + Raises: + ProfileDoesNotExist: If no profile exists for give name. + """ + path = os.path.join(self._base_folder, name) + try: + shutil.rmtree(path) + del self._cache[name] + except OSError: + raise ProfileDoesNotExist(name) + except KeyError: + pass + + +class Profile(object): + """Represents a profile's config files, databases, and other stuff.""" + + STORAGE_MAP = { + '.conf': ConfigParserStorage, + '.json': JSONConfigStorage, + } + + def __init__(self, folder, name): + self._folder = folder + self._name = name + self._storages = {} + self._databases = {} + + @property + def name(self): + return self._name + + def get_filepath(self, name): + """Returns the absolute path for a file called name. + + The filepath will be prefixed with the profile's base folder. + """ + return os.path.join(self._folder, name) + + def get_storage(self, name): + """ + Returns a ConfigStorage instance for a given config file name. + + The ConfigStorage instance is picked based on the file suffix. + It will also be cached for future invocations of this method. + """ + if name not in self._storages: + for suffix, engine in self.STORAGE_MAP.iteritems(): + if name.endswith(suffix): + self._storages[name] = engine(self.get_filepath(name)) + break + + if name in self._storages: + return self._storages[name] + else: + raise KeyError('{} does not have a valid suffix'.format(name)) + + def get_config(self, filename, config_class, storage_args=None, read_only=False): + """Returns an instance of config_class that has be loaded from filename. + + Params: + filename - name of the file, this will be passed to get_storage(..) + config_class - Config subclass + storage_args - an iterable of arguments that will be passed to + storage.load(config, ...) + read_only - if True, the storage will be passed to the Config + instance + """ + storage_args = storage_args if storage_args else [] + storage = self.get_storage(filename) + + if read_only: + config = config_class() + else: + config = config_class(storage, storage_args) + + return storage.load(config, *storage_args) + + def get_database(self, name='presentations.db'): + """Returns an instance of QtDBConnector for a specific database file. + + It is also cached for future gets. + """ + if name not in self._databases: + self._databases[name] = QtDBConnector(self.get_filepath(name), PluginManager(self)) + return self._databases[name] + + +class ProfileAlreadyExists(Exception): + def __init__(self, value): + message = 'Profile already exists: "{}"'.format(value) + super(Exception, self).__init__(message) + + +class ProfileDoesNotExist(Exception): + def __init__(self, value): + message = 'Profile does not exist: "{}"'.format(value) + super(Exception, self).__init__(message) diff --git a/src/freeseer/framework/database.py b/src/freeseer/framework/database.py index c4c9b3f3..1efeb4a4 100644 --- a/src/freeseer/framework/database.py +++ b/src/freeseer/framework/database.py @@ -239,16 +239,16 @@ def get_talks_by_room_and_time(self, room): def get_presentation(self, talk_id): """Returns a Presentation object associated to a talk_id""" result = QtSql.QSqlQuery('''SELECT * FROM presentations WHERE Id="%s"''' % talk_id) - if result.next(): - return Presentation(title=unicode(result.value(1).toString()), - speaker=unicode(result.value(2).toString()), - description=unicode(result.value(3).toString()), - category=unicode(result.value(4).toString()), - event=unicode(result.value(5).toString()), - room=unicode(result.value(6).toString()), - date=unicode(result.value(7).toString()), - startTime=unicode(result.value(8).toString()), - endTime=unicode(result.value(9).toString())) + if next(result): + return Presentation(title=str(result.value(1).toString()), + speaker=str(result.value(2).toString()), + description=str(result.value(3).toString()), + category=str(result.value(4).toString()), + event=str(result.value(5).toString()), + room=str(result.value(6).toString()), + date=str(result.value(7).toString()), + startTime=str(result.value(8).toString()), + endTime=str(result.value(9).toString())) else: return None @@ -256,16 +256,16 @@ def get_string_list(self, column): """Returns a column as a QStringList""" tempList = QStringList() result = QtSql.QSqlQuery('''SELECT DISTINCT %s FROM presentations''' % column) - while result.next(): + while next(result): tempList.append(result.value(0).toString()) return tempList def presentation_exists(self, presentation): """Checks if there's a presentation with the same Speaker and Title already stored""" result = QtSql.QSqlQuery('''SELECT * FROM presentations''') - while result.next(): - if (unicode(presentation.title) == unicode(result.value(1).toString()) - and unicode(presentation.speaker) == unicode(result.value(2).toString())): + while next(result): + if (str(presentation.title) == str(result.value(1).toString()) + and str(presentation.speaker) == str(result.value(2).toString())): return True return False @@ -374,7 +374,7 @@ def get_talk_between_time(self, event, room, startTime, endTime): WHERE Event='%s' AND Room='%s' \ AND Date BETWEEN '%s' \ AND '%s' ORDER BY Date ASC" % (event, room, startTime, endTime)) - query.next() + next(query) if query.isValid(): return query.value(0) else: @@ -458,17 +458,17 @@ def export_talks_to_csv(self, fname): writer.writerow(headers) result = self.get_talks() - while result.next(): - log.debug(unicode(result.value(1).toString())) - writer.writerow({'Title': unicode(result.value(1).toString()), - 'Speaker': unicode(result.value(2).toString()), - 'Abstract': unicode(result.value(3).toString()), - 'Category': unicode(result.value(4).toString()), - 'Event': unicode(result.value(5).toString()), - 'Room': unicode(result.value(6).toString()), - 'Date': unicode(result.value(7).toString()), - 'StartTime': unicode(result.value(8).toString()), - 'EndTime': unicode(result.value(9).toString())}) + while next(result): + log.debug(str(result.value(1).toString())) + writer.writerow({'Title': str(result.value(1).toString()), + 'Speaker': str(result.value(2).toString()), + 'Abstract': str(result.value(3).toString()), + 'Category': str(result.value(4).toString()), + 'Event': str(result.value(5).toString()), + 'Room': str(result.value(6).toString()), + 'Date': str(result.value(7).toString()), + 'StartTime': str(result.value(8).toString()), + 'EndTime': str(result.value(9).toString())}) finally: file.close() @@ -520,10 +520,10 @@ def clear_report_db(self): def get_report(self, talkid): """Returns a failure from a given talkid. Returned value is a Failure object""" result = QtSql.QSqlQuery('''SELECT * FROM failures WHERE Id = "%s"''' % talkid) - if result.next(): - failure = Failure(unicode(result.value(0).toString()), # id - unicode(result.value(1).toString()), # comment - unicode(result.value(2).toString()), # indicator + if next(result): + failure = Failure(str(result.value(0).toString()), # id + str(result.value(1).toString()), # comment + str(result.value(2).toString()), # indicator result.value(3).toBool()) # release else: failure = None @@ -533,10 +533,10 @@ def get_reports(self): """Returns a list of failures in Report format""" result = QtSql.QSqlQuery('''Select * FROM failures''') list = [] - while result.next(): - failure = Failure(unicode(result.value(0).toString()), # id - unicode(result.value(1).toString()), # comment - unicode(result.value(2).toString()), # indicator + while next(result): + failure = Failure(str(result.value(0).toString()), # id + str(result.value(1).toString()), # comment + str(result.value(2).toString()), # indicator bool(result.value(3))) # release p = self.get_presentation(failure.talkId) r = Report(p, failure) diff --git a/src/freeseer/framework/database.py.bak b/src/freeseer/framework/database.py.bak new file mode 100644 index 00000000..25b599a4 --- /dev/null +++ b/src/freeseer/framework/database.py.bak @@ -0,0 +1,605 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +import csv +import logging + +from PyQt4 import QtSql +from PyQt4.QtCore import QDate +from PyQt4.QtCore import QTime +from PyQt4.QtCore import QStringList + +from freeseer import SCHEMA_VERSION +from freeseer.framework.presentation import Presentation +from freeseer.framework.failure import Failure, Report + +log = logging.getLogger(__name__) + + +# Database Schema Versions +PRESENTATIONS_SCHEMA_300 = '''CREATE TABLE IF NOT EXISTS presentations + (Id INTEGER PRIMARY KEY, + Title varchar(255), + Speaker varchar(100), + Description text, + Level varchar(25), + Event varchar(100), + Room varchar(25), + Time timestamp, + UNIQUE (Speaker, Title) ON CONFLICT IGNORE)''' + +# The SQLITE timestamp field corresponds to the DateTime object. The Date column in the database can be created from +# a DateTime.date() call. +PRESENTATIONS_SCHEMA_310 = '''CREATE TABLE IF NOT EXISTS presentations + (Id INTEGER PRIMARY KEY, + Title varchar(255), + Speaker varchar(100), + Description text, + Category varchar(25), + Event varchar(100), + Room varchar(25), + Date timestamp, + StartTime timestamp, + EndTime timestamp, + UNIQUE (Speaker, Title) ON CONFLICT IGNORE)''' + +# TODO: Make PRESENTATIONS_SCHEMA_315 the default schema when the presentation table is created +# TODO: Make an upgrade method from PRESENTATIONS_SCHEMA_310 to PRESENTATIONS_SCHEMA_315 +# TODO: Update the SCHEMA_VERSION to 315 +# TODO: Integrate new database scheme with importers/exporter functions +# TODO: Force Presentation.Date to be a QDate +# TODO: Figure out what the types should be for Presentation.StartTime/EndTime e.g. QTime +# TODO: Enforce the default database values in the CSV/RSS importer +# TODO: Enforce the default database values in Presentation.__init__ +# TODO: Enforce the default database values in db.insert_presentation() +# TODO: Check what format the csv and rss sample files are in. For instance, do the rss files use 'None' instead of '' +# for missing fields? +# TODO: Update _helper_presentation_exists() and get_presentation_id() to use the new schema such that they no longer +# check if the stored data values are NULL +PRESENTATIONS_SCHEMA_315 = '''CREATE TABLE IF NOT EXISTS presentations + (Id INTEGER PRIMARY KEY, + Title varchar(255) NOT NULL DEFAULT '', + Speaker varchar(100) NOT NULL DEFAULT '', + Description text NOT NULL DEFAULT '', + Category varchar(25) NOT NULL DEFAULT '', + Event varchar(100) NOT NULL DEFAULT '', + Room varchar(25) NOT NULL DEFAULT '', + Date timestamp NOT NULL DEFAULT '', + StartTime timestamp NOT NULL DEFAULT '', + EndTime timestamp NOT NULL DEFAULT '', + UNIQUE (Speaker, Title, Event) ON CONFLICT IGNORE)''' + +REPORTS_SCHEMA_300 = '''CREATE TABLE IF NOT EXISTS failures + (Id INTERGER PRIMARY KEY, + Comments TEXT, + Indicator TEXT, + Release INTEGER, + UNIQUE (ID) ON CONFLICT REPLACE)''' + +# TODO: Ensure that the CSV/RSS to presentation importers are done using the same conventions. For instance, convert a +# room with value None to '' in both the csv and rss importer. Instead of the csv parser converting None to 'None' +# and the rss importer doing something else etcetera. + + +class QtDBConnector(object): + def __init__(self, db_filepath, plugman): + """ + Initialize the QtDBConnector + """ + self.talkdb_file = db_filepath + self.plugman = plugman + + self.presentationsModel = None + self.failuresModel = None + self.recentconnModel = None + self.__open_table() + + def __open_table(self): + """Opens a connection to the database. Uses by the init function.""" + self.talkdb = QtSql.QSqlDatabase.addDatabase("QSQLITE") + self.talkdb.setDatabaseName(self.talkdb_file) + + if self.talkdb.open(): + + # check if presentations table exists and if not create it. + if not self.talkdb.tables().contains("presentations"): + self.__create_presentations_table() + self.__insert_default_talk() + + # If presentations table did not exist, it is safe to say that the reports table needs to be reset + # or initialized. + self.clear_report_db() + self.__create_failures_table() + + # Set the database version (so the updater does not update) + QtSql.QSqlQuery('PRAGMA user_version = %i' % SCHEMA_VERSION) + + # check if recentConnections table exists and if not create it. + if not self.talkdb.tables().contains("recentconn"): + self.__create_recentconn_table() + + # verify that correct version of database exists + self.__update_version() + else: + log.error("Unable to create talkdb file.") + + def __close_table(self): + """Closes the connection the the database.""" + self.talkdb.close() + + def __get_db_version_int(self): + """Gets the database's current version. Default is 0 if unset (for 2x and older)""" + query = QtSql.QSqlQuery('PRAGMA user_version') + query.first() + return query.value(0).toInt()[0] + + def __update_version(self): + """Upgrade database to the latest SCHEMA_VERSION""" + + db_version = self.__get_db_version_int() + if db_version == SCHEMA_VERSION: + return + + # + # Define functions for upgrading between schema versions + # + def update_2xto30(): + """Incremental update of database from Freeseer 2.x and older to 3.0 + + SCHEMA_VERSION is 300 + """ + if db_version > 300: + log.debug('Database newer than schema version 300.') + return # No update needed + + log.debug('Updating to schema 300.') + QtSql.QSqlQuery('ALTER TABLE presentations RENAME TO presentations_old') # temporary table + self.__create_presentations_table(PRESENTATIONS_SCHEMA_300) + QtSql.QSqlQuery("""INSERT INTO presentations + SELECT Id, Title, Speaker, Description, Level, Event, Room, Time FROM presentations_old""") + QtSql.QSqlQuery('DROP TABLE presentations_old') + + def update_30to31(): + """Performs incremental update of database from 3.0 and older to 3.1.""" + QtSql.QSqlQuery('ALTER TABLE presentations RENAME TO presentations_old') + self.__create_presentations_table(PRESENTATIONS_SCHEMA_310) + QtSql.QSqlQuery("""INSERT INTO presentations + SELECT Id, Title, Speaker, Description, Level, Event, Room, Time, Time, Time + FROM presentations_old""") + QtSql.QSqlQuery('DROP TABLE presentations_old') + + # + # Perform the upgrade + # + updaters = [update_2xto30, update_30to31] + for updater in updaters: + updater() + + QtSql.QSqlQuery('PRAGMA user_version = %i' % SCHEMA_VERSION) + log.info('Upgraded presentations database from version {} to {}'.format(db_version, SCHEMA_VERSION)) + + def __create_presentations_table(self, schema=PRESENTATIONS_SCHEMA_310): + """Creates the presentations table in the database. Should be used to initialize a new table.""" + log.info("table created") + QtSql.QSqlQuery(schema) + + def __insert_default_talk(self): + """Inserts the required placeholder talk into the database.At least one talk must exist""" + self.insert_presentation(Presentation("", "", "", "", "", "", "", "", "")) + + def get_talks(self): + """Gets all the talks from the database including all columns""" + return QtSql.QSqlQuery('''SELECT * FROM presentations''') + + def get_events(self): + """Gets all the talk events from the database""" + return QtSql.QSqlQuery('''SELECT DISTINCT Event FROM presentations''') + + def get_talk_ids(self): + """Gets all the talk events from the database""" + return QtSql.QSqlQuery('''SELECT Id FROM presentations''') + + def get_talks_by_event(self, event): + """Gets the talks signed in a specific event from the database""" + return QtSql.QSqlQuery('''SELECT * FROM presentations WHERE Event=%s''' % event) + + def get_talks_by_room(self, room): + """Gets the talks hosted in a specific room from the database""" + return QtSql.QSqlQuery('''SELECT * FROM presentations WHERE Room=%s''' % room) + + def get_talks_by_room_and_time(self, room): + """Returns the talks hosted in a specified room, starting from the current date and time""" + current_date = QDate.currentDate().toString(1) # yyyy-mm-dd + current_time = QTime.currentTime().toString() # hh:mm:ss + return QtSql.QSqlQuery('''SELECT * FROM presentations + WHERE Room='{}' AND Date='{}' + AND StartTime >= '{}' ORDER BY StartTime ASC'''.format(room, current_date, current_time)) + + def get_presentation(self, talk_id): + """Returns a Presentation object associated to a talk_id""" + result = QtSql.QSqlQuery('''SELECT * FROM presentations WHERE Id="%s"''' % talk_id) + if result.next(): + return Presentation(title=unicode(result.value(1).toString()), + speaker=unicode(result.value(2).toString()), + description=unicode(result.value(3).toString()), + category=unicode(result.value(4).toString()), + event=unicode(result.value(5).toString()), + room=unicode(result.value(6).toString()), + date=unicode(result.value(7).toString()), + startTime=unicode(result.value(8).toString()), + endTime=unicode(result.value(9).toString())) + else: + return None + + def get_string_list(self, column): + """Returns a column as a QStringList""" + tempList = QStringList() + result = QtSql.QSqlQuery('''SELECT DISTINCT %s FROM presentations''' % column) + while result.next(): + tempList.append(result.value(0).toString()) + return tempList + + def presentation_exists(self, presentation): + """Checks if there's a presentation with the same Speaker and Title already stored""" + result = QtSql.QSqlQuery('''SELECT * FROM presentations''') + while result.next(): + if (unicode(presentation.title) == unicode(result.value(1).toString()) + and unicode(presentation.speaker) == unicode(result.value(2).toString())): + return True + return False + + # + # Presentation Create, Update, Delete + # + def insert_presentation(self, presentation): + """Inserts a passed Presentation into the database.""" + # Duplicate time to date field for older RSS / CSV formats + # If date is empty, and time has a full DateTime, split the DateTime to + # both Date and Time + + if not presentation.date and presentation.startTime and len(presentation.startTime) == 16: + presentation.date, presentation.startTime = presentation.startTime[:-6], presentation.startTime[11:] + + QtSql.QSqlQuery( + '''INSERT INTO presentations VALUES (NULL, "%s", "%s", "%s", "%s", "%s", "%s", "%s", "%s", "%s")''' % + (presentation.title, + presentation.speaker, + presentation.description, + presentation.category, + presentation.event, + presentation.room, + presentation.date, + presentation.startTime, + presentation.endTime)) + log.info("Talk added: %s - %s, Time: %s - %s" % (presentation.speaker, presentation.title, presentation.startTime, presentation.endTime)) + + def update_presentation(self, talk_id, presentation): + """Updates an existing Presentation in the database.""" + QtSql.QSqlQuery( + '''UPDATE presentations SET Title="%s", Speaker="%s", Description="%s", Category="%s", + Event="%s", Room="%s", Date="%s", StartTime="%s", EndTime="%s" + WHERE Id="%s"''' % + (presentation.title, + presentation.speaker, + presentation.description, + presentation.category, + presentation.event, + presentation.room, + presentation.date, + presentation.startTime, + presentation.endTime, + talk_id)) + log.info("Talk %s updated: %s - %s" % (talk_id, presentation.speaker, presentation.title)) + + def delete_presentation(self, talk_id): + """Removes a Presentation from the database""" + QtSql.QSqlQuery('''DELETE FROM presentations WHERE Id="%s"''' % talk_id) + log.info("Talk %s deleted." % talk_id) + + def clear_database(self): + """Clears the presentations table""" + QtSql.QSqlQuery('''DELETE FROM presentations''') + log.info("Database cleared.") + + # + # Data Model Retrieval + # + def get_presentations_model(self): + """Gets the Presentation Table Model. Useful for Qt GUI based Frontends to load the Model in Table Views""" + if self.presentationsModel is None: + self.presentationsModel = QtSql.QSqlTableModel() + self.presentationsModel.setTable("presentations") + self.presentationsModel.setEditStrategy(QtSql.QSqlTableModel.OnFieldChange) + self.presentationsModel.select() + return self.presentationsModel + + def get_events_model(self): + """Gets the Events Model. Useful for Qt GUI based Frontends to load the Model into Views""" + self.eventsModel = QtSql.QSqlQueryModel() + self.eventsModel.setQuery("SELECT DISTINCT Event FROM presentations ORDER BY Event ASC") + return self.eventsModel + + def get_dates_from_event_room_model(self, event, room): + """Gets the Dates Model. Useful for Qt GUI based Frontends to load the Model into Views.""" + self.datesModel = QtSql.QSqlQueryModel() + self.datesModel.setQuery( + "SELECT DISTINCT date FROM presentations WHERE Event='%s' and Room='%s' ORDER BY Date ASC" + % (event, room)) + return self.datesModel + + def get_rooms_model(self, event): + """Gets the Rooms Model. Useful for Qt GUI based Frontends to load the Model into Views""" + self.roomsModel = QtSql.QSqlQueryModel() + self.roomsModel.setQuery("SELECT DISTINCT Room FROM presentations WHERE Event='%s' ORDER BY Room ASC" % event) + return self.roomsModel + + def get_talks_model(self, event, room, date=None): + """Gets the Talks Model. A talk is defined as " - " + Useful for Qt GUI based Frontends to load the Model into Views""" + self.talksModel = QtSql.QSqlQueryModel() + if date == "": + self.talksModel.setQuery("SELECT (Speaker || ' - ' || Title), Id FROM presentations \ + WHERE Event='%s' and Room='%s' ORDER BY Date ASC" % (event, room)) + else: + self.talksModel.setQuery("SELECT (Speaker || ' - ' || Title), Id FROM presentations \ + WHERE Event='%s' and Room='%s' and date(Date) LIKE '%s' ORDER BY Date ASC" + % (event, room, date)) + return self.talksModel + + def get_talk_between_time(self, event, room, startTime, endTime): + """Returns the talkID of the first talk found between a startTime, and endTime for a specified event/room. + Else return None""" + query = QtSql.QSqlQuery("SELECT Id, Date FROM presentations \ + WHERE Event='%s' AND Room='%s' \ + AND Date BETWEEN '%s' \ + AND '%s' ORDER BY Date ASC" % (event, room, startTime, endTime)) + query.next() + if query.isValid(): + return query.value(0) + else: + return None + + # + # Import / Export Functions + # + # Needs to be updated for category field, separate date and time fields + def add_talks_from_rss(self, feed_url): + """Adds talks from an rss feed.""" + plugin = self.plugman.get_plugin_by_name("Rss FeedParser", "Importer") + feedparser = plugin.plugin_object + presentations = feedparser.get_presentations(feed_url) + + if presentations: + for presentation in presentations: + talk = Presentation(presentation["Title"], + presentation["Speaker"], + presentation["Abstract"], # Description + presentation["Level"], + presentation["Event"], + presentation["Room"], + presentation["Time"], + presentation["Time"]) + self.insert_presentation(talk) + + else: + log.info("RSS: No data found.") + + def add_talks_from_csv(self, fname): + """Adds talks from a csv file. + + Title and speaker must be present. + """ + plugin = self.plugman.get_plugin_by_name("CSV Importer", "Importer") + importer = plugin.plugin_object + presentations = importer.get_presentations(fname) + + if presentations: + for presentation in presentations: + if presentation['Time']: + talk = Presentation(presentation["Title"], + presentation["Speaker"], + presentation["Abstract"], # Description + presentation["Level"], + presentation["Event"], + presentation["Room"], + presentation["Time"], + presentation["Time"]) # Presentation using legacy time field + else: + talk = Presentation(presentation["Title"], + presentation["Speaker"], + presentation["Abstract"], # Description + presentation["Level"], + presentation["Event"], + presentation["Room"], + presentation["Date"], + presentation["StartTime"], + presentation["EndTime"]) + self.insert_presentation(talk) + + else: + log.info("CSV: No data found.") + + def export_talks_to_csv(self, fname): + fieldNames = ('Title', + 'Speaker', + 'Abstract', + 'Category', + 'Event', + 'Room', + 'Date', + 'StartTime', + 'EndTime') + + try: + file = open(fname, 'w') + writer = csv.DictWriter(file, fieldnames=fieldNames) + headers = dict((n, n) for n in fieldNames) + writer.writerow(headers) + + result = self.get_talks() + while result.next(): + log.debug(unicode(result.value(1).toString())) + writer.writerow({'Title': unicode(result.value(1).toString()), + 'Speaker': unicode(result.value(2).toString()), + 'Abstract': unicode(result.value(3).toString()), + 'Category': unicode(result.value(4).toString()), + 'Event': unicode(result.value(5).toString()), + 'Room': unicode(result.value(6).toString()), + 'Date': unicode(result.value(7).toString()), + 'StartTime': unicode(result.value(8).toString()), + 'EndTime': unicode(result.value(9).toString())}) + finally: + file.close() + + def export_reports_to_csv(self, fname): + fieldNames = ('Title', + 'Speaker', + 'Abstract', + 'Category', + 'Event', + 'Room', + 'Date', + 'StartTime', + 'EndTime', + 'Problem', + 'Error') + try: + file = open(fname, 'w') + writer = csv.DictWriter(file, fieldnames=fieldNames) + headers = dict((n, n) for n in fieldNames) + writer.writerow(headers) + + result = self.get_reports() + for report in result: + writer.writerow({'Title': report.presentation.title, + 'Speaker': report.presentation.speaker, + 'Abstract': report.presentation.description, + 'Category': report.presentation.category, + 'Event': report.presentation.event, + 'Room': report.presentation.room, + 'Date': report.presentation.date, + 'StartTime': report.presentation.startTime, + 'EndTime': report.presentation.endTime, + 'Problem': report.failure.indicator, + 'Error': report.failure.comment}) + finally: + file.close() + + # + # Reporting Feature + # + def __create_failures_table(self, schema=REPORTS_SCHEMA_300): + """Creates the failures table in the database. Should be used to initialize a new table""" + QtSql.QSqlQuery(schema) + + def clear_report_db(self): + """Drops the failures (reports) table from the database""" + QtSql.QSqlQuery('''DROP TABLE IF EXISTS failures''') + + def get_report(self, talkid): + """Returns a failure from a given talkid. Returned value is a Failure object""" + result = QtSql.QSqlQuery('''SELECT * FROM failures WHERE Id = "%s"''' % talkid) + if result.next(): + failure = Failure(unicode(result.value(0).toString()), # id + unicode(result.value(1).toString()), # comment + unicode(result.value(2).toString()), # indicator + result.value(3).toBool()) # release + else: + failure = None + return failure + + def get_reports(self): + """Returns a list of failures in Report format""" + result = QtSql.QSqlQuery('''Select * FROM failures''') + list = [] + while result.next(): + failure = Failure(unicode(result.value(0).toString()), # id + unicode(result.value(1).toString()), # comment + unicode(result.value(2).toString()), # indicator + bool(result.value(3))) # release + p = self.get_presentation(failure.talkId) + r = Report(p, failure) + list.append(r) + return list + + def insert_failure(self, failure): + """Inserts a failure into the database""" + QtSql.QSqlQuery( + '''INSERT INTO failures VALUES ("%d", "%s", "%s", %d)''' % + (int(failure.talkId), failure.comment, failure.indicator, failure.release)) + log.info("Failure added: %s - %s" % (failure.talkId, failure.comment)) + + def update_failure(self, talk_id, failure): + """Updates an existing Failure in the database""" + QtSql.QtSqlQuery('''UPDATE failures SET Comments="%s", Indicator="%s", Release="%d" WHERE Id="%s"''' % + (failure.comment, + failure.indicator, + failure.release, + failure.talkId)) + log.info("Failure updated: %s %s" % (failure.talkId, failure.comment)) + + def delete_failure(self, talk_id): + """Removes a Presentation from the database""" + QtSql.QSqlQuery('''DELETE FROM failures WHERE Id="%s"''' % talk_id) + log.info("Failure %s deleted." % talk_id) + + def get_failures_model(self): + """Gets the Failure reports table Model. Useful for QT GUI based Frontends to load the Model in Table Views""" + if self.failuresModel is None: + self.failuresModel = QtSql.QSqlTableModel() + self.failuresModel.setTable("failures") + self.failuresModel.setEditStrategy(QtSql.QSqlTableModel.OnFieldChange) + self.failuresModel.select() + + return self.failuresModel + + # + # Controller Feature + # + def __create_recentconn_table(self): + """Creates the recentconn table in the database. Should be used to initialize a new table""" + QtSql.QSqlQuery('''CREATE TABLE IF NOT EXISTS recentconn + (host varchar(255), + port int, + passphrase varchar(255), + UNIQUE (host, port) ON CONFLICT REPLACE)''') + + def clear_recentconn_table(self): + """Drops the recentconn (Controller) table from the database""" + QtSql.QSqlQuery('''DROP TABLE IF EXISTS recentconn''') + + def insert_recentconn(self, chost, cport, cpass): + """Insert a failure into the database""" + QtSql.QSqlQuery('''INSERT INTO recentconn VALUES("%s", "%d", "%s")''' % (chost, cport, cpass)) + log.info("Recent connection added: %s:%d" % (chost, cport)) + + def get_recentconn_model(self): + """Gets the Recent Connections table Model + Useful for QT GUI based Frontends to load the Model in Table Views""" + self.recentconnModel = QtSql.QSqlTableModel() + self.recentconnModel.setTable("recentconn") + self.recentconnModel.setEditStrategy(QtSql.QSqlTableModel.OnFieldChange) + self.recentconnModel.select() + + return self.recentconnModel diff --git a/src/freeseer/framework/qt_key_grabber.py b/src/freeseer/framework/qt_key_grabber.py index b08e990e..e55f71f7 100644 --- a/src/freeseer/framework/qt_key_grabber.py +++ b/src/freeseer/framework/qt_key_grabber.py @@ -48,22 +48,22 @@ def __init__(self, parent=None): def keyPressEvent(self, event): other = None if event.key() == QtCore.Qt.Key_Shift: - self.modifiers[QtCore.Qt.Key_Shift] = u'Shift' + self.modifiers[QtCore.Qt.Key_Shift] = 'Shift' elif event.key() == QtCore.Qt.Key_Control: - self.modifiers[QtCore.Qt.Key_Control] = u'Ctrl' + self.modifiers[QtCore.Qt.Key_Control] = 'Ctrl' elif event.key() == QtCore.Qt.Key_Alt: - self.modifiers[QtCore.Qt.Key_Alt] = u'Alt' + self.modifiers[QtCore.Qt.Key_Alt] = 'Alt' elif event.key() == QtCore.Qt.Key_Meta: - self.modifiers[QtCore.Qt.Key_Meta] = u'Meta' + self.modifiers[QtCore.Qt.Key_Meta] = 'Meta' else: other = event.text() if other: if QtCore.Qt.Key_Control in self.modifiers: - self.key_string = u'+'.join(self.modifiers.values() + [unicode(chr(event.key()))]) + self.key_string = '+'.join(list(self.modifiers.values()) + [str(chr(event.key()))]) else: - self.key_string = u'+'.join(self.modifiers.values() + [unicode(other)]) + self.key_string = '+'.join(list(self.modifiers.values()) + [str(other)]) else: - self.key_string = u'+'.join(self.modifiers.values()) + self.key_string = '+'.join(list(self.modifiers.values())) if (self.parent.core.config.key_rec == 'Ctrl+Shift+R'): self.flag = True diff --git a/src/freeseer/framework/qt_key_grabber.py.bak b/src/freeseer/framework/qt_key_grabber.py.bak new file mode 100644 index 00000000..b08e990e --- /dev/null +++ b/src/freeseer/framework/qt_key_grabber.py.bak @@ -0,0 +1,95 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +import sys +from PyQt4 import QtCore, QtGui + + +class QtKeyGrabber(QtGui.QWidget): + ''' + This class allows the user to press a combination of keys in order to + set a shortkey. + ''' + def __init__(self, parent=None): + ''' + Create an active screen and initialize variables used in this + class. + ''' + QtGui.QWidget.__init__(self, None, QtCore.Qt.FramelessWindowHint) + self.setAttribute(QtCore.Qt.WA_DeleteOnClose) + self.setWindowState(QtCore.Qt.WindowActive) + + self.parent = parent + self.flag = False + self.modifiers = {} + self.setWindowOpacity(0.3) + + def keyPressEvent(self, event): + other = None + if event.key() == QtCore.Qt.Key_Shift: + self.modifiers[QtCore.Qt.Key_Shift] = u'Shift' + elif event.key() == QtCore.Qt.Key_Control: + self.modifiers[QtCore.Qt.Key_Control] = u'Ctrl' + elif event.key() == QtCore.Qt.Key_Alt: + self.modifiers[QtCore.Qt.Key_Alt] = u'Alt' + elif event.key() == QtCore.Qt.Key_Meta: + self.modifiers[QtCore.Qt.Key_Meta] = u'Meta' + else: + other = event.text() + if other: + if QtCore.Qt.Key_Control in self.modifiers: + self.key_string = u'+'.join(self.modifiers.values() + [unicode(chr(event.key()))]) + else: + self.key_string = u'+'.join(self.modifiers.values() + [unicode(other)]) + else: + self.key_string = u'+'.join(self.modifiers.values()) + if (self.parent.core.config.key_rec == 'Ctrl+Shift+R'): + self.flag = True + + def keyReleaseEvent(self, event): + if event.key() == QtCore.Qt.Key_Shift: + if QtCore.Qt.Key_Shift in self.modifiers: + del self.modifiers[QtCore.Qt.Key_Shift] + elif event.key() == QtCore.Qt.Key_Control: + if QtCore.Qt.Key_Control in self.modifiers: + del self.modifiers[QtCore.Qt.Key_Control] + elif event.key() == QtCore.Qt.Key_Alt: + if QtCore.Qt.Key_Alt in self.modifiers: + del self.modifiers[QtCore.Qt.Key_Alt] + elif event.key() == QtCore.Qt.Key_Meta: + if QtCore.Qt.Key_Meta in self.modifiers: + del self.modifiers[QtCore.Qt.Key_Meta] + #print len(self.modifiers) + if len(self.modifiers) == 0: + if self.flag: + self.parent.grab_rec_set(self.key_string) + else: + self.parent.grab_stop_set(self.key_string) + self.close() + +if __name__ == "__main__": + app = QtGui.QApplication(sys.argv) + main = QtKeyGrabber() + main.show() + sys.exit(app.exec_()) diff --git a/src/freeseer/framework/util.py b/src/freeseer/framework/util.py index 40e43f68..e793ae3a 100644 --- a/src/freeseer/framework/util.py +++ b/src/freeseer/framework/util.py @@ -90,7 +90,7 @@ def make_record_name(presentation): make_shortname(presentation.speaker), make_shortname(presentation.title), ] - record_name = unicode('-'.join(tag for tag in tags if tag)) + record_name = str('-'.join(tag for tag in tags if tag)) # Convert unicode filenames to their equivalent ascii so that # we don't run into issues with gstreamer or filesystems. @@ -121,7 +121,7 @@ def reset(configdir): if confirm_yes() is True: shutil.rmtree(configdir) else: - print("%s is not a invalid configuration directory." % configdir) + print(("%s is not a invalid configuration directory." % configdir)) def reset_configuration(configdir, profile='default'): @@ -139,7 +139,7 @@ def reset_configuration(configdir, profile='default'): if os.path.exists(plugin_conf): os.remove(plugin_conf) else: - print("%s is not a invalid configuration directory." % configdir) + print(("%s is not a invalid configuration directory." % configdir)) def reset_database(configdir, profile='default'): @@ -153,7 +153,7 @@ def reset_database(configdir, profile='default'): if os.path.exists(dbfile): os.remove(dbfile) else: - print("%s is not a invalid configuration directory." % configdir) + print(("%s is not a invalid configuration directory." % configdir)) def validate_configdir(configdir): @@ -168,7 +168,7 @@ def validate_configdir(configdir): def confirm_yes(): """Prompts the user to confirm by typing 'yes' in response""" - confirm = raw_input("Enter 'yes' to confirm: ") + confirm = input("Enter 'yes' to confirm: ") if confirm == 'yes': return True return False diff --git a/src/freeseer/framework/util.py.bak b/src/freeseer/framework/util.py.bak new file mode 100644 index 00000000..40e43f68 --- /dev/null +++ b/src/freeseer/framework/util.py.bak @@ -0,0 +1,174 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2013, 2014 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +import ctypes +import os +import shutil +import sys +import unicodedata + + +def format_size(num): + for x in ['bytes', 'KB', 'MB', 'GB', 'TB']: + if num < 1024.0: + return "%3.1f %s" % (num, x) + num /= 1024.0 + + +def get_free_space(directory): + """ Return directory free space (in human readable form) """ + if sys.platform in ["win32", "cygwin"]: + free_bytes = ctypes.c_ulonglong(0) + ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(directory), + None, None, ctypes.pointer(free_bytes)) + space = free_bytes.value + else: + space = os.statvfs(directory).f_bfree * os.statvfs(directory).f_frsize + + return format_size(space) + +### +### Filename related functions +### + + +def get_record_name(extension, presentation=None, filename=None, path="."): + """Returns the filename to use when recording. + + If a record name with a .None extension is returned, the record name + will just be ignored by the output plugin (e.g. Video Preview plugin). + + Function will return None if neither presentation nor filename is passed. + """ + if presentation is not None: + recordname = make_record_name(presentation) + elif filename is not None: + recordname = filename + else: + return None + + count = 0 + tempname = recordname + + # Add a number to the end of a duplicate record name so we don't + # overwrite existing files + while(os.path.exists(os.path.join(path, "%s.%s" % (tempname, extension)))): + tempname = "{0}-{1}".format(recordname, count) + count += 1 + + recordname = "%s.%s" % (tempname, extension) + + return recordname + + +def make_record_name(presentation): + """Create an 'EVENT-ROOM-SPEAKER-TITLE' record name using presentation metadata.""" + tags = [ + make_shortname(presentation.event), + make_shortname(presentation.room), + make_shortname(presentation.speaker), + make_shortname(presentation.title), + ] + record_name = unicode('-'.join(tag for tag in tags if tag)) + + # Convert unicode filenames to their equivalent ascii so that + # we don't run into issues with gstreamer or filesystems. + safe_record_name = unicodedata.normalize('NFKD', record_name).encode('ascii', 'ignore') + + return safe_record_name or 'default' + + +def make_shortname(string): + """Returns the first 6 characters of a string in uppercase. + + Strip out non alpha-numeric characters, spaces, and most punctuation. + """ + bad_chars = set("!@#$%^&*()+=|:;{}[]',? <>~`/\\") + string = "".join(ch for ch in string if ch not in bad_chars) + return string[0:6].upper() + + +### +### Handy functions for reseting Freeseer configuration +### + + +def reset(configdir): + """Deletes the Freeseer configuration directory""" + if validate_configdir(configdir): + print('This will wipe out your freeseer configuration directory.') + if confirm_yes() is True: + shutil.rmtree(configdir) + else: + print("%s is not a invalid configuration directory." % configdir) + + +def reset_configuration(configdir, profile='default'): + """Deletes the Freeseer configuration files freeseer.conf and plugin.conf""" + if profile is None: + profile = 'default' + + if validate_configdir(configdir): + freeseer_conf = os.path.join(configdir, 'profiles', profile, 'freeseer.conf') + plugin_conf = os.path.join(configdir, 'profiles', profile, 'plugin.conf') + + if os.path.exists(freeseer_conf): + os.remove(freeseer_conf) + + if os.path.exists(plugin_conf): + os.remove(plugin_conf) + else: + print("%s is not a invalid configuration directory." % configdir) + + +def reset_database(configdir, profile='default'): + """Deletes the Freeseer database file""" + if profile is None: + profile = 'default' + + if validate_configdir(configdir): + dbfile = os.path.join(configdir, 'profiles', profile, 'presentations.db') + + if os.path.exists(dbfile): + os.remove(dbfile) + else: + print("%s is not a invalid configuration directory." % configdir) + + +def validate_configdir(configdir): + """Validate that the configdir is not one of the blacklisted directories""" + if (configdir and configdir != '/' and + configdir != '~' and + configdir != os.path.abspath(os.path.expanduser('~'))): + return True + + return False + + +def confirm_yes(): + """Prompts the user to confirm by typing 'yes' in response""" + confirm = raw_input("Enter 'yes' to confirm: ") + if confirm == 'yes': + return True + return False diff --git a/src/freeseer/framework/youtube.py b/src/freeseer/framework/youtube.py index c6be7afc..ba8c93b8 100644 --- a/src/freeseer/framework/youtube.py +++ b/src/freeseer/framework/youtube.py @@ -23,7 +23,7 @@ # http://wiki.github.com/Freeseer/freeseer/ -import httplib +import http.client import httplib2 import logging import os @@ -58,13 +58,13 @@ class YoutubeService(object): RETRIABLE_EXCEPTIONS = ( httplib2.HttpLib2Error, IOError, - httplib.NotConnected, - httplib.IncompleteRead, - httplib.ImproperConnectionState, - httplib.CannotSendRequest, - httplib.CannotSendHeader, - httplib.ResponseNotReady, - httplib.BadStatusLine + http.client.NotConnected, + http.client.IncompleteRead, + http.client.ImproperConnectionState, + http.client.CannotSendRequest, + http.client.CannotSendHeader, + http.client.ResponseNotReady, + http.client.BadStatusLine ) RETRIABLE_STATUS_CODES = (500, 502, 503, 504) diff --git a/src/freeseer/framework/youtube.py.bak b/src/freeseer/framework/youtube.py.bak new file mode 100644 index 00000000..c6be7afc --- /dev/null +++ b/src/freeseer/framework/youtube.py.bak @@ -0,0 +1,220 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + + +import httplib +import httplib2 +import logging +import os +import time + +from apiclient import discovery +from apiclient.errors import HttpError +from apiclient.http import MediaFileUpload +from mutagen import oggvorbis +from oauth2client import file +from oauth2client import client +from oauth2client import tools + +log = logging.getLogger(__name__) + + +class Response(object): + """Class to serve as enum for responses""" + + SUCCESS = 0 + UNEXPECTED_FAILURE = 1 + UNRETRIABLE_ERROR = 2 + ACCESS_TOKEN_ERROR = 3 + MAX_RETRIES_REACHED = 4 + + +class YoutubeService(object): + """Class for interacting with YouTube Data API v3""" + + # Status codes and exceptions for retry logic + MAX_RETRIES = 3 + RETRIABLE_EXCEPTIONS = ( + httplib2.HttpLib2Error, + IOError, + httplib.NotConnected, + httplib.IncompleteRead, + httplib.ImproperConnectionState, + httplib.CannotSendRequest, + httplib.CannotSendHeader, + httplib.ResponseNotReady, + httplib.BadStatusLine + ) + RETRIABLE_STATUS_CODES = (500, 502, 503, 504) + + @staticmethod + def acquire_token(client_secrets, oauth2_token, flags): + """Handles the user consent process and saves the retrieved OAuth2 token + + Args: + client_secrets - path to client_secrets file + oauth2_token - path to save oauth2_token + + (YouTube Service Parameters) + https://google-api-python-client.googlecode.com/hg/docs/epy/oauth2client.tools-module.html + + flags.auth_host_name - Host name to use when running a local web server to handle redirects during + OAuth authorization. + (default: 'localhost') + + flags..auth_host_port - Port to use when running a local web server to handle redirects during OAuth + authorization.; repeat this option to specify a list of values + (default: '[8080, 8090]') + (an integer) + + flags.auth_local_webserver - True/False Run a local web server to handle redirects during OAuth + authorization. + (default: True) + """ + scope = ['https://www.googleapis.com/auth/youtube.upload'] + message = ("Please specify a valid client_secrets.json file.\n" + "For instructions to obtain one, please visit:\n" + "https://docs.google.com/document/d/1ro9I8jnOCgQlWRRVCPbrNnQ5-bMvQxDVg6o45zxud4c/edit") + flow = client.flow_from_clientsecrets(client_secrets, scope=scope, message=message) + storage = file.Storage(oauth2_token) + tools.run_flow(flow, storage, flags) + + @staticmethod + def valid_video_file(file): + """Verify file is supported by Youtube + + Freeseer currently encodes to .ogg and .webm + TODO: expand list to all types supported by Youtube + + Args: + file: path to file to verify + + Returns: + True if file is supported + """ + return file.lower().endswith(('.ogg', '.webm')) + + @staticmethod + def get_metadata(video_file): + """Parses file metadata + + Parsing is delegated to appropriate library based on filetype + If filetype is unsupported default metadata is used instead + + Args: + video_file: path to file + + Returns: + Metadata formatted to Youtube APIs status parameter + """ + metadata = { + "title": os.path.basename(video_file).split(".")[0], + "description": "A video recorded with Freeseer", + "tags": ['Freeseer', 'FOSSLC', 'Open Source'], + "categoryId": 27 # temporary, see gh#415 + } + if video_file.lower().endswith('.ogg'): + tags = oggvorbis.Open(video_file) + if "title" in tags: + metadata['title'] = tags['title'][0] + if "album" in tags and "artist" in tags and "date" in tags: + metadata['description'] = "At {} by {} recorded on {}".format(tags['album'][0], tags['artist'][0], tags['date'][0]) + return metadata + + def __init__(self): + """Initialize YoutubeService setting up http related values""" + # Tell the underlying HTTP transport library not to retry, we want explicit control over the retry logic + # and to only retry on errors/exceptions specified by Google + httplib2.RETRIES = 1 + + def authorize(self, oauth2_token): + """Function that authorizes upcoming HTTP transactions + + If the token has expired, a new one is retrieved automatically via the refresh token (located inside the same file) + """ + storage = file.Storage(oauth2_token) + credentials = storage.get() + http = credentials.authorize(httplib2.Http()) + self.service = discovery.build('youtube', 'v3', http=http) + + def upload_video(self, video_file): + """Function to upload file to Youtube + + Function grabs metadata from video_file and passes it along in the upload, the files privacy and license are + set to public and youtube respectively + + Args: + video_file: path to video file for upload + + Returns: + A tuple containing a response code and a dictionary with the appropriate information + """ + part = "snippet,status" + metadata = self.get_metadata(video_file) + body = { + "snippet": { + "title": metadata['title'], + "description": metadata['description'], + "tags": metadata['categoryId'], + "categoryId": metadata['categoryId'] + }, + "status": { + "privacyStatus": "public", + "license": "youtube", # temporary, see gh#414 + "embeddable": True, + "publicStatsViewable": True + } + } + # This is to fix a bug, the API thinks our .ogg files are audio/ogg + mimetype = "video/{}".format(video_file.split(".")[-1]) + media_body = MediaFileUpload(video_file, chunksize=-1, resumable=True, mimetype=mimetype) + insert_request = self.service.videos().insert(part=part, body=body, media_body=media_body) + response = None + error = None + retry = 0 + sleep_seconds = 5.0 + while response is None: + try: + log.info("Uploading %s" % video_file) + (status, response) = insert_request.next_chunk() + if 'id' in response: + return (Response.SUCCESS, response) + else: + return (Response.UNEXPECTED_FAILURE, response) + except HttpError as e: + if e.resp.status in self.RETRIABLE_STATUS_CODES: + error = "A retriable HTTP error {} occurred:\n{}".format(e.resp.status, e.content) + else: + return (Response.UNRETRIABLE_ERROR, {"status": e.resp.status, "content": e.content}) + except self.RETRIABLE_EXCEPTIONS as e: + error = "A retriable error occurred: {}".format(e) + except client.AccessTokenRefreshError: + return (Response.ACCESS_TOKEN_ERROR, None) + if error is not None: + log.error(error) + retry += 1 + if retry > self.MAX_RETRIES: + return (Response.MAX_RETRIES_REACHED, None) + log.info("Sleeping %s seconds and then retrying..." % sleep_seconds) + time.sleep(sleep_seconds) diff --git a/src/freeseer/frontend/cli.py b/src/freeseer/frontend/cli.py index 898963d7..4449e705 100644 --- a/src/freeseer/frontend/cli.py +++ b/src/freeseer/frontend/cli.py @@ -77,8 +77,8 @@ def setup_parser_record(subparsers): """Setup the record command parser""" parser = subparsers.add_parser('record', help='Freeseer recording functions') parser.add_argument("-t", "--talk", type=int, help="Talk ID of the talk you would like to record") - parser.add_argument("-f", "--filename", type=unicode, help="Record to filename") - parser.add_argument("-p", "--profile", type=unicode, help="Use profile") + parser.add_argument("-f", "--filename", type=str, help="Record to filename") + parser.add_argument("-p", "--profile", type=str, help="Use profile") parser.add_argument("-s", "--show-talks", help="Shows all talks", action="store_true") @@ -107,7 +107,7 @@ def setup_parser_config_reset(subparsers): configuration - Resets Freeseer configuration (removes freeseer.conf and plugins.conf) database - Resets Freeseer database (removes presentations.db) """) - parser.add_argument("-p", "--profile", type=unicode, help="Profile to reset (Default: default)") + parser.add_argument("-p", "--profile", type=str, help="Profile to reset (Default: default)") def setup_parser_config_youtube(subparsers): @@ -124,10 +124,10 @@ def setup_parser_talk(subparsers): """Setup the talk command parser""" parser = subparsers.add_parser('talk', help='Freeseer talk database functions') parser.add_argument("action", choices=['add', 'remove', 'clear', 'list'], nargs='?') - parser.add_argument("-t", "--title", type=unicode, help="Title") - parser.add_argument("-s", "--speaker", type=unicode, help="Speaker") - parser.add_argument("-r", "--room", type=unicode, help="Room") - parser.add_argument("-e", "--event", type=unicode, help="Event") + parser.add_argument("-t", "--title", type=str, help="Title") + parser.add_argument("-s", "--speaker", type=str, help="Speaker") + parser.add_argument("-r", "--room", type=str, help="Room") + parser.add_argument("-e", "--event", type=str, help="Event") parser.add_argument("-i", "--talk-id", type=int, help="Talk ID") @@ -155,7 +155,7 @@ def setup_parser_upload_youtube(subparsers): def setup_parser_server(subparsers): """Setup server command parser""" parser = subparsers.add_parser("server", help="Setup a freeseer restful server") - parser.add_argument("-f", "--filename", type=unicode, help="file to load recordings") + parser.add_argument("-f", "--filename", type=str, help="file to load recordings") def parse_args(parser, parse_args=None): @@ -242,7 +242,7 @@ def parse_args(parser, parse_args=None): elif args.action == "list": talks_query = db.get_talks() talks_table = [] - while talks_query.next(): + while next(talks_query): record = talks_query.record() talks_table.append([ talks_query.value(record.indexOf('id')).toString(), @@ -251,7 +251,7 @@ def parse_args(parser, parse_args=None): talks_query.value(record.indexOf('event')).toString(), ]) if talks_table: - print(tabulate(talks_table, headers=["ID", "Title", "Speaker", "Event"])) + print((tabulate(talks_table, headers=["ID", "Title", "Speaker", "Event"]))) else: print("No talks present.") diff --git a/src/freeseer/frontend/cli.py.bak b/src/freeseer/frontend/cli.py.bak new file mode 100644 index 00000000..898963d7 --- /dev/null +++ b/src/freeseer/frontend/cli.py.bak @@ -0,0 +1,343 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2013, 2014 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + + +import argparse +import signal +import sys +import textwrap + +import pygst +import yapsy +from PyQt4 import QtCore +from tabulate import tabulate + +from freeseer import __version__ +from freeseer import settings +from freeseer.frontend.upload import youtube + +signal.signal(signal.SIGINT, signal.SIG_DFL) + + +def setup_parser(): + """Initialize the Argument Parser""" + parser = argparse.ArgumentParser(description='Freeseer Recording Utility', + formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument("-v", "--version", action='version', + version=textwrap.dedent('''\ + Freeseer {version} ({platform}) + Python {pymajor}.{pyminor}.{pymicro} + PyGst {pygst_version} + PyQt {pyqt_version} + Qt {qt_version} + Yapsy {yapsy_version} + '''.format(version=__version__, + platform=sys.platform, + pymajor=sys.version_info.major, + pyminor=sys.version_info.minor, + pymicro=sys.version_info.micro, + pygst_version=pygst._pygst_version, + pyqt_version=QtCore.PYQT_VERSION_STR, + qt_version=QtCore.QT_VERSION_STR, + yapsy_version=yapsy.__version__))) + + # Configure Subparsers + subparsers = parser.add_subparsers(dest='app', help='Command List') + setup_parser_record(subparsers) + setup_parser_config(subparsers) + setup_parser_talk(subparsers) + setup_parser_report(subparsers) + setup_parser_upload(subparsers) + setup_parser_server(subparsers) + return parser + + +def setup_parser_record(subparsers): + """Setup the record command parser""" + parser = subparsers.add_parser('record', help='Freeseer recording functions') + parser.add_argument("-t", "--talk", type=int, help="Talk ID of the talk you would like to record") + parser.add_argument("-f", "--filename", type=unicode, help="Record to filename") + parser.add_argument("-p", "--profile", type=unicode, help="Use profile") + parser.add_argument("-s", "--show-talks", help="Shows all talks", action="store_true") + + +### +### Config Parser and Subparsers +### + +def setup_parser_config(subparsers): + """Setup the config command parser""" + parser = subparsers.add_parser('config', help='Freeseer configuration functions') + subparsers = parser.add_subparsers(dest="config_service") + setup_parser_config_reset(subparsers) + setup_parser_config_youtube(subparsers) + + +def setup_parser_config_reset(subparsers): + """Setup reset command parser""" + parser = subparsers.add_parser("reset", help="Reset Freeseer configuration and database", + formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument("reset", + choices=['all', 'configuration', 'database'], + help="""Resets Freeseer (default: all) + + Options: + all - Resets Freeseer (removes the Freeseer configuration directory, thus clearing logs, settings, and talks) + configuration - Resets Freeseer configuration (removes freeseer.conf and plugins.conf) + database - Resets Freeseer database (removes presentations.db) + """) + parser.add_argument("-p", "--profile", type=unicode, help="Profile to reset (Default: default)") + + +def setup_parser_config_youtube(subparsers): + """Setup Youtube config parser""" + from oauth2client import tools + # Inherent Google API argparser + parser = subparsers.add_parser("youtube", help="Obtain OAuth2 token for Youtube access", parents=[tools.argparser]) + defaults = youtube.get_defaults() + parser.add_argument("-c", "--client-secrets", help="Path to client secrets file", default=defaults["client_secrets"]) + parser.add_argument("-t", "--token", help="Location to save token file", default=defaults["oauth2_token"]) + + +def setup_parser_talk(subparsers): + """Setup the talk command parser""" + parser = subparsers.add_parser('talk', help='Freeseer talk database functions') + parser.add_argument("action", choices=['add', 'remove', 'clear', 'list'], nargs='?') + parser.add_argument("-t", "--title", type=unicode, help="Title") + parser.add_argument("-s", "--speaker", type=unicode, help="Speaker") + parser.add_argument("-r", "--room", type=unicode, help="Room") + parser.add_argument("-e", "--event", type=unicode, help="Event") + parser.add_argument("-i", "--talk-id", type=int, help="Talk ID") + + +def setup_parser_report(subparsers): + """Setup the report command parser""" + subparsers.add_parser('report', help='Freeseer reporting functions') + + +def setup_parser_upload(subparsers): + """Setup upload tool command parser""" + parser = subparsers.add_parser("upload", help="Upload file tool") + subparsers = parser.add_subparsers(dest="upload_service", help="Service to upload with") + setup_parser_upload_youtube(subparsers) + + +def setup_parser_upload_youtube(subparsers): + """Setup youtube upload command parser""" + defaults = youtube.get_defaults() + parser = subparsers.add_parser("youtube", help="Youtube upload command line tool") + parser.add_argument("files", help="Path to videos or video directories to upload", nargs="*", default=[defaults["video_directory"]]) + parser.add_argument("-t", "--token", help="Path to OAuth2 token", default=defaults["oauth2_token"]) + parser.add_argument("-y", "--yes", help="Automatic yes to prompts", action="store_true") + + +def setup_parser_server(subparsers): + """Setup server command parser""" + parser = subparsers.add_parser("server", help="Setup a freeseer restful server") + parser.add_argument("-f", "--filename", type=unicode, help="file to load recordings") + + +def parse_args(parser, parse_args=None): + if len(sys.argv) == 1: # No arguments passed + launch_recordapp() + + args = parser.parse_args(parse_args) + + if args.app == 'record': + if len(sys.argv) == 2: # No 'record' arguments passed + launch_recordapp() + + import gobject + # Must declare after argparse otherwise GStreamer will take over the cli help + from freeseer.frontend.record.RecordingController import RecordingController + + # TODO: Abstract the database stuff away from here as it's only + # used in conjunction with talks. + if args.profile is None: # Profile is an optional parameter + args.profile = 'default' + profile = settings.profile_manager.get(args.profile) + config = profile.get_config('freeseer.conf', settings.FreeseerConfig, + storage_args=['Global'], read_only=False) + # XXX: There should only be 1 database per user. Workaround for this + # is to put it in the 'default' profile. + db = settings.profile_manager.get().get_database() + + app = RecordingController(profile, db, config, cli=True) + + if args.talk: + if app.record_talk_id(args.talk): + sys.exit(gobject.MainLoop().run()) + elif args.filename: + if app.record_filename(args.filename): + sys.exit(gobject.MainLoop().run()) + elif args.show_talks: + app.print_talks() + + elif args.app == 'config': + if len(sys.argv) == 2: # No 'config' arguments passed + launch_configtool() + + from freeseer.settings import configdir + from freeseer.framework.util import reset + from freeseer.framework.util import reset_configuration + from freeseer.framework.util import reset_database + from freeseer.framework.youtube import YoutubeService + + if args.config_service == "reset": + if args.reset == "all": + reset(configdir) + elif args.reset == "configuration": + reset_configuration(configdir, args.profile) + elif args.reset == "database": + reset_database(configdir, args.profile) + else: + print("Invalid reset option.") + + elif args.config_service == "youtube": + YoutubeService.acquire_token(args.client_secrets, args.token, args) + + elif args.app == 'talk': + if len(sys.argv) == 2: # No 'talk' arguments passed + launch_talkeditor() + + from freeseer.framework.presentation import Presentation + + profile = settings.profile_manager.get() + db = profile.get_database() + + if args.action == "add": + presentation = Presentation(args.title, + speaker=args.speaker, + room=args.room, + event=args.event) + db.insert_presentation(presentation) + + elif args.action == "remove": + db.delete_presentation(args.talk_id) + + elif args.action == "clear": + db.clear_database() + + elif args.action == "list": + talks_query = db.get_talks() + talks_table = [] + while talks_query.next(): + record = talks_query.record() + talks_table.append([ + talks_query.value(record.indexOf('id')).toString(), + talks_query.value(record.indexOf('title')).toString(), + talks_query.value(record.indexOf('speaker')).toString(), + talks_query.value(record.indexOf('event')).toString(), + ]) + if talks_table: + print(tabulate(talks_table, headers=["ID", "Title", "Speaker", "Event"])) + else: + print("No talks present.") + + else: + print("Invalid option.") + + elif args.app == 'report': + if len(sys.argv) == 2: # No 'report' arguments passed + launch_reporteditor() + + elif args.app == 'upload': + if args.upload_service == 'youtube': + youtube.upload(args.files, args.token, args.yes) + + elif args.app == 'server': + if args.filename: + launch_server(args.filename) + else: + launch_server() + + +def launch_recordapp(): + """Launch the Recording GUI if no arguments are passed""" + from PyQt4.QtGui import QApplication + from freeseer.frontend.record.record import RecordApp + + profile = settings.profile_manager.get() + config = profile.get_config('freeseer.conf', settings.FreeseerConfig, + storage_args=['Global'], read_only=False) + + app = QApplication(sys.argv) + main = RecordApp(profile, config) + main.show() + sys.exit(app.exec_()) + + +def launch_configtool(): + """Launch Freeseer Configuration GUI if no arguments are passed""" + from PyQt4 import QtGui + from freeseer.frontend.configtool.configtool import ConfigToolApp + + profile = settings.profile_manager.get() + config = profile.get_config('freeseer.conf', settings.FreeseerConfig, + storage_args=['Global'], read_only=False) + + app = QtGui.QApplication(sys.argv) + main = ConfigToolApp(profile, config) + main.show() + sys.exit(app.exec_()) + + +def launch_talkeditor(): + """Launch the Talk Editor GUI if no arguments are passed""" + from PyQt4.QtGui import QApplication + from freeseer.frontend.talkeditor.talkeditor import TalkEditorApp + + profile = settings.profile_manager.get() + config = profile.get_config('freeseer.conf', settings.FreeseerConfig, + storage_args=['Global'], read_only=True) + db = profile.get_database() + + app = QApplication(sys.argv) + main = TalkEditorApp(config, db) + main.show() + sys.exit(app.exec_()) + + +def launch_reporteditor(): + """Launch the Report Editor GUI""" + import sys + from PyQt4 import QtGui + from freeseer.frontend.reporteditor.reporteditor import ReportEditorApp + + profile = settings.profile_manager.get() + config = profile.get_config('freeseer.conf', settings.FreeseerConfig, + storage_args=['Global'], read_only=True) + db = profile.get_database() + + app = QtGui.QApplication(sys.argv) + main = ReportEditorApp(config, db) + main.show() + sys.exit(app.exec_()) + + +def launch_server(storage_file="recording_storage"): + """Launch the Server""" + import freeseer.frontend.controller.server as server + + server.start_server(storage_file) diff --git a/src/freeseer/frontend/configtool/AVWidget.py b/src/freeseer/frontend/configtool/AVWidget.py index 5c1269cb..7452a654 100644 --- a/src/freeseer/frontend/configtool/AVWidget.py +++ b/src/freeseer/frontend/configtool/AVWidget.py @@ -212,7 +212,7 @@ def __init__(self, parent=None): # self.mainLayout.insertSpacerItem(0, QtGui.QSpacerItem(0, fontSize * 2)) - self.title = QtGui.QLabel(u"{0} Recording {1}".format(u'

', u'

')) + self.title = QtGui.QLabel("{0} Recording {1}".format('

', '

')) self.mainLayout.insertWidget(0, self.title) diff --git a/src/freeseer/frontend/configtool/AVWidget.py.bak b/src/freeseer/frontend/configtool/AVWidget.py.bak new file mode 100644 index 00000000..5c1269cb --- /dev/null +++ b/src/freeseer/frontend/configtool/AVWidget.py.bak @@ -0,0 +1,224 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +''' +freeseer - vga/presentation capture software + +Copyright (C) 2011 Free and Open Source Software Learning Centre +http://fosslc.org + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +For support, questions, suggestions or any other inquiries, visit: +http://wiki.github.com/Freeseer/freeseer/ + +@author: Thanh Ha +''' + +from PyQt4 import QtCore +from PyQt4 import QtGui + +from freeseer.framework.multimedia import Quality +from freeseer.frontend.qtcommon.dpi_adapt_qtgui import QGroupBoxWithDpi +from freeseer.frontend.qtcommon.dpi_adapt_qtgui import QWidgetWithDpi + + +class AVWidget(QWidgetWithDpi): + ''' + classdocs + ''' + + def __init__(self, parent=None): + ''' + Constructor + ''' + super(AVWidget, self).__init__(parent) + + self.mainLayout = QtGui.QVBoxLayout() + self.mainLayout.addStretch(0) + self.setLayout(self.mainLayout) + + config_icon = QtGui.QIcon.fromTheme("preferences-system") + + fontSize = self.font().pixelSize() + fontUnit = "px" + if fontSize == -1: # Font is set as points, not pixels. + fontUnit = "pt" + fontSize = self.font().pointSize() + + boxStyle = "QGroupBox {{ font-weight: bold; font-size: {}{} }}".format(fontSize + 1, fontUnit) + BOX_WIDTH = 400 + BOX_HEIGHT = 60 + + # + # Audio Input + # + + audioLayout = QtGui.QGridLayout() + self.audioGroupBox = QGroupBoxWithDpi("Audio Input") + self.audioGroupBox.setLayout(audioLayout) + self.mainLayout.insertWidget(0, self.audioGroupBox) + + self.audioGroupBox.setCheckable(True) + self.audioGroupBox.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + self.audioGroupBox.setFixedSize(BOX_WIDTH, 1.5 * BOX_HEIGHT) + self.audioGroupBox.setStyleSheet(boxStyle) + + self.audioMixerLabel = QtGui.QLabel("Audio Mixer") + self.audioMixerLabel.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) + self.audioMixerComboBox = QtGui.QComboBox() + self.audioMixerLabel.setBuddy(self.audioMixerComboBox) + self.audioMixerSetupPushButton = QtGui.QToolButton() + self.audioMixerSetupPushButton.setText("Setup") + self.audioMixerSetupPushButton.setIcon(config_icon) + self.audioMixerSetupPushButton.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) + self.audioMixerSetupPushButton.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) + audioLayout.addWidget(self.audioMixerLabel, 0, 0) + audioLayout.addWidget(self.audioMixerComboBox, 0, 1) + audioLayout.addWidget(self.audioMixerSetupPushButton, 0, 2) + + self.audioQualityLabel = QtGui.QLabel("Audio Quality") + self.audioQualityLabel.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) + self.audioQualityComboBox = QtGui.QComboBox() + self.audioQualityComboBox.addItems(Quality.qualities) + self.audioQualityLabel.setBuddy(self.audioQualityComboBox) + self.audioQualitySetupPushButton = QtGui.QToolButton() + self.audioQualitySetupPushButton.setText("Setup") + self.audioQualitySetupPushButton.setIcon(config_icon) + self.audioQualitySetupPushButton.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) + self.audioQualitySetupPushButton.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) + self.audioQualitySetupPushButton.setEnabled(False) + audioLayout.addWidget(self.audioQualityLabel, 1, 0) + audioLayout.addWidget(self.audioQualityComboBox, 1, 1) + audioLayout.addWidget(self.audioQualitySetupPushButton, 1, 2) + + # + # Video Input + # + + videoLayout = QtGui.QGridLayout() + self.videoGroupBox = QGroupBoxWithDpi("Video Input") + self.videoGroupBox.setLayout(videoLayout) + self.mainLayout.insertWidget(0, self.videoGroupBox) + + self.videoGroupBox.setCheckable(True) + self.videoGroupBox.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + self.videoGroupBox.setFixedSize(BOX_WIDTH, 1.5 * BOX_HEIGHT) + self.videoGroupBox.setStyleSheet(boxStyle) + + self.videoMixerLabel = QtGui.QLabel("Video Mixer") + self.videoMixerLabel.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) + self.videoMixerComboBox = QtGui.QComboBox() + self.videoMixerLabel.setBuddy(self.videoMixerComboBox) + self.videoMixerSetupPushButton = QtGui.QToolButton() + self.videoMixerSetupPushButton.setText("Setup") + self.videoMixerSetupPushButton.setIcon(config_icon) + self.videoMixerSetupPushButton.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) + self.videoMixerSetupPushButton.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) + videoLayout.addWidget(self.videoMixerLabel, 0, 0) + videoLayout.addWidget(self.videoMixerComboBox, 0, 1) + videoLayout.addWidget(self.videoMixerSetupPushButton, 0, 2) + + self.videoQualityLabel = QtGui.QLabel("Video Quality") + self.videoQualityLabel.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) + self.videoQualityComboBox = QtGui.QComboBox() + self.videoQualityComboBox.addItems(Quality.qualities) + self.videoQualityLabel.setBuddy(self.videoQualityComboBox) + self.videoQualitySetupPushButton = QtGui.QToolButton() + self.videoQualitySetupPushButton.setText("Setup") + self.videoQualitySetupPushButton.setIcon(config_icon) + self.videoQualitySetupPushButton.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) + self.videoQualitySetupPushButton.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) + self.videoQualitySetupPushButton.setEnabled(False) + videoLayout.addWidget(self.videoQualityLabel, 1, 0) + videoLayout.addWidget(self.videoQualityComboBox, 1, 1) + videoLayout.addWidget(self.videoQualitySetupPushButton, 1, 2) + + # + # Record to Stream + # + + streamLayout = QtGui.QGridLayout() + self.streamGroupBox = QGroupBoxWithDpi("Record to Stream") + self.streamGroupBox.setLayout(streamLayout) + self.mainLayout.insertWidget(0, self.streamGroupBox) + + self.streamGroupBox.setCheckable(True) + self.streamGroupBox.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + self.streamGroupBox.setFixedSize(BOX_WIDTH, BOX_HEIGHT) + self.streamGroupBox.setStyleSheet(boxStyle) + + self.streamLabel = QtGui.QLabel("Stream Format") + self.streamLabel.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) + self.streamComboBox = QtGui.QComboBox() + self.streamLabel.setBuddy(self.streamComboBox) + self.streamSetupPushButton = QtGui.QToolButton() + self.streamSetupPushButton.setText("Setup") + self.streamSetupPushButton.setIcon(config_icon) + self.streamSetupPushButton.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) + self.streamSetupPushButton.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) + streamLayout.addWidget(self.streamLabel, 0, 0) + streamLayout.addWidget(self.streamComboBox, 0, 1) + streamLayout.addWidget(self.streamSetupPushButton, 0, 2) + + # + # Record to File + # + + fileLayout = QtGui.QGridLayout() + self.fileGroupBox = QGroupBoxWithDpi("Record to File") + self.fileGroupBox.setLayout(fileLayout) + self.mainLayout.insertWidget(0, self.fileGroupBox) + + self.fileGroupBox.setCheckable(True) + self.fileGroupBox.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + self.fileGroupBox.setFixedSize(BOX_WIDTH, BOX_HEIGHT + 40) + self.fileGroupBox.setStyleSheet(boxStyle) + + self.fileDirLabel = QtGui.QLabel("Record Directory") + self.fileDirLineEdit = QtGui.QLineEdit() + self.fileDirLabel.setBuddy(self.fileDirLineEdit) + self.fileDirPushButton = QtGui.QPushButton("Browse...") + fileLayout.addWidget(self.fileDirLabel, 0, 0) + fileLayout.addWidget(self.fileDirLineEdit, 0, 1) + fileLayout.addWidget(self.fileDirPushButton, 0, 2) + + self.fileLabel = QtGui.QLabel("File Format") + self.fileLabel.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) + self.fileComboBox = QtGui.QComboBox() + self.fileLabel.setBuddy(self.fileComboBox) + self.fileSetupPushButton = QtGui.QToolButton() + self.fileSetupPushButton.setText("Setup") + self.fileSetupPushButton.setIcon(config_icon) + self.fileSetupPushButton.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) + self.fileSetupPushButton.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) + fileLayout.addWidget(self.fileLabel, 1, 0) + fileLayout.addWidget(self.fileComboBox, 1, 1) + fileLayout.addWidget(self.fileSetupPushButton, 1, 2) + + # + # Heading + # + + self.mainLayout.insertSpacerItem(0, QtGui.QSpacerItem(0, fontSize * 2)) + self.title = QtGui.QLabel(u"{0} Recording {1}".format(u'

', u'

')) + self.mainLayout.insertWidget(0, self.title) + + +if __name__ == "__main__": + import sys + app = QtGui.QApplication(sys.argv) + main = AVWidget() + main.show() + sys.exit(app.exec_()) diff --git a/src/freeseer/frontend/configtool/GeneralWidget.py b/src/freeseer/frontend/configtool/GeneralWidget.py index 4566e1c6..68b6b469 100644 --- a/src/freeseer/frontend/configtool/GeneralWidget.py +++ b/src/freeseer/frontend/configtool/GeneralWidget.py @@ -61,7 +61,7 @@ def __init__(self, parent=None): # Heading # - self.title = QtGui.QLabel(u"{0} General {1}".format(u'

', u'

')) + self.title = QtGui.QLabel("{0} General {1}".format('

', '

')) self.mainLayout.insertWidget(0, self.title) self.mainLayout.insertSpacerItem(1, QtGui.QSpacerItem(0, fontSize * 2)) diff --git a/src/freeseer/frontend/configtool/GeneralWidget.py.bak b/src/freeseer/frontend/configtool/GeneralWidget.py.bak new file mode 100644 index 00000000..4566e1c6 --- /dev/null +++ b/src/freeseer/frontend/configtool/GeneralWidget.py.bak @@ -0,0 +1,127 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +''' +freeseer - vga/presentation capture software + +Copyright (C) 2011 Free and Open Source Software Learning Centre +http://fosslc.org + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +For support, questions, suggestions or any other inquiries, visit: +http://wiki.github.com/Freeseer/freeseer/ + +@author: Thanh Ha +''' + +from PyQt4 import QtCore, QtGui + +from freeseer.frontend.qtcommon.dpi_adapt_qtgui import QGroupBoxWithDpi +from freeseer.frontend.qtcommon.dpi_adapt_qtgui import QWidgetWithDpi + + +class GeneralWidget(QWidgetWithDpi): + ''' + classdocs + ''' + + def __init__(self, parent=None): + ''' + Constructor + ''' + super(GeneralWidget, self).__init__(parent) + + self.mainLayout = QtGui.QVBoxLayout() + self.mainLayout.addStretch(0) + self.setLayout(self.mainLayout) + + fontSize = self.font().pixelSize() + fontUnit = "px" + if fontSize == -1: # Font is set as points, not pixels. + fontUnit = "pt" + fontSize = self.font().pointSize() + + boxStyle = "QGroupBox {{ font-weight: bold; font-size: {}{} }}".format(fontSize + 1, fontUnit) + BOX_WIDTH = 400 + BOX_HEIGHT = 60 + + # + # Heading + # + + self.title = QtGui.QLabel(u"{0} General {1}".format(u'

', u'

')) + self.mainLayout.insertWidget(0, self.title) + self.mainLayout.insertSpacerItem(1, QtGui.QSpacerItem(0, fontSize * 2)) + + # + # Language + # + + languageBoxLayout = QtGui.QVBoxLayout() + self.languageGroupBox = QGroupBoxWithDpi("Language") + self.languageGroupBox.setLayout(languageBoxLayout) + self.languageGroupBox.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + self.languageGroupBox.setFixedSize(BOX_WIDTH, BOX_HEIGHT) + self.languageGroupBox.setStyleSheet(boxStyle) + self.mainLayout.insertWidget(2, self.languageGroupBox) + + languageLayout = QtGui.QHBoxLayout() + languageBoxLayout.addLayout(languageLayout) + self.translateButton = QtGui.QPushButton("Help us translate") + self.languageComboBox = QtGui.QComboBox() + self.languageComboBox.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) + languageLayout.addWidget(self.languageComboBox, 2) + languageLayout.addSpacerItem(self.qspacer_item_with_dpi(40, 0)) + languageLayout.addWidget(self.translateButton, 1) + + # + # Appearance + # + + appearanceBoxLayout = QtGui.QVBoxLayout() + self.appearanceGroupBox = QGroupBoxWithDpi("Appearance") + self.appearanceGroupBox.setLayout(appearanceBoxLayout) + self.appearanceGroupBox.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + self.appearanceGroupBox.setFixedSize(BOX_WIDTH, BOX_HEIGHT) + self.appearanceGroupBox.setStyleSheet(boxStyle) + self.mainLayout.insertWidget(3, self.appearanceGroupBox) + + self.autoHideCheckBox = QtGui.QCheckBox("Auto-Hide to system tray on record") + appearanceBoxLayout.addWidget(self.autoHideCheckBox) + + # + # Reset + # + + resetBoxLayout = QtGui.QVBoxLayout() + self.resetGroupBox = QGroupBoxWithDpi("Reset") + self.resetGroupBox.setLayout(resetBoxLayout) + self.resetGroupBox.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + self.resetGroupBox.setFixedSize(BOX_WIDTH / 2, BOX_HEIGHT) + self.resetGroupBox.setStyleSheet(boxStyle) + self.mainLayout.addWidget(self.resetGroupBox) + self.mainLayout.addSpacerItem(self.qspacer_item_with_dpi(0, 20)) + + resetLayout = QtGui.QHBoxLayout() + resetBoxLayout.addLayout(resetLayout) + self.resetButton = QtGui.QPushButton("Reset settings to defaults") + resetLayout.addWidget(self.resetButton) + +if __name__ == "__main__": + import sys + app = QtGui.QApplication(sys.argv) + main = GeneralWidget() + main.show() + sys.exit(app.exec_()) diff --git a/src/freeseer/frontend/configtool/PluginWidget.py b/src/freeseer/frontend/configtool/PluginWidget.py index 4909d4c2..53235d22 100644 --- a/src/freeseer/frontend/configtool/PluginWidget.py +++ b/src/freeseer/frontend/configtool/PluginWidget.py @@ -89,7 +89,7 @@ def __init__(self, parent=None): def treeViewSelect(self): item = self.list.currentItem() key = str(item.text(0)) - if key in self.pluginMetadata.keys(): + if key in list(self.pluginMetadata.keys()): self.showDetails(key) else: self.hideDetails() diff --git a/src/freeseer/frontend/configtool/PluginWidget.py.bak b/src/freeseer/frontend/configtool/PluginWidget.py.bak new file mode 100644 index 00000000..4909d4c2 --- /dev/null +++ b/src/freeseer/frontend/configtool/PluginWidget.py.bak @@ -0,0 +1,151 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software + +# Copyright (C) 2014 Free and Open Source Software Learning Centre +# http://fosslc.org + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +''' +@author: Mia Kilborn +''' +from PyQt4.QtCore import QSize +from PyQt4.QtGui import QFont +from PyQt4.QtGui import QGroupBox +from PyQt4.QtGui import QLabel +from PyQt4.QtGui import QTreeWidget +from PyQt4.QtGui import QTreeWidgetItem +from PyQt4.QtGui import QVBoxLayout +from PyQt4.QtGui import QWidget + + +class PluginWidget(QWidget): + + def __init__(self, parent=None): + QWidget.__init__(self, parent) + + self.pluginMetadata = {} + + # Main layout + self.mainLayout = QVBoxLayout() + self.setLayout(self.mainLayout) + + # Define size used for the underlines + self.underlineSize = QSize() + self.underlineSize.setHeight(1) + + # Define font used for headers + self.font = QFont() + self.font.setPointSize(11) + self.font.setBold(True) + self.font.setUnderline(True) + + # Plugins Description + self.pluginDescription = QLabel() + self.pluginDescription.setText("Click a plugin to see more information." + + " Plugins can be configured from the Recording tab. \n") + self.pluginDescription.setWordWrap(True) + + # Plugins GroupBox + self.pluginLayout = QVBoxLayout() + self.pluginGroupBox = QGroupBox("Plugins extend the functionality of Freeseer") + self.pluginGroupBox.setLayout(self.pluginLayout) + self.pluginLayout.insertWidget(0, self.pluginDescription) + self.mainLayout.insertWidget(0, self.pluginGroupBox) + + # Plugins list + self.list = QTreeWidget() + self.list.setHeaderHidden(True) + self.list.headerItem().setText(0, "1") + self.pluginLayout.insertWidget(1, self.list) + + # Details + self.detailPane = QGroupBox() + self.detailLayout = QVBoxLayout() + self.detailPane.setLayout(self.detailLayout) + self.detailPaneDesc = QLabel() + self.detailPaneDesc.setWordWrap(True) + self.detailLayout.addWidget(self.detailPaneDesc) + self.pluginLayout.insertWidget(2, self.detailPane) + + self.list.itemSelectionChanged.connect(self.treeViewSelect) + + def treeViewSelect(self): + item = self.list.currentItem() + key = str(item.text(0)) + if key in self.pluginMetadata.keys(): + self.showDetails(key) + else: + self.hideDetails() + + def showDetails(self, key): + self.detailPane.setTitle(key) + self.detailPaneDesc.setText(self.pluginMetadata[key]) + self.detailPane.show() + + def hideDetails(self): + self.detailPane.hide() + + def getWidgetPlugin(self, plugin, plugin_category, plugman): + plugin_name = plugin.plugin_object.get_name() + item = QTreeWidgetItem() + + # Display Plugin's meta data in a tooltip + pluginDetails = """ + + + + + + + + + + + + + + + + + + + + +
Name: %(name)s
Version: %(version)s
Author: %(author)s
Website: %(website)s
Description: %(description)s
+ """ % {"name": plugin.name, + "version": plugin.version, + "author": plugin.author, + "website": plugin.website, + "description": plugin.description} + + # put the details in the hash table + self.pluginMetadata[plugin_name] = pluginDetails + + item.setText(0, plugin_name) + return item + + +if __name__ == "__main__": + import sys + from PyQt4.QtGui import QApplication + app = QApplication(sys.argv) + main = PluginWidget() + main.show() + sys.exit(app.exec_()) diff --git a/src/freeseer/frontend/configtool/configtool.py b/src/freeseer/frontend/configtool/configtool.py index 58b8df0f..310ab2fc 100644 --- a/src/freeseer/frontend/configtool/configtool.py +++ b/src/freeseer/frontend/configtool/configtool.py @@ -225,7 +225,7 @@ def retranslate(self): # # AV Widget # - self.avWidget.title.setText(u"{0} {1} {2}".format(u'

', self.app.translate("ConfigToolApp", "Recording"), u'

')) + self.avWidget.title.setText("{0} {1} {2}".format('

', self.app.translate("ConfigToolApp", "Recording"), '

')) self.avWidget.audioGroupBox.setTitle(self.app.translate("ConfigToolApp", "Audio Input")) self.avWidget.audioMixerLabel.setText(self.app.translate("ConfigToolApp", "Audio Mixer")) @@ -237,7 +237,7 @@ def retranslate(self): self.avWidget.fileGroupBox.setTitle(self.app.translate("ConfigToolApp", "Record to File")) self.avWidget.fileDirLabel.setText(self.app.translate("ConfigToolApp", "Record Directory")) - self.avWidget.fileDirPushButton.setText(u"{}...".format(self.app.translate("ConfigToolApp", "Browse"))) + self.avWidget.fileDirPushButton.setText("{}...".format(self.app.translate("ConfigToolApp", "Browse"))) self.avWidget.fileLabel.setText(self.app.translate("ConfigToolApp", "File Format")) self.avWidget.fileSetupPushButton.setToolTip(self.app.translate("ConfigToolApp", "Setup")) @@ -496,13 +496,13 @@ def setup_audio_quality(self): if file_configurable: file_config_layout = file_output_plugin.plugin_object.get_audio_quality_layout() - self.audio_quality_dialog_layout.addWidget(QtGui.QLabel(u'File Output'), 0, 0, 1, 2, QtCore.Qt.AlignHCenter) + self.audio_quality_dialog_layout.addWidget(QtGui.QLabel('File Output'), 0, 0, 1, 2, QtCore.Qt.AlignHCenter) self.audio_quality_dialog_layout.addLayout(file_config_layout, 1, 0) if stream_configurable: stream_config_layout = stream_output_plugin.plugin_object.get_audio_quality_layout() column_count = self.audio_quality_dialog_layout.columnCount() - self.audio_quality_dialog_layout.addWidget(QtGui.QLabel(u'Stream Output'), 0, column_count, 1, 2, QtCore.Qt.AlignHCenter) + self.audio_quality_dialog_layout.addWidget(QtGui.QLabel('Stream Output'), 0, column_count, 1, 2, QtCore.Qt.AlignHCenter) self.audio_quality_dialog_layout.addLayout(stream_config_layout, 1, column_count) self.audio_quality_dialog_layout.addWidget(self.audio_quality_dialog.closeButton, 2, 0, 1, self.audio_quality_dialog_layout.columnCount()) @@ -561,13 +561,13 @@ def setup_video_quality(self): if file_configurable: file_config_layout = file_output_plugin.plugin_object.get_video_quality_layout() - self.video_quality_dialog_layout.addWidget(QtGui.QLabel(u'File Output'), 0, 0, 1, 2, QtCore.Qt.AlignHCenter) + self.video_quality_dialog_layout.addWidget(QtGui.QLabel('File Output'), 0, 0, 1, 2, QtCore.Qt.AlignHCenter) self.video_quality_dialog_layout.addLayout(file_config_layout, 1, 0) if stream_configurable: stream_config_layout = stream_output_plugin.plugin_object.get_video_quality_layout() column_count = self.video_quality_dialog_layout.columnCount() - self.video_quality_dialog_layout.addWidget(QtGui.QLabel(u'Stream Output'), 0, column_count, 1, 2, QtCore.Qt.AlignHCenter) + self.video_quality_dialog_layout.addWidget(QtGui.QLabel('Stream Output'), 0, column_count, 1, 2, QtCore.Qt.AlignHCenter) self.video_quality_dialog_layout.addLayout(stream_config_layout, 1, column_count) self.video_quality_dialog_layout.addWidget(self.video_quality_dialog.closeButton, 2, 0, 1, self.video_quality_dialog_layout.columnCount()) @@ -750,7 +750,7 @@ def show_plugin_widget_dialog(self, widget, name): self.dialog.closeButton = QtGui.QPushButton("Close") self.dialog_layout.addWidget(self.dialog.closeButton) self.connect(self.dialog.closeButton, QtCore.SIGNAL('clicked()'), self.dialog.close) - self.dialog.setWindowTitle(u'{} Setup'.format(name)) + self.dialog.setWindowTitle('{} Setup'.format(name)) self.dialog.setModal(True) self.dialog.show() diff --git a/src/freeseer/frontend/configtool/configtool.py.bak b/src/freeseer/frontend/configtool/configtool.py.bak new file mode 100644 index 00000000..58b8df0f --- /dev/null +++ b/src/freeseer/frontend/configtool/configtool.py.bak @@ -0,0 +1,760 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2014 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +import logging +import os +import re + +from PyQt4 import QtGui +from PyQt4 import QtCore +from PyQt4.QtGui import QInputDialog +from PyQt4.QtGui import QLineEdit +from PyQt4.QtGui import QMessageBox + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +from freeseer.framework.multimedia import Quality +from freeseer.framework.plugin import PluginManager, IOutput +from freeseer.frontend.qtcommon.FreeseerApp import FreeseerApp + +from freeseer.frontend.qtcommon.AboutWidget import AboutWidget +from freeseer.frontend.configtool.AVWidget import AVWidget +from freeseer.frontend.configtool.ConfigToolWidget import ConfigToolWidget +from freeseer.frontend.configtool.GeneralWidget import GeneralWidget +from freeseer.frontend.configtool.PluginWidget import PluginWidget + +log = logging.getLogger(__name__) + + +class ConfigToolApp(FreeseerApp): + ''' + ConfigTool is used to tune settings used by the Freeseer Application + ''' + + def __init__(self, profile, config): + super(ConfigToolApp, self).__init__(config) + + # Load Config Stuff + self.profile = profile + + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap(_fromUtf8(":/freeseer/logo.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.setWindowIcon(icon) + + # Initialize geometry, to be used for restoring window positioning. + self.geometry = None + + self.dialog = None + self.audio_quality_layout = None + self.video_quality_layout = None + + self.mainWidget = ConfigToolWidget() + self.setCentralWidget(self.mainWidget) + + self.currentWidget = None + self.mainWidgetLayout = QtGui.QVBoxLayout() + self.mainWidget.rightPanelWidget.setLayout(self.mainWidgetLayout) + + # Load all ConfigTool Widgets + self.aboutWidget = AboutWidget() + self.generalWidget = GeneralWidget() + self.avWidget = AVWidget() + self.pluginWidget = PluginWidget() + + self.plugman = PluginManager(profile) + + # XXX: Nasty hack to let our singleton plugins access the parent window + # for retranslate. + for plugin in self.plugman.get_all_plugins(): + plugin.plugin_object.set_gui(self) + + # Custom Menu Items + self.actionSaveProfile = QtGui.QAction(self) + self.menuFile.insertAction(self.actionExit, self.actionSaveProfile) + + # + # --- Language Related + # + # Fill in the langauges combobox and load the default language + for language in self.languages: + translator = QtCore.QTranslator() # Create a translator to translate Language Display Text + translator.load(":/languages/%s" % language) + language_display_text = translator.translate("Translation", "Language Display Text") + self.generalWidget.languageComboBox.addItem(language_display_text, language) + + # Load default language. + actions = self.menuLanguage.actions() + for action in actions: + if action.data().toString() == self.config.default_language: + action.setChecked(True) + self.translate(action) + break + # --- End Language Related + + # connections + self.connect(self.actionSaveProfile, QtCore.SIGNAL('triggered()'), self.show_save_profile_dialog) + self.connect(self.mainWidget.closePushButton, QtCore.SIGNAL('clicked()'), self.close) + self.connect(self.mainWidget.optionsTreeWidget, QtCore.SIGNAL('itemSelectionChanged()'), self.change_option) + + # + # general tab connections + # + self.connect(self.generalWidget.languageComboBox, QtCore.SIGNAL('currentIndexChanged(int)'), self.set_default_language) + self.connect(self.generalWidget.autoHideCheckBox, QtCore.SIGNAL('toggled(bool)'), self.toggle_autohide) + self.connect(self.generalWidget.translateButton, QtCore.SIGNAL('clicked()'), self.open_translate_url) + self.connect(self.generalWidget.resetButton, QtCore.SIGNAL('clicked()'), self.confirm_reset) + # + # AV tab connections + # + self.connect(self.avWidget.audioGroupBox, QtCore.SIGNAL('toggled(bool)'), self.toggle_audiomixer_state) + self.connect(self.avWidget.audioMixerComboBox, QtCore.SIGNAL('activated(const QString&)'), self.change_audiomixer) + self.connect(self.avWidget.audioMixerSetupPushButton, QtCore.SIGNAL('clicked()'), self.setup_audio_mixer) + self.connect(self.avWidget.audioQualityComboBox, QtCore.SIGNAL('currentIndexChanged(int)'), self.change_audio_quality) + self.connect(self.avWidget.audioQualitySetupPushButton, QtCore.SIGNAL('clicked()'), self.setup_audio_quality) + self.connect(self.avWidget.videoGroupBox, QtCore.SIGNAL('toggled(bool)'), self.toggle_videomixer_state) + self.connect(self.avWidget.videoMixerComboBox, QtCore.SIGNAL('activated(const QString&)'), self.change_videomixer) + self.connect(self.avWidget.videoMixerSetupPushButton, QtCore.SIGNAL('clicked()'), self.setup_video_mixer) + self.connect(self.avWidget.videoQualityComboBox, QtCore.SIGNAL('currentIndexChanged(int)'), self.change_video_quality) + self.connect(self.avWidget.videoQualitySetupPushButton, QtCore.SIGNAL('clicked()'), self.setup_video_quality) + self.connect(self.avWidget.fileGroupBox, QtCore.SIGNAL('toggled(bool)'), self.toggle_record_to_file) + self.connect(self.avWidget.fileDirPushButton, QtCore.SIGNAL('clicked()'), self.browse_video_directory) + self.connect(self.avWidget.fileDirLineEdit, QtCore.SIGNAL('editingFinished()'), self.update_record_directory) + self.connect(self.avWidget.fileComboBox, QtCore.SIGNAL('activated(const QString&)'), self.change_file_format) + self.connect(self.avWidget.fileSetupPushButton, QtCore.SIGNAL('clicked()'), self.setup_file_format) + self.connect(self.avWidget.streamGroupBox, QtCore.SIGNAL('toggled(bool)'), self.toggle_record_to_stream) + self.connect(self.avWidget.streamComboBox, QtCore.SIGNAL('activated(const QString&)'), self.change_stream_format) + self.connect(self.avWidget.streamSetupPushButton, QtCore.SIGNAL('clicked()'), self.setup_stream_format) + # GUI Disabling/Enabling Connections + self.connect(self.avWidget.audioGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.audioMixerLabel.setEnabled) + self.connect(self.avWidget.audioGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.audioMixerComboBox.setEnabled) + self.connect(self.avWidget.audioGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.audioMixerSetupPushButton.setEnabled) + self.connect(self.avWidget.videoGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.videoMixerLabel.setEnabled) + self.connect(self.avWidget.videoGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.videoMixerComboBox.setEnabled) + self.connect(self.avWidget.videoGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.videoMixerSetupPushButton.setEnabled) + self.connect(self.avWidget.fileGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.fileLabel.setEnabled) + self.connect(self.avWidget.fileGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.fileComboBox.setEnabled) + self.connect(self.avWidget.fileGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.fileSetupPushButton.setEnabled) + self.connect(self.avWidget.streamGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.streamLabel.setEnabled) + self.connect(self.avWidget.streamGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.streamComboBox.setEnabled) + self.connect(self.avWidget.streamGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.streamSetupPushButton.setEnabled) + + self.retranslate() + + # Start off with displaying the General Settings + items = self.mainWidget.optionsTreeWidget.findItems(self.generalString, QtCore.Qt.MatchExactly) + if len(items) > 0: + item = items[0] + self.mainWidget.optionsTreeWidget.setCurrentItem(item) + + ### + ### Translation + ### + + def retranslate(self): + self.setWindowTitle(self.app.translate("ConfigToolApp", "Freeseer ConfigTool")) + + # + # Reusable Strings + # + self.confirmResetDefaultsTitleString = self.app.translate("ConfigToolApp", "Freeseer") + self.confirmResetDefaultsQuestionString = self.app.translate( + "ConfigToolApp", + "Your Freeseer settings will be restored to their original defaults.") + # --- End Reusable Strings + + # + # Menu + # + self.saveProfileString = self.actionSaveProfile.setText(self.app.translate("ConfigToolApp", "Save Profile")) + + # + # ConfigToolWidget + # + self.aboutString = self.app.translate("ConfigToolApp", "About") + self.generalString = self.app.translate("ConfigToolApp", "General") + self.avString = self.app.translate("ConfigToolApp", "Recording") + self.pluginsString = self.app.translate("ConfigToolApp", "Plugins") + self.audioInputString = self.app.translate("ConfigToolApp", "AudioInput") + self.audioMixerString = self.app.translate("ConfigToolApp", "AudioMixer") + self.videoInputString = self.app.translate("ConfigToolApp", "VideoInput") + self.videoMixerString = self.app.translate("ConfigToolApp", "VideoMixer") + self.outputString = self.app.translate("ConfigToolApp", "Output") + + self.mainWidget.optionsTreeWidget.topLevelItem(0).setText(0, self.generalString) + self.mainWidget.optionsTreeWidget.topLevelItem(1).setText(0, self.avString) + self.mainWidget.optionsTreeWidget.topLevelItem(2).setText(0, self.pluginsString) + self.mainWidget.optionsTreeWidget.topLevelItem(3).setText(0, self.aboutString) + self.mainWidget.closePushButton.setText(self.app.translate("ConfigToolApp", "Close")) + # --- End ConfigToolWidget + + # + # GeneralWidget + # + self.generalWidget.languageGroupBox.setTitle(self.app.translate("ConfigToolApp", "Language")) + self.generalWidget.translateButton.setText(self.app.translate("ConfigToolApp", "Help us translate")) + self.generalWidget.appearanceGroupBox.setTitle(self.app.translate("ConfigToolApp", "Appearance")) + self.generalWidget.autoHideCheckBox.setText(self.app.translate("ConfigToolApp", "Auto-Hide to system tray on record")) + self.generalWidget.resetGroupBox.setTitle(self.app.translate("ConfigToolApp", "Reset")) + self.generalWidget.resetButton.setText(self.app.translate("ConfigToolApp", "Reset settings to defaults")) + # --- End GeneralWidget + + # + # AV Widget + # + self.avWidget.title.setText(u"{0} {1} {2}".format(u'

', self.app.translate("ConfigToolApp", "Recording"), u'

')) + + self.avWidget.audioGroupBox.setTitle(self.app.translate("ConfigToolApp", "Audio Input")) + self.avWidget.audioMixerLabel.setText(self.app.translate("ConfigToolApp", "Audio Mixer")) + self.avWidget.audioMixerSetupPushButton.setToolTip(self.app.translate("ConfigToolApp", "Setup")) + + self.avWidget.videoGroupBox.setTitle(self.app.translate("ConfigToolApp", "Video Input")) + self.avWidget.videoMixerLabel.setText(self.app.translate("ConfigToolApp", "Video Mixer")) + self.avWidget.videoMixerSetupPushButton.setToolTip(self.app.translate("ConfigToolApp", "Setup")) + + self.avWidget.fileGroupBox.setTitle(self.app.translate("ConfigToolApp", "Record to File")) + self.avWidget.fileDirLabel.setText(self.app.translate("ConfigToolApp", "Record Directory")) + self.avWidget.fileDirPushButton.setText(u"{}...".format(self.app.translate("ConfigToolApp", "Browse"))) + self.avWidget.fileLabel.setText(self.app.translate("ConfigToolApp", "File Format")) + self.avWidget.fileSetupPushButton.setToolTip(self.app.translate("ConfigToolApp", "Setup")) + + self.avWidget.streamGroupBox.setTitle(self.app.translate("ConfigToolApp", "Record to Stream")) + self.avWidget.streamLabel.setText(self.app.translate("ConfigToolApp", "Stream Format")) + self.avWidget.streamSetupPushButton.setToolTip(self.app.translate("ConfigToolApp", "Setup")) + # --- End AV Widget + + ### + ### Menu + ### + + def show_save_profile_dialog(self): + name, ok = QInputDialog().getText(self, "Save Profile", "Profile Name", QLineEdit.Normal) + + if ok: + if re.match('^[\w-]+$', name): + # TODO: This is a hack. Instead, there should be a option to + # copy the current profile or something. + pass + else: + QMessageBox.information(None, "Invalid name", "Invalid characters used. Only alphanumeric and dashes allowed.") + + ### + ### General + ### + + def change_option(self): + option = self.mainWidget.optionsTreeWidget.currentItem().text(0) + + if self.currentWidget is not None: + self.mainWidgetLayout.removeWidget(self.currentWidget) + self.currentWidget.hide() + + if option == self.aboutString: + self.load_about_widget() + elif option == self.generalString: + self.load_general_widget() + elif option == self.avString: + self.load_av_widget() + elif option == self.pluginsString: + self.load_plugins_widget() + else: + pass + + def load_about_widget(self): + """Loads AboutWidget onto the configuration tool""" + self.mainWidgetLayout.addWidget(self.aboutWidget) + self.currentWidget = self.aboutWidget + self.currentWidget.retranslate() + self.currentWidget.show() + + def load_general_widget(self): + self.mainWidgetLayout.addWidget(self.generalWidget) + self.currentWidget = self.generalWidget + self.currentWidget.show() + + # Load default language + i = self.generalWidget.languageComboBox.findData(self.config.default_language) + self.generalWidget.languageComboBox.setCurrentIndex(i) + + # Load Auto Hide Settings + if self.config.auto_hide: + self.generalWidget.autoHideCheckBox.setChecked(True) + else: + self.generalWidget.autoHideCheckBox.setChecked(False) + + def set_default_language(self, language): + language_file = str(self.generalWidget.languageComboBox.itemData(language).toString()) + self.config.default_language = language_file + self.config.save() + + def open_translate_url(self): + url = QtCore.QUrl("http://freeseer.readthedocs.org/en/latest/contribute/translation.html") + QtGui.QDesktopServices.openUrl(url) + + def confirm_reset(self): + """Presents a confirmation dialog to ask the user if they are sure they wish to reset all settings. + If confirmed, reset the settings in this profile to default + """ + confirm = QMessageBox.question(self, + self.confirmResetDefaultsTitleString, + self.confirmResetDefaultsQuestionString, + QMessageBox.Reset | QMessageBox.Cancel, + QMessageBox.Cancel) + + if confirm == QMessageBox.Reset: + self.config.set_defaults() + self.config.save() + self.load_general_widget() + + def toggle_autohide(self, state): + self.config.auto_hide = state + self.config.save() + + # Make recordapp to update it's config + # TODO: Surely there is a better way to do this + + ### + ### AV Related + ### + + def load_av_widget(self): + self.mainWidgetLayout.addWidget(self.avWidget) + self.currentWidget = self.avWidget + self.currentWidget.show() + + # + # Set up Quality + # + self.supports_video_quality = self.plugman.get_plugin_by_name(self.config.videomixer, "VideoMixer").plugin_object.supports_video_quality() + self.file_configurable = self.plugman.get_plugin_by_name(self.config.record_to_file_plugin, "Output").plugin_object.configurable + self.stream_configurable = self.plugman.get_plugin_by_name(self.config.record_to_stream_plugin, "Output").plugin_object.configurable + + self.refresh_quality_config() + self.avWidget.videoQualityComboBox.setCurrentIndex(self.config.video_quality) + self.avWidget.audioQualityComboBox.setCurrentIndex(self.config.audio_quality) + + if not self.supports_video_quality: + self.toggle_video_quality(False) + + # + # Set up Audio + # + if self.config.enable_audio_recording: + self.avWidget.audioGroupBox.setChecked(True) + else: + self.avWidget.audioGroupBox.setChecked(False) + self.avWidget.audioMixerComboBox.setEnabled(False) + self.avWidget.audioMixerSetupPushButton.setEnabled(False) + + n = 0 # Counter for finding Audio Mixer to set as current. + self.avWidget.audioMixerComboBox.clear() + plugins = self.plugman.get_audiomixer_plugins() + for plugin in plugins: + self.avWidget.audioMixerComboBox.addItem(plugin.plugin_object.get_name()) + if plugin.plugin_object.get_name() == self.config.audiomixer: + self.avWidget.audioMixerComboBox.setCurrentIndex(n) + n += 1 + + # + # Set up Video + # + if self.config.enable_video_recording: + self.avWidget.videoGroupBox.setChecked(True) + else: + self.avWidget.videoGroupBox.setChecked(False) + self.avWidget.videoMixerComboBox.setEnabled(False) + self.avWidget.videoMixerSetupPushButton.setEnabled(False) + + n = 0 # Counter for finding Video Mixer to set as current. + self.avWidget.videoMixerComboBox.clear() + plugins = self.plugman.get_videomixer_plugins() + for plugin in plugins: + self.avWidget.videoMixerComboBox.addItem(plugin.plugin_object.get_name()) + if plugin.plugin_object.get_name() == self.config.videomixer: + self.avWidget.videoMixerComboBox.setCurrentIndex(n) + n += 1 + + # Recording Directory Settings + self.avWidget.fileDirLineEdit.setText(self.config.videodir) + + # + # Set up File Format + # + if self.config.record_to_file: + self.avWidget.fileGroupBox.setChecked(True) + else: + self.avWidget.fileGroupBox.setChecked(False) + self.avWidget.fileComboBox.setEnabled(False) + self.avWidget.fileSetupPushButton.setEnabled(False) + + if not self.file_configurable: + self.avWidget.fileSetupPushButton.setEnabled(False) + + n = 0 # Counter for finding File Format to set as current + self.avWidget.fileComboBox.clear() + plugins = self.plugman.get_output_plugins() + for plugin in plugins: + if plugin.plugin_object.get_recordto() == IOutput.FILE: + self.avWidget.fileComboBox.addItem(plugin.plugin_object.get_name()) + if plugin.plugin_object.get_name() == self.config.record_to_file_plugin: + self.avWidget.fileComboBox.setCurrentIndex(n) + n += 1 + + # + # Set up Stream Format + # + if self.config.record_to_stream: + self.avWidget.streamGroupBox.setChecked(True) + else: + self.avWidget.streamGroupBox.setChecked(False) + self.avWidget.streamComboBox.setEnabled(False) + self.avWidget.streamSetupPushButton.setEnabled(False) + + if not self.stream_configurable: + self.avWidget.streamSetupPushButton.setEnabled(False) + + n = 0 # Counter for finding Stream Format to set as current + self.avWidget.streamComboBox.clear() + plugins = self.plugman.get_output_plugins() + for plugin in plugins: + if plugin.plugin_object.get_recordto() == IOutput.STREAM: + self.avWidget.streamComboBox.addItem(plugin.plugin_object.get_name()) + if plugin.plugin_object.get_name() == self.config.record_to_stream_plugin: + self.avWidget.streamComboBox.setCurrentIndex(n) + n += 1 + + def toggle_audiomixer_state(self, state): + self.config.enable_audio_recording = state + self.config.save() + + self.refresh_quality_config() + + def change_audiomixer(self, audiomixer): + self.config.audiomixer = audiomixer + self.config.save() + + def setup_audio_mixer(self): + mixer = str(self.avWidget.audioMixerComboBox.currentText()) + plugin = self.plugman.get_plugin_by_name(mixer, "AudioMixer") + plugin.plugin_object.get_dialog() + + def change_audio_quality(self, index): + """Used to change audio quality of output. + + If the quality is set to 'Custom' then the setup button for quality is enabled otherwise it is disabled. + """ + self.config.audio_quality = index + self.config.save() + + if self.config.audio_quality == Quality.CUSTOM: + self.avWidget.audioQualitySetupPushButton.setEnabled(True) + else: + self.avWidget.audioQualitySetupPushButton.setEnabled(False) + + def setup_audio_quality(self): + """Creates dialog to configure audio quality when quality is set to Custom""" + self.audio_quality_dialog = QtGui.QDialog(self) + + self.audio_quality_dialog_layout = QtGui.QGridLayout() + self.audio_quality_dialog_layout.setSizeConstraint(QtGui.QLayout.SetFixedSize) + self.audio_quality_dialog.setLayout(self.audio_quality_dialog_layout) + + self.audio_quality_dialog.closeButton = QtGui.QPushButton("Close") + self.connect(self.audio_quality_dialog.closeButton, QtCore.SIGNAL('clicked()'), self.audio_quality_dialog.close) + self.audio_quality_dialog.setWindowTitle("Audio Quality Setup") + self.audio_quality_dialog.setModal(True) + self.audio_quality_dialog.show() + + file_output_plugin = self.plugman.get_plugin_by_name(self.config.record_to_file_plugin, "Output") + stream_output_plugin = self.plugman.get_plugin_by_name(self.config.record_to_stream_plugin, "Output") + + file_configurable = file_output_plugin.plugin_object.configurable + stream_configurable = stream_output_plugin.plugin_object.configurable + + if file_configurable: + file_config_layout = file_output_plugin.plugin_object.get_audio_quality_layout() + self.audio_quality_dialog_layout.addWidget(QtGui.QLabel(u'File Output'), 0, 0, 1, 2, QtCore.Qt.AlignHCenter) + self.audio_quality_dialog_layout.addLayout(file_config_layout, 1, 0) + + if stream_configurable: + stream_config_layout = stream_output_plugin.plugin_object.get_audio_quality_layout() + column_count = self.audio_quality_dialog_layout.columnCount() + self.audio_quality_dialog_layout.addWidget(QtGui.QLabel(u'Stream Output'), 0, column_count, 1, 2, QtCore.Qt.AlignHCenter) + self.audio_quality_dialog_layout.addLayout(stream_config_layout, 1, column_count) + + self.audio_quality_dialog_layout.addWidget(self.audio_quality_dialog.closeButton, 2, 0, 1, self.audio_quality_dialog_layout.columnCount()) + + file_config_layout.itemAt(1).widget().setEnabled(self.config.record_to_file) + stream_config_layout.itemAt(1).widget().setEnabled(self.config.record_to_stream) + + def toggle_videomixer_state(self, state): + self.config.enable_video_recording = state + self.config.save() + + self.refresh_quality_config() + + def change_videomixer(self, videomixer): + self.config.videomixer = videomixer + self.config.save() + + def setup_video_mixer(self): + mixer = str(self.avWidget.videoMixerComboBox.currentText()) + plugin = self.plugman.get_plugin_by_name(mixer, "VideoMixer") + plugin.plugin_object.get_dialog() + + def change_video_quality(self, index): + """Used to change video quality of output. + + If the quality is set to 'Custom' then the setup button for quality is enabled otherwise it is disabled. + Calculations are done by multiplying a constant factor by the total number of pixels in the output. + """ + self.config.video_quality = index + self.config.save() + + if self.config.video_quality == Quality.CUSTOM: + self.avWidget.videoQualitySetupPushButton.setEnabled(True) + else: + self.avWidget.videoQualitySetupPushButton.setEnabled(False) + + def setup_video_quality(self): + """Creates dialog to configure video bitrate when quality is set to Custom""" + self.video_quality_dialog = QtGui.QDialog(self) + + self.video_quality_dialog_layout = QtGui.QGridLayout() + self.video_quality_dialog_layout.setSizeConstraint(QtGui.QLayout.SetFixedSize) + self.video_quality_dialog.setLayout(self.video_quality_dialog_layout) + + self.video_quality_dialog.closeButton = QtGui.QPushButton("Close") + self.connect(self.video_quality_dialog.closeButton, QtCore.SIGNAL('clicked()'), self.video_quality_dialog.close) + self.video_quality_dialog.setWindowTitle("Video Quality Setup") + self.video_quality_dialog.setModal(True) + self.video_quality_dialog.show() + + file_output_plugin = self.plugman.get_plugin_by_name(self.config.record_to_file_plugin, "Output") + stream_output_plugin = self.plugman.get_plugin_by_name(self.config.record_to_stream_plugin, "Output") + + file_configurable = file_output_plugin.plugin_object.configurable + stream_configurable = stream_output_plugin.plugin_object.configurable + + if file_configurable: + file_config_layout = file_output_plugin.plugin_object.get_video_quality_layout() + self.video_quality_dialog_layout.addWidget(QtGui.QLabel(u'File Output'), 0, 0, 1, 2, QtCore.Qt.AlignHCenter) + self.video_quality_dialog_layout.addLayout(file_config_layout, 1, 0) + + if stream_configurable: + stream_config_layout = stream_output_plugin.plugin_object.get_video_quality_layout() + column_count = self.video_quality_dialog_layout.columnCount() + self.video_quality_dialog_layout.addWidget(QtGui.QLabel(u'Stream Output'), 0, column_count, 1, 2, QtCore.Qt.AlignHCenter) + self.video_quality_dialog_layout.addLayout(stream_config_layout, 1, column_count) + + self.video_quality_dialog_layout.addWidget(self.video_quality_dialog.closeButton, 2, 0, 1, self.video_quality_dialog_layout.columnCount()) + + file_config_layout.itemAt(1).widget().setEnabled(self.config.record_to_file) + stream_config_layout.itemAt(1).widget().setEnabled(self.config.record_to_stream) + + def toggle_video_quality(self, enabled): + """Used by Video Mixer to disable quality options if video input is selected that does not support it.""" + self.supports_video_quality = enabled + self.avWidget.videoQualityComboBox.setEnabled(enabled) + + if enabled: + self.avWidget.videoQualityComboBox.setCurrentIndex(Quality.HIGH) + else: + self.avWidget.videoQualityComboBox.setCurrentIndex(Quality.CUSTOM) + + self.avWidget.videoQualitySetupPushButton.setEnabled(not enabled) + + def refresh_quality_config(self): + """Enable or disable quality options based on various variables""" + enabled = (self.config.record_to_file and self.file_configurable) or (self.config.record_to_stream and self.stream_configurable) + audio_enabled = self.config.enable_audio_recording and enabled + audio_configurable = self.config.audio_quality == Quality.CUSTOM + video_enabled = self.config.enable_video_recording and enabled and self.supports_video_quality + video_configurable = self.config.video_quality == Quality.CUSTOM + + self.avWidget.audioQualitySetupPushButton.setEnabled(audio_enabled and audio_configurable) + self.avWidget.audioQualityComboBox.setEnabled(audio_enabled) + self.avWidget.videoQualitySetupPushButton.setEnabled(video_enabled and video_configurable) + self.avWidget.videoQualityComboBox.setEnabled(video_enabled) + + def toggle_record_to_file(self, state): + self.config.record_to_file = state + self.config.save() + + self.refresh_quality_config() + + def browse_video_directory(self): + directory = self.avWidget.fileDirLineEdit.text() + + new_dir = QtGui.QFileDialog.getExistingDirectory(self, "Select Video Directory", directory) + if not new_dir: + new_dir = directory + + videodir = os.path.abspath(str(new_dir)) + self.avWidget.fileDirLineEdit.setText(videodir) + self.avWidget.fileDirLineEdit.emit(QtCore.SIGNAL("editingFinished()")) + + def update_record_directory(self): + self.config.videodir = str(self.avWidget.fileDirLineEdit.text()) + self.config.save() + + def change_file_format(self, format): + self.config.record_to_file_plugin = format + self.config.save() + + self.file_configurable = self.plugman.get_plugin_by_name(self.config.record_to_file_plugin, "Output").plugin_object.configurable + self.stream_configurable = self.plugman.get_plugin_by_name(self.config.record_to_stream_plugin, "Output").plugin_object.configurable + + self.avWidget.fileSetupPushButton.setEnabled(self.file_configurable) + + self.refresh_quality_config() + + def setup_file_format(self): + output = str(self.avWidget.fileComboBox.currentText()) + plugin = self.plugman.get_plugin_by_name(output, "Output") + plugin.plugin_object.get_dialog() + + def toggle_record_to_stream(self, state): + self.config.record_to_stream = state + self.config.save() + + self.refresh_quality_config() + + def change_stream_format(self, format): + self.config.record_to_stream_plugin = format + self.config.save() + + self.file_configurable = self.plugman.get_plugin_by_name(self.config.record_to_file_plugin, "Output").plugin_object.configurable + self.stream_configurable = self.plugman.get_plugin_by_name(self.config.record_to_stream_plugin, "Output").plugin_object.configurable + + self.avWidget.streamSetupPushButton.setEnabled(self.stream_configurable) + + self.refresh_quality_config() + + def setup_stream_format(self): + output = str(self.avWidget.streamComboBox.currentText()) + plugin = self.plugman.get_plugin_by_name(output, "Output") + plugin.plugin_object.get_dialog() + + ### + ### Plugin Loader Related + ### + + def load_plugins_widget(self): + self.mainWidgetLayout.addWidget(self.pluginWidget) + self.currentWidget = self.pluginWidget + self.currentWidget.show() + + if (self.currentWidget.list.topLevelItem(0) is None): + # Fill List + + # Audio Input Label + QtGui.QTreeWidgetItem(self.currentWidget.list) + self.currentWidget.list.topLevelItem(0).setText(0, "Audio Input") + self.add_plugins_to_list("AudioInput", self.currentWidget.list.topLevelItem(0)) + + # Audio Mixer Label + QtGui.QTreeWidgetItem(self.currentWidget.list) + self.currentWidget.list.topLevelItem(1).setText(0, "Audio Mixer") + self.add_plugins_to_list("AudioMixer", self.currentWidget.list.topLevelItem(1)) + + # Video Input Label + QtGui.QTreeWidgetItem(self.currentWidget.list) + self.currentWidget.list.topLevelItem(2).setText(0, "Video Input") + self.add_plugins_to_list("VideoInput", self.currentWidget.list.topLevelItem(2)) + + # Video Mixer Label + QtGui.QTreeWidgetItem(self.currentWidget.list) + self.currentWidget.list.topLevelItem(3).setText(0, "Video Mixer") + self.add_plugins_to_list("VideoMixer", self.currentWidget.list.topLevelItem(3)) + + # Output Label + QtGui.QTreeWidgetItem(self.currentWidget.list) + self.currentWidget.list.topLevelItem(4).setText(0, "Output") + self.add_plugins_to_list("Output", self.currentWidget.list.topLevelItem(4)) + + # Importer Label + QtGui.QTreeWidgetItem(self.currentWidget.list) + self.currentWidget.list.topLevelItem(5).setText(0, "Input") + self.add_plugins_to_list("Importer", self.currentWidget.list.topLevelItem(5)) + + self.currentWidget.list.expandAll() + + def add_plugins_to_list(self, plugin_type, parent): + plugins = self.get_plugins(plugin_type) + + for i, plugin in enumerate(plugins): + newItem = self.pluginWidget.getWidgetPlugin(plugin, plugin_type, self.plugman) + parent.addChild(newItem) + + def get_plugins(self, plugin_type): + """ + Returns a list of plugins of type + + Parameters: + plugin_type - type of plugins to get + + Returns: + list of plugins of type specified + """ + plugins = [] + + if plugin_type == "AudioInput": + plugins = self.plugman.get_audioinput_plugins() + elif plugin_type == "AudioMixer": + plugins = self.plugman.get_audiomixer_plugins() + elif plugin_type == "VideoInput": + plugins = self.plugman.get_videoinput_plugins() + elif plugin_type == "VideoMixer": + plugins = self.plugman.get_videomixer_plugins() + elif plugin_type == "Output": + plugins = self.plugman.get_output_plugins() + elif plugin_type == "Importer": + plugins = self.plugman.get_importer_plugins() + + return plugins + + def show_plugin_widget_dialog(self, widget, name): + """Shows the configuration dialog for a plugin.""" + self.last_dialog = self.dialog + self.dialog = QtGui.QDialog(self) + + self.dialog_layout = QtGui.QVBoxLayout() + self.dialog_layout.setSizeConstraint(QtGui.QLayout.SetFixedSize) + self.dialog.setLayout(self.dialog_layout) + self.dialog_layout.addWidget(widget) + + self.dialog.closeButton = QtGui.QPushButton("Close") + self.dialog_layout.addWidget(self.dialog.closeButton) + self.connect(self.dialog.closeButton, QtCore.SIGNAL('clicked()'), self.dialog.close) + self.dialog.setWindowTitle(u'{} Setup'.format(name)) + self.dialog.setModal(True) + self.dialog.show() + + def closeEvent(self, event): + log.info('Exiting configtool...') + self.geometry = self.saveGeometry() + event.accept() diff --git a/src/freeseer/frontend/controller/recording.py b/src/freeseer/frontend/controller/recording.py index 4c2e9eb3..af6ecfcd 100644 --- a/src/freeseer/frontend/controller/recording.py +++ b/src/freeseer/frontend/controller/recording.py @@ -89,7 +89,7 @@ def configure_recording(): recording.next_id = 1 recording.media_dict = {} - for key, value in media_info.iteritems(): + for key, value in media_info.items(): new_media = Multimedia(recording.config, recording.plugin_manager) if value['null_multimeda']: new_media.current_state = Multimedia.NULL @@ -122,7 +122,7 @@ def configure_recording(): @http_response(200) def get_all_recordings(): """Returns list of all recordings.""" - return {'recordings': recording.media_dict.keys()} + return {'recordings': list(recording.media_dict.keys())} @recording.route('/recordings/', methods=['GET']) diff --git a/src/freeseer/frontend/controller/recording.py.bak b/src/freeseer/frontend/controller/recording.py.bak new file mode 100644 index 00000000..4c2e9eb3 --- /dev/null +++ b/src/freeseer/frontend/controller/recording.py.bak @@ -0,0 +1,244 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2014 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +import functools +import os +import shelve + +from flask import Blueprint +from flask import request + +from freeseer import settings +from freeseer.framework.multimedia import Multimedia +from freeseer.framework.plugin import PluginManager +from freeseer.frontend.controller import app +from freeseer.frontend.controller import validate +from freeseer.frontend.controller.server import HTTPError +from freeseer.frontend.controller.server import ServerError +from freeseer.frontend.controller.server import http_response + +recording = Blueprint('recording', __name__) + +recording.form_schema = { + 'control_recording': { + 'type': 'object', + 'properties': { + 'command': { + 'enum': ['start', 'pause', 'stop'] + } + }, + 'required': ['command'] + }, + 'create_recording': { + 'type': 'object', + 'properties': { + 'filename': { + 'type': 'string', + 'pattern': '^\w+$' + } + }, + 'required': ['filename'] + } +} + + +def sync(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + response_dict = func(*args, **kwargs) + recording.media_info.sync() + return response_dict + return wrapper + + +@recording.before_app_first_request +def configure_recording(): + """Configures freeseer to record via REST server. + + Gets recording profiles and configuration and instantiates recording plugins. Then it restores any stored talks. + Runs upon first call to REST server. + """ + recording.profile = settings.profile_manager.get() + recording.config = recording.profile.get_config('freeseer.conf', settings.FreeseerConfig, + storage_args=['Global'], read_only=True) + recording.plugin_manager = PluginManager(recording.profile) + recording.storage_file = os.path.join(settings.configdir, app.storage_file_path) + + media_info = shelve.open(recording.storage_file, writeback=True) + + recording.next_id = 1 + recording.media_dict = {} + for key, value in media_info.iteritems(): + new_media = Multimedia(recording.config, recording.plugin_manager) + if value['null_multimeda']: + new_media.current_state = Multimedia.NULL + else: + # if null_multimeda is False, a video exists, set current_state to Multimedia.STOP + new_media.current_state = Multimedia.STOP + + media_id = int(key) + if media_id >= recording.next_id: + recording.next_id = media_id + 1 + + if new_media.current_state == Multimedia.NULL: + filename = value['filename'].split('.ogg')[0] + success, filename = new_media.load_backend(None, filename) + + if not success: + raise ServerError('Could not load multimedia backend') + + value['filename'] = filename + + value['filepath'] = new_media.plugman.get_plugin_by_name(new_media.config.record_to_file_plugin, "Output").plugin_object.location + + recording.media_dict[media_id] = new_media + + recording.media_info = media_info + recording.media_info.sync() + + +@recording.route('/recordings', methods=['GET']) +@http_response(200) +def get_all_recordings(): + """Returns list of all recordings.""" + return {'recordings': recording.media_dict.keys()} + + +@recording.route('/recordings/', methods=['GET']) +@http_response(200) +def get_specific_recording(recording_id): + """Returns specific recording by id.""" + + key = str(recording_id) + try: + retrieved_media_entry = recording.media_info[key] + except KeyError: + raise HTTPError(404, 'No recording with id "{}" was found'.format(recording_id)) + + current_state = recording.media_dict[recording_id].current_state + filename = retrieved_media_entry['filename'] + + try: + filesize = os.path.getsize(retrieved_media_entry['filepath']) + except OSError: + filesize = 'NA' + + return { + 'id': recording_id, + 'filename': filename, + 'filesize': filesize, + 'status': current_state, + } + + +@recording.route('/recordings/', methods=['PATCH']) +@http_response(200) +@sync +def control_recording(recording_id): + """Change the state of a recording.""" + + validate.validate_form(request.form, recording.form_schema['control_recording']) + + try: + retrieved_media = recording.media_dict[recording_id] + except KeyError: + raise HTTPError(404, 'No recording with id "{}" was found'.format(recording_id)) + + command = request.form['command'] + media_state = retrieved_media.current_state + + if command == 'start' and media_state in [Multimedia.NULL, Multimedia.PAUSE]: + retrieved_media.record() + elif command == 'pause' and media_state == Multimedia.RECORD: + retrieved_media.pause() + elif command == 'stop' and media_state in [Multimedia.RECORD, Multimedia.PAUSE]: + retrieved_media.stop() + else: + raise HTTPError(400, 'Command "{}" could not be performed'.format(command)) + + if media_state is not Multimedia.NULL: + key = str(recording_id) + recording.media_info[key]['null_multimeda'] = False + + return '' + + +@recording.route('/recordings', methods=['POST']) +@http_response(201) +@sync +def create_recording(): + """Initializes a recording and returns its id.""" + + validate.validate_form(request.form, recording.form_schema['create_recording']) + + new_filename = request.form['filename'] + new_media = Multimedia(recording.config, recording.plugin_manager) + success, filename = new_media.load_backend(None, new_filename) + + if not success: + raise HTTPError(500, 'Could not load multimedia backend') + + filepath = new_media.plugman.get_plugin_by_name(new_media.config.record_to_file_plugin, "Output").plugin_object.location + new_recording_id = recording.next_id + key = str(new_recording_id) + + recording.media_dict[new_recording_id] = new_media + recording.media_info[key] = { + 'filename': filename, + 'filepath': filepath, + 'null_multimeda': True, + } + recording.next_id = recording.next_id + 1 + recording.media_info.sync() + + return {'id': new_recording_id} + + +@recording.route('/recordings/', methods=['DELETE']) +@http_response(204) +@sync +def delete_recording(recording_id): + """Deletes a recording given an id.""" + try: + retrieved_media = recording.media_dict[recording_id] + except KeyError: + raise HTTPError(404, 'No recording with id "{}" was found'.format(recording_id)) + + key = str(recording_id) + retrieved_media_entry = recording.media_info[key] + + if retrieved_media.current_state in [Multimedia.RECORD, Multimedia.PAUSE]: + retrieved_media.stop() + + # Delete the file if it exists + try: + os.remove(retrieved_media_entry['filepath']) + except OSError: + pass + + del recording.media_dict[recording_id] + del recording.media_info[key] + recording.media_info.sync() + + return '' diff --git a/src/freeseer/frontend/qtcommon/AboutDialog.py b/src/freeseer/frontend/qtcommon/AboutDialog.py index b3d4ed78..ea70bc22 100644 --- a/src/freeseer/frontend/qtcommon/AboutDialog.py +++ b/src/freeseer/frontend/qtcommon/AboutDialog.py @@ -39,10 +39,10 @@ from freeseer.frontend.qtcommon import resource # noqa from freeseer.frontend.qtcommon.AboutWidget import AboutWidget -RECORD_BUTTON_ARTIST = u'Sekkyumu' -RECORD_BUTTON_LINK = u'http://sekkyumu.deviantart.com/' -HEADPHONES_ARTIST = u'Ben Fleming' -HEADPHONES_LINK = u'http://mediadesign.deviantart.com/' +RECORD_BUTTON_ARTIST = 'Sekkyumu' +RECORD_BUTTON_LINK = 'http://sekkyumu.deviantart.com/' +HEADPHONES_ARTIST = 'Ben Fleming' +HEADPHONES_LINK = 'http://mediadesign.deviantart.com/' class AboutDialog(QDialog): diff --git a/src/freeseer/frontend/qtcommon/AboutDialog.py.bak b/src/freeseer/frontend/qtcommon/AboutDialog.py.bak new file mode 100644 index 00000000..a1852a5a --- /dev/null +++ b/src/freeseer/frontend/qtcommon/AboutDialog.py.bak @@ -0,0 +1,79 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +from PyQt4.QtCore import QString +from PyQt4.QtCore import SIGNAL +from PyQt4.QtGui import QDialog +from PyQt4.QtGui import QDialogButtonBox +from PyQt4.QtGui import QIcon +from PyQt4.QtGui import QPixmap +from PyQt4.QtGui import QWidget +from PyQt4.QtGui import QGridLayout + +try: + _fromUtf8 = QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +from freeseer.frontend.qtcommon import resource # noqa +from freeseer.frontend.qtcommon.AboutWidget import AboutWidget + +RECORD_BUTTON_ARTIST = u'Sekkyumu' +RECORD_BUTTON_LINK = u'http://sekkyumu.deviantart.com/' +HEADPHONES_ARTIST = u'Ben Fleming' +HEADPHONES_LINK = u'http://mediadesign.deviantart.com/' + + +class AboutDialog(QDialog): + """ + Common About Dialog for the Freeseer Project. This should be used for the + about dialog when including one in GUIs. + + + Grid Layout: + + Logo | About Infos + ------|------------- + | Close Button + """ + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self.aboutWidget = AboutWidget() + + icon = QIcon() + icon.addPixmap(QPixmap(_fromUtf8(":/freeseer/logo.png")), QIcon.Normal, QIcon.Off) + self.setWindowIcon(icon) + + self.layout = QGridLayout() + self.setLayout(self.layout) + + self.layout.addWidget(self.aboutWidget) + + # Right Bottom corner of grid, Close Button + self.buttonBox = QDialogButtonBox() + self.closeButton = self.buttonBox.addButton("Close", QDialogButtonBox.AcceptRole) + self.layout.addWidget(self.buttonBox, 1, 1) + self.connect(self.closeButton, SIGNAL("clicked()"), self.close) + + self.setWindowTitle("About Freeseer") diff --git a/src/freeseer/frontend/qtcommon/AboutWidget.py b/src/freeseer/frontend/qtcommon/AboutWidget.py index 5178ecc6..a51aa9cc 100644 --- a/src/freeseer/frontend/qtcommon/AboutWidget.py +++ b/src/freeseer/frontend/qtcommon/AboutWidget.py @@ -51,10 +51,10 @@ from freeseer.frontend.qtcommon.dpi_adapt_qtgui import QWidgetWithDpi -RECORD_BUTTON_ARTIST = u'Sekkyumu' -RECORD_BUTTON_LINK = u'http://sekkyumu.deviantart.com/' -HEADPHONES_ARTIST = u'Ben Fleming' -HEADPHONES_LINK = u'http://mediadesign.deviantart.com/' +RECORD_BUTTON_ARTIST = 'Sekkyumu' +RECORD_BUTTON_LINK = 'http://sekkyumu.deviantart.com/' +HEADPHONES_ARTIST = 'Ben Fleming' +HEADPHONES_LINK = 'http://mediadesign.deviantart.com/' class AboutWidget(QWidgetWithDpi): @@ -137,17 +137,17 @@ def retranslate(self, language=None): "version 3. This software is provided 'as-is',without any express or implied warranty. In " "no event will the authors be held liable for any damages arising from the use of this software.") - self.aboutInfoString = u'

' + NAME.capitalize() + u'

' + \ - u'
' + self.uiTranslator.translate("AboutDialog", "Version") + \ - ": " + __version__ + u'' + \ - u'

' + self.descriptionString + u'

' + \ - u'

' + self.copyrightString + u'

' + \ - u'

' + URL + u'

' \ - u'

' + self.licenseTextString + u'

' \ - u'

' + self.uiTranslator.translate("AboutDialog", "Record button graphics by") + \ - u': ' + RECORD_BUTTON_ARTIST + u'

' \ - u'

' + self.uiTranslator.translate("AboutDialog", "Headphones graphics by") + \ - u': ' + HEADPHONES_ARTIST + u'


' + self.aboutInfoString = '

' + NAME.capitalize() + '

' + \ + '
' + self.uiTranslator.translate("AboutDialog", "Version") + \ + ": " + __version__ + '' + \ + '

' + self.descriptionString + '

' + \ + '

' + self.copyrightString + '

' + \ + '

' + URL + '

' \ + '

' + self.licenseTextString + '

' \ + '

' + self.uiTranslator.translate("AboutDialog", "Record button graphics by") + \ + ': ' + RECORD_BUTTON_ARTIST + '

' \ + '

' + self.uiTranslator.translate("AboutDialog", "Headphones graphics by") + \ + ': ' + HEADPHONES_ARTIST + '


' self.aboutInfo.setText(self.aboutInfoString) # --- End Main Text diff --git a/src/freeseer/frontend/qtcommon/AboutWidget.py.bak b/src/freeseer/frontend/qtcommon/AboutWidget.py.bak new file mode 100644 index 00000000..5178ecc6 --- /dev/null +++ b/src/freeseer/frontend/qtcommon/AboutWidget.py.bak @@ -0,0 +1,176 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2014 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +''' +@author: Thanh Ha, Mia Kilborn +''' +from PyQt4.QtCore import QString +from PyQt4.QtCore import QTranslator +from PyQt4.QtCore import QUrl +from PyQt4.QtCore import SIGNAL +from PyQt4.QtCore import Qt +from PyQt4.QtGui import QDesktopServices +from PyQt4.QtGui import QGridLayout +from PyQt4.QtGui import QHBoxLayout +from PyQt4.QtGui import QIcon +from PyQt4.QtGui import QLabel +from PyQt4.QtGui import QPixmap +from PyQt4.QtGui import QPushButton +from PyQt4.QtGui import QSizePolicy + +try: + _fromUtf8 = QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +from freeseer import NAME +from freeseer import URL +from freeseer import __version__ +from freeseer.frontend.qtcommon import resource # noqa +from freeseer.frontend.qtcommon.dpi_adapt_qtgui import QWidgetWithDpi + + +RECORD_BUTTON_ARTIST = u'Sekkyumu' +RECORD_BUTTON_LINK = u'http://sekkyumu.deviantart.com/' +HEADPHONES_ARTIST = u'Ben Fleming' +HEADPHONES_LINK = u'http://mediadesign.deviantart.com/' + + +class AboutWidget(QWidgetWithDpi): + """ Common About Dialog for the Freeseer Project. This should be used for the + about dialog when including one in GUIs. + + Layout: + Logo | About Infos + | Buttons + """ + + def __init__(self, parent=None): + super(AboutWidget, self).__init__(parent) + + self.current_language = "en_US" + self.uiTranslator = QTranslator() + self.uiTranslator.load(":/languages/tr_en_US.qm") + + self.fontSize = self.font().pixelSize() + self.fontUnit = "px" + if self.fontSize == -1: # Font is set as points, not pixels. + self.fontUnit = "pt" + self.fontSize = self.font().pointSize() + + icon = QIcon() + self.logoPixmap = QPixmap(_fromUtf8(":/freeseer/logo.png")) + icon.addPixmap(self.logoPixmap, QIcon.Normal, QIcon.Off) + self.setWindowIcon(icon) + + self.mainLayout = QGridLayout() + self.setLayout(self.mainLayout) + + # Logo + self.logo = QLabel("Logo") + # To offset the logo so that it's to the right of the title + self.logo.setStyleSheet("QLabel {{ margin-left: {} {} }}" + .format(self.set_width_with_dpi(90) + (self.fontSize * 2.5), self.fontUnit)) + self.logo.setPixmap(self.logoPixmap.scaledToHeight(self.set_height_with_dpi(80))) + self.mainLayout.addWidget(self.logo, 0, 0, Qt.AlignTop) + + # Info + self.aboutInfo = QLabel("About Info", openExternalLinks=True) + self.aboutInfo.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + self.aboutInfo.setWordWrap(True) + self.mainLayout.addWidget(self.aboutInfo, 0, 0) + + # Buttons + self.buttonsLayout = QHBoxLayout() + self.issueButton = QPushButton("Report an issue") + self.docsButton = QPushButton("Freeseer documentation") + self.contactButton = QPushButton("Contact us") + self.buttonsLayout.insertWidget(0, self.docsButton) + self.buttonsLayout.insertWidget(1, self.issueButton) + self.buttonsLayout.insertWidget(2, self.contactButton) + + self.mainLayout.addLayout(self.buttonsLayout, 2, 0) + + self.connect(self.docsButton, SIGNAL('clicked()'), self.openDocsUrl) + self.connect(self.issueButton, SIGNAL('clicked()'), self.openNewIssueUrl) + self.connect(self.contactButton, SIGNAL('clicked()'), self.openContactUrl) + + self.retranslate() + + def retranslate(self, language=None): + if language is not None: + self.current_language = language + + self.uiTranslator.load(":/languages/tr_%s.qm" % self.current_language) + + # + # Main Text + # + self.descriptionString = self.uiTranslator.translate("AboutDialog", + "Freeseer is a video capture utility capable of capturing presentations. It captures " + "video sources such as usb, firewire, or local desktop along with audio and mixes them " + "together to produce a video.") + self.copyrightString = self.uiTranslator.translate("AboutDialog", 'Copyright (C) 2014 The Free and ' + 'Open Source Software Learning Centre') + self.licenseTextString = self.uiTranslator.translate("AboutDialog", "Freeseer is licensed under the GPL " + "version 3. This software is provided 'as-is',without any express or implied warranty. In " + "no event will the authors be held liable for any damages arising from the use of this software.") + + self.aboutInfoString = u'

' + NAME.capitalize() + u'

' + \ + u'
' + self.uiTranslator.translate("AboutDialog", "Version") + \ + ": " + __version__ + u'' + \ + u'

' + self.descriptionString + u'

' + \ + u'

' + self.copyrightString + u'

' + \ + u'

' + URL + u'

' \ + u'

' + self.licenseTextString + u'

' \ + u'

' + self.uiTranslator.translate("AboutDialog", "Record button graphics by") + \ + u': ' + RECORD_BUTTON_ARTIST + u'

' \ + u'

' + self.uiTranslator.translate("AboutDialog", "Headphones graphics by") + \ + u': ' + HEADPHONES_ARTIST + u'


' + + self.aboutInfo.setText(self.aboutInfoString) + # --- End Main Text + + def openDocsUrl(self): + """Opens a link to the Freeseer online documentation""" + url = QUrl("http://freeseer.readthedocs.org") + QDesktopServices.openUrl(url) + + def openNewIssueUrl(self): + """Opens a link to the Freeseer new issue page""" + url = QUrl("https://github.com/Freeseer/freeseer/issues/new") + QDesktopServices.openUrl(url) + + def openContactUrl(self): + """Opens a link to Freeseer's contact information""" + url = QUrl("http://freeseer.readthedocs.org/en/latest/contact.html") + QDesktopServices.openUrl(url) + +if __name__ == "__main__": + import sys + from PyQt4.QtGui import QApplication + app = QApplication(sys.argv) + main = AboutWidget() + main.show() + sys.exit(app.exec_()) diff --git a/src/freeseer/frontend/record/RecordingController.py b/src/freeseer/frontend/record/RecordingController.py index 409c2cc2..b2427044 100644 --- a/src/freeseer/frontend/record/RecordingController.py +++ b/src/freeseer/frontend/record/RecordingController.py @@ -69,12 +69,12 @@ def print_talks(self): print("ID: Speaker - Title") print("-------------------") - while(query.next()): - talkid = unicode(query.value(0).toString()) - title = unicode(query.value(1).toString()) - speaker = unicode(query.value(2).toString()) + while(next(query)): + talkid = str(query.value(0).toString()) + title = str(query.value(1).toString()) + speaker = str(query.value(2).toString()) - print("{talkid}: {speaker} - {title}".format(talkid=talkid, speaker=speaker, title=title)) + print(("{talkid}: {speaker} - {title}".format(talkid=talkid, speaker=speaker, title=title))) ### ### Convenience commands diff --git a/src/freeseer/frontend/record/RecordingController.py.bak b/src/freeseer/frontend/record/RecordingController.py.bak new file mode 100644 index 00000000..409c2cc2 --- /dev/null +++ b/src/freeseer/frontend/record/RecordingController.py.bak @@ -0,0 +1,110 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +from freeseer.framework.multimedia import Multimedia +from freeseer.framework.plugin import PluginManager + + +class RecordingController: + def __init__(self, profile, db, config, cli=False): + self.config = config + self.db = db + self.plugman = PluginManager(profile) + self.media = Multimedia(self.config, self.plugman, cli=cli) + + def set_window_id(self, window_id): + """Sets the Window ID which GStreamer should paint on""" + self.media.set_window_id(window_id) + + def set_audio_feedback_handler(self, audio_feedback_handler): + """Sets the handler for Audio Feedback levels""" + self.media.set_audio_feedback_handler(audio_feedback_handler) + + def record(self): + """Start Recording""" + self.media.record() + + def stop(self): + """Stop Recording""" + self.media.stop() + + def pause(self): + """Pause Recording""" + self.media.pause() + + def load_backend(self, presentation=None): + """Prepares the backend for recording""" + initialized, filename_for_frontend = self.media.load_backend(presentation) + if initialized: + return True, filename_for_frontend + else: + return False # Error something failed while loading the backend + + def print_talks(self): + query = self.db.get_talks() + + # Print the header + print("\n") + print("ID: Speaker - Title") + print("-------------------") + + while(query.next()): + talkid = unicode(query.value(0).toString()) + title = unicode(query.value(1).toString()) + speaker = unicode(query.value(2).toString()) + + print("{talkid}: {speaker} - {title}".format(talkid=talkid, speaker=speaker, title=title)) + + ### + ### Convenience commands + ### + def record_talk_id(self, talk_id): + """Records using a known Talk ID + + Returns True if recording is successfully started + Returns False if any issues arise + """ + presentation = self.db.get_presentation(talk_id) + if self.media.load_backend(presentation): + # Only record if the backend successfully loaded + # No need to print error on failure since load_backend already + # prints an error message + self.record() + return True + + else: + return False + + def record_filename(self, filename): + """Records to a specific filename + + Returns True if recording is successfully started + Returns False if any issues arise + """ + if self.media.load_backend(filename=filename): + self.record() + return True + + else: + return False diff --git a/src/freeseer/frontend/record/record.py b/src/freeseer/frontend/record/record.py index 726504ab..08cdb009 100644 --- a/src/freeseer/frontend/record/record.py +++ b/src/freeseer/frontend/record/record.py @@ -235,7 +235,7 @@ def retranslate(self): elif self.actionAutoRecord.isChecked(): self.mainWidget.statusLabel.setText(self.autoRecordString) else: - self.mainWidget.statusLabel.setText(u"{} {} --- {} ".format(self.freeSpaceString, + self.mainWidget.statusLabel.setText("{} {} --- {} ".format(self.freeSpaceString, get_free_space(self.config.videodir), self.idleString)) @@ -363,7 +363,7 @@ def toggle_gui(state): if self.load_backend(): toggle_gui(True) self.controller.pause() - self.mainWidget.statusLabel.setText(u"{} {} --- {} ".format(self.freeSpaceString, + self.mainWidget.statusLabel.setText("{} {} --- {} ".format(self.freeSpaceString, get_free_space(self.config.videodir), self.readyString)) else: @@ -409,7 +409,7 @@ def record(self): self.mainWidget.pauseButton.setEnabled(False) self.recordAction.setText(self.recordString) self.mainWidget.audioSlider.setValue(0) - self.mainWidget.statusLabel.setText(u"{} {} --- {} ".format(self.freeSpaceString, + self.mainWidget.statusLabel.setText("{} {} --- {} ".format(self.freeSpaceString, get_free_space(self.config.videodir), self.idleString)) @@ -460,7 +460,7 @@ def auto_record(self, state): if self.current_room: self.autoTalks = self.db.get_talks_by_room_and_time(self.current_room) # Start recording if there are talks in database that can be auto-recorded - if self.autoTalks.next(): + if next(self.autoTalks): # Set the cursor back to before the first record so that single_auto_record works properly self.autoTalks.previous() self._enable_disable_gui(True) @@ -496,7 +496,7 @@ def single_auto_record(self): self.recorded = False log.debug("Auto-recording for the current talk stopped.") - if self.autoTalks.next(): + if next(self.autoTalks): starttime = QtCore.QTime.fromString(self.autoTalks.value(8).toString()) endtime = QtCore.QTime.fromString(self.autoTalks.value(9).toString()) currenttime = QtCore.QTime.currentTime() @@ -561,7 +561,7 @@ def load_backend(self): # If current presentation is no existant (empty talk database) # use a default recording name. else: - presentation = Presentation(title=unicode("default")) + presentation = Presentation(title=str("default")) initialized, self.recently_recorded_video = self.controller.load_backend(presentation) if initialized: @@ -580,7 +580,7 @@ def update_timer(self): self.time_seconds = 0 self.time_minutes += 1 - self.mainWidget.statusLabel.setText(u"{} {} --- {} {} --- {}".format(self.elapsedTimeString, + self.mainWidget.statusLabel.setText("{} {} --- {} {} --- {}".format(self.elapsedTimeString, frmt_time, self.freeSpaceString, get_free_space(self.config.videodir), diff --git a/src/freeseer/frontend/record/record.py.bak b/src/freeseer/frontend/record/record.py.bak new file mode 100644 index 00000000..726504ab --- /dev/null +++ b/src/freeseer/frontend/record/record.py.bak @@ -0,0 +1,762 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2014 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +import logging +import os +import subprocess +import sys +import functools + +from PyQt4 import QtGui, QtCore +from PyQt4.QtGui import QCursor + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +from freeseer.framework.presentation import Presentation +from freeseer.framework.failure import Failure +from freeseer.framework.util import get_free_space +from freeseer.frontend.qtcommon.FreeseerApp import FreeseerApp +from freeseer.frontend.qtcommon.log import LogStatusWidget +from freeseer.frontend.configtool.configtool import ConfigToolApp +from freeseer.frontend.record.RecordingController import RecordingController +from freeseer.frontend.record.RecordingWidget import RecordingWidget +from freeseer.frontend.record.AutoRecordWidget import AutoRecordWidget +from freeseer.frontend.record.ReportDialog import ReportDialog +from freeseer.frontend.talkeditor.talkeditor import TalkEditorApp + +log = logging.getLogger(__name__) + + +class RecordApp(FreeseerApp): + """Freeseer's main GUI class.""" + + def __init__(self, profile, config): + super(RecordApp, self).__init__(config) + + self.db = profile.get_database() + self.controller = RecordingController(profile, self.db, self.config) + + self.recently_recorded_video = None + + self.resize(550, 450) + + # Setup custom widgets + self.mainWidget = RecordingWidget() + self.setCentralWidget(self.mainWidget) + self.reportWidget = ReportDialog() + self.reportWidget.setModal(True) + self.autoRecordWidget = AutoRecordWidget() + self.configToolApp = ConfigToolApp(profile, config) + self.configToolApp.setWindowModality(QtCore.Qt.ApplicationModal) + self.configToolApp.setWindowFlags(QtCore.Qt.Dialog) + self.talkEditorApp = TalkEditorApp(self.config, self.db) + self.talkEditorApp.setWindowModality(QtCore.Qt.ApplicationModal) + self.talkEditorApp.setWindowFlags(QtCore.Qt.Dialog) + + self.logStatusWidget = LogStatusWidget(self.logDialog) + self.statusBar().addPermanentWidget(self.logStatusWidget, 1) + self.statusBar().addPermanentWidget(self.mainWidget.statusLabel) + + # Initialize geometry, to be used for restoring window positioning. + self.geometry = None + self.current_event = None + self.current_room = None + self.controller.set_window_id(self.mainWidget.previewWidget.winId()) + self.controller.set_audio_feedback_handler(self.audio_feedback) + + # Set timer for recording how much time elapsed during a recording + self.reset_timer() + self.timer = QtCore.QTimer(self) + self.timer.timeout.connect(self.update_timer) + + # Initialize variables for auto-recording + self.singleID = None + self.timeUntilStart = None + self.timeUntilEnd = None + self.autoTalks = None + self.recorded = False + self.beforeStartTimer = QtCore.QTimer(self) + self.beforeStartTimer.timeout.connect(self.start_single_record) + self.beforeEndTimer = QtCore.QTimer(self) + self.beforeEndTimer.timeout.connect(self.single_auto_record) + + # + # Setup Menubar + # + + # Build the options Menu, TalkEditor and ConfigTool + self.menuOptions = QtGui.QMenu(self.menubar) + self.menuOptions.setObjectName(_fromUtf8("menuOptions")) + self.menubar.insertMenu(self.menuHelp.menuAction(), self.menuOptions) + self.actionConfigTool = QtGui.QAction(self) + self.actionConfigTool.setShortcut("Ctrl+C") + self.actionConfigTool.setObjectName(_fromUtf8("actionConfigTool")) + self.actionTalkEditor = QtGui.QAction(self) + self.actionTalkEditor.setShortcut("Ctrl+E") + self.actionTalkEditor.setObjectName(_fromUtf8("actionTalkEditor")) + self.actionAutoRecord = QtGui.QAction(self) + leaveButtonShortcut = "Ctrl+R" + self.actionAutoRecord.setShortcut(leaveButtonShortcut) + self.actionAutoRecord.setCheckable(True) + self.actionAutoRecord.setObjectName(_fromUtf8("actionAutoRecord")) + self.menuOptions.addAction(self.actionConfigTool) + self.menuOptions.addAction(self.actionTalkEditor) + self.menuOptions.addAction(self.actionAutoRecord) + + folderIcon = QtGui.QIcon.fromTheme("folder") + self.actionOpenVideoFolder = QtGui.QAction(self) + self.actionOpenVideoFolder.setShortcut("Ctrl+O") + self.actionOpenVideoFolder.setObjectName(_fromUtf8("actionOpenVideoFolder")) + self.actionOpenVideoFolder.setIcon(folderIcon) + + self.actionReport = QtGui.QAction(self) + self.actionReport.setObjectName(_fromUtf8("actionReport")) + + # Actions + self.menuFile.insertAction(self.actionExit, self.actionOpenVideoFolder) + self.menuHelp.addAction(self.actionReport) + # --- End Menubar + + # + # Systray Setup + # + self.systray = QtGui.QSystemTrayIcon(self.icon) + self.systray.show() + self.systray.menu = QtGui.QMenu() + self.systray.setContextMenu(self.systray.menu) + + self.visibilityAction = QtGui.QAction(self) + self.recordAction = QtGui.QAction(self) + + self.systray.menu.addAction(self.visibilityAction) + self.systray.menu.addAction(self.recordAction) + + self.connect(self.visibilityAction, QtCore.SIGNAL('triggered()'), self.toggle_window_visibility) + self.connect(self.recordAction, QtCore.SIGNAL('triggered()'), self.toggle_record_button) + self.connect(self.systray, QtCore.SIGNAL('activated(QSystemTrayIcon::ActivationReason)'), self._icon_activated) + # --- End Systray Setup + + # main tab connections + self.connect(self.mainWidget.eventComboBox, QtCore.SIGNAL('currentIndexChanged(const QString&)'), self.load_rooms_from_event) + self.connect(self.mainWidget.roomComboBox, QtCore.SIGNAL('currentIndexChanged(const QString&)'), self.load_dates_from_event_room) + self.connect(self.mainWidget.dateComboBox, QtCore.SIGNAL('currentIndexChanged(const QString&)'), self.load_talks_from_date) + self.connect(self.mainWidget.talkComboBox, QtCore.SIGNAL('currentIndexChanged(const QString&)'), self.set_talk_tooltip) + self.connect(self.mainWidget.standbyButton, QtCore.SIGNAL("toggled(bool)"), self.standby) + self.connect(self.mainWidget.disengageButton, QtCore.SIGNAL("clicked()"), functools.partial(self.standby, state=False)) + self.connect(self.mainWidget.recordButton, QtCore.SIGNAL('clicked()'), self.record) + self.connect(self.mainWidget.pauseButton, QtCore.SIGNAL('toggled(bool)'), self.pause) + self.connect(self.mainWidget.audioFeedbackCheckbox, QtCore.SIGNAL('toggled(bool)'), self.toggle_audio_feedback) + self.connect(self.mainWidget.playButton, QtCore.SIGNAL('clicked()'), self.play_video) + + self.connect(self.autoRecordWidget.leaveButton, QtCore.SIGNAL('clicked()'), functools.partial(self.auto_record, state=False)) + self.autoRecordWidget.leaveButton.setShortcut(leaveButtonShortcut) + + # Main Window Connections + self.connect(self.actionConfigTool, QtCore.SIGNAL('triggered()'), self.open_configtool) + self.connect(self.actionTalkEditor, QtCore.SIGNAL('triggered()'), self.open_talkeditor) + self.connect(self.actionAutoRecord, QtCore.SIGNAL('toggled(bool)'), self.auto_record) + self.connect(self.actionOpenVideoFolder, QtCore.SIGNAL('triggered()'), self.open_video_directory) + self.connect(self.actionReport, QtCore.SIGNAL('triggered()'), self.show_report_widget) + + # + # ReportWidget Connections + # + self.connect(self.reportWidget.reportButton, QtCore.SIGNAL("clicked()"), self.report) + + self.load_settings() + + # Setup spacebar key. + self.mainWidget.pauseButton.setShortcut(QtCore.Qt.Key_Space) + self.mainWidget.pauseButton.setFocus() + + self.retranslate() + + ### + ### Translation Related + ### + def retranslate(self): + self.setWindowTitle(self.app.translate("RecordApp", "Freeseer - portable presentation recording station")) + # + # Reusable Strings + # + self.standbyString = self.app.translate("RecordApp", "Standby") + self.disengageString = self.app.translate("RecordApp", "Leave record-mode") + self.standbyTooltipString = self.app.translate("RecordApp", "Sets up the system for recording") + self.disengageTooltipString = self.app.translate("RecordApp", "Go back to edit talk information or select a different talk") + self.autoRecordString = self.app.translate("RecordApp", "Auto Record") + self.recordString = self.app.translate("RecordApp", "Record") + self.pauseString = self.app.translate("RecordApp", "Pause") + self.resumeString = self.app.translate("RecordApp", "Resume") + self.stopString = self.app.translate("RecordApp", "Stop") + self.stopAutoString = self.app.translate("RecordApp", "Stop Auto Record") + self.hideWindowString = self.app.translate("RecordApp", "Hide Main Window") + self.showWindowString = self.app.translate("RecordApp", "Show Main Window") + self.playVideoString = self.app.translate("RecordApp", "Play") + + # Status Bar messages + self.idleString = self.app.translate("RecordApp", "Idle.") + self.readyString = self.app.translate("RecordApp", "Ready.") + self.recordingString = self.app.translate("RecordApp", "Recording") + self.pausedString = self.app.translate("RecordApp", "Recording Paused.") + self.freeSpaceString = self.app.translate("RecordApp", "Free Space:") + self.elapsedTimeString = self.app.translate("RecordApp", "Elapsed Time:") + # --- End Reusable Strings + + if self.mainWidget.is_recording and self.mainWidget.pauseButton.isChecked(): + self.mainWidget.statusLabel.setText(self.pausedString) + elif self.mainWidget.is_recording and (not self.mainWidget.pauseButton.isChecked()): + self.mainWidget.statusLabel.setText(self.recordingString) + elif self.mainWidget.standbyButton.isChecked(): + self.mainWidget.statusLabel.setText(self.readyString) + elif self.actionAutoRecord.isChecked(): + self.mainWidget.statusLabel.setText(self.autoRecordString) + else: + self.mainWidget.statusLabel.setText(u"{} {} --- {} ".format(self.freeSpaceString, + get_free_space(self.config.videodir), + self.idleString)) + + # + # Menubar + # + self.menuOptions.setTitle(self.app.translate("RecordApp", "&Options")) + self.actionConfigTool.setText(self.app.translate("RecordApp", "&Configuration")) + self.actionTalkEditor.setText(self.app.translate("RecordApp", "&Edit Talks")) + self.actionAutoRecord.setText(self.autoRecordString) + self.actionOpenVideoFolder.setText(self.app.translate("RecordApp", "&Open Video Directory")) + self.actionReport.setText(self.app.translate("RecordApp", "&Report")) + # --- End Menubar + + # + # Systray + # + self.visibilityAction.setText(self.hideWindowString) + self.recordAction.setText(self.recordString) + # --- End Systray + + # + # RecordingWidget + # + self.mainWidget.disengageButton.setText(self.disengageString) + self.mainWidget.standbyButton.setText(self.standbyString) + self.mainWidget.standbyButton.setToolTip(self.standbyTooltipString) + self.mainWidget.disengageButton.setToolTip(self.disengageTooltipString) + if self.mainWidget.is_recording: + self.mainWidget.recordButton.setToolTip(self.stopString) + else: + self.mainWidget.recordButton.setToolTip(self.recordString) + self.mainWidget.pauseButton.setText(self.pauseString) + self.mainWidget.pauseButton.setToolTip(self.pauseString) + self.mainWidget.eventLabel.setText(self.app.translate("RecordApp", "Event")) + self.mainWidget.roomLabel.setText(self.app.translate("RecordApp", "Room")) + self.mainWidget.dateLabel.setText(self.app.translate("RecordApp", "Date")) + self.mainWidget.talkLabel.setText(self.app.translate("RecordApp", "Talk")) + # --- End RecordingWidget + + # + # ReportWidget + # + self.reportWidget.setWindowTitle(self.app.translate("RecordApp", "Reporting Tool")) + self.reportWidget.titleLabel.setText(self.app.translate("RecordApp", "Title:")) + self.reportWidget.speakerLabel.setText(self.app.translate("RecordApp", "Speaker:")) + self.reportWidget.eventLabel.setText(self.app.translate("RecordApp", "Event:")) + self.reportWidget.roomLabel.setText(self.app.translate("RecordApp", "Room:")) + self.reportWidget.startTimeLabel.setText(self.app.translate("RecordApp", "Start Time:")) + self.reportWidget.endTimeLabel.setText(self.app.translate("RecordApp", "End Time:")) + self.reportWidget.commentLabel.setText(self.app.translate("RecordApp", "Comment")) + self.reportWidget.releaseCheckBox.setText(self.app.translate("RecordApp", "Release Received")) + self.reportWidget.closeButton.setText(self.app.translate("RecordApp", "Close")) + self.reportWidget.reportButton.setText(self.app.translate("RecordApp", "Report")) + + # Logic for translating the report options + noissues = self.app.translate("RecordApp", "No Issues") + noaudio = self.app.translate("RecordApp", "No Audio") + novideo = self.app.translate("RecordApp", "No Video") + noaudiovideo = self.app.translate("RecordApp", "No Audio/Video") + self.reportWidget.options = [noissues, noaudio, novideo, noaudiovideo] + self.reportWidget.reportCombo.clear() + for i in self.reportWidget.options: + self.reportWidget.reportCombo.addItem(i) + # --- End ReportWidget + + self.logStatusWidget.retranslate() + + ### + ### UI Logic + ### + + def load_settings(self): + """Load settings for Freeseer""" + log.info('Loading settings...') + + # Load default language. + actions = self.menuLanguage.actions() + for action in actions: + if action.data().toString() == self.config.default_language: + action.setChecked(True) + self.translate(action) + break + + # Load Talks as a SQL Data Model. + self.load_event_list() + + def current_presentation(self): + """Creates a presentation object of the current presentation. + + Current presentation is the currently selected title on the GUI. + """ + #i = self.mainWidget.talkComboBox.currentIndex() + #p_id = self.mainWidget.talkComboBox.model().index(i, 1).data(QtCore.Qt.DisplayRole).toString() + return self.db.get_presentation(self.current_presentation_id()) + + def current_presentation_id(self): + """Returns the current selected presentation ID.""" + i = self.mainWidget.talkComboBox.currentIndex() + return self.mainWidget.talkComboBox.model().index(i, 1).data(QtCore.Qt.DisplayRole).toString() + + def standby(self, state): + """Prepares the GStreamer pipelines for recording + + Sets the pipeline to paused state so that initiating a recording + does not have a delay due to GStreamer initialization. + """ + def toggle_gui(state): + """Toggles GUI components when standby is pressed""" + if state: + self.mainWidget.standbyButton.setHidden(state) + self.mainWidget.disengageButton.setVisible(state) + else: + self.mainWidget.disengageButton.setVisible(state) + self.mainWidget.standbyButton.setHidden(state) + + self.mainWidget.recordButton.setEnabled(state) + self.mainWidget.eventComboBox.setDisabled(state) + self.mainWidget.roomComboBox.setDisabled(state) + self.mainWidget.dateComboBox.setDisabled(state) + self.mainWidget.talkComboBox.setDisabled(state) + self.mainWidget.audioFeedbackCheckbox.setDisabled(state) + + if state: # Prepare the pipelines + if self.load_backend(): + toggle_gui(True) + self.controller.pause() + self.mainWidget.statusLabel.setText(u"{} {} --- {} ".format(self.freeSpaceString, + get_free_space(self.config.videodir), + self.readyString)) + else: + toggle_gui(False) + self.mainWidget.standbyButton.setChecked(False) + else: + toggle_gui(False) + self.controller.stop() + self.mainWidget.standbyButton.setChecked(False) + + self.mainWidget.playButton.setEnabled(False) + + def record(self): + """The logic for recording and stopping recording.""" + + if self.mainWidget.is_recording: # Start Recording. + logo_rec = QtGui.QPixmap(":/freeseer/logo_rec.png") + sysIcon2 = QtGui.QIcon(logo_rec) + self.systray.setIcon(sysIcon2) + self.controller.record() + self.mainWidget.recordButton.setToolTip(self.stopString) + self.mainWidget.disengageButton.setEnabled(False) + self.mainWidget.pauseButton.setEnabled(True) + self.recordAction.setText(self.stopString) + + # Hide if auto-hide is set. + if self.config.auto_hide: + self.hide_window() + self.visibilityAction.setText(self.showWindowString) + log.debug('auto-hide is enabled, main window is now hidden in systray.') + + # Start timer. + self.timer.start(1000) + + else: # Stop Recording. + logo_rec = QtGui.QPixmap(":/freeseer/logo.png") + sysIcon = QtGui.QIcon(logo_rec) + self.systray.setIcon(sysIcon) + self.controller.stop() + self.mainWidget.pauseButton.setChecked(False) + self.mainWidget.recordButton.setToolTip(self.recordString) + self.mainWidget.disengageButton.setEnabled(True) + self.mainWidget.pauseButton.setEnabled(False) + self.recordAction.setText(self.recordString) + self.mainWidget.audioSlider.setValue(0) + self.mainWidget.statusLabel.setText(u"{} {} --- {} ".format(self.freeSpaceString, + get_free_space(self.config.videodir), + self.idleString)) + + # Finally set the standby button back to unchecked position. + self.standby(False) + + # Stop and reset timer. + self.timer.stop() + self.reset_timer() + + #Show playback button + self.mainWidget.playButton.setVisible(True) + self.mainWidget.playButton.setEnabled(True) + + # Select next talk if there is one within 15 minutes. + if self.current_event and self.current_room: + starttime = QtCore.QDateTime().currentDateTime() + stoptime = starttime.addSecs(900) + talkid = self.db.get_talk_between_time(self.current_event, self.current_room, + starttime.toString(), stoptime.toString()) + + if talkid is not None: + for i in range(self.mainWidget.talkComboBox.count()): + if talkid == self.mainWidget.talkComboBox.model().index(i, 1).data(QtCore.Qt.DisplayRole).toString(): + self.mainWidget.talkComboBox.setCurrentIndex(i) + + def _enable_disable_gui(self, state): + """Disables GUI components when Auto Record is pressed, and enables them when Auto Record is released""" + self.mainWidget.standbyButton.setDisabled(state) + self.mainWidget.eventComboBox.setDisabled(state) + self.mainWidget.roomComboBox.setDisabled(state) + self.mainWidget.dateComboBox.setDisabled(state) + self.mainWidget.talkComboBox.setDisabled(state) + self.mainWidget.audioFeedbackCheckbox.setDisabled(state) + + def stop_auto_record_gui(self): + """Sets the gui for stopping the auto record""" + self.autoRecordWidget.stop_timer() + self.autoRecordWidget.close() + self._enable_disable_gui(False) + self.recorded = False + self.actionAutoRecord.setChecked(False) + + def auto_record(self, state): + """Starts automated recording""" + if state: + # If there is a room selected, then it's possible to auto-record + if self.current_room: + self.autoTalks = self.db.get_talks_by_room_and_time(self.current_room) + # Start recording if there are talks in database that can be auto-recorded + if self.autoTalks.next(): + # Set the cursor back to before the first record so that single_auto_record works properly + self.autoTalks.previous() + self._enable_disable_gui(True) + self.single_auto_record() + else: + # Dialog for no talks to auto-record + QtGui.QMessageBox.information(self, 'No Talks to Record', + 'There are no upcoming talks to auto-record in this room', QtGui.QMessageBox.Ok) + self.actionAutoRecord.setChecked(False) + + else: + # Dialog that pops up when no room is selected + QtGui.QMessageBox.information(self, 'No Room Selected', + 'Please select a room to auto-record', QtGui.QMessageBox.Ok) + self.actionAutoRecord.setChecked(False) + else: + self.beforeStartTimer.stop() + self.beforeEndTimer.stop() + self.controller.stop() + self.stop_auto_record_gui() + + self.mainWidget.playButton.setEnabled(False) + + def single_auto_record(self): + """Completes one display and record cycle of the auto-record feature. + + Stops the recording of the last talk if it exists, displays the countdown until the start of + the next talk, and when the talk begins, records the talk while displaying the countdown until + the end of the talk. + """ + if self.recorded: + self.controller.stop() + self.recorded = False + log.debug("Auto-recording for the current talk stopped.") + + if self.autoTalks.next(): + starttime = QtCore.QTime.fromString(self.autoTalks.value(8).toString()) + endtime = QtCore.QTime.fromString(self.autoTalks.value(9).toString()) + currenttime = QtCore.QTime.currentTime() + + if currenttime <= starttime: + self.singleID = self.autoTalks.value(0).toString() + title = self.autoTalks.value(1).toString() + speaker = self.autoTalks.value(2).toString() + + # Time (in seconds) until recording for the talk starts + self.timeUntilStart = currenttime.secsTo(starttime) + # Time (in seconds) from the starttime to endtime of this talk + self.timeUntilEnd = starttime.secsTo(endtime) + + # Display fullscreen countdown and talk info until talk starts + self.autoRecordWidget.set_recording(False) + self.autoRecordWidget.set_display_message(title, speaker) + self.autoRecordWidget.start_timer(self.timeUntilStart) + self.autoRecordWidget.showFullScreen() + + # Wait for talk to start, then change display and start recording + self.beforeStartTimer.setInterval((self.timeUntilStart + 1) * 1000) + self.beforeStartTimer.setSingleShot(True) + self.beforeStartTimer.start() + else: + # Start time has already passed, so move on to next talk + self.single_auto_record() + else: + self.stop_auto_record_gui() + + def start_single_record(self): + """Begins the auto-recording of a single talk while displaying the countdown on screen""" + self.autoRecordWidget.set_recording(True) + self.autoRecordWidget.set_display_message() + self.autoRecordWidget.start_timer(self.timeUntilEnd) + if self.controller.record_talk_id(self.singleID): + log.debug("Auto-recording for the current talk started.") + self.recorded = True + self.beforeEndTimer.setInterval((self.timeUntilEnd + 1) * 1000) + self.beforeEndTimer.setSingleShot(True) + self.beforeEndTimer.start() + + def pause(self, state): + """Pause the recording""" + if state: # Pause Recording. + self.controller.pause() + log.info("Recording paused.") + self.mainWidget.pauseButton.setToolTip(self.resumeString) + self.mainWidget.statusLabel.setText(self.pausedString) + self.timer.stop() + elif self.mainWidget.is_recording: + self.controller.record() + log.info("Recording unpaused.") + self.mainWidget.pauseButton.setToolTip(self.pauseString) + self.timer.start(1000) + + def load_backend(self): + """Prepares the backend for recording""" + if self.current_presentation(): + presentation = self.current_presentation() + + # If current presentation is no existant (empty talk database) + # use a default recording name. + else: + presentation = Presentation(title=unicode("default")) + + initialized, self.recently_recorded_video = self.controller.load_backend(presentation) + if initialized: + return True + else: + return False # Error something failed while loading the backend + + def update_timer(self): + """Updates the Elapsed Time displayed. + + Uses the statusLabel for the display. + """ + frmt_time = "%d:%02d" % (self.time_minutes, self.time_seconds) + self.time_seconds += 1 + if self.time_seconds == 60: + self.time_seconds = 0 + self.time_minutes += 1 + + self.mainWidget.statusLabel.setText(u"{} {} --- {} {} --- {}".format(self.elapsedTimeString, + frmt_time, + self.freeSpaceString, + get_free_space(self.config.videodir), + self.recordingString)) + + def reset_timer(self): + """Resets the Elapsed Time.""" + self.time_minutes = 0 + self.time_seconds = 0 + + def toggle_audio_feedback(self, enabled): + """Enables or disables audio feedback according to checkbox state""" + self.config.audio_feedback = enabled + + ### + ### Talk Related + ### + + def set_talk_tooltip(self, talk): + self.mainWidget.talkComboBox.setToolTip(talk) + + def load_event_list(self): + model = self.db.get_events_model() + self.mainWidget.eventComboBox.setModel(model) + + def load_rooms_from_event(self, event): + #self.disconnect(self.mainWidget.roomComboBox, QtCore.SIGNAL('currentIndexChanged(const QString&)'), self.load_talks_from_room) + + self.current_event = event + + model = self.db.get_rooms_model(self.current_event) + self.mainWidget.roomComboBox.setModel(model) + + #self.connect(self.mainWidget.roomComboBox, QtCore.SIGNAL('currentIndexChanged(const QString&)'), self.load_talks_from_room) + + def load_dates_from_event_room(self, change): + event = str(self.mainWidget.eventComboBox.currentText()) + room = str(self.mainWidget.roomComboBox.currentText()) + model = self.db.get_dates_from_event_room_model(event, room) + self.mainWidget.dateComboBox.setModel(model) + + def load_talks_from_date(self, date): + self.current_room = str(self.mainWidget.roomComboBox.currentText()) + self.current_date = date + + model = self.db.get_talks_model(self.current_event, self.current_room, self.current_date) + self.mainWidget.talkComboBox.setModel(model) + + ### + ### Report Failure + ### + def show_report_widget(self): + p = self.current_presentation() + self.reportWidget.titleLabel2.setText(p.title) + self.reportWidget.speakerLabel2.setText(p.speaker) + self.reportWidget.eventLabel2.setText(p.event) + self.reportWidget.roomLabel2.setText(p.room) + self.reportWidget.startTimeLabel2.setText(p.startTime) + self.reportWidget.endTimeLabel2.setText(p.endTime) + + # Get existing report if there is one. + talk_id = self.current_presentation_id() + f = self.db.get_report(talk_id) + if f is not None: + self.reportWidget.commentEdit.setText(f.comment) + i = self.reportWidget.reportCombo.findText(f.indicator) + self.reportWidget.reportCombo.setCurrentIndex(i) + self.reportWidget.releaseCheckBox.setChecked(f.release) + else: + self.reportWidget.commentEdit.setText("") + self.reportWidget.reportCombo.setCurrentIndex(0) + self.reportWidget.releaseCheckBox.setChecked(False) + + self.reportWidget.show() + + def report(self): + talk_id = self.current_presentation_id() + i = self.reportWidget.reportCombo.currentIndex() + + failure = Failure(talk_id, self.reportWidget.commentEdit.text(), self.reportWidget.options[i], self.reportWidget.releaseCheckBox.isChecked()) + log.info("Report Failure: %s, %s, %s, release form? %s" % (talk_id, + self.reportWidget.commentEdit.text(), + self.reportWidget.options[i], + self.reportWidget.releaseCheckBox.isChecked())) + + self.db.insert_failure(failure) + self.reportWidget.close() + + ### + ### Misc. + ### + def _icon_activated(self, reason): + if reason == QtGui.QSystemTrayIcon.Trigger: + self.systray.menu.popup(QCursor.pos()) + if reason == QtGui.QSystemTrayIcon.DoubleClick: + self.toggle_record_button() + + def hide_window(self): + self.geometry = self.saveGeometry() + self.hide() + + def show_window(self): + if (self.geometry is not None): + self.restoreGeometry(self.geometry) + self.show() + + def toggle_window_visibility(self): + """Toggles the visibility of the Recording Main Window.""" + if self.isHidden(): + self.show_window() + self.visibilityAction.setText(self.hideWindowString) + else: + self.hide_window() + self.visibilityAction.setText(self.showWindowString) + + def toggle_record_button(self): + self.mainWidget.standbyButton.toggle() + self.mainWidget.recordButton.toggle() + + def audio_feedback(self, value): + self.mainWidget.audioSlider.setValue(value) + + def open_video_directory(self): + if sys.platform.startswith("linux"): + os.system("xdg-open %s" % self.config.videodir) + elif sys.platform.startswith("win32"): + os.system("explorer %s" % self.config.videodir) + else: + log.info("Error: This command is not supported on the current OS.") + + def closeEvent(self, event): + log.info('Exiting freeseer...') + event.accept() + + ''' + This function plays the most recently recorded video + ''' + def play_video(self): + if sys.platform.startswith("linux"): + subprocess.call(["xdg-open", "{}/{}".format(self.config.videodir, self.recently_recorded_video)]) + if sys.platform.startswith("win32"): + os.system("start {}".format(os.path.join(self.config.videodir, self.recently_recorded_video))) + + ''' + Client functions + ''' + def show_client_widget(self): + self.current_presentation() + self.clientWidget.show() + + ''' + This function is for handling commands sent from the server to the client + ''' + def getAction(self): + message = self.clientWidget.socket.read(self.clientWidget.socket.bytesAvailable()) + if message == 'Record': + self.mainWidget.standbyButton.toggle() + self.mainWidget.recordButton.toggle() + self.clientWidget.sendMessage('Started recording') + log.info("Started recording by server's request") + elif message == 'Stop': + self.mainWidget.recordButton.toggle() + log.info("Stopping recording by server's request") + elif message == 'Pause' or 'Resume': + self.mainWidget.pauseButton.toggle() + if message == 'Pause': + log.info("Paused recording by server's request") + elif message == 'Resume': + log.info("Resumed recording by server's request") + + ### + ### Utility + ### + def open_configtool(self): + self.configToolApp.show() + + def open_talkeditor(self): + self.talkEditorApp.show() + self.load_event_list() diff --git a/src/freeseer/frontend/reporteditor/reporteditor.py b/src/freeseer/frontend/reporteditor/reporteditor.py index 1c14f127..26ec3475 100644 --- a/src/freeseer/frontend/reporteditor/reporteditor.py +++ b/src/freeseer/frontend/reporteditor/reporteditor.py @@ -164,14 +164,14 @@ def add_talk(self): date = self.addTalkWidget.dateEdit.date() startTime = self.addTalkWidget.startTimeEdit.time() datetime = QDateTime(date, startTime) # original "time" is now "startTime" - presentation = Presentation(unicode(self.addTalkWidget.titleLineEdit.text()), - unicode(self.addTalkWidget.presenterLineEdit.text()), + presentation = Presentation(str(self.addTalkWidget.titleLineEdit.text()), + str(self.addTalkWidget.presenterLineEdit.text()), "", # description "", # level - unicode(self.addTalkWidget.eventLineEdit.text()), - unicode(self.addTalkWidget.roomLineEdit.text()), - unicode(datetime.toString()), - unicode(self.addTalkWidget.endTimeEdit.text())) + str(self.addTalkWidget.eventLineEdit.text()), + str(self.addTalkWidget.roomLineEdit.text()), + str(datetime.toString()), + str(self.addTalkWidget.endTimeEdit.text())) # Do not add talks if they are empty strings if (len(presentation.title) == 0): diff --git a/src/freeseer/frontend/reporteditor/reporteditor.py.bak b/src/freeseer/frontend/reporteditor/reporteditor.py.bak new file mode 100644 index 00000000..1c14f127 --- /dev/null +++ b/src/freeseer/frontend/reporteditor/reporteditor.py.bak @@ -0,0 +1,253 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2014 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +import logging + +from PyQt4.QtCore import QDateTime +from PyQt4.QtCore import QString +from PyQt4.QtCore import SIGNAL +from PyQt4.QtGui import QAction +from PyQt4.QtGui import QFileDialog +from PyQt4.QtGui import QHeaderView +from PyQt4.QtGui import QHBoxLayout +from PyQt4.QtGui import QIcon +from PyQt4.QtGui import QMessageBox +from PyQt4.QtGui import QPixmap +from PyQt4.QtGui import QWidget + +try: + _fromUtf8 = QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +from freeseer.framework.presentation import Presentation +from freeseer.frontend.qtcommon.FreeseerApp import FreeseerApp + +from freeseer.frontend.reporteditor.ReportEditorWidget import ReportEditorWidget + +log = logging.getLogger(__name__) + + +class ReportEditorApp(FreeseerApp): + ''' + Freeseer report editor main gui class + ''' + + def __init__(self, config, db): + super(ReportEditorApp, self).__init__(config) + + self.db = db + + icon = QIcon() + icon.addPixmap(QPixmap(_fromUtf8(":/freeseer/logo.png")), QIcon.Normal, QIcon.Off) + self.setWindowIcon(icon) + self.resize(960, 400) + + self.mainWidget = QWidget() + self.mainLayout = QHBoxLayout() + self.mainWidget.setLayout(self.mainLayout) + self.setCentralWidget(self.mainWidget) + + self.editorWidget = ReportEditorWidget() + self.editorWidget.editor.setColumnHidden(5, True) + + self.mainLayout.addWidget(self.editorWidget) + + # Initialize geometry, to be used for restoring window positioning. + self.geometry = None + + # + # Setup Menubar + # + self.actionExportCsv = QAction(self) + self.actionExportCsv.setObjectName(_fromUtf8("actionExportCsv")) + + # Actions + self.menuFile.insertAction(self.actionExit, self.actionExportCsv) + # --- End Menubar + + # + # Report Editor Connections + # + + # Editor Widget + self.connect(self.editorWidget.removeButton, SIGNAL('clicked()'), self.remove_talk) + self.connect(self.editorWidget.clearButton, SIGNAL('clicked()'), self.confirm_reset) + self.connect(self.editorWidget.closeButton, SIGNAL('clicked()'), self.close) + + # Main Window Connections + self.connect(self.actionExportCsv, SIGNAL('triggered()'), self.export_reports_to_csv) + self.connect(self.editorWidget.editor, SIGNAL('clicked (const QModelIndex&)'), self.editorSelectionChanged) + + # Load default language + actions = self.menuLanguage.actions() + for action in actions: + if action.data().toString() == self.config.default_language: + action.setChecked(True) + self.translate(action) + break + + self.load_failures_model() + self.editorWidget.editor.resizeColumnsToContents() + self.editorWidget.editor.resizeRowsToContents() + + ### + ### Translation + ### + def retranslate(self): + self.setWindowTitle(self.app.translate("ReportEditorApp", "Freeseer Report Editor")) + + # + # Reusable Strings + # + self.confirmDBClearTitleString = self.app.translate("ReportEditorApp", "Clear Database") + self.confirmDBClearQuestionString = self.app.translate("ReportEditorApp", "Are you sure you want to clear the DB?") + self.selectFileString = self.app.translate("ReportEditorApp", "Select File") + # --- End Reusable Strings + + # + # Menubar + # + self.actionExportCsv.setText(self.app.translate("ReportEditorApp", "&Export to CSV")) + # --- End Menubar + + # + # EditorWidget + # + self.editorWidget.removeButton.setText(self.app.translate("ReportEditorApp", "Remove")) + self.editorWidget.clearButton.setText(self.app.translate("ReportEditorApp", "Clear")) + self.editorWidget.closeButton.setText(self.app.translate("ReportEditorApp", "Close")) + + self.editorWidget.titleLabel.setText(self.app.translate("ReportEditorApp", "Title:")) + self.editorWidget.speakerLabel.setText(self.app.translate("ReportEditorApp", "Speaker:")) + self.editorWidget.descriptionLabel.setText(self.app.translate("ReportEditorApp", "Description:")) + self.editorWidget.levelLabel.setText(self.app.translate("ReportEditorApp", "Level:")) + self.editorWidget.eventLabel.setText(self.app.translate("ReportEditorApp", "Event:")) + self.editorWidget.roomLabel.setText(self.app.translate("ReportEditorApp", "Room:")) + self.editorWidget.startTimeLabel.setText(self.app.translate("ReportEditorApp", "Start Time:")) + self.editorWidget.endTimeLabel.setText(self.app.translate("ReportEditorApp", "End Time:")) + # --- End EditorWidget + + def load_failures_model(self): + # Load Presentation Model + self.failureModel = self.db.get_failures_model() + editor = self.editorWidget.editor + editor.setModel(self.failureModel) + editor.horizontalHeader().setResizeMode(QHeaderView.Stretch) + + def hide_add_talk_widget(self): + self.editorWidget.setHidden(False) + self.addTalkWidget.setHidden(True) + + def add_talk(self): + date = self.addTalkWidget.dateEdit.date() + startTime = self.addTalkWidget.startTimeEdit.time() + datetime = QDateTime(date, startTime) # original "time" is now "startTime" + presentation = Presentation(unicode(self.addTalkWidget.titleLineEdit.text()), + unicode(self.addTalkWidget.presenterLineEdit.text()), + "", # description + "", # level + unicode(self.addTalkWidget.eventLineEdit.text()), + unicode(self.addTalkWidget.roomLineEdit.text()), + unicode(datetime.toString()), + unicode(self.addTalkWidget.endTimeEdit.text())) + + # Do not add talks if they are empty strings + if (len(presentation.title) == 0): + return + + self.db.insert_presentation(presentation) + + # cleanup + self.addTalkWidget.titleLineEdit.clear() + self.addTalkWidget.presenterLineEdit.clear() + + self.failureModel.select() + + self.hide_add_talk_widget() + + def remove_talk(self): + try: + row_clicked = self.editorWidget.editor.currentIndex().row() + except: + return + + self.failureModel.removeRow(row_clicked) + self.failureModel.select() + + def reset(self): + self.db.clear_report_db() + self.failureModel.select() + + def confirm_reset(self): + """ + Presents a confirmation dialog to ask the user if they are sure they + wish to remove the report database. + + If Yes call the reset() function. + """ + confirm = QMessageBox.question(self, + self.confirmDBClearTitleString, + self.confirmDBClearQuestionString, + QMessageBox.Yes | + QMessageBox.No, + QMessageBox.No) + + if confirm == QMessageBox.Yes: + self.reset() + + def closeEvent(self, event): + log.info('Exiting report editor...') + self.geometry = self.saveGeometry() + event.accept() + + def editorSelectionChanged(self, index): + talkId = self.failureModel.record(index.row()).value(0).toString() + self.updatePresentationInfo(talkId) + + def updatePresentationInfo(self, talkId): + p = self.db.get_presentation(talkId) + if p is not None: + self.editorWidget.titleLabel2.setText(p.title) + self.editorWidget.speakerLabel2.setText(p.speaker) + self.editorWidget.descriptionLabel2.setText(p.description) + self.editorWidget.levelLabel2.setText(p.level) + self.editorWidget.eventLabel2.setText(p.event) + self.editorWidget.roomLabel2.setText(p.room) + self.editorWidget.startTimeLabel2.setText(p.startTime) + self.editorWidget.endTimeLabel2.setText(p.endTime) + else: + self.editorWidget.titleLabel2.setText("Talk not found") + self.editorWidget.speakerLabel2.setText("Talk not found") + self.editorWidget.descriptionLabel2.setText("Talk not found") + self.editorWidget.levelLabel2.setText("Talk not found") + self.editorWidget.eventLabel2.setText("Talk not found") + self.editorWidget.roomLabel2.setText("Talk not found") + self.editorWidget.startTimeLabel2.setText("Talk not found") + self.editorWidget.endTimeLabel2.setText("Talk not found") + + def export_reports_to_csv(self): + fname = QFileDialog.getSaveFileName(self, self.selectFileString, "", "*.csv") + if fname: + self.db.export_reports_to_csv(fname) diff --git a/src/freeseer/frontend/talkeditor/talkeditor.py b/src/freeseer/frontend/talkeditor/talkeditor.py index 4f713bb2..e87daa43 100644 --- a/src/freeseer/frontend/talkeditor/talkeditor.py +++ b/src/freeseer/frontend/talkeditor/talkeditor.py @@ -399,18 +399,18 @@ def create_presentation(self, talkDetailsWidget): startTime = talkDetailsWidget.startTimeEdit.time() endTime = talkDetailsWidget.endTimeEdit.time() - title = unicode(talkDetailsWidget.titleLineEdit.text()).strip() + title = str(talkDetailsWidget.titleLineEdit.text()).strip() if title: return Presentation( - unicode(talkDetailsWidget.titleLineEdit.text()).strip(), - unicode(talkDetailsWidget.presenterLineEdit.text()).strip(), - unicode(talkDetailsWidget.descriptionTextEdit.toPlainText()).strip(), - unicode(talkDetailsWidget.categoryLineEdit.text()).strip(), - unicode(talkDetailsWidget.eventLineEdit.text()).strip(), - unicode(talkDetailsWidget.roomLineEdit.text()).strip(), - unicode(date.toString(Qt.ISODate)), - unicode(startTime.toString(Qt.ISODate)), - unicode(endTime.toString(Qt.ISODate))) + str(talkDetailsWidget.titleLineEdit.text()).strip(), + str(talkDetailsWidget.presenterLineEdit.text()).strip(), + str(talkDetailsWidget.descriptionTextEdit.toPlainText()).strip(), + str(talkDetailsWidget.categoryLineEdit.text()).strip(), + str(talkDetailsWidget.eventLineEdit.text()).strip(), + str(talkDetailsWidget.roomLineEdit.text()).strip(), + str(date.toString(Qt.ISODate)), + str(startTime.toString(Qt.ISODate)), + str(endTime.toString(Qt.ISODate))) def show_new_talk_popup(self): """Displays a modal dialog with a talk details view @@ -495,7 +495,7 @@ def confirm_reset(self): self.reset() def add_talks_from_rss(self): - rss_url = unicode(self.importTalksWidget.rssLineEdit.text()) + rss_url = str(self.importTalksWidget.rssLineEdit.text()) if rss_url: self.db.add_talks_from_rss(rss_url) self.presentationModel.select() diff --git a/src/freeseer/frontend/talkeditor/talkeditor.py.bak b/src/freeseer/frontend/talkeditor/talkeditor.py.bak new file mode 100644 index 00000000..4f713bb2 --- /dev/null +++ b/src/freeseer/frontend/talkeditor/talkeditor.py.bak @@ -0,0 +1,626 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2014 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +# python-libs +import logging + +# PyQt modules +from PyQt4.QtCore import SIGNAL +from PyQt4.QtCore import QPersistentModelIndex +from PyQt4.QtCore import QStringList +from PyQt4.QtCore import Qt +from PyQt4.QtGui import QAbstractItemView +from PyQt4.QtGui import QAction +from PyQt4.QtGui import QCompleter +from PyQt4.QtGui import QDataWidgetMapper +from PyQt4.QtGui import QFileDialog +from PyQt4.QtGui import QHeaderView +from PyQt4.QtGui import QIcon +from PyQt4.QtGui import QMessageBox +from PyQt4.QtGui import QPixmap +from PyQt4.QtGui import QSortFilterProxyModel +from PyQt4.QtGui import QTableView +from PyQt4.QtGui import QVBoxLayout +from PyQt4.QtGui import QWidget + +# Freeseer modules +from freeseer.framework.presentation import Presentation +from freeseer.frontend.qtcommon.FreeseerApp import FreeseerApp + +# TalkEditor modules +from freeseer.frontend.talkeditor.CommandButtons import CommandButtons +from freeseer.frontend.talkeditor.TalkDetailsWidget import TalkDetailsWidget +from freeseer.frontend.talkeditor.NewTalkWidget import NewTalkWidget +from freeseer.frontend.talkeditor.ImportTalksWidget import ImportTalksWidget + +log = logging.getLogger(__name__) + + +class TalkEditorApp(FreeseerApp): + '''Freeseer talk database editor main gui class''' + def __init__(self, config, db): + super(TalkEditorApp, self).__init__(config) + + self.db = db + + icon = QIcon() + icon.addPixmap(QPixmap(':/freeseer/logo.png'), QIcon.Normal, QIcon.Off) + self.setWindowIcon(icon) + self.resize(960, 600) + + # + # Setup Layout + # + self.mainWidget = QWidget() + self.mainLayout = QVBoxLayout() + self.mainWidget.setLayout(self.mainLayout) + self.setCentralWidget(self.mainWidget) + self.mainLayout.setAlignment(Qt.AlignTop) + + # Add custom widgets + self.commandButtons = CommandButtons() + self.tableView = QTableView() + self.tableView.setSortingEnabled(True) + self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) + self.talkDetailsWidget = TalkDetailsWidget() + self.importTalksWidget = ImportTalksWidget() + self.newTalkWidget = NewTalkWidget() + self.mainLayout.addWidget(self.importTalksWidget) + #self.mainLayout.addLayout(self.titleLayout) + self.mainLayout.addWidget(self.commandButtons) + self.mainLayout.addWidget(self.tableView) + self.mainLayout.addWidget(self.talkDetailsWidget) + self.mainLayout.addWidget(self.importTalksWidget) + # --- End Layout + + # Keep track of index of the most recently selected talk + self.currentTalkIndex = QPersistentModelIndex() + + # Prompt user to "Continue Editing", "Discard Changes" or "Save Changes" + self.savePromptBox = QMessageBox() + self.savePromptBox.setWindowTitle("Unsaved Changes Exist") + self.savePromptBox.setIcon(QMessageBox.Information) + self.savePromptBox.setText("The talk you were editing has unsaved changes.") + self.continueButton = self.savePromptBox.addButton("Continue Editing", QMessageBox.RejectRole) + self.discardButton = self.savePromptBox.addButton("Discard Changes", QMessageBox.DestructiveRole) + self.saveButton = self.savePromptBox.addButton("Save Changes", QMessageBox.AcceptRole) + self.savePromptBox.setDefaultButton(self.saveButton) + + # Initialize geometry, to be used for restoring window positioning. + self.geometry = None + + # + # Setup Menubar + # + self.actionExportCsv = QAction(self) + self.actionExportCsv.setObjectName('actionExportCsv') + self.actionRemoveAll = QAction(self) + self.actionRemoveAll.setObjectName('actionRemoveAll') + + # Actions + self.menuFile.insertAction(self.actionExit, self.actionExportCsv) + self.menuFile.insertAction(self.actionExit, self.actionRemoveAll) + # --- End Menubar + + # + # TableView Connections + # + self.connect(self.tableView, SIGNAL('activated(const QModelIndex)'), self.click_talk) + self.connect(self.tableView, SIGNAL('selected(const QModelIndex)'), self.click_talk) + self.connect(self.tableView, SIGNAL('clicked(const QModelIndex)'), self.click_talk) + + # Import Widget + self.connect(self.importTalksWidget.csvRadioButton, SIGNAL('toggled(bool)'), self.toggle_import) + self.connect(self.importTalksWidget.importButton, SIGNAL('clicked()'), self.import_talks) + self.connect(self.importTalksWidget.cancelButton, SIGNAL('clicked()'), self.hide_import_talks_widget) + self.importTalksWidget.setHidden(True) + self.connect(self.importTalksWidget.csvFileSelectButton, SIGNAL('clicked()'), self.csv_file_select) + self.connect(self.importTalksWidget.csvLineEdit, SIGNAL('returnPressed()'), + self.importTalksWidget.importButton.click) + self.connect(self.importTalksWidget.rssLineEdit, SIGNAL('returnPressed()'), + self.importTalksWidget.importButton.click) + self.connect(self.actionExportCsv, SIGNAL('triggered()'), self.export_talks_to_csv) + self.connect(self.actionRemoveAll, SIGNAL('triggered()'), self.confirm_reset) + + # Command Buttons + self.connect(self.commandButtons.addButton, SIGNAL('clicked()'), self.click_add_button) + self.connect(self.commandButtons.removeButton, SIGNAL('clicked()'), self.remove_talk) + self.connect(self.commandButtons.removeAllButton, SIGNAL('clicked()'), self.confirm_reset) + self.connect(self.commandButtons.importButton, SIGNAL('clicked()'), self.show_import_talks_widget) + self.connect(self.commandButtons.exportButton, SIGNAL('clicked()'), self.export_talks_to_csv) + self.connect(self.commandButtons.searchButton, SIGNAL('clicked()'), self.search_talks) + self.connect(self.commandButtons.searchLineEdit, SIGNAL('textEdited(QString)'), self.search_talks) + self.connect(self.commandButtons.searchLineEdit, SIGNAL('returnPressed()'), self.search_talks) + + # Talk Details Buttons + self.connect(self.talkDetailsWidget.saveButton, SIGNAL('clicked()'), self.update_talk) + + # Talk Details Widget + self.connect(self.talkDetailsWidget.titleLineEdit, SIGNAL('textEdited(const QString)'), self.enable_save) + self.connect(self.talkDetailsWidget.presenterLineEdit, SIGNAL('textEdited(const QString)'), self.enable_save) + self.connect(self.talkDetailsWidget.categoryLineEdit, SIGNAL('textEdited(const QString)'), self.enable_save) + self.connect(self.talkDetailsWidget.eventLineEdit, SIGNAL('textEdited(const QString)'), self.enable_save) + self.connect(self.talkDetailsWidget.roomLineEdit, SIGNAL('textEdited(const QString)'), self.enable_save) + self.connect(self.talkDetailsWidget.descriptionTextEdit, SIGNAL('modificationChanged(bool)'), self.enable_save) + self.connect(self.talkDetailsWidget.dateEdit, SIGNAL('dateChanged(const QDate)'), self.enable_save) + self.connect(self.talkDetailsWidget.startTimeEdit, SIGNAL('timeChanged(const QTime)'), self.enable_save) + self.connect(self.talkDetailsWidget.endTimeEdit, SIGNAL('timeChanged(const QTime)'), self.enable_save) + + # New Talk Widget + self.newTalkWidget.connect(self.newTalkWidget.addButton, SIGNAL('clicked()'), self.add_talk) + self.newTalkWidget.connect(self.newTalkWidget.cancelButton, SIGNAL('clicked()'), self.newTalkWidget.reject) + + # Load default language + actions = self.menuLanguage.actions() + for action in actions: + if action.data().toString() == self.config.default_language: + action.setChecked(True) + self.translate(action) + break + + # Load Talk Database + self.load_presentations_model() + + # Setup Autocompletion + self.update_autocomplete_fields() + + self.talkDetailsWidget.saveButton.setEnabled(False) + + # Select first item + #self.tableView.setCurrentIndex(self.proxy.index(0,0)) + #self.talk_selected(self.proxy.index(0,0)) + + # + # Translation + # + def retranslate(self): + self.setWindowTitle(self.app.translate("TalkEditorApp", "Freeseer Talk Editor")) + + # + # Reusable Strings + # + self.confirmDBClearTitleString = self.app.translate("TalkEditorApp", "Remove All Talks from Database") + self.confirmDBClearQuestionString = self.app.translate("TalkEditorApp", + "Are you sure you want to clear the DB?") + self.confirmTalkDetailsClearTitleString = self.app.translate("TalkEditorApp", "Unsaved Data") + self.confirmTalkDetailsClearQuestionString = self.app.translate("TalkEditorApp", + "Unsaved talk details will be lost. Continue?") + # --- End Reusable Strings + + # + # Menubar + # + self.actionExportCsv.setText(self.app.translate("TalkEditorApp", "&Export to CSV")) + self.actionRemoveAll.setText(self.app.translate("TalkEditorApp", "&Remove All Talks")) + + # --- End Menubar + + # + # TalkDetailsWidget + # + self.talkDetailsWidget.titleLabel.setText(self.app.translate("TalkEditorApp", "Title")) + self.talkDetailsWidget.presenterLabel.setText(self.app.translate("TalkEditorApp", "Presenter")) + self.talkDetailsWidget.categoryLabel.setText(self.app.translate("TalkEditorApp", "Category")) + self.talkDetailsWidget.eventLabel.setText(self.app.translate("TalkEditorApp", "Event")) + self.talkDetailsWidget.roomLabel.setText(self.app.translate("TalkEditorApp", "Room")) + self.talkDetailsWidget.dateLabel.setText(self.app.translate("TalkEditorApp", "Date")) + self.talkDetailsWidget.startTimeLabel.setText(self.app.translate("TalkEditorApp", "Start Time")) + self.talkDetailsWidget.endTimeLabel.setText(self.app.translate("TalkEditorApp", "End Time")) + # --- End TalkDetailsWidget + + # + # Import Talks Widget Translations + # + self.importTalksWidget.rssRadioButton.setText(self.app.translate("TalkEditorApp", "RSS URL")) + self.importTalksWidget.csvRadioButton.setText(self.app.translate("TalkEditorApp", "CSV File")) + self.importTalksWidget.importButton.setText(self.app.translate("TalkEditorApp", "Import")) + # --- End Talks Widget Translations + + # + # Command Button Translations\ + # + self.commandButtons.importButton.setText(self.app.translate("TalkEditorApp", "Import")) + self.commandButtons.exportButton.setText(self.app.translate("TalkEditorApp", "Export")) + self.commandButtons.addButton.setText(self.app.translate("TalkEditorApp", "Add New Talk")) + self.commandButtons.removeButton.setText(self.app.translate("TalkEditorApp", "Remove")) + self.commandButtons.removeAllButton.setText(self.app.translate("TalkEditorApp", "Remove All")) + # --- End Command Butotn Translations + + # + # Search Widget Translations + # + self.commandButtons.searchButton.setText(self.app.translate("TalkEditorApp", "Search")) + # --- End Command Button Translations + + def load_presentations_model(self): + # Load Presentation Model + self.presentationModel = self.db.get_presentations_model() + self.proxy = QSortFilterProxyModel() + self.proxy.setSourceModel(self.presentationModel) + self.tableView.setModel(self.proxy) + self.proxy.setFilterCaseSensitivity(Qt.CaseInsensitive) + + # Fill table whitespace. + self.tableView.horizontalHeader().setStretchLastSection(False) + for i in range(2, self.tableView.horizontalHeader().count() + 1): + self.tableView.horizontalHeader().resizeSection(i, self.set_width_with_dpi(80)) + self.tableView.horizontalHeader().setResizeMode(1, QHeaderView.Stretch) + + # Hide the ID field + self.tableView.setColumnHidden(0, True) + + # Map data to widgets + self.mapper = QDataWidgetMapper() + self.mapper.setModel(self.proxy) + self.mapper.setSubmitPolicy(QDataWidgetMapper.ManualSubmit) + self.mapper.addMapping(self.talkDetailsWidget.titleLineEdit, 1) + self.mapper.addMapping(self.talkDetailsWidget.presenterLineEdit, 2) + self.mapper.addMapping(self.talkDetailsWidget.categoryLineEdit, 4) + self.mapper.addMapping(self.talkDetailsWidget.eventLineEdit, 5) + self.mapper.addMapping(self.talkDetailsWidget.roomLineEdit, 6) + self.mapper.addMapping(self.talkDetailsWidget.descriptionTextEdit, 3) + self.mapper.addMapping(self.talkDetailsWidget.dateEdit, 7) + self.mapper.addMapping(self.talkDetailsWidget.startTimeEdit, 8) + self.mapper.addMapping(self.talkDetailsWidget.endTimeEdit, 9) + + # Load StringLists + self.titleList = QStringList(self.db.get_string_list("Title")) + #self.speakerList = QStringList(self.db.get_speaker_list()) + #self.categoryList = QStringList(self.db.get_category_list()) + #self.eventList = QStringList(self.db.get_event_list()) + #self.roomList = QStringList(self.db.get_room_list()) + + #Disble input + self.talkDetailsWidget.disable_input_fields() + + def search_talks(self): + # The default value is 0. If the value is -1, the keys will be read from all columns. + self.proxy.setFilterKeyColumn(-1) + self.proxy.setFilterFixedString(self.commandButtons.searchLineEdit.text()) + + def show_save_prompt(self): + """Prompts the user to save or discard changes, or continue editing.""" + self.savePromptBox.exec_() + self.savePromptBox.setDefaultButton(self.saveButton) + return self.savePromptBox.clickedButton() + + def click_talk(self, model): + """Warns user if there are unsaved changes, and selects talk clicked by the user.""" + log.info("Selecting row %d", model.row()) + modelRow = model.row() + if self.unsaved_details_exist(): + log.info("Unsaved changes exist in row %d", self.currentTalkIndex.row()) + confirm = self.show_save_prompt() + if confirm == self.saveButton: + log.info("Saving changes in row %d...", self.currentTalkIndex.row()) + self.tableView.selectRow(self.currentTalkIndex.row()) + self.update_talk() + newModel = self.tableView.currentIndex().sibling(modelRow, 0) + self.select_talk(newModel) + elif confirm == self.discardButton: + log.info("Discarding changes in row %d...", self.currentTalkIndex.row()) + self.talk_selected(model) + else: + log.info("Continue editing row %d", self.currentTalkIndex.row()) + self.tableView.selectRow(self.currentTalkIndex.row()) + else: + self.talk_selected(model) + + def click_add_button(self): + """Warns user if there are unsaved changes, and shows the New Talk window.""" + if self.unsaved_details_exist(): + log.info("Unsaved changes exist in row %d", self.currentTalkIndex.row()) + confirm = self.show_save_prompt() + if confirm == self.saveButton: + log.info("Saving changes in row %d...", self.currentTalkIndex.row()) + self.update_talk() + self.show_new_talk_popup() + elif confirm == self.discardButton: + log.info("Discarding changes in row %d...", self.currentTalkIndex.row()) + # Ensure that changes are discarded + self.talk_selected(self.currentTalkIndex) + self.show_new_talk_popup() + else: + log.info("Continue editing row %d", self.currentTalkIndex.row()) + else: + self.show_new_talk_popup() + + def talk_selected(self, model): + self.mapper.setCurrentIndex(model.row()) + self.talkDetailsWidget.enable_input_fields() + self.talkDetailsWidget.saveButton.setEnabled(False) + self.currentTalkIndex = QPersistentModelIndex(model) + + def toggle_import(self): + if self.importTalksWidget.csvRadioButton.isChecked(): + self.importTalksWidget.csvLineEdit.setEnabled(True) + self.importTalksWidget.csvFileSelectButton.setEnabled(True) + self.importTalksWidget.rssLineEdit.setEnabled(False) + else: + self.importTalksWidget.csvLineEdit.setEnabled(False) + self.importTalksWidget.csvFileSelectButton.setEnabled(False) + self.importTalksWidget.rssLineEdit.setEnabled(True) + + def show_import_talks_widget(self): + self.commandButtons.setHidden(True) + self.tableView.setHidden(True) + self.talkDetailsWidget.setHidden(True) + self.importTalksWidget.setHidden(False) + + def hide_import_talks_widget(self): + self.commandButtons.setHidden(False) + self.tableView.setHidden(False) + self.talkDetailsWidget.setHidden(False) + self.importTalksWidget.setHidden(True) + + def add_talk(self): + """Adds a new talk to the database using data from the NewTalkWidget input fields""" + presentation = self.create_presentation(self.newTalkWidget.talkDetailsWidget) + + if presentation: + self.db.insert_presentation(presentation) + self.newTalkWidget.accept() # Close the dialog + + def update_talk(self): + """Updates the currently selected talk using data from the TalkEditorApp input fields""" + selected_talk = self.tableView.currentIndex() + if selected_talk.row() >= 0: # The tableView index begins at 0 and is -1 by default + talk_id = selected_talk.sibling(selected_talk.row(), 0).data().toString() + presentation = self.create_presentation(self.talkDetailsWidget) + + if presentation: + self.db.update_presentation(talk_id, presentation) + self.apply_changes(selected_talk) + self.talkDetailsWidget.saveButton.setEnabled(False) + + def create_presentation(self, talkDetailsWidget): + """Creates and returns an instance of Presentation using data from the input fields""" + date = talkDetailsWidget.dateEdit.date() + startTime = talkDetailsWidget.startTimeEdit.time() + endTime = talkDetailsWidget.endTimeEdit.time() + + title = unicode(talkDetailsWidget.titleLineEdit.text()).strip() + if title: + return Presentation( + unicode(talkDetailsWidget.titleLineEdit.text()).strip(), + unicode(talkDetailsWidget.presenterLineEdit.text()).strip(), + unicode(talkDetailsWidget.descriptionTextEdit.toPlainText()).strip(), + unicode(talkDetailsWidget.categoryLineEdit.text()).strip(), + unicode(talkDetailsWidget.eventLineEdit.text()).strip(), + unicode(talkDetailsWidget.roomLineEdit.text()).strip(), + unicode(date.toString(Qt.ISODate)), + unicode(startTime.toString(Qt.ISODate)), + unicode(endTime.toString(Qt.ISODate))) + + def show_new_talk_popup(self): + """Displays a modal dialog with a talk details view + + When Add is selected, a new talk is added to the database using the input field data. + When Cancel is selected, no talk is added. + """ + log.info('Opening Add Talk window...') + self.clear_new_talk_fields() + self.remove_new_talk_placeholder_text() + self.newTalkWidget.talkDetailsWidget.titleLineEdit.setFocus() + if self.newTalkWidget.exec_() == 1: + self.apply_changes() + self.talkDetailsWidget.disable_input_fields() + else: + log.info('No talk added...') + + def apply_changes(self, updated_talk=None): + """Repopulates the model to display the effective changes + + Updates the autocomplete fields. + Displays the updated model in the table view, and selects the newly updated/added talk. + """ + self.presentationModel.select() + self.select_talk(updated_talk) + self.update_autocomplete_fields() + + def select_talk(self, talk=None): + """Selects the given talk in the table view + + If no talk is given, the last row in the table view is selected. + """ + if talk: + row = talk.row() + column = talk.column() + else: + row = self.presentationModel.rowCount() - 1 # Select last row + column = 0 + + self.tableView.selectRow(row) + self.tableView.setCurrentIndex(self.proxy.index(row, column)) + self.talk_selected(self.proxy.index(row, column)) + + def remove_talk(self): + try: + rows_selected = self.tableView.selectionModel().selectedRows() + except: + return + + # Reversed because rows in list change position once row is removed + for row in reversed(rows_selected): + self.presentationModel.removeRow(row.row()) + self.talkDetailsWidget.clear_input_fields() + self.talkDetailsWidget.disable_input_fields() + + def load_talk(self): + try: + self.tableView.currentIndex().row() + except: + return + + self.mapper.addMapping(self.talkDetailsWidget.roomLineEdit, 6) + self.presentationModel.select() + + def reset(self): + self.db.clear_database() + self.presentationModel.select() + self.talkDetailsWidget.clear_input_fields() + self.talkDetailsWidget.disable_input_fields() + + def confirm_reset(self): + """Presents a confirmation dialog to ask the user if they are sure they wish to remove the talk database. + If Yes call the reset() function""" + confirm = QMessageBox.question(self, + self.confirmDBClearTitleString, + self.confirmDBClearQuestionString, + QMessageBox.Yes | + QMessageBox.No, + QMessageBox.No) + + if confirm == QMessageBox.Yes: + self.reset() + + def add_talks_from_rss(self): + rss_url = unicode(self.importTalksWidget.rssLineEdit.text()) + if rss_url: + self.db.add_talks_from_rss(rss_url) + self.presentationModel.select() + self.hide_import_talks_widget() + else: + error = QMessageBox() + error.setText("Please enter a RSS URL") + error.exec_() + + def closeEvent(self, event): + if self.unsaved_details_exist(): + log.info("Unsaved changes exist in row %d", self.currentTalkIndex.row()) + confirm = self.show_save_prompt() + if confirm == self.saveButton: + log.info("Saving changes in row %d...", self.currentTalkIndex.row()) + self.update_talk() + log.info('Exiting talk database editor...') + self.geometry = self.saveGeometry() + event.accept() + elif confirm == self.discardButton: + log.info("Discarding changes in row %d...", self.currentTalkIndex.row()) + # Ensure that changes are discarded + self.talk_selected(self.currentTalkIndex) + log.info('Exiting talk database editor...') + self.geometry = self.saveGeometry() + event.accept() + else: + log.info("Continue editing row %d", self.currentTalkIndex.row()) + event.ignore() + else: + log.info('Exiting talk database editor...') + self.geometry = self.saveGeometry() + event.accept() + + def csv_file_select(self): + fname = QFileDialog.getOpenFileName( + self, 'Select file', "", "*.csv") + if fname: + self.importTalksWidget.csvLineEdit.setText(fname) + + def add_talks_from_csv(self): + fname = self.importTalksWidget.csvLineEdit.text() + + if fname: + self.db.add_talks_from_csv(fname) + self.presentationModel.select() + self.hide_import_talks_widget() + else: + error = QMessageBox() + error.setText("Please select a file") + error.exec_() + + def import_talks(self): + if self.importTalksWidget.csvRadioButton.isChecked(): + self.add_talks_from_csv() + else: + self.add_talks_from_rss() + + self.update_autocomplete_fields() + + def export_talks_to_csv(self): + fname = QFileDialog.getSaveFileName(self, 'Select file', "", "*.csv") + if fname: + self.db.export_talks_to_csv(fname) + + def update_autocomplete_fields(self): + self.titleList = QStringList(self.db.get_string_list("Title")) + self.speakerList = QStringList(self.db.get_string_list("Speaker")) + self.categoryList = QStringList(self.db.get_string_list("Category")) + self.eventList = QStringList(self.db.get_string_list("Event")) + self.roomList = QStringList(self.db.get_string_list("Room")) + + self.titleCompleter = QCompleter(self.titleList) + self.titleCompleter.setCaseSensitivity(Qt.CaseInsensitive) + self.speakerCompleter = QCompleter(self.speakerList) + self.speakerCompleter.setCaseSensitivity(Qt.CaseInsensitive) + self.categoryCompleter = QCompleter(self.categoryList) + self.categoryCompleter.setCaseSensitivity(Qt.CaseInsensitive) + self.eventCompleter = QCompleter(self.eventList) + self.eventCompleter.setCaseSensitivity(Qt.CaseInsensitive) + self.roomCompleter = QCompleter(self.roomList) + self.roomCompleter.setCaseSensitivity(Qt.CaseInsensitive) + + self.talkDetailsWidget.titleLineEdit.setCompleter(self.titleCompleter) + self.talkDetailsWidget.presenterLineEdit.setCompleter(self.speakerCompleter) + self.talkDetailsWidget.categoryLineEdit.setCompleter(self.categoryCompleter) + self.talkDetailsWidget.eventLineEdit.setCompleter(self.eventCompleter) + self.talkDetailsWidget.roomLineEdit.setCompleter(self.roomCompleter) + + def are_fields_enabled(self): + return (self.talkDetailsWidget.titleLineEdit.isEnabled() and + self.talkDetailsWidget.presenterLineEdit.isEnabled() and + self.talkDetailsWidget.categoryLineEdit.isEnabled() and + self.talkDetailsWidget.eventLineEdit.isEnabled() and + self.talkDetailsWidget.roomLineEdit.isEnabled() and + self.talkDetailsWidget.dateEdit.isEnabled() and + self.talkDetailsWidget.startTimeEdit.isEnabled() and + self.talkDetailsWidget.endTimeEdit.isEnabled()) + + def unsaved_details_exist(self): + """Checks if changes have been made to new/existing talk details + + Looks for text in the input fields and check the enabled state of the Save Talk button + If the Save Talk button is enabled, the input fields contain modified values + """ + return (self.talkDetailsWidget.saveButton.isEnabled() and + (self.talkDetailsWidget.titleLineEdit.text() or + self.talkDetailsWidget.presenterLineEdit.text() or + self.talkDetailsWidget.categoryLineEdit.text() or + self.talkDetailsWidget.descriptionTextEdit.toPlainText())) + + def enable_save(self): + self.talkDetailsWidget.saveButton.setEnabled(True) + + def clear_new_talk_fields(self): + """Removes existing data from all NewTalkWidget fields except event, room, date and time""" + self.newTalkWidget.talkDetailsWidget.titleLineEdit.clear() + self.newTalkWidget.talkDetailsWidget.presenterLineEdit.clear() + self.newTalkWidget.talkDetailsWidget.descriptionTextEdit.clear() + self.newTalkWidget.talkDetailsWidget.categoryLineEdit.clear() + + def remove_new_talk_placeholder_text(self): + """Removes placeholder text in NewTalkWidget originally set by TalkDetailsWidget""" + self.newTalkWidget.talkDetailsWidget.titleLineEdit.setPlaceholderText("") + self.newTalkWidget.talkDetailsWidget.presenterLineEdit.setPlaceholderText("") + self.newTalkWidget.talkDetailsWidget.categoryLineEdit.setPlaceholderText("") + self.newTalkWidget.talkDetailsWidget.eventLineEdit.setPlaceholderText("") + self.newTalkWidget.talkDetailsWidget.roomLineEdit.setPlaceholderText("") diff --git a/src/freeseer/frontend/upload/youtube.py b/src/freeseer/frontend/upload/youtube.py index 682b8517..edf6f17c 100644 --- a/src/freeseer/frontend/upload/youtube.py +++ b/src/freeseer/frontend/upload/youtube.py @@ -51,11 +51,11 @@ def get_defaults(): def handle_response(response_code, response): """Process the response from the Youtube API""" if response_code is Response.SUCCESS: - print("The file was successfully uploaded with video id: {}".format(response['id'])) + print(("The file was successfully uploaded with video id: {}".format(response['id']))) elif response_code is Response.UNEXPECTED_FAILURE: - print("The file failed to upload with unexpected response: {}".format(response)) + print(("The file failed to upload with unexpected response: {}".format(response))) elif response_code is Response.UNRETRIABLE_ERROR: - print("An unretriable HTTP error {} occurred:\n{}".format(response['status'], response['content'])) + print(("An unretriable HTTP error {} occurred:\n{}".format(response['status'], response['content']))) elif response_code is Response.MAX_RETRIES_REACHED: print("The maximum number of retries has been reached") elif response_code is Response.ACCESS_TOKEN_ERROR: @@ -88,9 +88,9 @@ def prompt_user(videos, confirmation=False): """ if not confirmation: print("Found videos:") - print("\n".join(videos)) + print(("\n".join(videos))) question = "Are you sure you would like to upload these videos? [Y/n]" - confirmation = raw_input(question).lower() in ('', 'y', 'yes') + confirmation = input(question).lower() in ('', 'y', 'yes') return confirmation @@ -106,7 +106,7 @@ def upload(files, token, assume_yes): """ # check if token exists if not os.path.exists(token): - print("{} does not exist, please specify a valid token file".format(token)) + print(("{} does not exist, please specify a valid token file".format(token))) else: # Gather videos specified and vids from folders specified into list videos = gather_videos(files) diff --git a/src/freeseer/frontend/upload/youtube.py.bak b/src/freeseer/frontend/upload/youtube.py.bak new file mode 100644 index 00000000..682b8517 --- /dev/null +++ b/src/freeseer/frontend/upload/youtube.py.bak @@ -0,0 +1,126 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + + +import os + +from freeseer import settings +from freeseer.framework.youtube import Response +from freeseer.framework.youtube import YoutubeService + + +def get_defaults(): + """Retrieve the defaults value for client_secrets, video folder, etc. + + Returns: + dictionary of default values which are: + client_secrets: ~//client_secrets.json + oauth2_token: ~//oauth2_token.json + video_directory: ~/Videos + """ + profile = settings.profile_manager.get("default") + config = profile.get_config('freeseer.conf', settings.FreeseerConfig, storage_args=['Global'], read_only=True) + return { + "video_directory": config.videodir, + "oauth2_token": os.path.join(settings.configdir, "oauth2_token.json"), + "client_secrets": os.path.join(settings.configdir, "client_secrets.json") + } + + +def handle_response(response_code, response): + """Process the response from the Youtube API""" + if response_code is Response.SUCCESS: + print("The file was successfully uploaded with video id: {}".format(response['id'])) + elif response_code is Response.UNEXPECTED_FAILURE: + print("The file failed to upload with unexpected response: {}".format(response)) + elif response_code is Response.UNRETRIABLE_ERROR: + print("An unretriable HTTP error {} occurred:\n{}".format(response['status'], response['content'])) + elif response_code is Response.MAX_RETRIES_REACHED: + print("The maximum number of retries has been reached") + elif response_code is Response.ACCESS_TOKEN_ERROR: + print("The access token has expired or been revoked, please run python -m freeseer config youtube") + + +def gather_videos(files): + """Gather all valid videos into a set for upload""" + # Because we are using a set, no duplicates will be present + videos = set() + for item in files: + # Crawl subfolders + if os.path.isdir(item): + for root, _, filenames in os.walk(item): + for filename in filenames: + filepath = os.path.join(root, filename) + # Check if its a video + if YoutubeService.valid_video_file(filepath): + videos.add(filepath) + # If it exists it is a single file, check if its a video + elif os.path.exists(item) and YoutubeService.valid_video_file(item): + videos.add(item) + return videos + + +def prompt_user(videos, confirmation=False): + """Method to prompt user for confirmation, if yes is specified then no prompt is shown + + Returns: boolean value of final decision + """ + if not confirmation: + print("Found videos:") + print("\n".join(videos)) + question = "Are you sure you would like to upload these videos? [Y/n]" + confirmation = raw_input(question).lower() in ('', 'y', 'yes') + return confirmation + + +def upload(files, token, assume_yes): + """Uploads a file(s) to YouTube using the YouTube service API + + This function uploads a list of videos and/or directories of videos to YouTube. + + Args: + token - location of an oauth2 token + files - list of files and directories to upload + assume_yes - if True, assume yes to all interaction (default: False) + """ + # check if token exists + if not os.path.exists(token): + print("{} does not exist, please specify a valid token file".format(token)) + else: + # Gather videos specified and vids from folders specified into list + videos = gather_videos(files) + # Now begin upload process + if not videos: + print("Nothing to upload") + # Prompt for confirmation + elif prompt_user(videos, confirmation=assume_yes): + youtube_service = YoutubeService() + # Authorize with OAuth2 token + youtube_service.authorize(token) + for video in videos: + response_code, response = youtube_service.upload_video(video) + handle_response(response_code, response) + # Response was no, so do nothing + else: + print("Exiting...") diff --git a/src/freeseer/plugins/audioinput/jackaudiosrc/__init__.py b/src/freeseer/plugins/audioinput/jackaudiosrc/__init__.py index 7df368e4..cbeac084 100644 --- a/src/freeseer/plugins/audioinput/jackaudiosrc/__init__.py +++ b/src/freeseer/plugins/audioinput/jackaudiosrc/__init__.py @@ -41,7 +41,7 @@ import freeseer.framework.config.options as options # .freeseer-plugin custom -import widget +from . import widget class JackAudioConfig(Config): diff --git a/src/freeseer/plugins/audioinput/jackaudiosrc/__init__.py.bak b/src/freeseer/plugins/audioinput/jackaudiosrc/__init__.py.bak new file mode 100644 index 00000000..7df368e4 --- /dev/null +++ b/src/freeseer/plugins/audioinput/jackaudiosrc/__init__.py.bak @@ -0,0 +1,120 @@ +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://github.com/Freeseer/freeseer/ + +''' +Jack Audio Source +----------------- + +An audio plugin which uses JACK as the audio input. + +@author: Thanh Ha +''' +# GStreamer +import pygst +pygst.require("0.10") +import gst + +# PyQt +from PyQt4.QtCore import SIGNAL + +# Freeseer +from freeseer.framework.plugin import IAudioInput +from freeseer.framework.config import Config +import freeseer.framework.config.options as options + +# .freeseer-plugin custom +import widget + + +class JackAudioConfig(Config): + """Default Jackaudio Config settings""" + + client = options.StringOption('') + connect = options.StringOption('') + server = options.StringOption('') + clientname = options.StringOption('') + + +class JackAudioSrc(IAudioInput): + name = "Jack Audio Source" + os = ["linux", "linux2"] + CONFIG_CLASS = JackAudioConfig + + def get_audioinput_bin(self): + bin = gst.Bin() # Do not pass a name so that we can load this input more than once. + + audiosrc = gst.element_factory_make("jackaudiosrc", "audiosrc") + bin.add(audiosrc) + + # Setup ghost pad + pad = audiosrc.get_pad("src") + ghostpad = gst.GhostPad("audiosrc", pad) + bin.add_pad(ghostpad) + + return bin + + def get_widget(self): + if self.widget is None: + self.widget = widget.ConfigWidget() + + return self.widget + + def __enable_connections(self): + self.widget.connect(self.widget.lineedit_client, SIGNAL('editingFinished()'), self.set_client) + self.widget.connect(self.widget.lineedit_connect, SIGNAL('editingFinished()'), self.set_connect) + self.widget.connect(self.widget.lineedit_server, SIGNAL('editingFinished()'), self.set_server) + self.widget.connect(self.widget.lineedit_clientname, SIGNAL('editingFinished()'), self.set_clientname) + + def widget_load_config(self, plugman): + self.load_config(plugman) + + self.widget.lineedit_client.setText(self.config.client) + self.widget.lineedit_connect.setText(self.config.connect) + self.widget.lineedit_server.setText(self.config.server) + self.widget.lineedit_clientname.setText(self.config.clientname) + + # Finally enable connections + self.__enable_connections() + + def set_client(self): + self.config.client = str(self.widget.lineedit_client.text()) + self.config.save() + + def set_connect(self): + self.config.connect = str(self.widget.lineedit_connect.text()) + self.config.save() + + def set_server(self): + self.config.server = str(self.widget.lineedit_server.text()) + self.config.save() + + def set_clientname(self): + self.config.clientname = str(self.widget.lineedit_clientname.text()) + self.config.save() + + ### + ### Translations + ### + def retranslate(self): + self.widget.label_client.setText(self.gui.app.translate('plugin-jackaudio', 'Client')) + self.widget.label_connect.setText(self.gui.app.translate('plugin-jackaudio', 'Connect')) + self.widget.label_server.setText(self.gui.app.translate('plugin-jackaudio', 'Server')) + self.widget.label_clientname.setText(self.gui.app.translate('plugin-jackaudio', 'Client Name')) diff --git a/src/freeseer/plugins/audioinput/pulsesrc/__init__.py b/src/freeseer/plugins/audioinput/pulsesrc/__init__.py index 1c2e9daf..795701b8 100644 --- a/src/freeseer/plugins/audioinput/pulsesrc/__init__.py +++ b/src/freeseer/plugins/audioinput/pulsesrc/__init__.py @@ -44,7 +44,7 @@ from freeseer.framework.config import Config, options # .freeseer-plugin custom -import widget +from . import widget log = logging.getLogger(__name__) @@ -57,7 +57,7 @@ def get_sources(): audiosrc.probe_property_name('device') names = audiosrc.probe_get_values_name('device') # TODO: should be getting actual device description, but .get_property('device-name') does not work - return zip(names, names) + return list(zip(names, names)) def get_default_source(): diff --git a/src/freeseer/plugins/audioinput/pulsesrc/__init__.py.bak b/src/freeseer/plugins/audioinput/pulsesrc/__init__.py.bak new file mode 100644 index 00000000..1c2e9daf --- /dev/null +++ b/src/freeseer/plugins/audioinput/pulsesrc/__init__.py.bak @@ -0,0 +1,134 @@ +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://github.com/Freeseer/freeseer/ + +''' +PulseAudio Source +----------------- + +An audio plugin which uses PulseAudio as the audio input. + +@author: Thanh Ha +''' + +# python-libs +import logging + +# GStreamer +import pygst +pygst.require("0.10") +import gst + +# PyQt +from PyQt4.QtCore import SIGNAL + +# Freeseer +from freeseer.framework.plugin import IAudioInput +from freeseer.framework.config import Config, options + +# .freeseer-plugin custom +import widget + +log = logging.getLogger(__name__) + + +def get_sources(): + """ + Get a list of pairs in the form (name, description) for each pulseaudio source. + """ + audiosrc = gst.element_factory_make("pulsesrc", "audiosrc") + audiosrc.probe_property_name('device') + names = audiosrc.probe_get_values_name('device') + # TODO: should be getting actual device description, but .get_property('device-name') does not work + return zip(names, names) + + +def get_default_source(): + """Returns the default audio source.""" + sources = get_sources() + if not sources: + return '' + else: + return sources[0][0] + + +class PulseSrcConfig(Config): + """Default PulseSrc config settings.""" + source = options.StringOption('') + + +class PulseSrc(IAudioInput): + name = "Pulse Audio Source" + os = ["linux", "linux2"] + CONFIG_CLASS = PulseSrcConfig + + def get_audioinput_bin(self): + bin = gst.Bin() # Do not pass a name so that we can load this input more than once. + + audiosrc = gst.element_factory_make("pulsesrc", "audiosrc") + + if not self.config.source: + self.config.source = get_default_source() + + audiosrc.set_property('device', self.config.source) + log.debug('Pulseaudio source is set to %s', audiosrc.get_property('device')) + + bin.add(audiosrc) + + # Setup ghost pad + pad = audiosrc.get_pad("src") + ghostpad = gst.GhostPad("audiosrc", pad) + bin.add_pad(ghostpad) + + return bin + + def get_widget(self): + if self.widget is None: + self.widget = widget.ConfigWidget() + + return self.widget + + def __enable_connections(self): + self.widget.connect(self.widget.source_combobox, SIGNAL('currentIndexChanged(int)'), self.set_source) + + def widget_load_config(self, plugman): + self.load_config(plugman) + + sources = get_sources() + + self.widget.source_combobox.clear() + for i, source in enumerate(sources): + self.widget.source_combobox.addItem(source[1], userData=source[0]) + if self.config.source == source[0]: + self.widget.source_combobox.setCurrentIndex(i) + + # Finally connect the signals + self.__enable_connections() + + def set_source(self, index): + self.config.source = self.widget.source_combobox.itemData(index).toString() + log.debug('Set pulseaudio source to %s', self.config.source) + self.config.save() + + ### + ### Translations + ### + def retranslate(self): + self.widget.source_label.setText(self.gui.app.translate('plugin-pulseaudio', 'Source')) diff --git a/src/freeseer/plugins/audiomixer/audiopassthrough/__init__.py b/src/freeseer/plugins/audiomixer/audiopassthrough/__init__.py index 2f62fc26..eaeb7462 100644 --- a/src/freeseer/plugins/audiomixer/audiopassthrough/__init__.py +++ b/src/freeseer/plugins/audiomixer/audiopassthrough/__init__.py @@ -43,7 +43,7 @@ from freeseer.framework.config import Config, options # .freeseer-plugin custom -import widget +from . import widget class AudioPassThroughConfig(Config): diff --git a/src/freeseer/plugins/audiomixer/audiopassthrough/__init__.py.bak b/src/freeseer/plugins/audiomixer/audiopassthrough/__init__.py.bak new file mode 100644 index 00000000..2f62fc26 --- /dev/null +++ b/src/freeseer/plugins/audiomixer/audiopassthrough/__init__.py.bak @@ -0,0 +1,138 @@ +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://github.com/Freeseer/freeseer/ + + +''' +Audio Passthrough +----------------- + +A simple audio mixer plugin that connects a single audio source to +the output. + +@author: Thanh Ha +''' + +# GStreamer +import pygst +pygst.require("0.10") +import gst + +# PyQt +from PyQt4.QtCore import SIGNAL + +# Freeseer +from freeseer.framework.plugin import IAudioMixer +from freeseer.framework.config import Config, options + +# .freeseer-plugin custom +import widget + + +class AudioPassThroughConfig(Config): + """Configuration settings for AudioPassThrough plugin.""" + input = options.StringOption("Audio Test Source") + + +class AudioPassthrough(IAudioMixer): + name = "Audio Passthrough" + os = ["linux", "linux2", "win32", "cygwin", "darwin"] + widget = None + CONFIG_CLASS = AudioPassThroughConfig + + def get_audiomixer_bin(self): + bin = gst.Bin() + + audiomixer = gst.element_factory_make("adder", "audiomixer") + bin.add(audiomixer) + + # Setup ghost pad + sinkpad = audiomixer.get_pad("sink%d") + sink_ghostpad = gst.GhostPad("sink", sinkpad) + bin.add_pad(sink_ghostpad) + + srcpad = audiomixer.get_pad("src") + src_ghostpad = gst.GhostPad("src", srcpad) + bin.add_pad(src_ghostpad) + + return bin + + def get_inputs(self): + return [(self.config.input, 0)] + + def load_inputs(self, player, mixer, inputs): + # Load inputs + input = inputs[0] + player.add(input) + input.link(mixer) + + def get_widget(self): + if self.widget is None: + self.widget = widget.ConfigWidget() + + return self.widget + + def __enable_connections(self): + self.widget.connect(self.widget.combobox, SIGNAL('currentIndexChanged(const QString&)'), self.set_input) + self.widget.connect(self.widget.inputSettingsToolButton, SIGNAL('clicked()'), self.source1_setup) + + def widget_load_config(self, plugman): + self.load_config(plugman) + + sources = [] + plugins = self.plugman.get_audioinput_plugins() + for plugin in plugins: + sources.append(plugin.plugin_object.get_name()) + + # Load the combobox with inputs + self.widget.combobox.clear() + n = 0 + for i in sources: + self.widget.combobox.addItem(i) + if i == self.config.input: + self.widget.combobox.setCurrentIndex(n) + self.__enable_source_setup(self.config.input) + n = n + 1 + + # Finally enable connections + self.__enable_connections() + + def source1_setup(self): + plugin = self.plugman.get_plugin_by_name(self.config.input, "AudioInput") + plugin.plugin_object.get_dialog() + + def set_input(self, input): + self.config.input = input + self.__enable_source_setup(self.config.input) + self.config.save() + + def __enable_source_setup(self, source): + '''Activates the source setup button if it has configurable settings''' + plugin = self.plugman.get_plugin_by_name(source, "AudioInput") + if plugin.plugin_object.get_widget() is not None: + self.widget.inputSettingsStack.setCurrentIndex(1) + else: + self.widget.inputSettingsStack.setCurrentIndex(0) + + ### + ### Translations + ### + def retranslate(self): + self.widget.label.setText(self.gui.app.translate('plugin-audio-passthrough', 'Source')) diff --git a/src/freeseer/plugins/audiomixer/multiaudio/__init__.py b/src/freeseer/plugins/audiomixer/multiaudio/__init__.py index c13e0123..1eaeecd7 100644 --- a/src/freeseer/plugins/audiomixer/multiaudio/__init__.py +++ b/src/freeseer/plugins/audiomixer/multiaudio/__init__.py @@ -41,7 +41,7 @@ from freeseer.framework.config import Config, options # .freeseer-plugin custom -import widget +from . import widget class MultiAudioConfig(Config): diff --git a/src/freeseer/plugins/audiomixer/multiaudio/__init__.py.bak b/src/freeseer/plugins/audiomixer/multiaudio/__init__.py.bak new file mode 100644 index 00000000..c13e0123 --- /dev/null +++ b/src/freeseer/plugins/audiomixer/multiaudio/__init__.py.bak @@ -0,0 +1,176 @@ +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://github.com/Freeseer/freeseer/ + +''' +Multiple Audio Plugin +--------------------- + +An audio mixer plugin that combines 2 audio sources into a single output. + +@author: Aaron Brubacher +''' + +# GStreamer +import pygst +pygst.require('0.10') +import gst + +# PyQt +from PyQt4.QtCore import SIGNAL + +# Freeseer +from freeseer.framework.plugin import IAudioMixer +from freeseer.framework.config import Config, options + +# .freeseer-plugin custom +import widget + + +class MultiAudioConfig(Config): + """Configuration settings for MultiAudioConfig plugin.""" + input1 = options.StringOption("Audio Test Source") + input2 = options.StringOption("Audio Test Source") + + +class MultiAudio(IAudioMixer): + name = 'Multiple Audio Inputs' + os = ['linux', 'linux2', 'win32', 'cygwin', 'darwin'] + CONFIG_CLASS = MultiAudioConfig + widget = None + + def get_audiomixer_bin(self): + mixerbin = gst.Bin() + + audiomixer = gst.element_factory_make('adder', 'audiomixer') + mixerbin.add(audiomixer) + + # ghost pads + sinkpad1 = audiomixer.get_pad('sink%d') + sink_ghostpad1 = gst.GhostPad('sink1', sinkpad1) + mixerbin.add_pad(sink_ghostpad1) + + sinkpad2 = audiomixer.get_pad('sink%d') + sink_ghostpad2 = gst.GhostPad('sink2', sinkpad2) + mixerbin.add_pad(sink_ghostpad2) + + srcpad = audiomixer.get_pad('src') + src_ghostpad = gst.GhostPad('src', srcpad) + mixerbin.add_pad(src_ghostpad) + + return mixerbin + + def get_inputs(self): + inputs = [(self.config.input1, 0), (self.config.input2, 1)] + return inputs + + def load_inputs(self, player, mixer, inputs): + input1 = inputs[0] + player.add(input1) + input1.link(mixer) + + input2 = inputs[1] + player.add(input2) + input2.link(mixer) + + def get_widget(self): + if self.widget is None: + self.widget = widget.ConfigWidget() + + return self.widget + + def __enable_connections(self): + self.widget.connect(self.widget.source1_combobox, SIGNAL('currentIndexChanged(const QString&)'), self.set_input1) + self.widget.connect(self.widget.source1_button, SIGNAL('clicked()'), self.source1_setup) + self.widget.connect(self.widget.source2_combobox, SIGNAL('currentIndexChanged(const QString&)'), self.set_input2) + self.widget.connect(self.widget.source2_button, SIGNAL('clicked()'), self.source2_setup) + + def widget_load_config(self, plugman): + self.load_config(plugman) + + plugins = self.plugman.get_audioinput_plugins() + self.widget.source1_combobox.clear() + self.widget.source2_combobox.clear() + for i, source in enumerate(plugins): + name = source.plugin_object.get_name() + self.widget.source1_combobox.addItem(name) + if self.config.input1 == name: + self.widget.source1_combobox.setCurrentIndex(i) + self.__enable_source1_setup(self.config.input1) + self.widget.source2_combobox.addItem(name) + if self.config.input2 == name: + self.widget.source2_combobox.setCurrentIndex(i) + self.__enable_source2_setup(self.config.input2) + + # Finally enable connections + self.__enable_connections() + + ### + ### Source 1 + ### + + def source1_setup(self): + plugin_name = str(self.widget.source1_combobox.currentText()) + plugin = self.plugman.get_plugin_by_name(plugin_name, "AudioInput") + plugin.plugin_object.set_instance(0) + plugin.plugin_object.get_dialog() + + def set_input1(self, input1): + self.config.input1 = input1 + self.__enable_source1_setup(self.config.input1) + self.config.save() + + def __enable_source1_setup(self, source): + '''Activates the source setup button if it has configurable settings''' + plugin = self.plugman.get_plugin_by_name(source, "AudioInput") + if plugin.plugin_object.get_widget() is not None: + self.widget.source1_stack.setCurrentIndex(1) + else: + self.widget.source1_stack.setCurrentIndex(0) + + ### + ### Source 2 + ### + + def source2_setup(self): + plugin_name = str(self.widget.source2_combobox.currentText()) + plugin = self.plugman.get_plugin_by_name(plugin_name, "AudioInput") + plugin.plugin_object.set_instance(1) + plugin.plugin_object.get_dialog() + + def set_input2(self, input2): + self.config.input2 = input2 + self.__enable_source2_setup(self.config.input2) + self.config.save() + + def __enable_source2_setup(self, source): + '''Activates the source setup button if it has configurable settings''' + plugin = self.plugman.get_plugin_by_name(source, "AudioInput") + if plugin.plugin_object.get_widget() is not None: + self.widget.source2_stack.setCurrentIndex(1) + else: + self.widget.source2_stack.setCurrentIndex(0) + + ### + ### Translations + ### + def retranslate(self): + self.widget.source1_label.setText(self.gui.app.translate('plugin-multiaudio', 'Source 1')) + self.widget.source2_label.setText(self.gui.app.translate('plugin-multiaudio', 'Source 2')) diff --git a/src/freeseer/plugins/importer/csv_importer.py b/src/freeseer/plugins/importer/csv_importer.py index 1c0979d8..48f46830 100644 --- a/src/freeseer/plugins/importer/csv_importer.py +++ b/src/freeseer/plugins/importer/csv_importer.py @@ -57,16 +57,16 @@ def get_presentations(self, fname): reader = csv.DictReader(csv_file) for row in reader: talk = { - 'Title': unicode(row.get('Title', ''), 'utf-8'), - 'Speaker': unicode(row.get('Speaker', ''), 'utf-8'), - 'Abstract': unicode(row.get('Abstract', ''), 'utf-8'), # Description - 'Level': unicode(row.get('Level', ''), 'utf-8'), - 'Event': unicode(row.get('Event', ''), 'utf-8'), - 'Room': unicode(row.get('Room', ''), 'utf-8'), - 'Time': unicode(row.get('Time', ''), 'utf-8'), # Legacy csv time field - 'Date': unicode(row.get('Date', ''), 'utf-8'), - 'StartTime': unicode(row.get('StartTime', ''), 'utf-8'), - 'EndTime': unicode(row.get('EndTime', ''), 'utf-8') + 'Title': str(row.get('Title', ''), 'utf-8'), + 'Speaker': str(row.get('Speaker', ''), 'utf-8'), + 'Abstract': str(row.get('Abstract', ''), 'utf-8'), # Description + 'Level': str(row.get('Level', ''), 'utf-8'), + 'Event': str(row.get('Event', ''), 'utf-8'), + 'Room': str(row.get('Room', ''), 'utf-8'), + 'Time': str(row.get('Time', ''), 'utf-8'), # Legacy csv time field + 'Date': str(row.get('Date', ''), 'utf-8'), + 'StartTime': str(row.get('StartTime', ''), 'utf-8'), + 'EndTime': str(row.get('EndTime', ''), 'utf-8') } presentations.append(talk) diff --git a/src/freeseer/plugins/importer/csv_importer.py.bak b/src/freeseer/plugins/importer/csv_importer.py.bak new file mode 100644 index 00000000..1c0979d8 --- /dev/null +++ b/src/freeseer/plugins/importer/csv_importer.py.bak @@ -0,0 +1,77 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +""" +CSV Importer +-------------- + +An import plugin for CSV files used when adding presentations + +@author: Rio Lowry +""" + +import csv +import logging + +from freeseer.framework.plugin import IImporter + +log = logging.getLogger(__name__) + + +class CsvImporter(IImporter): + """CSV Importer plugin for Freeseer + + Provides functionality to import presentations from a CSV file + """ + + name = "CSV Importer" + os = ["linux", "linux2"] + + def get_presentations(self, fname): + """Returns list of dictionaries of all presentations in the csv file.""" + presentations = [] + + try: + with open(fname) as csv_file: + reader = csv.DictReader(csv_file) + for row in reader: + talk = { + 'Title': unicode(row.get('Title', ''), 'utf-8'), + 'Speaker': unicode(row.get('Speaker', ''), 'utf-8'), + 'Abstract': unicode(row.get('Abstract', ''), 'utf-8'), # Description + 'Level': unicode(row.get('Level', ''), 'utf-8'), + 'Event': unicode(row.get('Event', ''), 'utf-8'), + 'Room': unicode(row.get('Room', ''), 'utf-8'), + 'Time': unicode(row.get('Time', ''), 'utf-8'), # Legacy csv time field + 'Date': unicode(row.get('Date', ''), 'utf-8'), + 'StartTime': unicode(row.get('StartTime', ''), 'utf-8'), + 'EndTime': unicode(row.get('EndTime', ''), 'utf-8') + } + + presentations.append(talk) + + except IOError: + log.exception("CSV: File %s not found", csv_file) + + return presentations diff --git a/src/freeseer/plugins/importer/rss_feedparser/__init__.py b/src/freeseer/plugins/importer/rss_feedparser/__init__.py index a7be8001..a660c711 100644 --- a/src/freeseer/plugins/importer/rss_feedparser/__init__.py +++ b/src/freeseer/plugins/importer/rss_feedparser/__init__.py @@ -34,7 +34,7 @@ try: # Import Python3 module if possible from html.parser import HTMLParser except ImportError: - from HTMLParser import HTMLParser + from html.parser import HTMLParser from feedparser import parse @@ -87,7 +87,7 @@ def get_presentations(self, feed_url): # data contain spaces and we don't want to erroneously split that # data. - pres_data = filter(None, pres_data.split(" ")) + pres_data = [_f for _f in pres_data.split(" ") if _f] presentation = { 'Title': entry.title.strip(), @@ -116,6 +116,6 @@ def get_presentation_field(self, presentation, field_name): # data in element is in unicode, we want an error raised if # there are characters that we are not expecting - field_data = unicode(presentation[i + item_presentation_offset]) + field_data = str(presentation[i + item_presentation_offset]) return strip_tags(field_data).strip() diff --git a/src/freeseer/plugins/importer/rss_feedparser/__init__.py.bak b/src/freeseer/plugins/importer/rss_feedparser/__init__.py.bak new file mode 100644 index 00000000..a7be8001 --- /dev/null +++ b/src/freeseer/plugins/importer/rss_feedparser/__init__.py.bak @@ -0,0 +1,121 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +""" +Rss FeedParser +-------------- + +An import plugin which provides a RSS parser used when adding presentations + +@author: Rio Lowry +""" + +try: # Import Python3 module if possible + from html.parser import HTMLParser +except ImportError: + from HTMLParser import HTMLParser + +from feedparser import parse + +from freeseer.framework.plugin import IImporter + + +class MLStripper(HTMLParser): + """Simple stripper to remove markup + + MLStripper from http://stackoverflow.com/a/925630/72321 + """ + def __init__(self): + self.reset() + self.fed = [] + + def handle_data(self, d): + self.fed.append(d) + + def get_data(self): + return ''.join(self.fed) + + +def strip_tags(html): + """Helper functions to strip markup from passed object""" + s = MLStripper() + s.feed(html) + return s.get_data() + + +class FeedParser(IImporter): + """FeedParser plugin for Freeseer + + Provides functionality to allow a RSS feed to be fetched and parsed + """ + + name = "Rss FeedParser" + os = ["linux", "linux2"] + + def get_presentations(self, feed_url): + """Takes feed_url, fetches, parses feed_url + + Returns list of dictionaries of all presentations on parsed feed. + """ + parsed_feed = parse(feed_url) + presentations = [] + for entry in parsed_feed.entries: + pres_data = entry["summary_detail"]["value"] + + # We want to split the pres_data by 3 spaces because some field + # data contain spaces and we don't want to erroneously split that + # data. + + pres_data = filter(None, pres_data.split(" ")) + + presentation = { + 'Title': entry.title.strip(), + 'Speaker': self.get_presentation_field(pres_data, "field-field-speaker"), + 'Abstract': self.get_presentation_field(pres_data, "field-field-abstract"), + 'Level': self.get_presentation_field(pres_data, "field-field-level"), + 'Status': self.get_presentation_field(pres_data, "field-field-status"), + 'Time': self.get_presentation_field(pres_data, "field-field-time"), + 'Event': self.get_presentation_field(pres_data, "field-field-event"), + 'Room': self.get_presentation_field(pres_data, "field-field-room") + } + presentations.append(presentation) + + return presentations + + def get_presentation_field(self, presentation, field_name): + """Returns the field_name of the presentation at presentation""" + + # Due to the autogenerated structure of the rss feed the field data is + # offset by 4 elements from field_name in the passed presentation + + item_presentation_offset = 4 + for i, element in enumerate(presentation): + if field_name in element: + + # data in element is in unicode, we want an error raised if + # there are characters that we are not expecting + + field_data = unicode(presentation[i + item_presentation_offset]) + + return strip_tags(field_data).strip() diff --git a/src/freeseer/plugins/output/audiofeedback/__init__.py b/src/freeseer/plugins/output/audiofeedback/__init__.py index f72c592a..900ef42f 100644 --- a/src/freeseer/plugins/output/audiofeedback/__init__.py +++ b/src/freeseer/plugins/output/audiofeedback/__init__.py @@ -42,7 +42,7 @@ from freeseer.framework.config import Config, options # .freeseer-plugin -import widget +from . import widget class AudioFeedbackConfig(Config): diff --git a/src/freeseer/plugins/output/audiofeedback/__init__.py.bak b/src/freeseer/plugins/output/audiofeedback/__init__.py.bak new file mode 100644 index 00000000..f72c592a --- /dev/null +++ b/src/freeseer/plugins/output/audiofeedback/__init__.py.bak @@ -0,0 +1,104 @@ +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://github.com/Freeseer/freeseer/ + +''' +Audio Feedback +-------------- + +An output plugin which routes sound to the device's available +speakers. + +@author: Thanh Ha +''' + +# GStreamer +import pygst +pygst.require("0.10") +import gst + +# PyQt +from PyQt4.QtCore import SIGNAL + +# Freeseer +from freeseer.framework.plugin import IOutput +from freeseer.framework.config import Config, options + +# .freeseer-plugin +import widget + + +class AudioFeedbackConfig(Config): + """Configuration class for AudioFeedback plugin.""" + feedbacksink = options.StringOption("autoaudiosink") + + +class AudioFeedback(IOutput): + name = "Audio Feedback" + os = ["linux", "linux2", "win32", "cygwin", "darwin"] + type = IOutput.AUDIO + recordto = IOutput.OTHER + CONFIG_CLASS = AudioFeedbackConfig + + def get_output_bin(self, audio=True, video=False, metadata=None): + bin = gst.Bin() + + audioqueue = gst.element_factory_make("queue", "audioqueue") + bin.add(audioqueue) + + audiosink = gst.element_factory_make(self.config.feedbacksink, "audiosink") + bin.add(audiosink) + + # Setup ghost pad + pad = audioqueue.get_pad("sink") + ghostpad = gst.GhostPad("sink", pad) + bin.add_pad(ghostpad) + + audioqueue.link(audiosink) + + return bin + + def get_widget(self): + if self.widget is None: + self.widget = widget.ConfigWidget() + + return self.widget + + def __enable_connections(self): + self.widget.connect(self.widget.feedbackComboBox, SIGNAL('currentIndexChanged(const QString&)'), self.set_feedbacksink) + + def widget_load_config(self, plugman): + self.load_config(plugman) + + feedbackIndex = self.widget.feedbackComboBox.findText(self.config.feedbacksink) + self.widget.feedbackComboBox.setCurrentIndex(feedbackIndex) + + # Finally enable connections + self.__enable_connections() + + def set_feedbacksink(self, feedbacksink): + self.config.feedbacksink = feedbacksink + self.config.save() + + ### + ### Translations + ### + def retranslate(self): + self.widget.feedbackLabel.setText(self.gui.app.translate('plugin-audiofeedback', 'Feedback')) diff --git a/src/freeseer/plugins/output/ogg_icecast/__init__.py b/src/freeseer/plugins/output/ogg_icecast/__init__.py index ed6fcb45..474e0c92 100644 --- a/src/freeseer/plugins/output/ogg_icecast/__init__.py +++ b/src/freeseer/plugins/output/ogg_icecast/__init__.py @@ -42,7 +42,7 @@ from freeseer.framework.config import Config, options # .freeseer-plugin custom -import widget +from . import widget class OggIcecastConfig(Config): @@ -152,7 +152,7 @@ def set_metadata(self, data): ''' self.tags = gst.TagList() - for tag in data.keys(): + for tag in list(data.keys()): if(gst.tag_exists(tag)): self.tags[tag] = data[tag] else: diff --git a/src/freeseer/plugins/output/ogg_icecast/__init__.py.bak b/src/freeseer/plugins/output/ogg_icecast/__init__.py.bak new file mode 100644 index 00000000..ed6fcb45 --- /dev/null +++ b/src/freeseer/plugins/output/ogg_icecast/__init__.py.bak @@ -0,0 +1,252 @@ +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://github.com/Freeseer/freeseer/ + +''' +Ogg Icecast +----------- + +A streaming plugin which records sends an Ogg stream to an icecast server. + +@author: Thanh Ha +''' + +# GStreamer +import pygst +pygst.require("0.10") +import gst + +# PyQt +from PyQt4.QtCore import SIGNAL + +# Freeseer +from freeseer.framework.multimedia import Quality +from freeseer.framework.plugin import IOutput +from freeseer.framework.config import Config, options + +# .freeseer-plugin custom +import widget + + +class OggIcecastConfig(Config): + """Configuration class for OggIcecast Plugin.""" + ip = options.StringOption("127.0.0.1") + port = options.IntegerOption(8000) + password = options.StringOption("hackme") + mount = options.StringOption("stream.ogg") + audio_quality = options.FloatOption(0.3) + video_bitrate = options.IntegerOption(2400) + + +class OggIcecast(IOutput): + name = "Ogg Icecast" + os = ["linux", "linux2"] + type = IOutput.BOTH + recordto = IOutput.STREAM + extension = "ogg" + tags = None + CONFIG_CLASS = OggIcecastConfig + configurable = True + AUDIO_MIN = -0.1 + AUDIO_RANGE = 1.1 + + def get_output_bin(self, audio=True, video=True, metadata=None): + bin = gst.Bin() + + if metadata is not None: + self.set_metadata(metadata) + + # Muxer + muxer = gst.element_factory_make("oggmux", "muxer") + bin.add(muxer) + + icecast = gst.element_factory_make("shout2send", "icecast") + icecast.set_property("ip", self.config.ip) + icecast.set_property("port", self.config.port) + icecast.set_property("password", self.config.password) + icecast.set_property("mount", self.config.mount) + bin.add(icecast) + + # + # Setup Audio Pipeline + # + if audio: + audioqueue = gst.element_factory_make("queue", "audioqueue") + bin.add(audioqueue) + + audioconvert = gst.element_factory_make("audioconvert", "audioconvert") + bin.add(audioconvert) + + audiocodec = gst.element_factory_make("vorbisenc", "audiocodec") + audiocodec.set_property("quality", self.config.audio_quality) + bin.add(audiocodec) + + # Setup metadata + vorbistag = gst.element_factory_make("vorbistag", "vorbistag") + # set tag merge mode to GST_TAG_MERGE_REPLACE + merge_mode = gst.TagMergeMode.__enum_values__[2] + + if metadata is not None: + # Only set tag if metadata is set + vorbistag.merge_tags(self.tags, merge_mode) + vorbistag.set_tag_merge_mode(merge_mode) + bin.add(vorbistag) + + # Setup ghost pads + audiopad = audioqueue.get_pad("sink") + audio_ghostpad = gst.GhostPad("audiosink", audiopad) + bin.add_pad(audio_ghostpad) + + # Link elements + audioqueue.link(audioconvert) + audioconvert.link(audiocodec) + audiocodec.link(vorbistag) + vorbistag.link(muxer) + + # + # Setup Video Pipeline + # + if video: + videoqueue = gst.element_factory_make("queue", "videoqueue") + bin.add(videoqueue) + + videocodec = gst.element_factory_make("theoraenc", "videocodec") + videocodec.set_property("bitrate", self.config.video_bitrate) + bin.add(videocodec) + + videopad = videoqueue.get_pad("sink") + video_ghostpad = gst.GhostPad("videosink", videopad) + bin.add_pad(video_ghostpad) + + videoqueue.link(videocodec) + videocodec.link(muxer) + + # + # Link muxer to icecast + # + muxer.link(icecast) + + return bin + + def set_metadata(self, data): + ''' + Populate global tag list variable with file metadata for + vorbistag audio element + ''' + self.tags = gst.TagList() + + for tag in data.keys(): + if(gst.tag_exists(tag)): + self.tags[tag] = data[tag] + else: + #self.core.logger.log.debug("WARNING: Tag \"" + str(tag) + "\" is not registered with gstreamer.") + pass + + def get_widget(self): + if self.widget is None: + self.widget = widget.ConfigWidget() + + return self.widget + + def get_video_quality_layout(self): + """Returns a layout with the video quality config widgets for configtool to use.""" + return self.get_widget().get_video_quality_layout() + + def get_audio_quality_layout(self): + """Returns a layout with the audio quality config widgets for configtool to use.""" + return self.get_widget().get_audio_quality_layout() + + def __enable_connections(self): + self.widget.connect(self.widget.lineedit_ip, SIGNAL('editingFinished()'), self.set_ip) + self.widget.connect(self.widget.spinbox_port, SIGNAL('valueChanged(int)'), self.set_port) + self.widget.connect(self.widget.lineedit_password, SIGNAL('editingFinished()'), self.set_password) + self.widget.connect(self.widget.lineedit_mount, SIGNAL('editingFinished()'), self.set_mount) + self.widget.connect(self.widget.spinbox_audio_quality, SIGNAL('valueChanged(double)'), self.audio_quality_changed) + self.widget.connect(self.widget.spinbox_video_quality, SIGNAL('valueChanged(int)'), self.video_bitrate_changed) + + def widget_load_config(self, plugman): + self.get_config() + + self.widget.lineedit_ip.setText(self.config.ip) + self.widget.spinbox_port.setValue(self.config.port) + self.widget.lineedit_password.setText(self.config.password) + self.widget.lineedit_mount.setText(self.config.mount) + + # Finally enable connections + self.__enable_connections() + + def set_ip(self): + self.config.ip = str(self.widget.lineedit_ip.text()) + self.config.save() + + def set_port(self, port): + self.config.port = port + self.config.save() + + def set_password(self): + self.config.password = str(self.widget.lineedit_password.text()) + self.config.save() + + def set_mount(self): + self.config.mount = str(self.widget.lineedit_mount.text()) + self.config.save() + + def audio_quality_changed(self): + """Called when a change to the SpinBox for audio quality is made""" + self.config.audio_quality = self.widget.spinbox_audio_quality.value() + self.config.save() + + def set_audio_quality(self, quality): + self.get_config() + + if quality == Quality.LOW: + self.config.audio_quality = self.AUDIO_MIN + (self.AUDIO_RANGE * Quality.LOW_AUDIO_FACTOR) + elif quality == Quality.MEDIUM: + self.config.audio_quality = self.AUDIO_MIN + (self.AUDIO_RANGE * Quality.MEDIUM_AUDIO_FACTOR) + elif quality == Quality.HIGH: + self.config.audio_quality = self.AUDIO_MIN + (self.AUDIO_RANGE * Quality.HIGH_AUDIO_FACTOR) + + if self.widget_config_loaded: + self.widget.spinbox_audio_quality.setValue(self.config.audio_quality) + + self.config.save() + + def video_bitrate_changed(self): + """Called when a change to the SpinBox for video bitrate is made""" + self.config.video_bitrate = self.widget.spinbox_video_quality.value() + self.config.save() + + def set_video_bitrate(self, bitrate): + self.get_config() + + if self.widget_config_loaded: + self.widget.spinbox_video_quality.setValue(bitrate) + + self.config.video_bitrate = bitrate + self.config.save() + + ### + ### Translations + ### + def retranslate(self): + self.widget.label_ip.setText(self.gui.app.translate('plugin-icecast', 'IP')) + self.widget.label_port.setText(self.gui.app.translate('plugin-icecast', 'Port')) + self.widget.label_password.setText(self.gui.app.translate('plugin-icecast', 'Password')) + self.widget.label_mount.setText(self.gui.app.translate('plugin-icecast', 'Mount')) diff --git a/src/freeseer/plugins/output/ogg_output/__init__.py b/src/freeseer/plugins/output/ogg_output/__init__.py index 7e2b4e76..252be1d9 100644 --- a/src/freeseer/plugins/output/ogg_output/__init__.py +++ b/src/freeseer/plugins/output/ogg_output/__init__.py @@ -43,7 +43,7 @@ from freeseer.framework.config import Config, options # .freeseer-plugin custom -import widget +from . import widget class OggOutputConfig(Config): @@ -157,7 +157,7 @@ def set_metadata(self, data): ''' self.tags = gst.TagList() - for tag in data.keys(): + for tag in list(data.keys()): if(gst.tag_exists(tag)): self.tags[tag] = data[tag] else: diff --git a/src/freeseer/plugins/output/ogg_output/__init__.py.bak b/src/freeseer/plugins/output/ogg_output/__init__.py.bak new file mode 100644 index 00000000..7e2b4e76 --- /dev/null +++ b/src/freeseer/plugins/output/ogg_output/__init__.py.bak @@ -0,0 +1,247 @@ +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://github.com/Freeseer/freeseer/ + +''' +Ogg Output +---------- + +An output plugin which records to Ogg format using Theora for encoding for +video and Vorbis encoding for Audio. + +@author: Thanh Ha +''' + +# GStreamer +import pygst +pygst.require("0.10") +import gst + +# PyQt +from PyQt4.QtCore import SIGNAL + +# Freeeseer +from freeseer.framework.multimedia import Quality +from freeseer.framework.plugin import IOutput +from freeseer.framework.config import Config, options + +# .freeseer-plugin custom +import widget + + +class OggOutputConfig(Config): + """Configuration class for OggOutput plugin.""" + matterhorn = options.IntegerOption(0) + audio_quality = options.FloatOption(0.3) + video_bitrate = options.IntegerOption(2400) + + +class OggOutput(IOutput): + name = "Ogg Output" + os = ["linux", "linux2", "win32", "cygwin", "darwin"] + type = IOutput.BOTH + recordto = IOutput.FILE + extension = "ogg" + tags = None + CONFIG_CLASS = OggOutputConfig + configurable = True + AUDIO_MIN = -0.1 + AUDIO_RANGE = 1.1 + + def get_output_bin(self, audio=True, video=True, metadata=None): + bin = gst.Bin() + + if metadata is not None: + self.set_metadata(metadata) + if self.config.matterhorn == 2: # checked + self.generate_xml_metadata(metadata).write(self.location + ".xml") + + # Muxer + muxer = gst.element_factory_make("oggmux", "muxer") + bin.add(muxer) + + # File sink + filesink = gst.element_factory_make('filesink', 'filesink') + filesink.set_property('location', self.location) + bin.add(filesink) + + # + # Setup Audio Pipeline if Audio Recording is Enabled + # + if audio: + audioqueue = gst.element_factory_make("queue", "audioqueue") + bin.add(audioqueue) + + audioconvert = gst.element_factory_make("audioconvert", "audioconvert") + bin.add(audioconvert) + + audiolevel = gst.element_factory_make('level', 'audiolevel') + audiolevel.set_property('interval', 20000000) + bin.add(audiolevel) + + audiocodec = gst.element_factory_make("vorbisenc", "audiocodec") + audiocodec.set_property("quality", self.config.audio_quality) + bin.add(audiocodec) + + # Setup metadata + vorbistag = gst.element_factory_make("vorbistag", "vorbistag") + # set tag merge mode to GST_TAG_MERGE_REPLACE + merge_mode = gst.TagMergeMode.__enum_values__[2] + + if metadata is not None: + # Only set tag if metadata is set + vorbistag.merge_tags(self.tags, merge_mode) + vorbistag.set_tag_merge_mode(merge_mode) + bin.add(vorbistag) + + # Setup ghost pads + audiopad = audioqueue.get_pad("sink") + audio_ghostpad = gst.GhostPad("audiosink", audiopad) + bin.add_pad(audio_ghostpad) + + # Link Elements + audioqueue.link(audioconvert) + audioconvert.link(audiolevel) + audiolevel.link(audiocodec) + audiocodec.link(vorbistag) + vorbistag.link(muxer) + + # + # Setup Video Pipeline + # + if video: + videoqueue = gst.element_factory_make("queue", "videoqueue") + bin.add(videoqueue) + + videocodec = gst.element_factory_make("theoraenc", "videocodec") + videocodec.set_property("bitrate", self.config.video_bitrate) + bin.add(videocodec) + + # Setup ghost pads + videopad = videoqueue.get_pad("sink") + video_ghostpad = gst.GhostPad("videosink", videopad) + bin.add_pad(video_ghostpad) + + # Link Elements + videoqueue.link(videocodec) + videocodec.link(muxer) + + # + # Link muxer to filesink + # + muxer.link(filesink) + + return bin + + def set_metadata(self, data): + ''' + Populate global tag list variable with file metadata for + vorbistag audio element + ''' + self.tags = gst.TagList() + + for tag in data.keys(): + if(gst.tag_exists(tag)): + self.tags[tag] = data[tag] + else: + #self.core.logger.log.debug("WARNING: Tag \"" + str(tag) + "\" is not registered with gstreamer.") + pass + + def get_widget(self): + if self.widget is None: + self.widget = widget.ConfigWidget() + + return self.widget + + def get_video_quality_layout(self): + """Returns a layout with the video quality config widgets for configtool to use.""" + return self.get_widget().get_video_quality_layout() + + def get_audio_quality_layout(self): + """Returns a layout with the audio quality config widgets for configtool to use.""" + return self.get_widget().get_audio_quality_layout() + + def __enable_connections(self): + self.widget.connect(self.widget.spinbox_audio_quality, SIGNAL('valueChanged(double)'), self.audio_quality_changed) + self.widget.connect(self.widget.spinbox_video_quality, SIGNAL('valueChanged(int)'), self.video_bitrate_changed) + self.widget.connect(self.widget.checkbox_matterhorn, SIGNAL('stateChanged(int)'), self.set_matterhorn) + + def widget_load_config(self, plugman): + self.get_config() + + self.widget.spinbox_audio_quality.setValue(self.config.audio_quality) + self.widget.spinbox_video_quality.setValue(self.config.video_bitrate) + self.widget.checkbox_matterhorn.setCheckState(self.config.matterhorn) + + # Finally enable connections + self.__enable_connections() + + def audio_quality_changed(self): + """Called when a change to the SpinBox for audio quality is made""" + self.config.audio_quality = self.widget.spinbox_audio_quality.value() + self.config.save() + + def set_audio_quality(self, quality): + self.get_config() + + if quality == Quality.LOW: + self.config.audio_quality = self.AUDIO_MIN + (self.AUDIO_RANGE * Quality.LOW_AUDIO_FACTOR) + elif quality == Quality.MEDIUM: + self.config.audio_quality = self.AUDIO_MIN + (self.AUDIO_RANGE * Quality.MEDIUM_AUDIO_FACTOR) + elif quality == Quality.HIGH: + self.config.audio_quality = self.AUDIO_MIN + (self.AUDIO_RANGE * Quality.HIGH_AUDIO_FACTOR) + + if self.widget_config_loaded: + self.widget.spinbox_audio_quality.setValue(self.config.audio_quality) + + self.config.save() + + def video_bitrate_changed(self): + """Called when a change to the SpinBox for video bitrate is made""" + self.config.video_bitrate = self.widget.spinbox_video_quality.value() + self.config.save() + + def set_video_bitrate(self, bitrate): + self.get_config() + + if self.widget_config_loaded: + self.widget.spinbox_video_quality.setValue(bitrate) + + self.config.video_bitrate = bitrate + self.config.save() + + def set_matterhorn(self, state): + """ + Enables or Disables Matterhorn metadata generation. + + If enabled filename.xml will be created along side the video file + containing matterhorn metadata in xml format. + """ + self.config.matterhorn = state + self.config.save() + + ### + ### Translations + ### + def retranslate(self): + self.widget.label_audio_quality.setText(self.gui.app.translate('plugin-ogg-output', 'Audio Quality')) + self.widget.label_video_quality.setText(self.gui.app.translate('plugin-ogg-output', 'Video Quality (kb/s)')) + self.widget.label_matterhorn.setText(self.gui.app.translate('plugin-ogg-output', 'Matterhorn Metadata')) + self.widget.label_matterhorn.setToolTip(self.gui.app.translate('plugin-ogg-output', 'Generates Matterhorn Metadata in XML format')) diff --git a/src/freeseer/plugins/output/rtmp_streaming/__init__.py b/src/freeseer/plugins/output/rtmp_streaming/__init__.py index 5536e3bc..3da34942 100644 --- a/src/freeseer/plugins/output/rtmp_streaming/__init__.py +++ b/src/freeseer/plugins/output/rtmp_streaming/__init__.py @@ -104,7 +104,7 @@ # for freeseer to run. # try: - import httplib + import http.client import simplejson from oauth import oauth except: @@ -275,7 +275,7 @@ def set_metadata(self, data): ''' self.tags = gst.TagList() - for tag in data.keys(): + for tag in list(data.keys()): if(gst.tag_exists(tag)): self.tags[tag] = data[tag] else: @@ -690,7 +690,7 @@ def open_request(consumer_key, consumer_secret): request.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(), consumer, None) - connection = httplib.HTTPConnection(JustinApi.addr) + connection = http.client.HTTPConnection(JustinApi.addr) connection.request('GET', request.http_url, headers=request.to_header()) result = connection.getresponse().read() @@ -739,7 +739,7 @@ def obtain_access_token(self): http_method='GET', http_url=url) request.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(), consumer, token) - connection = httplib.HTTPConnection(self.addr) + connection = http.client.HTTPConnection(self.addr) connection.request('GET', request.http_url, headers=request.to_header()) result = connection.getresponse().read() self.access_token_str = result @@ -758,11 +758,12 @@ def get_data(self, endpoint): http_method='GET', http_url="http://%s/api/%s" % (JustinApi.addr, endpoint)) request.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(), consumer, token) - connection = httplib.HTTPConnection(self.addr) + connection = http.client.HTTPConnection(self.addr) connection.request('GET', request.http_url, headers=request.to_header()) result = connection.getresponse().read() data = simplejson.loads(result) - except KeyError, simplejson.decoder.JSONDecodeError: + except KeyError as xxx_todo_changeme: + simplejson.decoder.JSONDecodeError = xxx_todo_changeme log.error("justin.tv API: failed fetch data from endpoint %s" % endpoint) return dict() return data @@ -778,7 +779,7 @@ def set_data(self, endpoint, payload): http_url="http://%s/api/%s" % (JustinApi.addr, endpoint), parameters=payload) request.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(), consumer, token) - connection = httplib.HTTPConnection(self.addr) + connection = http.client.HTTPConnection(self.addr) connection.request('POST', request.http_url, body=request.to_postdata()) result = connection.getresponse().read() except KeyError: diff --git a/src/freeseer/plugins/output/rtmp_streaming/__init__.py.bak b/src/freeseer/plugins/output/rtmp_streaming/__init__.py.bak new file mode 100644 index 00000000..5536e3bc --- /dev/null +++ b/src/freeseer/plugins/output/rtmp_streaming/__init__.py.bak @@ -0,0 +1,805 @@ +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://github.com/Freeseer/freeseer/ + + +''' +RTMP streaming +-------------- + +The `Real Time Messaging Protocol (RTMP) `_ +is a popular format for video/audio streaming. You can stream from Freeseer +to an arbitrary server using the RTMP plugin. + +To enable streaming + +1. Open :ref:`Freeseer's configuration ` and go to the "Recording" tab +2. Check the "Record to Stream" box +3. Set the stream format to "RTMP Streaming" +4. Click the "Setup" button to access more options +5. Specify the "Stream URL" (the location you'll be streaming to) + +.. note:: All outputs must be set to "leaky mode". + Go to "Plugins" > "Output" > "Video Preview" > "Leaky Queue". + +Justin.tv +********* + +There is built-in support for streaming to `Justin.tv `_. +To enable it, repeat the above steps 1-4, then change the +"Streaming Destination" from "custom" to "justin.tv". + +You also have to input your "Streaming Key". +To get one, `log in to your Justin.tv account `_ +and go to http://www.justin.tv/broadcast/adv_other. + +You also have the option to set your Justin.tv channel properties (stream title +and description). Check the "Set Justin.tv channel properties" box and enter +your "Consumer Key" and "Consumer Secret". + +.. tip:: + To obtain a Consumer Key and Consumer Secret from Justin.tv, + got to http://www.twitch.tv/developer/activate. + + You will need to provide login credentials for Justin.tv. + This will make your account a developer account (as of this moment, + this does not have any adverse effects). + + In order to obtain the Consumer Key and Consumer Secret, + you will have to create an application in Justin.tv. + To do this, go to http://www.twitch.tv/oauth_clients/create. + + On this page you will be asked to provide a name for the application and + a set of URLs - these can be chosen arbitrarily, they serve no purpose for + RTMP streaming. After you press "Save", you will be taken to a page where the + Consumer Key and Consumer Secret will be shown - you can now provide + these to Freeseer! + +When you're done setting up your Justin.tv preferences in Freeseer, click the +"Apply - stream to Justin.tv" button on the bottom of the settings tab. Enjoy! + +@author: Jonathan Shen +''' + +# Python libs +import logging +import pickle +import webbrowser + +# GStreamer libs +import pygst +pygst.require("0.10") +import gst + +# Qt libs +from PyQt4 import QtGui, QtCore + +# Freeseer libs +from freeseer.framework.multimedia import Quality +from freeseer.framework.plugin import IOutput +from freeseer.framework.plugin import PluginError +from freeseer.framework.config import Config, options + +log = logging.getLogger(__name__) + +# +# Non-standard imports required for plugin but not +# for freeseer to run. +# +try: + import httplib + import simplejson + from oauth import oauth +except: + log.error("""RTMP-Streaming: Failed to load plugin. + This plugin requires the following libraries in order operate: + + - httplib + - simplejson + - oauth + + If you wish to use this plugin please ensure these libraries are installed on your system. + """) + raise PluginError("Plugin missing required dependencies.") + +TUNE_VALUES = ['none', 'film', 'animation', 'grain', 'stillimage', 'psnr', 'ssim', 'fastdecode', 'zerolatency'] +AUDIO_CODEC_VALUES = ['lame', 'faac'] +STREAMING_DESTINATION_VALUES = ['custom', 'justin.tv'] +JUSTIN_URL = 'rtmp://live-3c.justin.tv/app/' +STATUS_KEYS = ['artist', 'title'] +DESCRIPTION_KEY = 'comment' + + +class RTMPOutputConfig(Config): + """Configuration class for RTMPOut plugin.""" + url = options.StringOption('') + audio_quality = options.IntegerOption(3) + video_bitrate = options.IntegerOption(2400) + video_tune = options.ChoiceOption(TUNE_VALUES, 'none') + audio_codec = options.ChoiceOption(AUDIO_CODEC_VALUES, 'lame') + streaming_destination = options.ChoiceOption(STREAMING_DESTINATION_VALUES, 'custom') + streaming_key = options.StringOption('') + consumer_key = options.StringOption('') + consumer_secret = options.StringOption('') + authorization_url = options.StringOption('') + use_justin_api = options.StringOption('no') + justin_api_persistent = options.StringOption('') + + +class RTMPOutput(IOutput): + name = "RTMP Streaming" + os = ["linux", "linux2", "win32", "cygwin"] + type = IOutput.BOTH + recordto = IOutput.STREAM + tags = None + justin_api = None + streaming_destination_widget = None + load_config_delegate = None + CONFIG_CLASS = RTMPOutputConfig + configurable = True + LAME_AUDIO_MIN = 0 + LAME_AUDIO_RANGE = 9.999 + FAAC_AUDIO_MIN = 1 + FAAC_AUDIO_RANGE = 999 + + #@brief - RTMP Streaming plugin. + # Structure for function was based primarily off the ogg function + # Creates a bin to stream flv content to [self.config.url] + # Bin has audio and video ghost sink pads + # Converts audio and video to flv with [flvmux] element + # Streams flv content to [self.config.url] + # TODO - Error handling - verify pad setup + def get_output_bin(self, audio=True, video=True, metadata=None): + bin = gst.Bin() + + if metadata is not None: + self.set_metadata(metadata) + + # Muxer + muxer = gst.element_factory_make("flvmux", "muxer") + + # Setup metadata + # set tag merge mode to GST_TAG_MERGE_REPLACE + merge_mode = gst.TagMergeMode.__enum_values__[2] + + if metadata is not None: + # Only set tag if metadata is set + muxer.merge_tags(self.tags, merge_mode) + muxer.set_tag_merge_mode(merge_mode) + + bin.add(muxer) + + # RTMP sink + rtmpsink = gst.element_factory_make('rtmpsink', 'rtmpsink') + rtmpsink.set_property('location', self.config.url) + bin.add(rtmpsink) + + # + # Setup Audio Pipeline if Audio Recording is Enabled + # + if audio: + audioqueue = gst.element_factory_make("queue", "audioqueue") + bin.add(audioqueue) + + audioconvert = gst.element_factory_make("audioconvert", "audioconvert") + bin.add(audioconvert) + + audiolevel = gst.element_factory_make('level', 'audiolevel') + audiolevel.set_property('interval', 20000000) + bin.add(audiolevel) + + audiocodec = gst.element_factory_make(self.config.audio_codec, "audiocodec") + + if 'quality' in audiocodec.get_property_names(): + audiocodec.set_property("quality", self.config.audio_quality) + else: + log.debug("WARNING: Missing property: 'quality' on audiocodec; available: " + + ','.join(audiocodec.get_property_names())) + bin.add(audiocodec) + + # Setup ghost pads + audiopad = audioqueue.get_pad("sink") + audio_ghostpad = gst.GhostPad("audiosink", audiopad) + bin.add_pad(audio_ghostpad) + + # Link Elements + audioqueue.link(audioconvert) + audioconvert.link(audiolevel) + audiolevel.link(audiocodec) + audiocodec.link(muxer) + + # + # Setup Video Pipeline + # + if video: + videoqueue = gst.element_factory_make("queue", "videoqueue") + bin.add(videoqueue) + + videocodec = gst.element_factory_make("x264enc", "videocodec") + videocodec.set_property("bitrate", self.config.video_bitrate) + if self.config.video_tune != 'none': + videocodec.set_property('tune', self.config.video_tune) + bin.add(videocodec) + + # Setup ghost pads + videopad = videoqueue.get_pad("sink") + video_ghostpad = gst.GhostPad("videosink", videopad) + bin.add_pad(video_ghostpad) + + # Link Elements + videoqueue.link(videocodec) + videocodec.link(muxer) + + # + # Link muxer to rtmpsink + # + muxer.link(rtmpsink) + + if self.config.streaming_destination == STREAMING_DESTINATION_VALUES[1] and self.config.use_justin_api == 'yes': + self.justin_api.set_channel_status(self.get_talk_status(metadata), + self.get_description(metadata)) + + return bin + + def get_talk_status(self, metadata): + if not metadata: + return "" + return " - ".join([metadata[status_key] for status_key in self.STATUS_KEYS]) + + def get_description(self, metadata): + if not metadata: + return "" + return metadata[self.DESCRIPTION_KEY] + + def set_metadata(self, data): + ''' + Populate global tag list variable with file metadata for + vorbistag audio element + ''' + self.tags = gst.TagList() + + for tag in data.keys(): + if(gst.tag_exists(tag)): + self.tags[tag] = data[tag] + else: + #self.core.logger.log.debug("WARNING: Tag \"" + str(tag) + "\" is not registered with gstreamer.") + pass + + def load_config(self, plugman, config=None): + super(RTMPOutput, self).load_config(plugman) + if self.config.justin_api_persistent: + self.justin_api = JustinApi.from_string(self.config.justin_api_persistent) + self.justin_api.set_save_method(self.set_justin_api_persistent) + + def get_stream_settings_widget(self): + self.stream_settings_widget = QtGui.QWidget() + self.stream_settings_widget_layout = QtGui.QFormLayout() + self.stream_settings_widget.setLayout(self.stream_settings_widget_layout) + # + # Stream URL + # + + # TODO: URL validation? + + self.label_stream_url = QtGui.QLabel("Stream URL") + self.lineedit_stream_url = QtGui.QLineEdit() + self.stream_settings_widget_layout.addRow(self.label_stream_url, self.lineedit_stream_url) + + self.lineedit_stream_url.textEdited.connect(self.set_stream_url) + + # + # Audio Quality + # + + self.label_audio_quality = QtGui.QLabel("Audio Quality") + self.spinbox_audio_quality = QtGui.QSpinBox() + self.spinbox_audio_quality.setMinimum(0) + self.spinbox_audio_quality.setMaximum(9) + self.spinbox_audio_quality.setSingleStep(1) + self.spinbox_audio_quality.setValue(5) + + self.stream_settings_widget.connect(self.spinbox_audio_quality, QtCore.SIGNAL('valueChanged(int)'), self.audio_quality_changed) + + # + # Audio Codec + # + + self.label_audio_codec = QtGui.QLabel("Audio Codec") + self.combobox_audio_codec = QtGui.QComboBox() + self.combobox_audio_codec.addItems(AUDIO_CODEC_VALUES) + self.stream_settings_widget_layout.addRow(self.label_audio_codec, self.combobox_audio_codec) + + self.stream_settings_widget.connect(self.combobox_audio_codec, + QtCore.SIGNAL('currentIndexChanged(const QString&)'), + self.set_audio_codec) + + # + # Video Quality + # + + self.label_video_quality = QtGui.QLabel("Video Quality (kb/s)") + self.spinbox_video_quality = QtGui.QSpinBox() + self.spinbox_video_quality.setMinimum(0) + self.spinbox_video_quality.setMaximum(16777215) + self.spinbox_video_quality.setValue(2400) # Default value 2400 + + self.stream_settings_widget.connect(self.spinbox_video_quality, QtCore.SIGNAL('valueChanged(int)'), self.video_bitrate_changed) + + # + # Video Tune + # + + self.label_video_tune = QtGui.QLabel("Video Tune") + self.combobox_video_tune = QtGui.QComboBox() + self.combobox_video_tune.addItems(TUNE_VALUES) + self.stream_settings_widget_layout.addRow(self.label_video_tune, self.combobox_video_tune) + + self.stream_settings_widget.connect(self.combobox_video_tune, + QtCore.SIGNAL('currentIndexChanged(const QString&)'), + self.set_video_tune) + + # + # Note + # + + self.label_note = QtGui.QLabel(self.gui.uiTranslator.translate('rtmp', "*For RTMP streaming, all other outputs must be set to leaky")) + self.stream_settings_widget_layout.addRow(self.label_note) + + return self.stream_settings_widget + + def setup_streaming_destination_widget(self, streaming_dest): + if streaming_dest == STREAMING_DESTINATION_VALUES[0]: + self.load_config_delegate = None + self.unlock_stream_settings() + return None + if streaming_dest == STREAMING_DESTINATION_VALUES[1]: + self.load_config_delegate = self.justin_widget_load_config + self.lineedit_stream_url.setEnabled(False) + self.combobox_audio_codec.setEnabled(False) + return self.get_justin_widget() + + def get_justin_widget(self): + self.justin_widget = QtGui.QWidget() + self.justin_widget_layout = QtGui.QFormLayout() + self.justin_widget.setLayout(self.justin_widget_layout) + + # + # justin.tv Streaming Key + # + + self.label_streaming_key = QtGui.QLabel("Streaming Key") + self.lineedit_streaming_key = QtGui.QLineEdit() + self.justin_widget_layout.addRow(self.label_streaming_key, self.lineedit_streaming_key) + + self.lineedit_streaming_key.textEdited.connect(self.set_streaming_key) + + # + # Note + # + + self.label_note = QtGui.QLabel(self.gui.uiTranslator.translate('rtmp', "*See: http://www.justin.tv/broadcast/adv_other\n" + + "You must be logged in to obtain your Streaming Key")) + self.justin_widget_layout.addRow(self.label_note) + + # + # Checkbox for whether or not to use the justin.tv API to push channel settings + # + + self.label_api_checkbox = QtGui.QLabel("Set Justin.tv channel properties") + self.api_checkbox = QtGui.QCheckBox() + self.justin_widget_layout.addRow(self.label_api_checkbox, self.api_checkbox) + + self.api_checkbox.stateChanged.connect(self.set_use_justin_api) + + # + # Consumer key + # + + self.label_consumer_key = QtGui.QLabel("Consumer Key (optional)") + self.lineedit_consumer_key = QtGui.QLineEdit() + self.justin_widget_layout.addRow(self.label_consumer_key, self.lineedit_consumer_key) + + self.lineedit_consumer_key.textEdited.connect(self.set_consumer_key) + + # + # Consumer secret + # + + self.label_consumer_secret = QtGui.QLabel("Consumer Secret (optional)") + self.lineedit_consumer_secret = QtGui.QLineEdit() + self.justin_widget_layout.addRow(self.label_consumer_secret, self.lineedit_consumer_secret) + + self.lineedit_consumer_secret.textEdited.connect(self.set_consumer_secret) + + # + # Apply button, so as not to accidentally overwrite custom settings + # + + self.apply_button = QtGui.QPushButton("Apply - stream to Justin.tv") + self.apply_button.setToolTip(self.gui.uiTranslator.translate('rtmp', "Overwrite custom settings for justin.tv")) + self.justin_widget_layout.addRow(self.apply_button) + + self.apply_button.clicked.connect(self.apply_justin_settings) + + return self.justin_widget + + def get_widget(self): + if self.widget is None: + self.widget = QtGui.QWidget() + self.widget.setWindowTitle("RTMP Streaming Options") + + self.widget_layout = QtGui.QFormLayout() + self.widget.setLayout(self.widget_layout) + + # + # Streaming presets + # + + self.stream_settings_area = QtGui.QScrollArea() + self.stream_settings_area.setWidgetResizable(True) + self.widget_layout.addRow(self.stream_settings_area) + + self.stream_settings_area.setWidget(self.get_stream_settings_widget()) + + self.label_streaming_dest = QtGui.QLabel("Streaming Destination") + self.combobox_streaming_dest = QtGui.QComboBox() + self.combobox_streaming_dest.addItems(STREAMING_DESTINATION_VALUES) + + self.widget_layout.addRow(self.label_streaming_dest, self.combobox_streaming_dest) + + self.widget.connect(self.combobox_streaming_dest, + QtCore.SIGNAL('currentIndexChanged(const QString&)'), + self.set_streaming_dest) + + return self.widget + + def get_video_quality_layout(self): + """Returns a layout with the video quality config widgets for configtool to use.""" + self.get_widget() + + layout_video_quality = QtGui.QHBoxLayout() + layout_video_quality.addWidget(self.label_video_quality) + layout_video_quality.addWidget(self.spinbox_video_quality) + + return layout_video_quality + + def get_audio_quality_layout(self): + """Returns a layout with the audio quality config widgets for configtool to use.""" + self.get_widget() + + layout_audio_quality = QtGui.QHBoxLayout() + layout_audio_quality.addWidget(self.label_audio_quality) + layout_audio_quality.addWidget(self.spinbox_audio_quality) + + return layout_audio_quality + + def load_streaming_destination_widget(self): + streaming_destination_widget = self.setup_streaming_destination_widget(self.config.streaming_destination) + + if self.streaming_destination_widget is not None: + self.streaming_destination_widget.deleteLater() + self.streaming_destination_widget = None + + if streaming_destination_widget: + self.widget_layout.addRow(streaming_destination_widget) + self.streaming_destination_widget = streaming_destination_widget + + def widget_load_config(self, plugman): + self.get_config() + self.stream_settings_load_config() + + self.combobox_streaming_dest.setCurrentIndex(STREAMING_DESTINATION_VALUES.index(self.config.streaming_destination)) + + self.load_streaming_destination_widget() + if self.load_config_delegate: + self.load_config_delegate() + + def justin_widget_load_config(self): + self.lineedit_streaming_key.setText(self.config.streaming_key) + self.lineedit_consumer_key.setText(self.config.consumer_key) + self.lineedit_consumer_secret.setText(self.config.consumer_secret) + + check_state = 0 + if self.config.use_justin_api == 'yes': + check_state = 2 + self.api_checkbox.setCheckState(check_state) + self.toggle_consumer_key_secret_fields() + + def unlock_stream_settings(self): + self.lineedit_stream_url.setEnabled(True) + self.spinbox_audio_quality.setEnabled(True) + self.spinbox_video_quality.setEnabled(True) + self.combobox_video_tune.setEnabled(True) + self.combobox_audio_codec.setEnabled(True) + + def stream_settings_load_config(self): + self.lineedit_stream_url.setText(self.config.url) + + self.spinbox_audio_quality.setValue(self.config.audio_quality) + self.spinbox_video_quality.setValue(self.config.video_bitrate) + + tuneIndex = self.combobox_video_tune.findText(self.config.video_tune) + self.combobox_video_tune.setCurrentIndex(tuneIndex) + + acIndex = self.combobox_audio_codec.findText(self.config.audio_codec) + self.combobox_audio_codec.setCurrentIndex(acIndex) + + def set_stream_url(self, text): + self.config.url = text + self.config.save() + + def audio_quality_changed(self): + """Called when a change to the SpinBox for audio quality is made""" + self.config.audio_quality = self.spinbox_audio_quality.value() + self.config.save() + + def set_audio_quality(self, quality): + self.get_config() + + if self.config.audio_codec == "lame": + min_val = self.LAME_AUDIO_MIN + range_val = self.LAME_AUDIO_RANGE + else: + min_val = self.FAAC_AUDIO_MIN + range_val = self.FAAC_AUDIO_RANGE + + if quality == Quality.LOW: + self.config.audio_quality = int(min_val + (range_val * Quality.LOW_AUDIO_FACTOR)) + elif quality == Quality.MEDIUM: + self.config.audio_quality = int(min_val + (range_val * Quality.MEDIUM_AUDIO_FACTOR)) + elif quality == Quality.HIGH: + self.config.audio_quality = int(min_val + (range_val * Quality.HIGH_AUDIO_FACTOR)) + + if self.widget_config_loaded: + self.spinbox_audio_quality.setValue(self.config.audio_quality) + + self.config.save() + + def video_bitrate_changed(self): + """Called when a change to the SpinBox for video bitrate is made""" + self.config.video_bitrate = self.spinbox_video_quality.value() + self.config.save() + + def set_video_bitrate(self, bitrate): + self.get_config() + + if self.widget_config_loaded: + self.spinbox_video_quality.setValue(bitrate) + + self.config.video_bitrate = bitrate + self.config.save() + + def set_video_tune(self, tune): + self.config.video_tune = tune + self.config.save() + + def set_audio_codec(self, codec): + self.config.audio_codec = codec + self.config.save() + + if self.gui.config.audio_quality != Quality.CUSTOM: + self.set_audio_quality(self.gui.config.audio_quality) + + def set_streaming_dest(self, dest): + self.config.streaming_destination = dest + + if self.config.streaming_destination in STREAMING_DESTINATION_VALUES: + index = min([i for i in range(len(STREAMING_DESTINATION_VALUES)) + if STREAMING_DESTINATION_VALUES[i] == self.config.streaming_destination]) + self.combobox_streaming_dest.setCurrentIndex(index) + + self.load_streaming_destination_widget() + if self.load_config_delegate: + self.load_config_delegate() + self.config.save() + + def set_streaming_key(self, text): + self.config.streaming_key = str(text) + self.config.save() + + def set_use_justin_api(self, state): + if state != 0: + self.config.use_justin_api = 'yes' + else: + self.config.use_justin_api = 'no' + self.config.save() + self.toggle_consumer_key_secret_fields() + + def toggle_consumer_key_secret_fields(self): + if self.config.use_justin_api == 'yes': + self.lineedit_consumer_key.setEnabled(True) + self.lineedit_consumer_secret.setEnabled(True) + else: + self.lineedit_consumer_key.setEnabled(False) + self.lineedit_consumer_secret.setEnabled(False) + + def set_consumer_key(self, text): + self.config.consumer_key = str(text) + self.config.save() + + def set_consumer_secret(self, text): + self.config.consumer_secret = str(text) + self.config.save() + + def set_justin_api_persistent(self, text): + self.justin_api_persistent = str(text) + self.config.save() + + def apply_justin_settings(self): + # here is where all the justin.tv streaming presets will be applied + self.set_stream_url(self.JUSTIN_URL + self.config.streaming_key) + self.set_audio_codec('lame') + + self.stream_settings_load_config() + + try: + if self.config.consumer_key and self.config.consumer_secret: + url, self.justin_api = JustinApi.open_request(self.config.consumer_key, self.config.consumer_secret) + self.justin_api.set_save_method(self.set_justin_api_persistent) + webbrowser.open(url) + QtGui.QMessageBox.information(self.widget, + "justin.tv authentication", + self.gui.uiTranslator.translate('rtmp', "An authorization URL should have opened in your browser.\n" + "If not, go open the following URL to allow freeseer to manage your justin.tv channel.\n" + "%1").arg(url), + QtGui.QMessageBox.Ok, + QtGui.QMessageBox.Ok) + except KeyError: + log.error("justin.tv API error: Authentication failed. Supplied credentials may be incorrect.") + QtGui.QMessageBox.critical(self.widget, + "justin.tv error", + self.gui.uiTranslator.translate('rtmp', "Authentication failed. Supplied credentials for Justin.tv" + " may be incorrect."), + QtGui.QMessageBox.Ok, + QtGui.QMessageBox.Ok) + + +class JustinApi: + addr = 'api.justin.tv' + + @staticmethod + def open_request(consumer_key, consumer_secret): + """ + returns request url and JustinClient object + the object will need to obtain access token on first use + """ + consumer = oauth.OAuthConsumer(consumer_key, consumer_secret) + url = "http://%s/oauth/request_token" % JustinApi.addr + request = oauth.OAuthRequest.from_consumer_and_token( + consumer, + None, + http_method='GET', + http_url=url) + + request.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(), consumer, None) + + connection = httplib.HTTPConnection(JustinApi.addr) + connection.request('GET', request.http_url, headers=request.to_header()) + result = connection.getresponse().read() + + token = oauth.OAuthToken.from_string(result) + + auth_request = oauth.OAuthRequest.from_token_and_callback( + token=token, + callback='http://localhost/', + http_url='http://%s/oauth/authorize' % JustinApi.addr) + + return auth_request.to_url(), JustinApi(consumer_key=consumer_key, consumer_secret=consumer_secret, request_token_str=result) + + @staticmethod + def from_string(persistent_obj): + """ + Returns JustinClient object from string. + """ + consumer_key, consumer_secret, request_token_str, access_token_str = pickle.loads(persistent_obj) + return JustinApi(consumer_key, consumer_secret, request_token_str, access_token_str) + + def __init__(self, consumer_key="", consumer_secret="", request_token_str="", access_token_str=""): + self.config.consumer_key = consumer_key + self.config.consumer_secret = consumer_secret + self.request_token_str = request_token_str + self.access_token_str = access_token_str + + def set_save_method(self, save_method): + """ + upon obtaining an access token, this object will be have a different + serialization + + in order to support this the given save_method should be called + upon any such change with the new serialization as its only argument + """ + self.save_method = save_method + self.save_method(self.to_string()) + + def obtain_access_token(self): + try: + consumer = oauth.OAuthConsumer(self.config.consumer_key, self.config.consumer_secret) + token = oauth.OAuthToken.from_string(self.request_token_str) + url = "http://%s/oauth/access_token" % JustinApi.addr + request = oauth.OAuthRequest.from_consumer_and_token( + consumer, + token, + http_method='GET', + http_url=url) + request.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(), consumer, token) + connection = httplib.HTTPConnection(self.addr) + connection.request('GET', request.http_url, headers=request.to_header()) + result = connection.getresponse().read() + self.access_token_str = result + oauth.OAuthToken.from_string(result) + self.save_method(self.to_string()) + except KeyError: + log.error("justin.tv API: failed to obtain an access token") + + def get_data(self, endpoint): + try: + token = oauth.OAuthToken.from_string(self.access_token_str) + consumer = oauth.OAuthConsumer(self.config.consumer_key, self.config.consumer_secret) + request = oauth.OAuthRequest.from_consumer_and_token( + consumer, + token, + http_method='GET', + http_url="http://%s/api/%s" % (JustinApi.addr, endpoint)) + request.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(), consumer, token) + connection = httplib.HTTPConnection(self.addr) + connection.request('GET', request.http_url, headers=request.to_header()) + result = connection.getresponse().read() + data = simplejson.loads(result) + except KeyError, simplejson.decoder.JSONDecodeError: + log.error("justin.tv API: failed fetch data from endpoint %s" % endpoint) + return dict() + return data + + def set_data(self, endpoint, payload): + try: + token = oauth.OAuthToken.from_string(self.access_token_str) + consumer = oauth.OAuthConsumer(self.config.consumer_key, self.config.consumer_secret) + request = oauth.OAuthRequest.from_consumer_and_token( + consumer, + token, + http_method='POST', + http_url="http://%s/api/%s" % (JustinApi.addr, endpoint), + parameters=payload) + request.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(), consumer, token) + connection = httplib.HTTPConnection(self.addr) + connection.request('POST', request.http_url, body=request.to_postdata()) + result = connection.getresponse().read() + except KeyError: + log.error("justin.tv API: failed write data to endpoint %s" % endpoint) + return None + return result + + def set_channel_status(self, status, description): + if not self.access_token_str: + self.obtain_access_token() + data = self.get_data("account/whoami.json") + if not data: + return + login = data['login'] + data = self.get_data('channel/show/%s.json' % login) + update_contents = { + 'title': status, + 'status': status, + 'description': description, + } + self.set_data('channel/update.json', update_contents) + + def to_string(self): + return pickle.dumps([self.config.consumer_key, self.config.consumer_secret, str(self.request_token_str), str(self.access_token_str)]) diff --git a/src/freeseer/plugins/output/videopreview/__init__.py b/src/freeseer/plugins/output/videopreview/__init__.py index 8fa2e232..d03dc4ef 100644 --- a/src/freeseer/plugins/output/videopreview/__init__.py +++ b/src/freeseer/plugins/output/videopreview/__init__.py @@ -42,7 +42,7 @@ from freeseer.framework.config import Config, options # .freeseer-plugin custom -import widget +from . import widget # Leaky Queue LEAKY_VALUES = ["no", "upstream", "downstream"] diff --git a/src/freeseer/plugins/output/videopreview/__init__.py.bak b/src/freeseer/plugins/output/videopreview/__init__.py.bak new file mode 100644 index 00000000..8fa2e232 --- /dev/null +++ b/src/freeseer/plugins/output/videopreview/__init__.py.bak @@ -0,0 +1,125 @@ +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://github.com/Freeseer/freeseer/ + +''' +Video Preview +------------- + +An output plugin which provides a video window to preview the video that +is being recorded in real time. + +@author: Thanh Ha +''' + +# GStreamer +import pygst +pygst.require("0.10") +import gst + +# PyQT +from PyQt4.QtCore import SIGNAL + +# Freeseer +from freeseer.framework.plugin import IOutput +from freeseer.framework.config import Config, options + +# .freeseer-plugin custom +import widget + +# Leaky Queue +LEAKY_VALUES = ["no", "upstream", "downstream"] + + +class VideoPreviewConfig(Config): + """Configuration class for VideoPreview plugin.""" + # Video Preview variables + previewsink = options.StringOption("autovideosink") + leakyqueue = options.ChoiceOption(LEAKY_VALUES, "no") + + +class VideoPreview(IOutput): + name = "Video Preview" + os = ["linux", "linux2", "win32", "cygwin", "darwin"] + type = IOutput.VIDEO + recordto = IOutput.OTHER + CONFIG_CLASS = VideoPreviewConfig + + def get_output_bin(self, audio=False, video=True, metadata=None): + bin = gst.Bin() + + # Leaky queue necessary to work with rtmp streaming + videoqueue = gst.element_factory_make("queue", "videoqueue") + videoqueue.set_property("leaky", self.config.leakyqueue) + bin.add(videoqueue) + + cspace = gst.element_factory_make("ffmpegcolorspace", "cspace") + bin.add(cspace) + + videosink = gst.element_factory_make(self.config.previewsink, "videosink") + bin.add(videosink) + + # Setup ghost pad + pad = videoqueue.get_pad("sink") + ghostpad = gst.GhostPad("sink", pad) + bin.add_pad(ghostpad) + + # Link Elements + videoqueue.link(cspace) + cspace.link(videosink) + + return bin + + def get_widget(self): + if self.widget is None: + self.widget = widget.ConfigWidget() + self.widget.leakyQueueComboBox.addItems(LEAKY_VALUES) + return self.widget + + def __enable_connections(self): + self.widget.connect(self.widget.previewComboBox, SIGNAL('currentIndexChanged(const QString&)'), self.set_previewsink) + self.widget.connect(self.widget.leakyQueueComboBox, SIGNAL('currentIndexChanged(const QString&)'), self.set_leakyqueue) + + def widget_load_config(self, plugman): + self.load_config(plugman) + + previewIndex = self.widget.previewComboBox.findText(self.config.previewsink) + self.widget.previewComboBox.setCurrentIndex(previewIndex) + + leakyQueueIndex = self.widget.leakyQueueComboBox.findText(self.config.leakyqueue) + self.widget.leakyQueueComboBox.setCurrentIndex(leakyQueueIndex) + + # Finally enable connections + self.__enable_connections() + + def set_previewsink(self, previewsink): + self.config.previewsink = previewsink + self.config.save() + + def set_leakyqueue(self, value): + self.config.leakyqueue = value + self.config.save() + + ### + ### Translations + ### + def retranslate(self): + self.widget.previewLabel.setText(self.gui.app.translate('plugin-videopreview', 'Preview')) + self.widget.leakyQueueLabel.setText(self.gui.app.translate('plugin-videopreview', 'Leaky Queue')) diff --git a/src/freeseer/plugins/output/webm_output/__init__.py b/src/freeseer/plugins/output/webm_output/__init__.py index 63e0b847..cdac93bc 100644 --- a/src/freeseer/plugins/output/webm_output/__init__.py +++ b/src/freeseer/plugins/output/webm_output/__init__.py @@ -132,7 +132,7 @@ def set_metadata(self, data): ''' self.tags = gst.TagList() - for tag in data.keys(): + for tag in list(data.keys()): if(gst.tag_exists(tag)): self.tags[tag] = data[tag] else: diff --git a/src/freeseer/plugins/output/webm_output/__init__.py.bak b/src/freeseer/plugins/output/webm_output/__init__.py.bak new file mode 100644 index 00000000..63e0b847 --- /dev/null +++ b/src/freeseer/plugins/output/webm_output/__init__.py.bak @@ -0,0 +1,140 @@ +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://github.com/Freeseer/freeseer/ + +''' +WebM Output +----------- + +An output plugin that records to webm format with vp8 encoding for the video +and Vorbis encoding for audio. + +@author: Thanh Ha +''' + +# GStreamer +import pygst +pygst.require("0.10") +import gst + +# Freeseer +from freeseer.framework.plugin import IOutput + + +class WebMOutput(IOutput): + name = "WebM Output" + os = ["linux", "linux2", "win32", "cygwin"] + type = IOutput.BOTH + recordto = IOutput.FILE + extension = "webm" + tags = None + + def get_output_bin(self, audio=True, video=True, metadata=None): + bin = gst.Bin() + + if metadata is not None: + self.set_metadata(metadata) + + # Muxer + muxer = gst.element_factory_make("webmmux", "muxer") + bin.add(muxer) + + filesink = gst.element_factory_make('filesink', 'filesink') + filesink.set_property('location', self.location) + bin.add(filesink) + + # + # Setup Audio Pipeline + # + if audio: + audioqueue = gst.element_factory_make("queue", "audioqueue") + bin.add(audioqueue) + + audioconvert = gst.element_factory_make("audioconvert", "audioconvert") + bin.add(audioconvert) + + audiolevel = gst.element_factory_make('level', 'audiolevel') + audiolevel.set_property('interval', 20000000) + bin.add(audiolevel) + + audiocodec = gst.element_factory_make("vorbisenc", "audiocodec") + bin.add(audiocodec) + + # Setup metadata + vorbistag = gst.element_factory_make("vorbistag", "vorbistag") + # set tag merge mode to GST_TAG_MERGE_REPLACE + merge_mode = gst.TagMergeMode.__enum_values__[2] + + if metadata is not None: + # Only set tag if metadata is set + vorbistag.merge_tags(self.tags, merge_mode) + vorbistag.set_tag_merge_mode(merge_mode) + bin.add(vorbistag) + + # Setup ghost pads + audiopad = audioqueue.get_pad("sink") + audio_ghostpad = gst.GhostPad("audiosink", audiopad) + bin.add_pad(audio_ghostpad) + + # Link Elements + audioqueue.link(audioconvert) + audioconvert.link(audiolevel) + audiolevel.link(audiocodec) + audiocodec.link(vorbistag) + vorbistag.link(muxer) + + # + # Setup Video Pipeline + # + if video: + videoqueue = gst.element_factory_make("queue", "videoqueue") + bin.add(videoqueue) + + videocodec = gst.element_factory_make("vp8enc", "videocodec") + bin.add(videocodec) + + videopad = videoqueue.get_pad("sink") + video_ghostpad = gst.GhostPad("videosink", videopad) + bin.add_pad(video_ghostpad) + + # Link Elements + videoqueue.link(videocodec) + videocodec.link(muxer) + + # + # Link muxer to filesink + # + muxer.link(filesink) + + return bin + + def set_metadata(self, data): + ''' + Populate global tag list variable with file metadata for + vorbistag audio element + ''' + self.tags = gst.TagList() + + for tag in data.keys(): + if(gst.tag_exists(tag)): + self.tags[tag] = data[tag] + else: + #self.core.logger.log.debug("WARNING: Tag \"" + str(tag) + "\" is not registered with gstreamer.") + pass diff --git a/src/freeseer/plugins/videoinput/desktop/__init__.py b/src/freeseer/plugins/videoinput/desktop/__init__.py index a08eb878..0f43acca 100644 --- a/src/freeseer/plugins/videoinput/desktop/__init__.py +++ b/src/freeseer/plugins/videoinput/desktop/__init__.py @@ -46,7 +46,7 @@ from freeseer.framework.config import Config, options # .freeseer-plugin custom modules -import widget +from . import widget log = logging.getLogger(__name__) diff --git a/src/freeseer/plugins/videoinput/desktop/__init__.py.bak b/src/freeseer/plugins/videoinput/desktop/__init__.py.bak new file mode 100644 index 00000000..a08eb878 --- /dev/null +++ b/src/freeseer/plugins/videoinput/desktop/__init__.py.bak @@ -0,0 +1,229 @@ +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://github.com/Freeseer/freeseer/ + +''' +Desktop Source +-------------- + +A video input plugin that uses your desktop as the video source. + +@author: Thanh Ha +''' +import logging +import sys + +# GStreamer modules +import pygst +pygst.require("0.10") +import gst + +# PyQt4 modules +from PyQt4.QtCore import SIGNAL +from PyQt4.QtGui import QApplication +from PyQt4.QtGui import QDesktopWidget + +# Freeseer modules +from freeseer.framework.plugin import IVideoInput +from freeseer.framework.area_selector import AreaSelector +from freeseer.framework.config import Config, options + +# .freeseer-plugin custom modules +import widget + +log = logging.getLogger(__name__) + + +class DesktopLinuxSrcConfig(Config): + """Configuration settings for linux desktop video plugin.""" + + # ximagesrc + desktop = options.StringOption("Full") + screen = options.IntegerOption(0) + window = options.StringOption("") + + # Area Select + start_x = options.IntegerOption(0) + start_y = options.IntegerOption(0) + end_x = options.IntegerOption(0) + end_y = options.IntegerOption(0) + + +class DesktopLinuxSrc(IVideoInput): + name = "Desktop Source" + os = ["linux", "linux2", "win32", "cygwin"] + CONFIG_CLASS = DesktopLinuxSrcConfig + + def get_videoinput_bin(self): + """ + Return the video input object in gstreamer bin format. + """ + bin = gst.Bin() # Do not pass a name so that we can load this input more than once. + + videosrc = None + + if sys.platform.startswith("linux"): + videosrc = gst.element_factory_make("ximagesrc", "videosrc") + + # Configure coordinates if we're not recording full desktop + if self.config.desktop == "Area": + videosrc.set_property("startx", self.config.start_x) + videosrc.set_property("starty", self.config.start_y) + videosrc.set_property("endx", self.config.end_x) + videosrc.set_property("endy", self.config.end_y) + log.debug('Recording Area start: %sx%s end: %sx%s', + self.config.start_x, + self.config.start_y, + self.config.end_x, + self.config.end_y) + + if self.config.desktop == "Window": + videosrc.set_property("xname", self.config.window) + + elif sys.platform in ["win32", "cygwin"]: + videosrc = gst.element_factory_make("dx9screencapsrc", "videosrc") + + # Configure coordinates if we're not recording full desktop + if self.config.desktop == "Area": + videosrc.set_property("x", self.config.start_x) + videosrc.set_property("y", self.config.start_y) + videosrc.set_property("width", self.config.end_x - self.config.start_x) + videosrc.set_property("height", self.config.end_y - self.config.start_y) + log.debug('Recording Area start: %sx%s end: %sx%s', + self.config.start_x, + self.config.start_y, + self.config.end_x, + self.config.end_y) + + bin.add(videosrc) + + colorspace = gst.element_factory_make("ffmpegcolorspace", "colorspace") + bin.add(colorspace) + videosrc.link(colorspace) + + # Setup ghost pad + pad = colorspace.get_pad("src") + ghostpad = gst.GhostPad("videosrc", pad) + bin.add_pad(ghostpad) + + return bin + + def area_select(self): + self.area_selector = AreaSelector(self) + self.area_selector.show() + self.gui.hide() + self.gui.last_dialog.hide() + self.widget.window().hide() + + def areaSelectEvent(self, start_x, start_y, end_x, end_y): + if start_x <= end_x: + self.config.start_x = start_x + self.config.end_x = end_x + else: + self.config.start_x = end_x + self.config.end_x = start_x + + if start_y <= end_y: + self.config.start_y = start_y + self.config.end_y = end_y + else: + self.config.start_y = end_y + self.config.end_y = start_y + + self.config.save() + log.debug('Area selector start: %sx%s end: %sx%s', + self.config.start_x, + self.config.start_y, + self.config.end_x, + self.config.end_y) + # Automatically check the "Record Region" button. + self.set_desktop_area() + self.widget.areaButton.setChecked(True) + self.widget.regionLabel.setText("{}x{} to {}x{}".format( + self.config.start_x, self.config.start_y, self.config.end_x, self.config.end_y)) + + self.gui.show() + self.gui.last_dialog.show() + self.widget.window().show() + + def get_widget(self): + if self.widget is None: + self.widget = widget.ConfigWidget() + + return self.widget + + def get_resolution_pixels(self): + self.get_config() + + if self.config.desktop == "Full": + app = QApplication.instance() + screen_rect = app.desktop().screenGeometry() + return screen_rect.width() * screen_rect.height() + elif self.config.desktop == "Area": + width = self.config.end_x - self.config.start_x + height = self.config.end_y - self.config.start_y + return width * height + + def __enable_connections(self): + self.widget.connect(self.widget.desktopButton, SIGNAL('clicked()'), self.set_desktop_full) + self.widget.connect(self.widget.areaButton, SIGNAL('clicked()'), self.set_desktop_area) + self.widget.connect(self.widget.setAreaButton, SIGNAL('clicked()'), self.area_select) + self.widget.connect(self.widget.screenSpinBox, SIGNAL('valueChanged(int)'), self.set_screen) + + def widget_load_config(self, plugman): + self.get_config() + + if self.config.desktop == "Full": + self.widget.desktopButton.setChecked(True) + elif self.config.desktop == "Area": + self.widget.areaButton.setChecked(True) + + # Try to detect how many screens the user has + # minus 1 since we like to start count at 0 + max_screens = QDesktopWidget().screenCount() + self.widget.screenSpinBox.setMaximum(max_screens - 1) + + self.widget.regionLabel.setText("{}x{} to {}x{}".format( + self.config.start_x, self.config.start_y, self.config.end_x, self.config.end_y)) + + # Finally enable connections + self.__enable_connections() + + def set_screen(self, screen): + self.config.screen = screen + self.config.save() + + def set_desktop_full(self): + self.config.desktop = "Full" + self.config.save() + self.gui.update_video_quality() + + def set_desktop_area(self): + self.config.desktop = "Area" + self.config.save() + self.gui.update_video_quality() + + ### + ### Translations + ### + def retranslate(self): + self.widget.desktopLabel.setText(self.gui.app.translate('plugin-desktop', 'Record Desktop')) + self.widget.areaLabel.setText(self.gui.app.translate('plugin-desktop', 'Record Region')) + self.widget.screenLabel.setText(self.gui.app.translate('plugin-desktop', 'Screen')) diff --git a/src/freeseer/plugins/videoinput/firewiresrc/__init__.py b/src/freeseer/plugins/videoinput/firewiresrc/__init__.py index 2d41d9f4..a26329c2 100644 --- a/src/freeseer/plugins/videoinput/firewiresrc/__init__.py +++ b/src/freeseer/plugins/videoinput/firewiresrc/__init__.py @@ -45,7 +45,7 @@ from freeseer.framework.config import Config, options # .freeseer-plugin custom modules -import widget +from . import widget def detect_devices(): diff --git a/src/freeseer/plugins/videoinput/firewiresrc/__init__.py.bak b/src/freeseer/plugins/videoinput/firewiresrc/__init__.py.bak new file mode 100644 index 00000000..2d41d9f4 --- /dev/null +++ b/src/freeseer/plugins/videoinput/firewiresrc/__init__.py.bak @@ -0,0 +1,151 @@ +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://github.com/Freeseer/freeseer/ + +''' +Firewire Source +--------------- + +A video input plugin that uses connected Firewire devices as the input +source. + +@author: Thanh Ha +''' + +# python-libs +import os + +# GStreamer modules +import pygst +pygst.require("0.10") +import gst + +# PyQt4 modules +from PyQt4.QtCore import SIGNAL + +# Freeseer modules +from freeseer.framework.plugin import IVideoInput +from freeseer.framework.config import Config, options + +# .freeseer-plugin custom modules +import widget + + +def detect_devices(): + """ + Return a list of available firewire devices. + """ + device_list = [] + i = 1 + path = "/dev/fw" + devpath = path + str(i) + + while os.path.exists(devpath): + device_list.append(devpath) + i = i + 1 + devpath = path + str(i) + + return device_list + + +def get_default_device(): + """Returns a default recording device from get_devices().""" + devices = detect_devices() + if not devices: + return '' + return devices[0] + + +class FirewireSrcConfig(Config): + """Config settings for Firewire video source.""" + device = options.StringOption('') + + +class FirewireSrc(IVideoInput): + name = "Firewire Source" + os = ["linux", "linux2"] + CONFIG_CLASS = FirewireSrcConfig + + def __init__(self): + IVideoInput.__init__(self) + + def get_videoinput_bin(self): + bin = gst.Bin() # Do not pass a name so that we can load this input more than once. + + if not self.config.device: + self.config.device = get_default_device() + + videosrc = gst.element_factory_make("dv1394src", "videosrc") + dv1394q1 = gst.element_factory_make('queue', 'dv1394q1') + dv1394dvdemux = gst.element_factory_make('dvdemux', 'dv1394dvdemux') + dv1394q2 = gst.element_factory_make('queue', 'dv1394q2') + dv1394dvdec = gst.element_factory_make('dvdec', 'dv1394dvdec') + + # Add Elements + bin.add(videosrc) + bin.add(dv1394q1) + bin.add(dv1394dvdemux) + bin.add(dv1394q2) + bin.add(dv1394dvdec) + + # Link Elements + videosrc.link(dv1394q1) + dv1394q1.link(dv1394dvdemux) + dv1394dvdemux.link(dv1394q2) + dv1394q2.link(dv1394dvdec) + + # Setup ghost pad + pad = dv1394dvdec.get_pad("src") + ghostpad = gst.GhostPad("videosrc", pad) + bin.add_pad(ghostpad) + + return bin + + def get_widget(self): + if self.widget is None: + self.widget = widget.ConfigWidget() + + return self.widget + + def __enable_connections(self): + self.widget.connect(self.widget.devicesCombobox, SIGNAL('activated(const QString&)'), self.set_device) + + def widget_load_config(self, plugman): + self.load_config(plugman) + + # Load the combobox with inputs + self.widget.devicesCombobox.clear() + for i, device in enumerate(detect_devices()): + self.widget.devicesCombobox.addItem(device) + if device == self.config.device: + self.widget.devicesCombobox.setCurrentIndex(i) + + # Finally enable connections + self.__enable_connections() + + def set_device(self, device): + self.config.device = device + self.config.save() + + ### + ### Translations + ### + def retranslate(self): + self.widget.devicesLabel.setText(self.gui.app.translate('plugin-firewire', 'Video Device')) diff --git a/src/freeseer/plugins/videoinput/usbsrc/__init__.py b/src/freeseer/plugins/videoinput/usbsrc/__init__.py index 9e3e7132..f82d2d7f 100644 --- a/src/freeseer/plugins/videoinput/usbsrc/__init__.py +++ b/src/freeseer/plugins/videoinput/usbsrc/__init__.py @@ -44,7 +44,7 @@ from freeseer.framework.config import Config, options # .freeseer-plugin custom modules -import widget +from . import widget def get_devices(): @@ -90,7 +90,7 @@ def get_default_device(): devicemap = get_devices() if not devicemap: return '' - default = devicemap.itervalues().next() + default = next(iter(devicemap.values())) return default @@ -152,7 +152,7 @@ def widget_load_config(self, plugman): # Load the combobox with inputs self.widget.devicesCombobox.clear() n = 0 - for device, devurl in get_devices().items(): + for device, devurl in list(get_devices().items()): self.widget.devicesCombobox.addItem(device, devurl) if devurl == self.config.device: self.widget.devicesCombobox.setCurrentIndex(n) diff --git a/src/freeseer/plugins/videoinput/usbsrc/__init__.py.bak b/src/freeseer/plugins/videoinput/usbsrc/__init__.py.bak new file mode 100644 index 00000000..9e3e7132 --- /dev/null +++ b/src/freeseer/plugins/videoinput/usbsrc/__init__.py.bak @@ -0,0 +1,172 @@ +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://github.com/Freeseer/freeseer/ + +''' +USB Source +---------- + +A video input plugin that uses USB video devices as the video input source. + +Devices such as Webcams and vga2usb frame grabbers. + +@author: Thanh Ha +''' +import sys + +# GStreamer modules +import pygst +pygst.require("0.10") +import gst + +# PyQt modules +from PyQt4.QtCore import SIGNAL + +# Freeseer modules +from freeseer.framework.plugin import IVideoInput +from freeseer.framework.config import Config, options + +# .freeseer-plugin custom modules +import widget + + +def get_devices(): + """ + Returns a list of possible devices detected as a dictionary + + On Linux the dictionary is a key, value pair of: + Device Name : Device Path + + On Windows the dictionary is a key, value pair of: + Device Name : Device Name + + NOTE: GstPropertyProbe has been removed in later versions of Gstreamer + When a new method is available this function will need to be + redesigned: + https://bugzilla.gnome.org/show_bug.cgi?id=678402 + """ + + devicemap = {} + + if sys.platform.startswith("linux"): + videosrc = gst.element_factory_make("v4l2src", "videosrc") + videosrc.probe_property_name('device') + devices = videosrc.probe_get_values_name('device') + + for device in devices: + videosrc.set_property('device', device) + devicemap[videosrc.get_property('device-name')] = device + + elif sys.platform in ["win32", "cygwin"]: + videosrc = gst.element_factory_make("dshowvideosrc", "videosrc") + videosrc.probe_property_name('device-name') + devices = videosrc.probe_get_values_name('device-name') + + for device in devices: + devicemap[device] = device + + return devicemap + + +def get_default_device(): + """Returns a default recording device from get_devices().""" + devicemap = get_devices() + if not devicemap: + return '' + default = devicemap.itervalues().next() + return default + + +class USBSrcConfig(Config): + """USBSrc Configuration settings.""" + device = options.StringOption('') + + +class USBSrc(IVideoInput): + name = "USB Source" + os = ["linux", "linux2", "win32", "cygwin"] + CONFIG_CLASS = USBSrcConfig + + def __init__(self): + super(USBSrc, self).__init__() + + def get_videoinput_bin(self): + """ + Return the video input object in gstreamer bin format. + """ + bin = gst.Bin() # Do not pass a name so that we can load this input more than once. + + videosrc = None + + if not self.config.device: + self.config.device = get_default_device() + + if sys.platform.startswith("linux"): + videosrc = gst.element_factory_make("v4l2src", "videosrc") + videosrc.set_property("device", self.config.device) + elif sys.platform in ["win32", "cygwin"]: + videosrc = gst.element_factory_make("dshowvideosrc", "videosrc") + videosrc.set_property("device-name", self.config.device) + bin.add(videosrc) + + colorspace = gst.element_factory_make("ffmpegcolorspace", "colorspace") + bin.add(colorspace) + videosrc.link(colorspace) + + # Setup ghost pad + pad = colorspace.get_pad("src") + ghostpad = gst.GhostPad("videosrc", pad) + bin.add_pad(ghostpad) + + return bin + + def get_widget(self): + if self.widget is None: + self.widget = widget.ConfigWidget() + + return self.widget + + def __enable_connections(self): + self.widget.connect(self.widget.devicesCombobox, SIGNAL('activated(int)'), self.set_device) + + def widget_load_config(self, plugman): + self.load_config(plugman) + + # Load the combobox with inputs + self.widget.devicesCombobox.clear() + n = 0 + for device, devurl in get_devices().items(): + self.widget.devicesCombobox.addItem(device, devurl) + if devurl == self.config.device: + self.widget.devicesCombobox.setCurrentIndex(n) + n += 1 + + # Finally enable connections + self.__enable_connections() + + def set_device(self, device): + self.config.device = self.widget.devicesCombobox.itemData(device).toString() + self.config.save() + + ### + ### Translations + ### + def retranslate(self): + self.widget.devicesLabel.setText(self.gui.app.translate('plugin-usb', 'Video Device')) diff --git a/src/freeseer/plugins/videoinput/videotestsrc/__init__.py b/src/freeseer/plugins/videoinput/videotestsrc/__init__.py index ec1572aa..426e00d5 100644 --- a/src/freeseer/plugins/videoinput/videotestsrc/__init__.py +++ b/src/freeseer/plugins/videoinput/videotestsrc/__init__.py @@ -42,7 +42,7 @@ from freeseer.framework.config import Config, options # .freeseer-plugin custom modules -import widget +from . import widget # Patterns PATTERNS = ["smpte", "snow", "black", "white", "red", "green", "blue", diff --git a/src/freeseer/plugins/videoinput/videotestsrc/__init__.py.bak b/src/freeseer/plugins/videoinput/videotestsrc/__init__.py.bak new file mode 100644 index 00000000..ec1572aa --- /dev/null +++ b/src/freeseer/plugins/videoinput/videotestsrc/__init__.py.bak @@ -0,0 +1,116 @@ +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://github.com/Freeseer/freeseer/ + +''' +Video Test Source +----------------- + +A video input plugin that displays a test pattern to the screen. Useful for +testing and debugging Freeseer. + +@author: Thanh Ha +''' + +# GStreamer modules +import pygst +pygst.require("0.10") +import gst + +# PyQt4 modules +from PyQt4.QtCore import SIGNAL + +# Freeseer modules +from freeseer.framework.plugin import IVideoInput +from freeseer.framework.config import Config, options + +# .freeseer-plugin custom modules +import widget + +# Patterns +PATTERNS = ["smpte", "snow", "black", "white", "red", "green", "blue", + "circular", "blink", "smpte75", "zone-plate", "gamut", + "chroma-zone-plate", "ball", "smpte100", "bar"] + + +class VideoTestSrcConfig(Config): + """Config settings for VideoTestSrc plugin.""" + live = options.BooleanOption(False) + pattern = options.ChoiceOption(PATTERNS, default="smpte") + + +class VideoTestSrc(IVideoInput): + name = "Video Test Source" + os = ["linux", "linux2", "win32", "cygwin", "darwin"] + CONFIG_CLASS = VideoTestSrcConfig + + def get_videoinput_bin(self): + bin = gst.Bin() # Do not pass a name so that we can load this input more than once. + + videosrc = gst.element_factory_make("videotestsrc", "videosrc") + videosrc.set_property("pattern", self.config.pattern) + videosrc.set_property("is-live", self.config.live) + bin.add(videosrc) + + # Setup ghost pad + pad = videosrc.get_pad("src") + ghostpad = gst.GhostPad("videosrc", pad) + bin.add_pad(ghostpad) + + return bin + + def get_widget(self): + if self.widget is None: + self.widget = widget.ConfigWidget() + + for i in PATTERNS: + self.widget.patternComboBox.addItem(i) + + return self.widget + + def __enable_connections(self): + self.widget.connect(self.widget.liveCheckBox, SIGNAL('toggled(bool)'), self.set_live) + self.widget.connect(self.widget.patternComboBox, SIGNAL('currentIndexChanged(const QString&)'), self.set_pattern) + + def widget_load_config(self, plugman): + self.load_config(plugman) + + self.widget.liveCheckBox.setChecked(bool(self.config.live)) + patternIndex = self.widget.patternComboBox.findText(self.config.pattern) + self.widget.patternComboBox.setCurrentIndex(patternIndex) + + # Finally enable connections + self.__enable_connections() + + def set_live(self, checked): + self.config.live = checked + self.config.save() + + def set_pattern(self, pattern): + self.config.pattern = pattern + self.config.save() + + ### + ### Translations + ### + def retranslate(self): + self.widget.patternLabel.setText(self.gui.app.translate('plugin-videotest', 'Pattern')) + self.widget.liveCheckBox.setText(self.gui.app.translate('plugin-videotest', 'Live Source')) + self.widget.liveCheckBox.setToolTip(self.gui.app.translate('plugin-videotest', 'Act as a live video source')) diff --git a/src/freeseer/plugins/videomixer/pip/__init__.py b/src/freeseer/plugins/videomixer/pip/__init__.py index 34453e5d..12d0c75a 100644 --- a/src/freeseer/plugins/videomixer/pip/__init__.py +++ b/src/freeseer/plugins/videomixer/pip/__init__.py @@ -42,7 +42,7 @@ from freeseer.framework.config import Config, options # .freeseer-plugin custom modules -import widget +from . import widget class PictureInPictureConfig(Config): @@ -122,7 +122,7 @@ def load_inputs(self, player, mixer, inputs): mainsrc_elements = [input1, mainsrc_scale, mainsrc_capsfilter, mainsrc_colorspace] # Add elements to player in list order - map(lambda element: player.add(element), mainsrc_elements) + list(map(lambda element: player.add(element), mainsrc_elements)) # Link elements in a specific order input1.link(mainsrc_scale) @@ -147,7 +147,7 @@ def load_inputs(self, player, mixer, inputs): pipsrc_elements = [input2, pipsrc_scale, pipsrc_capsfilter, pipsrc_colorspace] #Add elements to player in list order - map(lambda element: player.add(element), pipsrc_elements) + list(map(lambda element: player.add(element), pipsrc_elements)) # Link elements in specific order input2.link(pipsrc_scale) diff --git a/src/freeseer/plugins/videomixer/pip/__init__.py.bak b/src/freeseer/plugins/videomixer/pip/__init__.py.bak new file mode 100644 index 00000000..34453e5d --- /dev/null +++ b/src/freeseer/plugins/videomixer/pip/__init__.py.bak @@ -0,0 +1,257 @@ +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://github.com/Freeseer/freeseer/ + +''' +Picture-In-Picture +------------------ + +A video mixer plugin which takes 2 video sources and creates a +Picture-In-Picture mode. + +@author: Thanh Ha +''' + +# GStreamer modules +import pygst +pygst.require("0.10") +import gst + +# PyQt modules +from PyQt4.QtCore import SIGNAL + +# Freeseer modules +from freeseer.framework.plugin import IVideoMixer +from freeseer.framework.config import Config, options + +# .freeseer-plugin custom modules +import widget + + +class PictureInPictureConfig(Config): + """Configuration class for PIP plugin.""" + main = options.StringOption("Video Test Source") + pip = options.StringOption("Video Test Source") + + +class PictureInPicture(IVideoMixer): + name = "Picture-In-Picture" + os = ["linux", "linux2", "win32", "cygwin", "darwin"] + widget = None + CONFIG_CLASS = PictureInPictureConfig + WIDTH = 640 + HEIGHT = 480 + + def get_videomixer_bin(self): + bin = gst.Bin() + + videomixer = gst.element_factory_make("videomixer", "videomixer") + bin.add(videomixer) + + colorspace = gst.element_factory_make("ffmpegcolorspace", "colorspace") + bin.add(colorspace) + videomixer.link(colorspace) + + # Picture-In-Picture + videobox = gst.element_factory_make("videobox", "videobox") + bin.add(videobox) + + videobox.link(videomixer) + + videobox2 = gst.element_factory_make("videobox", "videobox2") + bin.add(videobox2) + + videobox2.set_property("alpha", 0.6) + videobox2.set_property("border-alpha", 0) + videobox2.set_property("top", -20) + videobox2.set_property("left", -25) + + videobox2.link(videomixer) + + # Setup ghost pad + sinkpad = videobox.get_pad("sink") + sink_ghostpad = gst.GhostPad("sink_main", sinkpad) + bin.add_pad(sink_ghostpad) + + pip_sinkpad = videobox2.get_pad("sink") + pip_ghostpad = gst.GhostPad("sink_pip", pip_sinkpad) + bin.add_pad(pip_ghostpad) + + srcpad = colorspace.get_pad("src") + src_ghostpad = gst.GhostPad("src", srcpad) + bin.add_pad(src_ghostpad) + + return bin + + def get_inputs(self): + inputs = [(self.config.main, 0), (self.config.pip, 1)] + return inputs + + def load_inputs(self, player, mixer, inputs): + # Load main source + input1 = inputs[0] + + # Create videoscale element in order to scale to dimensions not supported by camera + mainsrc_scale = gst.element_factory_make("videoscale", "mainsrc_scale") + + # Create ffmpegcolorspace element to convert from what camera supports to rgb + mainsrc_colorspace = gst.element_factory_make("ffmpegcolorspace", "mainsrc_colorspace") + + # Create capsfilter for limiting to x-raw-rgb pixel video format and setting dimensions + mainsrc_capsfilter = gst.element_factory_make("capsfilter", "mainsrc_capsfilter") + mainsrc_capsfilter.set_property('caps', + gst.caps_from_string('video/x-raw-rgb, width={}, height={}'.format(self.WIDTH, self.HEIGHT))) + + mainsrc_elements = [input1, mainsrc_scale, mainsrc_capsfilter, mainsrc_colorspace] + + # Add elements to player in list order + map(lambda element: player.add(element), mainsrc_elements) + + # Link elements in a specific order + input1.link(mainsrc_scale) + mainsrc_scale.link(mainsrc_capsfilter) + mainsrc_capsfilter.link(mainsrc_colorspace) + + # Link colorspace element to sink pad for pixel format conversion + srcpad = mainsrc_colorspace.get_pad("src") + sinkpad = mixer.get_pad("sink_main") + srcpad.link(sinkpad) + + # Load the secondary source + input2 = inputs[1] + + # Create gst elements as above, but set smaller dimensions + pipsrc_scale = gst.element_factory_make("videoscale", "pipsrc_scale") + pipsrc_colorspace = gst.element_factory_make("ffmpegcolorspace", "pipsrc_colorspace") + pipsrc_capsfilter = gst.element_factory_make("capsfilter", "pipsrc_capsfilter") + pipsrc_capsfilter.set_property('caps', + gst.caps_from_string('video/x-raw-rgb, width=200, height=150')) + + pipsrc_elements = [input2, pipsrc_scale, pipsrc_capsfilter, pipsrc_colorspace] + + #Add elements to player in list order + map(lambda element: player.add(element), pipsrc_elements) + + # Link elements in specific order + input2.link(pipsrc_scale) + pipsrc_scale.link(pipsrc_capsfilter) + pipsrc_capsfilter.link(pipsrc_colorspace) + + # Link colorspace element to sink pad for pixel format conversion + srcpad = pipsrc_colorspace.get_pad("src") + sinkpad = mixer.get_pad("sink_pip") + srcpad.link(sinkpad) + + def get_widget(self): + + if self.widget is None: + self.widget = widget.ConfigWidget() + + return self.widget + + def __enable_connections(self): + self.widget.connect(self.widget.mainInputComboBox, SIGNAL('currentIndexChanged(const QString&)'), self.set_maininput) + self.widget.connect(self.widget.mainInputSetupButton, SIGNAL('clicked()'), self.open_mainInputSetup) + self.widget.connect(self.widget.pipInputComboBox, SIGNAL('currentIndexChanged(const QString&)'), self.set_pipinput) + self.widget.connect(self.widget.pipInputSetupButton, SIGNAL('clicked()'), self.open_pipInputSetup) + + def widget_load_config(self, plugman): + self.get_config() + + sources = [] + plugins = self.plugman.get_videoinput_plugins() + for plugin in plugins: + sources.append(plugin.plugin_object.get_name()) + + box_config_pairs = [ + (self.widget.mainInputComboBox, self.config.main, self.__enable_maininput_setup), + (self.widget.pipInputComboBox, self.config.pip, self.__enable_pipinput_setup), + ] + + # Load combo boxes with inputs: + for combo_box, config, setup in box_config_pairs: + combo_box.clear() + for i, source in enumerate(sources): + combo_box.addItem(source) + if source == config: # Find the current input source and set it + combo_box.setCurrentIndex(i) + setup(config) + + # Finally enable connections + self.__enable_connections() + + def supports_video_quality(self): + return True + + def get_resolution_pixels(self): + return self.WIDTH * self.HEIGHT + + ### + ### Main Input Functions + ### + + def set_maininput(self, input): + self.config.main = input + self.config.save() + self.__enable_maininput_setup(self.config.main) + + def open_mainInputSetup(self): + plugin_name = str(self.widget.mainInputComboBox.currentText()) + plugin = self.plugman.get_plugin_by_name(plugin_name, "VideoInput") + plugin.plugin_object.set_instance(0) + plugin.plugin_object.get_dialog() + + def __enable_maininput_setup(self, source): + '''Activates the source setup button if it has configurable settings''' + plugin = self.plugman.get_plugin_by_name(source, "VideoInput") + if plugin.plugin_object.get_widget() is not None: + self.widget.mainInputSetupStack.setCurrentIndex(1) + else: + self.widget.mainInputSetupStack.setCurrentIndex(0) + + ### + ### PIP Functions + ### + + def set_pipinput(self, input): + self.config.pip = input + self.config.save() + self.__enable_pipinput_setup(self.config.pip) + + def open_pipInputSetup(self): + plugin_name = str(self.widget.pipInputComboBox.currentText()) + plugin = self.plugman.get_plugin_by_name(plugin_name, "VideoInput") + plugin.plugin_object.set_instance(1) + plugin.plugin_object.get_dialog() + + def __enable_pipinput_setup(self, source): + '''Activates the source setup button if it has configurable settings''' + plugin = self.plugman.get_plugin_by_name(source, "VideoInput") + if plugin.plugin_object.get_widget() is not None: + self.widget.pipInputSetupStack.setCurrentIndex(1) + else: + self.widget.pipInputSetupStack.setCurrentIndex(0) + + ### + ### Translations + ### + def retranslate(self): + self.widget.mainInputLabel.setText(self.gui.app.translate('plugin-pip', 'Main Source')) + self.widget.pipInputLabel.setText(self.gui.app.translate('plugin-pip', 'PIP Source')) diff --git a/src/freeseer/plugins/videomixer/videopassthrough/__init__.py b/src/freeseer/plugins/videomixer/videopassthrough/__init__.py index dedfeaeb..cb00a6bc 100644 --- a/src/freeseer/plugins/videomixer/videopassthrough/__init__.py +++ b/src/freeseer/plugins/videomixer/videopassthrough/__init__.py @@ -44,7 +44,7 @@ from freeseer.framework.config import Config, options # .freeseer-plugin custom modules -import widget +from . import widget log = logging.getLogger(__name__) @@ -54,7 +54,7 @@ class VideoPassthroughConfig(Config): input = options.StringOption("Video Test Source") input_type = options.StringOption("video/x-raw-rgb") framerate = options.IntegerOption(30) - resolution = options.ChoiceOption(widget.resmap.keys(), "No Scaling") + resolution = options.ChoiceOption(list(widget.resmap.keys()), "No Scaling") class VideoPassthrough(IVideoMixer): diff --git a/src/freeseer/plugins/videomixer/videopassthrough/__init__.py.bak b/src/freeseer/plugins/videomixer/videopassthrough/__init__.py.bak new file mode 100644 index 00000000..dedfeaeb --- /dev/null +++ b/src/freeseer/plugins/videomixer/videopassthrough/__init__.py.bak @@ -0,0 +1,226 @@ +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://github.com/Freeseer/freeseer/ + +''' +Video Passthrough +----------------- + +A simple video mixer plugin that takes 1 input and passes it on to the +output plugin. This plugin is capable of configuring the video framerate +as well. + +@author: Thanh Ha +''' + +# GStreamer modules +import pygst +pygst.require("0.10") +import gst +import logging + +# PyQt modules +from PyQt4.QtCore import SIGNAL + +# Freeseer modules +from freeseer.framework.plugin import IVideoMixer +from freeseer.framework.config import Config, options + +# .freeseer-plugin custom modules +import widget + +log = logging.getLogger(__name__) + + +class VideoPassthroughConfig(Config): + """Configuration class for VideoPassthrough plugin.""" + input = options.StringOption("Video Test Source") + input_type = options.StringOption("video/x-raw-rgb") + framerate = options.IntegerOption(30) + resolution = options.ChoiceOption(widget.resmap.keys(), "No Scaling") + + +class VideoPassthrough(IVideoMixer): + name = "Video Passthrough" + os = ["linux", "linux2", "win32", "cygwin", "darwin"] + widget = None + CONFIG_CLASS = VideoPassthroughConfig + + def get_videomixer_bin(self): + bin = gst.Bin() + + # Video Rate + videorate = gst.element_factory_make("videorate", "videorate") + bin.add(videorate) + videorate_cap = gst.element_factory_make("capsfilter", + "video_rate_cap") + videorate_cap.set_property("caps", + gst.caps_from_string("%s, framerate=%d/1" % (self.config.input_type, self.config.framerate))) + bin.add(videorate_cap) + # --- End Video Rate + + # Video Scaler (Resolution) + videoscale = gst.element_factory_make("videoscale", "videoscale") + bin.add(videoscale) + videoscale_cap = gst.element_factory_make("capsfilter", + "videoscale_cap") + + # Change the resolution of the source video. + log.debug("Record Resolution: %s", self.config.resolution) + if self.config.resolution != "No Scaling": + width, height = widget.resmap[self.config.resolution] + videoscale_cap.set_property('caps', + gst.caps_from_string("{}, width={}, height={}" + .format(self.config.input_type, width, height))) + + bin.add(videoscale_cap) + # --- End Video Scaler + + colorspace = gst.element_factory_make("ffmpegcolorspace", "colorspace") + bin.add(colorspace) + + # Link Elements + videorate.link(videorate_cap) + videorate_cap.link(videoscale) + videoscale.link(videoscale_cap) + videoscale_cap.link(colorspace) + + # Setup ghost pad + sinkpad = videorate.get_pad("sink") + sink_ghostpad = gst.GhostPad("sink", sinkpad) + bin.add_pad(sink_ghostpad) + + srcpad = colorspace.get_pad("src") + src_ghostpad = gst.GhostPad("src", srcpad) + bin.add_pad(src_ghostpad) + + return bin + + def get_inputs(self): + inputs = [(self.config.input, 0)] + return inputs + + def get_resolution_pixels(self): + self.get_config() + + if self.config.resolution == "No Scaling": + plugin = self.plugman.get_plugin_by_name(self.config.input, "VideoInput") + return plugin.plugin_object.get_resolution_pixels() + else: + width, height = widget.resmap[str(self.config.resolution)] + return width * height + + def supports_video_quality(self): + self.get_config() + + return self.config.input == "Desktop Source" + + def load_inputs(self, player, mixer, inputs): + # Load source + input = inputs[0] + player.add(input) + input.link(mixer) + + def get_widget(self): + if self.widget is None: + self.widget = widget.ConfigWidget() + return self.widget + + def __enable_connections(self): + self.widget.connect(self.widget.inputCombobox, SIGNAL('currentIndexChanged(const QString&)'), self.set_input) + self.widget.connect(self.widget.framerateSlider, SIGNAL("valueChanged(int)"), self.widget.framerateSpinBox.setValue) + self.widget.connect(self.widget.framerateSpinBox, SIGNAL("valueChanged(int)"), self.widget.framerateSlider.setValue) + self.widget.connect(self.widget.videocolourComboBox, SIGNAL("currentIndexChanged(const QString&)"), self.set_input_type) + self.widget.connect(self.widget.framerateSlider, SIGNAL("valueChanged(int)"), self.set_framerate) + self.widget.connect(self.widget.framerateSpinBox, SIGNAL("valueChanged(int)"), self.set_framerate) + self.widget.connect(self.widget.inputSettingsToolButton, SIGNAL('clicked()'), self.source1_setup) + self.widget.connect(self.widget.videoscaleComboBox, SIGNAL("currentIndexChanged(const QString&)"), self.set_videoscale) + + def widget_load_config(self, plugman): + self.get_config() + + sources = [] + plugins = self.plugman.get_videoinput_plugins() + for plugin in plugins: + sources.append(plugin.plugin_object.get_name()) + + # Load the combobox with inputs + self.widget.inputCombobox.clear() + n = 0 + for i in sources: + self.widget.inputCombobox.addItem(i) + if i == self.config.input: + self.widget.inputCombobox.setCurrentIndex(n) + self.__enable_source_setup(self.config.input) + n = n + 1 + + vcolour_index = self.widget.videocolourComboBox.findText(self.config.input_type) + self.widget.videocolourComboBox.setCurrentIndex(vcolour_index) + + vscale_index = self.widget.videoscaleComboBox.findText(self.config.resolution) + self.widget.videoscaleComboBox.setCurrentIndex(vscale_index) + + # Need to set both the Slider and Spingbox since connections + # are not yet loaded at this point + self.widget.framerateSlider.setValue(self.config.framerate) + self.widget.framerateSpinBox.setValue(self.config.framerate) + + # Finally enable connections + self.__enable_connections() + + def source1_setup(self): + plugin = self.plugman.get_plugin_by_name(self.config.input, "VideoInput") + plugin.plugin_object.get_dialog() + + def set_input(self, input): + self.config.input = input + self.config.save() + self.__enable_source_setup(self.config.input) + self.gui.toggle_video_quality(input == "Desktop Source") + + def __enable_source_setup(self, source): + '''Activates the source setup button if it has configurable settings''' + plugin = self.plugman.get_plugin_by_name(source, "VideoInput") + if plugin.plugin_object.get_widget() is not None: + self.widget.inputSettingsStack.setCurrentIndex(1) + else: + self.widget.inputSettingsStack.setCurrentIndex(0) + + def set_input_type(self, input_type): + self.config.input_type = input_type + self.config.save() + + def set_framerate(self, framerate): + self.config.framerate = framerate + self.config.save() + + def set_videoscale(self, resolution): + self.config.resolution = resolution + self.config.save() + self.gui.update_video_quality() + + ### + ### Translations + ### + def retranslate(self): + self.widget.inputLabel.setText(self.gui.app.translate('plugin-video-passthrough', 'Video Input')) + self.widget.videocolourLabel.setText(self.gui.app.translate('plugin-video-passthrough', 'Colour Format')) + self.widget.framerateLabel.setText(self.gui.app.translate('plugin-video-passthrough', 'Framerate')) + self.widget.videoscaleLabel.setText(self.gui.app.translate('plugin-video-passthrough', 'Video Scale')) diff --git a/src/freeseer/tests/framework/config/options/test_choice.py b/src/freeseer/tests/framework/config/options/test_choice.py index 5b26650a..139aac79 100644 --- a/src/freeseer/tests/framework/config/options/test_choice.py +++ b/src/freeseer/tests/framework/config/options/test_choice.py @@ -44,9 +44,9 @@ class TestChoiceOptionNoDefault(unittest.TestCase, OptionTest): 'w0rld', ] - encode_success = zip(valid_success, valid_success) + encode_success = list(zip(valid_success, valid_success)) - decode_success = zip(valid_success, valid_success) + decode_success = list(zip(valid_success, valid_success)) decode_failure = valid_failure def setUp(self): diff --git a/src/freeseer/tests/framework/config/options/test_choice.py.bak b/src/freeseer/tests/framework/config/options/test_choice.py.bak new file mode 100644 index 00000000..5b26650a --- /dev/null +++ b/src/freeseer/tests/framework/config/options/test_choice.py.bak @@ -0,0 +1,95 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013, 2014 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +import unittest + +from jsonschema import validate +from jsonschema import ValidationError + +from freeseer.framework.config.options import ChoiceOption +from freeseer.tests.framework.config.options import OptionTest + + +class TestChoiceOptionNoDefault(unittest.TestCase, OptionTest): + """Tests ChoiceOption without a default value.""" + + valid_success = [ + 'hello', + 'world', + ] + valid_failure = [ + 'hello1', + '1hello', + 'w0rld', + ] + + encode_success = zip(valid_success, valid_success) + + decode_success = zip(valid_success, valid_success) + decode_failure = valid_failure + + def setUp(self): + self.option = ChoiceOption([ + 'hello', + 'world', + ]) + + def test_schema(self): + """Tests a ChoiceOption schema method.""" + expected = { + 'enum': [ + 'hello', + 'world', + ], + } + self.assertRaises(ValidationError, validate, 'error', self.option.schema()) + self.assertIsNone(validate('world', self.option.schema())) + self.assertDictEqual(self.option.schema(), expected) + + +class TestChoiceOptionWithDefault(TestChoiceOptionNoDefault): + """Tests ChoiceOption with a default value.""" + + def setUp(self): + self.option = ChoiceOption([ + 'hello', + 'world', + ], 'hello') + + def test_default(self): + """Tests that the default was set correctly.""" + self.assertEqual(self.option.default, 'hello') + + def test_schema(self): + """Tests a ChoiceOption schema method.""" + expected = { + 'default': 'hello', + 'enum': [ + 'hello', + 'world', + ], + } + self.assertRaises(ValidationError, validate, 'error', self.option.schema()) + self.assertIsNone(validate('world', self.option.schema())) + self.assertDictEqual(self.option.schema(), expected) diff --git a/src/freeseer/tests/framework/config/options/test_float.py b/src/freeseer/tests/framework/config/options/test_float.py index 54ba660f..0544c594 100644 --- a/src/freeseer/tests/framework/config/options/test_float.py +++ b/src/freeseer/tests/framework/config/options/test_float.py @@ -34,11 +34,11 @@ class TestFloatOptionNoDefault(unittest.TestCase, OptionTest): """Tests FloatOption without a default value.""" - valid_success = [x / 10.0 for x in xrange(-100, 100)] + valid_success = [x / 10.0 for x in range(-100, 100)] - encode_success = zip(valid_success, map(str, valid_success)) + encode_success = list(zip(valid_success, list(map(str, valid_success)))) - decode_success = zip(map(str, valid_success), valid_success) + decode_success = list(zip(list(map(str, valid_success)), valid_success)) decode_failure = [ 'hello', '1world', diff --git a/src/freeseer/tests/framework/config/options/test_float.py.bak b/src/freeseer/tests/framework/config/options/test_float.py.bak new file mode 100644 index 00000000..54ba660f --- /dev/null +++ b/src/freeseer/tests/framework/config/options/test_float.py.bak @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2014 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +import unittest + +from jsonschema import validate +from jsonschema import ValidationError + +from freeseer.framework.config.options import FloatOption +from freeseer.tests.framework.config.options import OptionTest + + +class TestFloatOptionNoDefault(unittest.TestCase, OptionTest): + """Tests FloatOption without a default value.""" + + valid_success = [x / 10.0 for x in xrange(-100, 100)] + + encode_success = zip(valid_success, map(str, valid_success)) + + decode_success = zip(map(str, valid_success), valid_success) + decode_failure = [ + 'hello', + '1world', + 'test2', + ] + + def setUp(self): + self.option = FloatOption() + + def test_schema(self): + """Tests FloatOption schema method.""" + self.assertRaises(ValidationError, validate, 'error', self.option.schema()) + self.assertIsNone(validate(5.5, self.option.schema())) + self.assertDictEqual(self.option.schema(), {'type': 'number'}) + + +class TestFloatOptionWithDefault(TestFloatOptionNoDefault): + """Tests FloatOption with a default value.""" + + def setUp(self): + self.option = FloatOption(1234.5) + + def test_default(self): + """Tests that the default was set correctly.""" + self.assertEqual(self.option.default, 1234.5) + + def test_schema(self): + """Tests FloatOption schema method.""" + self.assertRaises(ValidationError, validate, 'error', self.option.schema()) + self.assertIsNone(validate(5.0, self.option.schema())) + self.assertDictEqual(self.option.schema(), {'default': 1234.5, 'type': 'number'}) diff --git a/src/freeseer/tests/framework/config/options/test_folder.py b/src/freeseer/tests/framework/config/options/test_folder.py index 7dbc540d..5927c40e 100644 --- a/src/freeseer/tests/framework/config/options/test_folder.py +++ b/src/freeseer/tests/framework/config/options/test_folder.py @@ -44,9 +44,9 @@ class TestFolderOptionNoDefault(unittest.TestCase, OptionTest): '/tmp/1', ] - encode_success = zip(valid_success, valid_success) + encode_success = list(zip(valid_success, valid_success)) - decode_success = zip(valid_success, valid_success) + decode_success = list(zip(valid_success, valid_success)) decode_failure = valid_failure def setUp(self): diff --git a/src/freeseer/tests/framework/config/options/test_folder.py.bak b/src/freeseer/tests/framework/config/options/test_folder.py.bak new file mode 100644 index 00000000..7dbc540d --- /dev/null +++ b/src/freeseer/tests/framework/config/options/test_folder.py.bak @@ -0,0 +1,105 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013, 2014 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +import os +import shutil +import tempfile +import unittest + +from jsonschema import validate +from jsonschema import ValidationError + +from freeseer.framework.config.options import FolderOption +from freeseer.tests.framework.config.options import OptionTest + + +class TestFolderOptionNoDefault(unittest.TestCase, OptionTest): + """Tests FolderOption without a default value.""" + + valid_success = [ + '/tmp', + ] + valid_failure = [ + '/tmp/1', + ] + + encode_success = zip(valid_success, valid_success) + + decode_success = zip(valid_success, valid_success) + decode_failure = valid_failure + + def setUp(self): + self.option = FolderOption() + + def test_presentation(self): + path = tempfile.mkdtemp() + shutil.rmtree(path) + + presentation_value = self.option.presentation(path) + self.assertEqual(presentation_value, path) + self.assertFalse(os.path.exists(presentation_value)) + + def test_schema(self): + """Tests StringOption schema method.""" + self.assertRaises(ValidationError, validate, 1, self.option.schema()) + self.assertIsNone(validate('/tmp2', self.option.schema())) + self.assertDictEqual(self.option.schema(), {'type': 'string'}) + + +class TestFolderOptionAutoCreate(TestFolderOptionNoDefault): + """Tests FolderOption without a default value, and with auto_create turned on.""" + + valid_failure = [] + + decode_failure = [] + + def setUp(self): + self.option = FolderOption(auto_create=True) + + def test_presentation(self): + path = tempfile.mkdtemp() + shutil.rmtree(path) + + presentation_value = self.option.presentation(path) + self.assertEqual(presentation_value, path) + self.assertTrue(os.path.exists(presentation_value)) + + shutil.rmtree(path) + + +class TestFolderOptionWithDefault(TestFolderOptionNoDefault): + """Tests FolderOption with a default value.""" + + def setUp(self): + self.option = FolderOption('/tmp') + + def test_default(self): + """Tests that the default was set correctly.""" + self.assertEqual(self.option.default, '/tmp') + + def test_schema(self): + """Tests StringOption schema method.""" + self.assertRaises(ValidationError, validate, 1, self.option.schema()) + self.assertIsNone(validate('/tmp2', self.option.schema())) + self.assertDictEqual(self.option.schema(), {'default': '/tmp', 'type': 'string'}) diff --git a/src/freeseer/tests/framework/config/options/test_integer.py b/src/freeseer/tests/framework/config/options/test_integer.py index 237efd33..adf3c672 100644 --- a/src/freeseer/tests/framework/config/options/test_integer.py +++ b/src/freeseer/tests/framework/config/options/test_integer.py @@ -34,11 +34,11 @@ class TestIntegerOptionNoDefault(unittest.TestCase, OptionTest): """Tests IntegerOption without a default value.""" - valid_success = range(-1000, 1000) + valid_success = list(range(-1000, 1000)) - encode_success = zip(valid_success, map(str, valid_success)) + encode_success = list(zip(valid_success, list(map(str, valid_success)))) - decode_success = zip(map(str, valid_success), valid_success) + decode_success = list(zip(list(map(str, valid_success)), valid_success)) decode_failure = [ 'hello', '1world', diff --git a/src/freeseer/tests/framework/config/options/test_integer.py.bak b/src/freeseer/tests/framework/config/options/test_integer.py.bak new file mode 100644 index 00000000..237efd33 --- /dev/null +++ b/src/freeseer/tests/framework/config/options/test_integer.py.bak @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2011, 2013, 2014 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +import unittest + +from jsonschema import validate +from jsonschema import ValidationError + +from freeseer.framework.config.options import IntegerOption +from freeseer.tests.framework.config.options import OptionTest + + +class TestIntegerOptionNoDefault(unittest.TestCase, OptionTest): + """Tests IntegerOption without a default value.""" + + valid_success = range(-1000, 1000) + + encode_success = zip(valid_success, map(str, valid_success)) + + decode_success = zip(map(str, valid_success), valid_success) + decode_failure = [ + 'hello', + '1world', + 'test2', + ] + + def setUp(self): + self.option = IntegerOption() + + def test_schema(self): + """Tests IntegerOption schema method.""" + self.assertRaises(ValidationError, validate, 1.0, self.option.schema()) + self.assertIsNone(validate(5, self.option.schema())) + self.assertDictEqual(self.option.schema(), {'type': 'integer'}) + + +class TestIntegerOptionWithDefault(TestIntegerOptionNoDefault): + """Tests IntegerOption with a default value.""" + + def setUp(self): + self.option = IntegerOption(1234) + + def test_default(self): + """Tests that the default was set correctly.""" + self.assertEqual(self.option.default, 1234) + + def test_schema(self): + """Tests IntegerOption schema method.""" + self.assertRaises(ValidationError, validate, 1.0, self.option.schema()) + self.assertIsNone(validate(5, self.option.schema())) + self.assertDictEqual(self.option.schema(), {'default': 1234, 'type': 'integer'}) diff --git a/src/freeseer/tests/framework/test_multimedia.py b/src/freeseer/tests/framework/test_multimedia.py index eed754ac..1afced12 100644 --- a/src/freeseer/tests/framework/test_multimedia.py +++ b/src/freeseer/tests/framework/test_multimedia.py @@ -52,10 +52,10 @@ def tearDown(self): shutil.rmtree(self.profile_manager._base_folder) def test_load_backend(self): - self.multimedia.load_backend(filename=u"test.ogg") + self.multimedia.load_backend(filename="test.ogg") def test_record_functions(self): - self.multimedia.load_backend(filename=u"test.ogg") + self.multimedia.load_backend(filename="test.ogg") self.multimedia.record() self.multimedia.pause() self.multimedia.stop() diff --git a/src/freeseer/tests/framework/test_multimedia.py.bak b/src/freeseer/tests/framework/test_multimedia.py.bak new file mode 100644 index 00000000..eed754ac --- /dev/null +++ b/src/freeseer/tests/framework/test_multimedia.py.bak @@ -0,0 +1,77 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2012, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +import shutil +import tempfile +import unittest + +import pygst +pygst.require("0.10") +import gst + +from freeseer.framework.config.profile import ProfileManager +from freeseer.framework.multimedia import Multimedia +from freeseer.framework.plugin import PluginManager +from freeseer import settings + + +class TestMultimedia(unittest.TestCase): + + def setUp(self): + self.profile_manager = ProfileManager(tempfile.mkdtemp()) + self.temp_video_dir = tempfile.mkdtemp() + profile = self.profile_manager.get('testing') + config = profile.get_config('freeseer.conf', settings.FreeseerConfig, ['Global'], read_only=True) + config.videodir = self.temp_video_dir + plugin_manager = PluginManager(profile) + self.multimedia = Multimedia(config, plugin_manager) + + def tearDown(self): + shutil.rmtree(self.temp_video_dir) + shutil.rmtree(self.profile_manager._base_folder) + + def test_load_backend(self): + self.multimedia.load_backend(filename=u"test.ogg") + + def test_record_functions(self): + self.multimedia.load_backend(filename=u"test.ogg") + self.multimedia.record() + self.multimedia.pause() + self.multimedia.stop() + + def test_current_state_is_record(self): + self.multimedia.record() + self.assertEqual(self.multimedia.current_state, self.multimedia.RECORD) + self.assertEqual(self.multimedia.player.get_state()[1], gst.STATE_PLAYING) + + def test_current_state_is_pause(self): + self.multimedia.pause() + self.assertEqual(self.multimedia.current_state, self.multimedia.PAUSE) + self.assertEqual(self.multimedia.player.get_state()[1], gst.STATE_PAUSED) + + def test_current_state_is_not_stop(self): + self.multimedia.player.set_state(gst.STATE_NULL) + self.multimedia.stop() + self.assertNotEqual(self.multimedia.current_state, self.multimedia.STOP) + self.assertEqual(self.multimedia.player.get_state()[1], gst.STATE_NULL) diff --git a/src/freeseer/tests/framework/test_presentation.py b/src/freeseer/tests/framework/test_presentation.py index 5d882254..3b365961 100644 --- a/src/freeseer/tests/framework/test_presentation.py +++ b/src/freeseer/tests/framework/test_presentation.py @@ -51,7 +51,7 @@ def test_correct_time_set(self): self.assertTrue(self.pres.endTime == "Later") def test_speaker_not_first_param(self): - self.assertNotEquals(self.pres.speaker, "John Doe") + self.assertNotEqual(self.pres.speaker, "John Doe") def test_event_is_default(self): self.assertTrue(self.pres.event == "haha") diff --git a/src/freeseer/tests/framework/test_presentation.py.bak b/src/freeseer/tests/framework/test_presentation.py.bak new file mode 100644 index 00000000..5d882254 --- /dev/null +++ b/src/freeseer/tests/framework/test_presentation.py.bak @@ -0,0 +1,60 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2012, 2013 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +import unittest + +from freeseer.framework.presentation import Presentation + + +# +# Note: The Presentation class doesn't really need to be tested +# since all the functionality is set and referenced using +# public attributes. +# +# This test class was implemented as a quick-and-easy demo +# and proof-of-concept for Freeseer's test framework +# + + +class TestPresentation(unittest.TestCase): + + def setUp(self): + ''' + Generic unittest.TestCase.setUp() + ''' + + self.pres = Presentation("John Doe", event="haha", startTime="NOW", endTime="Later") + + def test_correct_time_set(self): + self.assertTrue(self.pres.startTime == "NOW") + self.assertTrue(self.pres.endTime == "Later") + + def test_speaker_not_first_param(self): + self.assertNotEquals(self.pres.speaker, "John Doe") + + def test_event_is_default(self): + self.assertTrue(self.pres.event == "haha") + + def test_room_is_default(self): + self.assertTrue(self.pres.room == "Default") diff --git a/src/freeseer/tests/framework/test_youtube.py b/src/freeseer/tests/framework/test_youtube.py index 2a7f3cd9..0816b16e 100644 --- a/src/freeseer/tests/framework/test_youtube.py +++ b/src/freeseer/tests/framework/test_youtube.py @@ -41,7 +41,7 @@ class TestYoutubeService(unittest.TestCase): ], 'categoryId': 27, 'description': 'At Test by Alex recorded on 2014-03-09', - 'title': u'Test', + 'title': 'Test', } def test_get_metadata(self): diff --git a/src/freeseer/tests/framework/test_youtube.py.bak b/src/freeseer/tests/framework/test_youtube.py.bak new file mode 100644 index 00000000..2a7f3cd9 --- /dev/null +++ b/src/freeseer/tests/framework/test_youtube.py.bak @@ -0,0 +1,70 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2014 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +import os +import unittest +from mock import Mock +import pytest + +from freeseer.framework.youtube import Response +from freeseer.framework.youtube import YoutubeService + + +class TestYoutubeService(unittest.TestCase): + SAMPLE_VIDEO = os.path.join(os.path.dirname(__file__), 'sample_video.ogg') + SAMPLE_VIDEO_METADATA = { + 'tags': [ + 'Freeseer', + 'FOSSLC', + 'Open Source', + ], + 'categoryId': 27, + 'description': 'At Test by Alex recorded on 2014-03-09', + 'title': u'Test', + } + + def test_get_metadata(self): + """Test retrieval of metadata from video file. + + Case: Returned metadata should be equal to sample video's metadata.""" + metadata = YoutubeService.get_metadata(self.SAMPLE_VIDEO) + self.assertDictEqual(self.SAMPLE_VIDEO_METADATA, metadata) + + def test_upload_video(self): + """Test uploading a video file using mocks""" + youtube = YoutubeService() + youtube.upload_video = Mock(return_value=(Response.SUCCESS, None)) + response_code, response = youtube.upload_video(self.SAMPLE_VIDEO) + youtube.upload_video.assert_called_with(self.SAMPLE_VIDEO) + self.assertEqual(Response.SUCCESS, response_code) + + +@pytest.mark.parametrize("video, expected", [ + ("/path/to/test.ogg", True), + ("test.webm", True), + ("asdfg.qwergb", False), +]) +def test_valid_video_file(video, expected): + """Tests valid_video_file function for all test cases.""" + assert YoutubeService.valid_video_file(video) == expected diff --git a/src/freeseer/tests/frontend/configtool/test_config_tool.py b/src/freeseer/tests/frontend/configtool/test_config_tool.py index 0665dafc..7c242c23 100644 --- a/src/freeseer/tests/frontend/configtool/test_config_tool.py +++ b/src/freeseer/tests/frontend/configtool/test_config_tool.py @@ -88,13 +88,13 @@ def check_combobox_corner_cases_frontend(self, combobox_widget): """ index = combobox_widget.currentIndex() combobox_widget.setCurrentIndex(0) - self.assertEquals(combobox_widget.currentIndex(), 0) + self.assertEqual(combobox_widget.currentIndex(), 0) self.assertIsNot(combobox_widget.currentText(), None) combobox_widget.setCurrentIndex(combobox_widget.count() - 1) - self.assertEquals(combobox_widget.currentIndex(), (combobox_widget.count() - 1)) + self.assertEqual(combobox_widget.currentIndex(), (combobox_widget.count() - 1)) self.assertIsNot(combobox_widget.currentText(), None) combobox_widget.setCurrentIndex(index) - self.assertEquals(combobox_widget.currentIndex(), index) + self.assertEqual(combobox_widget.currentIndex(), index) self.assertIsNot(combobox_widget.currentText(), None) def select_general_settings_tab(self): @@ -320,12 +320,12 @@ def test_plugin_settings(self): QtTest.QTest.keyClick(self.config_tool.pluginWidget.list, QtCore.Qt.Key_Down) # Assert expected categories exist. - self.assertIn('AudioInput', plugin_category.values()) - self.assertIn('AudioMixer', plugin_category.values()) - self.assertIn('VideoInput', plugin_category.values()) - self.assertIn('VideoMixer', plugin_category.values()) - self.assertIn('Output', plugin_category.values()) - self.assertIn('Importer', plugin_category.values()) + self.assertIn('AudioInput', list(plugin_category.values())) + self.assertIn('AudioMixer', list(plugin_category.values())) + self.assertIn('VideoInput', list(plugin_category.values())) + self.assertIn('VideoMixer', list(plugin_category.values())) + self.assertIn('Output', list(plugin_category.values())) + self.assertIn('Importer', list(plugin_category.values())) for category in ['AudioInput', 'AudioMixer', 'VideoInput', 'VideoMixer', 'Output', 'Importer']: for plugin in self.config_tool.get_plugins(category): diff --git a/src/freeseer/tests/frontend/configtool/test_config_tool.py.bak b/src/freeseer/tests/frontend/configtool/test_config_tool.py.bak new file mode 100644 index 00000000..0665dafc --- /dev/null +++ b/src/freeseer/tests/frontend/configtool/test_config_tool.py.bak @@ -0,0 +1,381 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2012, 2013, 2014 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +from mock import MagicMock +import shutil +import tempfile +import unittest + +from PyQt4 import Qt +from PyQt4 import QtCore +from PyQt4 import QtGui +from PyQt4 import QtTest + +from freeseer.framework.config.profile import ProfileManager +from freeseer.frontend.configtool.configtool import ConfigToolApp +from freeseer import settings + + +class TestConfigToolApp(unittest.TestCase): + """ + Test suite to verify the functionality of the ConfigToolApp class. + + Tests interact like an end user (using QtTest). Expect the app to be rendered. + + NOTE: most tests will follow this format: + 1. Get current config setting + 2. Make UI change (config change happens immediately) + 3. Reparse config file + 4. Test that has changed (using the previous and new) + """ + + def setUp(self): + """ + Stardard init method: runs before each test_* method + + Initializes a QtGui.QApplication and ConfigToolApp object. + ConfigToolApp.show() causes the UI to be rendered. + """ + self.profile_manager = ProfileManager(tempfile.mkdtemp()) + profile = self.profile_manager.get('testing') + config = profile.get_config('freeseer.conf', settings.FreeseerConfig, storage_args=['Global'], read_only=False) + + self.app = QtGui.QApplication([]) + self.config_tool = ConfigToolApp(profile, config) + self.config_tool.show() + + def tearDown(self): + """ + Standard tear down method. Runs after each test_* method. + + This method closes the ConfigToolApp by clicking the "close" button + """ + + QtTest.QTest.mouseClick(self.config_tool.mainWidget.closePushButton, Qt.Qt.LeftButton) + shutil.rmtree(self.profile_manager._base_folder) + del self.config_tool.app + self.app.deleteLater() + + def test_default_widget(self): + self.assertEqual(self.config_tool.currentWidget, self.config_tool.generalWidget) + + def check_combobox_corner_cases_frontend(self, combobox_widget): + """ + Tests that a given QtComboBox has: + - a default selected item + - does not blow up on the minimum and maximum index item in the combobox list + """ + index = combobox_widget.currentIndex() + combobox_widget.setCurrentIndex(0) + self.assertEquals(combobox_widget.currentIndex(), 0) + self.assertIsNot(combobox_widget.currentText(), None) + combobox_widget.setCurrentIndex(combobox_widget.count() - 1) + self.assertEquals(combobox_widget.currentIndex(), (combobox_widget.count() - 1)) + self.assertIsNot(combobox_widget.currentText(), None) + combobox_widget.setCurrentIndex(index) + self.assertEquals(combobox_widget.currentIndex(), index) + self.assertIsNot(combobox_widget.currentText(), None) + + def select_general_settings_tab(self): + # Select "General" tab + item = self.config_tool.mainWidget.optionsTreeWidget.findItems(self.config_tool.generalString, + QtCore.Qt.MatchExactly) + self.assertFalse(not item or item[0] is None) + self.config_tool.mainWidget.optionsTreeWidget.setCurrentItem(item[0]) + QtTest.QTest.mouseClick(self.config_tool.mainWidget.optionsTreeWidget, Qt.Qt.LeftButton) + + def test_general_settings_checkbox(self): + """ + Test the config tool's General Tab auto-hide system tray icon checkbox with simulated user input + """ + self.select_general_settings_tab() + # Test disabled checkbox + self.config_tool.currentWidget.autoHideCheckBox.setChecked(False) + self.assertEqual(self.config_tool.currentWidget.autoHideCheckBox.checkState(), QtCore.Qt.Unchecked) + self.assertFalse(self.config_tool.config.auto_hide) + + # Test enabled checkbox + self.config_tool.currentWidget.autoHideCheckBox.setChecked(True) + self.assertEqual(self.config_tool.currentWidget.autoHideCheckBox.checkState(), QtCore.Qt.Checked) + self.assertTrue(self.config_tool.config.auto_hide) + + def test_general_settings_dropdown_menu(self): + """ + Test the config tool's General Tab language selection drop down menu with simulated user input + """ + self.select_general_settings_tab() + # Test dropdown menu + language_combo_box = self.config_tool.generalWidget.languageComboBox + self.check_combobox_corner_cases_frontend(language_combo_box) + + QtTest.QTest.keyClick(language_combo_box, QtCore.Qt.Key_PageUp) # Test simulated user interaction with drop down list + for i in range(language_combo_box.count() - 2): + last_language = self.config_tool.config.default_language + QtTest.QTest.keyClick(language_combo_box, QtCore.Qt.Key_Down) + current_language = self.config_tool.config.default_language + self.assertNotEqual(last_language, current_language) + + # Test frontend constants + self.assertNotEqual(language_combo_box.findText('Deutsch'), -1) # Assert there are multiple languages in the menu + self.assertNotEqual(language_combo_box.findText('English'), -1) + self.assertNotEqual(language_combo_box.findText('Nederlands'), -1) + + def test_general_settings_help_reset(self): + """ + Test the config tool's General Tab help link and reset button with simulated user input + """ + self.select_general_settings_tab() + # Test that Help us translate tries to open a web url. + QtGui.QDesktopServices.openUrl = MagicMock(return_value=None) + self.config_tool.open_translate_url() + QtGui.QDesktopServices.openUrl.assert_called_with( + QtCore.QUrl("http://freeseer.readthedocs.org/en/latest/contribute/translation.html") + ) + + # Reset + # The reset test should set the backend config_tool values, simulate a user clicking through the reset popup and + # verify that the backend config_tool values have been set to defaults. + # TODO: FIXME. Related to gh#631. The buttons on the dialog cause segfaults in the test environment and prevent + # the test from being implemented at the present time. + # self.config_tool.confirm_reset() + + def select_recording_tab(self): + """ + Helper function used to open up the recording tab for recording tab related tests + """ + item = self.config_tool.mainWidget.optionsTreeWidget.findItems(self.config_tool.avString, + QtCore.Qt.MatchExactly) + self.assertFalse(not item or item[0] is None) + self.config_tool.mainWidget.optionsTreeWidget.setCurrentItem(item[0]) + QtTest.QTest.mouseClick(self.config_tool.mainWidget.optionsTreeWidget, Qt.Qt.LeftButton) + self.assertEqual(self.config_tool.currentWidget, self.config_tool.avWidget) + + def test_recording_settings_file(self): + """ + Simulates a user interacting with the config tool record to file settings. + """ + self.select_recording_tab() + # Test disabled checkbox + self.config_tool.currentWidget.fileGroupBox.setChecked(False) + self.assertFalse(self.config_tool.config.record_to_file) + + # Test enabled checkbox + self.config_tool.currentWidget.fileGroupBox.setChecked(True) + self.assertTrue(self.config_tool.config.record_to_file) + self.assertIn(self.config_tool.config.record_to_file_plugin, ['Ogg Output', 'WebM Output', 'Raw Output']) + + # Test combo box + file_combo_box = self.config_tool.avWidget.fileComboBox + self.check_combobox_corner_cases_frontend(file_combo_box) + QtTest.QTest.keyClick(file_combo_box, QtCore.Qt.Key_PageUp) # Simulate user input to test backend and frontend + for i in range(file_combo_box.count() - 2): + last_plugin = self.config_tool.config.record_to_file_plugin + QtTest.QTest.keyClick(file_combo_box, QtCore.Qt.Key_Down) + current_plugin = self.config_tool.config.record_to_file_plugin + self.assertNotEqual(last_plugin, current_plugin) + + # Test frontend text values + self.assertNotEqual(file_combo_box.findText('Ogg Output'), -1) + self.assertNotEqual(file_combo_box.findText('WebM Output'), -1) + self.assertNotEqual(file_combo_box.findText('Raw Output'), -1) + + def test_recording_settings_stream(self): + """ + Simulates a user interacting with the config tool record to output stream settings. + """ + self.select_recording_tab() + # Test disabled checkbox + self.config_tool.currentWidget.streamGroupBox.setChecked(False) + self.assertFalse(self.config_tool.config.record_to_stream) + + # Test enabled checkbox + self.config_tool.currentWidget.streamGroupBox.setChecked(True) + self.assertTrue(self.config_tool.config.record_to_stream) + + # Test combo box + stream_combo_box = self.config_tool.avWidget.streamComboBox + self.check_combobox_corner_cases_frontend(stream_combo_box) + QtTest.QTest.keyClick(stream_combo_box, QtCore.Qt.Key_PageUp) # Simulate user input to test backend and frontend + for i in range(stream_combo_box.count() - 2): + last_plugin = self.config_tool.plugman.get_plugin_by_name(stream_combo_box.currentText(), 'Output') + QtTest.QTest.keyClick(stream_combo_box, QtCore.Qt.Key_Down) + current_plugin = self.config_tool.plugman.get_plugin_by_name(stream_combo_box.currentText(), 'Output') + self.assertNotEqual(last_plugin, current_plugin) + + # Test frontend text values + self.assertNotEqual(stream_combo_box.findText('RTMP Streaming'), -1) + self.assertNotEqual(stream_combo_box.findText('Ogg Icecast'), -1) + + def test_recording_settings_video_input(self): + """ + Simulates a user interacting with the config tool record to video input setting. + """ + + self.select_recording_tab() + # Test disabled checkbox + self.config_tool.currentWidget.videoGroupBox.setChecked(False) + self.assertFalse(self.config_tool.config.enable_video_recording) + + # Test enabled checkbox + self.config_tool.currentWidget.videoGroupBox.setChecked(True) + self.assertTrue(self.config_tool.config.enable_video_recording) + self.assertIn(self.config_tool.config.videomixer, ['Video Passthrough', 'Picture-In-Picture']) + + # Test combo box + video_mixer_combo_box = self.config_tool.avWidget.videoMixerComboBox + self.check_combobox_corner_cases_frontend(video_mixer_combo_box) + QtTest.QTest.keyClick(video_mixer_combo_box, QtCore.Qt.Key_PageUp) # Simulate user to test backend/frontend + for i in range(video_mixer_combo_box.count() - 2): + last_plugin = self.config_tool.videomixer + QtTest.QTest.keyClick(video_mixer_combo_box, QtCore.Qt.Key_Down) + current_plugin = self.config_tool.videomixer + self.assertNotEqual(last_plugin, current_plugin) + + # Test frontend text values + self.assertTrue(video_mixer_combo_box.findText('Video Passthrough') != -1) + self.assertTrue(video_mixer_combo_box.findText('Picture-In-Picture') != -1) + + def test_recording_settings_audio_input(self): + """ + Simulates a user interacting with the config tool record to audio input settings. + """ + self.select_recording_tab() + # Test disabled checkbox + self.config_tool.currentWidget.audioGroupBox.setChecked(False) + self.assertFalse(self.config_tool.config.enable_audio_recording) + + # Test enabled checkbox + self.config_tool.currentWidget.audioGroupBox.setChecked(True) + self.assertTrue(self.config_tool.config.enable_audio_recording) + self.assertIn(self.config_tool.config.audiomixer, ['Audio Passthrough', 'Multiple Audio Inputs']) + + # Test combo box + audio_mixer_combo_box = self.config_tool.avWidget.audioMixerComboBox + self.check_combobox_corner_cases_frontend(audio_mixer_combo_box) + QtTest.QTest.keyClick(audio_mixer_combo_box, QtCore.Qt.Key_PageUp) # Simulate user to test backend/frontend + for i in range(audio_mixer_combo_box.count() - 2): + last_plugin = self.config_tool.audiomixer + QtTest.QTest.keyClick(audio_mixer_combo_box, QtCore.Qt.Key_Down) + current_plugin = self.config_tool.audiomixer + self.assertNotEqual(last_plugin, current_plugin) + + # Test frontend text values + self.assertNotEqual(audio_mixer_combo_box.findText('Audio Passthrough'), -1) + self.assertNotEqual(audio_mixer_combo_box.findText('Multiple Audio Inputs'), -1) + + def test_plugin_settings(self): + """ + Simulate a user going through the list of plugins in the plugin settings page of the config tool. + + This test builds a dictionary value based on a traversal of the QTreeWidget that contains the plugins displayed + in the GUI. The dictionary is then used to assert that plugins exist in the appropriate categories. This test + also uses a map to relate the GUI's category names to the backend's category names since the two differ. + """ + item = self.config_tool.mainWidget.optionsTreeWidget.findItems(self.config_tool.pluginsString, + QtCore.Qt.MatchExactly) + self.assertFalse(not item or item[0] is None) + self.config_tool.mainWidget.optionsTreeWidget.setCurrentItem(item[0]) + QtTest.QTest.mouseClick(self.config_tool.mainWidget.optionsTreeWidget, Qt.Qt.LeftButton) + self.assertEqual(self.config_tool.currentWidget, self.config_tool.pluginWidget) + + # GUI names categories are different than the backend. Define a translation from GUI -> backend + gui_category_to_backend_category = { + 'Audio Input': 'AudioInput', + 'Audio Mixer': 'AudioMixer', + 'Video Input': 'VideoInput', + 'Video Mixer': 'VideoMixer', + 'Output': 'Output', + 'Input': 'Importer' + } + QtTest.QTest.keyClick(self.config_tool.pluginWidget.list, QtCore.Qt.Key_PageUp) + root = self.config_tool.pluginWidget.list.invisibleRootItem() + plugin_category = {} # A dictionary of plugin -> category + for category_index in range(root.childCount()): + category = str(self.config_tool.pluginWidget.list.currentItem().text(0)) + QtTest.QTest.keyClick(self.config_tool.pluginWidget.list, QtCore.Qt.Key_Down) + for plugin_index in range(root.child(category_index).childCount()): + plugin_name = str(self.config_tool.pluginWidget.list.currentItem().text(0)) + plugin_category[plugin_name] = gui_category_to_backend_category[category] + QtTest.QTest.keyClick(self.config_tool.pluginWidget.list, QtCore.Qt.Key_Down) + + # Assert expected categories exist. + self.assertIn('AudioInput', plugin_category.values()) + self.assertIn('AudioMixer', plugin_category.values()) + self.assertIn('VideoInput', plugin_category.values()) + self.assertIn('VideoMixer', plugin_category.values()) + self.assertIn('Output', plugin_category.values()) + self.assertIn('Importer', plugin_category.values()) + + for category in ['AudioInput', 'AudioMixer', 'VideoInput', 'VideoMixer', 'Output', 'Importer']: + for plugin in self.config_tool.get_plugins(category): + self.assertIn(plugin.plugin_object.name, plugin_category) + self.assertEqual(plugin_category[plugin.plugin_object.name], category) + self.assertEqual(category, plugin.plugin_object.CATEGORY) + + def test_logger_settings(self): + """ + Tests for config tool's Logger tab + + Needs to be tested differently since the + Config instance isn't affected by changes in this tab. + """ + + # TODO + pass + + def test_close_configtool(self): + """ + Tests for config tool's close button + """ + + self.assertTrue(self.config_tool.mainWidget.isVisible()) + QtTest.QTest.mouseClick(self.config_tool.mainWidget.closePushButton, Qt.Qt.LeftButton) + self.assertFalse(self.config_tool.mainWidget.isVisible()) + + def test_file_menu_quit(self): + """ + Tests for config tool's File->Quit + """ + + self.assertTrue(self.config_tool.isVisible()) + + # File->Quit + self.config_tool.actionExit.trigger() + self.assertFalse(self.config_tool.isVisible()) + + def test_help_menu_about(self): + """ + Tests for config tool's Help->About + """ + + self.assertTrue(self.config_tool.isVisible()) + + # Help->About + self.config_tool.actionAbout.trigger() + self.assertFalse(self.config_tool.hasFocus()) + self.assertTrue(self.config_tool.aboutDialog.isVisible()) + + # Click "Close" + QtTest.QTest.mouseClick(self.config_tool.aboutDialog.closeButton, Qt.Qt.LeftButton) + self.assertFalse(self.config_tool.aboutDialog.isVisible()) diff --git a/src/freeseer/tests/frontend/controller/test_server.py b/src/freeseer/tests/frontend/controller/test_server.py index ff4102f8..cc790133 100644 --- a/src/freeseer/tests/frontend/controller/test_server.py +++ b/src/freeseer/tests/frontend/controller/test_server.py @@ -274,7 +274,7 @@ def test_post(self, test_client, recording): assert len(recording.media_dict) == 0 response = test_client.post('/recordings', data={'filename': 'test'}) assert response.status_code == 201 - assert recording.media_dict.keys() == [1] + assert list(recording.media_dict.keys()) == [1] assert recording.media_info['1']['filename'] == 'test.ogg' def test_delete_no_recording_id(self, test_client): @@ -311,7 +311,7 @@ def test_delete_in_progress_recording(self, test_client, recording, mock_media_d response = test_client.delete('/recordings/1') assert response.status_code == 204 assert del_media.num_times_stop_called == 1 - assert recording.media_dict.keys() == [2] + assert list(recording.media_dict.keys()) == [2] def test_delete_recording_id_and_file(self, test_client, recording, mock_media_dict): ''' @@ -333,7 +333,7 @@ def test_delete_recording_id_and_file(self, test_client, recording, mock_media_d response = test_client.delete('/recordings/1') assert response.status_code == 204 - assert recording.media_dict.keys() == [2] + assert list(recording.media_dict.keys()) == [2] # assert the file no longer exists assert not os.path.isfile(file_to_delete_path) diff --git a/src/freeseer/tests/frontend/controller/test_server.py.bak b/src/freeseer/tests/frontend/controller/test_server.py.bak new file mode 100644 index 00000000..ff4102f8 --- /dev/null +++ b/src/freeseer/tests/frontend/controller/test_server.py.bak @@ -0,0 +1,339 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2014 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +import json +import os + +import pytest + +from freeseer import settings +from freeseer.framework.config.profile import ProfileManager +from freeseer.framework.multimedia import Multimedia +from freeseer.framework.plugin import PluginManager +from freeseer.frontend.controller import server + + +class MockMedia: + def __init__(self): + self.current_state = Multimedia.NULL + self.num_times_record_called = 0 + self.num_times_stop_called = 0 + self.num_times_pause_called = 0 + + def record(self): + self.num_times_record_called += 1 + + def pause(self): + self.num_times_pause_called += 1 + + def stop(self): + self.num_times_stop_called += 1 + + +class TestServerApp: + ''' + Test cases for Server. + ''' + + @pytest.fixture(scope='module') + def test_client(self): + server.app.config['TESTING'] = True + server.app.storage_file_path = "test_storage_file" + return server.app.test_client() + + @pytest.fixture(scope='function', autouse=True) + def recording(self, request, test_client, monkeypatch, tmpdir): + + recording = server.app.blueprints['recording'] + + monkeypatch.setattr(settings, 'configdir', str(tmpdir.mkdir('configdir'))) + test_client.get('/recordings') + + profile_manager = ProfileManager(str(tmpdir.mkdir('profile'))) + recording.profile = profile_manager.get('testing') + recording.config = recording.profile.get_config('freeseer.conf', settings.FreeseerConfig, ['Global'], read_only=True) + recording.config.videodir = str(tmpdir.mkdir('Videos')) + recording.plugin_manager = PluginManager(recording.profile) + + return recording + + @pytest.fixture(scope='function') + def mock_media_dict(self, request, recording): + mock_media_1 = MockMedia() + mock_media_2 = MockMedia() + + filepath1 = os.path.join(recording.config.videodir, 'mock_media_1') + filepath2 = os.path.join(recording.config.videodir, 'mock_media_2') + + recording.media_info['1'] = { + 'status': Multimedia.NULL, + 'filename': 'mock_media_1', + 'filepath': filepath1, + } + recording.media_info['2'] = { + 'status': Multimedia.NULL, + 'filename': 'mock_media_2', + 'filepath': filepath2, + } + + recording.media_dict = { + 1: mock_media_1, + 2: mock_media_2, + } + + def clear_media(): + recording.media_info.clear() + recording.media_dict = {} + request.addfinalizer(clear_media) + + return recording.media_dict + + def test_get_all_recordings_empty_media_dict(self, test_client): + ''' + Tests GET request retrieving all recordings with from an empty media dict + ''' + response = test_client.get('/recordings') + response_data = json.loads(response.data) + assert response_data == {'recordings': []} + + def test_get_nonexistent_recording_id(self, test_client): + ''' + Tests GET request retrieving non-existent recording + ''' + response = test_client.get('/recordings/1') + response_data = json.loads(response.data) + assert response_data['error_code'] == 404 + assert response.status_code == 404 + + def test_get_all_recordings(self, test_client, mock_media_dict): + ''' + Tests GET request all recordings with 2 entries in media dict + ''' + response = test_client.get('/recordings') + response_data = json.loads(response.data) + assert response_data == {'recordings': [1, 2]} + + def test_get_recording_id(self, test_client, mock_media_dict): + ''' + Tests GET request specific valid recording + ''' + response = test_client.get('/recordings/1') + response_data = json.loads(response.data) + assert response.status_code == 200 + + assert response_data == { + 'filename': 'mock_media_1', + 'filesize': 'NA', + 'id': 1, + 'status': 'NULL', + } + + def test_get_invalid_recording_id(self, test_client, mock_media_dict): + ''' + Tests GET request with an invalid id (a non integer id) + ''' + response = test_client.get('/recordings/abc') + assert response.status_code == 404 + + response = test_client.get('/recordings/1.0') + assert response.status_code == 404 + + def test_patch_no_id(self, test_client): + ''' + Tests a PATCH request without a recording id + ''' + response = test_client.patch('/recordings') + assert response.status_code == 405 + + def test_patch_nonexistant_id(self, test_client, mock_media_dict): + ''' + Tests a PATCH request with non-existant recording id + ''' + response = test_client.patch('/recordings/100', data={'command': 'start'}) + assert response.status_code == 404 + + def test_patch_no_command(self, test_client, mock_media_dict): + ''' + Tests a PATCH request without a command + ''' + response = test_client.patch('/recordings/1') + assert response.status_code == 400 + + def test_patch_invalid_command(self, test_client, mock_media_dict): + ''' + Tests a Patch request with an invalid command + ''' + response = test_client.patch('/recordings/1', data={'command': 'invalid command'}) + assert response.status_code == 400 + + @pytest.mark.parametrize("current_state", [Multimedia.NULL, Multimedia.PAUSE]) + def test_patch_start(self, test_client, recording, mock_media_dict, current_state): + ''' + Tests a Patch request to start a recording + ''' + mock_media_dict[1].current_state = current_state + response = test_client.patch('/recordings/1', data={'command': 'start'}) + assert response.status_code == 200 + assert mock_media_dict[1].num_times_record_called == 1 + + @pytest.mark.parametrize("current_state", [Multimedia.STOP, Multimedia.RECORD]) + def test_patch_start_invalid(self, test_client, mock_media_dict, current_state): + ''' + Tests a Patch request to start a recording with an invalid media state + ''' + mock_media_dict[1].current_state = current_state + response = test_client.patch('/recordings/1', data={'command': 'start'}) + assert response.status_code == 400 + assert mock_media_dict[1].num_times_record_called == 0 + + def test_patch_pause(self, test_client, mock_media_dict): + ''' + Tests a Patch request to pause a recording + ''' + mock_media_dict[1].current_state = Multimedia.RECORD + response = test_client.patch('/recordings/1', data={'command': 'pause'}) + assert response.status_code == 200 + assert mock_media_dict[1].num_times_pause_called == 1 + + @pytest.mark.parametrize("current_state", [Multimedia.NULL, Multimedia.PAUSE, Multimedia.STOP]) + def test_patch_pause_invalid(self, test_client, mock_media_dict, current_state): + ''' + Tests a Patch request to pause a recording with an invalid media state + ''' + mock_media_dict[1].current_state = current_state + response = test_client.patch('/recordings/1', data={'command': 'pause'}) + assert response.status_code == 400 + assert mock_media_dict[1].num_times_pause_called == 0 + + @pytest.mark.parametrize("current_state", [Multimedia.PAUSE, Multimedia.RECORD]) + def test_patch_stop(self, test_client, mock_media_dict, current_state): + ''' + Tests a Patch request to stop a recording + ''' + mock_media_dict[1].current_state = current_state + response = test_client.patch('/recordings/1', data={'command': 'stop'}) + assert response.status_code == 200 + assert mock_media_dict[1].num_times_stop_called == 1 + + @pytest.mark.parametrize("current_state", [Multimedia.STOP, Multimedia.NULL]) + def test_patch_stop_invalid(self, test_client, mock_media_dict, current_state): + ''' + Tests a Patch request to stop a recording with an invalid media state + ''' + mock_media_dict[1].current_state = current_state + response = test_client.patch('/recordings/1', data={'command': 'stop'}) + assert response.status_code == 400 + assert mock_media_dict[1].num_times_stop_called == 0 + + def test_post_no_filename(self, test_client): + ''' + Tests a POST request without a filename + ''' + response = test_client.post('/recordings') + assert response.status_code == 400 + + def test_post_empty_filename(self, test_client): + ''' + Tests a POST request with an empty filename + ''' + response = test_client.post('/recordings', data={'filename': ''}) + assert response.status_code == 400 + + def test_post_invalid_filename(self, test_client): + ''' + Tests a POST request with an invalid filename + ''' + response = test_client.post('/recordings', data={'filename': 'abc/123'}) + assert response.status_code == 400 + + def test_post(self, test_client, recording): + ''' + Tests a regular POST request + ''' + assert len(recording.media_dict) == 0 + response = test_client.post('/recordings', data={'filename': 'test'}) + assert response.status_code == 201 + assert recording.media_dict.keys() == [1] + assert recording.media_info['1']['filename'] == 'test.ogg' + + def test_delete_no_recording_id(self, test_client): + ''' + Tests a DELETE request without a provided recording id + ''' + response = test_client.delete('/recordings') + assert response.status_code == 405 + + def test_delete_nonexistent_recording_id(self, test_client): + ''' + Tests a DELETE request with a recording id that doesn't exist + ''' + response = test_client.delete('/recordings/100') + assert response.status_code == 404 + + @pytest.mark.parametrize("invalid_id", ['abc', '1.0']) + def test_delete_invalid_recording_id(self, test_client, invalid_id): + ''' + Tests a DELETE request with an invalid recording id + ''' + response = test_client.delete('/recordings/{0}'.format(invalid_id)) + assert response.status_code == 404 + + @pytest.mark.parametrize("current_state", [Multimedia.RECORD, Multimedia.PAUSE]) + def test_delete_in_progress_recording(self, test_client, recording, mock_media_dict, current_state): + ''' + Tests a DELETE request for a recording that is paused or in progress + ''' + + # delete a recording that is in the middle of recording + mock_media_dict[1].current_state = current_state + del_media = mock_media_dict[1] + response = test_client.delete('/recordings/1') + assert response.status_code == 204 + assert del_media.num_times_stop_called == 1 + assert recording.media_dict.keys() == [2] + + def test_delete_recording_id_and_file(self, test_client, recording, mock_media_dict): + ''' + Tests a DELETE request where the recording has a specified file + ''' + + # setup - create the file to be deleted + file_base_name = 'testDeleteFile' + file_to_delete = file_base_name + file_to_delete_path = os.path.join(recording.config.videodir, file_to_delete) + test_file = open(file_to_delete_path, 'a') + test_file.close() + + # assert the file exists + assert os.path.isfile(file_to_delete_path) + + # set mock_media filepath to filepath of file to be deleted + recording.media_info['1']['filepath'] = file_to_delete_path + + response = test_client.delete('/recordings/1') + assert response.status_code == 204 + assert recording.media_dict.keys() == [2] + + # assert the file no longer exists + assert not os.path.isfile(file_to_delete_path) diff --git a/src/freeseer/tests/frontend/test_cli_talk.py b/src/freeseer/tests/frontend/test_cli_talk.py index eaa9142a..6cb03fec 100644 --- a/src/freeseer/tests/frontend/test_cli_talk.py +++ b/src/freeseer/tests/frontend/test_cli_talk.py @@ -63,16 +63,16 @@ def test_add_talk(self, mock_profile): sys.argv[1:] = args cli.parse_args(self.parser, args) talks = self.db.get_talks() - talks.next() # Point to talk data - talks.next() # Skip default blank entry - talks.next() # Skip first test entry - talks.next() # Skip second test entry + next(talks) # Point to talk data + next(talks) # Skip default blank entry + next(talks) # Skip first test entry + next(talks) # Skip second test entry record = talks.record() - self.assertEqual(talks.value(record.indexOf('title')).toString(), u'test title') - self.assertEqual(talks.value(record.indexOf('speaker')).toString(), u'john doe') - self.assertEqual(talks.value(record.indexOf('event')).toString(), u'testing') - self.assertEqual(talks.value(record.indexOf('room')).toString(), u'rm123') + self.assertEqual(talks.value(record.indexOf('title')).toString(), 'test title') + self.assertEqual(talks.value(record.indexOf('speaker')).toString(), 'john doe') + self.assertEqual(talks.value(record.indexOf('event')).toString(), 'testing') + self.assertEqual(talks.value(record.indexOf('room')).toString(), 'rm123') def test_remove_talk(self): """Test removing a talk""" diff --git a/src/freeseer/tests/frontend/test_cli_talk.py.bak b/src/freeseer/tests/frontend/test_cli_talk.py.bak new file mode 100644 index 00000000..eaa9142a --- /dev/null +++ b/src/freeseer/tests/frontend/test_cli_talk.py.bak @@ -0,0 +1,91 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2014 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +import shlex +import shutil +import sys +import tempfile +import unittest + +from mock import patch +from PyQt4 import QtSql + +from freeseer.framework.config.profile import Profile +from freeseer.framework.presentation import Presentation +from freeseer.frontend import cli + + +class TestCli(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.presentations = [] + cls.presentations.append(Presentation("test title 1", "test speaker", "test room", "test event",)) + cls.presentations.append(Presentation("test title 2", "test speaker", "test room", "test event",)) + + def setUp(self): + self.parser = cli.setup_parser() + self.profile_path = tempfile.mkdtemp() + profile = Profile(self.profile_path, 'testing') + self.db = profile.get_database() + for talk in self.presentations: + self.db.insert_presentation(talk) + + def tearDown(self): + shutil.rmtree(self.profile_path) + + @patch.object(Profile, 'get_database') + def test_add_talk(self, mock_profile): + """Test adding a talk""" + mock_profile.return_value = self.db + args = shlex.split('talk add -t "test title" -s "john doe" -e testing -r rm123') + sys.argv[1:] = args + cli.parse_args(self.parser, args) + talks = self.db.get_talks() + talks.next() # Point to talk data + talks.next() # Skip default blank entry + talks.next() # Skip first test entry + talks.next() # Skip second test entry + record = talks.record() + + self.assertEqual(talks.value(record.indexOf('title')).toString(), u'test title') + self.assertEqual(talks.value(record.indexOf('speaker')).toString(), u'john doe') + self.assertEqual(talks.value(record.indexOf('event')).toString(), u'testing') + self.assertEqual(talks.value(record.indexOf('room')).toString(), u'rm123') + + def test_remove_talk(self): + """Test removing a talk""" + args = shlex.split("talk remove -i 1") + sys.argv[1:] = args + cli.parse_args(self.parser, args) + talks = QtSql.QSqlQuery('SELECT COUNT(*) FROM presentations WHERE title="test title 1" AND speaker="test speaker"') + self.assertEqual(talks.value(0).toInt()[0], 0) + + def test_clear_talks(self): + """Test clearing all talks""" + args = shlex.split("talk clear") + sys.argv[1:] = args + cli.parse_args(self.parser, args) + count = QtSql.QSqlQuery('SELECT COUNT(*) FROM presentations') + self.assertEqual(count.value(0).toInt()[0], 0) diff --git a/src/freeseer/tests/frontend/upload/test_youtube.py b/src/freeseer/tests/frontend/upload/test_youtube.py index f8b9a3d4..1185186a 100644 --- a/src/freeseer/tests/frontend/upload/test_youtube.py +++ b/src/freeseer/tests/frontend/upload/test_youtube.py @@ -26,7 +26,7 @@ import sys import unittest import pytest -from StringIO import StringIO +from io import StringIO from freeseer.framework.youtube import Response from freeseer.frontend.upload import youtube diff --git a/src/freeseer/tests/frontend/upload/test_youtube.py.bak b/src/freeseer/tests/frontend/upload/test_youtube.py.bak new file mode 100644 index 00000000..f8b9a3d4 --- /dev/null +++ b/src/freeseer/tests/frontend/upload/test_youtube.py.bak @@ -0,0 +1,111 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2014 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ +import os +import mock +import sys +import unittest +import pytest +from StringIO import StringIO + +from freeseer.framework.youtube import Response +from freeseer.frontend.upload import youtube +from freeseer.tests.framework.test_youtube import TestYoutubeService + + +class TestYoutubeFrontend(unittest.TestCase): + TEST_RESPONSE = { + "id": "test", + "status": "test", + "content": "test", + } + + def setUp(self): + """Stardard init method: runs before each test_* method""" + self.saved_stdout = sys.stdout + self.out = StringIO() + + def test_get_defaults(self): + """Test get_defaults, should always return a dict""" + home = os.getenv('HOME') + expected = { + 'client_secrets': '{}/.freeseer/client_secrets.json'.format(home), + 'video_directory': '{}/Videos'.format(home), + 'oauth2_token': '{}/.freeseer/oauth2_token.json'.format(home) + } + actual = youtube.get_defaults() + self.assertDictEqual(actual, expected) + + def test_prompt_user_not_yes(self): + """Test output prompting user if they want to upload (assume yes was not specified)""" + test_videos = {"v1", "v2", "v3"} + expected = "Found videos:\n" + joined = "\n".join(test_videos) + expected += joined + try: + sys.stdout = self.out + with mock.patch('__builtin__.raw_input', return_value='no'): + youtube.prompt_user(test_videos, confirmation=False) + output = self.out.getvalue().strip() + self.assertEqual(expected, output) + finally: + sys.stdout = self.saved_stdout + + def test_prompt_user_yes(self): + """Test assume yes (assume yes was not specified)""" + self.assertTrue(youtube.prompt_user({"v1", "v2", "v3"}, confirmation=True)) + + +@pytest.mark.parametrize("expected, path", [ + ("/path/that/does/not/exist does not exist, please specify a valid token file", "/path/that/does/not/exist"), + ("Nothing to upload", TestYoutubeService.SAMPLE_VIDEO), +]) +def test_upload(capsys, expected, path): + """ + Test the responses from the upload test cases. + """ + youtube.upload([], path, False) + out, err = capsys.readouterr() + assert expected == out.strip() + + +@pytest.mark.parametrize("expected, response", [ + ("The file was successfully uploaded with video id: {}".format(TestYoutubeFrontend.TEST_RESPONSE['id']), + Response.SUCCESS), + ("The file failed to upload with unexpected response: {}".format(TestYoutubeFrontend.TEST_RESPONSE), + Response.UNEXPECTED_FAILURE), + ("An unretriable HTTP error {} occurred:\n{}".format(TestYoutubeFrontend.TEST_RESPONSE['status'], + TestYoutubeFrontend.TEST_RESPONSE['content']), + Response.UNRETRIABLE_ERROR), + ("The maximum number of retries has been reached", + Response.MAX_RETRIES_REACHED), + ("The access token has expired or been revoked, please run python -m freeseer config youtube", + Response.ACCESS_TOKEN_ERROR), +]) +def test_handle_response(capsys, expected, response): + """ + Test the actual response vs the expected response from response_test_cases. + """ + youtube.handle_response(response, TestYoutubeFrontend.TEST_RESPONSE) + out, err = capsys.readouterr() + assert expected == out.strip() From fb55893ce39987cfe06c5e0ac198d478527b9eaa Mon Sep 17 00:00:00 2001 From: Sunny Dhoke Date: Thu, 10 Oct 2019 12:58:22 +0530 Subject: [PATCH 2/2] Removing .bak backup files. --- docs/source/conf.py.bak | 221 ----- src/freeseer/framework/config/core.py.bak | 252 ------ .../config/persist/configparser.py.bak | 58 -- .../config/persist/jsonstorage.py.bak | 69 -- src/freeseer/framework/config/profile.py.bak | 206 ----- src/freeseer/framework/database.py.bak | 605 ------------- src/freeseer/framework/qt_key_grabber.py.bak | 95 --- src/freeseer/framework/youtube.py.bak | 220 ----- .../frontend/configtool/AVWidget.py.bak | 224 ----- .../frontend/configtool/GeneralWidget.py.bak | 127 --- .../frontend/configtool/PluginWidget.py.bak | 151 ---- .../frontend/configtool/configtool.py.bak | 760 ----------------- .../frontend/controller/recording.py.bak | 244 ------ .../frontend/qtcommon/AboutDialog.py.bak | 79 -- .../frontend/qtcommon/AboutWidget.py.bak | 176 ---- .../record/RecordingController.py.bak | 110 --- .../frontend/reporteditor/reporteditor.py.bak | 253 ------ .../frontend/talkeditor/talkeditor.py.bak | 626 -------------- src/freeseer/frontend/upload/youtube.py.bak | 126 --- .../audioinput/jackaudiosrc/__init__.py.bak | 120 --- .../audioinput/pulsesrc/__init__.py.bak | 134 --- .../audiopassthrough/__init__.py.bak | 138 --- .../audiomixer/multiaudio/__init__.py.bak | 176 ---- .../plugins/importer/csv_importer.py.bak | 77 -- .../importer/rss_feedparser/__init__.py.bak | 121 --- .../output/audiofeedback/__init__.py.bak | 104 --- .../output/ogg_icecast/__init__.py.bak | 252 ------ .../plugins/output/ogg_output/__init__.py.bak | 247 ------ .../output/rtmp_streaming/__init__.py.bak | 805 ------------------ .../output/videopreview/__init__.py.bak | 125 --- .../output/webm_output/__init__.py.bak | 140 --- .../videoinput/desktop/__init__.py.bak | 229 ----- .../videoinput/firewiresrc/__init__.py.bak | 151 ---- .../plugins/videoinput/usbsrc/__init__.py.bak | 172 ---- .../videoinput/videotestsrc/__init__.py.bak | 116 --- .../plugins/videomixer/pip/__init__.py.bak | 257 ------ .../videopassthrough/__init__.py.bak | 226 ----- .../config/options/test_choice.py.bak | 95 --- .../config/options/test_float.py.bak | 72 -- .../config/options/test_folder.py.bak | 105 --- .../config/options/test_integer.py.bak | 72 -- .../tests/framework/test_multimedia.py.bak | 77 -- .../tests/framework/test_presentation.py.bak | 60 -- .../tests/framework/test_youtube.py.bak | 70 -- .../configtool/test_config_tool.py.bak | 381 --------- .../frontend/controller/test_server.py.bak | 339 -------- .../tests/frontend/test_cli_talk.py.bak | 91 -- .../tests/frontend/upload/test_youtube.py.bak | 111 --- 48 files changed, 9665 deletions(-) delete mode 100644 docs/source/conf.py.bak delete mode 100644 src/freeseer/framework/config/core.py.bak delete mode 100644 src/freeseer/framework/config/persist/configparser.py.bak delete mode 100644 src/freeseer/framework/config/persist/jsonstorage.py.bak delete mode 100644 src/freeseer/framework/config/profile.py.bak delete mode 100644 src/freeseer/framework/database.py.bak delete mode 100644 src/freeseer/framework/qt_key_grabber.py.bak delete mode 100644 src/freeseer/framework/youtube.py.bak delete mode 100644 src/freeseer/frontend/configtool/AVWidget.py.bak delete mode 100644 src/freeseer/frontend/configtool/GeneralWidget.py.bak delete mode 100644 src/freeseer/frontend/configtool/PluginWidget.py.bak delete mode 100644 src/freeseer/frontend/configtool/configtool.py.bak delete mode 100644 src/freeseer/frontend/controller/recording.py.bak delete mode 100644 src/freeseer/frontend/qtcommon/AboutDialog.py.bak delete mode 100644 src/freeseer/frontend/qtcommon/AboutWidget.py.bak delete mode 100644 src/freeseer/frontend/record/RecordingController.py.bak delete mode 100644 src/freeseer/frontend/reporteditor/reporteditor.py.bak delete mode 100644 src/freeseer/frontend/talkeditor/talkeditor.py.bak delete mode 100644 src/freeseer/frontend/upload/youtube.py.bak delete mode 100644 src/freeseer/plugins/audioinput/jackaudiosrc/__init__.py.bak delete mode 100644 src/freeseer/plugins/audioinput/pulsesrc/__init__.py.bak delete mode 100644 src/freeseer/plugins/audiomixer/audiopassthrough/__init__.py.bak delete mode 100644 src/freeseer/plugins/audiomixer/multiaudio/__init__.py.bak delete mode 100644 src/freeseer/plugins/importer/csv_importer.py.bak delete mode 100644 src/freeseer/plugins/importer/rss_feedparser/__init__.py.bak delete mode 100644 src/freeseer/plugins/output/audiofeedback/__init__.py.bak delete mode 100644 src/freeseer/plugins/output/ogg_icecast/__init__.py.bak delete mode 100644 src/freeseer/plugins/output/ogg_output/__init__.py.bak delete mode 100644 src/freeseer/plugins/output/rtmp_streaming/__init__.py.bak delete mode 100644 src/freeseer/plugins/output/videopreview/__init__.py.bak delete mode 100644 src/freeseer/plugins/output/webm_output/__init__.py.bak delete mode 100644 src/freeseer/plugins/videoinput/desktop/__init__.py.bak delete mode 100644 src/freeseer/plugins/videoinput/firewiresrc/__init__.py.bak delete mode 100644 src/freeseer/plugins/videoinput/usbsrc/__init__.py.bak delete mode 100644 src/freeseer/plugins/videoinput/videotestsrc/__init__.py.bak delete mode 100644 src/freeseer/plugins/videomixer/pip/__init__.py.bak delete mode 100644 src/freeseer/plugins/videomixer/videopassthrough/__init__.py.bak delete mode 100644 src/freeseer/tests/framework/config/options/test_choice.py.bak delete mode 100644 src/freeseer/tests/framework/config/options/test_float.py.bak delete mode 100644 src/freeseer/tests/framework/config/options/test_folder.py.bak delete mode 100644 src/freeseer/tests/framework/config/options/test_integer.py.bak delete mode 100644 src/freeseer/tests/framework/test_multimedia.py.bak delete mode 100644 src/freeseer/tests/framework/test_presentation.py.bak delete mode 100644 src/freeseer/tests/framework/test_youtube.py.bak delete mode 100644 src/freeseer/tests/frontend/test_cli_talk.py.bak delete mode 100644 src/freeseer/tests/frontend/upload/test_youtube.py.bak diff --git a/docs/source/conf.py.bak b/docs/source/conf.py.bak deleted file mode 100644 index 1b01c570..00000000 --- a/docs/source/conf.py.bak +++ /dev/null @@ -1,221 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Freeseer documentation build configuration file, created by -# sphinx-quickstart on Sun Sep 4 18:21:52 2011. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import os -import sys - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. -sys.path.append('../src') # Temporarily add freeseer/src to $PATH. - -# -- General configuration ---------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', - 'sphinx.ext.viewcode', 'sphinx.ext.todo'] - -# If True, the todo and todolist directives will produce output. -# http://sphinx.pocoo.org/ext/todo.html#module-sphinx.ext.todo -todo_include_todos = True - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Freeseer' -copyright = u'© 2011-2014 Free and Open Source Software Learning Centre' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '3.0' -# The full version, including alpha/beta/rc tags. -release = '3.0.0' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['contribute/packaging.rst'] - -# The reST default role (for text `like this`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -show_authors = True - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output -------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'sphinx_rtd_theme' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -html_theme_path = ['_themes', ] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = 'freeseer_logo.png' - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -html_favicon = 'favicon.ico' - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -html_show_copyright = False - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'Freeseerdoc' - - -# -- Options for LaTeX output ------------------------------------------------- - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. Takes a list of tuples. -latex_documents = [( - 'index', # source start file - 'Freeseer.tex', # target name - u'Freeseer Documentation', # title - u'Free and Open Source Software Learning Centre', # author - 'manual' # documentclass [howto/manual] -)] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output ------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'freeseer', u'Freeseer Documentation', - [u'Free and Open Source Software Learning Centre'], 1) -] diff --git a/src/freeseer/framework/config/core.py.bak b/src/freeseer/framework/config/core.py.bak deleted file mode 100644 index 8ccdafff..00000000 --- a/src/freeseer/framework/config/core.py.bak +++ /dev/null @@ -1,252 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013, 2014 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -import abc -import collections -import functools - -from freeseer.framework.config.exceptions import InvalidOptionValueError -from freeseer.framework.config.exceptions import OptionValueNotSetError -from freeseer.framework.config.exceptions import StorageNotSetError - - -class Option(object): - """Represents a Config option.""" - - __metaclass__ = abc.ABCMeta - - class NotSpecified(object): - pass - - def __init__(self, default=NotSpecified): - self.default = default - - def is_required(self): - """Returns true iff this option is required.""" - return self.default == self.NotSpecified - - # Override these if you know what you're doing - - def pre_set(self, value): - """Do something before value is stored for this option.""" - return value - - def presentation(self, value): - """Returns a modified version of value that will not itself be persisted.""" - return value - - def schema(self): - """Returns the json schema for an Option.""" - schema = {'type': self.SCHEMA_TYPE} - if self.default != self.NotSpecified: - schema['default'] = self.default - return schema - - # Override these! - - @abc.abstractmethod - def is_valid(self, value): - """Checks if a value is valid for this option.""" - pass - - @abc.abstractmethod - def encode(self, value): - """Encodes value into a string. - - Should raise something if unable to encode. - """ - pass - - @abc.abstractmethod - def decode(self, value): - """Decodes value into a proper Option value. - - Should raise something if unable to decode. - """ - pass - - -class ConfigBase(abc.ABCMeta): - """Metaclass for Config subclasses. - - It does some transformations on the options you specify to let them be used as properties. - """ - - def __new__(meta, name, bases, class_attributes): - """Finds all Options delcared in the subclass and transform them into properties.""" - class_attributes, options = meta.find_options(class_attributes) - class_attributes['options'] = options - cls = super(ConfigBase, meta).__new__(meta, name, bases, class_attributes) - for opt_name, option in options.iteritems(): - opt_get = functools.partial(cls.get_value, name=opt_name, option=option, presentation=True) - opt_set = functools.partial(cls._set_value, name=opt_name, option=option) - setattr(cls, opt_name, property(opt_get, opt_set)) - return cls - - @staticmethod - def find_options(class_attributes): - """Find all Option subclasses within the class body.""" - new_attributes = {} - options = collections.OrderedDict() - for name in sorted(class_attributes.keys()): - attr = class_attributes[name] - if name.startswith('_') or not isinstance(attr, Option): - new_attributes[name] = attr - else: - options[name] = attr - return new_attributes, options - - -class Config(object): - """Base class for all custom configs. - - To be useful, its body must contain some number of Option instances. - - Example: - class MyConfig(Config): - test = StringOption('default_value') - """ - - __metaclass__ = ConfigBase - - def __init__(self, storage=None, storage_args=None): - """ - Params: - storage - an instance of a ConfigStorage - storage_args - an iterable of arguments that will be passed to - storage.load(...) - """ - self._storage = storage - self._storage_args = storage_args if storage_args else [] - - self.values = {} - self.set_defaults() - - def _set_value(self, value, name, option): - """This is just here to make the argument order more logical.""" - self.set_value(name, option, value) - - def set_defaults(self): - """Sets the values of all options to their default value (if applicable).""" - for name, option in self.options.iteritems(): - if not option.is_required(): - self.set_value(name, option, option.default) - - # You probably will not need to override these: - - def get_value(self, name, option, presentation=False): - """Gets the value of an option. - - Params: - name - the string name of the option instance - option - the option instance itself - presentation - boolean - - Returns: the value that the config has stored for this option - """ - if name in self.values: - value = self.values[name] - if presentation: - return option.presentation(value) - else: - return value - else: - raise OptionValueNotSetError(name, option) - - def set_value(self, name, option, value): - """Sets the value of an option. - - Params: - name - the string name of the option instance - option - the option instance itself - value - the value that will be set for the option - - Returns: nothing - """ - if option.is_valid(value): - mod_value = option.pre_set(value) - self.values[name] = mod_value - else: - raise InvalidOptionValueError(name, option) - - def save(self): - """Persist the Config instance. - - This only works if the storage stuff is passed to the Config's constructor. - """ - if self._storage: - self._storage.store(self, *self._storage_args) - else: - raise StorageNotSetError() - - @classmethod - def schema(cls): - """Returns the json schema for this Config instance.""" - required = [] - - schema = { - 'type': 'object', - 'properties': {}, - } - - for name, instance in cls.options.iteritems(): - schema['properties'][name] = instance.schema() - if instance.is_required(): - required.append(name) - - if required: - schema['required'] = required - - return schema - - -class ConfigStorage(object): - """Defines an interface for loading and storing Config instances.""" - - __metaclass__ = abc.ABCMeta - - def __init__(self, filepath): - """ - Params: - filepath - the path to file where the config will be persisted or loaded from - """ - self._filepath = filepath - - # Override these! - - @abc.abstractmethod - def load(self, config_instance): - """Populates the Config instance from somewhere. - - It should iterate over all options in self.options and determine the value to store by using option.decode(..). - """ - pass - - @abc.abstractmethod - def store(self, config_instance): - """Persists the Config to somewhere. - - It should iterate over all options in self.options and determine the value to persis by using option.encode(..). - """ - pass diff --git a/src/freeseer/framework/config/persist/configparser.py.bak b/src/freeseer/framework/config/persist/configparser.py.bak deleted file mode 100644 index 2c47b015..00000000 --- a/src/freeseer/framework/config/persist/configparser.py.bak +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -import ConfigParser - -from freeseer.framework.config.core import ConfigStorage - - -class ConfigParserStorage(ConfigStorage): - """Persists Configs to and from INI style config files.""" - - def load(self, config_instance, section): - parser = ConfigParser.ConfigParser() - parser.read([self._filepath]) - - for name, option in config_instance.options.iteritems(): - if parser.has_option(section, name): - raw = parser.get(section, name) - clean = option.decode(raw) - config_instance.set_value(name, option, clean) - - return config_instance - - def store(self, config_instance, section): - parser = ConfigParser.ConfigParser() - parser.read([self._filepath]) - - if not parser.has_section(section): - parser.add_section(section) - - for name, option in config_instance.options.iteritems(): - raw = config_instance.get_value(name, option) - clean = option.encode(raw) - parser.set(section, name, clean) - - with open(self._filepath, 'w') as config_fd: - parser.write(config_fd) diff --git a/src/freeseer/framework/config/persist/jsonstorage.py.bak b/src/freeseer/framework/config/persist/jsonstorage.py.bak deleted file mode 100644 index 32d88399..00000000 --- a/src/freeseer/framework/config/persist/jsonstorage.py.bak +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -import json -import os - -from freeseer.framework.config.core import ConfigStorage - - -class JSONConfigStorage(ConfigStorage): - """Persists Configs to and from JSON formatted config files.""" - - def parse_json(self): - if os.path.isfile(self._filepath): - return json.load(open(self._filepath)) - else: - return {} - - def write_json(self, dict_): - with open(self._filepath, 'wc') as config_fd: - config_fd.write(json.dumps(dict_, - sort_keys=True, - indent=4, - separators=(',', ': '))) - - def load(self, config_instance, section): - dict_ = self.parse_json() - if section not in dict_: - return config_instance - - for name, option in config_instance.options.iteritems(): - if name in dict_[section]: - raw = dict_[section][name] - clean = option.decode(raw) - config_instance.set_value(name, option, clean) - return config_instance - - def store(self, config_instance, section): - dict_ = self.parse_json() - if section not in dict_: - dict_[section] = {} - - for name, option in config_instance.options.iteritems(): - raw = config_instance.get_value(name, option) - clean = option.encode(raw) - dict_[section][name] = clean - - self.write_json(dict_) diff --git a/src/freeseer/framework/config/profile.py.bak b/src/freeseer/framework/config/profile.py.bak deleted file mode 100644 index 5f417ee6..00000000 --- a/src/freeseer/framework/config/profile.py.bak +++ /dev/null @@ -1,206 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -import os -import shutil - -from freeseer.framework.config.persist import ConfigParserStorage -from freeseer.framework.config.persist import JSONConfigStorage -from freeseer.framework.database import QtDBConnector -from freeseer.framework.plugin import PluginManager - - -class ProfileManager(object): - """Manages Profile instances for the current system user.""" - - def __init__(self, base_folder): - self._base_folder = base_folder - self._cache = {} - self._create_if_needed(base_folder) - - def _create_if_needed(self, path): - try: - os.makedirs(path) - except OSError: - # This is thrown if path already exists. - pass - - def get(self, name='default', create_if_needed=True): - """ - Retrieve Profile instances by name. Profiles are cached for future gets. - - Args: - name: The name of the profile. - create_if_needed: When True, get creates a new profile instance - if profile for given name doesn't exist. - - Returns: - The instance of Profile for the given name. - - Raises: - ProfileDoesNotExist: If create_if_needed==False and - the given profile name doesn't exist. - """ - if name in self._cache: - return self._cache[name] - else: - full_path = os.path.join(self._base_folder, name) - if os.path.exists(full_path): - self._cache[name] = Profile(full_path, name) - return self._cache[name] - elif create_if_needed: - return self.create(name) - - raise ProfileDoesNotExist(name) - - def create(self, name): - """ - Creates a new Profile on file and adds it to the cache. - - Args: - name: The name of the profile to create. - - Returns: - The instance for the created Profile. - - Raises: - ProfileAlreadyExists: If a profile by the same name exists. - """ - path = os.path.join(self._base_folder, name) - try: - os.makedirs(path) - except OSError: - raise ProfileAlreadyExists(name) - - self._cache[name] = Profile(path, name) - return self._cache[name] - - def list_profiles(self): - """Returns a list of available profiles on file.""" - return os.listdir(self._base_folder) - - def delete(self, name): - """ - Deletes a profile and its configuration files from disk and cache. - - Args: - name: The name of the profile to delete. - - Raises: - ProfileDoesNotExist: If no profile exists for give name. - """ - path = os.path.join(self._base_folder, name) - try: - shutil.rmtree(path) - del self._cache[name] - except OSError: - raise ProfileDoesNotExist(name) - except KeyError: - pass - - -class Profile(object): - """Represents a profile's config files, databases, and other stuff.""" - - STORAGE_MAP = { - '.conf': ConfigParserStorage, - '.json': JSONConfigStorage, - } - - def __init__(self, folder, name): - self._folder = folder - self._name = name - self._storages = {} - self._databases = {} - - @property - def name(self): - return self._name - - def get_filepath(self, name): - """Returns the absolute path for a file called name. - - The filepath will be prefixed with the profile's base folder. - """ - return os.path.join(self._folder, name) - - def get_storage(self, name): - """ - Returns a ConfigStorage instance for a given config file name. - - The ConfigStorage instance is picked based on the file suffix. - It will also be cached for future invocations of this method. - """ - if name not in self._storages: - for suffix, engine in self.STORAGE_MAP.iteritems(): - if name.endswith(suffix): - self._storages[name] = engine(self.get_filepath(name)) - break - - if name in self._storages: - return self._storages[name] - else: - raise KeyError('{} does not have a valid suffix'.format(name)) - - def get_config(self, filename, config_class, storage_args=None, read_only=False): - """Returns an instance of config_class that has be loaded from filename. - - Params: - filename - name of the file, this will be passed to get_storage(..) - config_class - Config subclass - storage_args - an iterable of arguments that will be passed to - storage.load(config, ...) - read_only - if True, the storage will be passed to the Config - instance - """ - storage_args = storage_args if storage_args else [] - storage = self.get_storage(filename) - - if read_only: - config = config_class() - else: - config = config_class(storage, storage_args) - - return storage.load(config, *storage_args) - - def get_database(self, name='presentations.db'): - """Returns an instance of QtDBConnector for a specific database file. - - It is also cached for future gets. - """ - if name not in self._databases: - self._databases[name] = QtDBConnector(self.get_filepath(name), PluginManager(self)) - return self._databases[name] - - -class ProfileAlreadyExists(Exception): - def __init__(self, value): - message = 'Profile already exists: "{}"'.format(value) - super(Exception, self).__init__(message) - - -class ProfileDoesNotExist(Exception): - def __init__(self, value): - message = 'Profile does not exist: "{}"'.format(value) - super(Exception, self).__init__(message) diff --git a/src/freeseer/framework/database.py.bak b/src/freeseer/framework/database.py.bak deleted file mode 100644 index 25b599a4..00000000 --- a/src/freeseer/framework/database.py.bak +++ /dev/null @@ -1,605 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -import csv -import logging - -from PyQt4 import QtSql -from PyQt4.QtCore import QDate -from PyQt4.QtCore import QTime -from PyQt4.QtCore import QStringList - -from freeseer import SCHEMA_VERSION -from freeseer.framework.presentation import Presentation -from freeseer.framework.failure import Failure, Report - -log = logging.getLogger(__name__) - - -# Database Schema Versions -PRESENTATIONS_SCHEMA_300 = '''CREATE TABLE IF NOT EXISTS presentations - (Id INTEGER PRIMARY KEY, - Title varchar(255), - Speaker varchar(100), - Description text, - Level varchar(25), - Event varchar(100), - Room varchar(25), - Time timestamp, - UNIQUE (Speaker, Title) ON CONFLICT IGNORE)''' - -# The SQLITE timestamp field corresponds to the DateTime object. The Date column in the database can be created from -# a DateTime.date() call. -PRESENTATIONS_SCHEMA_310 = '''CREATE TABLE IF NOT EXISTS presentations - (Id INTEGER PRIMARY KEY, - Title varchar(255), - Speaker varchar(100), - Description text, - Category varchar(25), - Event varchar(100), - Room varchar(25), - Date timestamp, - StartTime timestamp, - EndTime timestamp, - UNIQUE (Speaker, Title) ON CONFLICT IGNORE)''' - -# TODO: Make PRESENTATIONS_SCHEMA_315 the default schema when the presentation table is created -# TODO: Make an upgrade method from PRESENTATIONS_SCHEMA_310 to PRESENTATIONS_SCHEMA_315 -# TODO: Update the SCHEMA_VERSION to 315 -# TODO: Integrate new database scheme with importers/exporter functions -# TODO: Force Presentation.Date to be a QDate -# TODO: Figure out what the types should be for Presentation.StartTime/EndTime e.g. QTime -# TODO: Enforce the default database values in the CSV/RSS importer -# TODO: Enforce the default database values in Presentation.__init__ -# TODO: Enforce the default database values in db.insert_presentation() -# TODO: Check what format the csv and rss sample files are in. For instance, do the rss files use 'None' instead of '' -# for missing fields? -# TODO: Update _helper_presentation_exists() and get_presentation_id() to use the new schema such that they no longer -# check if the stored data values are NULL -PRESENTATIONS_SCHEMA_315 = '''CREATE TABLE IF NOT EXISTS presentations - (Id INTEGER PRIMARY KEY, - Title varchar(255) NOT NULL DEFAULT '', - Speaker varchar(100) NOT NULL DEFAULT '', - Description text NOT NULL DEFAULT '', - Category varchar(25) NOT NULL DEFAULT '', - Event varchar(100) NOT NULL DEFAULT '', - Room varchar(25) NOT NULL DEFAULT '', - Date timestamp NOT NULL DEFAULT '', - StartTime timestamp NOT NULL DEFAULT '', - EndTime timestamp NOT NULL DEFAULT '', - UNIQUE (Speaker, Title, Event) ON CONFLICT IGNORE)''' - -REPORTS_SCHEMA_300 = '''CREATE TABLE IF NOT EXISTS failures - (Id INTERGER PRIMARY KEY, - Comments TEXT, - Indicator TEXT, - Release INTEGER, - UNIQUE (ID) ON CONFLICT REPLACE)''' - -# TODO: Ensure that the CSV/RSS to presentation importers are done using the same conventions. For instance, convert a -# room with value None to '' in both the csv and rss importer. Instead of the csv parser converting None to 'None' -# and the rss importer doing something else etcetera. - - -class QtDBConnector(object): - def __init__(self, db_filepath, plugman): - """ - Initialize the QtDBConnector - """ - self.talkdb_file = db_filepath - self.plugman = plugman - - self.presentationsModel = None - self.failuresModel = None - self.recentconnModel = None - self.__open_table() - - def __open_table(self): - """Opens a connection to the database. Uses by the init function.""" - self.talkdb = QtSql.QSqlDatabase.addDatabase("QSQLITE") - self.talkdb.setDatabaseName(self.talkdb_file) - - if self.talkdb.open(): - - # check if presentations table exists and if not create it. - if not self.talkdb.tables().contains("presentations"): - self.__create_presentations_table() - self.__insert_default_talk() - - # If presentations table did not exist, it is safe to say that the reports table needs to be reset - # or initialized. - self.clear_report_db() - self.__create_failures_table() - - # Set the database version (so the updater does not update) - QtSql.QSqlQuery('PRAGMA user_version = %i' % SCHEMA_VERSION) - - # check if recentConnections table exists and if not create it. - if not self.talkdb.tables().contains("recentconn"): - self.__create_recentconn_table() - - # verify that correct version of database exists - self.__update_version() - else: - log.error("Unable to create talkdb file.") - - def __close_table(self): - """Closes the connection the the database.""" - self.talkdb.close() - - def __get_db_version_int(self): - """Gets the database's current version. Default is 0 if unset (for 2x and older)""" - query = QtSql.QSqlQuery('PRAGMA user_version') - query.first() - return query.value(0).toInt()[0] - - def __update_version(self): - """Upgrade database to the latest SCHEMA_VERSION""" - - db_version = self.__get_db_version_int() - if db_version == SCHEMA_VERSION: - return - - # - # Define functions for upgrading between schema versions - # - def update_2xto30(): - """Incremental update of database from Freeseer 2.x and older to 3.0 - - SCHEMA_VERSION is 300 - """ - if db_version > 300: - log.debug('Database newer than schema version 300.') - return # No update needed - - log.debug('Updating to schema 300.') - QtSql.QSqlQuery('ALTER TABLE presentations RENAME TO presentations_old') # temporary table - self.__create_presentations_table(PRESENTATIONS_SCHEMA_300) - QtSql.QSqlQuery("""INSERT INTO presentations - SELECT Id, Title, Speaker, Description, Level, Event, Room, Time FROM presentations_old""") - QtSql.QSqlQuery('DROP TABLE presentations_old') - - def update_30to31(): - """Performs incremental update of database from 3.0 and older to 3.1.""" - QtSql.QSqlQuery('ALTER TABLE presentations RENAME TO presentations_old') - self.__create_presentations_table(PRESENTATIONS_SCHEMA_310) - QtSql.QSqlQuery("""INSERT INTO presentations - SELECT Id, Title, Speaker, Description, Level, Event, Room, Time, Time, Time - FROM presentations_old""") - QtSql.QSqlQuery('DROP TABLE presentations_old') - - # - # Perform the upgrade - # - updaters = [update_2xto30, update_30to31] - for updater in updaters: - updater() - - QtSql.QSqlQuery('PRAGMA user_version = %i' % SCHEMA_VERSION) - log.info('Upgraded presentations database from version {} to {}'.format(db_version, SCHEMA_VERSION)) - - def __create_presentations_table(self, schema=PRESENTATIONS_SCHEMA_310): - """Creates the presentations table in the database. Should be used to initialize a new table.""" - log.info("table created") - QtSql.QSqlQuery(schema) - - def __insert_default_talk(self): - """Inserts the required placeholder talk into the database.At least one talk must exist""" - self.insert_presentation(Presentation("", "", "", "", "", "", "", "", "")) - - def get_talks(self): - """Gets all the talks from the database including all columns""" - return QtSql.QSqlQuery('''SELECT * FROM presentations''') - - def get_events(self): - """Gets all the talk events from the database""" - return QtSql.QSqlQuery('''SELECT DISTINCT Event FROM presentations''') - - def get_talk_ids(self): - """Gets all the talk events from the database""" - return QtSql.QSqlQuery('''SELECT Id FROM presentations''') - - def get_talks_by_event(self, event): - """Gets the talks signed in a specific event from the database""" - return QtSql.QSqlQuery('''SELECT * FROM presentations WHERE Event=%s''' % event) - - def get_talks_by_room(self, room): - """Gets the talks hosted in a specific room from the database""" - return QtSql.QSqlQuery('''SELECT * FROM presentations WHERE Room=%s''' % room) - - def get_talks_by_room_and_time(self, room): - """Returns the talks hosted in a specified room, starting from the current date and time""" - current_date = QDate.currentDate().toString(1) # yyyy-mm-dd - current_time = QTime.currentTime().toString() # hh:mm:ss - return QtSql.QSqlQuery('''SELECT * FROM presentations - WHERE Room='{}' AND Date='{}' - AND StartTime >= '{}' ORDER BY StartTime ASC'''.format(room, current_date, current_time)) - - def get_presentation(self, talk_id): - """Returns a Presentation object associated to a talk_id""" - result = QtSql.QSqlQuery('''SELECT * FROM presentations WHERE Id="%s"''' % talk_id) - if result.next(): - return Presentation(title=unicode(result.value(1).toString()), - speaker=unicode(result.value(2).toString()), - description=unicode(result.value(3).toString()), - category=unicode(result.value(4).toString()), - event=unicode(result.value(5).toString()), - room=unicode(result.value(6).toString()), - date=unicode(result.value(7).toString()), - startTime=unicode(result.value(8).toString()), - endTime=unicode(result.value(9).toString())) - else: - return None - - def get_string_list(self, column): - """Returns a column as a QStringList""" - tempList = QStringList() - result = QtSql.QSqlQuery('''SELECT DISTINCT %s FROM presentations''' % column) - while result.next(): - tempList.append(result.value(0).toString()) - return tempList - - def presentation_exists(self, presentation): - """Checks if there's a presentation with the same Speaker and Title already stored""" - result = QtSql.QSqlQuery('''SELECT * FROM presentations''') - while result.next(): - if (unicode(presentation.title) == unicode(result.value(1).toString()) - and unicode(presentation.speaker) == unicode(result.value(2).toString())): - return True - return False - - # - # Presentation Create, Update, Delete - # - def insert_presentation(self, presentation): - """Inserts a passed Presentation into the database.""" - # Duplicate time to date field for older RSS / CSV formats - # If date is empty, and time has a full DateTime, split the DateTime to - # both Date and Time - - if not presentation.date and presentation.startTime and len(presentation.startTime) == 16: - presentation.date, presentation.startTime = presentation.startTime[:-6], presentation.startTime[11:] - - QtSql.QSqlQuery( - '''INSERT INTO presentations VALUES (NULL, "%s", "%s", "%s", "%s", "%s", "%s", "%s", "%s", "%s")''' % - (presentation.title, - presentation.speaker, - presentation.description, - presentation.category, - presentation.event, - presentation.room, - presentation.date, - presentation.startTime, - presentation.endTime)) - log.info("Talk added: %s - %s, Time: %s - %s" % (presentation.speaker, presentation.title, presentation.startTime, presentation.endTime)) - - def update_presentation(self, talk_id, presentation): - """Updates an existing Presentation in the database.""" - QtSql.QSqlQuery( - '''UPDATE presentations SET Title="%s", Speaker="%s", Description="%s", Category="%s", - Event="%s", Room="%s", Date="%s", StartTime="%s", EndTime="%s" - WHERE Id="%s"''' % - (presentation.title, - presentation.speaker, - presentation.description, - presentation.category, - presentation.event, - presentation.room, - presentation.date, - presentation.startTime, - presentation.endTime, - talk_id)) - log.info("Talk %s updated: %s - %s" % (talk_id, presentation.speaker, presentation.title)) - - def delete_presentation(self, talk_id): - """Removes a Presentation from the database""" - QtSql.QSqlQuery('''DELETE FROM presentations WHERE Id="%s"''' % talk_id) - log.info("Talk %s deleted." % talk_id) - - def clear_database(self): - """Clears the presentations table""" - QtSql.QSqlQuery('''DELETE FROM presentations''') - log.info("Database cleared.") - - # - # Data Model Retrieval - # - def get_presentations_model(self): - """Gets the Presentation Table Model. Useful for Qt GUI based Frontends to load the Model in Table Views""" - if self.presentationsModel is None: - self.presentationsModel = QtSql.QSqlTableModel() - self.presentationsModel.setTable("presentations") - self.presentationsModel.setEditStrategy(QtSql.QSqlTableModel.OnFieldChange) - self.presentationsModel.select() - return self.presentationsModel - - def get_events_model(self): - """Gets the Events Model. Useful for Qt GUI based Frontends to load the Model into Views""" - self.eventsModel = QtSql.QSqlQueryModel() - self.eventsModel.setQuery("SELECT DISTINCT Event FROM presentations ORDER BY Event ASC") - return self.eventsModel - - def get_dates_from_event_room_model(self, event, room): - """Gets the Dates Model. Useful for Qt GUI based Frontends to load the Model into Views.""" - self.datesModel = QtSql.QSqlQueryModel() - self.datesModel.setQuery( - "SELECT DISTINCT date FROM presentations WHERE Event='%s' and Room='%s' ORDER BY Date ASC" - % (event, room)) - return self.datesModel - - def get_rooms_model(self, event): - """Gets the Rooms Model. Useful for Qt GUI based Frontends to load the Model into Views""" - self.roomsModel = QtSql.QSqlQueryModel() - self.roomsModel.setQuery("SELECT DISTINCT Room FROM presentations WHERE Event='%s' ORDER BY Room ASC" % event) - return self.roomsModel - - def get_talks_model(self, event, room, date=None): - """Gets the Talks Model. A talk is defined as " - " - Useful for Qt GUI based Frontends to load the Model into Views""" - self.talksModel = QtSql.QSqlQueryModel() - if date == "": - self.talksModel.setQuery("SELECT (Speaker || ' - ' || Title), Id FROM presentations \ - WHERE Event='%s' and Room='%s' ORDER BY Date ASC" % (event, room)) - else: - self.talksModel.setQuery("SELECT (Speaker || ' - ' || Title), Id FROM presentations \ - WHERE Event='%s' and Room='%s' and date(Date) LIKE '%s' ORDER BY Date ASC" - % (event, room, date)) - return self.talksModel - - def get_talk_between_time(self, event, room, startTime, endTime): - """Returns the talkID of the first talk found between a startTime, and endTime for a specified event/room. - Else return None""" - query = QtSql.QSqlQuery("SELECT Id, Date FROM presentations \ - WHERE Event='%s' AND Room='%s' \ - AND Date BETWEEN '%s' \ - AND '%s' ORDER BY Date ASC" % (event, room, startTime, endTime)) - query.next() - if query.isValid(): - return query.value(0) - else: - return None - - # - # Import / Export Functions - # - # Needs to be updated for category field, separate date and time fields - def add_talks_from_rss(self, feed_url): - """Adds talks from an rss feed.""" - plugin = self.plugman.get_plugin_by_name("Rss FeedParser", "Importer") - feedparser = plugin.plugin_object - presentations = feedparser.get_presentations(feed_url) - - if presentations: - for presentation in presentations: - talk = Presentation(presentation["Title"], - presentation["Speaker"], - presentation["Abstract"], # Description - presentation["Level"], - presentation["Event"], - presentation["Room"], - presentation["Time"], - presentation["Time"]) - self.insert_presentation(talk) - - else: - log.info("RSS: No data found.") - - def add_talks_from_csv(self, fname): - """Adds talks from a csv file. - - Title and speaker must be present. - """ - plugin = self.plugman.get_plugin_by_name("CSV Importer", "Importer") - importer = plugin.plugin_object - presentations = importer.get_presentations(fname) - - if presentations: - for presentation in presentations: - if presentation['Time']: - talk = Presentation(presentation["Title"], - presentation["Speaker"], - presentation["Abstract"], # Description - presentation["Level"], - presentation["Event"], - presentation["Room"], - presentation["Time"], - presentation["Time"]) # Presentation using legacy time field - else: - talk = Presentation(presentation["Title"], - presentation["Speaker"], - presentation["Abstract"], # Description - presentation["Level"], - presentation["Event"], - presentation["Room"], - presentation["Date"], - presentation["StartTime"], - presentation["EndTime"]) - self.insert_presentation(talk) - - else: - log.info("CSV: No data found.") - - def export_talks_to_csv(self, fname): - fieldNames = ('Title', - 'Speaker', - 'Abstract', - 'Category', - 'Event', - 'Room', - 'Date', - 'StartTime', - 'EndTime') - - try: - file = open(fname, 'w') - writer = csv.DictWriter(file, fieldnames=fieldNames) - headers = dict((n, n) for n in fieldNames) - writer.writerow(headers) - - result = self.get_talks() - while result.next(): - log.debug(unicode(result.value(1).toString())) - writer.writerow({'Title': unicode(result.value(1).toString()), - 'Speaker': unicode(result.value(2).toString()), - 'Abstract': unicode(result.value(3).toString()), - 'Category': unicode(result.value(4).toString()), - 'Event': unicode(result.value(5).toString()), - 'Room': unicode(result.value(6).toString()), - 'Date': unicode(result.value(7).toString()), - 'StartTime': unicode(result.value(8).toString()), - 'EndTime': unicode(result.value(9).toString())}) - finally: - file.close() - - def export_reports_to_csv(self, fname): - fieldNames = ('Title', - 'Speaker', - 'Abstract', - 'Category', - 'Event', - 'Room', - 'Date', - 'StartTime', - 'EndTime', - 'Problem', - 'Error') - try: - file = open(fname, 'w') - writer = csv.DictWriter(file, fieldnames=fieldNames) - headers = dict((n, n) for n in fieldNames) - writer.writerow(headers) - - result = self.get_reports() - for report in result: - writer.writerow({'Title': report.presentation.title, - 'Speaker': report.presentation.speaker, - 'Abstract': report.presentation.description, - 'Category': report.presentation.category, - 'Event': report.presentation.event, - 'Room': report.presentation.room, - 'Date': report.presentation.date, - 'StartTime': report.presentation.startTime, - 'EndTime': report.presentation.endTime, - 'Problem': report.failure.indicator, - 'Error': report.failure.comment}) - finally: - file.close() - - # - # Reporting Feature - # - def __create_failures_table(self, schema=REPORTS_SCHEMA_300): - """Creates the failures table in the database. Should be used to initialize a new table""" - QtSql.QSqlQuery(schema) - - def clear_report_db(self): - """Drops the failures (reports) table from the database""" - QtSql.QSqlQuery('''DROP TABLE IF EXISTS failures''') - - def get_report(self, talkid): - """Returns a failure from a given talkid. Returned value is a Failure object""" - result = QtSql.QSqlQuery('''SELECT * FROM failures WHERE Id = "%s"''' % talkid) - if result.next(): - failure = Failure(unicode(result.value(0).toString()), # id - unicode(result.value(1).toString()), # comment - unicode(result.value(2).toString()), # indicator - result.value(3).toBool()) # release - else: - failure = None - return failure - - def get_reports(self): - """Returns a list of failures in Report format""" - result = QtSql.QSqlQuery('''Select * FROM failures''') - list = [] - while result.next(): - failure = Failure(unicode(result.value(0).toString()), # id - unicode(result.value(1).toString()), # comment - unicode(result.value(2).toString()), # indicator - bool(result.value(3))) # release - p = self.get_presentation(failure.talkId) - r = Report(p, failure) - list.append(r) - return list - - def insert_failure(self, failure): - """Inserts a failure into the database""" - QtSql.QSqlQuery( - '''INSERT INTO failures VALUES ("%d", "%s", "%s", %d)''' % - (int(failure.talkId), failure.comment, failure.indicator, failure.release)) - log.info("Failure added: %s - %s" % (failure.talkId, failure.comment)) - - def update_failure(self, talk_id, failure): - """Updates an existing Failure in the database""" - QtSql.QtSqlQuery('''UPDATE failures SET Comments="%s", Indicator="%s", Release="%d" WHERE Id="%s"''' % - (failure.comment, - failure.indicator, - failure.release, - failure.talkId)) - log.info("Failure updated: %s %s" % (failure.talkId, failure.comment)) - - def delete_failure(self, talk_id): - """Removes a Presentation from the database""" - QtSql.QSqlQuery('''DELETE FROM failures WHERE Id="%s"''' % talk_id) - log.info("Failure %s deleted." % talk_id) - - def get_failures_model(self): - """Gets the Failure reports table Model. Useful for QT GUI based Frontends to load the Model in Table Views""" - if self.failuresModel is None: - self.failuresModel = QtSql.QSqlTableModel() - self.failuresModel.setTable("failures") - self.failuresModel.setEditStrategy(QtSql.QSqlTableModel.OnFieldChange) - self.failuresModel.select() - - return self.failuresModel - - # - # Controller Feature - # - def __create_recentconn_table(self): - """Creates the recentconn table in the database. Should be used to initialize a new table""" - QtSql.QSqlQuery('''CREATE TABLE IF NOT EXISTS recentconn - (host varchar(255), - port int, - passphrase varchar(255), - UNIQUE (host, port) ON CONFLICT REPLACE)''') - - def clear_recentconn_table(self): - """Drops the recentconn (Controller) table from the database""" - QtSql.QSqlQuery('''DROP TABLE IF EXISTS recentconn''') - - def insert_recentconn(self, chost, cport, cpass): - """Insert a failure into the database""" - QtSql.QSqlQuery('''INSERT INTO recentconn VALUES("%s", "%d", "%s")''' % (chost, cport, cpass)) - log.info("Recent connection added: %s:%d" % (chost, cport)) - - def get_recentconn_model(self): - """Gets the Recent Connections table Model - Useful for QT GUI based Frontends to load the Model in Table Views""" - self.recentconnModel = QtSql.QSqlTableModel() - self.recentconnModel.setTable("recentconn") - self.recentconnModel.setEditStrategy(QtSql.QSqlTableModel.OnFieldChange) - self.recentconnModel.select() - - return self.recentconnModel diff --git a/src/freeseer/framework/qt_key_grabber.py.bak b/src/freeseer/framework/qt_key_grabber.py.bak deleted file mode 100644 index b08e990e..00000000 --- a/src/freeseer/framework/qt_key_grabber.py.bak +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -import sys -from PyQt4 import QtCore, QtGui - - -class QtKeyGrabber(QtGui.QWidget): - ''' - This class allows the user to press a combination of keys in order to - set a shortkey. - ''' - def __init__(self, parent=None): - ''' - Create an active screen and initialize variables used in this - class. - ''' - QtGui.QWidget.__init__(self, None, QtCore.Qt.FramelessWindowHint) - self.setAttribute(QtCore.Qt.WA_DeleteOnClose) - self.setWindowState(QtCore.Qt.WindowActive) - - self.parent = parent - self.flag = False - self.modifiers = {} - self.setWindowOpacity(0.3) - - def keyPressEvent(self, event): - other = None - if event.key() == QtCore.Qt.Key_Shift: - self.modifiers[QtCore.Qt.Key_Shift] = u'Shift' - elif event.key() == QtCore.Qt.Key_Control: - self.modifiers[QtCore.Qt.Key_Control] = u'Ctrl' - elif event.key() == QtCore.Qt.Key_Alt: - self.modifiers[QtCore.Qt.Key_Alt] = u'Alt' - elif event.key() == QtCore.Qt.Key_Meta: - self.modifiers[QtCore.Qt.Key_Meta] = u'Meta' - else: - other = event.text() - if other: - if QtCore.Qt.Key_Control in self.modifiers: - self.key_string = u'+'.join(self.modifiers.values() + [unicode(chr(event.key()))]) - else: - self.key_string = u'+'.join(self.modifiers.values() + [unicode(other)]) - else: - self.key_string = u'+'.join(self.modifiers.values()) - if (self.parent.core.config.key_rec == 'Ctrl+Shift+R'): - self.flag = True - - def keyReleaseEvent(self, event): - if event.key() == QtCore.Qt.Key_Shift: - if QtCore.Qt.Key_Shift in self.modifiers: - del self.modifiers[QtCore.Qt.Key_Shift] - elif event.key() == QtCore.Qt.Key_Control: - if QtCore.Qt.Key_Control in self.modifiers: - del self.modifiers[QtCore.Qt.Key_Control] - elif event.key() == QtCore.Qt.Key_Alt: - if QtCore.Qt.Key_Alt in self.modifiers: - del self.modifiers[QtCore.Qt.Key_Alt] - elif event.key() == QtCore.Qt.Key_Meta: - if QtCore.Qt.Key_Meta in self.modifiers: - del self.modifiers[QtCore.Qt.Key_Meta] - #print len(self.modifiers) - if len(self.modifiers) == 0: - if self.flag: - self.parent.grab_rec_set(self.key_string) - else: - self.parent.grab_stop_set(self.key_string) - self.close() - -if __name__ == "__main__": - app = QtGui.QApplication(sys.argv) - main = QtKeyGrabber() - main.show() - sys.exit(app.exec_()) diff --git a/src/freeseer/framework/youtube.py.bak b/src/freeseer/framework/youtube.py.bak deleted file mode 100644 index c6be7afc..00000000 --- a/src/freeseer/framework/youtube.py.bak +++ /dev/null @@ -1,220 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - - -import httplib -import httplib2 -import logging -import os -import time - -from apiclient import discovery -from apiclient.errors import HttpError -from apiclient.http import MediaFileUpload -from mutagen import oggvorbis -from oauth2client import file -from oauth2client import client -from oauth2client import tools - -log = logging.getLogger(__name__) - - -class Response(object): - """Class to serve as enum for responses""" - - SUCCESS = 0 - UNEXPECTED_FAILURE = 1 - UNRETRIABLE_ERROR = 2 - ACCESS_TOKEN_ERROR = 3 - MAX_RETRIES_REACHED = 4 - - -class YoutubeService(object): - """Class for interacting with YouTube Data API v3""" - - # Status codes and exceptions for retry logic - MAX_RETRIES = 3 - RETRIABLE_EXCEPTIONS = ( - httplib2.HttpLib2Error, - IOError, - httplib.NotConnected, - httplib.IncompleteRead, - httplib.ImproperConnectionState, - httplib.CannotSendRequest, - httplib.CannotSendHeader, - httplib.ResponseNotReady, - httplib.BadStatusLine - ) - RETRIABLE_STATUS_CODES = (500, 502, 503, 504) - - @staticmethod - def acquire_token(client_secrets, oauth2_token, flags): - """Handles the user consent process and saves the retrieved OAuth2 token - - Args: - client_secrets - path to client_secrets file - oauth2_token - path to save oauth2_token - - (YouTube Service Parameters) - https://google-api-python-client.googlecode.com/hg/docs/epy/oauth2client.tools-module.html - - flags.auth_host_name - Host name to use when running a local web server to handle redirects during - OAuth authorization. - (default: 'localhost') - - flags..auth_host_port - Port to use when running a local web server to handle redirects during OAuth - authorization.; repeat this option to specify a list of values - (default: '[8080, 8090]') - (an integer) - - flags.auth_local_webserver - True/False Run a local web server to handle redirects during OAuth - authorization. - (default: True) - """ - scope = ['https://www.googleapis.com/auth/youtube.upload'] - message = ("Please specify a valid client_secrets.json file.\n" - "For instructions to obtain one, please visit:\n" - "https://docs.google.com/document/d/1ro9I8jnOCgQlWRRVCPbrNnQ5-bMvQxDVg6o45zxud4c/edit") - flow = client.flow_from_clientsecrets(client_secrets, scope=scope, message=message) - storage = file.Storage(oauth2_token) - tools.run_flow(flow, storage, flags) - - @staticmethod - def valid_video_file(file): - """Verify file is supported by Youtube - - Freeseer currently encodes to .ogg and .webm - TODO: expand list to all types supported by Youtube - - Args: - file: path to file to verify - - Returns: - True if file is supported - """ - return file.lower().endswith(('.ogg', '.webm')) - - @staticmethod - def get_metadata(video_file): - """Parses file metadata - - Parsing is delegated to appropriate library based on filetype - If filetype is unsupported default metadata is used instead - - Args: - video_file: path to file - - Returns: - Metadata formatted to Youtube APIs status parameter - """ - metadata = { - "title": os.path.basename(video_file).split(".")[0], - "description": "A video recorded with Freeseer", - "tags": ['Freeseer', 'FOSSLC', 'Open Source'], - "categoryId": 27 # temporary, see gh#415 - } - if video_file.lower().endswith('.ogg'): - tags = oggvorbis.Open(video_file) - if "title" in tags: - metadata['title'] = tags['title'][0] - if "album" in tags and "artist" in tags and "date" in tags: - metadata['description'] = "At {} by {} recorded on {}".format(tags['album'][0], tags['artist'][0], tags['date'][0]) - return metadata - - def __init__(self): - """Initialize YoutubeService setting up http related values""" - # Tell the underlying HTTP transport library not to retry, we want explicit control over the retry logic - # and to only retry on errors/exceptions specified by Google - httplib2.RETRIES = 1 - - def authorize(self, oauth2_token): - """Function that authorizes upcoming HTTP transactions - - If the token has expired, a new one is retrieved automatically via the refresh token (located inside the same file) - """ - storage = file.Storage(oauth2_token) - credentials = storage.get() - http = credentials.authorize(httplib2.Http()) - self.service = discovery.build('youtube', 'v3', http=http) - - def upload_video(self, video_file): - """Function to upload file to Youtube - - Function grabs metadata from video_file and passes it along in the upload, the files privacy and license are - set to public and youtube respectively - - Args: - video_file: path to video file for upload - - Returns: - A tuple containing a response code and a dictionary with the appropriate information - """ - part = "snippet,status" - metadata = self.get_metadata(video_file) - body = { - "snippet": { - "title": metadata['title'], - "description": metadata['description'], - "tags": metadata['categoryId'], - "categoryId": metadata['categoryId'] - }, - "status": { - "privacyStatus": "public", - "license": "youtube", # temporary, see gh#414 - "embeddable": True, - "publicStatsViewable": True - } - } - # This is to fix a bug, the API thinks our .ogg files are audio/ogg - mimetype = "video/{}".format(video_file.split(".")[-1]) - media_body = MediaFileUpload(video_file, chunksize=-1, resumable=True, mimetype=mimetype) - insert_request = self.service.videos().insert(part=part, body=body, media_body=media_body) - response = None - error = None - retry = 0 - sleep_seconds = 5.0 - while response is None: - try: - log.info("Uploading %s" % video_file) - (status, response) = insert_request.next_chunk() - if 'id' in response: - return (Response.SUCCESS, response) - else: - return (Response.UNEXPECTED_FAILURE, response) - except HttpError as e: - if e.resp.status in self.RETRIABLE_STATUS_CODES: - error = "A retriable HTTP error {} occurred:\n{}".format(e.resp.status, e.content) - else: - return (Response.UNRETRIABLE_ERROR, {"status": e.resp.status, "content": e.content}) - except self.RETRIABLE_EXCEPTIONS as e: - error = "A retriable error occurred: {}".format(e) - except client.AccessTokenRefreshError: - return (Response.ACCESS_TOKEN_ERROR, None) - if error is not None: - log.error(error) - retry += 1 - if retry > self.MAX_RETRIES: - return (Response.MAX_RETRIES_REACHED, None) - log.info("Sleeping %s seconds and then retrying..." % sleep_seconds) - time.sleep(sleep_seconds) diff --git a/src/freeseer/frontend/configtool/AVWidget.py.bak b/src/freeseer/frontend/configtool/AVWidget.py.bak deleted file mode 100644 index 5c1269cb..00000000 --- a/src/freeseer/frontend/configtool/AVWidget.py.bak +++ /dev/null @@ -1,224 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -''' -freeseer - vga/presentation capture software - -Copyright (C) 2011 Free and Open Source Software Learning Centre -http://fosslc.org - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -For support, questions, suggestions or any other inquiries, visit: -http://wiki.github.com/Freeseer/freeseer/ - -@author: Thanh Ha -''' - -from PyQt4 import QtCore -from PyQt4 import QtGui - -from freeseer.framework.multimedia import Quality -from freeseer.frontend.qtcommon.dpi_adapt_qtgui import QGroupBoxWithDpi -from freeseer.frontend.qtcommon.dpi_adapt_qtgui import QWidgetWithDpi - - -class AVWidget(QWidgetWithDpi): - ''' - classdocs - ''' - - def __init__(self, parent=None): - ''' - Constructor - ''' - super(AVWidget, self).__init__(parent) - - self.mainLayout = QtGui.QVBoxLayout() - self.mainLayout.addStretch(0) - self.setLayout(self.mainLayout) - - config_icon = QtGui.QIcon.fromTheme("preferences-system") - - fontSize = self.font().pixelSize() - fontUnit = "px" - if fontSize == -1: # Font is set as points, not pixels. - fontUnit = "pt" - fontSize = self.font().pointSize() - - boxStyle = "QGroupBox {{ font-weight: bold; font-size: {}{} }}".format(fontSize + 1, fontUnit) - BOX_WIDTH = 400 - BOX_HEIGHT = 60 - - # - # Audio Input - # - - audioLayout = QtGui.QGridLayout() - self.audioGroupBox = QGroupBoxWithDpi("Audio Input") - self.audioGroupBox.setLayout(audioLayout) - self.mainLayout.insertWidget(0, self.audioGroupBox) - - self.audioGroupBox.setCheckable(True) - self.audioGroupBox.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - self.audioGroupBox.setFixedSize(BOX_WIDTH, 1.5 * BOX_HEIGHT) - self.audioGroupBox.setStyleSheet(boxStyle) - - self.audioMixerLabel = QtGui.QLabel("Audio Mixer") - self.audioMixerLabel.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) - self.audioMixerComboBox = QtGui.QComboBox() - self.audioMixerLabel.setBuddy(self.audioMixerComboBox) - self.audioMixerSetupPushButton = QtGui.QToolButton() - self.audioMixerSetupPushButton.setText("Setup") - self.audioMixerSetupPushButton.setIcon(config_icon) - self.audioMixerSetupPushButton.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) - self.audioMixerSetupPushButton.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) - audioLayout.addWidget(self.audioMixerLabel, 0, 0) - audioLayout.addWidget(self.audioMixerComboBox, 0, 1) - audioLayout.addWidget(self.audioMixerSetupPushButton, 0, 2) - - self.audioQualityLabel = QtGui.QLabel("Audio Quality") - self.audioQualityLabel.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) - self.audioQualityComboBox = QtGui.QComboBox() - self.audioQualityComboBox.addItems(Quality.qualities) - self.audioQualityLabel.setBuddy(self.audioQualityComboBox) - self.audioQualitySetupPushButton = QtGui.QToolButton() - self.audioQualitySetupPushButton.setText("Setup") - self.audioQualitySetupPushButton.setIcon(config_icon) - self.audioQualitySetupPushButton.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) - self.audioQualitySetupPushButton.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) - self.audioQualitySetupPushButton.setEnabled(False) - audioLayout.addWidget(self.audioQualityLabel, 1, 0) - audioLayout.addWidget(self.audioQualityComboBox, 1, 1) - audioLayout.addWidget(self.audioQualitySetupPushButton, 1, 2) - - # - # Video Input - # - - videoLayout = QtGui.QGridLayout() - self.videoGroupBox = QGroupBoxWithDpi("Video Input") - self.videoGroupBox.setLayout(videoLayout) - self.mainLayout.insertWidget(0, self.videoGroupBox) - - self.videoGroupBox.setCheckable(True) - self.videoGroupBox.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - self.videoGroupBox.setFixedSize(BOX_WIDTH, 1.5 * BOX_HEIGHT) - self.videoGroupBox.setStyleSheet(boxStyle) - - self.videoMixerLabel = QtGui.QLabel("Video Mixer") - self.videoMixerLabel.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) - self.videoMixerComboBox = QtGui.QComboBox() - self.videoMixerLabel.setBuddy(self.videoMixerComboBox) - self.videoMixerSetupPushButton = QtGui.QToolButton() - self.videoMixerSetupPushButton.setText("Setup") - self.videoMixerSetupPushButton.setIcon(config_icon) - self.videoMixerSetupPushButton.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) - self.videoMixerSetupPushButton.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) - videoLayout.addWidget(self.videoMixerLabel, 0, 0) - videoLayout.addWidget(self.videoMixerComboBox, 0, 1) - videoLayout.addWidget(self.videoMixerSetupPushButton, 0, 2) - - self.videoQualityLabel = QtGui.QLabel("Video Quality") - self.videoQualityLabel.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) - self.videoQualityComboBox = QtGui.QComboBox() - self.videoQualityComboBox.addItems(Quality.qualities) - self.videoQualityLabel.setBuddy(self.videoQualityComboBox) - self.videoQualitySetupPushButton = QtGui.QToolButton() - self.videoQualitySetupPushButton.setText("Setup") - self.videoQualitySetupPushButton.setIcon(config_icon) - self.videoQualitySetupPushButton.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) - self.videoQualitySetupPushButton.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) - self.videoQualitySetupPushButton.setEnabled(False) - videoLayout.addWidget(self.videoQualityLabel, 1, 0) - videoLayout.addWidget(self.videoQualityComboBox, 1, 1) - videoLayout.addWidget(self.videoQualitySetupPushButton, 1, 2) - - # - # Record to Stream - # - - streamLayout = QtGui.QGridLayout() - self.streamGroupBox = QGroupBoxWithDpi("Record to Stream") - self.streamGroupBox.setLayout(streamLayout) - self.mainLayout.insertWidget(0, self.streamGroupBox) - - self.streamGroupBox.setCheckable(True) - self.streamGroupBox.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - self.streamGroupBox.setFixedSize(BOX_WIDTH, BOX_HEIGHT) - self.streamGroupBox.setStyleSheet(boxStyle) - - self.streamLabel = QtGui.QLabel("Stream Format") - self.streamLabel.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) - self.streamComboBox = QtGui.QComboBox() - self.streamLabel.setBuddy(self.streamComboBox) - self.streamSetupPushButton = QtGui.QToolButton() - self.streamSetupPushButton.setText("Setup") - self.streamSetupPushButton.setIcon(config_icon) - self.streamSetupPushButton.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) - self.streamSetupPushButton.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) - streamLayout.addWidget(self.streamLabel, 0, 0) - streamLayout.addWidget(self.streamComboBox, 0, 1) - streamLayout.addWidget(self.streamSetupPushButton, 0, 2) - - # - # Record to File - # - - fileLayout = QtGui.QGridLayout() - self.fileGroupBox = QGroupBoxWithDpi("Record to File") - self.fileGroupBox.setLayout(fileLayout) - self.mainLayout.insertWidget(0, self.fileGroupBox) - - self.fileGroupBox.setCheckable(True) - self.fileGroupBox.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - self.fileGroupBox.setFixedSize(BOX_WIDTH, BOX_HEIGHT + 40) - self.fileGroupBox.setStyleSheet(boxStyle) - - self.fileDirLabel = QtGui.QLabel("Record Directory") - self.fileDirLineEdit = QtGui.QLineEdit() - self.fileDirLabel.setBuddy(self.fileDirLineEdit) - self.fileDirPushButton = QtGui.QPushButton("Browse...") - fileLayout.addWidget(self.fileDirLabel, 0, 0) - fileLayout.addWidget(self.fileDirLineEdit, 0, 1) - fileLayout.addWidget(self.fileDirPushButton, 0, 2) - - self.fileLabel = QtGui.QLabel("File Format") - self.fileLabel.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) - self.fileComboBox = QtGui.QComboBox() - self.fileLabel.setBuddy(self.fileComboBox) - self.fileSetupPushButton = QtGui.QToolButton() - self.fileSetupPushButton.setText("Setup") - self.fileSetupPushButton.setIcon(config_icon) - self.fileSetupPushButton.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum) - self.fileSetupPushButton.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly) - fileLayout.addWidget(self.fileLabel, 1, 0) - fileLayout.addWidget(self.fileComboBox, 1, 1) - fileLayout.addWidget(self.fileSetupPushButton, 1, 2) - - # - # Heading - # - - self.mainLayout.insertSpacerItem(0, QtGui.QSpacerItem(0, fontSize * 2)) - self.title = QtGui.QLabel(u"{0} Recording {1}".format(u'

', u'

')) - self.mainLayout.insertWidget(0, self.title) - - -if __name__ == "__main__": - import sys - app = QtGui.QApplication(sys.argv) - main = AVWidget() - main.show() - sys.exit(app.exec_()) diff --git a/src/freeseer/frontend/configtool/GeneralWidget.py.bak b/src/freeseer/frontend/configtool/GeneralWidget.py.bak deleted file mode 100644 index 4566e1c6..00000000 --- a/src/freeseer/frontend/configtool/GeneralWidget.py.bak +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -''' -freeseer - vga/presentation capture software - -Copyright (C) 2011 Free and Open Source Software Learning Centre -http://fosslc.org - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . - -For support, questions, suggestions or any other inquiries, visit: -http://wiki.github.com/Freeseer/freeseer/ - -@author: Thanh Ha -''' - -from PyQt4 import QtCore, QtGui - -from freeseer.frontend.qtcommon.dpi_adapt_qtgui import QGroupBoxWithDpi -from freeseer.frontend.qtcommon.dpi_adapt_qtgui import QWidgetWithDpi - - -class GeneralWidget(QWidgetWithDpi): - ''' - classdocs - ''' - - def __init__(self, parent=None): - ''' - Constructor - ''' - super(GeneralWidget, self).__init__(parent) - - self.mainLayout = QtGui.QVBoxLayout() - self.mainLayout.addStretch(0) - self.setLayout(self.mainLayout) - - fontSize = self.font().pixelSize() - fontUnit = "px" - if fontSize == -1: # Font is set as points, not pixels. - fontUnit = "pt" - fontSize = self.font().pointSize() - - boxStyle = "QGroupBox {{ font-weight: bold; font-size: {}{} }}".format(fontSize + 1, fontUnit) - BOX_WIDTH = 400 - BOX_HEIGHT = 60 - - # - # Heading - # - - self.title = QtGui.QLabel(u"{0} General {1}".format(u'

', u'

')) - self.mainLayout.insertWidget(0, self.title) - self.mainLayout.insertSpacerItem(1, QtGui.QSpacerItem(0, fontSize * 2)) - - # - # Language - # - - languageBoxLayout = QtGui.QVBoxLayout() - self.languageGroupBox = QGroupBoxWithDpi("Language") - self.languageGroupBox.setLayout(languageBoxLayout) - self.languageGroupBox.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - self.languageGroupBox.setFixedSize(BOX_WIDTH, BOX_HEIGHT) - self.languageGroupBox.setStyleSheet(boxStyle) - self.mainLayout.insertWidget(2, self.languageGroupBox) - - languageLayout = QtGui.QHBoxLayout() - languageBoxLayout.addLayout(languageLayout) - self.translateButton = QtGui.QPushButton("Help us translate") - self.languageComboBox = QtGui.QComboBox() - self.languageComboBox.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) - languageLayout.addWidget(self.languageComboBox, 2) - languageLayout.addSpacerItem(self.qspacer_item_with_dpi(40, 0)) - languageLayout.addWidget(self.translateButton, 1) - - # - # Appearance - # - - appearanceBoxLayout = QtGui.QVBoxLayout() - self.appearanceGroupBox = QGroupBoxWithDpi("Appearance") - self.appearanceGroupBox.setLayout(appearanceBoxLayout) - self.appearanceGroupBox.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - self.appearanceGroupBox.setFixedSize(BOX_WIDTH, BOX_HEIGHT) - self.appearanceGroupBox.setStyleSheet(boxStyle) - self.mainLayout.insertWidget(3, self.appearanceGroupBox) - - self.autoHideCheckBox = QtGui.QCheckBox("Auto-Hide to system tray on record") - appearanceBoxLayout.addWidget(self.autoHideCheckBox) - - # - # Reset - # - - resetBoxLayout = QtGui.QVBoxLayout() - self.resetGroupBox = QGroupBoxWithDpi("Reset") - self.resetGroupBox.setLayout(resetBoxLayout) - self.resetGroupBox.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - self.resetGroupBox.setFixedSize(BOX_WIDTH / 2, BOX_HEIGHT) - self.resetGroupBox.setStyleSheet(boxStyle) - self.mainLayout.addWidget(self.resetGroupBox) - self.mainLayout.addSpacerItem(self.qspacer_item_with_dpi(0, 20)) - - resetLayout = QtGui.QHBoxLayout() - resetBoxLayout.addLayout(resetLayout) - self.resetButton = QtGui.QPushButton("Reset settings to defaults") - resetLayout.addWidget(self.resetButton) - -if __name__ == "__main__": - import sys - app = QtGui.QApplication(sys.argv) - main = GeneralWidget() - main.show() - sys.exit(app.exec_()) diff --git a/src/freeseer/frontend/configtool/PluginWidget.py.bak b/src/freeseer/frontend/configtool/PluginWidget.py.bak deleted file mode 100644 index 4909d4c2..00000000 --- a/src/freeseer/frontend/configtool/PluginWidget.py.bak +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software - -# Copyright (C) 2014 Free and Open Source Software Learning Centre -# http://fosslc.org - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -''' -@author: Mia Kilborn -''' -from PyQt4.QtCore import QSize -from PyQt4.QtGui import QFont -from PyQt4.QtGui import QGroupBox -from PyQt4.QtGui import QLabel -from PyQt4.QtGui import QTreeWidget -from PyQt4.QtGui import QTreeWidgetItem -from PyQt4.QtGui import QVBoxLayout -from PyQt4.QtGui import QWidget - - -class PluginWidget(QWidget): - - def __init__(self, parent=None): - QWidget.__init__(self, parent) - - self.pluginMetadata = {} - - # Main layout - self.mainLayout = QVBoxLayout() - self.setLayout(self.mainLayout) - - # Define size used for the underlines - self.underlineSize = QSize() - self.underlineSize.setHeight(1) - - # Define font used for headers - self.font = QFont() - self.font.setPointSize(11) - self.font.setBold(True) - self.font.setUnderline(True) - - # Plugins Description - self.pluginDescription = QLabel() - self.pluginDescription.setText("Click a plugin to see more information." + - " Plugins can be configured from the Recording tab. \n") - self.pluginDescription.setWordWrap(True) - - # Plugins GroupBox - self.pluginLayout = QVBoxLayout() - self.pluginGroupBox = QGroupBox("Plugins extend the functionality of Freeseer") - self.pluginGroupBox.setLayout(self.pluginLayout) - self.pluginLayout.insertWidget(0, self.pluginDescription) - self.mainLayout.insertWidget(0, self.pluginGroupBox) - - # Plugins list - self.list = QTreeWidget() - self.list.setHeaderHidden(True) - self.list.headerItem().setText(0, "1") - self.pluginLayout.insertWidget(1, self.list) - - # Details - self.detailPane = QGroupBox() - self.detailLayout = QVBoxLayout() - self.detailPane.setLayout(self.detailLayout) - self.detailPaneDesc = QLabel() - self.detailPaneDesc.setWordWrap(True) - self.detailLayout.addWidget(self.detailPaneDesc) - self.pluginLayout.insertWidget(2, self.detailPane) - - self.list.itemSelectionChanged.connect(self.treeViewSelect) - - def treeViewSelect(self): - item = self.list.currentItem() - key = str(item.text(0)) - if key in self.pluginMetadata.keys(): - self.showDetails(key) - else: - self.hideDetails() - - def showDetails(self, key): - self.detailPane.setTitle(key) - self.detailPaneDesc.setText(self.pluginMetadata[key]) - self.detailPane.show() - - def hideDetails(self): - self.detailPane.hide() - - def getWidgetPlugin(self, plugin, plugin_category, plugman): - plugin_name = plugin.plugin_object.get_name() - item = QTreeWidgetItem() - - # Display Plugin's meta data in a tooltip - pluginDetails = """ - - - - - - - - - - - - - - - - - - - - -
Name: %(name)s
Version: %(version)s
Author: %(author)s
Website: %(website)s
Description: %(description)s
- """ % {"name": plugin.name, - "version": plugin.version, - "author": plugin.author, - "website": plugin.website, - "description": plugin.description} - - # put the details in the hash table - self.pluginMetadata[plugin_name] = pluginDetails - - item.setText(0, plugin_name) - return item - - -if __name__ == "__main__": - import sys - from PyQt4.QtGui import QApplication - app = QApplication(sys.argv) - main = PluginWidget() - main.show() - sys.exit(app.exec_()) diff --git a/src/freeseer/frontend/configtool/configtool.py.bak b/src/freeseer/frontend/configtool/configtool.py.bak deleted file mode 100644 index 58b8df0f..00000000 --- a/src/freeseer/frontend/configtool/configtool.py.bak +++ /dev/null @@ -1,760 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2014 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -import logging -import os -import re - -from PyQt4 import QtGui -from PyQt4 import QtCore -from PyQt4.QtGui import QInputDialog -from PyQt4.QtGui import QLineEdit -from PyQt4.QtGui import QMessageBox - -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - _fromUtf8 = lambda s: s - -from freeseer.framework.multimedia import Quality -from freeseer.framework.plugin import PluginManager, IOutput -from freeseer.frontend.qtcommon.FreeseerApp import FreeseerApp - -from freeseer.frontend.qtcommon.AboutWidget import AboutWidget -from freeseer.frontend.configtool.AVWidget import AVWidget -from freeseer.frontend.configtool.ConfigToolWidget import ConfigToolWidget -from freeseer.frontend.configtool.GeneralWidget import GeneralWidget -from freeseer.frontend.configtool.PluginWidget import PluginWidget - -log = logging.getLogger(__name__) - - -class ConfigToolApp(FreeseerApp): - ''' - ConfigTool is used to tune settings used by the Freeseer Application - ''' - - def __init__(self, profile, config): - super(ConfigToolApp, self).__init__(config) - - # Load Config Stuff - self.profile = profile - - icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap(_fromUtf8(":/freeseer/logo.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.setWindowIcon(icon) - - # Initialize geometry, to be used for restoring window positioning. - self.geometry = None - - self.dialog = None - self.audio_quality_layout = None - self.video_quality_layout = None - - self.mainWidget = ConfigToolWidget() - self.setCentralWidget(self.mainWidget) - - self.currentWidget = None - self.mainWidgetLayout = QtGui.QVBoxLayout() - self.mainWidget.rightPanelWidget.setLayout(self.mainWidgetLayout) - - # Load all ConfigTool Widgets - self.aboutWidget = AboutWidget() - self.generalWidget = GeneralWidget() - self.avWidget = AVWidget() - self.pluginWidget = PluginWidget() - - self.plugman = PluginManager(profile) - - # XXX: Nasty hack to let our singleton plugins access the parent window - # for retranslate. - for plugin in self.plugman.get_all_plugins(): - plugin.plugin_object.set_gui(self) - - # Custom Menu Items - self.actionSaveProfile = QtGui.QAction(self) - self.menuFile.insertAction(self.actionExit, self.actionSaveProfile) - - # - # --- Language Related - # - # Fill in the langauges combobox and load the default language - for language in self.languages: - translator = QtCore.QTranslator() # Create a translator to translate Language Display Text - translator.load(":/languages/%s" % language) - language_display_text = translator.translate("Translation", "Language Display Text") - self.generalWidget.languageComboBox.addItem(language_display_text, language) - - # Load default language. - actions = self.menuLanguage.actions() - for action in actions: - if action.data().toString() == self.config.default_language: - action.setChecked(True) - self.translate(action) - break - # --- End Language Related - - # connections - self.connect(self.actionSaveProfile, QtCore.SIGNAL('triggered()'), self.show_save_profile_dialog) - self.connect(self.mainWidget.closePushButton, QtCore.SIGNAL('clicked()'), self.close) - self.connect(self.mainWidget.optionsTreeWidget, QtCore.SIGNAL('itemSelectionChanged()'), self.change_option) - - # - # general tab connections - # - self.connect(self.generalWidget.languageComboBox, QtCore.SIGNAL('currentIndexChanged(int)'), self.set_default_language) - self.connect(self.generalWidget.autoHideCheckBox, QtCore.SIGNAL('toggled(bool)'), self.toggle_autohide) - self.connect(self.generalWidget.translateButton, QtCore.SIGNAL('clicked()'), self.open_translate_url) - self.connect(self.generalWidget.resetButton, QtCore.SIGNAL('clicked()'), self.confirm_reset) - # - # AV tab connections - # - self.connect(self.avWidget.audioGroupBox, QtCore.SIGNAL('toggled(bool)'), self.toggle_audiomixer_state) - self.connect(self.avWidget.audioMixerComboBox, QtCore.SIGNAL('activated(const QString&)'), self.change_audiomixer) - self.connect(self.avWidget.audioMixerSetupPushButton, QtCore.SIGNAL('clicked()'), self.setup_audio_mixer) - self.connect(self.avWidget.audioQualityComboBox, QtCore.SIGNAL('currentIndexChanged(int)'), self.change_audio_quality) - self.connect(self.avWidget.audioQualitySetupPushButton, QtCore.SIGNAL('clicked()'), self.setup_audio_quality) - self.connect(self.avWidget.videoGroupBox, QtCore.SIGNAL('toggled(bool)'), self.toggle_videomixer_state) - self.connect(self.avWidget.videoMixerComboBox, QtCore.SIGNAL('activated(const QString&)'), self.change_videomixer) - self.connect(self.avWidget.videoMixerSetupPushButton, QtCore.SIGNAL('clicked()'), self.setup_video_mixer) - self.connect(self.avWidget.videoQualityComboBox, QtCore.SIGNAL('currentIndexChanged(int)'), self.change_video_quality) - self.connect(self.avWidget.videoQualitySetupPushButton, QtCore.SIGNAL('clicked()'), self.setup_video_quality) - self.connect(self.avWidget.fileGroupBox, QtCore.SIGNAL('toggled(bool)'), self.toggle_record_to_file) - self.connect(self.avWidget.fileDirPushButton, QtCore.SIGNAL('clicked()'), self.browse_video_directory) - self.connect(self.avWidget.fileDirLineEdit, QtCore.SIGNAL('editingFinished()'), self.update_record_directory) - self.connect(self.avWidget.fileComboBox, QtCore.SIGNAL('activated(const QString&)'), self.change_file_format) - self.connect(self.avWidget.fileSetupPushButton, QtCore.SIGNAL('clicked()'), self.setup_file_format) - self.connect(self.avWidget.streamGroupBox, QtCore.SIGNAL('toggled(bool)'), self.toggle_record_to_stream) - self.connect(self.avWidget.streamComboBox, QtCore.SIGNAL('activated(const QString&)'), self.change_stream_format) - self.connect(self.avWidget.streamSetupPushButton, QtCore.SIGNAL('clicked()'), self.setup_stream_format) - # GUI Disabling/Enabling Connections - self.connect(self.avWidget.audioGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.audioMixerLabel.setEnabled) - self.connect(self.avWidget.audioGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.audioMixerComboBox.setEnabled) - self.connect(self.avWidget.audioGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.audioMixerSetupPushButton.setEnabled) - self.connect(self.avWidget.videoGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.videoMixerLabel.setEnabled) - self.connect(self.avWidget.videoGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.videoMixerComboBox.setEnabled) - self.connect(self.avWidget.videoGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.videoMixerSetupPushButton.setEnabled) - self.connect(self.avWidget.fileGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.fileLabel.setEnabled) - self.connect(self.avWidget.fileGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.fileComboBox.setEnabled) - self.connect(self.avWidget.fileGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.fileSetupPushButton.setEnabled) - self.connect(self.avWidget.streamGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.streamLabel.setEnabled) - self.connect(self.avWidget.streamGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.streamComboBox.setEnabled) - self.connect(self.avWidget.streamGroupBox, QtCore.SIGNAL("toggled(bool)"), self.avWidget.streamSetupPushButton.setEnabled) - - self.retranslate() - - # Start off with displaying the General Settings - items = self.mainWidget.optionsTreeWidget.findItems(self.generalString, QtCore.Qt.MatchExactly) - if len(items) > 0: - item = items[0] - self.mainWidget.optionsTreeWidget.setCurrentItem(item) - - ### - ### Translation - ### - - def retranslate(self): - self.setWindowTitle(self.app.translate("ConfigToolApp", "Freeseer ConfigTool")) - - # - # Reusable Strings - # - self.confirmResetDefaultsTitleString = self.app.translate("ConfigToolApp", "Freeseer") - self.confirmResetDefaultsQuestionString = self.app.translate( - "ConfigToolApp", - "Your Freeseer settings will be restored to their original defaults.") - # --- End Reusable Strings - - # - # Menu - # - self.saveProfileString = self.actionSaveProfile.setText(self.app.translate("ConfigToolApp", "Save Profile")) - - # - # ConfigToolWidget - # - self.aboutString = self.app.translate("ConfigToolApp", "About") - self.generalString = self.app.translate("ConfigToolApp", "General") - self.avString = self.app.translate("ConfigToolApp", "Recording") - self.pluginsString = self.app.translate("ConfigToolApp", "Plugins") - self.audioInputString = self.app.translate("ConfigToolApp", "AudioInput") - self.audioMixerString = self.app.translate("ConfigToolApp", "AudioMixer") - self.videoInputString = self.app.translate("ConfigToolApp", "VideoInput") - self.videoMixerString = self.app.translate("ConfigToolApp", "VideoMixer") - self.outputString = self.app.translate("ConfigToolApp", "Output") - - self.mainWidget.optionsTreeWidget.topLevelItem(0).setText(0, self.generalString) - self.mainWidget.optionsTreeWidget.topLevelItem(1).setText(0, self.avString) - self.mainWidget.optionsTreeWidget.topLevelItem(2).setText(0, self.pluginsString) - self.mainWidget.optionsTreeWidget.topLevelItem(3).setText(0, self.aboutString) - self.mainWidget.closePushButton.setText(self.app.translate("ConfigToolApp", "Close")) - # --- End ConfigToolWidget - - # - # GeneralWidget - # - self.generalWidget.languageGroupBox.setTitle(self.app.translate("ConfigToolApp", "Language")) - self.generalWidget.translateButton.setText(self.app.translate("ConfigToolApp", "Help us translate")) - self.generalWidget.appearanceGroupBox.setTitle(self.app.translate("ConfigToolApp", "Appearance")) - self.generalWidget.autoHideCheckBox.setText(self.app.translate("ConfigToolApp", "Auto-Hide to system tray on record")) - self.generalWidget.resetGroupBox.setTitle(self.app.translate("ConfigToolApp", "Reset")) - self.generalWidget.resetButton.setText(self.app.translate("ConfigToolApp", "Reset settings to defaults")) - # --- End GeneralWidget - - # - # AV Widget - # - self.avWidget.title.setText(u"{0} {1} {2}".format(u'

', self.app.translate("ConfigToolApp", "Recording"), u'

')) - - self.avWidget.audioGroupBox.setTitle(self.app.translate("ConfigToolApp", "Audio Input")) - self.avWidget.audioMixerLabel.setText(self.app.translate("ConfigToolApp", "Audio Mixer")) - self.avWidget.audioMixerSetupPushButton.setToolTip(self.app.translate("ConfigToolApp", "Setup")) - - self.avWidget.videoGroupBox.setTitle(self.app.translate("ConfigToolApp", "Video Input")) - self.avWidget.videoMixerLabel.setText(self.app.translate("ConfigToolApp", "Video Mixer")) - self.avWidget.videoMixerSetupPushButton.setToolTip(self.app.translate("ConfigToolApp", "Setup")) - - self.avWidget.fileGroupBox.setTitle(self.app.translate("ConfigToolApp", "Record to File")) - self.avWidget.fileDirLabel.setText(self.app.translate("ConfigToolApp", "Record Directory")) - self.avWidget.fileDirPushButton.setText(u"{}...".format(self.app.translate("ConfigToolApp", "Browse"))) - self.avWidget.fileLabel.setText(self.app.translate("ConfigToolApp", "File Format")) - self.avWidget.fileSetupPushButton.setToolTip(self.app.translate("ConfigToolApp", "Setup")) - - self.avWidget.streamGroupBox.setTitle(self.app.translate("ConfigToolApp", "Record to Stream")) - self.avWidget.streamLabel.setText(self.app.translate("ConfigToolApp", "Stream Format")) - self.avWidget.streamSetupPushButton.setToolTip(self.app.translate("ConfigToolApp", "Setup")) - # --- End AV Widget - - ### - ### Menu - ### - - def show_save_profile_dialog(self): - name, ok = QInputDialog().getText(self, "Save Profile", "Profile Name", QLineEdit.Normal) - - if ok: - if re.match('^[\w-]+$', name): - # TODO: This is a hack. Instead, there should be a option to - # copy the current profile or something. - pass - else: - QMessageBox.information(None, "Invalid name", "Invalid characters used. Only alphanumeric and dashes allowed.") - - ### - ### General - ### - - def change_option(self): - option = self.mainWidget.optionsTreeWidget.currentItem().text(0) - - if self.currentWidget is not None: - self.mainWidgetLayout.removeWidget(self.currentWidget) - self.currentWidget.hide() - - if option == self.aboutString: - self.load_about_widget() - elif option == self.generalString: - self.load_general_widget() - elif option == self.avString: - self.load_av_widget() - elif option == self.pluginsString: - self.load_plugins_widget() - else: - pass - - def load_about_widget(self): - """Loads AboutWidget onto the configuration tool""" - self.mainWidgetLayout.addWidget(self.aboutWidget) - self.currentWidget = self.aboutWidget - self.currentWidget.retranslate() - self.currentWidget.show() - - def load_general_widget(self): - self.mainWidgetLayout.addWidget(self.generalWidget) - self.currentWidget = self.generalWidget - self.currentWidget.show() - - # Load default language - i = self.generalWidget.languageComboBox.findData(self.config.default_language) - self.generalWidget.languageComboBox.setCurrentIndex(i) - - # Load Auto Hide Settings - if self.config.auto_hide: - self.generalWidget.autoHideCheckBox.setChecked(True) - else: - self.generalWidget.autoHideCheckBox.setChecked(False) - - def set_default_language(self, language): - language_file = str(self.generalWidget.languageComboBox.itemData(language).toString()) - self.config.default_language = language_file - self.config.save() - - def open_translate_url(self): - url = QtCore.QUrl("http://freeseer.readthedocs.org/en/latest/contribute/translation.html") - QtGui.QDesktopServices.openUrl(url) - - def confirm_reset(self): - """Presents a confirmation dialog to ask the user if they are sure they wish to reset all settings. - If confirmed, reset the settings in this profile to default - """ - confirm = QMessageBox.question(self, - self.confirmResetDefaultsTitleString, - self.confirmResetDefaultsQuestionString, - QMessageBox.Reset | QMessageBox.Cancel, - QMessageBox.Cancel) - - if confirm == QMessageBox.Reset: - self.config.set_defaults() - self.config.save() - self.load_general_widget() - - def toggle_autohide(self, state): - self.config.auto_hide = state - self.config.save() - - # Make recordapp to update it's config - # TODO: Surely there is a better way to do this - - ### - ### AV Related - ### - - def load_av_widget(self): - self.mainWidgetLayout.addWidget(self.avWidget) - self.currentWidget = self.avWidget - self.currentWidget.show() - - # - # Set up Quality - # - self.supports_video_quality = self.plugman.get_plugin_by_name(self.config.videomixer, "VideoMixer").plugin_object.supports_video_quality() - self.file_configurable = self.plugman.get_plugin_by_name(self.config.record_to_file_plugin, "Output").plugin_object.configurable - self.stream_configurable = self.plugman.get_plugin_by_name(self.config.record_to_stream_plugin, "Output").plugin_object.configurable - - self.refresh_quality_config() - self.avWidget.videoQualityComboBox.setCurrentIndex(self.config.video_quality) - self.avWidget.audioQualityComboBox.setCurrentIndex(self.config.audio_quality) - - if not self.supports_video_quality: - self.toggle_video_quality(False) - - # - # Set up Audio - # - if self.config.enable_audio_recording: - self.avWidget.audioGroupBox.setChecked(True) - else: - self.avWidget.audioGroupBox.setChecked(False) - self.avWidget.audioMixerComboBox.setEnabled(False) - self.avWidget.audioMixerSetupPushButton.setEnabled(False) - - n = 0 # Counter for finding Audio Mixer to set as current. - self.avWidget.audioMixerComboBox.clear() - plugins = self.plugman.get_audiomixer_plugins() - for plugin in plugins: - self.avWidget.audioMixerComboBox.addItem(plugin.plugin_object.get_name()) - if plugin.plugin_object.get_name() == self.config.audiomixer: - self.avWidget.audioMixerComboBox.setCurrentIndex(n) - n += 1 - - # - # Set up Video - # - if self.config.enable_video_recording: - self.avWidget.videoGroupBox.setChecked(True) - else: - self.avWidget.videoGroupBox.setChecked(False) - self.avWidget.videoMixerComboBox.setEnabled(False) - self.avWidget.videoMixerSetupPushButton.setEnabled(False) - - n = 0 # Counter for finding Video Mixer to set as current. - self.avWidget.videoMixerComboBox.clear() - plugins = self.plugman.get_videomixer_plugins() - for plugin in plugins: - self.avWidget.videoMixerComboBox.addItem(plugin.plugin_object.get_name()) - if plugin.plugin_object.get_name() == self.config.videomixer: - self.avWidget.videoMixerComboBox.setCurrentIndex(n) - n += 1 - - # Recording Directory Settings - self.avWidget.fileDirLineEdit.setText(self.config.videodir) - - # - # Set up File Format - # - if self.config.record_to_file: - self.avWidget.fileGroupBox.setChecked(True) - else: - self.avWidget.fileGroupBox.setChecked(False) - self.avWidget.fileComboBox.setEnabled(False) - self.avWidget.fileSetupPushButton.setEnabled(False) - - if not self.file_configurable: - self.avWidget.fileSetupPushButton.setEnabled(False) - - n = 0 # Counter for finding File Format to set as current - self.avWidget.fileComboBox.clear() - plugins = self.plugman.get_output_plugins() - for plugin in plugins: - if plugin.plugin_object.get_recordto() == IOutput.FILE: - self.avWidget.fileComboBox.addItem(plugin.plugin_object.get_name()) - if plugin.plugin_object.get_name() == self.config.record_to_file_plugin: - self.avWidget.fileComboBox.setCurrentIndex(n) - n += 1 - - # - # Set up Stream Format - # - if self.config.record_to_stream: - self.avWidget.streamGroupBox.setChecked(True) - else: - self.avWidget.streamGroupBox.setChecked(False) - self.avWidget.streamComboBox.setEnabled(False) - self.avWidget.streamSetupPushButton.setEnabled(False) - - if not self.stream_configurable: - self.avWidget.streamSetupPushButton.setEnabled(False) - - n = 0 # Counter for finding Stream Format to set as current - self.avWidget.streamComboBox.clear() - plugins = self.plugman.get_output_plugins() - for plugin in plugins: - if plugin.plugin_object.get_recordto() == IOutput.STREAM: - self.avWidget.streamComboBox.addItem(plugin.plugin_object.get_name()) - if plugin.plugin_object.get_name() == self.config.record_to_stream_plugin: - self.avWidget.streamComboBox.setCurrentIndex(n) - n += 1 - - def toggle_audiomixer_state(self, state): - self.config.enable_audio_recording = state - self.config.save() - - self.refresh_quality_config() - - def change_audiomixer(self, audiomixer): - self.config.audiomixer = audiomixer - self.config.save() - - def setup_audio_mixer(self): - mixer = str(self.avWidget.audioMixerComboBox.currentText()) - plugin = self.plugman.get_plugin_by_name(mixer, "AudioMixer") - plugin.plugin_object.get_dialog() - - def change_audio_quality(self, index): - """Used to change audio quality of output. - - If the quality is set to 'Custom' then the setup button for quality is enabled otherwise it is disabled. - """ - self.config.audio_quality = index - self.config.save() - - if self.config.audio_quality == Quality.CUSTOM: - self.avWidget.audioQualitySetupPushButton.setEnabled(True) - else: - self.avWidget.audioQualitySetupPushButton.setEnabled(False) - - def setup_audio_quality(self): - """Creates dialog to configure audio quality when quality is set to Custom""" - self.audio_quality_dialog = QtGui.QDialog(self) - - self.audio_quality_dialog_layout = QtGui.QGridLayout() - self.audio_quality_dialog_layout.setSizeConstraint(QtGui.QLayout.SetFixedSize) - self.audio_quality_dialog.setLayout(self.audio_quality_dialog_layout) - - self.audio_quality_dialog.closeButton = QtGui.QPushButton("Close") - self.connect(self.audio_quality_dialog.closeButton, QtCore.SIGNAL('clicked()'), self.audio_quality_dialog.close) - self.audio_quality_dialog.setWindowTitle("Audio Quality Setup") - self.audio_quality_dialog.setModal(True) - self.audio_quality_dialog.show() - - file_output_plugin = self.plugman.get_plugin_by_name(self.config.record_to_file_plugin, "Output") - stream_output_plugin = self.plugman.get_plugin_by_name(self.config.record_to_stream_plugin, "Output") - - file_configurable = file_output_plugin.plugin_object.configurable - stream_configurable = stream_output_plugin.plugin_object.configurable - - if file_configurable: - file_config_layout = file_output_plugin.plugin_object.get_audio_quality_layout() - self.audio_quality_dialog_layout.addWidget(QtGui.QLabel(u'File Output'), 0, 0, 1, 2, QtCore.Qt.AlignHCenter) - self.audio_quality_dialog_layout.addLayout(file_config_layout, 1, 0) - - if stream_configurable: - stream_config_layout = stream_output_plugin.plugin_object.get_audio_quality_layout() - column_count = self.audio_quality_dialog_layout.columnCount() - self.audio_quality_dialog_layout.addWidget(QtGui.QLabel(u'Stream Output'), 0, column_count, 1, 2, QtCore.Qt.AlignHCenter) - self.audio_quality_dialog_layout.addLayout(stream_config_layout, 1, column_count) - - self.audio_quality_dialog_layout.addWidget(self.audio_quality_dialog.closeButton, 2, 0, 1, self.audio_quality_dialog_layout.columnCount()) - - file_config_layout.itemAt(1).widget().setEnabled(self.config.record_to_file) - stream_config_layout.itemAt(1).widget().setEnabled(self.config.record_to_stream) - - def toggle_videomixer_state(self, state): - self.config.enable_video_recording = state - self.config.save() - - self.refresh_quality_config() - - def change_videomixer(self, videomixer): - self.config.videomixer = videomixer - self.config.save() - - def setup_video_mixer(self): - mixer = str(self.avWidget.videoMixerComboBox.currentText()) - plugin = self.plugman.get_plugin_by_name(mixer, "VideoMixer") - plugin.plugin_object.get_dialog() - - def change_video_quality(self, index): - """Used to change video quality of output. - - If the quality is set to 'Custom' then the setup button for quality is enabled otherwise it is disabled. - Calculations are done by multiplying a constant factor by the total number of pixels in the output. - """ - self.config.video_quality = index - self.config.save() - - if self.config.video_quality == Quality.CUSTOM: - self.avWidget.videoQualitySetupPushButton.setEnabled(True) - else: - self.avWidget.videoQualitySetupPushButton.setEnabled(False) - - def setup_video_quality(self): - """Creates dialog to configure video bitrate when quality is set to Custom""" - self.video_quality_dialog = QtGui.QDialog(self) - - self.video_quality_dialog_layout = QtGui.QGridLayout() - self.video_quality_dialog_layout.setSizeConstraint(QtGui.QLayout.SetFixedSize) - self.video_quality_dialog.setLayout(self.video_quality_dialog_layout) - - self.video_quality_dialog.closeButton = QtGui.QPushButton("Close") - self.connect(self.video_quality_dialog.closeButton, QtCore.SIGNAL('clicked()'), self.video_quality_dialog.close) - self.video_quality_dialog.setWindowTitle("Video Quality Setup") - self.video_quality_dialog.setModal(True) - self.video_quality_dialog.show() - - file_output_plugin = self.plugman.get_plugin_by_name(self.config.record_to_file_plugin, "Output") - stream_output_plugin = self.plugman.get_plugin_by_name(self.config.record_to_stream_plugin, "Output") - - file_configurable = file_output_plugin.plugin_object.configurable - stream_configurable = stream_output_plugin.plugin_object.configurable - - if file_configurable: - file_config_layout = file_output_plugin.plugin_object.get_video_quality_layout() - self.video_quality_dialog_layout.addWidget(QtGui.QLabel(u'File Output'), 0, 0, 1, 2, QtCore.Qt.AlignHCenter) - self.video_quality_dialog_layout.addLayout(file_config_layout, 1, 0) - - if stream_configurable: - stream_config_layout = stream_output_plugin.plugin_object.get_video_quality_layout() - column_count = self.video_quality_dialog_layout.columnCount() - self.video_quality_dialog_layout.addWidget(QtGui.QLabel(u'Stream Output'), 0, column_count, 1, 2, QtCore.Qt.AlignHCenter) - self.video_quality_dialog_layout.addLayout(stream_config_layout, 1, column_count) - - self.video_quality_dialog_layout.addWidget(self.video_quality_dialog.closeButton, 2, 0, 1, self.video_quality_dialog_layout.columnCount()) - - file_config_layout.itemAt(1).widget().setEnabled(self.config.record_to_file) - stream_config_layout.itemAt(1).widget().setEnabled(self.config.record_to_stream) - - def toggle_video_quality(self, enabled): - """Used by Video Mixer to disable quality options if video input is selected that does not support it.""" - self.supports_video_quality = enabled - self.avWidget.videoQualityComboBox.setEnabled(enabled) - - if enabled: - self.avWidget.videoQualityComboBox.setCurrentIndex(Quality.HIGH) - else: - self.avWidget.videoQualityComboBox.setCurrentIndex(Quality.CUSTOM) - - self.avWidget.videoQualitySetupPushButton.setEnabled(not enabled) - - def refresh_quality_config(self): - """Enable or disable quality options based on various variables""" - enabled = (self.config.record_to_file and self.file_configurable) or (self.config.record_to_stream and self.stream_configurable) - audio_enabled = self.config.enable_audio_recording and enabled - audio_configurable = self.config.audio_quality == Quality.CUSTOM - video_enabled = self.config.enable_video_recording and enabled and self.supports_video_quality - video_configurable = self.config.video_quality == Quality.CUSTOM - - self.avWidget.audioQualitySetupPushButton.setEnabled(audio_enabled and audio_configurable) - self.avWidget.audioQualityComboBox.setEnabled(audio_enabled) - self.avWidget.videoQualitySetupPushButton.setEnabled(video_enabled and video_configurable) - self.avWidget.videoQualityComboBox.setEnabled(video_enabled) - - def toggle_record_to_file(self, state): - self.config.record_to_file = state - self.config.save() - - self.refresh_quality_config() - - def browse_video_directory(self): - directory = self.avWidget.fileDirLineEdit.text() - - new_dir = QtGui.QFileDialog.getExistingDirectory(self, "Select Video Directory", directory) - if not new_dir: - new_dir = directory - - videodir = os.path.abspath(str(new_dir)) - self.avWidget.fileDirLineEdit.setText(videodir) - self.avWidget.fileDirLineEdit.emit(QtCore.SIGNAL("editingFinished()")) - - def update_record_directory(self): - self.config.videodir = str(self.avWidget.fileDirLineEdit.text()) - self.config.save() - - def change_file_format(self, format): - self.config.record_to_file_plugin = format - self.config.save() - - self.file_configurable = self.plugman.get_plugin_by_name(self.config.record_to_file_plugin, "Output").plugin_object.configurable - self.stream_configurable = self.plugman.get_plugin_by_name(self.config.record_to_stream_plugin, "Output").plugin_object.configurable - - self.avWidget.fileSetupPushButton.setEnabled(self.file_configurable) - - self.refresh_quality_config() - - def setup_file_format(self): - output = str(self.avWidget.fileComboBox.currentText()) - plugin = self.plugman.get_plugin_by_name(output, "Output") - plugin.plugin_object.get_dialog() - - def toggle_record_to_stream(self, state): - self.config.record_to_stream = state - self.config.save() - - self.refresh_quality_config() - - def change_stream_format(self, format): - self.config.record_to_stream_plugin = format - self.config.save() - - self.file_configurable = self.plugman.get_plugin_by_name(self.config.record_to_file_plugin, "Output").plugin_object.configurable - self.stream_configurable = self.plugman.get_plugin_by_name(self.config.record_to_stream_plugin, "Output").plugin_object.configurable - - self.avWidget.streamSetupPushButton.setEnabled(self.stream_configurable) - - self.refresh_quality_config() - - def setup_stream_format(self): - output = str(self.avWidget.streamComboBox.currentText()) - plugin = self.plugman.get_plugin_by_name(output, "Output") - plugin.plugin_object.get_dialog() - - ### - ### Plugin Loader Related - ### - - def load_plugins_widget(self): - self.mainWidgetLayout.addWidget(self.pluginWidget) - self.currentWidget = self.pluginWidget - self.currentWidget.show() - - if (self.currentWidget.list.topLevelItem(0) is None): - # Fill List - - # Audio Input Label - QtGui.QTreeWidgetItem(self.currentWidget.list) - self.currentWidget.list.topLevelItem(0).setText(0, "Audio Input") - self.add_plugins_to_list("AudioInput", self.currentWidget.list.topLevelItem(0)) - - # Audio Mixer Label - QtGui.QTreeWidgetItem(self.currentWidget.list) - self.currentWidget.list.topLevelItem(1).setText(0, "Audio Mixer") - self.add_plugins_to_list("AudioMixer", self.currentWidget.list.topLevelItem(1)) - - # Video Input Label - QtGui.QTreeWidgetItem(self.currentWidget.list) - self.currentWidget.list.topLevelItem(2).setText(0, "Video Input") - self.add_plugins_to_list("VideoInput", self.currentWidget.list.topLevelItem(2)) - - # Video Mixer Label - QtGui.QTreeWidgetItem(self.currentWidget.list) - self.currentWidget.list.topLevelItem(3).setText(0, "Video Mixer") - self.add_plugins_to_list("VideoMixer", self.currentWidget.list.topLevelItem(3)) - - # Output Label - QtGui.QTreeWidgetItem(self.currentWidget.list) - self.currentWidget.list.topLevelItem(4).setText(0, "Output") - self.add_plugins_to_list("Output", self.currentWidget.list.topLevelItem(4)) - - # Importer Label - QtGui.QTreeWidgetItem(self.currentWidget.list) - self.currentWidget.list.topLevelItem(5).setText(0, "Input") - self.add_plugins_to_list("Importer", self.currentWidget.list.topLevelItem(5)) - - self.currentWidget.list.expandAll() - - def add_plugins_to_list(self, plugin_type, parent): - plugins = self.get_plugins(plugin_type) - - for i, plugin in enumerate(plugins): - newItem = self.pluginWidget.getWidgetPlugin(plugin, plugin_type, self.plugman) - parent.addChild(newItem) - - def get_plugins(self, plugin_type): - """ - Returns a list of plugins of type - - Parameters: - plugin_type - type of plugins to get - - Returns: - list of plugins of type specified - """ - plugins = [] - - if plugin_type == "AudioInput": - plugins = self.plugman.get_audioinput_plugins() - elif plugin_type == "AudioMixer": - plugins = self.plugman.get_audiomixer_plugins() - elif plugin_type == "VideoInput": - plugins = self.plugman.get_videoinput_plugins() - elif plugin_type == "VideoMixer": - plugins = self.plugman.get_videomixer_plugins() - elif plugin_type == "Output": - plugins = self.plugman.get_output_plugins() - elif plugin_type == "Importer": - plugins = self.plugman.get_importer_plugins() - - return plugins - - def show_plugin_widget_dialog(self, widget, name): - """Shows the configuration dialog for a plugin.""" - self.last_dialog = self.dialog - self.dialog = QtGui.QDialog(self) - - self.dialog_layout = QtGui.QVBoxLayout() - self.dialog_layout.setSizeConstraint(QtGui.QLayout.SetFixedSize) - self.dialog.setLayout(self.dialog_layout) - self.dialog_layout.addWidget(widget) - - self.dialog.closeButton = QtGui.QPushButton("Close") - self.dialog_layout.addWidget(self.dialog.closeButton) - self.connect(self.dialog.closeButton, QtCore.SIGNAL('clicked()'), self.dialog.close) - self.dialog.setWindowTitle(u'{} Setup'.format(name)) - self.dialog.setModal(True) - self.dialog.show() - - def closeEvent(self, event): - log.info('Exiting configtool...') - self.geometry = self.saveGeometry() - event.accept() diff --git a/src/freeseer/frontend/controller/recording.py.bak b/src/freeseer/frontend/controller/recording.py.bak deleted file mode 100644 index 4c2e9eb3..00000000 --- a/src/freeseer/frontend/controller/recording.py.bak +++ /dev/null @@ -1,244 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2014 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -import functools -import os -import shelve - -from flask import Blueprint -from flask import request - -from freeseer import settings -from freeseer.framework.multimedia import Multimedia -from freeseer.framework.plugin import PluginManager -from freeseer.frontend.controller import app -from freeseer.frontend.controller import validate -from freeseer.frontend.controller.server import HTTPError -from freeseer.frontend.controller.server import ServerError -from freeseer.frontend.controller.server import http_response - -recording = Blueprint('recording', __name__) - -recording.form_schema = { - 'control_recording': { - 'type': 'object', - 'properties': { - 'command': { - 'enum': ['start', 'pause', 'stop'] - } - }, - 'required': ['command'] - }, - 'create_recording': { - 'type': 'object', - 'properties': { - 'filename': { - 'type': 'string', - 'pattern': '^\w+$' - } - }, - 'required': ['filename'] - } -} - - -def sync(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - response_dict = func(*args, **kwargs) - recording.media_info.sync() - return response_dict - return wrapper - - -@recording.before_app_first_request -def configure_recording(): - """Configures freeseer to record via REST server. - - Gets recording profiles and configuration and instantiates recording plugins. Then it restores any stored talks. - Runs upon first call to REST server. - """ - recording.profile = settings.profile_manager.get() - recording.config = recording.profile.get_config('freeseer.conf', settings.FreeseerConfig, - storage_args=['Global'], read_only=True) - recording.plugin_manager = PluginManager(recording.profile) - recording.storage_file = os.path.join(settings.configdir, app.storage_file_path) - - media_info = shelve.open(recording.storage_file, writeback=True) - - recording.next_id = 1 - recording.media_dict = {} - for key, value in media_info.iteritems(): - new_media = Multimedia(recording.config, recording.plugin_manager) - if value['null_multimeda']: - new_media.current_state = Multimedia.NULL - else: - # if null_multimeda is False, a video exists, set current_state to Multimedia.STOP - new_media.current_state = Multimedia.STOP - - media_id = int(key) - if media_id >= recording.next_id: - recording.next_id = media_id + 1 - - if new_media.current_state == Multimedia.NULL: - filename = value['filename'].split('.ogg')[0] - success, filename = new_media.load_backend(None, filename) - - if not success: - raise ServerError('Could not load multimedia backend') - - value['filename'] = filename - - value['filepath'] = new_media.plugman.get_plugin_by_name(new_media.config.record_to_file_plugin, "Output").plugin_object.location - - recording.media_dict[media_id] = new_media - - recording.media_info = media_info - recording.media_info.sync() - - -@recording.route('/recordings', methods=['GET']) -@http_response(200) -def get_all_recordings(): - """Returns list of all recordings.""" - return {'recordings': recording.media_dict.keys()} - - -@recording.route('/recordings/', methods=['GET']) -@http_response(200) -def get_specific_recording(recording_id): - """Returns specific recording by id.""" - - key = str(recording_id) - try: - retrieved_media_entry = recording.media_info[key] - except KeyError: - raise HTTPError(404, 'No recording with id "{}" was found'.format(recording_id)) - - current_state = recording.media_dict[recording_id].current_state - filename = retrieved_media_entry['filename'] - - try: - filesize = os.path.getsize(retrieved_media_entry['filepath']) - except OSError: - filesize = 'NA' - - return { - 'id': recording_id, - 'filename': filename, - 'filesize': filesize, - 'status': current_state, - } - - -@recording.route('/recordings/', methods=['PATCH']) -@http_response(200) -@sync -def control_recording(recording_id): - """Change the state of a recording.""" - - validate.validate_form(request.form, recording.form_schema['control_recording']) - - try: - retrieved_media = recording.media_dict[recording_id] - except KeyError: - raise HTTPError(404, 'No recording with id "{}" was found'.format(recording_id)) - - command = request.form['command'] - media_state = retrieved_media.current_state - - if command == 'start' and media_state in [Multimedia.NULL, Multimedia.PAUSE]: - retrieved_media.record() - elif command == 'pause' and media_state == Multimedia.RECORD: - retrieved_media.pause() - elif command == 'stop' and media_state in [Multimedia.RECORD, Multimedia.PAUSE]: - retrieved_media.stop() - else: - raise HTTPError(400, 'Command "{}" could not be performed'.format(command)) - - if media_state is not Multimedia.NULL: - key = str(recording_id) - recording.media_info[key]['null_multimeda'] = False - - return '' - - -@recording.route('/recordings', methods=['POST']) -@http_response(201) -@sync -def create_recording(): - """Initializes a recording and returns its id.""" - - validate.validate_form(request.form, recording.form_schema['create_recording']) - - new_filename = request.form['filename'] - new_media = Multimedia(recording.config, recording.plugin_manager) - success, filename = new_media.load_backend(None, new_filename) - - if not success: - raise HTTPError(500, 'Could not load multimedia backend') - - filepath = new_media.plugman.get_plugin_by_name(new_media.config.record_to_file_plugin, "Output").plugin_object.location - new_recording_id = recording.next_id - key = str(new_recording_id) - - recording.media_dict[new_recording_id] = new_media - recording.media_info[key] = { - 'filename': filename, - 'filepath': filepath, - 'null_multimeda': True, - } - recording.next_id = recording.next_id + 1 - recording.media_info.sync() - - return {'id': new_recording_id} - - -@recording.route('/recordings/', methods=['DELETE']) -@http_response(204) -@sync -def delete_recording(recording_id): - """Deletes a recording given an id.""" - try: - retrieved_media = recording.media_dict[recording_id] - except KeyError: - raise HTTPError(404, 'No recording with id "{}" was found'.format(recording_id)) - - key = str(recording_id) - retrieved_media_entry = recording.media_info[key] - - if retrieved_media.current_state in [Multimedia.RECORD, Multimedia.PAUSE]: - retrieved_media.stop() - - # Delete the file if it exists - try: - os.remove(retrieved_media_entry['filepath']) - except OSError: - pass - - del recording.media_dict[recording_id] - del recording.media_info[key] - recording.media_info.sync() - - return '' diff --git a/src/freeseer/frontend/qtcommon/AboutDialog.py.bak b/src/freeseer/frontend/qtcommon/AboutDialog.py.bak deleted file mode 100644 index a1852a5a..00000000 --- a/src/freeseer/frontend/qtcommon/AboutDialog.py.bak +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -from PyQt4.QtCore import QString -from PyQt4.QtCore import SIGNAL -from PyQt4.QtGui import QDialog -from PyQt4.QtGui import QDialogButtonBox -from PyQt4.QtGui import QIcon -from PyQt4.QtGui import QPixmap -from PyQt4.QtGui import QWidget -from PyQt4.QtGui import QGridLayout - -try: - _fromUtf8 = QString.fromUtf8 -except AttributeError: - _fromUtf8 = lambda s: s - -from freeseer.frontend.qtcommon import resource # noqa -from freeseer.frontend.qtcommon.AboutWidget import AboutWidget - -RECORD_BUTTON_ARTIST = u'Sekkyumu' -RECORD_BUTTON_LINK = u'http://sekkyumu.deviantart.com/' -HEADPHONES_ARTIST = u'Ben Fleming' -HEADPHONES_LINK = u'http://mediadesign.deviantart.com/' - - -class AboutDialog(QDialog): - """ - Common About Dialog for the Freeseer Project. This should be used for the - about dialog when including one in GUIs. - - - Grid Layout: - - Logo | About Infos - ------|------------- - | Close Button - """ - def __init__(self, parent=None): - QWidget.__init__(self, parent) - self.aboutWidget = AboutWidget() - - icon = QIcon() - icon.addPixmap(QPixmap(_fromUtf8(":/freeseer/logo.png")), QIcon.Normal, QIcon.Off) - self.setWindowIcon(icon) - - self.layout = QGridLayout() - self.setLayout(self.layout) - - self.layout.addWidget(self.aboutWidget) - - # Right Bottom corner of grid, Close Button - self.buttonBox = QDialogButtonBox() - self.closeButton = self.buttonBox.addButton("Close", QDialogButtonBox.AcceptRole) - self.layout.addWidget(self.buttonBox, 1, 1) - self.connect(self.closeButton, SIGNAL("clicked()"), self.close) - - self.setWindowTitle("About Freeseer") diff --git a/src/freeseer/frontend/qtcommon/AboutWidget.py.bak b/src/freeseer/frontend/qtcommon/AboutWidget.py.bak deleted file mode 100644 index 5178ecc6..00000000 --- a/src/freeseer/frontend/qtcommon/AboutWidget.py.bak +++ /dev/null @@ -1,176 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2014 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -''' -@author: Thanh Ha, Mia Kilborn -''' -from PyQt4.QtCore import QString -from PyQt4.QtCore import QTranslator -from PyQt4.QtCore import QUrl -from PyQt4.QtCore import SIGNAL -from PyQt4.QtCore import Qt -from PyQt4.QtGui import QDesktopServices -from PyQt4.QtGui import QGridLayout -from PyQt4.QtGui import QHBoxLayout -from PyQt4.QtGui import QIcon -from PyQt4.QtGui import QLabel -from PyQt4.QtGui import QPixmap -from PyQt4.QtGui import QPushButton -from PyQt4.QtGui import QSizePolicy - -try: - _fromUtf8 = QString.fromUtf8 -except AttributeError: - _fromUtf8 = lambda s: s - -from freeseer import NAME -from freeseer import URL -from freeseer import __version__ -from freeseer.frontend.qtcommon import resource # noqa -from freeseer.frontend.qtcommon.dpi_adapt_qtgui import QWidgetWithDpi - - -RECORD_BUTTON_ARTIST = u'Sekkyumu' -RECORD_BUTTON_LINK = u'http://sekkyumu.deviantart.com/' -HEADPHONES_ARTIST = u'Ben Fleming' -HEADPHONES_LINK = u'http://mediadesign.deviantart.com/' - - -class AboutWidget(QWidgetWithDpi): - """ Common About Dialog for the Freeseer Project. This should be used for the - about dialog when including one in GUIs. - - Layout: - Logo | About Infos - | Buttons - """ - - def __init__(self, parent=None): - super(AboutWidget, self).__init__(parent) - - self.current_language = "en_US" - self.uiTranslator = QTranslator() - self.uiTranslator.load(":/languages/tr_en_US.qm") - - self.fontSize = self.font().pixelSize() - self.fontUnit = "px" - if self.fontSize == -1: # Font is set as points, not pixels. - self.fontUnit = "pt" - self.fontSize = self.font().pointSize() - - icon = QIcon() - self.logoPixmap = QPixmap(_fromUtf8(":/freeseer/logo.png")) - icon.addPixmap(self.logoPixmap, QIcon.Normal, QIcon.Off) - self.setWindowIcon(icon) - - self.mainLayout = QGridLayout() - self.setLayout(self.mainLayout) - - # Logo - self.logo = QLabel("Logo") - # To offset the logo so that it's to the right of the title - self.logo.setStyleSheet("QLabel {{ margin-left: {} {} }}" - .format(self.set_width_with_dpi(90) + (self.fontSize * 2.5), self.fontUnit)) - self.logo.setPixmap(self.logoPixmap.scaledToHeight(self.set_height_with_dpi(80))) - self.mainLayout.addWidget(self.logo, 0, 0, Qt.AlignTop) - - # Info - self.aboutInfo = QLabel("About Info", openExternalLinks=True) - self.aboutInfo.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - self.aboutInfo.setWordWrap(True) - self.mainLayout.addWidget(self.aboutInfo, 0, 0) - - # Buttons - self.buttonsLayout = QHBoxLayout() - self.issueButton = QPushButton("Report an issue") - self.docsButton = QPushButton("Freeseer documentation") - self.contactButton = QPushButton("Contact us") - self.buttonsLayout.insertWidget(0, self.docsButton) - self.buttonsLayout.insertWidget(1, self.issueButton) - self.buttonsLayout.insertWidget(2, self.contactButton) - - self.mainLayout.addLayout(self.buttonsLayout, 2, 0) - - self.connect(self.docsButton, SIGNAL('clicked()'), self.openDocsUrl) - self.connect(self.issueButton, SIGNAL('clicked()'), self.openNewIssueUrl) - self.connect(self.contactButton, SIGNAL('clicked()'), self.openContactUrl) - - self.retranslate() - - def retranslate(self, language=None): - if language is not None: - self.current_language = language - - self.uiTranslator.load(":/languages/tr_%s.qm" % self.current_language) - - # - # Main Text - # - self.descriptionString = self.uiTranslator.translate("AboutDialog", - "Freeseer is a video capture utility capable of capturing presentations. It captures " - "video sources such as usb, firewire, or local desktop along with audio and mixes them " - "together to produce a video.") - self.copyrightString = self.uiTranslator.translate("AboutDialog", 'Copyright (C) 2014 The Free and ' - 'Open Source Software Learning Centre') - self.licenseTextString = self.uiTranslator.translate("AboutDialog", "Freeseer is licensed under the GPL " - "version 3. This software is provided 'as-is',without any express or implied warranty. In " - "no event will the authors be held liable for any damages arising from the use of this software.") - - self.aboutInfoString = u'

' + NAME.capitalize() + u'

' + \ - u'
' + self.uiTranslator.translate("AboutDialog", "Version") + \ - ": " + __version__ + u'' + \ - u'

' + self.descriptionString + u'

' + \ - u'

' + self.copyrightString + u'

' + \ - u'

' + URL + u'

' \ - u'

' + self.licenseTextString + u'

' \ - u'

' + self.uiTranslator.translate("AboutDialog", "Record button graphics by") + \ - u': ' + RECORD_BUTTON_ARTIST + u'

' \ - u'

' + self.uiTranslator.translate("AboutDialog", "Headphones graphics by") + \ - u': ' + HEADPHONES_ARTIST + u'


' - - self.aboutInfo.setText(self.aboutInfoString) - # --- End Main Text - - def openDocsUrl(self): - """Opens a link to the Freeseer online documentation""" - url = QUrl("http://freeseer.readthedocs.org") - QDesktopServices.openUrl(url) - - def openNewIssueUrl(self): - """Opens a link to the Freeseer new issue page""" - url = QUrl("https://github.com/Freeseer/freeseer/issues/new") - QDesktopServices.openUrl(url) - - def openContactUrl(self): - """Opens a link to Freeseer's contact information""" - url = QUrl("http://freeseer.readthedocs.org/en/latest/contact.html") - QDesktopServices.openUrl(url) - -if __name__ == "__main__": - import sys - from PyQt4.QtGui import QApplication - app = QApplication(sys.argv) - main = AboutWidget() - main.show() - sys.exit(app.exec_()) diff --git a/src/freeseer/frontend/record/RecordingController.py.bak b/src/freeseer/frontend/record/RecordingController.py.bak deleted file mode 100644 index 409c2cc2..00000000 --- a/src/freeseer/frontend/record/RecordingController.py.bak +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -from freeseer.framework.multimedia import Multimedia -from freeseer.framework.plugin import PluginManager - - -class RecordingController: - def __init__(self, profile, db, config, cli=False): - self.config = config - self.db = db - self.plugman = PluginManager(profile) - self.media = Multimedia(self.config, self.plugman, cli=cli) - - def set_window_id(self, window_id): - """Sets the Window ID which GStreamer should paint on""" - self.media.set_window_id(window_id) - - def set_audio_feedback_handler(self, audio_feedback_handler): - """Sets the handler for Audio Feedback levels""" - self.media.set_audio_feedback_handler(audio_feedback_handler) - - def record(self): - """Start Recording""" - self.media.record() - - def stop(self): - """Stop Recording""" - self.media.stop() - - def pause(self): - """Pause Recording""" - self.media.pause() - - def load_backend(self, presentation=None): - """Prepares the backend for recording""" - initialized, filename_for_frontend = self.media.load_backend(presentation) - if initialized: - return True, filename_for_frontend - else: - return False # Error something failed while loading the backend - - def print_talks(self): - query = self.db.get_talks() - - # Print the header - print("\n") - print("ID: Speaker - Title") - print("-------------------") - - while(query.next()): - talkid = unicode(query.value(0).toString()) - title = unicode(query.value(1).toString()) - speaker = unicode(query.value(2).toString()) - - print("{talkid}: {speaker} - {title}".format(talkid=talkid, speaker=speaker, title=title)) - - ### - ### Convenience commands - ### - def record_talk_id(self, talk_id): - """Records using a known Talk ID - - Returns True if recording is successfully started - Returns False if any issues arise - """ - presentation = self.db.get_presentation(talk_id) - if self.media.load_backend(presentation): - # Only record if the backend successfully loaded - # No need to print error on failure since load_backend already - # prints an error message - self.record() - return True - - else: - return False - - def record_filename(self, filename): - """Records to a specific filename - - Returns True if recording is successfully started - Returns False if any issues arise - """ - if self.media.load_backend(filename=filename): - self.record() - return True - - else: - return False diff --git a/src/freeseer/frontend/reporteditor/reporteditor.py.bak b/src/freeseer/frontend/reporteditor/reporteditor.py.bak deleted file mode 100644 index 1c14f127..00000000 --- a/src/freeseer/frontend/reporteditor/reporteditor.py.bak +++ /dev/null @@ -1,253 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2014 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -import logging - -from PyQt4.QtCore import QDateTime -from PyQt4.QtCore import QString -from PyQt4.QtCore import SIGNAL -from PyQt4.QtGui import QAction -from PyQt4.QtGui import QFileDialog -from PyQt4.QtGui import QHeaderView -from PyQt4.QtGui import QHBoxLayout -from PyQt4.QtGui import QIcon -from PyQt4.QtGui import QMessageBox -from PyQt4.QtGui import QPixmap -from PyQt4.QtGui import QWidget - -try: - _fromUtf8 = QString.fromUtf8 -except AttributeError: - _fromUtf8 = lambda s: s - -from freeseer.framework.presentation import Presentation -from freeseer.frontend.qtcommon.FreeseerApp import FreeseerApp - -from freeseer.frontend.reporteditor.ReportEditorWidget import ReportEditorWidget - -log = logging.getLogger(__name__) - - -class ReportEditorApp(FreeseerApp): - ''' - Freeseer report editor main gui class - ''' - - def __init__(self, config, db): - super(ReportEditorApp, self).__init__(config) - - self.db = db - - icon = QIcon() - icon.addPixmap(QPixmap(_fromUtf8(":/freeseer/logo.png")), QIcon.Normal, QIcon.Off) - self.setWindowIcon(icon) - self.resize(960, 400) - - self.mainWidget = QWidget() - self.mainLayout = QHBoxLayout() - self.mainWidget.setLayout(self.mainLayout) - self.setCentralWidget(self.mainWidget) - - self.editorWidget = ReportEditorWidget() - self.editorWidget.editor.setColumnHidden(5, True) - - self.mainLayout.addWidget(self.editorWidget) - - # Initialize geometry, to be used for restoring window positioning. - self.geometry = None - - # - # Setup Menubar - # - self.actionExportCsv = QAction(self) - self.actionExportCsv.setObjectName(_fromUtf8("actionExportCsv")) - - # Actions - self.menuFile.insertAction(self.actionExit, self.actionExportCsv) - # --- End Menubar - - # - # Report Editor Connections - # - - # Editor Widget - self.connect(self.editorWidget.removeButton, SIGNAL('clicked()'), self.remove_talk) - self.connect(self.editorWidget.clearButton, SIGNAL('clicked()'), self.confirm_reset) - self.connect(self.editorWidget.closeButton, SIGNAL('clicked()'), self.close) - - # Main Window Connections - self.connect(self.actionExportCsv, SIGNAL('triggered()'), self.export_reports_to_csv) - self.connect(self.editorWidget.editor, SIGNAL('clicked (const QModelIndex&)'), self.editorSelectionChanged) - - # Load default language - actions = self.menuLanguage.actions() - for action in actions: - if action.data().toString() == self.config.default_language: - action.setChecked(True) - self.translate(action) - break - - self.load_failures_model() - self.editorWidget.editor.resizeColumnsToContents() - self.editorWidget.editor.resizeRowsToContents() - - ### - ### Translation - ### - def retranslate(self): - self.setWindowTitle(self.app.translate("ReportEditorApp", "Freeseer Report Editor")) - - # - # Reusable Strings - # - self.confirmDBClearTitleString = self.app.translate("ReportEditorApp", "Clear Database") - self.confirmDBClearQuestionString = self.app.translate("ReportEditorApp", "Are you sure you want to clear the DB?") - self.selectFileString = self.app.translate("ReportEditorApp", "Select File") - # --- End Reusable Strings - - # - # Menubar - # - self.actionExportCsv.setText(self.app.translate("ReportEditorApp", "&Export to CSV")) - # --- End Menubar - - # - # EditorWidget - # - self.editorWidget.removeButton.setText(self.app.translate("ReportEditorApp", "Remove")) - self.editorWidget.clearButton.setText(self.app.translate("ReportEditorApp", "Clear")) - self.editorWidget.closeButton.setText(self.app.translate("ReportEditorApp", "Close")) - - self.editorWidget.titleLabel.setText(self.app.translate("ReportEditorApp", "Title:")) - self.editorWidget.speakerLabel.setText(self.app.translate("ReportEditorApp", "Speaker:")) - self.editorWidget.descriptionLabel.setText(self.app.translate("ReportEditorApp", "Description:")) - self.editorWidget.levelLabel.setText(self.app.translate("ReportEditorApp", "Level:")) - self.editorWidget.eventLabel.setText(self.app.translate("ReportEditorApp", "Event:")) - self.editorWidget.roomLabel.setText(self.app.translate("ReportEditorApp", "Room:")) - self.editorWidget.startTimeLabel.setText(self.app.translate("ReportEditorApp", "Start Time:")) - self.editorWidget.endTimeLabel.setText(self.app.translate("ReportEditorApp", "End Time:")) - # --- End EditorWidget - - def load_failures_model(self): - # Load Presentation Model - self.failureModel = self.db.get_failures_model() - editor = self.editorWidget.editor - editor.setModel(self.failureModel) - editor.horizontalHeader().setResizeMode(QHeaderView.Stretch) - - def hide_add_talk_widget(self): - self.editorWidget.setHidden(False) - self.addTalkWidget.setHidden(True) - - def add_talk(self): - date = self.addTalkWidget.dateEdit.date() - startTime = self.addTalkWidget.startTimeEdit.time() - datetime = QDateTime(date, startTime) # original "time" is now "startTime" - presentation = Presentation(unicode(self.addTalkWidget.titleLineEdit.text()), - unicode(self.addTalkWidget.presenterLineEdit.text()), - "", # description - "", # level - unicode(self.addTalkWidget.eventLineEdit.text()), - unicode(self.addTalkWidget.roomLineEdit.text()), - unicode(datetime.toString()), - unicode(self.addTalkWidget.endTimeEdit.text())) - - # Do not add talks if they are empty strings - if (len(presentation.title) == 0): - return - - self.db.insert_presentation(presentation) - - # cleanup - self.addTalkWidget.titleLineEdit.clear() - self.addTalkWidget.presenterLineEdit.clear() - - self.failureModel.select() - - self.hide_add_talk_widget() - - def remove_talk(self): - try: - row_clicked = self.editorWidget.editor.currentIndex().row() - except: - return - - self.failureModel.removeRow(row_clicked) - self.failureModel.select() - - def reset(self): - self.db.clear_report_db() - self.failureModel.select() - - def confirm_reset(self): - """ - Presents a confirmation dialog to ask the user if they are sure they - wish to remove the report database. - - If Yes call the reset() function. - """ - confirm = QMessageBox.question(self, - self.confirmDBClearTitleString, - self.confirmDBClearQuestionString, - QMessageBox.Yes | - QMessageBox.No, - QMessageBox.No) - - if confirm == QMessageBox.Yes: - self.reset() - - def closeEvent(self, event): - log.info('Exiting report editor...') - self.geometry = self.saveGeometry() - event.accept() - - def editorSelectionChanged(self, index): - talkId = self.failureModel.record(index.row()).value(0).toString() - self.updatePresentationInfo(talkId) - - def updatePresentationInfo(self, talkId): - p = self.db.get_presentation(talkId) - if p is not None: - self.editorWidget.titleLabel2.setText(p.title) - self.editorWidget.speakerLabel2.setText(p.speaker) - self.editorWidget.descriptionLabel2.setText(p.description) - self.editorWidget.levelLabel2.setText(p.level) - self.editorWidget.eventLabel2.setText(p.event) - self.editorWidget.roomLabel2.setText(p.room) - self.editorWidget.startTimeLabel2.setText(p.startTime) - self.editorWidget.endTimeLabel2.setText(p.endTime) - else: - self.editorWidget.titleLabel2.setText("Talk not found") - self.editorWidget.speakerLabel2.setText("Talk not found") - self.editorWidget.descriptionLabel2.setText("Talk not found") - self.editorWidget.levelLabel2.setText("Talk not found") - self.editorWidget.eventLabel2.setText("Talk not found") - self.editorWidget.roomLabel2.setText("Talk not found") - self.editorWidget.startTimeLabel2.setText("Talk not found") - self.editorWidget.endTimeLabel2.setText("Talk not found") - - def export_reports_to_csv(self): - fname = QFileDialog.getSaveFileName(self, self.selectFileString, "", "*.csv") - if fname: - self.db.export_reports_to_csv(fname) diff --git a/src/freeseer/frontend/talkeditor/talkeditor.py.bak b/src/freeseer/frontend/talkeditor/talkeditor.py.bak deleted file mode 100644 index 4f713bb2..00000000 --- a/src/freeseer/frontend/talkeditor/talkeditor.py.bak +++ /dev/null @@ -1,626 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2014 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -# python-libs -import logging - -# PyQt modules -from PyQt4.QtCore import SIGNAL -from PyQt4.QtCore import QPersistentModelIndex -from PyQt4.QtCore import QStringList -from PyQt4.QtCore import Qt -from PyQt4.QtGui import QAbstractItemView -from PyQt4.QtGui import QAction -from PyQt4.QtGui import QCompleter -from PyQt4.QtGui import QDataWidgetMapper -from PyQt4.QtGui import QFileDialog -from PyQt4.QtGui import QHeaderView -from PyQt4.QtGui import QIcon -from PyQt4.QtGui import QMessageBox -from PyQt4.QtGui import QPixmap -from PyQt4.QtGui import QSortFilterProxyModel -from PyQt4.QtGui import QTableView -from PyQt4.QtGui import QVBoxLayout -from PyQt4.QtGui import QWidget - -# Freeseer modules -from freeseer.framework.presentation import Presentation -from freeseer.frontend.qtcommon.FreeseerApp import FreeseerApp - -# TalkEditor modules -from freeseer.frontend.talkeditor.CommandButtons import CommandButtons -from freeseer.frontend.talkeditor.TalkDetailsWidget import TalkDetailsWidget -from freeseer.frontend.talkeditor.NewTalkWidget import NewTalkWidget -from freeseer.frontend.talkeditor.ImportTalksWidget import ImportTalksWidget - -log = logging.getLogger(__name__) - - -class TalkEditorApp(FreeseerApp): - '''Freeseer talk database editor main gui class''' - def __init__(self, config, db): - super(TalkEditorApp, self).__init__(config) - - self.db = db - - icon = QIcon() - icon.addPixmap(QPixmap(':/freeseer/logo.png'), QIcon.Normal, QIcon.Off) - self.setWindowIcon(icon) - self.resize(960, 600) - - # - # Setup Layout - # - self.mainWidget = QWidget() - self.mainLayout = QVBoxLayout() - self.mainWidget.setLayout(self.mainLayout) - self.setCentralWidget(self.mainWidget) - self.mainLayout.setAlignment(Qt.AlignTop) - - # Add custom widgets - self.commandButtons = CommandButtons() - self.tableView = QTableView() - self.tableView.setSortingEnabled(True) - self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) - self.talkDetailsWidget = TalkDetailsWidget() - self.importTalksWidget = ImportTalksWidget() - self.newTalkWidget = NewTalkWidget() - self.mainLayout.addWidget(self.importTalksWidget) - #self.mainLayout.addLayout(self.titleLayout) - self.mainLayout.addWidget(self.commandButtons) - self.mainLayout.addWidget(self.tableView) - self.mainLayout.addWidget(self.talkDetailsWidget) - self.mainLayout.addWidget(self.importTalksWidget) - # --- End Layout - - # Keep track of index of the most recently selected talk - self.currentTalkIndex = QPersistentModelIndex() - - # Prompt user to "Continue Editing", "Discard Changes" or "Save Changes" - self.savePromptBox = QMessageBox() - self.savePromptBox.setWindowTitle("Unsaved Changes Exist") - self.savePromptBox.setIcon(QMessageBox.Information) - self.savePromptBox.setText("The talk you were editing has unsaved changes.") - self.continueButton = self.savePromptBox.addButton("Continue Editing", QMessageBox.RejectRole) - self.discardButton = self.savePromptBox.addButton("Discard Changes", QMessageBox.DestructiveRole) - self.saveButton = self.savePromptBox.addButton("Save Changes", QMessageBox.AcceptRole) - self.savePromptBox.setDefaultButton(self.saveButton) - - # Initialize geometry, to be used for restoring window positioning. - self.geometry = None - - # - # Setup Menubar - # - self.actionExportCsv = QAction(self) - self.actionExportCsv.setObjectName('actionExportCsv') - self.actionRemoveAll = QAction(self) - self.actionRemoveAll.setObjectName('actionRemoveAll') - - # Actions - self.menuFile.insertAction(self.actionExit, self.actionExportCsv) - self.menuFile.insertAction(self.actionExit, self.actionRemoveAll) - # --- End Menubar - - # - # TableView Connections - # - self.connect(self.tableView, SIGNAL('activated(const QModelIndex)'), self.click_talk) - self.connect(self.tableView, SIGNAL('selected(const QModelIndex)'), self.click_talk) - self.connect(self.tableView, SIGNAL('clicked(const QModelIndex)'), self.click_talk) - - # Import Widget - self.connect(self.importTalksWidget.csvRadioButton, SIGNAL('toggled(bool)'), self.toggle_import) - self.connect(self.importTalksWidget.importButton, SIGNAL('clicked()'), self.import_talks) - self.connect(self.importTalksWidget.cancelButton, SIGNAL('clicked()'), self.hide_import_talks_widget) - self.importTalksWidget.setHidden(True) - self.connect(self.importTalksWidget.csvFileSelectButton, SIGNAL('clicked()'), self.csv_file_select) - self.connect(self.importTalksWidget.csvLineEdit, SIGNAL('returnPressed()'), - self.importTalksWidget.importButton.click) - self.connect(self.importTalksWidget.rssLineEdit, SIGNAL('returnPressed()'), - self.importTalksWidget.importButton.click) - self.connect(self.actionExportCsv, SIGNAL('triggered()'), self.export_talks_to_csv) - self.connect(self.actionRemoveAll, SIGNAL('triggered()'), self.confirm_reset) - - # Command Buttons - self.connect(self.commandButtons.addButton, SIGNAL('clicked()'), self.click_add_button) - self.connect(self.commandButtons.removeButton, SIGNAL('clicked()'), self.remove_talk) - self.connect(self.commandButtons.removeAllButton, SIGNAL('clicked()'), self.confirm_reset) - self.connect(self.commandButtons.importButton, SIGNAL('clicked()'), self.show_import_talks_widget) - self.connect(self.commandButtons.exportButton, SIGNAL('clicked()'), self.export_talks_to_csv) - self.connect(self.commandButtons.searchButton, SIGNAL('clicked()'), self.search_talks) - self.connect(self.commandButtons.searchLineEdit, SIGNAL('textEdited(QString)'), self.search_talks) - self.connect(self.commandButtons.searchLineEdit, SIGNAL('returnPressed()'), self.search_talks) - - # Talk Details Buttons - self.connect(self.talkDetailsWidget.saveButton, SIGNAL('clicked()'), self.update_talk) - - # Talk Details Widget - self.connect(self.talkDetailsWidget.titleLineEdit, SIGNAL('textEdited(const QString)'), self.enable_save) - self.connect(self.talkDetailsWidget.presenterLineEdit, SIGNAL('textEdited(const QString)'), self.enable_save) - self.connect(self.talkDetailsWidget.categoryLineEdit, SIGNAL('textEdited(const QString)'), self.enable_save) - self.connect(self.talkDetailsWidget.eventLineEdit, SIGNAL('textEdited(const QString)'), self.enable_save) - self.connect(self.talkDetailsWidget.roomLineEdit, SIGNAL('textEdited(const QString)'), self.enable_save) - self.connect(self.talkDetailsWidget.descriptionTextEdit, SIGNAL('modificationChanged(bool)'), self.enable_save) - self.connect(self.talkDetailsWidget.dateEdit, SIGNAL('dateChanged(const QDate)'), self.enable_save) - self.connect(self.talkDetailsWidget.startTimeEdit, SIGNAL('timeChanged(const QTime)'), self.enable_save) - self.connect(self.talkDetailsWidget.endTimeEdit, SIGNAL('timeChanged(const QTime)'), self.enable_save) - - # New Talk Widget - self.newTalkWidget.connect(self.newTalkWidget.addButton, SIGNAL('clicked()'), self.add_talk) - self.newTalkWidget.connect(self.newTalkWidget.cancelButton, SIGNAL('clicked()'), self.newTalkWidget.reject) - - # Load default language - actions = self.menuLanguage.actions() - for action in actions: - if action.data().toString() == self.config.default_language: - action.setChecked(True) - self.translate(action) - break - - # Load Talk Database - self.load_presentations_model() - - # Setup Autocompletion - self.update_autocomplete_fields() - - self.talkDetailsWidget.saveButton.setEnabled(False) - - # Select first item - #self.tableView.setCurrentIndex(self.proxy.index(0,0)) - #self.talk_selected(self.proxy.index(0,0)) - - # - # Translation - # - def retranslate(self): - self.setWindowTitle(self.app.translate("TalkEditorApp", "Freeseer Talk Editor")) - - # - # Reusable Strings - # - self.confirmDBClearTitleString = self.app.translate("TalkEditorApp", "Remove All Talks from Database") - self.confirmDBClearQuestionString = self.app.translate("TalkEditorApp", - "Are you sure you want to clear the DB?") - self.confirmTalkDetailsClearTitleString = self.app.translate("TalkEditorApp", "Unsaved Data") - self.confirmTalkDetailsClearQuestionString = self.app.translate("TalkEditorApp", - "Unsaved talk details will be lost. Continue?") - # --- End Reusable Strings - - # - # Menubar - # - self.actionExportCsv.setText(self.app.translate("TalkEditorApp", "&Export to CSV")) - self.actionRemoveAll.setText(self.app.translate("TalkEditorApp", "&Remove All Talks")) - - # --- End Menubar - - # - # TalkDetailsWidget - # - self.talkDetailsWidget.titleLabel.setText(self.app.translate("TalkEditorApp", "Title")) - self.talkDetailsWidget.presenterLabel.setText(self.app.translate("TalkEditorApp", "Presenter")) - self.talkDetailsWidget.categoryLabel.setText(self.app.translate("TalkEditorApp", "Category")) - self.talkDetailsWidget.eventLabel.setText(self.app.translate("TalkEditorApp", "Event")) - self.talkDetailsWidget.roomLabel.setText(self.app.translate("TalkEditorApp", "Room")) - self.talkDetailsWidget.dateLabel.setText(self.app.translate("TalkEditorApp", "Date")) - self.talkDetailsWidget.startTimeLabel.setText(self.app.translate("TalkEditorApp", "Start Time")) - self.talkDetailsWidget.endTimeLabel.setText(self.app.translate("TalkEditorApp", "End Time")) - # --- End TalkDetailsWidget - - # - # Import Talks Widget Translations - # - self.importTalksWidget.rssRadioButton.setText(self.app.translate("TalkEditorApp", "RSS URL")) - self.importTalksWidget.csvRadioButton.setText(self.app.translate("TalkEditorApp", "CSV File")) - self.importTalksWidget.importButton.setText(self.app.translate("TalkEditorApp", "Import")) - # --- End Talks Widget Translations - - # - # Command Button Translations\ - # - self.commandButtons.importButton.setText(self.app.translate("TalkEditorApp", "Import")) - self.commandButtons.exportButton.setText(self.app.translate("TalkEditorApp", "Export")) - self.commandButtons.addButton.setText(self.app.translate("TalkEditorApp", "Add New Talk")) - self.commandButtons.removeButton.setText(self.app.translate("TalkEditorApp", "Remove")) - self.commandButtons.removeAllButton.setText(self.app.translate("TalkEditorApp", "Remove All")) - # --- End Command Butotn Translations - - # - # Search Widget Translations - # - self.commandButtons.searchButton.setText(self.app.translate("TalkEditorApp", "Search")) - # --- End Command Button Translations - - def load_presentations_model(self): - # Load Presentation Model - self.presentationModel = self.db.get_presentations_model() - self.proxy = QSortFilterProxyModel() - self.proxy.setSourceModel(self.presentationModel) - self.tableView.setModel(self.proxy) - self.proxy.setFilterCaseSensitivity(Qt.CaseInsensitive) - - # Fill table whitespace. - self.tableView.horizontalHeader().setStretchLastSection(False) - for i in range(2, self.tableView.horizontalHeader().count() + 1): - self.tableView.horizontalHeader().resizeSection(i, self.set_width_with_dpi(80)) - self.tableView.horizontalHeader().setResizeMode(1, QHeaderView.Stretch) - - # Hide the ID field - self.tableView.setColumnHidden(0, True) - - # Map data to widgets - self.mapper = QDataWidgetMapper() - self.mapper.setModel(self.proxy) - self.mapper.setSubmitPolicy(QDataWidgetMapper.ManualSubmit) - self.mapper.addMapping(self.talkDetailsWidget.titleLineEdit, 1) - self.mapper.addMapping(self.talkDetailsWidget.presenterLineEdit, 2) - self.mapper.addMapping(self.talkDetailsWidget.categoryLineEdit, 4) - self.mapper.addMapping(self.talkDetailsWidget.eventLineEdit, 5) - self.mapper.addMapping(self.talkDetailsWidget.roomLineEdit, 6) - self.mapper.addMapping(self.talkDetailsWidget.descriptionTextEdit, 3) - self.mapper.addMapping(self.talkDetailsWidget.dateEdit, 7) - self.mapper.addMapping(self.talkDetailsWidget.startTimeEdit, 8) - self.mapper.addMapping(self.talkDetailsWidget.endTimeEdit, 9) - - # Load StringLists - self.titleList = QStringList(self.db.get_string_list("Title")) - #self.speakerList = QStringList(self.db.get_speaker_list()) - #self.categoryList = QStringList(self.db.get_category_list()) - #self.eventList = QStringList(self.db.get_event_list()) - #self.roomList = QStringList(self.db.get_room_list()) - - #Disble input - self.talkDetailsWidget.disable_input_fields() - - def search_talks(self): - # The default value is 0. If the value is -1, the keys will be read from all columns. - self.proxy.setFilterKeyColumn(-1) - self.proxy.setFilterFixedString(self.commandButtons.searchLineEdit.text()) - - def show_save_prompt(self): - """Prompts the user to save or discard changes, or continue editing.""" - self.savePromptBox.exec_() - self.savePromptBox.setDefaultButton(self.saveButton) - return self.savePromptBox.clickedButton() - - def click_talk(self, model): - """Warns user if there are unsaved changes, and selects talk clicked by the user.""" - log.info("Selecting row %d", model.row()) - modelRow = model.row() - if self.unsaved_details_exist(): - log.info("Unsaved changes exist in row %d", self.currentTalkIndex.row()) - confirm = self.show_save_prompt() - if confirm == self.saveButton: - log.info("Saving changes in row %d...", self.currentTalkIndex.row()) - self.tableView.selectRow(self.currentTalkIndex.row()) - self.update_talk() - newModel = self.tableView.currentIndex().sibling(modelRow, 0) - self.select_talk(newModel) - elif confirm == self.discardButton: - log.info("Discarding changes in row %d...", self.currentTalkIndex.row()) - self.talk_selected(model) - else: - log.info("Continue editing row %d", self.currentTalkIndex.row()) - self.tableView.selectRow(self.currentTalkIndex.row()) - else: - self.talk_selected(model) - - def click_add_button(self): - """Warns user if there are unsaved changes, and shows the New Talk window.""" - if self.unsaved_details_exist(): - log.info("Unsaved changes exist in row %d", self.currentTalkIndex.row()) - confirm = self.show_save_prompt() - if confirm == self.saveButton: - log.info("Saving changes in row %d...", self.currentTalkIndex.row()) - self.update_talk() - self.show_new_talk_popup() - elif confirm == self.discardButton: - log.info("Discarding changes in row %d...", self.currentTalkIndex.row()) - # Ensure that changes are discarded - self.talk_selected(self.currentTalkIndex) - self.show_new_talk_popup() - else: - log.info("Continue editing row %d", self.currentTalkIndex.row()) - else: - self.show_new_talk_popup() - - def talk_selected(self, model): - self.mapper.setCurrentIndex(model.row()) - self.talkDetailsWidget.enable_input_fields() - self.talkDetailsWidget.saveButton.setEnabled(False) - self.currentTalkIndex = QPersistentModelIndex(model) - - def toggle_import(self): - if self.importTalksWidget.csvRadioButton.isChecked(): - self.importTalksWidget.csvLineEdit.setEnabled(True) - self.importTalksWidget.csvFileSelectButton.setEnabled(True) - self.importTalksWidget.rssLineEdit.setEnabled(False) - else: - self.importTalksWidget.csvLineEdit.setEnabled(False) - self.importTalksWidget.csvFileSelectButton.setEnabled(False) - self.importTalksWidget.rssLineEdit.setEnabled(True) - - def show_import_talks_widget(self): - self.commandButtons.setHidden(True) - self.tableView.setHidden(True) - self.talkDetailsWidget.setHidden(True) - self.importTalksWidget.setHidden(False) - - def hide_import_talks_widget(self): - self.commandButtons.setHidden(False) - self.tableView.setHidden(False) - self.talkDetailsWidget.setHidden(False) - self.importTalksWidget.setHidden(True) - - def add_talk(self): - """Adds a new talk to the database using data from the NewTalkWidget input fields""" - presentation = self.create_presentation(self.newTalkWidget.talkDetailsWidget) - - if presentation: - self.db.insert_presentation(presentation) - self.newTalkWidget.accept() # Close the dialog - - def update_talk(self): - """Updates the currently selected talk using data from the TalkEditorApp input fields""" - selected_talk = self.tableView.currentIndex() - if selected_talk.row() >= 0: # The tableView index begins at 0 and is -1 by default - talk_id = selected_talk.sibling(selected_talk.row(), 0).data().toString() - presentation = self.create_presentation(self.talkDetailsWidget) - - if presentation: - self.db.update_presentation(talk_id, presentation) - self.apply_changes(selected_talk) - self.talkDetailsWidget.saveButton.setEnabled(False) - - def create_presentation(self, talkDetailsWidget): - """Creates and returns an instance of Presentation using data from the input fields""" - date = talkDetailsWidget.dateEdit.date() - startTime = talkDetailsWidget.startTimeEdit.time() - endTime = talkDetailsWidget.endTimeEdit.time() - - title = unicode(talkDetailsWidget.titleLineEdit.text()).strip() - if title: - return Presentation( - unicode(talkDetailsWidget.titleLineEdit.text()).strip(), - unicode(talkDetailsWidget.presenterLineEdit.text()).strip(), - unicode(talkDetailsWidget.descriptionTextEdit.toPlainText()).strip(), - unicode(talkDetailsWidget.categoryLineEdit.text()).strip(), - unicode(talkDetailsWidget.eventLineEdit.text()).strip(), - unicode(talkDetailsWidget.roomLineEdit.text()).strip(), - unicode(date.toString(Qt.ISODate)), - unicode(startTime.toString(Qt.ISODate)), - unicode(endTime.toString(Qt.ISODate))) - - def show_new_talk_popup(self): - """Displays a modal dialog with a talk details view - - When Add is selected, a new talk is added to the database using the input field data. - When Cancel is selected, no talk is added. - """ - log.info('Opening Add Talk window...') - self.clear_new_talk_fields() - self.remove_new_talk_placeholder_text() - self.newTalkWidget.talkDetailsWidget.titleLineEdit.setFocus() - if self.newTalkWidget.exec_() == 1: - self.apply_changes() - self.talkDetailsWidget.disable_input_fields() - else: - log.info('No talk added...') - - def apply_changes(self, updated_talk=None): - """Repopulates the model to display the effective changes - - Updates the autocomplete fields. - Displays the updated model in the table view, and selects the newly updated/added talk. - """ - self.presentationModel.select() - self.select_talk(updated_talk) - self.update_autocomplete_fields() - - def select_talk(self, talk=None): - """Selects the given talk in the table view - - If no talk is given, the last row in the table view is selected. - """ - if talk: - row = talk.row() - column = talk.column() - else: - row = self.presentationModel.rowCount() - 1 # Select last row - column = 0 - - self.tableView.selectRow(row) - self.tableView.setCurrentIndex(self.proxy.index(row, column)) - self.talk_selected(self.proxy.index(row, column)) - - def remove_talk(self): - try: - rows_selected = self.tableView.selectionModel().selectedRows() - except: - return - - # Reversed because rows in list change position once row is removed - for row in reversed(rows_selected): - self.presentationModel.removeRow(row.row()) - self.talkDetailsWidget.clear_input_fields() - self.talkDetailsWidget.disable_input_fields() - - def load_talk(self): - try: - self.tableView.currentIndex().row() - except: - return - - self.mapper.addMapping(self.talkDetailsWidget.roomLineEdit, 6) - self.presentationModel.select() - - def reset(self): - self.db.clear_database() - self.presentationModel.select() - self.talkDetailsWidget.clear_input_fields() - self.talkDetailsWidget.disable_input_fields() - - def confirm_reset(self): - """Presents a confirmation dialog to ask the user if they are sure they wish to remove the talk database. - If Yes call the reset() function""" - confirm = QMessageBox.question(self, - self.confirmDBClearTitleString, - self.confirmDBClearQuestionString, - QMessageBox.Yes | - QMessageBox.No, - QMessageBox.No) - - if confirm == QMessageBox.Yes: - self.reset() - - def add_talks_from_rss(self): - rss_url = unicode(self.importTalksWidget.rssLineEdit.text()) - if rss_url: - self.db.add_talks_from_rss(rss_url) - self.presentationModel.select() - self.hide_import_talks_widget() - else: - error = QMessageBox() - error.setText("Please enter a RSS URL") - error.exec_() - - def closeEvent(self, event): - if self.unsaved_details_exist(): - log.info("Unsaved changes exist in row %d", self.currentTalkIndex.row()) - confirm = self.show_save_prompt() - if confirm == self.saveButton: - log.info("Saving changes in row %d...", self.currentTalkIndex.row()) - self.update_talk() - log.info('Exiting talk database editor...') - self.geometry = self.saveGeometry() - event.accept() - elif confirm == self.discardButton: - log.info("Discarding changes in row %d...", self.currentTalkIndex.row()) - # Ensure that changes are discarded - self.talk_selected(self.currentTalkIndex) - log.info('Exiting talk database editor...') - self.geometry = self.saveGeometry() - event.accept() - else: - log.info("Continue editing row %d", self.currentTalkIndex.row()) - event.ignore() - else: - log.info('Exiting talk database editor...') - self.geometry = self.saveGeometry() - event.accept() - - def csv_file_select(self): - fname = QFileDialog.getOpenFileName( - self, 'Select file', "", "*.csv") - if fname: - self.importTalksWidget.csvLineEdit.setText(fname) - - def add_talks_from_csv(self): - fname = self.importTalksWidget.csvLineEdit.text() - - if fname: - self.db.add_talks_from_csv(fname) - self.presentationModel.select() - self.hide_import_talks_widget() - else: - error = QMessageBox() - error.setText("Please select a file") - error.exec_() - - def import_talks(self): - if self.importTalksWidget.csvRadioButton.isChecked(): - self.add_talks_from_csv() - else: - self.add_talks_from_rss() - - self.update_autocomplete_fields() - - def export_talks_to_csv(self): - fname = QFileDialog.getSaveFileName(self, 'Select file', "", "*.csv") - if fname: - self.db.export_talks_to_csv(fname) - - def update_autocomplete_fields(self): - self.titleList = QStringList(self.db.get_string_list("Title")) - self.speakerList = QStringList(self.db.get_string_list("Speaker")) - self.categoryList = QStringList(self.db.get_string_list("Category")) - self.eventList = QStringList(self.db.get_string_list("Event")) - self.roomList = QStringList(self.db.get_string_list("Room")) - - self.titleCompleter = QCompleter(self.titleList) - self.titleCompleter.setCaseSensitivity(Qt.CaseInsensitive) - self.speakerCompleter = QCompleter(self.speakerList) - self.speakerCompleter.setCaseSensitivity(Qt.CaseInsensitive) - self.categoryCompleter = QCompleter(self.categoryList) - self.categoryCompleter.setCaseSensitivity(Qt.CaseInsensitive) - self.eventCompleter = QCompleter(self.eventList) - self.eventCompleter.setCaseSensitivity(Qt.CaseInsensitive) - self.roomCompleter = QCompleter(self.roomList) - self.roomCompleter.setCaseSensitivity(Qt.CaseInsensitive) - - self.talkDetailsWidget.titleLineEdit.setCompleter(self.titleCompleter) - self.talkDetailsWidget.presenterLineEdit.setCompleter(self.speakerCompleter) - self.talkDetailsWidget.categoryLineEdit.setCompleter(self.categoryCompleter) - self.talkDetailsWidget.eventLineEdit.setCompleter(self.eventCompleter) - self.talkDetailsWidget.roomLineEdit.setCompleter(self.roomCompleter) - - def are_fields_enabled(self): - return (self.talkDetailsWidget.titleLineEdit.isEnabled() and - self.talkDetailsWidget.presenterLineEdit.isEnabled() and - self.talkDetailsWidget.categoryLineEdit.isEnabled() and - self.talkDetailsWidget.eventLineEdit.isEnabled() and - self.talkDetailsWidget.roomLineEdit.isEnabled() and - self.talkDetailsWidget.dateEdit.isEnabled() and - self.talkDetailsWidget.startTimeEdit.isEnabled() and - self.talkDetailsWidget.endTimeEdit.isEnabled()) - - def unsaved_details_exist(self): - """Checks if changes have been made to new/existing talk details - - Looks for text in the input fields and check the enabled state of the Save Talk button - If the Save Talk button is enabled, the input fields contain modified values - """ - return (self.talkDetailsWidget.saveButton.isEnabled() and - (self.talkDetailsWidget.titleLineEdit.text() or - self.talkDetailsWidget.presenterLineEdit.text() or - self.talkDetailsWidget.categoryLineEdit.text() or - self.talkDetailsWidget.descriptionTextEdit.toPlainText())) - - def enable_save(self): - self.talkDetailsWidget.saveButton.setEnabled(True) - - def clear_new_talk_fields(self): - """Removes existing data from all NewTalkWidget fields except event, room, date and time""" - self.newTalkWidget.talkDetailsWidget.titleLineEdit.clear() - self.newTalkWidget.talkDetailsWidget.presenterLineEdit.clear() - self.newTalkWidget.talkDetailsWidget.descriptionTextEdit.clear() - self.newTalkWidget.talkDetailsWidget.categoryLineEdit.clear() - - def remove_new_talk_placeholder_text(self): - """Removes placeholder text in NewTalkWidget originally set by TalkDetailsWidget""" - self.newTalkWidget.talkDetailsWidget.titleLineEdit.setPlaceholderText("") - self.newTalkWidget.talkDetailsWidget.presenterLineEdit.setPlaceholderText("") - self.newTalkWidget.talkDetailsWidget.categoryLineEdit.setPlaceholderText("") - self.newTalkWidget.talkDetailsWidget.eventLineEdit.setPlaceholderText("") - self.newTalkWidget.talkDetailsWidget.roomLineEdit.setPlaceholderText("") diff --git a/src/freeseer/frontend/upload/youtube.py.bak b/src/freeseer/frontend/upload/youtube.py.bak deleted file mode 100644 index 682b8517..00000000 --- a/src/freeseer/frontend/upload/youtube.py.bak +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - - -import os - -from freeseer import settings -from freeseer.framework.youtube import Response -from freeseer.framework.youtube import YoutubeService - - -def get_defaults(): - """Retrieve the defaults value for client_secrets, video folder, etc. - - Returns: - dictionary of default values which are: - client_secrets: ~//client_secrets.json - oauth2_token: ~//oauth2_token.json - video_directory: ~/Videos - """ - profile = settings.profile_manager.get("default") - config = profile.get_config('freeseer.conf', settings.FreeseerConfig, storage_args=['Global'], read_only=True) - return { - "video_directory": config.videodir, - "oauth2_token": os.path.join(settings.configdir, "oauth2_token.json"), - "client_secrets": os.path.join(settings.configdir, "client_secrets.json") - } - - -def handle_response(response_code, response): - """Process the response from the Youtube API""" - if response_code is Response.SUCCESS: - print("The file was successfully uploaded with video id: {}".format(response['id'])) - elif response_code is Response.UNEXPECTED_FAILURE: - print("The file failed to upload with unexpected response: {}".format(response)) - elif response_code is Response.UNRETRIABLE_ERROR: - print("An unretriable HTTP error {} occurred:\n{}".format(response['status'], response['content'])) - elif response_code is Response.MAX_RETRIES_REACHED: - print("The maximum number of retries has been reached") - elif response_code is Response.ACCESS_TOKEN_ERROR: - print("The access token has expired or been revoked, please run python -m freeseer config youtube") - - -def gather_videos(files): - """Gather all valid videos into a set for upload""" - # Because we are using a set, no duplicates will be present - videos = set() - for item in files: - # Crawl subfolders - if os.path.isdir(item): - for root, _, filenames in os.walk(item): - for filename in filenames: - filepath = os.path.join(root, filename) - # Check if its a video - if YoutubeService.valid_video_file(filepath): - videos.add(filepath) - # If it exists it is a single file, check if its a video - elif os.path.exists(item) and YoutubeService.valid_video_file(item): - videos.add(item) - return videos - - -def prompt_user(videos, confirmation=False): - """Method to prompt user for confirmation, if yes is specified then no prompt is shown - - Returns: boolean value of final decision - """ - if not confirmation: - print("Found videos:") - print("\n".join(videos)) - question = "Are you sure you would like to upload these videos? [Y/n]" - confirmation = raw_input(question).lower() in ('', 'y', 'yes') - return confirmation - - -def upload(files, token, assume_yes): - """Uploads a file(s) to YouTube using the YouTube service API - - This function uploads a list of videos and/or directories of videos to YouTube. - - Args: - token - location of an oauth2 token - files - list of files and directories to upload - assume_yes - if True, assume yes to all interaction (default: False) - """ - # check if token exists - if not os.path.exists(token): - print("{} does not exist, please specify a valid token file".format(token)) - else: - # Gather videos specified and vids from folders specified into list - videos = gather_videos(files) - # Now begin upload process - if not videos: - print("Nothing to upload") - # Prompt for confirmation - elif prompt_user(videos, confirmation=assume_yes): - youtube_service = YoutubeService() - # Authorize with OAuth2 token - youtube_service.authorize(token) - for video in videos: - response_code, response = youtube_service.upload_video(video) - handle_response(response_code, response) - # Response was no, so do nothing - else: - print("Exiting...") diff --git a/src/freeseer/plugins/audioinput/jackaudiosrc/__init__.py.bak b/src/freeseer/plugins/audioinput/jackaudiosrc/__init__.py.bak deleted file mode 100644 index 7df368e4..00000000 --- a/src/freeseer/plugins/audioinput/jackaudiosrc/__init__.py.bak +++ /dev/null @@ -1,120 +0,0 @@ -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://github.com/Freeseer/freeseer/ - -''' -Jack Audio Source ------------------ - -An audio plugin which uses JACK as the audio input. - -@author: Thanh Ha -''' -# GStreamer -import pygst -pygst.require("0.10") -import gst - -# PyQt -from PyQt4.QtCore import SIGNAL - -# Freeseer -from freeseer.framework.plugin import IAudioInput -from freeseer.framework.config import Config -import freeseer.framework.config.options as options - -# .freeseer-plugin custom -import widget - - -class JackAudioConfig(Config): - """Default Jackaudio Config settings""" - - client = options.StringOption('') - connect = options.StringOption('') - server = options.StringOption('') - clientname = options.StringOption('') - - -class JackAudioSrc(IAudioInput): - name = "Jack Audio Source" - os = ["linux", "linux2"] - CONFIG_CLASS = JackAudioConfig - - def get_audioinput_bin(self): - bin = gst.Bin() # Do not pass a name so that we can load this input more than once. - - audiosrc = gst.element_factory_make("jackaudiosrc", "audiosrc") - bin.add(audiosrc) - - # Setup ghost pad - pad = audiosrc.get_pad("src") - ghostpad = gst.GhostPad("audiosrc", pad) - bin.add_pad(ghostpad) - - return bin - - def get_widget(self): - if self.widget is None: - self.widget = widget.ConfigWidget() - - return self.widget - - def __enable_connections(self): - self.widget.connect(self.widget.lineedit_client, SIGNAL('editingFinished()'), self.set_client) - self.widget.connect(self.widget.lineedit_connect, SIGNAL('editingFinished()'), self.set_connect) - self.widget.connect(self.widget.lineedit_server, SIGNAL('editingFinished()'), self.set_server) - self.widget.connect(self.widget.lineedit_clientname, SIGNAL('editingFinished()'), self.set_clientname) - - def widget_load_config(self, plugman): - self.load_config(plugman) - - self.widget.lineedit_client.setText(self.config.client) - self.widget.lineedit_connect.setText(self.config.connect) - self.widget.lineedit_server.setText(self.config.server) - self.widget.lineedit_clientname.setText(self.config.clientname) - - # Finally enable connections - self.__enable_connections() - - def set_client(self): - self.config.client = str(self.widget.lineedit_client.text()) - self.config.save() - - def set_connect(self): - self.config.connect = str(self.widget.lineedit_connect.text()) - self.config.save() - - def set_server(self): - self.config.server = str(self.widget.lineedit_server.text()) - self.config.save() - - def set_clientname(self): - self.config.clientname = str(self.widget.lineedit_clientname.text()) - self.config.save() - - ### - ### Translations - ### - def retranslate(self): - self.widget.label_client.setText(self.gui.app.translate('plugin-jackaudio', 'Client')) - self.widget.label_connect.setText(self.gui.app.translate('plugin-jackaudio', 'Connect')) - self.widget.label_server.setText(self.gui.app.translate('plugin-jackaudio', 'Server')) - self.widget.label_clientname.setText(self.gui.app.translate('plugin-jackaudio', 'Client Name')) diff --git a/src/freeseer/plugins/audioinput/pulsesrc/__init__.py.bak b/src/freeseer/plugins/audioinput/pulsesrc/__init__.py.bak deleted file mode 100644 index 1c2e9daf..00000000 --- a/src/freeseer/plugins/audioinput/pulsesrc/__init__.py.bak +++ /dev/null @@ -1,134 +0,0 @@ -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://github.com/Freeseer/freeseer/ - -''' -PulseAudio Source ------------------ - -An audio plugin which uses PulseAudio as the audio input. - -@author: Thanh Ha -''' - -# python-libs -import logging - -# GStreamer -import pygst -pygst.require("0.10") -import gst - -# PyQt -from PyQt4.QtCore import SIGNAL - -# Freeseer -from freeseer.framework.plugin import IAudioInput -from freeseer.framework.config import Config, options - -# .freeseer-plugin custom -import widget - -log = logging.getLogger(__name__) - - -def get_sources(): - """ - Get a list of pairs in the form (name, description) for each pulseaudio source. - """ - audiosrc = gst.element_factory_make("pulsesrc", "audiosrc") - audiosrc.probe_property_name('device') - names = audiosrc.probe_get_values_name('device') - # TODO: should be getting actual device description, but .get_property('device-name') does not work - return zip(names, names) - - -def get_default_source(): - """Returns the default audio source.""" - sources = get_sources() - if not sources: - return '' - else: - return sources[0][0] - - -class PulseSrcConfig(Config): - """Default PulseSrc config settings.""" - source = options.StringOption('') - - -class PulseSrc(IAudioInput): - name = "Pulse Audio Source" - os = ["linux", "linux2"] - CONFIG_CLASS = PulseSrcConfig - - def get_audioinput_bin(self): - bin = gst.Bin() # Do not pass a name so that we can load this input more than once. - - audiosrc = gst.element_factory_make("pulsesrc", "audiosrc") - - if not self.config.source: - self.config.source = get_default_source() - - audiosrc.set_property('device', self.config.source) - log.debug('Pulseaudio source is set to %s', audiosrc.get_property('device')) - - bin.add(audiosrc) - - # Setup ghost pad - pad = audiosrc.get_pad("src") - ghostpad = gst.GhostPad("audiosrc", pad) - bin.add_pad(ghostpad) - - return bin - - def get_widget(self): - if self.widget is None: - self.widget = widget.ConfigWidget() - - return self.widget - - def __enable_connections(self): - self.widget.connect(self.widget.source_combobox, SIGNAL('currentIndexChanged(int)'), self.set_source) - - def widget_load_config(self, plugman): - self.load_config(plugman) - - sources = get_sources() - - self.widget.source_combobox.clear() - for i, source in enumerate(sources): - self.widget.source_combobox.addItem(source[1], userData=source[0]) - if self.config.source == source[0]: - self.widget.source_combobox.setCurrentIndex(i) - - # Finally connect the signals - self.__enable_connections() - - def set_source(self, index): - self.config.source = self.widget.source_combobox.itemData(index).toString() - log.debug('Set pulseaudio source to %s', self.config.source) - self.config.save() - - ### - ### Translations - ### - def retranslate(self): - self.widget.source_label.setText(self.gui.app.translate('plugin-pulseaudio', 'Source')) diff --git a/src/freeseer/plugins/audiomixer/audiopassthrough/__init__.py.bak b/src/freeseer/plugins/audiomixer/audiopassthrough/__init__.py.bak deleted file mode 100644 index 2f62fc26..00000000 --- a/src/freeseer/plugins/audiomixer/audiopassthrough/__init__.py.bak +++ /dev/null @@ -1,138 +0,0 @@ -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://github.com/Freeseer/freeseer/ - - -''' -Audio Passthrough ------------------ - -A simple audio mixer plugin that connects a single audio source to -the output. - -@author: Thanh Ha -''' - -# GStreamer -import pygst -pygst.require("0.10") -import gst - -# PyQt -from PyQt4.QtCore import SIGNAL - -# Freeseer -from freeseer.framework.plugin import IAudioMixer -from freeseer.framework.config import Config, options - -# .freeseer-plugin custom -import widget - - -class AudioPassThroughConfig(Config): - """Configuration settings for AudioPassThrough plugin.""" - input = options.StringOption("Audio Test Source") - - -class AudioPassthrough(IAudioMixer): - name = "Audio Passthrough" - os = ["linux", "linux2", "win32", "cygwin", "darwin"] - widget = None - CONFIG_CLASS = AudioPassThroughConfig - - def get_audiomixer_bin(self): - bin = gst.Bin() - - audiomixer = gst.element_factory_make("adder", "audiomixer") - bin.add(audiomixer) - - # Setup ghost pad - sinkpad = audiomixer.get_pad("sink%d") - sink_ghostpad = gst.GhostPad("sink", sinkpad) - bin.add_pad(sink_ghostpad) - - srcpad = audiomixer.get_pad("src") - src_ghostpad = gst.GhostPad("src", srcpad) - bin.add_pad(src_ghostpad) - - return bin - - def get_inputs(self): - return [(self.config.input, 0)] - - def load_inputs(self, player, mixer, inputs): - # Load inputs - input = inputs[0] - player.add(input) - input.link(mixer) - - def get_widget(self): - if self.widget is None: - self.widget = widget.ConfigWidget() - - return self.widget - - def __enable_connections(self): - self.widget.connect(self.widget.combobox, SIGNAL('currentIndexChanged(const QString&)'), self.set_input) - self.widget.connect(self.widget.inputSettingsToolButton, SIGNAL('clicked()'), self.source1_setup) - - def widget_load_config(self, plugman): - self.load_config(plugman) - - sources = [] - plugins = self.plugman.get_audioinput_plugins() - for plugin in plugins: - sources.append(plugin.plugin_object.get_name()) - - # Load the combobox with inputs - self.widget.combobox.clear() - n = 0 - for i in sources: - self.widget.combobox.addItem(i) - if i == self.config.input: - self.widget.combobox.setCurrentIndex(n) - self.__enable_source_setup(self.config.input) - n = n + 1 - - # Finally enable connections - self.__enable_connections() - - def source1_setup(self): - plugin = self.plugman.get_plugin_by_name(self.config.input, "AudioInput") - plugin.plugin_object.get_dialog() - - def set_input(self, input): - self.config.input = input - self.__enable_source_setup(self.config.input) - self.config.save() - - def __enable_source_setup(self, source): - '''Activates the source setup button if it has configurable settings''' - plugin = self.plugman.get_plugin_by_name(source, "AudioInput") - if plugin.plugin_object.get_widget() is not None: - self.widget.inputSettingsStack.setCurrentIndex(1) - else: - self.widget.inputSettingsStack.setCurrentIndex(0) - - ### - ### Translations - ### - def retranslate(self): - self.widget.label.setText(self.gui.app.translate('plugin-audio-passthrough', 'Source')) diff --git a/src/freeseer/plugins/audiomixer/multiaudio/__init__.py.bak b/src/freeseer/plugins/audiomixer/multiaudio/__init__.py.bak deleted file mode 100644 index c13e0123..00000000 --- a/src/freeseer/plugins/audiomixer/multiaudio/__init__.py.bak +++ /dev/null @@ -1,176 +0,0 @@ -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://github.com/Freeseer/freeseer/ - -''' -Multiple Audio Plugin ---------------------- - -An audio mixer plugin that combines 2 audio sources into a single output. - -@author: Aaron Brubacher -''' - -# GStreamer -import pygst -pygst.require('0.10') -import gst - -# PyQt -from PyQt4.QtCore import SIGNAL - -# Freeseer -from freeseer.framework.plugin import IAudioMixer -from freeseer.framework.config import Config, options - -# .freeseer-plugin custom -import widget - - -class MultiAudioConfig(Config): - """Configuration settings for MultiAudioConfig plugin.""" - input1 = options.StringOption("Audio Test Source") - input2 = options.StringOption("Audio Test Source") - - -class MultiAudio(IAudioMixer): - name = 'Multiple Audio Inputs' - os = ['linux', 'linux2', 'win32', 'cygwin', 'darwin'] - CONFIG_CLASS = MultiAudioConfig - widget = None - - def get_audiomixer_bin(self): - mixerbin = gst.Bin() - - audiomixer = gst.element_factory_make('adder', 'audiomixer') - mixerbin.add(audiomixer) - - # ghost pads - sinkpad1 = audiomixer.get_pad('sink%d') - sink_ghostpad1 = gst.GhostPad('sink1', sinkpad1) - mixerbin.add_pad(sink_ghostpad1) - - sinkpad2 = audiomixer.get_pad('sink%d') - sink_ghostpad2 = gst.GhostPad('sink2', sinkpad2) - mixerbin.add_pad(sink_ghostpad2) - - srcpad = audiomixer.get_pad('src') - src_ghostpad = gst.GhostPad('src', srcpad) - mixerbin.add_pad(src_ghostpad) - - return mixerbin - - def get_inputs(self): - inputs = [(self.config.input1, 0), (self.config.input2, 1)] - return inputs - - def load_inputs(self, player, mixer, inputs): - input1 = inputs[0] - player.add(input1) - input1.link(mixer) - - input2 = inputs[1] - player.add(input2) - input2.link(mixer) - - def get_widget(self): - if self.widget is None: - self.widget = widget.ConfigWidget() - - return self.widget - - def __enable_connections(self): - self.widget.connect(self.widget.source1_combobox, SIGNAL('currentIndexChanged(const QString&)'), self.set_input1) - self.widget.connect(self.widget.source1_button, SIGNAL('clicked()'), self.source1_setup) - self.widget.connect(self.widget.source2_combobox, SIGNAL('currentIndexChanged(const QString&)'), self.set_input2) - self.widget.connect(self.widget.source2_button, SIGNAL('clicked()'), self.source2_setup) - - def widget_load_config(self, plugman): - self.load_config(plugman) - - plugins = self.plugman.get_audioinput_plugins() - self.widget.source1_combobox.clear() - self.widget.source2_combobox.clear() - for i, source in enumerate(plugins): - name = source.plugin_object.get_name() - self.widget.source1_combobox.addItem(name) - if self.config.input1 == name: - self.widget.source1_combobox.setCurrentIndex(i) - self.__enable_source1_setup(self.config.input1) - self.widget.source2_combobox.addItem(name) - if self.config.input2 == name: - self.widget.source2_combobox.setCurrentIndex(i) - self.__enable_source2_setup(self.config.input2) - - # Finally enable connections - self.__enable_connections() - - ### - ### Source 1 - ### - - def source1_setup(self): - plugin_name = str(self.widget.source1_combobox.currentText()) - plugin = self.plugman.get_plugin_by_name(plugin_name, "AudioInput") - plugin.plugin_object.set_instance(0) - plugin.plugin_object.get_dialog() - - def set_input1(self, input1): - self.config.input1 = input1 - self.__enable_source1_setup(self.config.input1) - self.config.save() - - def __enable_source1_setup(self, source): - '''Activates the source setup button if it has configurable settings''' - plugin = self.plugman.get_plugin_by_name(source, "AudioInput") - if plugin.plugin_object.get_widget() is not None: - self.widget.source1_stack.setCurrentIndex(1) - else: - self.widget.source1_stack.setCurrentIndex(0) - - ### - ### Source 2 - ### - - def source2_setup(self): - plugin_name = str(self.widget.source2_combobox.currentText()) - plugin = self.plugman.get_plugin_by_name(plugin_name, "AudioInput") - plugin.plugin_object.set_instance(1) - plugin.plugin_object.get_dialog() - - def set_input2(self, input2): - self.config.input2 = input2 - self.__enable_source2_setup(self.config.input2) - self.config.save() - - def __enable_source2_setup(self, source): - '''Activates the source setup button if it has configurable settings''' - plugin = self.plugman.get_plugin_by_name(source, "AudioInput") - if plugin.plugin_object.get_widget() is not None: - self.widget.source2_stack.setCurrentIndex(1) - else: - self.widget.source2_stack.setCurrentIndex(0) - - ### - ### Translations - ### - def retranslate(self): - self.widget.source1_label.setText(self.gui.app.translate('plugin-multiaudio', 'Source 1')) - self.widget.source2_label.setText(self.gui.app.translate('plugin-multiaudio', 'Source 2')) diff --git a/src/freeseer/plugins/importer/csv_importer.py.bak b/src/freeseer/plugins/importer/csv_importer.py.bak deleted file mode 100644 index 1c0979d8..00000000 --- a/src/freeseer/plugins/importer/csv_importer.py.bak +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -""" -CSV Importer --------------- - -An import plugin for CSV files used when adding presentations - -@author: Rio Lowry -""" - -import csv -import logging - -from freeseer.framework.plugin import IImporter - -log = logging.getLogger(__name__) - - -class CsvImporter(IImporter): - """CSV Importer plugin for Freeseer - - Provides functionality to import presentations from a CSV file - """ - - name = "CSV Importer" - os = ["linux", "linux2"] - - def get_presentations(self, fname): - """Returns list of dictionaries of all presentations in the csv file.""" - presentations = [] - - try: - with open(fname) as csv_file: - reader = csv.DictReader(csv_file) - for row in reader: - talk = { - 'Title': unicode(row.get('Title', ''), 'utf-8'), - 'Speaker': unicode(row.get('Speaker', ''), 'utf-8'), - 'Abstract': unicode(row.get('Abstract', ''), 'utf-8'), # Description - 'Level': unicode(row.get('Level', ''), 'utf-8'), - 'Event': unicode(row.get('Event', ''), 'utf-8'), - 'Room': unicode(row.get('Room', ''), 'utf-8'), - 'Time': unicode(row.get('Time', ''), 'utf-8'), # Legacy csv time field - 'Date': unicode(row.get('Date', ''), 'utf-8'), - 'StartTime': unicode(row.get('StartTime', ''), 'utf-8'), - 'EndTime': unicode(row.get('EndTime', ''), 'utf-8') - } - - presentations.append(talk) - - except IOError: - log.exception("CSV: File %s not found", csv_file) - - return presentations diff --git a/src/freeseer/plugins/importer/rss_feedparser/__init__.py.bak b/src/freeseer/plugins/importer/rss_feedparser/__init__.py.bak deleted file mode 100644 index a7be8001..00000000 --- a/src/freeseer/plugins/importer/rss_feedparser/__init__.py.bak +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -""" -Rss FeedParser --------------- - -An import plugin which provides a RSS parser used when adding presentations - -@author: Rio Lowry -""" - -try: # Import Python3 module if possible - from html.parser import HTMLParser -except ImportError: - from HTMLParser import HTMLParser - -from feedparser import parse - -from freeseer.framework.plugin import IImporter - - -class MLStripper(HTMLParser): - """Simple stripper to remove markup - - MLStripper from http://stackoverflow.com/a/925630/72321 - """ - def __init__(self): - self.reset() - self.fed = [] - - def handle_data(self, d): - self.fed.append(d) - - def get_data(self): - return ''.join(self.fed) - - -def strip_tags(html): - """Helper functions to strip markup from passed object""" - s = MLStripper() - s.feed(html) - return s.get_data() - - -class FeedParser(IImporter): - """FeedParser plugin for Freeseer - - Provides functionality to allow a RSS feed to be fetched and parsed - """ - - name = "Rss FeedParser" - os = ["linux", "linux2"] - - def get_presentations(self, feed_url): - """Takes feed_url, fetches, parses feed_url - - Returns list of dictionaries of all presentations on parsed feed. - """ - parsed_feed = parse(feed_url) - presentations = [] - for entry in parsed_feed.entries: - pres_data = entry["summary_detail"]["value"] - - # We want to split the pres_data by 3 spaces because some field - # data contain spaces and we don't want to erroneously split that - # data. - - pres_data = filter(None, pres_data.split(" ")) - - presentation = { - 'Title': entry.title.strip(), - 'Speaker': self.get_presentation_field(pres_data, "field-field-speaker"), - 'Abstract': self.get_presentation_field(pres_data, "field-field-abstract"), - 'Level': self.get_presentation_field(pres_data, "field-field-level"), - 'Status': self.get_presentation_field(pres_data, "field-field-status"), - 'Time': self.get_presentation_field(pres_data, "field-field-time"), - 'Event': self.get_presentation_field(pres_data, "field-field-event"), - 'Room': self.get_presentation_field(pres_data, "field-field-room") - } - presentations.append(presentation) - - return presentations - - def get_presentation_field(self, presentation, field_name): - """Returns the field_name of the presentation at presentation""" - - # Due to the autogenerated structure of the rss feed the field data is - # offset by 4 elements from field_name in the passed presentation - - item_presentation_offset = 4 - for i, element in enumerate(presentation): - if field_name in element: - - # data in element is in unicode, we want an error raised if - # there are characters that we are not expecting - - field_data = unicode(presentation[i + item_presentation_offset]) - - return strip_tags(field_data).strip() diff --git a/src/freeseer/plugins/output/audiofeedback/__init__.py.bak b/src/freeseer/plugins/output/audiofeedback/__init__.py.bak deleted file mode 100644 index f72c592a..00000000 --- a/src/freeseer/plugins/output/audiofeedback/__init__.py.bak +++ /dev/null @@ -1,104 +0,0 @@ -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://github.com/Freeseer/freeseer/ - -''' -Audio Feedback --------------- - -An output plugin which routes sound to the device's available -speakers. - -@author: Thanh Ha -''' - -# GStreamer -import pygst -pygst.require("0.10") -import gst - -# PyQt -from PyQt4.QtCore import SIGNAL - -# Freeseer -from freeseer.framework.plugin import IOutput -from freeseer.framework.config import Config, options - -# .freeseer-plugin -import widget - - -class AudioFeedbackConfig(Config): - """Configuration class for AudioFeedback plugin.""" - feedbacksink = options.StringOption("autoaudiosink") - - -class AudioFeedback(IOutput): - name = "Audio Feedback" - os = ["linux", "linux2", "win32", "cygwin", "darwin"] - type = IOutput.AUDIO - recordto = IOutput.OTHER - CONFIG_CLASS = AudioFeedbackConfig - - def get_output_bin(self, audio=True, video=False, metadata=None): - bin = gst.Bin() - - audioqueue = gst.element_factory_make("queue", "audioqueue") - bin.add(audioqueue) - - audiosink = gst.element_factory_make(self.config.feedbacksink, "audiosink") - bin.add(audiosink) - - # Setup ghost pad - pad = audioqueue.get_pad("sink") - ghostpad = gst.GhostPad("sink", pad) - bin.add_pad(ghostpad) - - audioqueue.link(audiosink) - - return bin - - def get_widget(self): - if self.widget is None: - self.widget = widget.ConfigWidget() - - return self.widget - - def __enable_connections(self): - self.widget.connect(self.widget.feedbackComboBox, SIGNAL('currentIndexChanged(const QString&)'), self.set_feedbacksink) - - def widget_load_config(self, plugman): - self.load_config(plugman) - - feedbackIndex = self.widget.feedbackComboBox.findText(self.config.feedbacksink) - self.widget.feedbackComboBox.setCurrentIndex(feedbackIndex) - - # Finally enable connections - self.__enable_connections() - - def set_feedbacksink(self, feedbacksink): - self.config.feedbacksink = feedbacksink - self.config.save() - - ### - ### Translations - ### - def retranslate(self): - self.widget.feedbackLabel.setText(self.gui.app.translate('plugin-audiofeedback', 'Feedback')) diff --git a/src/freeseer/plugins/output/ogg_icecast/__init__.py.bak b/src/freeseer/plugins/output/ogg_icecast/__init__.py.bak deleted file mode 100644 index ed6fcb45..00000000 --- a/src/freeseer/plugins/output/ogg_icecast/__init__.py.bak +++ /dev/null @@ -1,252 +0,0 @@ -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://github.com/Freeseer/freeseer/ - -''' -Ogg Icecast ------------ - -A streaming plugin which records sends an Ogg stream to an icecast server. - -@author: Thanh Ha -''' - -# GStreamer -import pygst -pygst.require("0.10") -import gst - -# PyQt -from PyQt4.QtCore import SIGNAL - -# Freeseer -from freeseer.framework.multimedia import Quality -from freeseer.framework.plugin import IOutput -from freeseer.framework.config import Config, options - -# .freeseer-plugin custom -import widget - - -class OggIcecastConfig(Config): - """Configuration class for OggIcecast Plugin.""" - ip = options.StringOption("127.0.0.1") - port = options.IntegerOption(8000) - password = options.StringOption("hackme") - mount = options.StringOption("stream.ogg") - audio_quality = options.FloatOption(0.3) - video_bitrate = options.IntegerOption(2400) - - -class OggIcecast(IOutput): - name = "Ogg Icecast" - os = ["linux", "linux2"] - type = IOutput.BOTH - recordto = IOutput.STREAM - extension = "ogg" - tags = None - CONFIG_CLASS = OggIcecastConfig - configurable = True - AUDIO_MIN = -0.1 - AUDIO_RANGE = 1.1 - - def get_output_bin(self, audio=True, video=True, metadata=None): - bin = gst.Bin() - - if metadata is not None: - self.set_metadata(metadata) - - # Muxer - muxer = gst.element_factory_make("oggmux", "muxer") - bin.add(muxer) - - icecast = gst.element_factory_make("shout2send", "icecast") - icecast.set_property("ip", self.config.ip) - icecast.set_property("port", self.config.port) - icecast.set_property("password", self.config.password) - icecast.set_property("mount", self.config.mount) - bin.add(icecast) - - # - # Setup Audio Pipeline - # - if audio: - audioqueue = gst.element_factory_make("queue", "audioqueue") - bin.add(audioqueue) - - audioconvert = gst.element_factory_make("audioconvert", "audioconvert") - bin.add(audioconvert) - - audiocodec = gst.element_factory_make("vorbisenc", "audiocodec") - audiocodec.set_property("quality", self.config.audio_quality) - bin.add(audiocodec) - - # Setup metadata - vorbistag = gst.element_factory_make("vorbistag", "vorbistag") - # set tag merge mode to GST_TAG_MERGE_REPLACE - merge_mode = gst.TagMergeMode.__enum_values__[2] - - if metadata is not None: - # Only set tag if metadata is set - vorbistag.merge_tags(self.tags, merge_mode) - vorbistag.set_tag_merge_mode(merge_mode) - bin.add(vorbistag) - - # Setup ghost pads - audiopad = audioqueue.get_pad("sink") - audio_ghostpad = gst.GhostPad("audiosink", audiopad) - bin.add_pad(audio_ghostpad) - - # Link elements - audioqueue.link(audioconvert) - audioconvert.link(audiocodec) - audiocodec.link(vorbistag) - vorbistag.link(muxer) - - # - # Setup Video Pipeline - # - if video: - videoqueue = gst.element_factory_make("queue", "videoqueue") - bin.add(videoqueue) - - videocodec = gst.element_factory_make("theoraenc", "videocodec") - videocodec.set_property("bitrate", self.config.video_bitrate) - bin.add(videocodec) - - videopad = videoqueue.get_pad("sink") - video_ghostpad = gst.GhostPad("videosink", videopad) - bin.add_pad(video_ghostpad) - - videoqueue.link(videocodec) - videocodec.link(muxer) - - # - # Link muxer to icecast - # - muxer.link(icecast) - - return bin - - def set_metadata(self, data): - ''' - Populate global tag list variable with file metadata for - vorbistag audio element - ''' - self.tags = gst.TagList() - - for tag in data.keys(): - if(gst.tag_exists(tag)): - self.tags[tag] = data[tag] - else: - #self.core.logger.log.debug("WARNING: Tag \"" + str(tag) + "\" is not registered with gstreamer.") - pass - - def get_widget(self): - if self.widget is None: - self.widget = widget.ConfigWidget() - - return self.widget - - def get_video_quality_layout(self): - """Returns a layout with the video quality config widgets for configtool to use.""" - return self.get_widget().get_video_quality_layout() - - def get_audio_quality_layout(self): - """Returns a layout with the audio quality config widgets for configtool to use.""" - return self.get_widget().get_audio_quality_layout() - - def __enable_connections(self): - self.widget.connect(self.widget.lineedit_ip, SIGNAL('editingFinished()'), self.set_ip) - self.widget.connect(self.widget.spinbox_port, SIGNAL('valueChanged(int)'), self.set_port) - self.widget.connect(self.widget.lineedit_password, SIGNAL('editingFinished()'), self.set_password) - self.widget.connect(self.widget.lineedit_mount, SIGNAL('editingFinished()'), self.set_mount) - self.widget.connect(self.widget.spinbox_audio_quality, SIGNAL('valueChanged(double)'), self.audio_quality_changed) - self.widget.connect(self.widget.spinbox_video_quality, SIGNAL('valueChanged(int)'), self.video_bitrate_changed) - - def widget_load_config(self, plugman): - self.get_config() - - self.widget.lineedit_ip.setText(self.config.ip) - self.widget.spinbox_port.setValue(self.config.port) - self.widget.lineedit_password.setText(self.config.password) - self.widget.lineedit_mount.setText(self.config.mount) - - # Finally enable connections - self.__enable_connections() - - def set_ip(self): - self.config.ip = str(self.widget.lineedit_ip.text()) - self.config.save() - - def set_port(self, port): - self.config.port = port - self.config.save() - - def set_password(self): - self.config.password = str(self.widget.lineedit_password.text()) - self.config.save() - - def set_mount(self): - self.config.mount = str(self.widget.lineedit_mount.text()) - self.config.save() - - def audio_quality_changed(self): - """Called when a change to the SpinBox for audio quality is made""" - self.config.audio_quality = self.widget.spinbox_audio_quality.value() - self.config.save() - - def set_audio_quality(self, quality): - self.get_config() - - if quality == Quality.LOW: - self.config.audio_quality = self.AUDIO_MIN + (self.AUDIO_RANGE * Quality.LOW_AUDIO_FACTOR) - elif quality == Quality.MEDIUM: - self.config.audio_quality = self.AUDIO_MIN + (self.AUDIO_RANGE * Quality.MEDIUM_AUDIO_FACTOR) - elif quality == Quality.HIGH: - self.config.audio_quality = self.AUDIO_MIN + (self.AUDIO_RANGE * Quality.HIGH_AUDIO_FACTOR) - - if self.widget_config_loaded: - self.widget.spinbox_audio_quality.setValue(self.config.audio_quality) - - self.config.save() - - def video_bitrate_changed(self): - """Called when a change to the SpinBox for video bitrate is made""" - self.config.video_bitrate = self.widget.spinbox_video_quality.value() - self.config.save() - - def set_video_bitrate(self, bitrate): - self.get_config() - - if self.widget_config_loaded: - self.widget.spinbox_video_quality.setValue(bitrate) - - self.config.video_bitrate = bitrate - self.config.save() - - ### - ### Translations - ### - def retranslate(self): - self.widget.label_ip.setText(self.gui.app.translate('plugin-icecast', 'IP')) - self.widget.label_port.setText(self.gui.app.translate('plugin-icecast', 'Port')) - self.widget.label_password.setText(self.gui.app.translate('plugin-icecast', 'Password')) - self.widget.label_mount.setText(self.gui.app.translate('plugin-icecast', 'Mount')) diff --git a/src/freeseer/plugins/output/ogg_output/__init__.py.bak b/src/freeseer/plugins/output/ogg_output/__init__.py.bak deleted file mode 100644 index 7e2b4e76..00000000 --- a/src/freeseer/plugins/output/ogg_output/__init__.py.bak +++ /dev/null @@ -1,247 +0,0 @@ -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://github.com/Freeseer/freeseer/ - -''' -Ogg Output ----------- - -An output plugin which records to Ogg format using Theora for encoding for -video and Vorbis encoding for Audio. - -@author: Thanh Ha -''' - -# GStreamer -import pygst -pygst.require("0.10") -import gst - -# PyQt -from PyQt4.QtCore import SIGNAL - -# Freeeseer -from freeseer.framework.multimedia import Quality -from freeseer.framework.plugin import IOutput -from freeseer.framework.config import Config, options - -# .freeseer-plugin custom -import widget - - -class OggOutputConfig(Config): - """Configuration class for OggOutput plugin.""" - matterhorn = options.IntegerOption(0) - audio_quality = options.FloatOption(0.3) - video_bitrate = options.IntegerOption(2400) - - -class OggOutput(IOutput): - name = "Ogg Output" - os = ["linux", "linux2", "win32", "cygwin", "darwin"] - type = IOutput.BOTH - recordto = IOutput.FILE - extension = "ogg" - tags = None - CONFIG_CLASS = OggOutputConfig - configurable = True - AUDIO_MIN = -0.1 - AUDIO_RANGE = 1.1 - - def get_output_bin(self, audio=True, video=True, metadata=None): - bin = gst.Bin() - - if metadata is not None: - self.set_metadata(metadata) - if self.config.matterhorn == 2: # checked - self.generate_xml_metadata(metadata).write(self.location + ".xml") - - # Muxer - muxer = gst.element_factory_make("oggmux", "muxer") - bin.add(muxer) - - # File sink - filesink = gst.element_factory_make('filesink', 'filesink') - filesink.set_property('location', self.location) - bin.add(filesink) - - # - # Setup Audio Pipeline if Audio Recording is Enabled - # - if audio: - audioqueue = gst.element_factory_make("queue", "audioqueue") - bin.add(audioqueue) - - audioconvert = gst.element_factory_make("audioconvert", "audioconvert") - bin.add(audioconvert) - - audiolevel = gst.element_factory_make('level', 'audiolevel') - audiolevel.set_property('interval', 20000000) - bin.add(audiolevel) - - audiocodec = gst.element_factory_make("vorbisenc", "audiocodec") - audiocodec.set_property("quality", self.config.audio_quality) - bin.add(audiocodec) - - # Setup metadata - vorbistag = gst.element_factory_make("vorbistag", "vorbistag") - # set tag merge mode to GST_TAG_MERGE_REPLACE - merge_mode = gst.TagMergeMode.__enum_values__[2] - - if metadata is not None: - # Only set tag if metadata is set - vorbistag.merge_tags(self.tags, merge_mode) - vorbistag.set_tag_merge_mode(merge_mode) - bin.add(vorbistag) - - # Setup ghost pads - audiopad = audioqueue.get_pad("sink") - audio_ghostpad = gst.GhostPad("audiosink", audiopad) - bin.add_pad(audio_ghostpad) - - # Link Elements - audioqueue.link(audioconvert) - audioconvert.link(audiolevel) - audiolevel.link(audiocodec) - audiocodec.link(vorbistag) - vorbistag.link(muxer) - - # - # Setup Video Pipeline - # - if video: - videoqueue = gst.element_factory_make("queue", "videoqueue") - bin.add(videoqueue) - - videocodec = gst.element_factory_make("theoraenc", "videocodec") - videocodec.set_property("bitrate", self.config.video_bitrate) - bin.add(videocodec) - - # Setup ghost pads - videopad = videoqueue.get_pad("sink") - video_ghostpad = gst.GhostPad("videosink", videopad) - bin.add_pad(video_ghostpad) - - # Link Elements - videoqueue.link(videocodec) - videocodec.link(muxer) - - # - # Link muxer to filesink - # - muxer.link(filesink) - - return bin - - def set_metadata(self, data): - ''' - Populate global tag list variable with file metadata for - vorbistag audio element - ''' - self.tags = gst.TagList() - - for tag in data.keys(): - if(gst.tag_exists(tag)): - self.tags[tag] = data[tag] - else: - #self.core.logger.log.debug("WARNING: Tag \"" + str(tag) + "\" is not registered with gstreamer.") - pass - - def get_widget(self): - if self.widget is None: - self.widget = widget.ConfigWidget() - - return self.widget - - def get_video_quality_layout(self): - """Returns a layout with the video quality config widgets for configtool to use.""" - return self.get_widget().get_video_quality_layout() - - def get_audio_quality_layout(self): - """Returns a layout with the audio quality config widgets for configtool to use.""" - return self.get_widget().get_audio_quality_layout() - - def __enable_connections(self): - self.widget.connect(self.widget.spinbox_audio_quality, SIGNAL('valueChanged(double)'), self.audio_quality_changed) - self.widget.connect(self.widget.spinbox_video_quality, SIGNAL('valueChanged(int)'), self.video_bitrate_changed) - self.widget.connect(self.widget.checkbox_matterhorn, SIGNAL('stateChanged(int)'), self.set_matterhorn) - - def widget_load_config(self, plugman): - self.get_config() - - self.widget.spinbox_audio_quality.setValue(self.config.audio_quality) - self.widget.spinbox_video_quality.setValue(self.config.video_bitrate) - self.widget.checkbox_matterhorn.setCheckState(self.config.matterhorn) - - # Finally enable connections - self.__enable_connections() - - def audio_quality_changed(self): - """Called when a change to the SpinBox for audio quality is made""" - self.config.audio_quality = self.widget.spinbox_audio_quality.value() - self.config.save() - - def set_audio_quality(self, quality): - self.get_config() - - if quality == Quality.LOW: - self.config.audio_quality = self.AUDIO_MIN + (self.AUDIO_RANGE * Quality.LOW_AUDIO_FACTOR) - elif quality == Quality.MEDIUM: - self.config.audio_quality = self.AUDIO_MIN + (self.AUDIO_RANGE * Quality.MEDIUM_AUDIO_FACTOR) - elif quality == Quality.HIGH: - self.config.audio_quality = self.AUDIO_MIN + (self.AUDIO_RANGE * Quality.HIGH_AUDIO_FACTOR) - - if self.widget_config_loaded: - self.widget.spinbox_audio_quality.setValue(self.config.audio_quality) - - self.config.save() - - def video_bitrate_changed(self): - """Called when a change to the SpinBox for video bitrate is made""" - self.config.video_bitrate = self.widget.spinbox_video_quality.value() - self.config.save() - - def set_video_bitrate(self, bitrate): - self.get_config() - - if self.widget_config_loaded: - self.widget.spinbox_video_quality.setValue(bitrate) - - self.config.video_bitrate = bitrate - self.config.save() - - def set_matterhorn(self, state): - """ - Enables or Disables Matterhorn metadata generation. - - If enabled filename.xml will be created along side the video file - containing matterhorn metadata in xml format. - """ - self.config.matterhorn = state - self.config.save() - - ### - ### Translations - ### - def retranslate(self): - self.widget.label_audio_quality.setText(self.gui.app.translate('plugin-ogg-output', 'Audio Quality')) - self.widget.label_video_quality.setText(self.gui.app.translate('plugin-ogg-output', 'Video Quality (kb/s)')) - self.widget.label_matterhorn.setText(self.gui.app.translate('plugin-ogg-output', 'Matterhorn Metadata')) - self.widget.label_matterhorn.setToolTip(self.gui.app.translate('plugin-ogg-output', 'Generates Matterhorn Metadata in XML format')) diff --git a/src/freeseer/plugins/output/rtmp_streaming/__init__.py.bak b/src/freeseer/plugins/output/rtmp_streaming/__init__.py.bak deleted file mode 100644 index 5536e3bc..00000000 --- a/src/freeseer/plugins/output/rtmp_streaming/__init__.py.bak +++ /dev/null @@ -1,805 +0,0 @@ -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://github.com/Freeseer/freeseer/ - - -''' -RTMP streaming --------------- - -The `Real Time Messaging Protocol (RTMP) `_ -is a popular format for video/audio streaming. You can stream from Freeseer -to an arbitrary server using the RTMP plugin. - -To enable streaming - -1. Open :ref:`Freeseer's configuration ` and go to the "Recording" tab -2. Check the "Record to Stream" box -3. Set the stream format to "RTMP Streaming" -4. Click the "Setup" button to access more options -5. Specify the "Stream URL" (the location you'll be streaming to) - -.. note:: All outputs must be set to "leaky mode". - Go to "Plugins" > "Output" > "Video Preview" > "Leaky Queue". - -Justin.tv -********* - -There is built-in support for streaming to `Justin.tv `_. -To enable it, repeat the above steps 1-4, then change the -"Streaming Destination" from "custom" to "justin.tv". - -You also have to input your "Streaming Key". -To get one, `log in to your Justin.tv account `_ -and go to http://www.justin.tv/broadcast/adv_other. - -You also have the option to set your Justin.tv channel properties (stream title -and description). Check the "Set Justin.tv channel properties" box and enter -your "Consumer Key" and "Consumer Secret". - -.. tip:: - To obtain a Consumer Key and Consumer Secret from Justin.tv, - got to http://www.twitch.tv/developer/activate. - - You will need to provide login credentials for Justin.tv. - This will make your account a developer account (as of this moment, - this does not have any adverse effects). - - In order to obtain the Consumer Key and Consumer Secret, - you will have to create an application in Justin.tv. - To do this, go to http://www.twitch.tv/oauth_clients/create. - - On this page you will be asked to provide a name for the application and - a set of URLs - these can be chosen arbitrarily, they serve no purpose for - RTMP streaming. After you press "Save", you will be taken to a page where the - Consumer Key and Consumer Secret will be shown - you can now provide - these to Freeseer! - -When you're done setting up your Justin.tv preferences in Freeseer, click the -"Apply - stream to Justin.tv" button on the bottom of the settings tab. Enjoy! - -@author: Jonathan Shen -''' - -# Python libs -import logging -import pickle -import webbrowser - -# GStreamer libs -import pygst -pygst.require("0.10") -import gst - -# Qt libs -from PyQt4 import QtGui, QtCore - -# Freeseer libs -from freeseer.framework.multimedia import Quality -from freeseer.framework.plugin import IOutput -from freeseer.framework.plugin import PluginError -from freeseer.framework.config import Config, options - -log = logging.getLogger(__name__) - -# -# Non-standard imports required for plugin but not -# for freeseer to run. -# -try: - import httplib - import simplejson - from oauth import oauth -except: - log.error("""RTMP-Streaming: Failed to load plugin. - This plugin requires the following libraries in order operate: - - - httplib - - simplejson - - oauth - - If you wish to use this plugin please ensure these libraries are installed on your system. - """) - raise PluginError("Plugin missing required dependencies.") - -TUNE_VALUES = ['none', 'film', 'animation', 'grain', 'stillimage', 'psnr', 'ssim', 'fastdecode', 'zerolatency'] -AUDIO_CODEC_VALUES = ['lame', 'faac'] -STREAMING_DESTINATION_VALUES = ['custom', 'justin.tv'] -JUSTIN_URL = 'rtmp://live-3c.justin.tv/app/' -STATUS_KEYS = ['artist', 'title'] -DESCRIPTION_KEY = 'comment' - - -class RTMPOutputConfig(Config): - """Configuration class for RTMPOut plugin.""" - url = options.StringOption('') - audio_quality = options.IntegerOption(3) - video_bitrate = options.IntegerOption(2400) - video_tune = options.ChoiceOption(TUNE_VALUES, 'none') - audio_codec = options.ChoiceOption(AUDIO_CODEC_VALUES, 'lame') - streaming_destination = options.ChoiceOption(STREAMING_DESTINATION_VALUES, 'custom') - streaming_key = options.StringOption('') - consumer_key = options.StringOption('') - consumer_secret = options.StringOption('') - authorization_url = options.StringOption('') - use_justin_api = options.StringOption('no') - justin_api_persistent = options.StringOption('') - - -class RTMPOutput(IOutput): - name = "RTMP Streaming" - os = ["linux", "linux2", "win32", "cygwin"] - type = IOutput.BOTH - recordto = IOutput.STREAM - tags = None - justin_api = None - streaming_destination_widget = None - load_config_delegate = None - CONFIG_CLASS = RTMPOutputConfig - configurable = True - LAME_AUDIO_MIN = 0 - LAME_AUDIO_RANGE = 9.999 - FAAC_AUDIO_MIN = 1 - FAAC_AUDIO_RANGE = 999 - - #@brief - RTMP Streaming plugin. - # Structure for function was based primarily off the ogg function - # Creates a bin to stream flv content to [self.config.url] - # Bin has audio and video ghost sink pads - # Converts audio and video to flv with [flvmux] element - # Streams flv content to [self.config.url] - # TODO - Error handling - verify pad setup - def get_output_bin(self, audio=True, video=True, metadata=None): - bin = gst.Bin() - - if metadata is not None: - self.set_metadata(metadata) - - # Muxer - muxer = gst.element_factory_make("flvmux", "muxer") - - # Setup metadata - # set tag merge mode to GST_TAG_MERGE_REPLACE - merge_mode = gst.TagMergeMode.__enum_values__[2] - - if metadata is not None: - # Only set tag if metadata is set - muxer.merge_tags(self.tags, merge_mode) - muxer.set_tag_merge_mode(merge_mode) - - bin.add(muxer) - - # RTMP sink - rtmpsink = gst.element_factory_make('rtmpsink', 'rtmpsink') - rtmpsink.set_property('location', self.config.url) - bin.add(rtmpsink) - - # - # Setup Audio Pipeline if Audio Recording is Enabled - # - if audio: - audioqueue = gst.element_factory_make("queue", "audioqueue") - bin.add(audioqueue) - - audioconvert = gst.element_factory_make("audioconvert", "audioconvert") - bin.add(audioconvert) - - audiolevel = gst.element_factory_make('level', 'audiolevel') - audiolevel.set_property('interval', 20000000) - bin.add(audiolevel) - - audiocodec = gst.element_factory_make(self.config.audio_codec, "audiocodec") - - if 'quality' in audiocodec.get_property_names(): - audiocodec.set_property("quality", self.config.audio_quality) - else: - log.debug("WARNING: Missing property: 'quality' on audiocodec; available: " + - ','.join(audiocodec.get_property_names())) - bin.add(audiocodec) - - # Setup ghost pads - audiopad = audioqueue.get_pad("sink") - audio_ghostpad = gst.GhostPad("audiosink", audiopad) - bin.add_pad(audio_ghostpad) - - # Link Elements - audioqueue.link(audioconvert) - audioconvert.link(audiolevel) - audiolevel.link(audiocodec) - audiocodec.link(muxer) - - # - # Setup Video Pipeline - # - if video: - videoqueue = gst.element_factory_make("queue", "videoqueue") - bin.add(videoqueue) - - videocodec = gst.element_factory_make("x264enc", "videocodec") - videocodec.set_property("bitrate", self.config.video_bitrate) - if self.config.video_tune != 'none': - videocodec.set_property('tune', self.config.video_tune) - bin.add(videocodec) - - # Setup ghost pads - videopad = videoqueue.get_pad("sink") - video_ghostpad = gst.GhostPad("videosink", videopad) - bin.add_pad(video_ghostpad) - - # Link Elements - videoqueue.link(videocodec) - videocodec.link(muxer) - - # - # Link muxer to rtmpsink - # - muxer.link(rtmpsink) - - if self.config.streaming_destination == STREAMING_DESTINATION_VALUES[1] and self.config.use_justin_api == 'yes': - self.justin_api.set_channel_status(self.get_talk_status(metadata), - self.get_description(metadata)) - - return bin - - def get_talk_status(self, metadata): - if not metadata: - return "" - return " - ".join([metadata[status_key] for status_key in self.STATUS_KEYS]) - - def get_description(self, metadata): - if not metadata: - return "" - return metadata[self.DESCRIPTION_KEY] - - def set_metadata(self, data): - ''' - Populate global tag list variable with file metadata for - vorbistag audio element - ''' - self.tags = gst.TagList() - - for tag in data.keys(): - if(gst.tag_exists(tag)): - self.tags[tag] = data[tag] - else: - #self.core.logger.log.debug("WARNING: Tag \"" + str(tag) + "\" is not registered with gstreamer.") - pass - - def load_config(self, plugman, config=None): - super(RTMPOutput, self).load_config(plugman) - if self.config.justin_api_persistent: - self.justin_api = JustinApi.from_string(self.config.justin_api_persistent) - self.justin_api.set_save_method(self.set_justin_api_persistent) - - def get_stream_settings_widget(self): - self.stream_settings_widget = QtGui.QWidget() - self.stream_settings_widget_layout = QtGui.QFormLayout() - self.stream_settings_widget.setLayout(self.stream_settings_widget_layout) - # - # Stream URL - # - - # TODO: URL validation? - - self.label_stream_url = QtGui.QLabel("Stream URL") - self.lineedit_stream_url = QtGui.QLineEdit() - self.stream_settings_widget_layout.addRow(self.label_stream_url, self.lineedit_stream_url) - - self.lineedit_stream_url.textEdited.connect(self.set_stream_url) - - # - # Audio Quality - # - - self.label_audio_quality = QtGui.QLabel("Audio Quality") - self.spinbox_audio_quality = QtGui.QSpinBox() - self.spinbox_audio_quality.setMinimum(0) - self.spinbox_audio_quality.setMaximum(9) - self.spinbox_audio_quality.setSingleStep(1) - self.spinbox_audio_quality.setValue(5) - - self.stream_settings_widget.connect(self.spinbox_audio_quality, QtCore.SIGNAL('valueChanged(int)'), self.audio_quality_changed) - - # - # Audio Codec - # - - self.label_audio_codec = QtGui.QLabel("Audio Codec") - self.combobox_audio_codec = QtGui.QComboBox() - self.combobox_audio_codec.addItems(AUDIO_CODEC_VALUES) - self.stream_settings_widget_layout.addRow(self.label_audio_codec, self.combobox_audio_codec) - - self.stream_settings_widget.connect(self.combobox_audio_codec, - QtCore.SIGNAL('currentIndexChanged(const QString&)'), - self.set_audio_codec) - - # - # Video Quality - # - - self.label_video_quality = QtGui.QLabel("Video Quality (kb/s)") - self.spinbox_video_quality = QtGui.QSpinBox() - self.spinbox_video_quality.setMinimum(0) - self.spinbox_video_quality.setMaximum(16777215) - self.spinbox_video_quality.setValue(2400) # Default value 2400 - - self.stream_settings_widget.connect(self.spinbox_video_quality, QtCore.SIGNAL('valueChanged(int)'), self.video_bitrate_changed) - - # - # Video Tune - # - - self.label_video_tune = QtGui.QLabel("Video Tune") - self.combobox_video_tune = QtGui.QComboBox() - self.combobox_video_tune.addItems(TUNE_VALUES) - self.stream_settings_widget_layout.addRow(self.label_video_tune, self.combobox_video_tune) - - self.stream_settings_widget.connect(self.combobox_video_tune, - QtCore.SIGNAL('currentIndexChanged(const QString&)'), - self.set_video_tune) - - # - # Note - # - - self.label_note = QtGui.QLabel(self.gui.uiTranslator.translate('rtmp', "*For RTMP streaming, all other outputs must be set to leaky")) - self.stream_settings_widget_layout.addRow(self.label_note) - - return self.stream_settings_widget - - def setup_streaming_destination_widget(self, streaming_dest): - if streaming_dest == STREAMING_DESTINATION_VALUES[0]: - self.load_config_delegate = None - self.unlock_stream_settings() - return None - if streaming_dest == STREAMING_DESTINATION_VALUES[1]: - self.load_config_delegate = self.justin_widget_load_config - self.lineedit_stream_url.setEnabled(False) - self.combobox_audio_codec.setEnabled(False) - return self.get_justin_widget() - - def get_justin_widget(self): - self.justin_widget = QtGui.QWidget() - self.justin_widget_layout = QtGui.QFormLayout() - self.justin_widget.setLayout(self.justin_widget_layout) - - # - # justin.tv Streaming Key - # - - self.label_streaming_key = QtGui.QLabel("Streaming Key") - self.lineedit_streaming_key = QtGui.QLineEdit() - self.justin_widget_layout.addRow(self.label_streaming_key, self.lineedit_streaming_key) - - self.lineedit_streaming_key.textEdited.connect(self.set_streaming_key) - - # - # Note - # - - self.label_note = QtGui.QLabel(self.gui.uiTranslator.translate('rtmp', "*See: http://www.justin.tv/broadcast/adv_other\n" + - "You must be logged in to obtain your Streaming Key")) - self.justin_widget_layout.addRow(self.label_note) - - # - # Checkbox for whether or not to use the justin.tv API to push channel settings - # - - self.label_api_checkbox = QtGui.QLabel("Set Justin.tv channel properties") - self.api_checkbox = QtGui.QCheckBox() - self.justin_widget_layout.addRow(self.label_api_checkbox, self.api_checkbox) - - self.api_checkbox.stateChanged.connect(self.set_use_justin_api) - - # - # Consumer key - # - - self.label_consumer_key = QtGui.QLabel("Consumer Key (optional)") - self.lineedit_consumer_key = QtGui.QLineEdit() - self.justin_widget_layout.addRow(self.label_consumer_key, self.lineedit_consumer_key) - - self.lineedit_consumer_key.textEdited.connect(self.set_consumer_key) - - # - # Consumer secret - # - - self.label_consumer_secret = QtGui.QLabel("Consumer Secret (optional)") - self.lineedit_consumer_secret = QtGui.QLineEdit() - self.justin_widget_layout.addRow(self.label_consumer_secret, self.lineedit_consumer_secret) - - self.lineedit_consumer_secret.textEdited.connect(self.set_consumer_secret) - - # - # Apply button, so as not to accidentally overwrite custom settings - # - - self.apply_button = QtGui.QPushButton("Apply - stream to Justin.tv") - self.apply_button.setToolTip(self.gui.uiTranslator.translate('rtmp', "Overwrite custom settings for justin.tv")) - self.justin_widget_layout.addRow(self.apply_button) - - self.apply_button.clicked.connect(self.apply_justin_settings) - - return self.justin_widget - - def get_widget(self): - if self.widget is None: - self.widget = QtGui.QWidget() - self.widget.setWindowTitle("RTMP Streaming Options") - - self.widget_layout = QtGui.QFormLayout() - self.widget.setLayout(self.widget_layout) - - # - # Streaming presets - # - - self.stream_settings_area = QtGui.QScrollArea() - self.stream_settings_area.setWidgetResizable(True) - self.widget_layout.addRow(self.stream_settings_area) - - self.stream_settings_area.setWidget(self.get_stream_settings_widget()) - - self.label_streaming_dest = QtGui.QLabel("Streaming Destination") - self.combobox_streaming_dest = QtGui.QComboBox() - self.combobox_streaming_dest.addItems(STREAMING_DESTINATION_VALUES) - - self.widget_layout.addRow(self.label_streaming_dest, self.combobox_streaming_dest) - - self.widget.connect(self.combobox_streaming_dest, - QtCore.SIGNAL('currentIndexChanged(const QString&)'), - self.set_streaming_dest) - - return self.widget - - def get_video_quality_layout(self): - """Returns a layout with the video quality config widgets for configtool to use.""" - self.get_widget() - - layout_video_quality = QtGui.QHBoxLayout() - layout_video_quality.addWidget(self.label_video_quality) - layout_video_quality.addWidget(self.spinbox_video_quality) - - return layout_video_quality - - def get_audio_quality_layout(self): - """Returns a layout with the audio quality config widgets for configtool to use.""" - self.get_widget() - - layout_audio_quality = QtGui.QHBoxLayout() - layout_audio_quality.addWidget(self.label_audio_quality) - layout_audio_quality.addWidget(self.spinbox_audio_quality) - - return layout_audio_quality - - def load_streaming_destination_widget(self): - streaming_destination_widget = self.setup_streaming_destination_widget(self.config.streaming_destination) - - if self.streaming_destination_widget is not None: - self.streaming_destination_widget.deleteLater() - self.streaming_destination_widget = None - - if streaming_destination_widget: - self.widget_layout.addRow(streaming_destination_widget) - self.streaming_destination_widget = streaming_destination_widget - - def widget_load_config(self, plugman): - self.get_config() - self.stream_settings_load_config() - - self.combobox_streaming_dest.setCurrentIndex(STREAMING_DESTINATION_VALUES.index(self.config.streaming_destination)) - - self.load_streaming_destination_widget() - if self.load_config_delegate: - self.load_config_delegate() - - def justin_widget_load_config(self): - self.lineedit_streaming_key.setText(self.config.streaming_key) - self.lineedit_consumer_key.setText(self.config.consumer_key) - self.lineedit_consumer_secret.setText(self.config.consumer_secret) - - check_state = 0 - if self.config.use_justin_api == 'yes': - check_state = 2 - self.api_checkbox.setCheckState(check_state) - self.toggle_consumer_key_secret_fields() - - def unlock_stream_settings(self): - self.lineedit_stream_url.setEnabled(True) - self.spinbox_audio_quality.setEnabled(True) - self.spinbox_video_quality.setEnabled(True) - self.combobox_video_tune.setEnabled(True) - self.combobox_audio_codec.setEnabled(True) - - def stream_settings_load_config(self): - self.lineedit_stream_url.setText(self.config.url) - - self.spinbox_audio_quality.setValue(self.config.audio_quality) - self.spinbox_video_quality.setValue(self.config.video_bitrate) - - tuneIndex = self.combobox_video_tune.findText(self.config.video_tune) - self.combobox_video_tune.setCurrentIndex(tuneIndex) - - acIndex = self.combobox_audio_codec.findText(self.config.audio_codec) - self.combobox_audio_codec.setCurrentIndex(acIndex) - - def set_stream_url(self, text): - self.config.url = text - self.config.save() - - def audio_quality_changed(self): - """Called when a change to the SpinBox for audio quality is made""" - self.config.audio_quality = self.spinbox_audio_quality.value() - self.config.save() - - def set_audio_quality(self, quality): - self.get_config() - - if self.config.audio_codec == "lame": - min_val = self.LAME_AUDIO_MIN - range_val = self.LAME_AUDIO_RANGE - else: - min_val = self.FAAC_AUDIO_MIN - range_val = self.FAAC_AUDIO_RANGE - - if quality == Quality.LOW: - self.config.audio_quality = int(min_val + (range_val * Quality.LOW_AUDIO_FACTOR)) - elif quality == Quality.MEDIUM: - self.config.audio_quality = int(min_val + (range_val * Quality.MEDIUM_AUDIO_FACTOR)) - elif quality == Quality.HIGH: - self.config.audio_quality = int(min_val + (range_val * Quality.HIGH_AUDIO_FACTOR)) - - if self.widget_config_loaded: - self.spinbox_audio_quality.setValue(self.config.audio_quality) - - self.config.save() - - def video_bitrate_changed(self): - """Called when a change to the SpinBox for video bitrate is made""" - self.config.video_bitrate = self.spinbox_video_quality.value() - self.config.save() - - def set_video_bitrate(self, bitrate): - self.get_config() - - if self.widget_config_loaded: - self.spinbox_video_quality.setValue(bitrate) - - self.config.video_bitrate = bitrate - self.config.save() - - def set_video_tune(self, tune): - self.config.video_tune = tune - self.config.save() - - def set_audio_codec(self, codec): - self.config.audio_codec = codec - self.config.save() - - if self.gui.config.audio_quality != Quality.CUSTOM: - self.set_audio_quality(self.gui.config.audio_quality) - - def set_streaming_dest(self, dest): - self.config.streaming_destination = dest - - if self.config.streaming_destination in STREAMING_DESTINATION_VALUES: - index = min([i for i in range(len(STREAMING_DESTINATION_VALUES)) - if STREAMING_DESTINATION_VALUES[i] == self.config.streaming_destination]) - self.combobox_streaming_dest.setCurrentIndex(index) - - self.load_streaming_destination_widget() - if self.load_config_delegate: - self.load_config_delegate() - self.config.save() - - def set_streaming_key(self, text): - self.config.streaming_key = str(text) - self.config.save() - - def set_use_justin_api(self, state): - if state != 0: - self.config.use_justin_api = 'yes' - else: - self.config.use_justin_api = 'no' - self.config.save() - self.toggle_consumer_key_secret_fields() - - def toggle_consumer_key_secret_fields(self): - if self.config.use_justin_api == 'yes': - self.lineedit_consumer_key.setEnabled(True) - self.lineedit_consumer_secret.setEnabled(True) - else: - self.lineedit_consumer_key.setEnabled(False) - self.lineedit_consumer_secret.setEnabled(False) - - def set_consumer_key(self, text): - self.config.consumer_key = str(text) - self.config.save() - - def set_consumer_secret(self, text): - self.config.consumer_secret = str(text) - self.config.save() - - def set_justin_api_persistent(self, text): - self.justin_api_persistent = str(text) - self.config.save() - - def apply_justin_settings(self): - # here is where all the justin.tv streaming presets will be applied - self.set_stream_url(self.JUSTIN_URL + self.config.streaming_key) - self.set_audio_codec('lame') - - self.stream_settings_load_config() - - try: - if self.config.consumer_key and self.config.consumer_secret: - url, self.justin_api = JustinApi.open_request(self.config.consumer_key, self.config.consumer_secret) - self.justin_api.set_save_method(self.set_justin_api_persistent) - webbrowser.open(url) - QtGui.QMessageBox.information(self.widget, - "justin.tv authentication", - self.gui.uiTranslator.translate('rtmp', "An authorization URL should have opened in your browser.\n" - "If not, go open the following URL to allow freeseer to manage your justin.tv channel.\n" - "%1").arg(url), - QtGui.QMessageBox.Ok, - QtGui.QMessageBox.Ok) - except KeyError: - log.error("justin.tv API error: Authentication failed. Supplied credentials may be incorrect.") - QtGui.QMessageBox.critical(self.widget, - "justin.tv error", - self.gui.uiTranslator.translate('rtmp', "Authentication failed. Supplied credentials for Justin.tv" - " may be incorrect."), - QtGui.QMessageBox.Ok, - QtGui.QMessageBox.Ok) - - -class JustinApi: - addr = 'api.justin.tv' - - @staticmethod - def open_request(consumer_key, consumer_secret): - """ - returns request url and JustinClient object - the object will need to obtain access token on first use - """ - consumer = oauth.OAuthConsumer(consumer_key, consumer_secret) - url = "http://%s/oauth/request_token" % JustinApi.addr - request = oauth.OAuthRequest.from_consumer_and_token( - consumer, - None, - http_method='GET', - http_url=url) - - request.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(), consumer, None) - - connection = httplib.HTTPConnection(JustinApi.addr) - connection.request('GET', request.http_url, headers=request.to_header()) - result = connection.getresponse().read() - - token = oauth.OAuthToken.from_string(result) - - auth_request = oauth.OAuthRequest.from_token_and_callback( - token=token, - callback='http://localhost/', - http_url='http://%s/oauth/authorize' % JustinApi.addr) - - return auth_request.to_url(), JustinApi(consumer_key=consumer_key, consumer_secret=consumer_secret, request_token_str=result) - - @staticmethod - def from_string(persistent_obj): - """ - Returns JustinClient object from string. - """ - consumer_key, consumer_secret, request_token_str, access_token_str = pickle.loads(persistent_obj) - return JustinApi(consumer_key, consumer_secret, request_token_str, access_token_str) - - def __init__(self, consumer_key="", consumer_secret="", request_token_str="", access_token_str=""): - self.config.consumer_key = consumer_key - self.config.consumer_secret = consumer_secret - self.request_token_str = request_token_str - self.access_token_str = access_token_str - - def set_save_method(self, save_method): - """ - upon obtaining an access token, this object will be have a different - serialization - - in order to support this the given save_method should be called - upon any such change with the new serialization as its only argument - """ - self.save_method = save_method - self.save_method(self.to_string()) - - def obtain_access_token(self): - try: - consumer = oauth.OAuthConsumer(self.config.consumer_key, self.config.consumer_secret) - token = oauth.OAuthToken.from_string(self.request_token_str) - url = "http://%s/oauth/access_token" % JustinApi.addr - request = oauth.OAuthRequest.from_consumer_and_token( - consumer, - token, - http_method='GET', - http_url=url) - request.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(), consumer, token) - connection = httplib.HTTPConnection(self.addr) - connection.request('GET', request.http_url, headers=request.to_header()) - result = connection.getresponse().read() - self.access_token_str = result - oauth.OAuthToken.from_string(result) - self.save_method(self.to_string()) - except KeyError: - log.error("justin.tv API: failed to obtain an access token") - - def get_data(self, endpoint): - try: - token = oauth.OAuthToken.from_string(self.access_token_str) - consumer = oauth.OAuthConsumer(self.config.consumer_key, self.config.consumer_secret) - request = oauth.OAuthRequest.from_consumer_and_token( - consumer, - token, - http_method='GET', - http_url="http://%s/api/%s" % (JustinApi.addr, endpoint)) - request.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(), consumer, token) - connection = httplib.HTTPConnection(self.addr) - connection.request('GET', request.http_url, headers=request.to_header()) - result = connection.getresponse().read() - data = simplejson.loads(result) - except KeyError, simplejson.decoder.JSONDecodeError: - log.error("justin.tv API: failed fetch data from endpoint %s" % endpoint) - return dict() - return data - - def set_data(self, endpoint, payload): - try: - token = oauth.OAuthToken.from_string(self.access_token_str) - consumer = oauth.OAuthConsumer(self.config.consumer_key, self.config.consumer_secret) - request = oauth.OAuthRequest.from_consumer_and_token( - consumer, - token, - http_method='POST', - http_url="http://%s/api/%s" % (JustinApi.addr, endpoint), - parameters=payload) - request.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(), consumer, token) - connection = httplib.HTTPConnection(self.addr) - connection.request('POST', request.http_url, body=request.to_postdata()) - result = connection.getresponse().read() - except KeyError: - log.error("justin.tv API: failed write data to endpoint %s" % endpoint) - return None - return result - - def set_channel_status(self, status, description): - if not self.access_token_str: - self.obtain_access_token() - data = self.get_data("account/whoami.json") - if not data: - return - login = data['login'] - data = self.get_data('channel/show/%s.json' % login) - update_contents = { - 'title': status, - 'status': status, - 'description': description, - } - self.set_data('channel/update.json', update_contents) - - def to_string(self): - return pickle.dumps([self.config.consumer_key, self.config.consumer_secret, str(self.request_token_str), str(self.access_token_str)]) diff --git a/src/freeseer/plugins/output/videopreview/__init__.py.bak b/src/freeseer/plugins/output/videopreview/__init__.py.bak deleted file mode 100644 index 8fa2e232..00000000 --- a/src/freeseer/plugins/output/videopreview/__init__.py.bak +++ /dev/null @@ -1,125 +0,0 @@ -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://github.com/Freeseer/freeseer/ - -''' -Video Preview -------------- - -An output plugin which provides a video window to preview the video that -is being recorded in real time. - -@author: Thanh Ha -''' - -# GStreamer -import pygst -pygst.require("0.10") -import gst - -# PyQT -from PyQt4.QtCore import SIGNAL - -# Freeseer -from freeseer.framework.plugin import IOutput -from freeseer.framework.config import Config, options - -# .freeseer-plugin custom -import widget - -# Leaky Queue -LEAKY_VALUES = ["no", "upstream", "downstream"] - - -class VideoPreviewConfig(Config): - """Configuration class for VideoPreview plugin.""" - # Video Preview variables - previewsink = options.StringOption("autovideosink") - leakyqueue = options.ChoiceOption(LEAKY_VALUES, "no") - - -class VideoPreview(IOutput): - name = "Video Preview" - os = ["linux", "linux2", "win32", "cygwin", "darwin"] - type = IOutput.VIDEO - recordto = IOutput.OTHER - CONFIG_CLASS = VideoPreviewConfig - - def get_output_bin(self, audio=False, video=True, metadata=None): - bin = gst.Bin() - - # Leaky queue necessary to work with rtmp streaming - videoqueue = gst.element_factory_make("queue", "videoqueue") - videoqueue.set_property("leaky", self.config.leakyqueue) - bin.add(videoqueue) - - cspace = gst.element_factory_make("ffmpegcolorspace", "cspace") - bin.add(cspace) - - videosink = gst.element_factory_make(self.config.previewsink, "videosink") - bin.add(videosink) - - # Setup ghost pad - pad = videoqueue.get_pad("sink") - ghostpad = gst.GhostPad("sink", pad) - bin.add_pad(ghostpad) - - # Link Elements - videoqueue.link(cspace) - cspace.link(videosink) - - return bin - - def get_widget(self): - if self.widget is None: - self.widget = widget.ConfigWidget() - self.widget.leakyQueueComboBox.addItems(LEAKY_VALUES) - return self.widget - - def __enable_connections(self): - self.widget.connect(self.widget.previewComboBox, SIGNAL('currentIndexChanged(const QString&)'), self.set_previewsink) - self.widget.connect(self.widget.leakyQueueComboBox, SIGNAL('currentIndexChanged(const QString&)'), self.set_leakyqueue) - - def widget_load_config(self, plugman): - self.load_config(plugman) - - previewIndex = self.widget.previewComboBox.findText(self.config.previewsink) - self.widget.previewComboBox.setCurrentIndex(previewIndex) - - leakyQueueIndex = self.widget.leakyQueueComboBox.findText(self.config.leakyqueue) - self.widget.leakyQueueComboBox.setCurrentIndex(leakyQueueIndex) - - # Finally enable connections - self.__enable_connections() - - def set_previewsink(self, previewsink): - self.config.previewsink = previewsink - self.config.save() - - def set_leakyqueue(self, value): - self.config.leakyqueue = value - self.config.save() - - ### - ### Translations - ### - def retranslate(self): - self.widget.previewLabel.setText(self.gui.app.translate('plugin-videopreview', 'Preview')) - self.widget.leakyQueueLabel.setText(self.gui.app.translate('plugin-videopreview', 'Leaky Queue')) diff --git a/src/freeseer/plugins/output/webm_output/__init__.py.bak b/src/freeseer/plugins/output/webm_output/__init__.py.bak deleted file mode 100644 index 63e0b847..00000000 --- a/src/freeseer/plugins/output/webm_output/__init__.py.bak +++ /dev/null @@ -1,140 +0,0 @@ -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://github.com/Freeseer/freeseer/ - -''' -WebM Output ------------ - -An output plugin that records to webm format with vp8 encoding for the video -and Vorbis encoding for audio. - -@author: Thanh Ha -''' - -# GStreamer -import pygst -pygst.require("0.10") -import gst - -# Freeseer -from freeseer.framework.plugin import IOutput - - -class WebMOutput(IOutput): - name = "WebM Output" - os = ["linux", "linux2", "win32", "cygwin"] - type = IOutput.BOTH - recordto = IOutput.FILE - extension = "webm" - tags = None - - def get_output_bin(self, audio=True, video=True, metadata=None): - bin = gst.Bin() - - if metadata is not None: - self.set_metadata(metadata) - - # Muxer - muxer = gst.element_factory_make("webmmux", "muxer") - bin.add(muxer) - - filesink = gst.element_factory_make('filesink', 'filesink') - filesink.set_property('location', self.location) - bin.add(filesink) - - # - # Setup Audio Pipeline - # - if audio: - audioqueue = gst.element_factory_make("queue", "audioqueue") - bin.add(audioqueue) - - audioconvert = gst.element_factory_make("audioconvert", "audioconvert") - bin.add(audioconvert) - - audiolevel = gst.element_factory_make('level', 'audiolevel') - audiolevel.set_property('interval', 20000000) - bin.add(audiolevel) - - audiocodec = gst.element_factory_make("vorbisenc", "audiocodec") - bin.add(audiocodec) - - # Setup metadata - vorbistag = gst.element_factory_make("vorbistag", "vorbistag") - # set tag merge mode to GST_TAG_MERGE_REPLACE - merge_mode = gst.TagMergeMode.__enum_values__[2] - - if metadata is not None: - # Only set tag if metadata is set - vorbistag.merge_tags(self.tags, merge_mode) - vorbistag.set_tag_merge_mode(merge_mode) - bin.add(vorbistag) - - # Setup ghost pads - audiopad = audioqueue.get_pad("sink") - audio_ghostpad = gst.GhostPad("audiosink", audiopad) - bin.add_pad(audio_ghostpad) - - # Link Elements - audioqueue.link(audioconvert) - audioconvert.link(audiolevel) - audiolevel.link(audiocodec) - audiocodec.link(vorbistag) - vorbistag.link(muxer) - - # - # Setup Video Pipeline - # - if video: - videoqueue = gst.element_factory_make("queue", "videoqueue") - bin.add(videoqueue) - - videocodec = gst.element_factory_make("vp8enc", "videocodec") - bin.add(videocodec) - - videopad = videoqueue.get_pad("sink") - video_ghostpad = gst.GhostPad("videosink", videopad) - bin.add_pad(video_ghostpad) - - # Link Elements - videoqueue.link(videocodec) - videocodec.link(muxer) - - # - # Link muxer to filesink - # - muxer.link(filesink) - - return bin - - def set_metadata(self, data): - ''' - Populate global tag list variable with file metadata for - vorbistag audio element - ''' - self.tags = gst.TagList() - - for tag in data.keys(): - if(gst.tag_exists(tag)): - self.tags[tag] = data[tag] - else: - #self.core.logger.log.debug("WARNING: Tag \"" + str(tag) + "\" is not registered with gstreamer.") - pass diff --git a/src/freeseer/plugins/videoinput/desktop/__init__.py.bak b/src/freeseer/plugins/videoinput/desktop/__init__.py.bak deleted file mode 100644 index a08eb878..00000000 --- a/src/freeseer/plugins/videoinput/desktop/__init__.py.bak +++ /dev/null @@ -1,229 +0,0 @@ -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://github.com/Freeseer/freeseer/ - -''' -Desktop Source --------------- - -A video input plugin that uses your desktop as the video source. - -@author: Thanh Ha -''' -import logging -import sys - -# GStreamer modules -import pygst -pygst.require("0.10") -import gst - -# PyQt4 modules -from PyQt4.QtCore import SIGNAL -from PyQt4.QtGui import QApplication -from PyQt4.QtGui import QDesktopWidget - -# Freeseer modules -from freeseer.framework.plugin import IVideoInput -from freeseer.framework.area_selector import AreaSelector -from freeseer.framework.config import Config, options - -# .freeseer-plugin custom modules -import widget - -log = logging.getLogger(__name__) - - -class DesktopLinuxSrcConfig(Config): - """Configuration settings for linux desktop video plugin.""" - - # ximagesrc - desktop = options.StringOption("Full") - screen = options.IntegerOption(0) - window = options.StringOption("") - - # Area Select - start_x = options.IntegerOption(0) - start_y = options.IntegerOption(0) - end_x = options.IntegerOption(0) - end_y = options.IntegerOption(0) - - -class DesktopLinuxSrc(IVideoInput): - name = "Desktop Source" - os = ["linux", "linux2", "win32", "cygwin"] - CONFIG_CLASS = DesktopLinuxSrcConfig - - def get_videoinput_bin(self): - """ - Return the video input object in gstreamer bin format. - """ - bin = gst.Bin() # Do not pass a name so that we can load this input more than once. - - videosrc = None - - if sys.platform.startswith("linux"): - videosrc = gst.element_factory_make("ximagesrc", "videosrc") - - # Configure coordinates if we're not recording full desktop - if self.config.desktop == "Area": - videosrc.set_property("startx", self.config.start_x) - videosrc.set_property("starty", self.config.start_y) - videosrc.set_property("endx", self.config.end_x) - videosrc.set_property("endy", self.config.end_y) - log.debug('Recording Area start: %sx%s end: %sx%s', - self.config.start_x, - self.config.start_y, - self.config.end_x, - self.config.end_y) - - if self.config.desktop == "Window": - videosrc.set_property("xname", self.config.window) - - elif sys.platform in ["win32", "cygwin"]: - videosrc = gst.element_factory_make("dx9screencapsrc", "videosrc") - - # Configure coordinates if we're not recording full desktop - if self.config.desktop == "Area": - videosrc.set_property("x", self.config.start_x) - videosrc.set_property("y", self.config.start_y) - videosrc.set_property("width", self.config.end_x - self.config.start_x) - videosrc.set_property("height", self.config.end_y - self.config.start_y) - log.debug('Recording Area start: %sx%s end: %sx%s', - self.config.start_x, - self.config.start_y, - self.config.end_x, - self.config.end_y) - - bin.add(videosrc) - - colorspace = gst.element_factory_make("ffmpegcolorspace", "colorspace") - bin.add(colorspace) - videosrc.link(colorspace) - - # Setup ghost pad - pad = colorspace.get_pad("src") - ghostpad = gst.GhostPad("videosrc", pad) - bin.add_pad(ghostpad) - - return bin - - def area_select(self): - self.area_selector = AreaSelector(self) - self.area_selector.show() - self.gui.hide() - self.gui.last_dialog.hide() - self.widget.window().hide() - - def areaSelectEvent(self, start_x, start_y, end_x, end_y): - if start_x <= end_x: - self.config.start_x = start_x - self.config.end_x = end_x - else: - self.config.start_x = end_x - self.config.end_x = start_x - - if start_y <= end_y: - self.config.start_y = start_y - self.config.end_y = end_y - else: - self.config.start_y = end_y - self.config.end_y = start_y - - self.config.save() - log.debug('Area selector start: %sx%s end: %sx%s', - self.config.start_x, - self.config.start_y, - self.config.end_x, - self.config.end_y) - # Automatically check the "Record Region" button. - self.set_desktop_area() - self.widget.areaButton.setChecked(True) - self.widget.regionLabel.setText("{}x{} to {}x{}".format( - self.config.start_x, self.config.start_y, self.config.end_x, self.config.end_y)) - - self.gui.show() - self.gui.last_dialog.show() - self.widget.window().show() - - def get_widget(self): - if self.widget is None: - self.widget = widget.ConfigWidget() - - return self.widget - - def get_resolution_pixels(self): - self.get_config() - - if self.config.desktop == "Full": - app = QApplication.instance() - screen_rect = app.desktop().screenGeometry() - return screen_rect.width() * screen_rect.height() - elif self.config.desktop == "Area": - width = self.config.end_x - self.config.start_x - height = self.config.end_y - self.config.start_y - return width * height - - def __enable_connections(self): - self.widget.connect(self.widget.desktopButton, SIGNAL('clicked()'), self.set_desktop_full) - self.widget.connect(self.widget.areaButton, SIGNAL('clicked()'), self.set_desktop_area) - self.widget.connect(self.widget.setAreaButton, SIGNAL('clicked()'), self.area_select) - self.widget.connect(self.widget.screenSpinBox, SIGNAL('valueChanged(int)'), self.set_screen) - - def widget_load_config(self, plugman): - self.get_config() - - if self.config.desktop == "Full": - self.widget.desktopButton.setChecked(True) - elif self.config.desktop == "Area": - self.widget.areaButton.setChecked(True) - - # Try to detect how many screens the user has - # minus 1 since we like to start count at 0 - max_screens = QDesktopWidget().screenCount() - self.widget.screenSpinBox.setMaximum(max_screens - 1) - - self.widget.regionLabel.setText("{}x{} to {}x{}".format( - self.config.start_x, self.config.start_y, self.config.end_x, self.config.end_y)) - - # Finally enable connections - self.__enable_connections() - - def set_screen(self, screen): - self.config.screen = screen - self.config.save() - - def set_desktop_full(self): - self.config.desktop = "Full" - self.config.save() - self.gui.update_video_quality() - - def set_desktop_area(self): - self.config.desktop = "Area" - self.config.save() - self.gui.update_video_quality() - - ### - ### Translations - ### - def retranslate(self): - self.widget.desktopLabel.setText(self.gui.app.translate('plugin-desktop', 'Record Desktop')) - self.widget.areaLabel.setText(self.gui.app.translate('plugin-desktop', 'Record Region')) - self.widget.screenLabel.setText(self.gui.app.translate('plugin-desktop', 'Screen')) diff --git a/src/freeseer/plugins/videoinput/firewiresrc/__init__.py.bak b/src/freeseer/plugins/videoinput/firewiresrc/__init__.py.bak deleted file mode 100644 index 2d41d9f4..00000000 --- a/src/freeseer/plugins/videoinput/firewiresrc/__init__.py.bak +++ /dev/null @@ -1,151 +0,0 @@ -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://github.com/Freeseer/freeseer/ - -''' -Firewire Source ---------------- - -A video input plugin that uses connected Firewire devices as the input -source. - -@author: Thanh Ha -''' - -# python-libs -import os - -# GStreamer modules -import pygst -pygst.require("0.10") -import gst - -# PyQt4 modules -from PyQt4.QtCore import SIGNAL - -# Freeseer modules -from freeseer.framework.plugin import IVideoInput -from freeseer.framework.config import Config, options - -# .freeseer-plugin custom modules -import widget - - -def detect_devices(): - """ - Return a list of available firewire devices. - """ - device_list = [] - i = 1 - path = "/dev/fw" - devpath = path + str(i) - - while os.path.exists(devpath): - device_list.append(devpath) - i = i + 1 - devpath = path + str(i) - - return device_list - - -def get_default_device(): - """Returns a default recording device from get_devices().""" - devices = detect_devices() - if not devices: - return '' - return devices[0] - - -class FirewireSrcConfig(Config): - """Config settings for Firewire video source.""" - device = options.StringOption('') - - -class FirewireSrc(IVideoInput): - name = "Firewire Source" - os = ["linux", "linux2"] - CONFIG_CLASS = FirewireSrcConfig - - def __init__(self): - IVideoInput.__init__(self) - - def get_videoinput_bin(self): - bin = gst.Bin() # Do not pass a name so that we can load this input more than once. - - if not self.config.device: - self.config.device = get_default_device() - - videosrc = gst.element_factory_make("dv1394src", "videosrc") - dv1394q1 = gst.element_factory_make('queue', 'dv1394q1') - dv1394dvdemux = gst.element_factory_make('dvdemux', 'dv1394dvdemux') - dv1394q2 = gst.element_factory_make('queue', 'dv1394q2') - dv1394dvdec = gst.element_factory_make('dvdec', 'dv1394dvdec') - - # Add Elements - bin.add(videosrc) - bin.add(dv1394q1) - bin.add(dv1394dvdemux) - bin.add(dv1394q2) - bin.add(dv1394dvdec) - - # Link Elements - videosrc.link(dv1394q1) - dv1394q1.link(dv1394dvdemux) - dv1394dvdemux.link(dv1394q2) - dv1394q2.link(dv1394dvdec) - - # Setup ghost pad - pad = dv1394dvdec.get_pad("src") - ghostpad = gst.GhostPad("videosrc", pad) - bin.add_pad(ghostpad) - - return bin - - def get_widget(self): - if self.widget is None: - self.widget = widget.ConfigWidget() - - return self.widget - - def __enable_connections(self): - self.widget.connect(self.widget.devicesCombobox, SIGNAL('activated(const QString&)'), self.set_device) - - def widget_load_config(self, plugman): - self.load_config(plugman) - - # Load the combobox with inputs - self.widget.devicesCombobox.clear() - for i, device in enumerate(detect_devices()): - self.widget.devicesCombobox.addItem(device) - if device == self.config.device: - self.widget.devicesCombobox.setCurrentIndex(i) - - # Finally enable connections - self.__enable_connections() - - def set_device(self, device): - self.config.device = device - self.config.save() - - ### - ### Translations - ### - def retranslate(self): - self.widget.devicesLabel.setText(self.gui.app.translate('plugin-firewire', 'Video Device')) diff --git a/src/freeseer/plugins/videoinput/usbsrc/__init__.py.bak b/src/freeseer/plugins/videoinput/usbsrc/__init__.py.bak deleted file mode 100644 index 9e3e7132..00000000 --- a/src/freeseer/plugins/videoinput/usbsrc/__init__.py.bak +++ /dev/null @@ -1,172 +0,0 @@ -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://github.com/Freeseer/freeseer/ - -''' -USB Source ----------- - -A video input plugin that uses USB video devices as the video input source. - -Devices such as Webcams and vga2usb frame grabbers. - -@author: Thanh Ha -''' -import sys - -# GStreamer modules -import pygst -pygst.require("0.10") -import gst - -# PyQt modules -from PyQt4.QtCore import SIGNAL - -# Freeseer modules -from freeseer.framework.plugin import IVideoInput -from freeseer.framework.config import Config, options - -# .freeseer-plugin custom modules -import widget - - -def get_devices(): - """ - Returns a list of possible devices detected as a dictionary - - On Linux the dictionary is a key, value pair of: - Device Name : Device Path - - On Windows the dictionary is a key, value pair of: - Device Name : Device Name - - NOTE: GstPropertyProbe has been removed in later versions of Gstreamer - When a new method is available this function will need to be - redesigned: - https://bugzilla.gnome.org/show_bug.cgi?id=678402 - """ - - devicemap = {} - - if sys.platform.startswith("linux"): - videosrc = gst.element_factory_make("v4l2src", "videosrc") - videosrc.probe_property_name('device') - devices = videosrc.probe_get_values_name('device') - - for device in devices: - videosrc.set_property('device', device) - devicemap[videosrc.get_property('device-name')] = device - - elif sys.platform in ["win32", "cygwin"]: - videosrc = gst.element_factory_make("dshowvideosrc", "videosrc") - videosrc.probe_property_name('device-name') - devices = videosrc.probe_get_values_name('device-name') - - for device in devices: - devicemap[device] = device - - return devicemap - - -def get_default_device(): - """Returns a default recording device from get_devices().""" - devicemap = get_devices() - if not devicemap: - return '' - default = devicemap.itervalues().next() - return default - - -class USBSrcConfig(Config): - """USBSrc Configuration settings.""" - device = options.StringOption('') - - -class USBSrc(IVideoInput): - name = "USB Source" - os = ["linux", "linux2", "win32", "cygwin"] - CONFIG_CLASS = USBSrcConfig - - def __init__(self): - super(USBSrc, self).__init__() - - def get_videoinput_bin(self): - """ - Return the video input object in gstreamer bin format. - """ - bin = gst.Bin() # Do not pass a name so that we can load this input more than once. - - videosrc = None - - if not self.config.device: - self.config.device = get_default_device() - - if sys.platform.startswith("linux"): - videosrc = gst.element_factory_make("v4l2src", "videosrc") - videosrc.set_property("device", self.config.device) - elif sys.platform in ["win32", "cygwin"]: - videosrc = gst.element_factory_make("dshowvideosrc", "videosrc") - videosrc.set_property("device-name", self.config.device) - bin.add(videosrc) - - colorspace = gst.element_factory_make("ffmpegcolorspace", "colorspace") - bin.add(colorspace) - videosrc.link(colorspace) - - # Setup ghost pad - pad = colorspace.get_pad("src") - ghostpad = gst.GhostPad("videosrc", pad) - bin.add_pad(ghostpad) - - return bin - - def get_widget(self): - if self.widget is None: - self.widget = widget.ConfigWidget() - - return self.widget - - def __enable_connections(self): - self.widget.connect(self.widget.devicesCombobox, SIGNAL('activated(int)'), self.set_device) - - def widget_load_config(self, plugman): - self.load_config(plugman) - - # Load the combobox with inputs - self.widget.devicesCombobox.clear() - n = 0 - for device, devurl in get_devices().items(): - self.widget.devicesCombobox.addItem(device, devurl) - if devurl == self.config.device: - self.widget.devicesCombobox.setCurrentIndex(n) - n += 1 - - # Finally enable connections - self.__enable_connections() - - def set_device(self, device): - self.config.device = self.widget.devicesCombobox.itemData(device).toString() - self.config.save() - - ### - ### Translations - ### - def retranslate(self): - self.widget.devicesLabel.setText(self.gui.app.translate('plugin-usb', 'Video Device')) diff --git a/src/freeseer/plugins/videoinput/videotestsrc/__init__.py.bak b/src/freeseer/plugins/videoinput/videotestsrc/__init__.py.bak deleted file mode 100644 index ec1572aa..00000000 --- a/src/freeseer/plugins/videoinput/videotestsrc/__init__.py.bak +++ /dev/null @@ -1,116 +0,0 @@ -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://github.com/Freeseer/freeseer/ - -''' -Video Test Source ------------------ - -A video input plugin that displays a test pattern to the screen. Useful for -testing and debugging Freeseer. - -@author: Thanh Ha -''' - -# GStreamer modules -import pygst -pygst.require("0.10") -import gst - -# PyQt4 modules -from PyQt4.QtCore import SIGNAL - -# Freeseer modules -from freeseer.framework.plugin import IVideoInput -from freeseer.framework.config import Config, options - -# .freeseer-plugin custom modules -import widget - -# Patterns -PATTERNS = ["smpte", "snow", "black", "white", "red", "green", "blue", - "circular", "blink", "smpte75", "zone-plate", "gamut", - "chroma-zone-plate", "ball", "smpte100", "bar"] - - -class VideoTestSrcConfig(Config): - """Config settings for VideoTestSrc plugin.""" - live = options.BooleanOption(False) - pattern = options.ChoiceOption(PATTERNS, default="smpte") - - -class VideoTestSrc(IVideoInput): - name = "Video Test Source" - os = ["linux", "linux2", "win32", "cygwin", "darwin"] - CONFIG_CLASS = VideoTestSrcConfig - - def get_videoinput_bin(self): - bin = gst.Bin() # Do not pass a name so that we can load this input more than once. - - videosrc = gst.element_factory_make("videotestsrc", "videosrc") - videosrc.set_property("pattern", self.config.pattern) - videosrc.set_property("is-live", self.config.live) - bin.add(videosrc) - - # Setup ghost pad - pad = videosrc.get_pad("src") - ghostpad = gst.GhostPad("videosrc", pad) - bin.add_pad(ghostpad) - - return bin - - def get_widget(self): - if self.widget is None: - self.widget = widget.ConfigWidget() - - for i in PATTERNS: - self.widget.patternComboBox.addItem(i) - - return self.widget - - def __enable_connections(self): - self.widget.connect(self.widget.liveCheckBox, SIGNAL('toggled(bool)'), self.set_live) - self.widget.connect(self.widget.patternComboBox, SIGNAL('currentIndexChanged(const QString&)'), self.set_pattern) - - def widget_load_config(self, plugman): - self.load_config(plugman) - - self.widget.liveCheckBox.setChecked(bool(self.config.live)) - patternIndex = self.widget.patternComboBox.findText(self.config.pattern) - self.widget.patternComboBox.setCurrentIndex(patternIndex) - - # Finally enable connections - self.__enable_connections() - - def set_live(self, checked): - self.config.live = checked - self.config.save() - - def set_pattern(self, pattern): - self.config.pattern = pattern - self.config.save() - - ### - ### Translations - ### - def retranslate(self): - self.widget.patternLabel.setText(self.gui.app.translate('plugin-videotest', 'Pattern')) - self.widget.liveCheckBox.setText(self.gui.app.translate('plugin-videotest', 'Live Source')) - self.widget.liveCheckBox.setToolTip(self.gui.app.translate('plugin-videotest', 'Act as a live video source')) diff --git a/src/freeseer/plugins/videomixer/pip/__init__.py.bak b/src/freeseer/plugins/videomixer/pip/__init__.py.bak deleted file mode 100644 index 34453e5d..00000000 --- a/src/freeseer/plugins/videomixer/pip/__init__.py.bak +++ /dev/null @@ -1,257 +0,0 @@ -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://github.com/Freeseer/freeseer/ - -''' -Picture-In-Picture ------------------- - -A video mixer plugin which takes 2 video sources and creates a -Picture-In-Picture mode. - -@author: Thanh Ha -''' - -# GStreamer modules -import pygst -pygst.require("0.10") -import gst - -# PyQt modules -from PyQt4.QtCore import SIGNAL - -# Freeseer modules -from freeseer.framework.plugin import IVideoMixer -from freeseer.framework.config import Config, options - -# .freeseer-plugin custom modules -import widget - - -class PictureInPictureConfig(Config): - """Configuration class for PIP plugin.""" - main = options.StringOption("Video Test Source") - pip = options.StringOption("Video Test Source") - - -class PictureInPicture(IVideoMixer): - name = "Picture-In-Picture" - os = ["linux", "linux2", "win32", "cygwin", "darwin"] - widget = None - CONFIG_CLASS = PictureInPictureConfig - WIDTH = 640 - HEIGHT = 480 - - def get_videomixer_bin(self): - bin = gst.Bin() - - videomixer = gst.element_factory_make("videomixer", "videomixer") - bin.add(videomixer) - - colorspace = gst.element_factory_make("ffmpegcolorspace", "colorspace") - bin.add(colorspace) - videomixer.link(colorspace) - - # Picture-In-Picture - videobox = gst.element_factory_make("videobox", "videobox") - bin.add(videobox) - - videobox.link(videomixer) - - videobox2 = gst.element_factory_make("videobox", "videobox2") - bin.add(videobox2) - - videobox2.set_property("alpha", 0.6) - videobox2.set_property("border-alpha", 0) - videobox2.set_property("top", -20) - videobox2.set_property("left", -25) - - videobox2.link(videomixer) - - # Setup ghost pad - sinkpad = videobox.get_pad("sink") - sink_ghostpad = gst.GhostPad("sink_main", sinkpad) - bin.add_pad(sink_ghostpad) - - pip_sinkpad = videobox2.get_pad("sink") - pip_ghostpad = gst.GhostPad("sink_pip", pip_sinkpad) - bin.add_pad(pip_ghostpad) - - srcpad = colorspace.get_pad("src") - src_ghostpad = gst.GhostPad("src", srcpad) - bin.add_pad(src_ghostpad) - - return bin - - def get_inputs(self): - inputs = [(self.config.main, 0), (self.config.pip, 1)] - return inputs - - def load_inputs(self, player, mixer, inputs): - # Load main source - input1 = inputs[0] - - # Create videoscale element in order to scale to dimensions not supported by camera - mainsrc_scale = gst.element_factory_make("videoscale", "mainsrc_scale") - - # Create ffmpegcolorspace element to convert from what camera supports to rgb - mainsrc_colorspace = gst.element_factory_make("ffmpegcolorspace", "mainsrc_colorspace") - - # Create capsfilter for limiting to x-raw-rgb pixel video format and setting dimensions - mainsrc_capsfilter = gst.element_factory_make("capsfilter", "mainsrc_capsfilter") - mainsrc_capsfilter.set_property('caps', - gst.caps_from_string('video/x-raw-rgb, width={}, height={}'.format(self.WIDTH, self.HEIGHT))) - - mainsrc_elements = [input1, mainsrc_scale, mainsrc_capsfilter, mainsrc_colorspace] - - # Add elements to player in list order - map(lambda element: player.add(element), mainsrc_elements) - - # Link elements in a specific order - input1.link(mainsrc_scale) - mainsrc_scale.link(mainsrc_capsfilter) - mainsrc_capsfilter.link(mainsrc_colorspace) - - # Link colorspace element to sink pad for pixel format conversion - srcpad = mainsrc_colorspace.get_pad("src") - sinkpad = mixer.get_pad("sink_main") - srcpad.link(sinkpad) - - # Load the secondary source - input2 = inputs[1] - - # Create gst elements as above, but set smaller dimensions - pipsrc_scale = gst.element_factory_make("videoscale", "pipsrc_scale") - pipsrc_colorspace = gst.element_factory_make("ffmpegcolorspace", "pipsrc_colorspace") - pipsrc_capsfilter = gst.element_factory_make("capsfilter", "pipsrc_capsfilter") - pipsrc_capsfilter.set_property('caps', - gst.caps_from_string('video/x-raw-rgb, width=200, height=150')) - - pipsrc_elements = [input2, pipsrc_scale, pipsrc_capsfilter, pipsrc_colorspace] - - #Add elements to player in list order - map(lambda element: player.add(element), pipsrc_elements) - - # Link elements in specific order - input2.link(pipsrc_scale) - pipsrc_scale.link(pipsrc_capsfilter) - pipsrc_capsfilter.link(pipsrc_colorspace) - - # Link colorspace element to sink pad for pixel format conversion - srcpad = pipsrc_colorspace.get_pad("src") - sinkpad = mixer.get_pad("sink_pip") - srcpad.link(sinkpad) - - def get_widget(self): - - if self.widget is None: - self.widget = widget.ConfigWidget() - - return self.widget - - def __enable_connections(self): - self.widget.connect(self.widget.mainInputComboBox, SIGNAL('currentIndexChanged(const QString&)'), self.set_maininput) - self.widget.connect(self.widget.mainInputSetupButton, SIGNAL('clicked()'), self.open_mainInputSetup) - self.widget.connect(self.widget.pipInputComboBox, SIGNAL('currentIndexChanged(const QString&)'), self.set_pipinput) - self.widget.connect(self.widget.pipInputSetupButton, SIGNAL('clicked()'), self.open_pipInputSetup) - - def widget_load_config(self, plugman): - self.get_config() - - sources = [] - plugins = self.plugman.get_videoinput_plugins() - for plugin in plugins: - sources.append(plugin.plugin_object.get_name()) - - box_config_pairs = [ - (self.widget.mainInputComboBox, self.config.main, self.__enable_maininput_setup), - (self.widget.pipInputComboBox, self.config.pip, self.__enable_pipinput_setup), - ] - - # Load combo boxes with inputs: - for combo_box, config, setup in box_config_pairs: - combo_box.clear() - for i, source in enumerate(sources): - combo_box.addItem(source) - if source == config: # Find the current input source and set it - combo_box.setCurrentIndex(i) - setup(config) - - # Finally enable connections - self.__enable_connections() - - def supports_video_quality(self): - return True - - def get_resolution_pixels(self): - return self.WIDTH * self.HEIGHT - - ### - ### Main Input Functions - ### - - def set_maininput(self, input): - self.config.main = input - self.config.save() - self.__enable_maininput_setup(self.config.main) - - def open_mainInputSetup(self): - plugin_name = str(self.widget.mainInputComboBox.currentText()) - plugin = self.plugman.get_plugin_by_name(plugin_name, "VideoInput") - plugin.plugin_object.set_instance(0) - plugin.plugin_object.get_dialog() - - def __enable_maininput_setup(self, source): - '''Activates the source setup button if it has configurable settings''' - plugin = self.plugman.get_plugin_by_name(source, "VideoInput") - if plugin.plugin_object.get_widget() is not None: - self.widget.mainInputSetupStack.setCurrentIndex(1) - else: - self.widget.mainInputSetupStack.setCurrentIndex(0) - - ### - ### PIP Functions - ### - - def set_pipinput(self, input): - self.config.pip = input - self.config.save() - self.__enable_pipinput_setup(self.config.pip) - - def open_pipInputSetup(self): - plugin_name = str(self.widget.pipInputComboBox.currentText()) - plugin = self.plugman.get_plugin_by_name(plugin_name, "VideoInput") - plugin.plugin_object.set_instance(1) - plugin.plugin_object.get_dialog() - - def __enable_pipinput_setup(self, source): - '''Activates the source setup button if it has configurable settings''' - plugin = self.plugman.get_plugin_by_name(source, "VideoInput") - if plugin.plugin_object.get_widget() is not None: - self.widget.pipInputSetupStack.setCurrentIndex(1) - else: - self.widget.pipInputSetupStack.setCurrentIndex(0) - - ### - ### Translations - ### - def retranslate(self): - self.widget.mainInputLabel.setText(self.gui.app.translate('plugin-pip', 'Main Source')) - self.widget.pipInputLabel.setText(self.gui.app.translate('plugin-pip', 'PIP Source')) diff --git a/src/freeseer/plugins/videomixer/videopassthrough/__init__.py.bak b/src/freeseer/plugins/videomixer/videopassthrough/__init__.py.bak deleted file mode 100644 index dedfeaeb..00000000 --- a/src/freeseer/plugins/videomixer/videopassthrough/__init__.py.bak +++ /dev/null @@ -1,226 +0,0 @@ -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://github.com/Freeseer/freeseer/ - -''' -Video Passthrough ------------------ - -A simple video mixer plugin that takes 1 input and passes it on to the -output plugin. This plugin is capable of configuring the video framerate -as well. - -@author: Thanh Ha -''' - -# GStreamer modules -import pygst -pygst.require("0.10") -import gst -import logging - -# PyQt modules -from PyQt4.QtCore import SIGNAL - -# Freeseer modules -from freeseer.framework.plugin import IVideoMixer -from freeseer.framework.config import Config, options - -# .freeseer-plugin custom modules -import widget - -log = logging.getLogger(__name__) - - -class VideoPassthroughConfig(Config): - """Configuration class for VideoPassthrough plugin.""" - input = options.StringOption("Video Test Source") - input_type = options.StringOption("video/x-raw-rgb") - framerate = options.IntegerOption(30) - resolution = options.ChoiceOption(widget.resmap.keys(), "No Scaling") - - -class VideoPassthrough(IVideoMixer): - name = "Video Passthrough" - os = ["linux", "linux2", "win32", "cygwin", "darwin"] - widget = None - CONFIG_CLASS = VideoPassthroughConfig - - def get_videomixer_bin(self): - bin = gst.Bin() - - # Video Rate - videorate = gst.element_factory_make("videorate", "videorate") - bin.add(videorate) - videorate_cap = gst.element_factory_make("capsfilter", - "video_rate_cap") - videorate_cap.set_property("caps", - gst.caps_from_string("%s, framerate=%d/1" % (self.config.input_type, self.config.framerate))) - bin.add(videorate_cap) - # --- End Video Rate - - # Video Scaler (Resolution) - videoscale = gst.element_factory_make("videoscale", "videoscale") - bin.add(videoscale) - videoscale_cap = gst.element_factory_make("capsfilter", - "videoscale_cap") - - # Change the resolution of the source video. - log.debug("Record Resolution: %s", self.config.resolution) - if self.config.resolution != "No Scaling": - width, height = widget.resmap[self.config.resolution] - videoscale_cap.set_property('caps', - gst.caps_from_string("{}, width={}, height={}" - .format(self.config.input_type, width, height))) - - bin.add(videoscale_cap) - # --- End Video Scaler - - colorspace = gst.element_factory_make("ffmpegcolorspace", "colorspace") - bin.add(colorspace) - - # Link Elements - videorate.link(videorate_cap) - videorate_cap.link(videoscale) - videoscale.link(videoscale_cap) - videoscale_cap.link(colorspace) - - # Setup ghost pad - sinkpad = videorate.get_pad("sink") - sink_ghostpad = gst.GhostPad("sink", sinkpad) - bin.add_pad(sink_ghostpad) - - srcpad = colorspace.get_pad("src") - src_ghostpad = gst.GhostPad("src", srcpad) - bin.add_pad(src_ghostpad) - - return bin - - def get_inputs(self): - inputs = [(self.config.input, 0)] - return inputs - - def get_resolution_pixels(self): - self.get_config() - - if self.config.resolution == "No Scaling": - plugin = self.plugman.get_plugin_by_name(self.config.input, "VideoInput") - return plugin.plugin_object.get_resolution_pixels() - else: - width, height = widget.resmap[str(self.config.resolution)] - return width * height - - def supports_video_quality(self): - self.get_config() - - return self.config.input == "Desktop Source" - - def load_inputs(self, player, mixer, inputs): - # Load source - input = inputs[0] - player.add(input) - input.link(mixer) - - def get_widget(self): - if self.widget is None: - self.widget = widget.ConfigWidget() - return self.widget - - def __enable_connections(self): - self.widget.connect(self.widget.inputCombobox, SIGNAL('currentIndexChanged(const QString&)'), self.set_input) - self.widget.connect(self.widget.framerateSlider, SIGNAL("valueChanged(int)"), self.widget.framerateSpinBox.setValue) - self.widget.connect(self.widget.framerateSpinBox, SIGNAL("valueChanged(int)"), self.widget.framerateSlider.setValue) - self.widget.connect(self.widget.videocolourComboBox, SIGNAL("currentIndexChanged(const QString&)"), self.set_input_type) - self.widget.connect(self.widget.framerateSlider, SIGNAL("valueChanged(int)"), self.set_framerate) - self.widget.connect(self.widget.framerateSpinBox, SIGNAL("valueChanged(int)"), self.set_framerate) - self.widget.connect(self.widget.inputSettingsToolButton, SIGNAL('clicked()'), self.source1_setup) - self.widget.connect(self.widget.videoscaleComboBox, SIGNAL("currentIndexChanged(const QString&)"), self.set_videoscale) - - def widget_load_config(self, plugman): - self.get_config() - - sources = [] - plugins = self.plugman.get_videoinput_plugins() - for plugin in plugins: - sources.append(plugin.plugin_object.get_name()) - - # Load the combobox with inputs - self.widget.inputCombobox.clear() - n = 0 - for i in sources: - self.widget.inputCombobox.addItem(i) - if i == self.config.input: - self.widget.inputCombobox.setCurrentIndex(n) - self.__enable_source_setup(self.config.input) - n = n + 1 - - vcolour_index = self.widget.videocolourComboBox.findText(self.config.input_type) - self.widget.videocolourComboBox.setCurrentIndex(vcolour_index) - - vscale_index = self.widget.videoscaleComboBox.findText(self.config.resolution) - self.widget.videoscaleComboBox.setCurrentIndex(vscale_index) - - # Need to set both the Slider and Spingbox since connections - # are not yet loaded at this point - self.widget.framerateSlider.setValue(self.config.framerate) - self.widget.framerateSpinBox.setValue(self.config.framerate) - - # Finally enable connections - self.__enable_connections() - - def source1_setup(self): - plugin = self.plugman.get_plugin_by_name(self.config.input, "VideoInput") - plugin.plugin_object.get_dialog() - - def set_input(self, input): - self.config.input = input - self.config.save() - self.__enable_source_setup(self.config.input) - self.gui.toggle_video_quality(input == "Desktop Source") - - def __enable_source_setup(self, source): - '''Activates the source setup button if it has configurable settings''' - plugin = self.plugman.get_plugin_by_name(source, "VideoInput") - if plugin.plugin_object.get_widget() is not None: - self.widget.inputSettingsStack.setCurrentIndex(1) - else: - self.widget.inputSettingsStack.setCurrentIndex(0) - - def set_input_type(self, input_type): - self.config.input_type = input_type - self.config.save() - - def set_framerate(self, framerate): - self.config.framerate = framerate - self.config.save() - - def set_videoscale(self, resolution): - self.config.resolution = resolution - self.config.save() - self.gui.update_video_quality() - - ### - ### Translations - ### - def retranslate(self): - self.widget.inputLabel.setText(self.gui.app.translate('plugin-video-passthrough', 'Video Input')) - self.widget.videocolourLabel.setText(self.gui.app.translate('plugin-video-passthrough', 'Colour Format')) - self.widget.framerateLabel.setText(self.gui.app.translate('plugin-video-passthrough', 'Framerate')) - self.widget.videoscaleLabel.setText(self.gui.app.translate('plugin-video-passthrough', 'Video Scale')) diff --git a/src/freeseer/tests/framework/config/options/test_choice.py.bak b/src/freeseer/tests/framework/config/options/test_choice.py.bak deleted file mode 100644 index 5b26650a..00000000 --- a/src/freeseer/tests/framework/config/options/test_choice.py.bak +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013, 2014 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -import unittest - -from jsonschema import validate -from jsonschema import ValidationError - -from freeseer.framework.config.options import ChoiceOption -from freeseer.tests.framework.config.options import OptionTest - - -class TestChoiceOptionNoDefault(unittest.TestCase, OptionTest): - """Tests ChoiceOption without a default value.""" - - valid_success = [ - 'hello', - 'world', - ] - valid_failure = [ - 'hello1', - '1hello', - 'w0rld', - ] - - encode_success = zip(valid_success, valid_success) - - decode_success = zip(valid_success, valid_success) - decode_failure = valid_failure - - def setUp(self): - self.option = ChoiceOption([ - 'hello', - 'world', - ]) - - def test_schema(self): - """Tests a ChoiceOption schema method.""" - expected = { - 'enum': [ - 'hello', - 'world', - ], - } - self.assertRaises(ValidationError, validate, 'error', self.option.schema()) - self.assertIsNone(validate('world', self.option.schema())) - self.assertDictEqual(self.option.schema(), expected) - - -class TestChoiceOptionWithDefault(TestChoiceOptionNoDefault): - """Tests ChoiceOption with a default value.""" - - def setUp(self): - self.option = ChoiceOption([ - 'hello', - 'world', - ], 'hello') - - def test_default(self): - """Tests that the default was set correctly.""" - self.assertEqual(self.option.default, 'hello') - - def test_schema(self): - """Tests a ChoiceOption schema method.""" - expected = { - 'default': 'hello', - 'enum': [ - 'hello', - 'world', - ], - } - self.assertRaises(ValidationError, validate, 'error', self.option.schema()) - self.assertIsNone(validate('world', self.option.schema())) - self.assertDictEqual(self.option.schema(), expected) diff --git a/src/freeseer/tests/framework/config/options/test_float.py.bak b/src/freeseer/tests/framework/config/options/test_float.py.bak deleted file mode 100644 index 54ba660f..00000000 --- a/src/freeseer/tests/framework/config/options/test_float.py.bak +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2014 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -import unittest - -from jsonschema import validate -from jsonschema import ValidationError - -from freeseer.framework.config.options import FloatOption -from freeseer.tests.framework.config.options import OptionTest - - -class TestFloatOptionNoDefault(unittest.TestCase, OptionTest): - """Tests FloatOption without a default value.""" - - valid_success = [x / 10.0 for x in xrange(-100, 100)] - - encode_success = zip(valid_success, map(str, valid_success)) - - decode_success = zip(map(str, valid_success), valid_success) - decode_failure = [ - 'hello', - '1world', - 'test2', - ] - - def setUp(self): - self.option = FloatOption() - - def test_schema(self): - """Tests FloatOption schema method.""" - self.assertRaises(ValidationError, validate, 'error', self.option.schema()) - self.assertIsNone(validate(5.5, self.option.schema())) - self.assertDictEqual(self.option.schema(), {'type': 'number'}) - - -class TestFloatOptionWithDefault(TestFloatOptionNoDefault): - """Tests FloatOption with a default value.""" - - def setUp(self): - self.option = FloatOption(1234.5) - - def test_default(self): - """Tests that the default was set correctly.""" - self.assertEqual(self.option.default, 1234.5) - - def test_schema(self): - """Tests FloatOption schema method.""" - self.assertRaises(ValidationError, validate, 'error', self.option.schema()) - self.assertIsNone(validate(5.0, self.option.schema())) - self.assertDictEqual(self.option.schema(), {'default': 1234.5, 'type': 'number'}) diff --git a/src/freeseer/tests/framework/config/options/test_folder.py.bak b/src/freeseer/tests/framework/config/options/test_folder.py.bak deleted file mode 100644 index 7dbc540d..00000000 --- a/src/freeseer/tests/framework/config/options/test_folder.py.bak +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013, 2014 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -import os -import shutil -import tempfile -import unittest - -from jsonschema import validate -from jsonschema import ValidationError - -from freeseer.framework.config.options import FolderOption -from freeseer.tests.framework.config.options import OptionTest - - -class TestFolderOptionNoDefault(unittest.TestCase, OptionTest): - """Tests FolderOption without a default value.""" - - valid_success = [ - '/tmp', - ] - valid_failure = [ - '/tmp/1', - ] - - encode_success = zip(valid_success, valid_success) - - decode_success = zip(valid_success, valid_success) - decode_failure = valid_failure - - def setUp(self): - self.option = FolderOption() - - def test_presentation(self): - path = tempfile.mkdtemp() - shutil.rmtree(path) - - presentation_value = self.option.presentation(path) - self.assertEqual(presentation_value, path) - self.assertFalse(os.path.exists(presentation_value)) - - def test_schema(self): - """Tests StringOption schema method.""" - self.assertRaises(ValidationError, validate, 1, self.option.schema()) - self.assertIsNone(validate('/tmp2', self.option.schema())) - self.assertDictEqual(self.option.schema(), {'type': 'string'}) - - -class TestFolderOptionAutoCreate(TestFolderOptionNoDefault): - """Tests FolderOption without a default value, and with auto_create turned on.""" - - valid_failure = [] - - decode_failure = [] - - def setUp(self): - self.option = FolderOption(auto_create=True) - - def test_presentation(self): - path = tempfile.mkdtemp() - shutil.rmtree(path) - - presentation_value = self.option.presentation(path) - self.assertEqual(presentation_value, path) - self.assertTrue(os.path.exists(presentation_value)) - - shutil.rmtree(path) - - -class TestFolderOptionWithDefault(TestFolderOptionNoDefault): - """Tests FolderOption with a default value.""" - - def setUp(self): - self.option = FolderOption('/tmp') - - def test_default(self): - """Tests that the default was set correctly.""" - self.assertEqual(self.option.default, '/tmp') - - def test_schema(self): - """Tests StringOption schema method.""" - self.assertRaises(ValidationError, validate, 1, self.option.schema()) - self.assertIsNone(validate('/tmp2', self.option.schema())) - self.assertDictEqual(self.option.schema(), {'default': '/tmp', 'type': 'string'}) diff --git a/src/freeseer/tests/framework/config/options/test_integer.py.bak b/src/freeseer/tests/framework/config/options/test_integer.py.bak deleted file mode 100644 index 237efd33..00000000 --- a/src/freeseer/tests/framework/config/options/test_integer.py.bak +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2011, 2013, 2014 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -import unittest - -from jsonschema import validate -from jsonschema import ValidationError - -from freeseer.framework.config.options import IntegerOption -from freeseer.tests.framework.config.options import OptionTest - - -class TestIntegerOptionNoDefault(unittest.TestCase, OptionTest): - """Tests IntegerOption without a default value.""" - - valid_success = range(-1000, 1000) - - encode_success = zip(valid_success, map(str, valid_success)) - - decode_success = zip(map(str, valid_success), valid_success) - decode_failure = [ - 'hello', - '1world', - 'test2', - ] - - def setUp(self): - self.option = IntegerOption() - - def test_schema(self): - """Tests IntegerOption schema method.""" - self.assertRaises(ValidationError, validate, 1.0, self.option.schema()) - self.assertIsNone(validate(5, self.option.schema())) - self.assertDictEqual(self.option.schema(), {'type': 'integer'}) - - -class TestIntegerOptionWithDefault(TestIntegerOptionNoDefault): - """Tests IntegerOption with a default value.""" - - def setUp(self): - self.option = IntegerOption(1234) - - def test_default(self): - """Tests that the default was set correctly.""" - self.assertEqual(self.option.default, 1234) - - def test_schema(self): - """Tests IntegerOption schema method.""" - self.assertRaises(ValidationError, validate, 1.0, self.option.schema()) - self.assertIsNone(validate(5, self.option.schema())) - self.assertDictEqual(self.option.schema(), {'default': 1234, 'type': 'integer'}) diff --git a/src/freeseer/tests/framework/test_multimedia.py.bak b/src/freeseer/tests/framework/test_multimedia.py.bak deleted file mode 100644 index eed754ac..00000000 --- a/src/freeseer/tests/framework/test_multimedia.py.bak +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2012, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -import shutil -import tempfile -import unittest - -import pygst -pygst.require("0.10") -import gst - -from freeseer.framework.config.profile import ProfileManager -from freeseer.framework.multimedia import Multimedia -from freeseer.framework.plugin import PluginManager -from freeseer import settings - - -class TestMultimedia(unittest.TestCase): - - def setUp(self): - self.profile_manager = ProfileManager(tempfile.mkdtemp()) - self.temp_video_dir = tempfile.mkdtemp() - profile = self.profile_manager.get('testing') - config = profile.get_config('freeseer.conf', settings.FreeseerConfig, ['Global'], read_only=True) - config.videodir = self.temp_video_dir - plugin_manager = PluginManager(profile) - self.multimedia = Multimedia(config, plugin_manager) - - def tearDown(self): - shutil.rmtree(self.temp_video_dir) - shutil.rmtree(self.profile_manager._base_folder) - - def test_load_backend(self): - self.multimedia.load_backend(filename=u"test.ogg") - - def test_record_functions(self): - self.multimedia.load_backend(filename=u"test.ogg") - self.multimedia.record() - self.multimedia.pause() - self.multimedia.stop() - - def test_current_state_is_record(self): - self.multimedia.record() - self.assertEqual(self.multimedia.current_state, self.multimedia.RECORD) - self.assertEqual(self.multimedia.player.get_state()[1], gst.STATE_PLAYING) - - def test_current_state_is_pause(self): - self.multimedia.pause() - self.assertEqual(self.multimedia.current_state, self.multimedia.PAUSE) - self.assertEqual(self.multimedia.player.get_state()[1], gst.STATE_PAUSED) - - def test_current_state_is_not_stop(self): - self.multimedia.player.set_state(gst.STATE_NULL) - self.multimedia.stop() - self.assertNotEqual(self.multimedia.current_state, self.multimedia.STOP) - self.assertEqual(self.multimedia.player.get_state()[1], gst.STATE_NULL) diff --git a/src/freeseer/tests/framework/test_presentation.py.bak b/src/freeseer/tests/framework/test_presentation.py.bak deleted file mode 100644 index 5d882254..00000000 --- a/src/freeseer/tests/framework/test_presentation.py.bak +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2012, 2013 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -import unittest - -from freeseer.framework.presentation import Presentation - - -# -# Note: The Presentation class doesn't really need to be tested -# since all the functionality is set and referenced using -# public attributes. -# -# This test class was implemented as a quick-and-easy demo -# and proof-of-concept for Freeseer's test framework -# - - -class TestPresentation(unittest.TestCase): - - def setUp(self): - ''' - Generic unittest.TestCase.setUp() - ''' - - self.pres = Presentation("John Doe", event="haha", startTime="NOW", endTime="Later") - - def test_correct_time_set(self): - self.assertTrue(self.pres.startTime == "NOW") - self.assertTrue(self.pres.endTime == "Later") - - def test_speaker_not_first_param(self): - self.assertNotEquals(self.pres.speaker, "John Doe") - - def test_event_is_default(self): - self.assertTrue(self.pres.event == "haha") - - def test_room_is_default(self): - self.assertTrue(self.pres.room == "Default") diff --git a/src/freeseer/tests/framework/test_youtube.py.bak b/src/freeseer/tests/framework/test_youtube.py.bak deleted file mode 100644 index 2a7f3cd9..00000000 --- a/src/freeseer/tests/framework/test_youtube.py.bak +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2014 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -import os -import unittest -from mock import Mock -import pytest - -from freeseer.framework.youtube import Response -from freeseer.framework.youtube import YoutubeService - - -class TestYoutubeService(unittest.TestCase): - SAMPLE_VIDEO = os.path.join(os.path.dirname(__file__), 'sample_video.ogg') - SAMPLE_VIDEO_METADATA = { - 'tags': [ - 'Freeseer', - 'FOSSLC', - 'Open Source', - ], - 'categoryId': 27, - 'description': 'At Test by Alex recorded on 2014-03-09', - 'title': u'Test', - } - - def test_get_metadata(self): - """Test retrieval of metadata from video file. - - Case: Returned metadata should be equal to sample video's metadata.""" - metadata = YoutubeService.get_metadata(self.SAMPLE_VIDEO) - self.assertDictEqual(self.SAMPLE_VIDEO_METADATA, metadata) - - def test_upload_video(self): - """Test uploading a video file using mocks""" - youtube = YoutubeService() - youtube.upload_video = Mock(return_value=(Response.SUCCESS, None)) - response_code, response = youtube.upload_video(self.SAMPLE_VIDEO) - youtube.upload_video.assert_called_with(self.SAMPLE_VIDEO) - self.assertEqual(Response.SUCCESS, response_code) - - -@pytest.mark.parametrize("video, expected", [ - ("/path/to/test.ogg", True), - ("test.webm", True), - ("asdfg.qwergb", False), -]) -def test_valid_video_file(video, expected): - """Tests valid_video_file function for all test cases.""" - assert YoutubeService.valid_video_file(video) == expected diff --git a/src/freeseer/tests/frontend/configtool/test_config_tool.py.bak b/src/freeseer/tests/frontend/configtool/test_config_tool.py.bak index 0665dafc..e69de29b 100644 --- a/src/freeseer/tests/frontend/configtool/test_config_tool.py.bak +++ b/src/freeseer/tests/frontend/configtool/test_config_tool.py.bak @@ -1,381 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2012, 2013, 2014 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -from mock import MagicMock -import shutil -import tempfile -import unittest - -from PyQt4 import Qt -from PyQt4 import QtCore -from PyQt4 import QtGui -from PyQt4 import QtTest - -from freeseer.framework.config.profile import ProfileManager -from freeseer.frontend.configtool.configtool import ConfigToolApp -from freeseer import settings - - -class TestConfigToolApp(unittest.TestCase): - """ - Test suite to verify the functionality of the ConfigToolApp class. - - Tests interact like an end user (using QtTest). Expect the app to be rendered. - - NOTE: most tests will follow this format: - 1. Get current config setting - 2. Make UI change (config change happens immediately) - 3. Reparse config file - 4. Test that has changed (using the previous and new) - """ - - def setUp(self): - """ - Stardard init method: runs before each test_* method - - Initializes a QtGui.QApplication and ConfigToolApp object. - ConfigToolApp.show() causes the UI to be rendered. - """ - self.profile_manager = ProfileManager(tempfile.mkdtemp()) - profile = self.profile_manager.get('testing') - config = profile.get_config('freeseer.conf', settings.FreeseerConfig, storage_args=['Global'], read_only=False) - - self.app = QtGui.QApplication([]) - self.config_tool = ConfigToolApp(profile, config) - self.config_tool.show() - - def tearDown(self): - """ - Standard tear down method. Runs after each test_* method. - - This method closes the ConfigToolApp by clicking the "close" button - """ - - QtTest.QTest.mouseClick(self.config_tool.mainWidget.closePushButton, Qt.Qt.LeftButton) - shutil.rmtree(self.profile_manager._base_folder) - del self.config_tool.app - self.app.deleteLater() - - def test_default_widget(self): - self.assertEqual(self.config_tool.currentWidget, self.config_tool.generalWidget) - - def check_combobox_corner_cases_frontend(self, combobox_widget): - """ - Tests that a given QtComboBox has: - - a default selected item - - does not blow up on the minimum and maximum index item in the combobox list - """ - index = combobox_widget.currentIndex() - combobox_widget.setCurrentIndex(0) - self.assertEquals(combobox_widget.currentIndex(), 0) - self.assertIsNot(combobox_widget.currentText(), None) - combobox_widget.setCurrentIndex(combobox_widget.count() - 1) - self.assertEquals(combobox_widget.currentIndex(), (combobox_widget.count() - 1)) - self.assertIsNot(combobox_widget.currentText(), None) - combobox_widget.setCurrentIndex(index) - self.assertEquals(combobox_widget.currentIndex(), index) - self.assertIsNot(combobox_widget.currentText(), None) - - def select_general_settings_tab(self): - # Select "General" tab - item = self.config_tool.mainWidget.optionsTreeWidget.findItems(self.config_tool.generalString, - QtCore.Qt.MatchExactly) - self.assertFalse(not item or item[0] is None) - self.config_tool.mainWidget.optionsTreeWidget.setCurrentItem(item[0]) - QtTest.QTest.mouseClick(self.config_tool.mainWidget.optionsTreeWidget, Qt.Qt.LeftButton) - - def test_general_settings_checkbox(self): - """ - Test the config tool's General Tab auto-hide system tray icon checkbox with simulated user input - """ - self.select_general_settings_tab() - # Test disabled checkbox - self.config_tool.currentWidget.autoHideCheckBox.setChecked(False) - self.assertEqual(self.config_tool.currentWidget.autoHideCheckBox.checkState(), QtCore.Qt.Unchecked) - self.assertFalse(self.config_tool.config.auto_hide) - - # Test enabled checkbox - self.config_tool.currentWidget.autoHideCheckBox.setChecked(True) - self.assertEqual(self.config_tool.currentWidget.autoHideCheckBox.checkState(), QtCore.Qt.Checked) - self.assertTrue(self.config_tool.config.auto_hide) - - def test_general_settings_dropdown_menu(self): - """ - Test the config tool's General Tab language selection drop down menu with simulated user input - """ - self.select_general_settings_tab() - # Test dropdown menu - language_combo_box = self.config_tool.generalWidget.languageComboBox - self.check_combobox_corner_cases_frontend(language_combo_box) - - QtTest.QTest.keyClick(language_combo_box, QtCore.Qt.Key_PageUp) # Test simulated user interaction with drop down list - for i in range(language_combo_box.count() - 2): - last_language = self.config_tool.config.default_language - QtTest.QTest.keyClick(language_combo_box, QtCore.Qt.Key_Down) - current_language = self.config_tool.config.default_language - self.assertNotEqual(last_language, current_language) - - # Test frontend constants - self.assertNotEqual(language_combo_box.findText('Deutsch'), -1) # Assert there are multiple languages in the menu - self.assertNotEqual(language_combo_box.findText('English'), -1) - self.assertNotEqual(language_combo_box.findText('Nederlands'), -1) - - def test_general_settings_help_reset(self): - """ - Test the config tool's General Tab help link and reset button with simulated user input - """ - self.select_general_settings_tab() - # Test that Help us translate tries to open a web url. - QtGui.QDesktopServices.openUrl = MagicMock(return_value=None) - self.config_tool.open_translate_url() - QtGui.QDesktopServices.openUrl.assert_called_with( - QtCore.QUrl("http://freeseer.readthedocs.org/en/latest/contribute/translation.html") - ) - - # Reset - # The reset test should set the backend config_tool values, simulate a user clicking through the reset popup and - # verify that the backend config_tool values have been set to defaults. - # TODO: FIXME. Related to gh#631. The buttons on the dialog cause segfaults in the test environment and prevent - # the test from being implemented at the present time. - # self.config_tool.confirm_reset() - - def select_recording_tab(self): - """ - Helper function used to open up the recording tab for recording tab related tests - """ - item = self.config_tool.mainWidget.optionsTreeWidget.findItems(self.config_tool.avString, - QtCore.Qt.MatchExactly) - self.assertFalse(not item or item[0] is None) - self.config_tool.mainWidget.optionsTreeWidget.setCurrentItem(item[0]) - QtTest.QTest.mouseClick(self.config_tool.mainWidget.optionsTreeWidget, Qt.Qt.LeftButton) - self.assertEqual(self.config_tool.currentWidget, self.config_tool.avWidget) - - def test_recording_settings_file(self): - """ - Simulates a user interacting with the config tool record to file settings. - """ - self.select_recording_tab() - # Test disabled checkbox - self.config_tool.currentWidget.fileGroupBox.setChecked(False) - self.assertFalse(self.config_tool.config.record_to_file) - - # Test enabled checkbox - self.config_tool.currentWidget.fileGroupBox.setChecked(True) - self.assertTrue(self.config_tool.config.record_to_file) - self.assertIn(self.config_tool.config.record_to_file_plugin, ['Ogg Output', 'WebM Output', 'Raw Output']) - - # Test combo box - file_combo_box = self.config_tool.avWidget.fileComboBox - self.check_combobox_corner_cases_frontend(file_combo_box) - QtTest.QTest.keyClick(file_combo_box, QtCore.Qt.Key_PageUp) # Simulate user input to test backend and frontend - for i in range(file_combo_box.count() - 2): - last_plugin = self.config_tool.config.record_to_file_plugin - QtTest.QTest.keyClick(file_combo_box, QtCore.Qt.Key_Down) - current_plugin = self.config_tool.config.record_to_file_plugin - self.assertNotEqual(last_plugin, current_plugin) - - # Test frontend text values - self.assertNotEqual(file_combo_box.findText('Ogg Output'), -1) - self.assertNotEqual(file_combo_box.findText('WebM Output'), -1) - self.assertNotEqual(file_combo_box.findText('Raw Output'), -1) - - def test_recording_settings_stream(self): - """ - Simulates a user interacting with the config tool record to output stream settings. - """ - self.select_recording_tab() - # Test disabled checkbox - self.config_tool.currentWidget.streamGroupBox.setChecked(False) - self.assertFalse(self.config_tool.config.record_to_stream) - - # Test enabled checkbox - self.config_tool.currentWidget.streamGroupBox.setChecked(True) - self.assertTrue(self.config_tool.config.record_to_stream) - - # Test combo box - stream_combo_box = self.config_tool.avWidget.streamComboBox - self.check_combobox_corner_cases_frontend(stream_combo_box) - QtTest.QTest.keyClick(stream_combo_box, QtCore.Qt.Key_PageUp) # Simulate user input to test backend and frontend - for i in range(stream_combo_box.count() - 2): - last_plugin = self.config_tool.plugman.get_plugin_by_name(stream_combo_box.currentText(), 'Output') - QtTest.QTest.keyClick(stream_combo_box, QtCore.Qt.Key_Down) - current_plugin = self.config_tool.plugman.get_plugin_by_name(stream_combo_box.currentText(), 'Output') - self.assertNotEqual(last_plugin, current_plugin) - - # Test frontend text values - self.assertNotEqual(stream_combo_box.findText('RTMP Streaming'), -1) - self.assertNotEqual(stream_combo_box.findText('Ogg Icecast'), -1) - - def test_recording_settings_video_input(self): - """ - Simulates a user interacting with the config tool record to video input setting. - """ - - self.select_recording_tab() - # Test disabled checkbox - self.config_tool.currentWidget.videoGroupBox.setChecked(False) - self.assertFalse(self.config_tool.config.enable_video_recording) - - # Test enabled checkbox - self.config_tool.currentWidget.videoGroupBox.setChecked(True) - self.assertTrue(self.config_tool.config.enable_video_recording) - self.assertIn(self.config_tool.config.videomixer, ['Video Passthrough', 'Picture-In-Picture']) - - # Test combo box - video_mixer_combo_box = self.config_tool.avWidget.videoMixerComboBox - self.check_combobox_corner_cases_frontend(video_mixer_combo_box) - QtTest.QTest.keyClick(video_mixer_combo_box, QtCore.Qt.Key_PageUp) # Simulate user to test backend/frontend - for i in range(video_mixer_combo_box.count() - 2): - last_plugin = self.config_tool.videomixer - QtTest.QTest.keyClick(video_mixer_combo_box, QtCore.Qt.Key_Down) - current_plugin = self.config_tool.videomixer - self.assertNotEqual(last_plugin, current_plugin) - - # Test frontend text values - self.assertTrue(video_mixer_combo_box.findText('Video Passthrough') != -1) - self.assertTrue(video_mixer_combo_box.findText('Picture-In-Picture') != -1) - - def test_recording_settings_audio_input(self): - """ - Simulates a user interacting with the config tool record to audio input settings. - """ - self.select_recording_tab() - # Test disabled checkbox - self.config_tool.currentWidget.audioGroupBox.setChecked(False) - self.assertFalse(self.config_tool.config.enable_audio_recording) - - # Test enabled checkbox - self.config_tool.currentWidget.audioGroupBox.setChecked(True) - self.assertTrue(self.config_tool.config.enable_audio_recording) - self.assertIn(self.config_tool.config.audiomixer, ['Audio Passthrough', 'Multiple Audio Inputs']) - - # Test combo box - audio_mixer_combo_box = self.config_tool.avWidget.audioMixerComboBox - self.check_combobox_corner_cases_frontend(audio_mixer_combo_box) - QtTest.QTest.keyClick(audio_mixer_combo_box, QtCore.Qt.Key_PageUp) # Simulate user to test backend/frontend - for i in range(audio_mixer_combo_box.count() - 2): - last_plugin = self.config_tool.audiomixer - QtTest.QTest.keyClick(audio_mixer_combo_box, QtCore.Qt.Key_Down) - current_plugin = self.config_tool.audiomixer - self.assertNotEqual(last_plugin, current_plugin) - - # Test frontend text values - self.assertNotEqual(audio_mixer_combo_box.findText('Audio Passthrough'), -1) - self.assertNotEqual(audio_mixer_combo_box.findText('Multiple Audio Inputs'), -1) - - def test_plugin_settings(self): - """ - Simulate a user going through the list of plugins in the plugin settings page of the config tool. - - This test builds a dictionary value based on a traversal of the QTreeWidget that contains the plugins displayed - in the GUI. The dictionary is then used to assert that plugins exist in the appropriate categories. This test - also uses a map to relate the GUI's category names to the backend's category names since the two differ. - """ - item = self.config_tool.mainWidget.optionsTreeWidget.findItems(self.config_tool.pluginsString, - QtCore.Qt.MatchExactly) - self.assertFalse(not item or item[0] is None) - self.config_tool.mainWidget.optionsTreeWidget.setCurrentItem(item[0]) - QtTest.QTest.mouseClick(self.config_tool.mainWidget.optionsTreeWidget, Qt.Qt.LeftButton) - self.assertEqual(self.config_tool.currentWidget, self.config_tool.pluginWidget) - - # GUI names categories are different than the backend. Define a translation from GUI -> backend - gui_category_to_backend_category = { - 'Audio Input': 'AudioInput', - 'Audio Mixer': 'AudioMixer', - 'Video Input': 'VideoInput', - 'Video Mixer': 'VideoMixer', - 'Output': 'Output', - 'Input': 'Importer' - } - QtTest.QTest.keyClick(self.config_tool.pluginWidget.list, QtCore.Qt.Key_PageUp) - root = self.config_tool.pluginWidget.list.invisibleRootItem() - plugin_category = {} # A dictionary of plugin -> category - for category_index in range(root.childCount()): - category = str(self.config_tool.pluginWidget.list.currentItem().text(0)) - QtTest.QTest.keyClick(self.config_tool.pluginWidget.list, QtCore.Qt.Key_Down) - for plugin_index in range(root.child(category_index).childCount()): - plugin_name = str(self.config_tool.pluginWidget.list.currentItem().text(0)) - plugin_category[plugin_name] = gui_category_to_backend_category[category] - QtTest.QTest.keyClick(self.config_tool.pluginWidget.list, QtCore.Qt.Key_Down) - - # Assert expected categories exist. - self.assertIn('AudioInput', plugin_category.values()) - self.assertIn('AudioMixer', plugin_category.values()) - self.assertIn('VideoInput', plugin_category.values()) - self.assertIn('VideoMixer', plugin_category.values()) - self.assertIn('Output', plugin_category.values()) - self.assertIn('Importer', plugin_category.values()) - - for category in ['AudioInput', 'AudioMixer', 'VideoInput', 'VideoMixer', 'Output', 'Importer']: - for plugin in self.config_tool.get_plugins(category): - self.assertIn(plugin.plugin_object.name, plugin_category) - self.assertEqual(plugin_category[plugin.plugin_object.name], category) - self.assertEqual(category, plugin.plugin_object.CATEGORY) - - def test_logger_settings(self): - """ - Tests for config tool's Logger tab - - Needs to be tested differently since the - Config instance isn't affected by changes in this tab. - """ - - # TODO - pass - - def test_close_configtool(self): - """ - Tests for config tool's close button - """ - - self.assertTrue(self.config_tool.mainWidget.isVisible()) - QtTest.QTest.mouseClick(self.config_tool.mainWidget.closePushButton, Qt.Qt.LeftButton) - self.assertFalse(self.config_tool.mainWidget.isVisible()) - - def test_file_menu_quit(self): - """ - Tests for config tool's File->Quit - """ - - self.assertTrue(self.config_tool.isVisible()) - - # File->Quit - self.config_tool.actionExit.trigger() - self.assertFalse(self.config_tool.isVisible()) - - def test_help_menu_about(self): - """ - Tests for config tool's Help->About - """ - - self.assertTrue(self.config_tool.isVisible()) - - # Help->About - self.config_tool.actionAbout.trigger() - self.assertFalse(self.config_tool.hasFocus()) - self.assertTrue(self.config_tool.aboutDialog.isVisible()) - - # Click "Close" - QtTest.QTest.mouseClick(self.config_tool.aboutDialog.closeButton, Qt.Qt.LeftButton) - self.assertFalse(self.config_tool.aboutDialog.isVisible()) diff --git a/src/freeseer/tests/frontend/controller/test_server.py.bak b/src/freeseer/tests/frontend/controller/test_server.py.bak index ff4102f8..e69de29b 100644 --- a/src/freeseer/tests/frontend/controller/test_server.py.bak +++ b/src/freeseer/tests/frontend/controller/test_server.py.bak @@ -1,339 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2014 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -import json -import os - -import pytest - -from freeseer import settings -from freeseer.framework.config.profile import ProfileManager -from freeseer.framework.multimedia import Multimedia -from freeseer.framework.plugin import PluginManager -from freeseer.frontend.controller import server - - -class MockMedia: - def __init__(self): - self.current_state = Multimedia.NULL - self.num_times_record_called = 0 - self.num_times_stop_called = 0 - self.num_times_pause_called = 0 - - def record(self): - self.num_times_record_called += 1 - - def pause(self): - self.num_times_pause_called += 1 - - def stop(self): - self.num_times_stop_called += 1 - - -class TestServerApp: - ''' - Test cases for Server. - ''' - - @pytest.fixture(scope='module') - def test_client(self): - server.app.config['TESTING'] = True - server.app.storage_file_path = "test_storage_file" - return server.app.test_client() - - @pytest.fixture(scope='function', autouse=True) - def recording(self, request, test_client, monkeypatch, tmpdir): - - recording = server.app.blueprints['recording'] - - monkeypatch.setattr(settings, 'configdir', str(tmpdir.mkdir('configdir'))) - test_client.get('/recordings') - - profile_manager = ProfileManager(str(tmpdir.mkdir('profile'))) - recording.profile = profile_manager.get('testing') - recording.config = recording.profile.get_config('freeseer.conf', settings.FreeseerConfig, ['Global'], read_only=True) - recording.config.videodir = str(tmpdir.mkdir('Videos')) - recording.plugin_manager = PluginManager(recording.profile) - - return recording - - @pytest.fixture(scope='function') - def mock_media_dict(self, request, recording): - mock_media_1 = MockMedia() - mock_media_2 = MockMedia() - - filepath1 = os.path.join(recording.config.videodir, 'mock_media_1') - filepath2 = os.path.join(recording.config.videodir, 'mock_media_2') - - recording.media_info['1'] = { - 'status': Multimedia.NULL, - 'filename': 'mock_media_1', - 'filepath': filepath1, - } - recording.media_info['2'] = { - 'status': Multimedia.NULL, - 'filename': 'mock_media_2', - 'filepath': filepath2, - } - - recording.media_dict = { - 1: mock_media_1, - 2: mock_media_2, - } - - def clear_media(): - recording.media_info.clear() - recording.media_dict = {} - request.addfinalizer(clear_media) - - return recording.media_dict - - def test_get_all_recordings_empty_media_dict(self, test_client): - ''' - Tests GET request retrieving all recordings with from an empty media dict - ''' - response = test_client.get('/recordings') - response_data = json.loads(response.data) - assert response_data == {'recordings': []} - - def test_get_nonexistent_recording_id(self, test_client): - ''' - Tests GET request retrieving non-existent recording - ''' - response = test_client.get('/recordings/1') - response_data = json.loads(response.data) - assert response_data['error_code'] == 404 - assert response.status_code == 404 - - def test_get_all_recordings(self, test_client, mock_media_dict): - ''' - Tests GET request all recordings with 2 entries in media dict - ''' - response = test_client.get('/recordings') - response_data = json.loads(response.data) - assert response_data == {'recordings': [1, 2]} - - def test_get_recording_id(self, test_client, mock_media_dict): - ''' - Tests GET request specific valid recording - ''' - response = test_client.get('/recordings/1') - response_data = json.loads(response.data) - assert response.status_code == 200 - - assert response_data == { - 'filename': 'mock_media_1', - 'filesize': 'NA', - 'id': 1, - 'status': 'NULL', - } - - def test_get_invalid_recording_id(self, test_client, mock_media_dict): - ''' - Tests GET request with an invalid id (a non integer id) - ''' - response = test_client.get('/recordings/abc') - assert response.status_code == 404 - - response = test_client.get('/recordings/1.0') - assert response.status_code == 404 - - def test_patch_no_id(self, test_client): - ''' - Tests a PATCH request without a recording id - ''' - response = test_client.patch('/recordings') - assert response.status_code == 405 - - def test_patch_nonexistant_id(self, test_client, mock_media_dict): - ''' - Tests a PATCH request with non-existant recording id - ''' - response = test_client.patch('/recordings/100', data={'command': 'start'}) - assert response.status_code == 404 - - def test_patch_no_command(self, test_client, mock_media_dict): - ''' - Tests a PATCH request without a command - ''' - response = test_client.patch('/recordings/1') - assert response.status_code == 400 - - def test_patch_invalid_command(self, test_client, mock_media_dict): - ''' - Tests a Patch request with an invalid command - ''' - response = test_client.patch('/recordings/1', data={'command': 'invalid command'}) - assert response.status_code == 400 - - @pytest.mark.parametrize("current_state", [Multimedia.NULL, Multimedia.PAUSE]) - def test_patch_start(self, test_client, recording, mock_media_dict, current_state): - ''' - Tests a Patch request to start a recording - ''' - mock_media_dict[1].current_state = current_state - response = test_client.patch('/recordings/1', data={'command': 'start'}) - assert response.status_code == 200 - assert mock_media_dict[1].num_times_record_called == 1 - - @pytest.mark.parametrize("current_state", [Multimedia.STOP, Multimedia.RECORD]) - def test_patch_start_invalid(self, test_client, mock_media_dict, current_state): - ''' - Tests a Patch request to start a recording with an invalid media state - ''' - mock_media_dict[1].current_state = current_state - response = test_client.patch('/recordings/1', data={'command': 'start'}) - assert response.status_code == 400 - assert mock_media_dict[1].num_times_record_called == 0 - - def test_patch_pause(self, test_client, mock_media_dict): - ''' - Tests a Patch request to pause a recording - ''' - mock_media_dict[1].current_state = Multimedia.RECORD - response = test_client.patch('/recordings/1', data={'command': 'pause'}) - assert response.status_code == 200 - assert mock_media_dict[1].num_times_pause_called == 1 - - @pytest.mark.parametrize("current_state", [Multimedia.NULL, Multimedia.PAUSE, Multimedia.STOP]) - def test_patch_pause_invalid(self, test_client, mock_media_dict, current_state): - ''' - Tests a Patch request to pause a recording with an invalid media state - ''' - mock_media_dict[1].current_state = current_state - response = test_client.patch('/recordings/1', data={'command': 'pause'}) - assert response.status_code == 400 - assert mock_media_dict[1].num_times_pause_called == 0 - - @pytest.mark.parametrize("current_state", [Multimedia.PAUSE, Multimedia.RECORD]) - def test_patch_stop(self, test_client, mock_media_dict, current_state): - ''' - Tests a Patch request to stop a recording - ''' - mock_media_dict[1].current_state = current_state - response = test_client.patch('/recordings/1', data={'command': 'stop'}) - assert response.status_code == 200 - assert mock_media_dict[1].num_times_stop_called == 1 - - @pytest.mark.parametrize("current_state", [Multimedia.STOP, Multimedia.NULL]) - def test_patch_stop_invalid(self, test_client, mock_media_dict, current_state): - ''' - Tests a Patch request to stop a recording with an invalid media state - ''' - mock_media_dict[1].current_state = current_state - response = test_client.patch('/recordings/1', data={'command': 'stop'}) - assert response.status_code == 400 - assert mock_media_dict[1].num_times_stop_called == 0 - - def test_post_no_filename(self, test_client): - ''' - Tests a POST request without a filename - ''' - response = test_client.post('/recordings') - assert response.status_code == 400 - - def test_post_empty_filename(self, test_client): - ''' - Tests a POST request with an empty filename - ''' - response = test_client.post('/recordings', data={'filename': ''}) - assert response.status_code == 400 - - def test_post_invalid_filename(self, test_client): - ''' - Tests a POST request with an invalid filename - ''' - response = test_client.post('/recordings', data={'filename': 'abc/123'}) - assert response.status_code == 400 - - def test_post(self, test_client, recording): - ''' - Tests a regular POST request - ''' - assert len(recording.media_dict) == 0 - response = test_client.post('/recordings', data={'filename': 'test'}) - assert response.status_code == 201 - assert recording.media_dict.keys() == [1] - assert recording.media_info['1']['filename'] == 'test.ogg' - - def test_delete_no_recording_id(self, test_client): - ''' - Tests a DELETE request without a provided recording id - ''' - response = test_client.delete('/recordings') - assert response.status_code == 405 - - def test_delete_nonexistent_recording_id(self, test_client): - ''' - Tests a DELETE request with a recording id that doesn't exist - ''' - response = test_client.delete('/recordings/100') - assert response.status_code == 404 - - @pytest.mark.parametrize("invalid_id", ['abc', '1.0']) - def test_delete_invalid_recording_id(self, test_client, invalid_id): - ''' - Tests a DELETE request with an invalid recording id - ''' - response = test_client.delete('/recordings/{0}'.format(invalid_id)) - assert response.status_code == 404 - - @pytest.mark.parametrize("current_state", [Multimedia.RECORD, Multimedia.PAUSE]) - def test_delete_in_progress_recording(self, test_client, recording, mock_media_dict, current_state): - ''' - Tests a DELETE request for a recording that is paused or in progress - ''' - - # delete a recording that is in the middle of recording - mock_media_dict[1].current_state = current_state - del_media = mock_media_dict[1] - response = test_client.delete('/recordings/1') - assert response.status_code == 204 - assert del_media.num_times_stop_called == 1 - assert recording.media_dict.keys() == [2] - - def test_delete_recording_id_and_file(self, test_client, recording, mock_media_dict): - ''' - Tests a DELETE request where the recording has a specified file - ''' - - # setup - create the file to be deleted - file_base_name = 'testDeleteFile' - file_to_delete = file_base_name - file_to_delete_path = os.path.join(recording.config.videodir, file_to_delete) - test_file = open(file_to_delete_path, 'a') - test_file.close() - - # assert the file exists - assert os.path.isfile(file_to_delete_path) - - # set mock_media filepath to filepath of file to be deleted - recording.media_info['1']['filepath'] = file_to_delete_path - - response = test_client.delete('/recordings/1') - assert response.status_code == 204 - assert recording.media_dict.keys() == [2] - - # assert the file no longer exists - assert not os.path.isfile(file_to_delete_path) diff --git a/src/freeseer/tests/frontend/test_cli_talk.py.bak b/src/freeseer/tests/frontend/test_cli_talk.py.bak deleted file mode 100644 index eaa9142a..00000000 --- a/src/freeseer/tests/frontend/test_cli_talk.py.bak +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2014 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ - -import shlex -import shutil -import sys -import tempfile -import unittest - -from mock import patch -from PyQt4 import QtSql - -from freeseer.framework.config.profile import Profile -from freeseer.framework.presentation import Presentation -from freeseer.frontend import cli - - -class TestCli(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.presentations = [] - cls.presentations.append(Presentation("test title 1", "test speaker", "test room", "test event",)) - cls.presentations.append(Presentation("test title 2", "test speaker", "test room", "test event",)) - - def setUp(self): - self.parser = cli.setup_parser() - self.profile_path = tempfile.mkdtemp() - profile = Profile(self.profile_path, 'testing') - self.db = profile.get_database() - for talk in self.presentations: - self.db.insert_presentation(talk) - - def tearDown(self): - shutil.rmtree(self.profile_path) - - @patch.object(Profile, 'get_database') - def test_add_talk(self, mock_profile): - """Test adding a talk""" - mock_profile.return_value = self.db - args = shlex.split('talk add -t "test title" -s "john doe" -e testing -r rm123') - sys.argv[1:] = args - cli.parse_args(self.parser, args) - talks = self.db.get_talks() - talks.next() # Point to talk data - talks.next() # Skip default blank entry - talks.next() # Skip first test entry - talks.next() # Skip second test entry - record = talks.record() - - self.assertEqual(talks.value(record.indexOf('title')).toString(), u'test title') - self.assertEqual(talks.value(record.indexOf('speaker')).toString(), u'john doe') - self.assertEqual(talks.value(record.indexOf('event')).toString(), u'testing') - self.assertEqual(talks.value(record.indexOf('room')).toString(), u'rm123') - - def test_remove_talk(self): - """Test removing a talk""" - args = shlex.split("talk remove -i 1") - sys.argv[1:] = args - cli.parse_args(self.parser, args) - talks = QtSql.QSqlQuery('SELECT COUNT(*) FROM presentations WHERE title="test title 1" AND speaker="test speaker"') - self.assertEqual(talks.value(0).toInt()[0], 0) - - def test_clear_talks(self): - """Test clearing all talks""" - args = shlex.split("talk clear") - sys.argv[1:] = args - cli.parse_args(self.parser, args) - count = QtSql.QSqlQuery('SELECT COUNT(*) FROM presentations') - self.assertEqual(count.value(0).toInt()[0], 0) diff --git a/src/freeseer/tests/frontend/upload/test_youtube.py.bak b/src/freeseer/tests/frontend/upload/test_youtube.py.bak deleted file mode 100644 index f8b9a3d4..00000000 --- a/src/freeseer/tests/frontend/upload/test_youtube.py.bak +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# freeseer - vga/presentation capture software -# -# Copyright (C) 2014 Free and Open Source Software Learning Centre -# http://fosslc.org -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# For support, questions, suggestions or any other inquiries, visit: -# http://wiki.github.com/Freeseer/freeseer/ -import os -import mock -import sys -import unittest -import pytest -from StringIO import StringIO - -from freeseer.framework.youtube import Response -from freeseer.frontend.upload import youtube -from freeseer.tests.framework.test_youtube import TestYoutubeService - - -class TestYoutubeFrontend(unittest.TestCase): - TEST_RESPONSE = { - "id": "test", - "status": "test", - "content": "test", - } - - def setUp(self): - """Stardard init method: runs before each test_* method""" - self.saved_stdout = sys.stdout - self.out = StringIO() - - def test_get_defaults(self): - """Test get_defaults, should always return a dict""" - home = os.getenv('HOME') - expected = { - 'client_secrets': '{}/.freeseer/client_secrets.json'.format(home), - 'video_directory': '{}/Videos'.format(home), - 'oauth2_token': '{}/.freeseer/oauth2_token.json'.format(home) - } - actual = youtube.get_defaults() - self.assertDictEqual(actual, expected) - - def test_prompt_user_not_yes(self): - """Test output prompting user if they want to upload (assume yes was not specified)""" - test_videos = {"v1", "v2", "v3"} - expected = "Found videos:\n" - joined = "\n".join(test_videos) - expected += joined - try: - sys.stdout = self.out - with mock.patch('__builtin__.raw_input', return_value='no'): - youtube.prompt_user(test_videos, confirmation=False) - output = self.out.getvalue().strip() - self.assertEqual(expected, output) - finally: - sys.stdout = self.saved_stdout - - def test_prompt_user_yes(self): - """Test assume yes (assume yes was not specified)""" - self.assertTrue(youtube.prompt_user({"v1", "v2", "v3"}, confirmation=True)) - - -@pytest.mark.parametrize("expected, path", [ - ("/path/that/does/not/exist does not exist, please specify a valid token file", "/path/that/does/not/exist"), - ("Nothing to upload", TestYoutubeService.SAMPLE_VIDEO), -]) -def test_upload(capsys, expected, path): - """ - Test the responses from the upload test cases. - """ - youtube.upload([], path, False) - out, err = capsys.readouterr() - assert expected == out.strip() - - -@pytest.mark.parametrize("expected, response", [ - ("The file was successfully uploaded with video id: {}".format(TestYoutubeFrontend.TEST_RESPONSE['id']), - Response.SUCCESS), - ("The file failed to upload with unexpected response: {}".format(TestYoutubeFrontend.TEST_RESPONSE), - Response.UNEXPECTED_FAILURE), - ("An unretriable HTTP error {} occurred:\n{}".format(TestYoutubeFrontend.TEST_RESPONSE['status'], - TestYoutubeFrontend.TEST_RESPONSE['content']), - Response.UNRETRIABLE_ERROR), - ("The maximum number of retries has been reached", - Response.MAX_RETRIES_REACHED), - ("The access token has expired or been revoked, please run python -m freeseer config youtube", - Response.ACCESS_TOKEN_ERROR), -]) -def test_handle_response(capsys, expected, response): - """ - Test the actual response vs the expected response from response_test_cases. - """ - youtube.handle_response(response, TestYoutubeFrontend.TEST_RESPONSE) - out, err = capsys.readouterr() - assert expected == out.strip()