tubearchivist/tubearchivist/home/src/download.py

775 lines
28 KiB
Python
Raw Normal View History

2021-09-05 17:10:14 +00:00
"""
Functionality:
- handele the download queue
- manage subscriptions to channels
- downloading videos
"""
import json
import os
2021-09-18 13:02:54 +00:00
import shutil
2021-09-05 17:10:14 +00:00
from datetime import datetime
from time import sleep
import requests
import yt_dlp as youtube_dl
from home.src.config import AppConfig
from home.src.helper import (
DurationConverter,
RedisArchivist,
RedisQueue,
clean_string,
ignore_filelist,
)
from home.src.index import (
IndexPaginate,
YoutubeChannel,
YoutubePlaylist,
index_new_video,
)
2021-09-05 17:10:14 +00:00
class PendingList:
2021-09-21 09:25:22 +00:00
"""manage the pending videos list"""
2021-09-05 17:10:14 +00:00
CONFIG = AppConfig().config
2021-09-21 09:25:22 +00:00
ES_URL = CONFIG["application"]["es_url"]
2021-10-28 08:49:58 +00:00
ES_AUTH = CONFIG["application"]["es_auth"]
2021-09-21 09:25:22 +00:00
VIDEOS = CONFIG["application"]["videos"]
2021-09-05 17:10:14 +00:00
def __init__(self):
self.all_channel_ids = False
self.all_downloaded = False
self.missing_from_playlists = []
def parse_url_list(self, youtube_ids):
2021-09-21 09:25:22 +00:00
"""extract youtube ids from list"""
2021-09-05 17:10:14 +00:00
missing_videos = []
for entry in youtube_ids:
# notify
mess_dict = {
"status": "message:add",
"level": "info",
"title": "Adding to download queue.",
2021-09-21 09:25:22 +00:00
"message": "Extracting lists",
}
RedisArchivist().set_message("message:add", mess_dict)
# extract
2021-09-21 09:25:22 +00:00
url = entry["url"]
url_type = entry["type"]
if url_type == "video":
2021-09-05 17:10:14 +00:00
missing_videos.append(url)
2021-09-21 09:25:22 +00:00
elif url_type == "channel":
video_results = ChannelSubscription().get_last_youtube_videos(
2021-09-05 17:10:14 +00:00
url, limit=False
)
youtube_ids = [i[0] for i in video_results]
2021-09-05 17:10:14 +00:00
missing_videos = missing_videos + youtube_ids
2021-09-21 09:25:22 +00:00
elif url_type == "playlist":
self.missing_from_playlists.append(entry)
2021-11-05 09:24:05 +00:00
video_results = YoutubePlaylist(url).get_entries()
youtube_ids = [i["youtube_id"] for i in video_results]
2021-09-10 08:07:38 +00:00
missing_videos = missing_videos + youtube_ids
2021-09-05 17:10:14 +00:00
return missing_videos
def add_to_pending(self, missing_videos):
2021-09-21 09:25:22 +00:00
"""build the bulk json data from pending"""
2021-09-05 17:10:14 +00:00
# check if channel is indexed
channel_handler = ChannelSubscription()
all_indexed = channel_handler.get_channels(subscribed_only=False)
self.all_channel_ids = [i["channel_id"] for i in all_indexed]
2021-09-05 17:10:14 +00:00
# check if already there
self.all_downloaded = self.get_all_downloaded()
bulk_list, all_videos_added = self.build_bulk(missing_videos)
# add last newline
bulk_list.append("\n")
query_str = "\n".join(bulk_list)
headers = {"Content-type": "application/x-ndjson"}
url = self.ES_URL + "/_bulk"
request = requests.post(
url, data=query_str, headers=headers, auth=self.ES_AUTH
)
if not request.ok:
print(request)
raise ValueError("failed to add video to download queue")
return all_videos_added
def build_bulk(self, missing_videos):
"""build the bulk lists"""
2021-09-05 17:10:14 +00:00
bulk_list = []
all_videos_added = []
for idx, youtube_id in enumerate(missing_videos):
# check if already downloaded
if youtube_id in self.all_downloaded:
2021-09-05 17:10:14 +00:00
continue
2021-09-05 17:10:14 +00:00
video = self.get_youtube_details(youtube_id)
# skip on download error
if not video:
continue
channel_indexed = video["channel_id"] in self.all_channel_ids
video["channel_indexed"] = channel_indexed
2021-09-21 09:25:22 +00:00
video["status"] = "pending"
2021-09-05 17:10:14 +00:00
action = {"create": {"_id": youtube_id, "_index": "ta_download"}}
bulk_list.append(json.dumps(action))
bulk_list.append(json.dumps(video))
all_videos_added.append((youtube_id, video["vid_thumb_url"]))
# notify
progress = f"{idx + 1}/{len(missing_videos)}"
mess_dict = {
"status": "message:add",
"level": "info",
"title": "Adding new videos to download queue.",
"message": "Progress: " + progress,
}
if idx + 1 == len(missing_videos):
RedisArchivist().set_message(
"message:add", mess_dict, expire=4
)
else:
RedisArchivist().set_message("message:add", mess_dict)
if idx + 1 % 25 == 0:
print("adding to queue progress: " + progress)
2021-09-05 17:10:14 +00:00
return bulk_list, all_videos_added
2021-09-05 17:10:14 +00:00
@staticmethod
def get_youtube_details(youtube_id):
2021-09-21 09:25:22 +00:00
"""get details from youtubedl for single pending video"""
2021-09-05 17:10:14 +00:00
obs = {
2021-09-21 09:25:22 +00:00
"default_search": "ytsearch",
"quiet": True,
"check_formats": "selected",
"noplaylist": True,
"writethumbnail": True,
"simulate": True,
2021-09-05 17:10:14 +00:00
}
try:
vid = youtube_dl.YoutubeDL(obs).extract_info(youtube_id)
except youtube_dl.utils.DownloadError:
2021-09-21 09:25:22 +00:00
print("failed to extract info for: " + youtube_id)
2021-09-05 17:10:14 +00:00
return False
2021-10-17 03:49:02 +00:00
# stop if video is streaming live now
if vid["is_live"]:
return False
2021-09-05 17:10:14 +00:00
# parse response
2021-09-21 09:25:22 +00:00
seconds = vid["duration"]
2021-09-05 17:10:14 +00:00
duration_str = DurationConverter.get_str(seconds)
if duration_str == "NA":
print(f"skip extracting duration for: {youtube_id}")
2021-09-21 09:25:22 +00:00
upload_date = vid["upload_date"]
2021-09-05 17:10:14 +00:00
upload_dt = datetime.strptime(upload_date, "%Y%m%d")
published = upload_dt.strftime("%Y-%m-%d")
# build dict
youtube_details = {
"youtube_id": youtube_id,
2021-09-21 09:25:22 +00:00
"channel_name": vid["channel"],
"vid_thumb_url": vid["thumbnail"],
"title": vid["title"],
"channel_id": vid["channel_id"],
2021-09-05 17:10:14 +00:00
"duration": duration_str,
"published": published,
2021-09-21 09:25:22 +00:00
"timestamp": int(datetime.now().strftime("%s")),
2021-09-05 17:10:14 +00:00
}
return youtube_details
@staticmethod
def get_all_pending():
2021-09-21 09:25:22 +00:00
"""get a list of all pending videos in ta_download"""
2021-09-05 17:10:14 +00:00
data = {
2021-09-21 09:25:22 +00:00
"query": {"match_all": {}},
"sort": [{"timestamp": {"order": "asc"}}],
2021-09-05 17:10:14 +00:00
}
all_results = IndexPaginate("ta_download", data).get_results()
2021-09-05 17:10:14 +00:00
all_pending = []
all_ignore = []
for result in all_results:
if result["status"] == "pending":
all_pending.append(result)
elif result["status"] == "ignore":
all_ignore.append(result)
2021-09-05 17:10:14 +00:00
return all_pending, all_ignore
@staticmethod
def get_all_indexed():
2021-09-21 09:25:22 +00:00
"""get a list of all videos indexed"""
2021-09-05 17:10:14 +00:00
data = {
2021-09-21 09:25:22 +00:00
"query": {"match_all": {}},
"sort": [{"published": {"order": "desc"}}],
2021-09-05 17:10:14 +00:00
}
all_indexed = IndexPaginate("ta_video", data).get_results()
2021-09-05 17:10:14 +00:00
return all_indexed
def get_all_downloaded(self):
2021-09-21 09:25:22 +00:00
"""get a list of all videos in archive"""
channel_folders = os.listdir(self.VIDEOS)
all_channel_folders = ignore_filelist(channel_folders)
2021-09-05 17:10:14 +00:00
all_downloaded = []
for channel_folder in all_channel_folders:
channel_path = os.path.join(self.VIDEOS, channel_folder)
videos = os.listdir(channel_path)
all_videos = ignore_filelist(videos)
2021-09-05 17:10:14 +00:00
youtube_vids = [i[9:20] for i in all_videos]
for youtube_id in youtube_vids:
all_downloaded.append(youtube_id)
return all_downloaded
def delete_from_pending(self, youtube_id):
2021-09-21 09:25:22 +00:00
"""delete the youtube_id from ta_download"""
url = f"{self.ES_URL}/ta_download/_doc/{youtube_id}"
2021-10-28 08:49:58 +00:00
response = requests.delete(url, auth=self.ES_AUTH)
2021-09-05 17:10:14 +00:00
if not response.ok:
print(response.text)
def delete_pending(self, status):
"""delete download queue based on status value"""
data = {"query": {"term": {"status": {"value": status}}}}
payload = json.dumps(data)
url = self.ES_URL + "/ta_download/_delete_by_query"
headers = {"Content-type": "application/json"}
response = requests.post(
url, data=payload, headers=headers, auth=self.ES_AUTH
)
if not response.ok:
print(response.text)
2021-09-05 17:10:14 +00:00
def ignore_from_pending(self, ignore_list):
2021-09-21 09:25:22 +00:00
"""build the bulk query string"""
2021-09-05 17:10:14 +00:00
stamp = int(datetime.now().strftime("%s"))
bulk_list = []
for youtube_id in ignore_list:
action = {"update": {"_id": youtube_id, "_index": "ta_download"}}
2021-09-21 09:25:22 +00:00
source = {"doc": {"status": "ignore", "timestamp": stamp}}
2021-09-05 17:10:14 +00:00
bulk_list.append(json.dumps(action))
bulk_list.append(json.dumps(source))
# add last newline
2021-09-21 09:25:22 +00:00
bulk_list.append("\n")
query_str = "\n".join(bulk_list)
2021-09-05 17:10:14 +00:00
2021-09-21 09:25:22 +00:00
headers = {"Content-type": "application/x-ndjson"}
url = self.ES_URL + "/_bulk"
2021-10-28 08:49:58 +00:00
request = requests.post(
url, data=query_str, headers=headers, auth=self.ES_AUTH
)
2021-09-05 17:10:14 +00:00
if not request.ok:
print(request)
raise ValueError("failed to set video to ignore")
2021-09-05 17:10:14 +00:00
class ChannelSubscription:
2021-09-21 09:25:22 +00:00
"""manage the list of channels subscribed"""
2021-09-05 17:10:14 +00:00
def __init__(self):
config = AppConfig().config
2021-09-21 09:25:22 +00:00
self.es_url = config["application"]["es_url"]
2021-10-28 08:49:58 +00:00
self.es_auth = config["application"]["es_auth"]
2021-09-21 09:25:22 +00:00
self.channel_size = config["subscriptions"]["channel_size"]
2021-09-05 17:10:14 +00:00
@staticmethod
def get_channels(subscribed_only=True):
2021-09-21 09:25:22 +00:00
"""get a list of all channels subscribed to"""
data = {
"sort": [{"channel_name.keyword": {"order": "asc"}}],
}
2021-09-05 17:10:14 +00:00
if subscribed_only:
data["query"] = {"term": {"channel_subscribed": {"value": True}}}
2021-09-05 17:10:14 +00:00
else:
data["query"] = {"match_all": {}}
all_channels = IndexPaginate("ta_channel", data).get_results()
2021-09-05 17:10:14 +00:00
return all_channels
def get_last_youtube_videos(self, channel_id, limit=True):
2021-09-21 09:25:22 +00:00
"""get a list of last videos from channel"""
url = f"https://www.youtube.com/channel/{channel_id}/videos"
2021-09-05 17:10:14 +00:00
obs = {
2021-09-21 09:25:22 +00:00
"default_search": "ytsearch",
"quiet": True,
"skip_download": True,
"extract_flat": True,
2021-09-05 17:10:14 +00:00
}
if limit:
2021-09-21 09:25:22 +00:00
obs["playlistend"] = self.channel_size
2021-09-05 17:10:14 +00:00
chan = youtube_dl.YoutubeDL(obs).extract_info(url, download=False)
2021-09-21 09:25:22 +00:00
last_videos = [(i["id"], i["title"]) for i in chan["entries"]]
2021-09-05 17:10:14 +00:00
return last_videos
def find_missing(self):
2021-09-21 09:25:22 +00:00
"""add missing videos from subscribed channels to pending"""
2021-09-05 17:10:14 +00:00
all_channels = self.get_channels()
pending_handler = PendingList()
all_pending, all_ignore = pending_handler.get_all_pending()
all_ids = [i["youtube_id"] for i in all_ignore + all_pending]
2021-09-05 17:10:14 +00:00
all_downloaded = pending_handler.get_all_downloaded()
to_ignore = all_ids + all_downloaded
2021-09-05 17:10:14 +00:00
missing_videos = []
for idx, channel in enumerate(all_channels):
2021-09-21 09:25:22 +00:00
channel_id = channel["channel_id"]
2021-09-05 17:10:14 +00:00
last_videos = self.get_last_youtube_videos(channel_id)
for video in last_videos:
if video[0] not in to_ignore:
missing_videos.append(video[0])
# notify
message = {
"status": "message:rescan",
"level": "info",
"title": "Scanning channels: Looking for new videos.",
"message": f"Progress: {idx + 1}/{len(all_channels)}",
}
if idx + 1 == len(all_channels):
RedisArchivist().set_message(
"message:rescan", message=message, expire=4
)
else:
RedisArchivist().set_message("message:rescan", message=message)
2021-09-05 17:10:14 +00:00
return missing_videos
def change_subscribe(self, channel_id, channel_subscribed):
2021-09-21 09:25:22 +00:00
"""subscribe or unsubscribe from channel and update"""
2021-09-05 17:10:14 +00:00
if not isinstance(channel_subscribed, bool):
2021-09-21 09:25:22 +00:00
print("invalid status, should be bool")
2021-09-05 17:10:14 +00:00
return
2021-09-21 09:25:22 +00:00
headers = {"Content-type": "application/json"}
2021-09-05 17:10:14 +00:00
channel_handler = YoutubeChannel(channel_id)
channel_dict = channel_handler.channel_dict
2021-09-21 09:25:22 +00:00
channel_dict["channel_subscribed"] = channel_subscribed
2021-09-05 17:10:14 +00:00
if channel_subscribed:
# handle subscribe
2021-09-21 09:25:22 +00:00
url = self.es_url + "/ta_channel/_doc/" + channel_id
2021-09-05 17:10:14 +00:00
payload = json.dumps(channel_dict)
print(channel_dict)
else:
2021-09-21 09:25:22 +00:00
url = self.es_url + "/ta_channel/_update/" + channel_id
payload = json.dumps({"doc": channel_dict})
2021-09-05 17:10:14 +00:00
# update channel
2021-10-28 08:49:58 +00:00
request = requests.post(
url, data=payload, headers=headers, auth=self.es_auth
)
2021-09-05 17:10:14 +00:00
if not request.ok:
print(request.text)
raise ValueError("failed change subscribe status")
2021-09-05 17:10:14 +00:00
# sync to videos
channel_handler.sync_to_videos()
if channel_handler.source == "scraped":
channel_handler.get_channel_art()
2021-09-05 17:10:14 +00:00
2021-11-10 10:55:34 +00:00
class PlaylistSubscription:
"""manage the playlist download functionality"""
def __init__(self):
2021-11-18 09:58:39 +00:00
self.config = AppConfig().config
2021-11-10 10:55:34 +00:00
@staticmethod
def get_playlists(subscribed_only=True):
2021-11-27 04:47:12 +00:00
"""get a list of all active playlists"""
data = {
"sort": [{"playlist_channel.keyword": {"order": "desc"}}],
}
2021-11-27 04:47:12 +00:00
data["query"] = {
"bool": {"must": [{"term": {"playlist_active": {"value": True}}}]}
}
2021-11-10 10:55:34 +00:00
if subscribed_only:
2021-11-27 04:47:12 +00:00
data["query"]["bool"]["must"].append(
{"term": {"playlist_subscribed": {"value": True}}}
)
all_playlists = IndexPaginate("ta_playlist", data).get_results()
2021-11-10 10:55:34 +00:00
return all_playlists
def process_url_str(self, new_playlists, subscribed=True):
"""process playlist subscribe form url_str"""
all_indexed = PendingList().get_all_indexed()
all_youtube_ids = [i["youtube_id"] for i in all_indexed]
new_thumbs = []
for idx, playlist in enumerate(new_playlists):
url_type = playlist["type"]
playlist_id = playlist["url"]
if not url_type == "playlist":
print(f"{playlist_id} not a playlist, skipping...")
continue
playlist_h = YoutubePlaylist(
playlist_id, all_youtube_ids=all_youtube_ids
)
if not playlist_h.get_es_playlist():
playlist_h.get_playlist_dict()
playlist_h.playlist_dict["playlist_subscribed"] = subscribed
playlist_h.upload_to_es()
playlist_h.add_vids_to_playlist()
thumb = playlist_h.playlist_dict["playlist_thumbnail"]
new_thumbs.append((playlist_id, thumb))
self.channel_validate(playlist_h)
else:
self.change_subscribe(playlist_id, subscribe_status=True)
# notify
message = {
"status": "message:subplaylist",
"level": "info",
"title": "Subscribing to Playlists",
"message": f"Processing {idx + 1} of {len(new_playlists)}",
}
RedisArchivist().set_message(
"message:subplaylist", message=message
)
return new_thumbs
@staticmethod
def channel_validate(playlist_handler):
"""make sure channel of playlist is there"""
channel_id = playlist_handler.playlist_dict["playlist_channel_id"]
channel_handler = YoutubeChannel(channel_id)
if channel_handler.source == "scraped":
channel_handler.channel_dict["channel_subscribed"] = False
channel_handler.upload_to_es()
channel_handler.get_channel_art()
def change_subscribe(self, playlist_id, subscribe_status):
"""change the subscribe status of a playlist"""
2021-11-18 09:58:39 +00:00
es_url = self.config["application"]["es_url"]
es_auth = self.config["application"]["es_auth"]
playlist_handler = YoutubePlaylist(playlist_id)
playlist_handler.get_playlist_dict()
subed_now = playlist_handler.playlist_dict["playlist_subscribed"]
if subed_now == subscribe_status:
# status already as expected, do nothing
return False
# update subscribed status
headers = {"Content-type": "application/json"}
2021-11-18 09:58:39 +00:00
url = f"{es_url}/ta_playlist/_update/{playlist_id}"
payload = json.dumps(
{"doc": {"playlist_subscribed": subscribe_status}}
)
response = requests.post(
2021-11-18 09:58:39 +00:00
url, data=payload, headers=headers, auth=es_auth
)
if not response.ok:
print(response.text)
raise ValueError("failed to change subscribe status")
return True
@staticmethod
def get_to_ignore():
"""get all youtube_ids already downloaded or ignored"""
pending_handler = PendingList()
all_pending, all_ignore = pending_handler.get_all_pending()
all_ids = [i["youtube_id"] for i in all_ignore + all_pending]
all_downloaded = pending_handler.get_all_downloaded()
to_ignore = all_ids + all_downloaded
return to_ignore
def find_missing(self):
"""find videos in subscribed playlists not downloaded yet"""
all_playlists = [i["playlist_id"] for i in self.get_playlists()]
to_ignore = self.get_to_ignore()
missing_videos = []
counter = 1
for playlist_id in all_playlists:
2021-11-18 09:58:39 +00:00
size_limit = self.config["subscriptions"]["channel_size"]
2021-11-27 04:47:12 +00:00
playlist_handler = YoutubePlaylist(playlist_id)
playlist = playlist_handler.update_playlist()
if not playlist:
playlist_handler.deactivate()
continue
2021-11-18 09:58:39 +00:00
if size_limit:
playlist_entries = playlist["playlist_entries"][:size_limit]
else:
playlist_entries = playlist["playlist_entries"]
all_missing = [i for i in playlist_entries if not i["downloaded"]]
message = {
"status": "message:rescan",
"level": "info",
"title": "Scanning playlists: Looking for new videos.",
"message": f"Progress: {counter}/{len(all_playlists)}",
}
RedisArchivist().set_message("message:rescan", message=message)
for video in all_missing:
youtube_id = video["youtube_id"]
if youtube_id not in to_ignore:
missing_videos.append(youtube_id)
counter = counter + 1
return missing_videos
2021-11-10 10:55:34 +00:00
2021-09-05 17:10:14 +00:00
class VideoDownloader:
"""
handle the video download functionality
if not initiated with list, take from queue
"""
2021-09-05 17:10:14 +00:00
def __init__(self, youtube_id_list=False):
2021-09-05 17:10:14 +00:00
self.youtube_id_list = youtube_id_list
self.config = AppConfig().config
self.channels = set()
2021-09-05 17:10:14 +00:00
def run_queue(self):
"""setup download queue in redis loop until no more items"""
queue = RedisQueue("dl_queue")
limit_queue = self.config["downloads"]["limit_count"]
if limit_queue:
queue.trim(limit_queue - 1)
while True:
youtube_id = queue.get_next()
if not youtube_id:
break
2021-09-05 17:10:14 +00:00
try:
self.dl_single_vid(youtube_id)
except youtube_dl.utils.DownloadError:
2021-09-21 09:25:22 +00:00
print("failed to download " + youtube_id)
2021-09-05 17:10:14 +00:00
continue
vid_dict = index_new_video(youtube_id)
self.channels.add(vid_dict["channel"]["channel_id"])
2021-09-05 17:10:14 +00:00
self.move_to_archive(vid_dict)
self.delete_from_pending(youtube_id)
@staticmethod
def add_pending():
"""add pending videos to download queue"""
mess_dict = {
"status": "message:download",
"level": "info",
"title": "Looking for videos to download",
"message": "Scanning your download queue.",
}
RedisArchivist().set_message("message:download", mess_dict)
all_pending, _ = PendingList().get_all_pending()
to_add = [i["youtube_id"] for i in all_pending]
2021-10-17 04:28:05 +00:00
if not to_add:
# there is nothing pending
print("download queue is empty")
mess_dict = {
"status": "message:download",
2021-10-17 04:28:05 +00:00
"level": "error",
"title": "Download queue is empty",
"message": "Add some videos to the queue first.",
2021-10-17 04:28:05 +00:00
}
RedisArchivist().set_message("message:download", mess_dict)
2021-10-17 04:28:05 +00:00
return
queue = RedisQueue("dl_queue")
queue.add_list(to_add)
2021-09-05 17:10:14 +00:00
@staticmethod
def progress_hook(response):
2021-09-21 09:25:22 +00:00
"""process the progress_hooks from youtube_dl"""
2021-09-05 17:10:14 +00:00
# title
path = os.path.split(response["filename"])[-1][12:]
filename = os.path.splitext(os.path.splitext(path)[0])[0]
filename_clean = filename.replace("_", " ")
title = "Downloading: " + filename_clean
2021-09-05 17:10:14 +00:00
# message
try:
2021-09-21 09:25:22 +00:00
percent = response["_percent_str"]
size = response["_total_bytes_str"]
speed = response["_speed_str"]
eta = response["_eta_str"]
message = f"{percent} of {size} at {speed} - time left: {eta}"
2021-09-05 17:10:14 +00:00
except KeyError:
message = "processing"
2021-09-05 17:10:14 +00:00
mess_dict = {
2021-12-05 09:41:06 +00:00
"status": "message:download",
2021-09-05 17:10:14 +00:00
"level": "info",
"title": title,
2021-09-21 09:25:22 +00:00
"message": message,
2021-09-05 17:10:14 +00:00
}
RedisArchivist().set_message("message:download", mess_dict)
2021-09-05 17:10:14 +00:00
def build_obs(self):
"""build obs dictionary for yt-dlp"""
2021-09-05 17:10:14 +00:00
obs = {
2021-09-21 09:25:22 +00:00
"default_search": "ytsearch",
"merge_output_format": "mp4",
"restrictfilenames": True,
"outtmpl": (
self.config["application"]["cache_dir"]
+ "/download/"
+ self.config["application"]["file_template"]
),
"progress_hooks": [self.progress_hook],
"noprogress": True,
2021-09-21 09:25:22 +00:00
"quiet": True,
"continuedl": True,
"retries": 3,
"writethumbnail": False,
"noplaylist": True,
"check_formats": "selected",
2021-09-05 17:10:14 +00:00
}
2021-09-21 09:25:22 +00:00
if self.config["downloads"]["format"]:
obs["format"] = self.config["downloads"]["format"]
if self.config["downloads"]["limit_speed"]:
obs["ratelimit"] = self.config["downloads"]["limit_speed"] * 1024
2021-10-26 10:14:26 +00:00
throttle = self.config["downloads"]["throttledratelimit"]
if throttle:
obs["throttledratelimit"] = throttle * 1024
2021-09-05 17:10:14 +00:00
external = False
if external:
2021-09-21 09:25:22 +00:00
obs["external_downloader"] = "aria2c"
postprocessors = []
2021-09-21 09:25:22 +00:00
if self.config["downloads"]["add_metadata"]:
postprocessors.append(
{
"key": "FFmpegMetadata",
"add_chapters": True,
"add_metadata": True,
}
)
if self.config["downloads"]["add_thumbnail"]:
postprocessors.append(
{
"key": "EmbedThumbnail",
"already_have_thumbnail": True,
}
)
obs["writethumbnail"] = True
2021-09-21 09:25:22 +00:00
obs["postprocessors"] = postprocessors
return obs
def dl_single_vid(self, youtube_id):
"""download single video"""
dl_cache = self.config["application"]["cache_dir"] + "/download/"
obs = self.build_obs()
2021-09-05 17:10:14 +00:00
# check if already in cache to continue from there
all_cached = ignore_filelist(os.listdir(dl_cache))
2021-09-05 17:10:14 +00:00
for file_name in all_cached:
if youtube_id in file_name:
obs["outtmpl"] = os.path.join(dl_cache, file_name)
2021-09-05 17:10:14 +00:00
with youtube_dl.YoutubeDL(obs) as ydl:
try:
ydl.download([youtube_id])
except youtube_dl.utils.DownloadError:
2021-09-21 09:25:22 +00:00
print("retry failed download: " + youtube_id)
2021-09-05 17:10:14 +00:00
sleep(10)
ydl.download([youtube_id])
if obs["writethumbnail"]:
# webp files don't get cleaned up automatically
all_cached = ignore_filelist(os.listdir(dl_cache))
to_clean = [i for i in all_cached if not i.endswith(".mp4")]
for file_name in to_clean:
file_path = os.path.join(dl_cache, file_name)
os.remove(file_path)
2021-09-05 17:10:14 +00:00
def move_to_archive(self, vid_dict):
2021-09-21 09:25:22 +00:00
"""move downloaded video from cache to archive"""
videos = self.config["application"]["videos"]
host_uid = self.config["application"]["HOST_UID"]
host_gid = self.config["application"]["HOST_GID"]
channel_name = clean_string(vid_dict["channel"]["channel_name"])
# make archive folder with correct permissions
new_folder = os.path.join(videos, channel_name)
if not os.path.exists(new_folder):
os.makedirs(new_folder)
if host_uid and host_gid:
os.chown(new_folder, host_uid, host_gid)
2021-09-05 17:10:14 +00:00
# find real filename
2021-09-21 09:25:22 +00:00
cache_dir = self.config["application"]["cache_dir"]
all_cached = ignore_filelist(os.listdir(cache_dir + "/download/"))
for file_str in all_cached:
if vid_dict["youtube_id"] in file_str:
2021-09-05 17:10:14 +00:00
old_file = file_str
2021-09-21 09:25:22 +00:00
old_file_path = os.path.join(cache_dir, "download", old_file)
new_file_path = os.path.join(videos, vid_dict["media_url"])
# move media file and fix permission
2021-09-05 17:10:14 +00:00
shutil.move(old_file_path, new_file_path)
if host_uid and host_gid:
os.chown(new_file_path, host_uid, host_gid)
2021-09-05 17:10:14 +00:00
def delete_from_pending(self, youtube_id):
2021-09-21 09:25:22 +00:00
"""delete downloaded video from pending index if its there"""
es_url = self.config["application"]["es_url"]
2021-10-28 08:49:58 +00:00
es_auth = self.config["application"]["es_auth"]
2021-09-21 09:25:22 +00:00
url = f"{es_url}/ta_download/_doc/{youtube_id}"
2021-10-28 08:49:58 +00:00
response = requests.delete(url, auth=es_auth)
2021-09-05 17:10:14 +00:00
if not response.ok and not response.status_code == 404:
print(response.text)
def add_subscribed_channels(self):
"""add all channels subscribed to refresh"""
all_subscribed = PlaylistSubscription().get_playlists()
if not all_subscribed:
return
channel_ids = [i["playlist_channel_id"] for i in all_subscribed]
for channel_id in channel_ids:
self.channels.add(channel_id)
return
def validate_playlists(self):
"""look for playlist needing to update"""
print("sync playlists")
self.add_subscribed_channels()
all_indexed = PendingList().get_all_indexed()
all_youtube_ids = [i["youtube_id"] for i in all_indexed]
for id_c, channel_id in enumerate(self.channels):
playlists = YoutubeChannel(channel_id).get_indexed_playlists()
all_playlist_ids = [i["playlist_id"] for i in playlists]
for id_p, playlist_id in enumerate(all_playlist_ids):
playlist_handler = YoutubePlaylist(
playlist_id, all_youtube_ids=all_youtube_ids
)
2021-11-27 04:47:12 +00:00
playlist_dict = playlist_handler.update_playlist()
if not playlist_dict:
playlist_handler.deactivate()
continue
playlist_handler.add_vids_to_playlist()
2021-11-19 08:53:06 +00:00
# notify
title = (
"Processing playlists for channels: "
+ f"{id_c + 1}/{len(self.channels)}"
2021-11-19 08:53:06 +00:00
)
message = f"Progress: {id_p + 1}/{len(all_playlist_ids)}"
2021-11-19 08:53:06 +00:00
mess_dict = {
"status": "message:download",
2021-11-19 08:53:06 +00:00
"level": "info",
"title": title,
"message": message,
}
if id_p + 1 == len(all_playlist_ids):
RedisArchivist().set_message(
"message:download", mess_dict, expire=4
)
else:
RedisArchivist().set_message("message:download", mess_dict)