Browse Source

Implement support for ZDF Mediathek (closes #4)

Helmut Pozimski 6 years ago
parent
commit
74ac9bd328

+ 1 - 1
lib_stov/database.py

@@ -214,7 +214,7 @@ class Db(object):
         video_insert = "INSERT INTO videos (title, ytid, \
                             subscription_id, downloaded) VALUES \
                             (?, ?, ?, ?)"
-        insert_data = (video.title, video.ytid,
+        insert_data = (video.title, video.video_id,
                        subscription_id, 0)
         self._execute_statement(video_insert, insert_data)
 

+ 3 - 3
lib_stov/program.py

@@ -102,7 +102,7 @@ def add_subscription(conf, database, channel="",
     except stov_exceptions.NoDataFromYoutubeAPIException as error:
         LOGGER.error(error)
     for video in new_subscription.parsed_response.videos:
-        if not database.video_in_database(video.ytid):
+        if not database.video_in_database(video.video_id):
             if new_subscription.check_string_match(video):
                 try:
                     database.insert_video(video, new_subscription.get_id())
@@ -182,7 +182,7 @@ def update_subscriptions(database, conf):
         except stov_exceptions.NoDataFromYoutubeAPIException as error:
             LOGGER.error(error)
         for video in element.parsed_response.videos:
-            if not database.video_in_database(video.ytid):
+            if not database.video_in_database(video.video_id):
                 if element.check_string_match(video):
                     try:
                         database.insert_video(video, element.get_id())
@@ -498,7 +498,7 @@ def initialize_sites(database):
     :param database: database object
     :type database: lib_stov.database.Db
     """
-    supported_sites = ["youtube"]
+    supported_sites = ["youtube", "zdf_mediathek"]
     sites = database.get_sites()
     for site in supported_sites:
         site_found = False

+ 13 - 0
lib_stov/stov_exceptions.py

@@ -223,3 +223,16 @@ class SiteUnsupported(Exception):
 
     def __str__(self):
         return self._message
+
+
+class TypeNotSupported(Exception):
+    """ Will be raised when a video site does not support a specific
+    subscription type
+    """
+    def __init__(self):
+        super(TypeNotSupported, self).__init__()
+        self._message = _("Error: This subscription type is not supported by "
+                          "the video site")
+
+    def __str__(self):
+        return self._message

+ 8 - 4
lib_stov/subscription.py

@@ -1,5 +1,5 @@
 #
-#   This file is part of stov, written by Helmut Pozimski 2012-2015.
+#   This file is part of stov, written by Helmut Pozimski 2012-2017.
 #
 #   stov is free software: you can redistribute it and/or modify
 #   it under the terms of the GNU General Public License as published by
@@ -20,6 +20,7 @@
 
 from lib_stov import stov_exceptions
 from lib_stov import yt_noapi
+from lib_stov import zdf_mediathek
 
 
 class Sub(object):
@@ -52,6 +53,9 @@ class Sub(object):
         if site == "youtube":
             self._connector = yt_noapi.Connector(self._type, self._name,
                                                  self._conf, self._search)
+        elif site == "zdf_mediathek":
+            self._connector = zdf_mediathek.Connector(self._type, self._name,
+                                                      self._conf)
         else:
             raise stov_exceptions.SiteUnsupported()
 
@@ -95,7 +99,7 @@ class Sub(object):
             itag_value = self._connector.get_quality_parameter(self._conf)
             for video in self._video_list:
                 if video.downloaded == 0:
-                    video_url = self._connector.costruct_video_url(
+                    video_url = self._connector.construct_video_url(
                         video.site_id)
                     if video.download_video(self._directory, itag_value,
                                             self._conf.values["videocodec"],
@@ -145,9 +149,9 @@ class Sub(object):
         self._type = parsed_response.type
         self.gather_videos(videos)
         for entry in parsed_response.videos:
-            self._id_list.append(entry.ytid)
+            self._id_list.append(entry.video_id)
         for item in self._video_list:
-            if item.ytid not in self._id_list:
+            if item.video_id not in self._id_list:
                 self.to_delete.append(item)
 
     def update_data(self):

+ 2 - 2
lib_stov/yt_noapi.py

@@ -44,7 +44,7 @@ class YtVideo(object):
     """Stores the relevant attributes of a single youtube video."""
     def __init__(self, title, ytid):
         self.title = title
-        self.ytid = ytid
+        self.video_id = ytid
 
 
 class Connector(object):
@@ -157,7 +157,7 @@ class Connector(object):
         return channel
 
     @staticmethod
-    def costruct_video_url(ytid):
+    def construct_video_url(ytid):
         """
         Resturns the URL to a specified youtube video
 

+ 144 - 0
lib_stov/zdf_mediathek.py

@@ -0,0 +1,144 @@
+#
+#   This file is part of stov, written by Helmut Pozimski 2012-2017.
+#
+#   stov 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, version 2 of the License.
+#
+#   stov 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 stov.  If not, see <http://www.gnu.org/licenses/>.
+
+
+# -*- coding: utf8 -*-
+
+""" This module implements support for subscriptions from the ZDF Mediathek"""
+
+import json
+import logging
+import urllib.request
+from datetime import datetime, timedelta
+
+from lib_stov import stov_exceptions
+
+LOGGER = logging.getLogger("stov")
+
+
+class ZDFChannel(object):
+    """Stores the relevant attributes of a ZDF Mediathek channel"""
+    def __init__(self, title, videos=[]):
+        self.title = title
+        self.videos = videos
+        self.type = "channel"
+
+
+class ZDFVideo(object):
+    """Stores the relevant attributes of a single video in the ZDF
+    Mediathek
+    """
+    def __init__(self, title, url):
+        self.title = title
+        self.video_id = url
+
+
+class Connector(object):
+    """Connector class performing operations against the API"""
+    def __init__(self, subscription_type, name, conf):
+        if subscription_type == "user":
+            self._type = "channel"
+        else:
+            self._type = subscription_type
+        self._name = name
+        self._conf = conf
+        if self._type != "channel":
+            raise stov_exceptions.TypeNotSupported()
+
+    def _fetch_videos(self, existing_videos):
+        """ Fetches the videos of the day before from the ZDF API and returns
+        a list of newly added videos.
+
+        :param existing_videos: videos that already exist in the database
+        :type existing_videos: list
+        :return: List of all newly retrieved videos
+        :rtype: list
+        """
+        videos_list = []
+        new_videos = []
+        today = datetime.today()
+        connection = urllib.request.urlopen(
+            "https://zdf-cdn.live.cellular.de/mediathekV2/broadcast-missed/%s"
+            % (today-timedelta(days=1)).strftime("%Y-%m-%d"))
+        response = json.load(connection)
+        for cluster in response["broadcastCluster"]:
+            for broadcast in cluster["teaser"]:
+                if self._name in broadcast["titel"]:
+                    new_videos.append((broadcast["sharingUrl"],
+                                       broadcast["titel"]))
+        if new_videos:
+            for broadcast in new_videos:
+                video_exists = False
+                if existing_videos:
+                    for existing_video in existing_videos:
+                        if broadcast[0] == existing_video.site_id:
+                            video_exists = True
+                            break
+                if not video_exists:
+                    videos_list.append(ZDFVideo(broadcast[1], broadcast[0]))
+        return videos_list
+
+    def parse_api_data(self, existing_videos):
+        """ Takes the existing videos passed to it and wraps them into a
+        channel object.
+
+        :param existing_videos: list of existing_videos
+        :type existing_videos: list
+        :return: Channel object
+        :rtype: ZDFChannel
+        """
+        videos = self._fetch_videos(existing_videos)
+        channel = ZDFChannel(self._name, videos)
+        return channel
+
+    @staticmethod
+    def construct_video_url(url):
+        """
+        Compatibility method, just returns the url
+
+        :param url: The url to return
+        :type url: str
+        :return: url
+        :rtype: str
+        """
+        return url
+
+    @staticmethod
+    def get_quality_parameter(config):
+        """
+        Determines which quality value results from codec and resolution
+        settings and returns it
+
+        :param config: configuration object
+        :type config: lib_stov.configuration.Conf
+        :return: itag value
+        :rtype: str
+        """
+        LOGGER.debug(_("Trying to determine the itag value for youtube-dl from"
+                       " your quality and codec settings."))
+        quality_value = ""
+        if config.values["videocodec"] == "flv":
+            if config.values["maxresolution"] == "480p":
+                quality_value = "hds-1489"
+        elif config.values["videocodec"] == "mp4":
+            if config.values["maxresolution"] == "720p":
+                quality_value = "hls-3286"
+        if quality_value:
+            LOGGER.debug(_("Found value: %s."), quality_value)
+            return quality_value
+        else:
+            LOGGER.debug(_("Could not determine an itag value "
+                           "from the configuration"))
+            return "hls-3286"