feat(downloads command): improve local downloads experience

This commit is contained in:
Benex254
2024-08-16 13:12:10 +03:00
parent 1291d55ab0
commit 9e48e02f7a
2 changed files with 200 additions and 45 deletions

View File

@@ -1,7 +1,9 @@
import logging
from typing import TYPE_CHECKING
import click
logger = logging.getLogger(__name__)
if TYPE_CHECKING:
from ..config import Config
@@ -10,9 +12,16 @@ if TYPE_CHECKING:
help="View and watch your downloads using mpv", short_help="Watch downloads"
)
@click.option("--path", "-p", help="print the downloads folder and exit", is_flag=True)
@click.option("--view-episodes", "-v", help="View individual episodes", is_flag=True)
@click.option(
"--ffmpegthumbnailer-seek-time",
"--time-to-seek",
"-t",
type=click.IntRange(-1, 100),
help="ffmpegthumbnailer seek time [0-100]",
)
@click.pass_obj
def downloads(config: "Config", path: bool):
import logging
def downloads(config: "Config", path: bool, view_episodes, ffmpegthumbnailer_seek_time):
import os
from ...cli.utils.mpv import run_mpv
@@ -21,8 +30,8 @@ def downloads(config: "Config", path: bool):
from ..utils.tools import exit_app
from ..utils.utils import fuzzy_inquirer
logger = logging.getLogger(__name__)
if not ffmpegthumbnailer_seek_time:
ffmpegthumbnailer_seek_time = config.ffmpegthumbnailer_seek_time
USER_VIDEOS_DIR = config.downloads_dir
if path:
print(USER_VIDEOS_DIR)
@@ -43,15 +52,29 @@ def downloads(config: "Config", path: bool):
return
out = os.path.join(downloads_thumbnail_cache_dir, anime_title)
completed_process = subprocess.run(
[FFMPEG_THUMBNAILER, "-i", video_path, "-o", out], stderr=subprocess.PIPE
)
if completed_process.returncode == 0:
logger.info(f"Success in creating {anime_title} thumbnail")
else:
logger.warn(f"Failed in creating {anime_title} thumbnail")
if ffmpegthumbnailer_seek_time == -1:
import random
def get_previews(workers=None):
seektime = str(random.randrange(0, 100))
else:
seektime = str(ffmpegthumbnailer_seek_time)
_ = subprocess.run(
[
FFMPEG_THUMBNAILER,
"-i",
video_path,
"-o",
out,
"-s",
"0",
"-t",
seektime,
],
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
)
def get_previews_anime(workers=None, bg=True):
import concurrent.futures
import shutil
from pathlib import Path
@@ -66,38 +89,52 @@ def downloads(config: "Config", path: bool):
downloads_thumbnail_cache_dir = os.path.join(APP_CACHE_DIR, "video_thumbnails")
Path(downloads_thumbnail_cache_dir).mkdir(parents=True, exist_ok=True)
# use concurrency to download the images as fast as possible
with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor:
# load the jobs
future_to_url = {}
for anime_title in anime_downloads:
anime_path = os.path.join(USER_VIDEOS_DIR, anime_title)
if not os.path.isdir(anime_path):
continue
playlist = os.listdir(anime_path)
if playlist:
# actual link to download image from
video_path = os.path.join(anime_path, playlist[0])
future_to_url[
executor.submit(
create_thumbnails,
video_path,
anime_title,
downloads_thumbnail_cache_dir,
)
] = anime_title
# execute the jobs
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
future.result()
except Exception as e:
logger.error("%r generated an exception: %s" % (url, e))
def _worker():
# use concurrency to download the images as fast as possible
with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor:
# load the jobs
future_to_url = {}
for anime_title in anime_downloads:
anime_path = os.path.join(USER_VIDEOS_DIR, anime_title)
if not os.path.isdir(anime_path):
continue
playlist = os.listdir(anime_path)
if playlist:
# actual link to download image from
video_path = os.path.join(anime_path, playlist[0])
future_to_url[
executor.submit(
create_thumbnails,
video_path,
anime_title,
downloads_thumbnail_cache_dir,
)
] = anime_title
# execute the jobs
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
future.result()
except Exception as e:
logger.error("%r generated an exception: %s" % (url, e))
if bg:
from threading import Thread
worker = Thread(target=_worker)
worker.daemon = True
worker.start()
else:
_worker()
os.environ["SHELL"] = shutil.which("bash") or "bash"
preview = """
%s
if [ -s %s/{} ]; then fzf-preview %s/{}
if [ -s %s/{} ]; then
if ! fzf-preview %s/{} 2>/dev/null; then
echo Loading...
fi
else echo Loading...
fi
""" % (
@@ -107,7 +144,115 @@ def downloads(config: "Config", path: bool):
)
return preview
def stream():
def get_previews_episodes(anime_playlist_path, workers=None, bg=True):
import shutil
from pathlib import Path
from ...constants import APP_CACHE_DIR
from ..utils.scripts import fzf_preview
if not shutil.which("ffmpegthumbnailer"):
print("ffmpegthumbnailer not found")
logger.error("ffmpegthumbnailer not found")
return
downloads_thumbnail_cache_dir = os.path.join(APP_CACHE_DIR, "video_thumbnails")
Path(downloads_thumbnail_cache_dir).mkdir(parents=True, exist_ok=True)
def _worker():
import concurrent.futures
# use concurrency to download the images as fast as possible
# anime_playlist_path = os.path.join(USER_VIDEOS_DIR, anime_playlist_path)
if not os.path.isdir(anime_playlist_path):
return
anime_episodes = os.listdir(anime_playlist_path)
with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor:
# load the jobs
future_to_url = {}
for episode_title in anime_episodes:
episode_path = os.path.join(anime_playlist_path, episode_title)
# actual link to download image from
future_to_url[
executor.submit(
create_thumbnails,
episode_path,
episode_title,
downloads_thumbnail_cache_dir,
)
] = episode_title
# execute the jobs
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
future.result()
except Exception as e:
logger.error("%r generated an exception: %s" % (url, e))
if bg:
from threading import Thread
worker = Thread(target=_worker)
worker.daemon = True
worker.start()
else:
_worker()
os.environ["SHELL"] = shutil.which("bash") or "bash"
preview = """
%s
if [ -s %s/{} ]; then
if ! fzf-preview %s/{} 2>/dev/null; then
echo Loading...
fi
else echo Loading...
fi
""" % (
fzf_preview,
downloads_thumbnail_cache_dir,
downloads_thumbnail_cache_dir,
)
return preview
def stream_episode(
anime_playlist_path,
):
if view_episodes:
if not os.path.isdir(anime_playlist_path):
print(anime_playlist_path, "is not dir")
exit_app(1)
return
episodes = os.listdir(anime_playlist_path)
downloaded_episodes = [*episodes, "Back"]
if config.use_fzf:
if not config.preview:
episode_title = fzf.run(
downloaded_episodes,
"Enter Episode ",
)
else:
preview = get_previews_episodes(anime_playlist_path)
episode_title = fzf.run(
downloaded_episodes,
"Enter Episode ",
preview=preview,
)
elif config.use_rofi:
episode_title = Rofi.run(downloaded_episodes, "Enter Episode")
else:
episode_title = fuzzy_inquirer(
downloaded_episodes,
"Enter Playlist Name: ",
)
if episode_title == "Back":
stream_anime()
return
episode_path = os.path.join(anime_playlist_path, episode_title)
run_mpv(episode_path)
stream_episode(anime_playlist_path)
def stream_anime():
if config.use_fzf:
if not config.preview:
playlist_name = fzf.run(
@@ -115,7 +260,7 @@ def downloads(config: "Config", path: bool):
"Enter Playlist Name",
)
else:
preview = get_previews()
preview = get_previews_anime()
playlist_name = fzf.run(
anime_downloads,
"Enter Playlist Name",
@@ -132,7 +277,12 @@ def downloads(config: "Config", path: bool):
exit_app()
return
playlist = os.path.join(USER_VIDEOS_DIR, playlist_name)
run_mpv(playlist)
stream()
if view_episodes:
stream_episode(
playlist,
)
else:
run_mpv(playlist)
stream_anime()
stream()
stream_anime()

View File

@@ -94,6 +94,7 @@ class Config(object):
"rofi_theme": "",
"rofi_theme_input": "",
"rofi_theme_confirm": "",
"ffmpegthumnailer_seek_time": "-1",
}
)
self.configparser.add_section("stream")
@@ -133,6 +134,7 @@ class Config(object):
Rofi.rofi_theme_input = self.rofi_theme_input
self.rofi_theme_confirm = self.get_rofi_theme_confirm()
Rofi.rofi_theme_confirm = self.rofi_theme_confirm
self.ffmpegthumbnailer_seek_time = self.get_ffmpegthumnailer_seek_time()
# ---- setup user data ------
self.watch_history: dict = self.user_data.get("watch_history", {})
self.anime_list: list = self.user_data.get("animelist", [])
@@ -178,6 +180,9 @@ class Config(object):
def get_provider(self):
return self.configparser.get("general", "provider")
def get_ffmpegthumnailer_seek_time(self):
return self.configparser.getint("general", "ffmpegthumnailer_seek_time")
def get_preferred_language(self):
return self.configparser.get("general", "preferred_language")