Beispiel #1
0
class AbstractEmbedPlayer(AbstractPlayer):
    """
    Abstract Embed Player for third-party services like YouTube

    Typically embed players will play only their own content, and that is
    the only way such content can be played. Therefore each embed type has
    been given its own :attr:`~mediacore.lib.uri.StorageURI.scheme` which
    uniquely identifies it.

    For example, :meth:`mediacore.lib.storage.YoutubeStorage.get_uris`
    returns URIs with a scheme of `'youtube'`, and the special
    :class:`YoutubePlayer` would overload :attr:`scheme` to also be
    `'youtube'`. This would allow the Youtube player to play only those URIs.

    """
    scheme = abstractproperty()
    """The `StorageURI.scheme` which uniquely identifies this embed type."""
    @classmethod
    def can_play(cls, uris):
        """Test all the given URIs to see if they can be played by this player.

        This is a class method, not an instance or static method.

        :type uris: list
        :param uris: A collection of StorageURI tuples to test.
        :rtype: tuple
        :returns: Boolean result for each of the given URIs.

        """
        return tuple(uri.scheme == cls.scheme for uri in uris)
Beispiel #2
0
class EmbedStorageEngine(StorageEngine):
    """
    A specialized URL storage engine for URLs that match a certain pattern.
    """

    is_singleton = True

    try_after = [FileStorageEngine]

    url_pattern = abstractproperty()
    """A compiled pattern object that uses named groupings for matches."""

    def parse(self, file=None, url=None):
        """Return metadata for the given URL or raise an error.

        If the given URL matches :attr:`url_pattern` then :meth:`_parse`
        is called with the named matches as kwargs and the result returned.

        :type file: :class:`cgi.FieldStorage` or None
        :param file: A freshly uploaded file object.
        :type url: unicode or None
        :param url: A remote URL string.
        :rtype: dict
        :returns: Any extracted metadata.
        :raises UnsuitableEngineError: If file information cannot be parsed.

        """
        if url is None:
            raise UnsuitableEngineError
        match = self.url_pattern.match(url)
        if match is None:
            raise UnsuitableEngineError
        return self._parse(url, **match.groupdict())

    @abstractmethod
    def _parse(self, url, **kwargs):
        """Return metadata for the given URL that matches :attr:`url_pattern`.
Beispiel #3
0
class FileSupportMixin(object):
    """
    Mixin that provides a can_play test on a number of common parameters.
    """
    supported_containers = abstractproperty()
    supported_schemes = set([HTTP])
    supported_types = set([AUDIO, VIDEO])

    @classmethod
    def can_play(cls, uris):
        """Test all the given URIs to see if they can be played by this player.

        This is a class method, not an instance or static method.

        :type uris: list
        :param uris: A collection of StorageURI tuples to test.
        :rtype: tuple
        :returns: Boolean result for each of the given URIs.

        """
        return tuple(
            uri.file.container in cls.supported_containers and uri.scheme in
            cls.supported_schemes and uri.file.type in cls.supported_types
            for uri in uris)
Beispiel #4
0
class AbstractPlayer(AbstractClass):
    """
    Player Base Class that all players must implement.
    """

    name = abstractproperty()
    """A unicode string identifier for this class."""

    display_name = abstractproperty()
    """A unicode display name for the class, to be used in the settings UI."""

    settings_form_class = None
    """An optional :class:`mediacore.forms.admin.players.PlayerPrefsForm`."""

    default_data = {}
    """An optional default data dictionary for user preferences."""

    supports_resizing = True
    """A flag that allows us to mark the few players that can't be resized.

    Setting this to False ensures that the resize (expand/shrink) controls will
    not be shown in our player control bar.
    """
    @abstractmethod
    def can_play(cls, uris):
        """Test all the given URIs to see if they can be played by this player.

        This is a class method, not an instance or static method.

        :type uris: list
        :param uris: A collection of StorageURI tuples to test.
        :rtype: tuple
        :returns: Boolean result for each of the given URIs.

        """

    def render_markup(self, error_text=None):
        """Render the XHTML markup for this player instance.

        :param error_text: Optional error text that should be included in
            the final markup if appropriate for the player.
        :rtype: ``unicode`` or :class:`genshi.core.Markup`
        :returns: XHTML that will not be escaped by Genshi.

        """
        return error_text or u''

    @abstractmethod
    def render_js_player(self):
        """Render a javascript string to instantiate a javascript player.

        Each player has a client-side component to provide a consistent
        way of initializing and interacting with the player. For more
        information see :file:`mediacore/public/scripts/mcore/players/`.

        :rtype: ``unicode``
        :returns: A javascript string which will evaluate to an instance
            of a JS player class. For example: ``new mcore.Html5Player()``.

        """

    def __init__(self,
                 media,
                 uris,
                 data=None,
                 width=None,
                 height=None,
                 autoplay=False,
                 autobuffer=False,
                 qualified=False,
                 **kwargs):
        """Initialize the player with the media that it will be playing.

        :type media: :class:`mediacore.model.media.Media` instance
        :param media: The media object that will be rendered.
        :type uris: list
        :param uris: The StorageURIs this player has said it :meth:`can_play`.
        :type data: dict or None
        :param data: Optional player preferences from the database.
        :type elem_id: unicode, None, Default
        :param elem_id: The element ID to use when rendering. If left
            undefined, a sane default value is provided. Use None to disable.

        """
        self.media = media
        self.uris = uris
        self.data = data or {}
        self.width = width or 400
        self.height = height or 225
        self.autoplay = autoplay
        self.autobuffer = autobuffer
        self.qualified = qualified
        self.elem_id = kwargs.pop('elem_id', '%s-player' % media.slug)

    _width_diff = 0
    _height_diff = 0

    @property
    def adjusted_width(self):
        """Return the desired viewable width + any extra for the player."""
        return self.width + self._width_diff

    @property
    def adjusted_height(self):
        """Return the desired viewable height + the height of the controls."""
        return self.height + self._height_diff

    def get_uris(self, **kwargs):
        """Return a subset of the :attr:`uris` for this player.

        This allows for easy filtering of URIs by feeding any number of
        kwargs to this function. See :func:`mediacore.lib.uri.pick_uris`.

        """
        return pick_uris(self.uris, **kwargs)

    @classmethod
    def inject_in_db(cls, enable_player=False):
        from mediacore.model import DBSession
        from mediacore.model.players import players as players_table, PlayerPrefs

        prefs = PlayerPrefs()
        prefs.name = cls.name
        prefs.enabled = enable_player
        # didn't get direct SQL expression to work with SQLAlchemy
        # player_table = sql.func.max(player_table.c.priority)
        query = sql.select([sql.func.max(players_table.c.priority)])
        max_priority = DBSession.execute(query).first()[0]
        if max_priority is None:
            max_priority = -1
        prefs.priority = max_priority + 1
        prefs.created_on = datetime.now()
        prefs.modified_on = datetime.now()
        prefs.data = cls.default_data
        DBSession.add(prefs)
        DBSession.commit()
Beispiel #5
0
class StorageEngine(AbstractClass):
    """
    Base class for all Storage Engine implementations.
    """

    engine_type = abstractproperty()
    """A unique identifying unicode string for the StorageEngine."""

    default_name = abstractproperty()
    """A user-friendly display name that identifies this StorageEngine."""

    is_singleton = abstractproperty()
    """A flag that indicates whether this engine should be added only once."""

    settings_form_class = None
    """Your :class:`mediacore.forms.Form` class for changing :attr:`_data`."""

    _default_data = {}
    """The default data dictionary to create from the start.

    If you plan to store something in :attr:`_data`, declare it in
    this dict for documentation purposes, if nothing else. Down the
    road, we may validate data against this dict to ensure that only
    known keys are used.
    """

    try_before = []
    """Storage Engines that should :meth:`parse` after this class has.

    This is a list of StorageEngine class objects which is used to
    perform a topological sort of engines. See :func:`sort_engines`
    and :func:`add_new_media_file`.
    """

    try_after = []
    """Storage Engines that should :meth:`parse` before this class has.

    This is a list of StorageEngine class objects which is used to
    perform a topological sort of engines. See :func:`sort_engines`
    and :func:`add_new_media_file`.
    """
    def __init__(self, display_name=None, data=None):
        """Initialize with the given data, or the class defaults.

        :type display_name: unicode
        :param display_name: Name, defaults to :attr:`default_name`.
        :type data: dict
        :param data: The unique parameters of this engine instance.

        """
        self.display_name = display_name or self.default_name
        self._data = data or self._default_data

    def engine_params(self):
        """Return the unique parameters of this engine instance.

        :rtype: dict
        :returns: All the data necessary to create a functionally
            equivalent instance of this engine.

        """
        return self._data

    @property
    @memoize
    def settings_form(self):
        """Return an instance of :attr:`settings_form_class` if defined.

        :rtype: :class:`mediacore.forms.Form` or None
        :returns: A memoized form instance, since instantiation is expensive.

        """
        if self.settings_form_class is None:
            return None
        return self.settings_form_class()

    @abstractmethod
    def parse(self, file=None, url=None):
        """Return metadata for the given file or URL, or raise an error.

        It is expected that different storage engines will be able to
        extract different metadata.

        **Required metadata keys**:

            * type (generally 'audio' or 'video')

        **Optional metadata keys**:

            * unique_id
            * container
            * display_name
            * title
            * size
            * width
            * height
            * bitrate
            * thumbnail_file
            * thumbnail_url

        :type file: :class:`cgi.FieldStorage` or None
        :param file: A freshly uploaded file object.
        :type url: unicode or None
        :param url: A remote URL string.
        :rtype: dict
        :returns: Any extracted metadata.
        :raises UnsuitableEngineError: If file information cannot be parsed.

        """

    def store(self, media_file, file=None, url=None, meta=None):
        """Store the given file or URL and return a unique identifier for it.

        This method is called with a newly persisted instance of
        :class:`~mediacore.model.media.MediaFile`. The instance has
        been flushed and therefore has its primary key, but it has
        not yet been committed. An exception here will trigger a rollback.

        This method need not necessarily return anything. If :meth:`parse`
        returned a `unique_id` key, this can return None. It is only when
        this method generates the unique ID, or if it must override the
        unique ID from :meth:`parse`, that it should be returned here.

        This method SHOULD NOT modify the `media_file`. It is provided
        for informational purposes only, so that a unique ID may be
        generated with the primary key from the database.

        :type media_file: :class:`~mediacore.model.media.MediaFile`
        :param media_file: The associated media file object.
        :type file: :class:`cgi.FieldStorage` or None
        :param file: A freshly uploaded file object.
        :type url: unicode or None
        :param url: A remote URL string.
        :type meta: dict
        :param meta: The metadata returned by :meth:`parse`.
        :rtype: unicode or None
        :returns: The unique ID string. Return None if not generating it here.

        """

    def postprocess(self, media_file):
        """Perform additional post-processing after the save is complete.

        This is called after :meth:`parse`, :meth:`store`, thumbnails
        have been saved and the changes to database flushed.

        :type media_file: :class:`~mediacore.model.media.MediaFile`
        :param media_file: The associated media file object.
        :returns: None

        """

    def delete(self, unique_id):
        """Delete the stored file represented by the given unique ID.

        :type unique_id: unicode
        :param unique_id: The identifying string for this file.
        :rtype: boolean
        :returns: True if successful, False if an error occurred.

        """

    def transcode(self, media_file):
        """Transcode an existing MediaFile.

        The MediaFile may be stored already by another storage engine.
        New MediaFiles will be created for each transcoding generated by this
        method.

        :type media_file: :class:`~mediacore.model.media.MediaFile`
        :param media_file: The MediaFile object to transcode.
        :raises CannotTranscode: If this storage engine can't or won't transcode the file.
        :rtype: NoneType
        :returns: Nothing

        """
        raise CannotTranscode(
            'This StorageEngine does not support transcoding.')

    @abstractmethod
    def get_uris(self, media_file):
        """Return a list of URIs from which the stored file can be accessed.
Beispiel #6
0
 def _makeOne(self):
     from mediacore.plugin.abc import abstractproperty
     return abstractproperty()