mirror of
https://github.com/Benexl/FastAnime.git
synced 2025-12-27 05:03:41 -08:00
feat(download screen):implement download capabilities
This commit is contained in:
@@ -5,3 +5,17 @@ class DownloadsScreenModel(BaseScreenModel):
|
||||
"""
|
||||
Handles the download screen logic
|
||||
"""
|
||||
|
||||
def update_download_progress(self, d):
|
||||
print(
|
||||
d["filename"],
|
||||
d["downloaded_bytes"],
|
||||
d["total_bytes"],
|
||||
d.get("total_bytes"),
|
||||
d["elapsed"],
|
||||
d["eta"],
|
||||
d["speed"],
|
||||
d.get("percent"),
|
||||
)
|
||||
if d["status"] == "finished":
|
||||
print("Done downloading, now converting ...")
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
from threading import Thread
|
||||
from queue import Queue
|
||||
|
||||
import yt_dlp
|
||||
from ... import downloads_dir
|
||||
from ..utils import sanitize_filename
|
||||
from ..show_notification import show_notification
|
||||
|
||||
|
||||
class MyLogger:
|
||||
@@ -12,27 +18,55 @@ class MyLogger:
|
||||
print(msg)
|
||||
|
||||
|
||||
def my_hook(d):
|
||||
if d["status"] == "finished":
|
||||
print("Done downloading, now converting ...")
|
||||
def main_progress_hook(data):
|
||||
match data["status"]:
|
||||
case "error":
|
||||
show_notification(
|
||||
"Something went wrong while downloading the video", data["filename"]
|
||||
)
|
||||
case "finished":
|
||||
show_notification("Downloaded", data["filename"])
|
||||
|
||||
|
||||
# URL of the file you want to download
|
||||
url = "http://example.com/path/to/file.mp4"
|
||||
|
||||
# Options for yt-dlp
|
||||
ydl_opts = {
|
||||
"outtmpl": "/path/to/downloaded/file.%(ext)s", # Specify the output path and template
|
||||
"logger": MyLogger(), # Custom logger
|
||||
"progress_hooks": [my_hook], # Progress hook
|
||||
}
|
||||
|
||||
|
||||
# Function to download the file
|
||||
def download_file(url, options):
|
||||
with yt_dlp.YoutubeDL(options) as ydl:
|
||||
ydl.download([url])
|
||||
class YtDLPDownloader:
|
||||
downloads_queue = Queue()
|
||||
|
||||
def _worker(self):
|
||||
while True:
|
||||
task, args = self.downloads_queue.get()
|
||||
try:
|
||||
task(*args)
|
||||
except Exception as e:
|
||||
show_notification("Something went wrong", f"Reason: {e}")
|
||||
self.downloads_queue.task_done()
|
||||
|
||||
def __init__(self):
|
||||
self._thread = Thread(target=self._worker)
|
||||
self._thread.daemon = True
|
||||
self._thread.start()
|
||||
|
||||
# Function to download the file
|
||||
def _download_file(self, url: str, title, custom_progress_hook, silent):
|
||||
anime_title = sanitize_filename(title[0])
|
||||
ydl_opts = {
|
||||
"outtmpl": f"{downloads_dir}/{anime_title}/{anime_title}-episode {title[1]}.%(ext)s", # Specify the output path and template
|
||||
"progress_hooks": [
|
||||
main_progress_hook,
|
||||
custom_progress_hook,
|
||||
], # Progress hook
|
||||
"silent": silent,
|
||||
}
|
||||
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
ydl.download([url])
|
||||
|
||||
def download_file(self, url: str, title, custom_progress_hook, silent=True):
|
||||
self.downloads_queue.put(
|
||||
(self._download_file, (url, title, custom_progress_hook, silent))
|
||||
)
|
||||
|
||||
|
||||
# Call the function
|
||||
download_file(url, ydl_opts)
|
||||
downloader = YtDLPDownloader()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import os
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
|
||||
import re
|
||||
# TODO: make it use color_text instead of fixed vals
|
||||
# from .kivy_markup_helper import color_text
|
||||
|
||||
@@ -34,3 +34,62 @@ def move_file(source_path, dest_path):
|
||||
return (1, path)
|
||||
except Exception as e:
|
||||
return (0, e)
|
||||
|
||||
|
||||
def sanitize_filename(filename: str):
|
||||
"""
|
||||
Sanitize a string to be safe for use as a file name.
|
||||
|
||||
:param filename: The original filename string.
|
||||
:return: A sanitized filename string.
|
||||
"""
|
||||
# List of characters not allowed in filenames on various operating systems
|
||||
invalid_chars = r'[<>:"/\\|?*\0]'
|
||||
reserved_names = {
|
||||
"CON",
|
||||
"PRN",
|
||||
"AUX",
|
||||
"NUL",
|
||||
"COM1",
|
||||
"COM2",
|
||||
"COM3",
|
||||
"COM4",
|
||||
"COM5",
|
||||
"COM6",
|
||||
"COM7",
|
||||
"COM8",
|
||||
"COM9",
|
||||
"LPT1",
|
||||
"LPT2",
|
||||
"LPT3",
|
||||
"LPT4",
|
||||
"LPT5",
|
||||
"LPT6",
|
||||
"LPT7",
|
||||
"LPT8",
|
||||
"LPT9",
|
||||
}
|
||||
|
||||
# Replace invalid characters with an underscore
|
||||
sanitized = re.sub(invalid_chars, "_", filename)
|
||||
|
||||
# Remove leading and trailing whitespace
|
||||
sanitized = sanitized.strip()
|
||||
|
||||
# Check for reserved filenames
|
||||
name, ext = os.path.splitext(sanitized)
|
||||
if name.upper() in reserved_names:
|
||||
name += "_file"
|
||||
sanitized = name + ext
|
||||
|
||||
# Ensure the filename is not empty
|
||||
if not sanitized:
|
||||
sanitized = "default_filename"
|
||||
|
||||
return sanitized
|
||||
|
||||
|
||||
# Example usage
|
||||
unsafe_filename = "CON:example?file*name.txt"
|
||||
safe_filename = sanitize_filename(unsafe_filename)
|
||||
print(safe_filename) # Output: 'CON_example_file_name.txt'
|
||||
|
||||
@@ -47,6 +47,8 @@
|
||||
on_press: root.next_episode()
|
||||
MDIconButton:
|
||||
icon:"download"
|
||||
on_press:
|
||||
if root.current_link: app.download_anime_video(root.current_link,(root.current_title[0],root.current_episode))
|
||||
MDButton:
|
||||
on_press:
|
||||
if root.current_link: app.play_on_mpv(root.current_link)
|
||||
|
||||
@@ -18,11 +18,6 @@
|
||||
|
||||
TaskText:
|
||||
size_hint_x:.8
|
||||
text:color_text(root.anime_task_name,root.theme_cls.primaryColor)
|
||||
TaskText:
|
||||
size_hint_x:.2
|
||||
# color:self.theme_cls.surfaceDimColor
|
||||
theme_text_color:"Secondary"
|
||||
text:color_text(root.episodes_to_download,root.theme_cls.secondaryColor)
|
||||
MDIcon:
|
||||
icon:"download"
|
||||
text:"{} Episode: {}".format(root.file[0],root.file[1])
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
from kivy.properties import StringProperty
|
||||
from kivy.properties import StringProperty, ListProperty
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
|
||||
|
||||
# TODO: add a progress bar to show the individual progress of each task
|
||||
class TaskCard(MDBoxLayout):
|
||||
anime_task_name = StringProperty()
|
||||
episodes_to_download = StringProperty()
|
||||
file = ListProperty(("", ""))
|
||||
eta = StringProperty()
|
||||
|
||||
def __init__(self, anime_title: str, episodes: str, *args, **kwargs):
|
||||
def __init__(self, file: str, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.anime_task_name = f"{anime_title}"
|
||||
self.episodes_to_download = f"Episodes: {episodes}"
|
||||
self.file = file
|
||||
# self.eta = eta
|
||||
#
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
shorten:False
|
||||
markup:True
|
||||
font_style: "Label"
|
||||
role: "large"
|
||||
role: "small"
|
||||
bold:True
|
||||
<DownloadsScreenView>
|
||||
md_bg_color: self.theme_cls.backgroundColor
|
||||
@@ -46,12 +46,12 @@
|
||||
md_bg_color:self.theme_cls.secondaryContainerColor
|
||||
DownloadsScreenLabel:
|
||||
id:download_progress_label
|
||||
size_hint_x: .6
|
||||
size_hint_x: .8
|
||||
text:"Try Downloading sth :)"
|
||||
pos_hint: {'center_y': .5}
|
||||
MDLinearProgressIndicator:
|
||||
id: progress_bar
|
||||
size_hint_x: .4
|
||||
size_hint_x: .2
|
||||
size_hint_y:None
|
||||
height:"10dp"
|
||||
type: "determinate"
|
||||
|
||||
@@ -11,21 +11,31 @@ class DownloadsScreenView(BaseScreenView):
|
||||
progress_bar = ObjectProperty()
|
||||
download_progress_label = ObjectProperty()
|
||||
|
||||
def on_new_download_task(self, anime_title: str, episodes: str | None):
|
||||
if not episodes:
|
||||
episodes = "All"
|
||||
def new_download_task(self, filename):
|
||||
Clock.schedule_once(
|
||||
lambda _: self.main_container.add_widget(TaskCard(anime_title, episodes))
|
||||
lambda _: self.main_container.add_widget(TaskCard(filename))
|
||||
)
|
||||
|
||||
def on_episode_download_progress(
|
||||
self, current_bytes_downloaded, total_bytes, episode_info
|
||||
):
|
||||
percentage_completion = round((current_bytes_downloaded / total_bytes) * 100)
|
||||
progress_text = f"Downloading: {episode_info['anime_title']} - {episode_info['episode']} ({format_bytes_to_human(current_bytes_downloaded)}/{format_bytes_to_human(total_bytes)})"
|
||||
if (percentage_completion % 5) == 0:
|
||||
self.progress_bar.value = max(min(percentage_completion, 100), 0)
|
||||
self.download_progress_label.text = progress_text
|
||||
def on_episode_download_progress(self, data):
|
||||
percentage_completion = round(
|
||||
(data.get("downloaded_bytes", 0) / data.get("total_bytes", 0)) * 100
|
||||
)
|
||||
speed = format_bytes_to_human(data.get("speed", 0)) if data.get("speed") else 0
|
||||
progress_text = f"Downloading: {data.get('filename', 'unknown')} ({format_bytes_to_human(data.get('downloaded_bytes',0)) if data.get('downloaded_bytes') else 0}/{format_bytes_to_human(data.get('total_bytes',0)) if data.get('total_bytes') else 0})\n Elapsed: {round(data.get('elapsed',0)) if data.get('elapsed') else 0}s ETA: {data.get('eta',0) if data.get('eta') else 0}s Speed: {speed}/s"
|
||||
|
||||
self.progress_bar.value = max(min(percentage_completion, 100), 0)
|
||||
self.download_progress_label.text = progress_text
|
||||
|
||||
def update_layout(self, widget):
|
||||
self.user_anime_list_container.add_widget(widget)
|
||||
|
||||
#
|
||||
# d["filename"],
|
||||
# d["downloaded_bytes"],
|
||||
# d["total_bytes"],
|
||||
# d.get("total_bytes"),
|
||||
# d["elapsed"],
|
||||
# d["eta"],
|
||||
# d["speed"],
|
||||
# d.get("percent"),
|
||||
# )
|
||||
|
||||
@@ -15,7 +15,7 @@ if vid_path := plyer.storagepath.get_videos_dir(): # type: ignore
|
||||
if not os.path.exists(downloads_dir):
|
||||
os.mkdir(downloads_dir)
|
||||
else:
|
||||
downloads_dir = os.path.join(".", "videos")
|
||||
downloads_dir = os.path.join(app_dir, "videos")
|
||||
if not os.path.exists(downloads_dir):
|
||||
os.mkdir(downloads_dir)
|
||||
|
||||
|
||||
@@ -10,12 +10,15 @@ from kivy.uix.screenmanager import FadeTransition, ScreenManager
|
||||
from kivy.uix.settings import Settings, SettingsWithSidebar
|
||||
from kivymd.app import MDApp
|
||||
|
||||
from fastanime.Utility.show_notification import show_notification
|
||||
|
||||
from . import downloads_dir
|
||||
from .libs.mpv.player import mpv_player
|
||||
from .Utility import (
|
||||
themes_available,
|
||||
user_data_helper,
|
||||
)
|
||||
from .Utility.downloader.downloader import downloader
|
||||
from .Utility.utils import write_crash
|
||||
from .View.components.media_card.components.media_popup import MediaPopup
|
||||
from .View.screens import screens
|
||||
@@ -141,6 +144,12 @@ class FastAnime(MDApp):
|
||||
mpv_player.stop_mpv()
|
||||
mpv_player.run_mpv(anime_video_url)
|
||||
|
||||
def download_anime_video(self, url: str, anime_title: tuple):
|
||||
self.download_screen.new_download_task(anime_title)
|
||||
show_notification("New Download", f"{anime_title[0]} episode: {anime_title[1]}")
|
||||
progress_hook = self.download_screen.on_episode_download_progress
|
||||
downloader.download_file(url, anime_title, progress_hook)
|
||||
|
||||
|
||||
def run_app():
|
||||
FastAnime().run()
|
||||
|
||||
BIN
mpv-shot0001.jpg
BIN
mpv-shot0001.jpg
Binary file not shown.
|
Before Width: | Height: | Size: 181 KiB |
Reference in New Issue
Block a user