zdf_mediathek.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. #
  2. # This file is part of stov, written by Helmut Pozimski 2012-2018.
  3. #
  4. # stov is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation, version 2 of the License.
  7. #
  8. # stov is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with stov. If not, see <http://www.gnu.org/licenses/>.
  15. # -*- coding: utf8 -*-
  16. """ This module implements support for subscriptions from the ZDF Mediathek"""
  17. import json
  18. import logging
  19. import urllib.request
  20. import urllib.error
  21. import socket
  22. from datetime import datetime, timedelta
  23. from lib_stov import stov_exceptions
  24. from lib_stov import configuration
  25. LOGGER = logging.getLogger("stov")
  26. class ZDFChannel(object):
  27. """Stores the relevant attributes of a ZDF Mediathek channel"""
  28. def __init__(self, title, videos=None):
  29. if videos is None:
  30. videos = []
  31. self.title = title
  32. self.videos = videos
  33. self.type = "channel"
  34. class ZDFVideo(object):
  35. """Stores the relevant attributes of a single video in the ZDF
  36. Mediathek
  37. """
  38. def __init__(self, title, url):
  39. self.title = title
  40. self.video_id = url
  41. class Connector(object):
  42. """Connector class performing operations against the API"""
  43. def __init__(self, subscription_type, name, search=""):
  44. if subscription_type == "user":
  45. self._type = "channel"
  46. else:
  47. self._type = subscription_type
  48. self._name = name
  49. self._conf = configuration.Conf.get_instance()
  50. self._search = search
  51. if self._type != "channel":
  52. raise stov_exceptions.TypeNotSupported()
  53. def _fetch_videos(self, existing_videos):
  54. """ Fetches the videos of the day before from the ZDF API and returns
  55. a list of newly added videos.
  56. :param existing_videos: videos that already exist in the database
  57. :type existing_videos: list
  58. :return: List of all newly retrieved videos
  59. :rtype: list
  60. """
  61. name_found = False
  62. videos_list = []
  63. new_videos = []
  64. today = datetime.today()
  65. for i in range(7, -1, -1):
  66. day = (today - timedelta(days=i)).strftime("%Y-%m-%d")
  67. try:
  68. connection = urllib.request.urlopen(
  69. "https://zdf-cdn.live.cellular.de/mediathekV2/broadcast-"
  70. "missed/%s" % day, timeout=10)
  71. data = connection.read().decode("utf-8")
  72. except urllib.error.HTTPError:
  73. LOGGER.error("Error while fetching ZDF data for %s", day)
  74. except socket.timeout:
  75. LOGGER.error("Connection timed out while fetching ZDF data "
  76. "for %s", day)
  77. else:
  78. response = json.loads(data)
  79. for cluster in response["broadcastCluster"]:
  80. for broadcast in cluster["teaser"]:
  81. try:
  82. name_found = self._name in broadcast["brandTitle"]
  83. except KeyError:
  84. name_found = self._name in broadcast["headline"]
  85. if name_found:
  86. if self._search:
  87. if self._search in broadcast["titel"]:
  88. new_videos.append((broadcast["sharingUrl"],
  89. broadcast["titel"]))
  90. else:
  91. new_videos.append((broadcast["sharingUrl"],
  92. broadcast["titel"]))
  93. if new_videos:
  94. for broadcast in new_videos:
  95. video_exists = False
  96. if existing_videos:
  97. for existing_video in existing_videos:
  98. if broadcast[0] == existing_video.site_id:
  99. video_exists = True
  100. break
  101. if not video_exists:
  102. videos_list.append(ZDFVideo(broadcast[1], broadcast[0]))
  103. return videos_list
  104. def parse_api_data(self, existing_videos):
  105. """ Takes the existing videos passed to it and wraps them into a
  106. channel object.
  107. :param existing_videos: list of existing_videos
  108. :type existing_videos: list
  109. :return: Channel object
  110. :rtype: ZDFChannel
  111. """
  112. videos = self._fetch_videos(existing_videos)
  113. channel = ZDFChannel(self._name, videos)
  114. return channel
  115. @staticmethod
  116. def construct_video_url(url):
  117. """
  118. Compatibility method, just returns the url
  119. :param url: The url to return
  120. :type url: str
  121. :return: url
  122. :rtype: str
  123. """
  124. return url
  125. @staticmethod
  126. def get_quality_parameter(config):
  127. """
  128. Determines which quality value results from codec and resolution
  129. settings and returns it
  130. :param config: configuration object
  131. :type config: lib_stov.configuration.Conf
  132. :return: itag value
  133. :rtype: str
  134. """
  135. LOGGER.debug(_("Trying to determine the itag value for youtube-dl from"
  136. " your quality and codec settings."))
  137. quality_value = ""
  138. if config.values["videocodec"] == "flv":
  139. if config.values["maxresolution"] == "480p":
  140. quality_value = "hds-1489"
  141. elif config.values["videocodec"] == "mp4":
  142. if config.values["maxresolution"] == "720p":
  143. quality_value = "hls-3286"
  144. if quality_value:
  145. LOGGER.debug(_("Found value: %s."), quality_value)
  146. return quality_value + "/" + config.values["videocodec"]
  147. LOGGER.debug(_("Could not determine an itag value "
  148. "from the configuration"))
  149. return "hls-3286" + "/" + config.values["videocodec"]