def process_entry(self, e_hash: str, entry: Any) -> Tuple[str, Optional[Video]]: with Database(self.db_path) as database: if database.get_extractor_fail_count(e_hash) >= self.max_fail: return e_hash, None with youtube_dl.YoutubeDL(self.ydl_opts) as ydl: try: processed = ydl.process_ie_result(entry, False) except youtube_dl.DownloadError as download_error: logging.warning("Failed to get a video. Youtube-dl said: '%s'", download_error) return e_hash, None else: publish_date = 0.0 date_str = processed.get("upload_date") if date_str: publish_date = datetime.datetime.strptime( date_str, "%Y%m%d").timestamp() if processed.get("age_limit", 0) > config.ytcc.age_limit: logger.warning("Ignoring video '%s' due to age limit", processed.get("title")) return e_hash, None logger.info("Processed video '%s'", processed.get("title")) return e_hash, Video(url=processed["webpage_url"], title=processed["title"], description=processed.get( "description", ""), publish_date=publish_date, watch_date=None, duration=processed.get("duration", -1), extractor_hash=e_hash)
def test_resolve_video_id(self): db = init_db() video = db.resolve_video_id(1) expected = Video(id=1, yt_videoid="0", title="title1", description="description1", publisher="id_publisher1", publish_date=1488286166.0, watched=False) self.eq_video(video, expected)
def play_video(self, video: Video, audio_only: bool = False) -> bool: """Play the given video with the mpv video player and mark the the video as watched. The video will not be marked as watched, if the player exits unexpectedly (i.e. exits with non-zero exit code) or another error occurs. :param video: The video to play. :param audio_only: If True, only the audio track of the video is played :return: False if the given video_id does not exist or the player closed with a non-zero exit code. True if the video was played successfully. """ no_video_flag = [] if audio_only: no_video_flag.append("--no-video") if video: try: command = [ "mpv", *no_video_flag, *self.config.mpv_flags, self.get_youtube_video_url(video.yt_videoid) ] subprocess.run(command, check=True) except FileNotFoundError: raise YtccException("Could not locate the mpv video player!") except subprocess.CalledProcessError: return False video.watched = True return True return False
def download_video(self, video: Video, path: str = "", audio_only: bool = False) -> bool: """Download the given video with youtube-dl and mark it as watched. If the path is not given, the path is read from the config file. :param video: The video to download. :param path: The directory where the download is saved. :param audio_only: If True, only the audio track is downloaded. :return: True, if the video was downloaded successfully. False otherwise. """ if path: download_dir = path elif self.config.download_dir: download_dir = self.config.download_dir else: download_dir = "" conf = self.config.youtube_dl ydl_opts: Dict[str, Any] = { "outtmpl": os.path.join(download_dir, conf.output_template), "ratelimit": conf.ratelimit, "retries": conf.retries, "quiet": conf.loglevel == "quiet", "verbose": conf.loglevel == "verbose", "ignoreerrors": False } if audio_only: ydl_opts["format"] = "bestaudio/best" if conf.thumbnail: ydl_opts["writethumbnail"] = True ydl_opts["postprocessors"] = [ { 'key': 'FFmpegExtractAudio', 'preferredcodec': "m4a" }, {"key": "EmbedThumbnail"} ] else: ydl_opts["format"] = conf.format if conf.subtitles != "off": ydl_opts["subtitleslangs"] = list(map(str.strip, conf.subtitles.split(","))) ydl_opts["writesubtitles"] = True ydl_opts["writeautomaticsub"] = True ydl_opts["postprocessors"] = [{"key": "FFmpegEmbedSubtitle"}] with youtube_dl.YoutubeDL(ydl_opts) as ydl: url = self.get_youtube_video_url(video.yt_videoid) try: info = ydl.extract_info(url, download=False, process=False) if info.get("is_live", False) and conf.skip_live_stream: return False ydl.process_ie_result(info, download=True) video.watched = True return True except youtube_dl.utils.YoutubeDLError: return False
def test_mark_watched(self): db = init_db() for video in db.resolve_video_ids([2, 3]): video.watched = True videos = db.session.query(Video).filter(Video.watched == False).all() expected = Video(id=1, yt_videoid="0", title="title1", description="description1", publisher="id_publisher1", publish_date=1488286166.0, watched=False) self.assertEqual(len(videos), 1) self.eq_video(videos[0], expected)
def init_db(): insert_list = [ Video(yt_videoid="0", title="title1", description="description1", publisher="id_publisher1", publish_date=1488286166, watched=False), Video(yt_videoid="0", title="title1", description="description1", publisher="id_publisher1", publish_date=1488286167, watched=False), Video(yt_videoid="1", title="title2", description="description1", publisher="id_publisher1", publish_date=1488286168, watched=False), Video(yt_videoid="1", title="title2", description="description2", publisher="id_publisher2", publish_date=1488286170, watched=False), Video(yt_videoid="2", title="title3", description="description3", publisher="id_publisher2", publish_date=1488286171, watched=False) ] db = Database(":memory:") db.add_channel(Channel(displayname="publisher1", yt_channelid="id_publisher1")) db.add_channel(Channel(displayname="publisher2", yt_channelid="id_publisher2")) db.add_channel(Channel(displayname="publisher3", yt_channelid="id_publisher3")) db.add_videos(insert_list) return db
def _update_channel(channel: Channel) -> List[Video]: yt_channel_id = channel.yt_channelid url = _get_youtube_rss_url(yt_channel_id) feed = feedparser.parse(url) return [ Video(yt_videoid=str(entry.yt_videoid), title=str(entry.title), description=str(entry.description), publisher=yt_channel_id, publish_date=time.mktime(entry.published_parsed), watched=False) for entry in feed.entries ]
def _update_channel(channel: Channel) -> List[Video]: yt_channel_id = channel.yt_channelid url = f"https://www.youtube.com/feeds/videos.xml?channel_id={yt_channel_id}" feed = feedparser.parse(url) return [ Video( yt_videoid=str(entry.yt_videoid), title=str(entry.title), description=str(entry.description), publisher=yt_channel_id, publish_date=time.mktime(entry.published_parsed), watched=False ) for entry in feed.entries ]
def setUp(self): self.current_dir = os.path.dirname(__file__) self.ytcc = Ytcc(os.path.join(self.current_dir, "data/ytcc_test.conf")) self.db_conn = self.ytcc.database insert_list = [ Video(yt_videoid="V-ozGFl3Jks", title="tmptYnCut", description="", publisher="UCsLiV4WJfkTEHH0b9PmRklw", publish_date=1488348731.0, watched=True), Video(yt_videoid="a1gOeiyIqPs", title="tmp99Yc1l", description="", publisher="UCsLiV4WJfkTEHH0b9PmRklw", publish_date=1488348519.0, watched=True), Video(yt_videoid="0ounUgOrcqo", title="tmppfXKp6", description="", publisher="UCsLiV4WJfkTEHH0b9PmRklw", publish_date=1488345630.0, watched=True), Video(yt_videoid="7mckB-NdKWY", title="tmpiM62pN", description="", publisher="UCsLiV4WJfkTEHH0b9PmRklw", publish_date=1488345565.0, watched=False), Video(yt_videoid="RmRPt93uAsQ", title="tmpIXBgjd", description="", publisher="UCsLiV4WJfkTEHH0b9PmRklw", publish_date=1488344217.0, watched=False), Video(yt_videoid="nDPy3RyKdrg", title="tmpwA0TjG", description="", publisher="UCsLiV4WJfkTEHH0b9PmRklw", publish_date=1488343000.0, watched=False), Video(yt_videoid="L0_F805qUIM", title="tmpKDOkro", description="", publisher="UCxexYYtOetqikZqriLuTS-g", publish_date=1488344253.0, watched=True), Video(yt_videoid="lXWrdlDEzQs", title="tmpEvCR4s", description="", publisher="UCxexYYtOetqikZqriLuTS-g", publish_date=1488343152.0, watched=True), Video(yt_videoid="cCnXsCQNkr8", title="tmp1rpsWK", description="", publisher="UCxexYYtOetqikZqriLuTS-g", publish_date=1488343046.0, watched=True), Video(yt_videoid="rSxVs0XeQa4", title="tmpc5Y2pd", description="", publisher="UCxexYYtOetqikZqriLuTS-g", publish_date=1488342015.0, watched=False), Video(yt_videoid="gQAsWrGfsrw", title="tmpn1M1Oa", description="", publisher="UCxexYYtOetqikZqriLuTS-g", publish_date=1488341324.0, watched=False), ] self.db_conn.session.execute("delete from video") self.db_conn.session.execute("delete from channel") self.db_conn.add_channel( Channel(displayname="Webdriver Torso", yt_channelid="UCsLiV4WJfkTEHH0b9PmRklw")) self.db_conn.add_channel( Channel(displayname="Webdriver YPP", yt_channelid="UCxexYYtOetqikZqriLuTS-g")) self.db_conn.add_videos(insert_list) self.video = self.db_conn.session.query(Video).filter( Video.title == "tmpIXBgjd").one() self.video_id = self.video.id