noapi.py 5.5 KB

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