From ad5b80e1f64eae1b5b2c3971753f6256f3500370 Mon Sep 17 00:00:00 2001 From: Vincent A Date: Sat, 7 Sep 2013 20:38:04 +0200 Subject: [PATCH] new video backend: quvi --- modules/quvi/__init__.py | 24 ++++++++ modules/quvi/backend.py | 117 +++++++++++++++++++++++++++++++++++ modules/quvi/favicon.png | Bin 0 -> 2919 bytes modules/quvi/quvi.py | 129 +++++++++++++++++++++++++++++++++++++++ modules/quvi/test.py | 48 +++++++++++++++ 5 files changed, 318 insertions(+) create mode 100644 modules/quvi/__init__.py create mode 100644 modules/quvi/backend.py create mode 100644 modules/quvi/favicon.png create mode 100644 modules/quvi/quvi.py create mode 100644 modules/quvi/test.py 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 0000000000000000000000000000000000000000..a84132a89c03074360d8ef9a6b4aef01c69ea03b GIT binary patch literal 2919 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. + + +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') +