diff --git a/modules/quvi/__init__.py b/modules/quvi/__init__.py new file mode 100644 index 00000000..f4925f8c --- /dev/null +++ b/modules/quvi/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Vincent A +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from .backend import QuviBackend, QuviVideo + + +__all__ = ['QuviBackend', 'QuviVideo'] diff --git a/modules/quvi/backend.py b/modules/quvi/backend.py new file mode 100644 index 00000000..7891296c --- /dev/null +++ b/modules/quvi/backend.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Vincent A +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + +# sample usage: youtube.XXXXXX@quvi +# or also: https://www.youtube.com/watch?v=XXXXXX@quvi +# shortened URLs are also supported + +# this backend requires the quvi 0.4 C library to be installed + + +import datetime +from weboob.capabilities.base import UserError, StringField +from weboob.capabilities.video import ICapVideo, BaseVideo +from weboob.tools.backend import BaseBackend +from weboob.tools.capabilities.thumbnail import Thumbnail +from weboob.tools.misc import to_unicode + +from quvi import LibQuvi, QuviError + + +__all__ = ['QuviBackend', 'QuviVideo'] + + +class QuviBackend(BaseBackend, ICapVideo): + NAME = 'quvi' + DESCRIPTION = u'Multi-website video helper with quvi. Handles Youtube, BBC, and a lot more' + MAINTAINER = u'Vincent A' + EMAIL = 'dev@indigo.re' + LICENSE = 'AGPLv3+' + VERSION = '0.h' + + BROWSER = None + + def get_video(self, _id): + video = QuviVideo(_id) + + parser = LibQuvi() + if not parser.load(): + raise UserError('Make sure libquvi 0.4 is installed') + + try: + info = parser.get_info(video.page_url) + except QuviError as qerror: + raise UserError(qerror.message) + + video.url = to_unicode(info.get('url')) + if not video.url: + raise NotImplementedError() + + video.ext = to_unicode(info.get('suffix')) + video.title = to_unicode(info.get('title')) + video.page = to_unicode(info.get('page')) + duration = int(info.get('duration', 0)) + if duration: + video.duration = datetime.timedelta(milliseconds=duration) + if info.get('thumbnail'): + video.thumbnail = Thumbnail(info.get('thumbnail')) + return video + + +class QuviVideo(BaseVideo): + BACKENDS = { + 'youtube': 'https://www.youtube.com/watch?v=%s', + 'vimeo': 'http://vimeo.com/%s', + 'dailymotion': 'http://www.dailymotion.com/video/%s', + 'metacafe': 'http://www.metacafe.com/watch/%s/', + 'arte': 'http://videos.arte.tv/fr/videos/plop--%s.html', + 'videa': 'http://videa.hu/videok/%s/', + 'wimp': 'http://www.wimp.com/%s/', + 'funnyordie': 'http://www.funnyordie.com/videos/%s/', + 'tapuz': 'http://flix.tapuz.co.il/v/watch-%s-.html', + 'liveleak': 'http://www.liveleak.com/view?i=%s', + # nsfw + 'xhamster': 'https://xhamster.com/movies/%s/plop.html', + 'xvideos': 'http://www.xvideos.com/video%s/', + 'redtube': 'http://www.redtube.com/%s', + 'xnxx': 'http://video.xnxx.com/video%s/', + # more websites are supported, but . isn't always enough + # however, URLs are supported, like this: + # https://www.youtube.com/watch?v=BaW_jenozKc@quvi + } + + page = StringField('Page URL of the video') + + @classmethod + def id2url(cls, _id): + if _id.startswith('http'): + return _id + + sub_backend, sub_id = _id.split('.', 1) + try: + return cls.BACKENDS[sub_backend] % sub_id + except KeyError: + raise NotImplementedError() + + @property + def page_url(self): + if self.page: + return self.page + else: + return self.id2url(self.id) diff --git a/modules/quvi/favicon.png b/modules/quvi/favicon.png new file mode 100644 index 00000000..a84132a8 Binary files /dev/null and b/modules/quvi/favicon.png differ diff --git a/modules/quvi/quvi.py b/modules/quvi/quvi.py new file mode 100644 index 00000000..f7fbb4a0 --- /dev/null +++ b/modules/quvi/quvi.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Vincent A +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from ctypes import cdll, c_char_p, c_double, c_void_p, byref +from ctypes.util import find_library + + +__all__ = ['LibQuvi', 'QuviError'] + + +class QuviError(Exception): + pass + + +class LibQuvi04(object): + QUVI_VERSION = 0 + + QUVIOPT_FORMAT = 0 + QUVIOPT_CATEGORY = 4 + QUVI_OK = 0 + QUVI_LAST = 5 + + QUVIPROP_PAGEURL = 0x100002 + QUVIPROP_PAGETITLE = 0x100003 + QUVIPROP_MEDIAID = 0x100004 + QUVIPROP_MEDIAURL = 0x100005 + QUVIPROP_FILESUFFIX = 0x100008 + #~ QUVIPROP_FORMAT = 0x10000A + QUVIPROP_MEDIATHUMBNAILURL = 0x10000C + QUVIPROP_MEDIACONTENTLENGTH = 0x300006 + QUVIPROP_MEDIADURATION = 0x30000D + + QUVIPROTO_HTTP = 1 + QUVIPROTO_RTMP = 8 + + def __init__(self, lib=None): + self.lib = lib + self.qh = c_void_p() + self.qmh = c_void_p() + + def load(self): + path = find_library('quvi') + if not path: + return False + + self.lib = cdll.LoadLibrary(path) + if self.lib is None: + return False + + version_str = c_char_p(self.lib.quvi_version(self.QUVI_VERSION)).value + if version_str.startswith('v0.4'): + return True + else: + return False + + def _cleanup(self): + if self.qmh: + self.lib.quvi_parse_close(byref(self.qmh)) + self.qmh = c_void_p() + if self.qh: + self.lib.quvi_close(byref(self.qh)) + self.qh = c_void_p() + + def get_info(self, url): + try: + return self._get_info(url) + finally: + self._cleanup() + + def _get_info(self, url): + status = self.lib.quvi_init(byref(self.qh)) + self._assert_ok(status) + + status = self.lib.quvi_setopt(self.qh, self.QUVIOPT_FORMAT, 'best') + self._assert_ok(status) + + status = self.lib.quvi_parse(self.qh, c_char_p(url), byref(self.qmh)) + self._assert_ok(status) + + info = {} + info['url'] = self._get_str(self.QUVIPROP_MEDIAURL) + info['title'] = self._get_str(self.QUVIPROP_PAGETITLE) + info['suffix'] = self._get_str(self.QUVIPROP_FILESUFFIX) + info['page'] = self._get_str(self.QUVIPROP_PAGEURL) # uncut! + info['media_id'] = self._get_str(self.QUVIPROP_MEDIAID) + info['thumbnail'] = self._get_str(self.QUVIPROP_MEDIATHUMBNAILURL) + info['duration'] = self._get_double(self.QUVIPROP_MEDIADURATION) + info['size'] = self._get_double(self.QUVIPROP_MEDIACONTENTLENGTH) + + return info + + def _assert_ok(self, status): + if status != self.QUVI_OK: + c_msg = c_char_p(self.lib.quvi_strerror(self.qh, status)) + raise QuviError(c_msg.value) + + def _get_str(self, prop): + c_value = c_char_p() + status = self.lib.quvi_getprop(self.qmh, prop, byref(c_value)) + self._assert_ok(status) + + return c_value.value + + def _get_double(self, prop): + c_value = c_double() + status = self.lib.quvi_getprop(self.qmh, prop, byref(c_value)) + self._assert_ok(status) + + return c_value.value + + +LibQuvi = LibQuvi04 diff --git a/modules/quvi/test.py b/modules/quvi/test.py new file mode 100644 index 00000000..ee2c0322 --- /dev/null +++ b/modules/quvi/test.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Vincent A +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from weboob.tools.test import BackendTest + + +class QuviTest(BackendTest): + BACKEND = 'quvi' + + def test_get_id(self): + v = self.backend.get_video('youtube.BaW_jenozKc') + assert len(v.url) + assert len(v.title) + assert (v.page_url == 'https://www.youtube.com/watch?v=BaW_jenozKc') + + def test_get_url(self): + v = self.backend.get_video('https://www.youtube.com/watch?v=BaW_jenozKc') + assert len(v.url) + assert len(v.title) + # did we retrieve more? + assert len(v.ext) + assert v.duration + assert v.thumbnail + assert v.page_url == 'https://www.youtube.com/watch?v=BaW_jenozKc' + + def test_get_shortened(self): + v = self.backend.get_video('http://youtu.be/BaW_jenozKc') + assert len(v.url) + assert len(v.title) + assert v.page_url.startswith('http://www.youtube.com/watch?v=BaW_jenozKc') +