noapi.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. #
  2. # This file is part of stov, written by Helmut Pozimski 2012-2015.
  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 provides all classes and methods that were provided by the
  17. youtubeAPI module earlier."""
  18. import subprocess
  19. import sys
  20. import lxml.html
  21. import urllib.parse
  22. import urllib.request
  23. from lib_stov import stov_exceptions
  24. class YtChannel(object):
  25. """Stores the relevant attributes of a youtube channel."""
  26. def __init__(self):
  27. self.title = ""
  28. self.videos = []
  29. class YtVideo(object):
  30. """Stores the relevant attributes of a single youtube video."""
  31. def __init__(self, title, description, ytid):
  32. self.title = title
  33. self.description = description
  34. self.ytid = ytid
  35. class Connector(object):
  36. """This class will retrieve all the necessary data from youtube using
  37. youtube-dl, thus bypassing the API.
  38. """
  39. def __init__(self, subscription_type, name, conf, search=""):
  40. """Populates the object with all necessary data."""
  41. self._type = subscription_type
  42. self._name = name
  43. self._search = search
  44. self._conf = conf
  45. self._title = ""
  46. self._url = ""
  47. self._construct_url()
  48. def _construct_url(self):
  49. """Constructs the URL to request from youtube-dl according to the
  50. subscription type and the given parameters.
  51. """
  52. if self._type == "channel":
  53. self._url = "https://www.youtube.com/user/%s" \
  54. % urllib.parse.quote(self._name)
  55. elif self._type == "search":
  56. self._url = "https://www.youtube.com/results?search_query=%s"\
  57. % urllib.parse.quote(self._search)
  58. elif self._type == "playlist":
  59. self._url = "https://www.youtube.com/playlist?list=%s" \
  60. % urllib.parse.quote(self._name)
  61. def _fetch_title(self):
  62. """Retrieves the title of the HTML page to use as a title for the
  63. subscription."""
  64. data = urllib.request.urlopen(self._url)
  65. parsed_html = lxml.html.parse(data)
  66. data.close()
  67. i = 0
  68. for item in parsed_html.iter("title"):
  69. if i == 0:
  70. self._title = item.text_content().strip().replace("\n", "")
  71. i += 1
  72. if self._search != "" and self._type == "channel":
  73. self._title += _(" search %s") % self._search
  74. def _fetch_videos(self, existing_videos):
  75. """Retrieves all the relevant videos in a subscription."""
  76. videos_list = []
  77. if self._conf.outputlevel == "verbose":
  78. stderr = sys.stderr
  79. else:
  80. stderr = open("/dev/null", "w")
  81. if self._type == "channel" and self._search != "":
  82. try:
  83. video_ids = subprocess.check_output([
  84. self._conf.values["youtube-dl"],
  85. "--max-downloads",
  86. self._conf.values["maxvideos"],
  87. "--match-title",
  88. self._search,
  89. "--get-id",
  90. self._url], stderr=stderr)
  91. except subprocess.CalledProcessError as error_message:
  92. video_ids = error_message.output
  93. else:
  94. try:
  95. video_ids = subprocess.check_output([
  96. self._conf.values["youtube-dl"], "--max-downloads",
  97. self._conf.values["maxvideos"], "--get-id",
  98. self._url], stderr=stderr).strip()
  99. except subprocess.CalledProcessError as error_message:
  100. video_ids = error_message.output
  101. video_ids = video_ids.decode(sys.stdout.encoding).strip()
  102. if len(video_ids) >= 1:
  103. for video_id in video_ids.split("\n"):
  104. video_exists = False
  105. if existing_videos:
  106. for existing_video in existing_videos:
  107. if video_id == existing_video.ytid:
  108. video_exists = True
  109. break
  110. if not video_exists:
  111. try:
  112. video_title = subprocess.check_output([
  113. self._conf.values["youtube-dl"], "--get-title",
  114. "https://www.youtube.com/watch?v=%s"
  115. % video_id], stderr=stderr)
  116. video_description = subprocess.check_output([
  117. self._conf.values["youtube-dl"],
  118. "--get-description",
  119. "https://www.youtube.com/watch?v=%s"
  120. % video_id], stderr=stderr)
  121. except subprocess.CalledProcessError:
  122. raise stov_exceptions.YoutubeDlCallFailed()
  123. else:
  124. video_title = video_title.decode(sys.stdout.encoding)
  125. video_description = video_description.decode(
  126. sys.stdout.encoding)
  127. videos_list.append(YtVideo(
  128. video_title,
  129. video_description,
  130. video_id))
  131. return videos_list
  132. def parse_api_data(self, existing_videos):
  133. """This method calls all necessary methods to retrieve the data
  134. and assembles them into a Channel object. The naming of this
  135. method was set according to the method in youtubeAPI to be
  136. compatible.
  137. """
  138. self._fetch_title()
  139. videos = self._fetch_videos(existing_videos)
  140. channel = YtChannel()
  141. channel.title = self._title
  142. channel.videos = videos
  143. return channel