Compare commits
134 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
384edfee3f | ||
|
|
3c1b361fc1 | ||
|
|
953dbaebf0 | ||
|
|
efd5458493 | ||
|
|
3473f6540d | ||
|
|
7fe5f4b83e | ||
|
|
072b582622 | ||
|
|
de936e8d96 | ||
|
|
9d0f0dda23 | ||
|
|
222ec769db | ||
|
|
6771ede722 | ||
|
|
8199b79dc7 | ||
|
|
dfca265380 | ||
|
|
d68bab40d9 | ||
|
|
3fc2cfde99 | ||
|
|
304ad2b3c1 | ||
|
|
64a314713e | ||
|
|
d5d6fd91ef | ||
|
|
01665c366b | ||
|
|
b443ec1da9 | ||
|
|
ae4ee57500 | ||
|
|
11d0515f8b | ||
|
|
1b95f13b37 | ||
|
|
1cb70e2d4d | ||
|
|
2bda267c3e | ||
|
|
a721900179 | ||
|
|
b4bea4d4a3 | ||
|
|
373c0c868c | ||
|
|
6960cbed9a | ||
|
|
8645c0c290 | ||
|
|
76486a92fd | ||
|
|
823bd438bc | ||
|
|
360c0c247a | ||
|
|
474ba0280a | ||
|
|
d7fa6b1bd6 | ||
|
|
deb543b596 | ||
|
|
e32eb98bb7 | ||
|
|
461249b8ba | ||
|
|
f8d67b0555 | ||
|
|
9701abde5f | ||
|
|
0940bdb8dd | ||
|
|
b744e83f09 | ||
|
|
811b7b765c | ||
|
|
3bc3c7b760 | ||
|
|
d8ed588033 | ||
|
|
f6b7919043 | ||
|
|
401ccd9884 | ||
|
|
3b010b8eeb | ||
|
|
da29ccfc1f | ||
|
|
3197be300f | ||
|
|
2cf73e6dbd | ||
|
|
bd46002684 | ||
|
|
5a6474cb8a | ||
|
|
cdf4e9bdfb | ||
|
|
10b2ec666f | ||
|
|
08dac4df1e | ||
|
|
b3c837e88a | ||
|
|
e395a8411d | ||
|
|
ec9e595167 | ||
|
|
5ff87f914a | ||
|
|
318b9095a7 | ||
|
|
437df4e73a | ||
|
|
3eb26bcf0c | ||
|
|
db7a08f18a | ||
|
|
dc1314a148 | ||
|
|
21ba95e325 | ||
|
|
9367970ec0 | ||
|
|
c34863a397 | ||
|
|
f93795e370 | ||
|
|
7d4e785ca1 | ||
|
|
31b1cb2873 | ||
|
|
5e23e544e8 | ||
|
|
80feac092d | ||
|
|
9e73125d69 | ||
|
|
b32cbf0dfd | ||
|
|
a9c9fde855 | ||
|
|
46658a7bab | ||
|
|
927c11f2bb | ||
|
|
a54f2b3567 | ||
|
|
7f2312b64f | ||
|
|
7106694bcb | ||
|
|
6b37d73e5a | ||
|
|
d1c5b205ef | ||
|
|
10b567a5fd | ||
|
|
eed0a919aa | ||
|
|
78357df07f | ||
|
|
8137c76eb4 | ||
|
|
be3a522305 | ||
|
|
13d05765b2 | ||
|
|
f52d16d1e4 | ||
|
|
acb91c7e8a | ||
|
|
c765a7a281 | ||
|
|
5abfcc8550 | ||
|
|
7957468077 | ||
|
|
f774773b63 | ||
|
|
8036cb9835 | ||
|
|
13fc33d2c0 | ||
|
|
8663ef54a3 | ||
|
|
0316813792 | ||
|
|
d201a5396c | ||
|
|
86f9396b6c | ||
|
|
0fb4bb3cb0 | ||
|
|
1528d7ce25 | ||
|
|
4e7eeb7989 | ||
|
|
7f2976a4f4 | ||
|
|
8928cb92da | ||
|
|
a181b76124 | ||
|
|
8f085a8f63 | ||
|
|
93a997351b | ||
|
|
b3af6c1c15 | ||
|
|
4a65263f7d | ||
|
|
1091b5b9b4 | ||
|
|
f6b3ff2f5c | ||
|
|
b399bdf5cf | ||
|
|
9ace161bc8 | ||
|
|
66e52cfd78 | ||
|
|
e665fd3cde | ||
|
|
fc94f4c691 | ||
|
|
78e2012f04 | ||
|
|
3fe9dbacc6 | ||
|
|
004dea06e0 | ||
|
|
8994a69c34 | ||
|
|
f4a692673e | ||
|
|
4cb5f14ef6 | ||
|
|
a596c4f350 | ||
|
|
e091c60d29 | ||
|
|
d2ea026a41 | ||
|
|
bb3d5c20f5 | ||
|
|
a13eae8f16 | ||
|
|
7e5dc71720 | ||
|
|
d7960bbb85 | ||
|
|
c4d5ba3040 | ||
|
|
fd84de7bce | ||
|
|
a6383b20a4 |
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
github: [Yuvi9587]
|
||||
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 [Yuvi9587]
|
||||
Copyright (c) [2025] [Yuvi9587]
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
BIN
Read/Cat.gif
Normal file
|
After Width: | Height: | Size: 779 KiB |
BIN
Read/Read.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
Read/Read1.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
Read/Read2.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
Read/Read3.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
BIN
assets/Kemono.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/discord.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
assets/github.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
assets/instagram.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
2291826
creators.json
Normal file
3810
downloader_utils.py
205
drive.py
Normal file
@@ -0,0 +1,205 @@
|
||||
from mega import Mega
|
||||
import os
|
||||
import requests
|
||||
import traceback
|
||||
from urllib .parse import urlparse ,urlunparse ,parse_qs ,urlencode
|
||||
|
||||
try :
|
||||
import gdown
|
||||
GDOWN_AVAILABLE =True
|
||||
except ImportError :
|
||||
GDOWN_AVAILABLE =False
|
||||
|
||||
def download_mega_file (mega_link ,download_path =".",logger_func =print ):
|
||||
"""
|
||||
Downloads a file from a public Mega.nz link.
|
||||
|
||||
Args:
|
||||
mega_link (str): The public Mega.nz link to the file.
|
||||
download_path (str, optional): The directory to save the downloaded file.
|
||||
Defaults to the current directory.
|
||||
logger_func (callable, optional): Function to use for logging. Defaults to print.
|
||||
"""
|
||||
logger_func ("drive.py: download_mega_file called.")
|
||||
logger_func (f"drive.py: mega_link='{mega_link}', download_path='{download_path}'")
|
||||
|
||||
logger_func ("drive.py: Initializing Mega client (Mega())...")
|
||||
try:
|
||||
mega_client = Mega()
|
||||
except Exception as e_init:
|
||||
logger_func(f"drive.py: ERROR during Mega() instantiation: {e_init}")
|
||||
traceback.print_exc()
|
||||
raise
|
||||
logger_func ("drive.py: Mega client initialized. Logging in anonymously (m.login())...")
|
||||
try:
|
||||
m = mega_client.login()
|
||||
except Exception as e_login:
|
||||
logger_func(f"drive.py: ERROR during m.login(): {e_login}")
|
||||
traceback.print_exc()
|
||||
raise
|
||||
logger_func ("drive.py: Logged in anonymously.")
|
||||
|
||||
logger_func (f"drive.py: Attempting to download from: {mega_link }")
|
||||
|
||||
try :
|
||||
if not os .path .exists (download_path ):
|
||||
logger_func (f"drive.py: Download path '{download_path }' does not exist. Creating it...")
|
||||
os .makedirs (download_path ,exist_ok =True )
|
||||
logger_func (f"drive.py: Download path ensured: '{download_path }'")
|
||||
|
||||
logger_func (f"drive.py: Calling m.download_url for '{mega_link }' to '{download_path }'...")
|
||||
|
||||
# The download_url method returns the local file path of the downloaded file.
|
||||
# It takes dest_path (directory) and dest_filename (optional).
|
||||
# If dest_filename is None, it uses the name from get_public_url_info().
|
||||
downloaded_file_path = m.download_url(mega_link, dest_path=download_path, dest_filename=None)
|
||||
|
||||
logger_func(f"drive.py: m.download_url returned: {downloaded_file_path}")
|
||||
|
||||
if downloaded_file_path and os.path.exists(downloaded_file_path):
|
||||
logger_func(f"drive.py: File downloaded successfully! Saved as: {downloaded_file_path}")
|
||||
# Optional: Verify size if possible, but get_public_url_info is another network call
|
||||
# and might be redundant or problematic if the download itself worked.
|
||||
elif downloaded_file_path:
|
||||
logger_func(f"drive.py: m.download_url returned a path '{downloaded_file_path}', but it does not exist on disk. Download may have failed silently or path is incorrect.")
|
||||
raise Exception(f"Mega download_url returned path '{downloaded_file_path}' which was not found.")
|
||||
else :
|
||||
logger_func ("drive.py: Download failed. m.download_url did not return a valid file path.")
|
||||
raise Exception ("Mega download_url did not return a file path or failed.")
|
||||
|
||||
except PermissionError as e:
|
||||
logger_func(f"drive.py: PermissionError: {e}. Denied to write to '{download_path}'. Please check permissions.")
|
||||
raise
|
||||
except FileNotFoundError as e:
|
||||
logger_func(f"drive.py: FileNotFoundError: {e}. The path '{download_path}' is invalid.")
|
||||
raise
|
||||
except requests.exceptions.ConnectionError as e: # More specific for network
|
||||
logger_func(f"drive.py: requests.exceptions.ConnectionError: {e}. Network problem during Mega operation.")
|
||||
raise
|
||||
except requests.exceptions.RequestException as e: # General requests error
|
||||
logger_func(f"drive.py: requests.exceptions.RequestException: {e} during request to Mega.")
|
||||
raise
|
||||
except Exception as e: # Catch-all for other errors from mega.py or os calls
|
||||
logger_func(f"drive.py: An unexpected error occurred during Mega download: {e}")
|
||||
traceback.print_exc() # Print full traceback for unexpected errors
|
||||
raise
|
||||
|
||||
def download_gdrive_file (gdrive_link ,download_path =".",logger_func =print ):
|
||||
"""
|
||||
Downloads a file from a public Google Drive link.
|
||||
|
||||
Args:
|
||||
gdrive_link (str): The public Google Drive link to the file.
|
||||
download_path (str, optional): The directory to save the downloaded file.
|
||||
Defaults to the current directory.
|
||||
logger_func (callable, optional): Function to use for logging. Defaults to print.
|
||||
"""
|
||||
if not GDOWN_AVAILABLE :
|
||||
logger_func ("❌ Error: gdown library is not installed. Cannot download from Google Drive.")
|
||||
logger_func ("Please install it: pip install gdown")
|
||||
raise ImportError ("gdown library not found. Please install it: pip install gdown")
|
||||
|
||||
logger_func (f"Attempting to download from Google Drive: {gdrive_link }")
|
||||
try :
|
||||
if not os .path .exists (download_path ):
|
||||
logger_func (f"Download path '{download_path }' does not exist. Creating it...")
|
||||
os .makedirs (download_path ,exist_ok =True )
|
||||
|
||||
logger_func (f"Starting Google Drive download to '{download_path }'...")
|
||||
|
||||
output_file_path =gdown .download (gdrive_link ,output =download_path ,quiet =False ,fuzzy =True )
|
||||
|
||||
if output_file_path and os .path .exists (os .path .join (download_path ,os .path .basename (output_file_path ))):
|
||||
logger_func (f"✅ Google Drive file downloaded successfully: {output_file_path }")
|
||||
elif output_file_path :
|
||||
full_path_check =os .path .join (download_path ,output_file_path )
|
||||
if os .path .exists (full_path_check ):
|
||||
logger_func (f"✅ Google Drive file downloaded successfully: {full_path_check }")
|
||||
else :
|
||||
logger_func (f"⚠️ Google Drive download finished, gdown returned '{output_file_path }', but file not found at expected location.")
|
||||
logger_func (f" Please check '{download_path }' for the downloaded file, it might have a different name than expected by gdown's return.")
|
||||
|
||||
files_in_dest =[f for f in os .listdir (download_path )if os .path .isfile (os .path .join (download_path ,f ))]
|
||||
if len (files_in_dest )==1 :
|
||||
logger_func (f" Found one file in destination: {os .path .join (download_path ,files_in_dest [0 ])}. Assuming this is it.")
|
||||
elif len (files_in_dest )>1 and output_file_path in files_in_dest :
|
||||
logger_func (f" Confirmed file '{output_file_path }' exists in '{download_path }'.")
|
||||
else :
|
||||
raise Exception (f"gdown download failed or file not found. Returned: {output_file_path }")
|
||||
else :
|
||||
logger_func ("❌ Google Drive download failed. gdown did not return an output path.")
|
||||
raise Exception ("gdown download failed.")
|
||||
|
||||
except PermissionError :
|
||||
logger_func (f"❌ Error: Permission denied to write to '{download_path }'. Please check permissions.")
|
||||
raise
|
||||
except Exception as e :
|
||||
logger_func (f"❌ An error occurred during Google Drive download: {e }")
|
||||
traceback .print_exc ()
|
||||
raise
|
||||
|
||||
def _get_filename_from_headers (headers ):
|
||||
cd =headers .get ('content-disposition')
|
||||
if not cd :
|
||||
return None
|
||||
fname_match =re .findall ('filename="?([^"]+)"?',cd )
|
||||
if fname_match :
|
||||
return fname_match [0 ].strip ()
|
||||
return None
|
||||
|
||||
def download_dropbox_file (dropbox_link ,download_path =".",logger_func =print ):
|
||||
"""
|
||||
Downloads a file from a public Dropbox link.
|
||||
|
||||
Args:
|
||||
dropbox_link (str): The public Dropbox link to the file.
|
||||
download_path (str, optional): The directory to save the downloaded file.
|
||||
Defaults to the current directory.
|
||||
logger_func (callable, optional): Function to use for logging. Defaults to print.
|
||||
"""
|
||||
logger_func (f"Attempting to download from Dropbox: {dropbox_link }")
|
||||
|
||||
|
||||
parsed_url =urlparse (dropbox_link )
|
||||
query_params =parse_qs (parsed_url .query )
|
||||
query_params ['dl']=['1']
|
||||
new_query =urlencode (query_params ,doseq =True )
|
||||
direct_download_url =urlunparse (parsed_url ._replace (query =new_query ))
|
||||
|
||||
logger_func (f" Using direct download URL: {direct_download_url }")
|
||||
|
||||
try :
|
||||
if not os .path .exists (download_path ):
|
||||
logger_func (f"Download path '{download_path }' does not exist. Creating it...")
|
||||
os .makedirs (download_path ,exist_ok =True )
|
||||
|
||||
with requests .get (direct_download_url ,stream =True ,allow_redirects =True ,timeout =(10 ,300 ))as r :
|
||||
r .raise_for_status ()
|
||||
filename =_get_filename_from_headers (r .headers )or os .path .basename (urlparse (dropbox_link ).path )or "dropbox_downloaded_file"
|
||||
|
||||
filename =re .sub (r'[<>:"/\\|?*]','_',filename )
|
||||
full_save_path =os .path .join (download_path ,filename )
|
||||
logger_func (f"Starting Dropbox download of '{filename }' to '{full_save_path }'...")
|
||||
with open (full_save_path ,'wb')as f :
|
||||
for chunk in r .iter_content (chunk_size =8192 ):
|
||||
f .write (chunk )
|
||||
logger_func (f"✅ Dropbox file downloaded successfully: {full_save_path }")
|
||||
except Exception as e :
|
||||
logger_func (f"❌ An error occurred during Dropbox download: {e }")
|
||||
traceback .print_exc ()
|
||||
raise
|
||||
|
||||
if __name__ =="__main__":
|
||||
|
||||
mega_file_link ="https://mega.nz/file/03oRjBQT#Tcbp5sQVIyPbdmv8sLgbb9Lf9AZvZLdKRSQiuXkNW0k"
|
||||
|
||||
if not mega_file_link .startswith ("https://mega.nz/file/"):
|
||||
print ("Invalid Mega file link format. It should start with 'https://mega.nz/file/'.")
|
||||
else :
|
||||
|
||||
|
||||
script_dir =os .path .dirname (os .path .abspath (__file__ ))
|
||||
download_directory =os .path .join (script_dir ,"mega_downloads")
|
||||
|
||||
print (f"Files will be downloaded to: {download_directory }")
|
||||
download_mega_file (mega_file_link ,download_directory ,logger_func =print )
|
||||
391
features.md
Normal file
@@ -0,0 +1,391 @@
|
||||
# Kemono Downloader - Detailed Feature Guide
|
||||
|
||||
This guide provides a comprehensive overview of all user interface elements, input fields, buttons, popups, and functionalities available in the Kemono Downloader.
|
||||
|
||||
---
|
||||
|
||||
## Main Interface & Workflow
|
||||
|
||||
These are the primary controls you'll interact with to initiate and manage downloads.
|
||||
|
||||
### 1. Main Inputs
|
||||
|
||||
- **🔗 Kemono Creator/Post URL Input Field:**
|
||||
- **Purpose:** This is where you paste the URL of the content you want to download.
|
||||
- **Usage:** Supports full URLs for:
|
||||
- Kemono.su (and mirrors like kemono.party) creator pages (e.g., `https://kemono.su/patreon/user/12345`).
|
||||
- Kemono.su (and mirrors) individual posts (e.g., `https://kemono.su/patreon/user/12345/post/98765`).
|
||||
- Coomer.party (and mirrors like coomer.su) creator pages.
|
||||
- Coomer.party (and mirrors) individual posts.
|
||||
- **Note:**
|
||||
- When **⭐ Favorite Mode** is active, this field is disabled and shows a "Favorite Mode active" message.
|
||||
- This field can also be populated with a placeholder message (e.g., "{count} items in queue from popup") if posts are added to the download queue directly from the 'Creator Selection' dialog's 'Fetched Posts' view.
|
||||
|
||||
- **🎨 Creator Selection Button:**
|
||||
- **Icon:** 🎨 (Artist Palette)
|
||||
- **Location:** Next to the URL input field.
|
||||
- **Purpose:** Opens the "Creator Selection" dialog to easily add multiple creators to the URL field.
|
||||
- **Dialog Features:**
|
||||
- Loads creators from your `creators.json` file (expected in the app's directory).
|
||||
- **Search Bar:** Filter the list of creators by name.
|
||||
- **Creator List:** Displays creators with their service (e.g., Patreon, Fanbox) and ID.
|
||||
- **Selection:** Checkboxes to select one or more creators.
|
||||
- **"Add Selected to URL" Button:** Adds the names of selected creators to the URL input field, comma-separated.
|
||||
- **"Fetch Posts" Button:** After selecting creators, click this to retrieve their latest posts. This will display a new pane within the dialog showing the fetched posts.
|
||||
- **"Download Scope" Radio Buttons (`Characters` / `Creators`):** Determines the folder structure for items added via this popup.
|
||||
- `Characters`: Assumes creator names are character names for folder organization.
|
||||
- `Creators`: Uses the actual creator names for folder organization.
|
||||
- **Fetched Posts View (Right Pane - Appears after clicking 'Fetch Posts'):**
|
||||
- **Posts Area Title Label:** Indicates loading status or number of fetched posts.
|
||||
- **Posts Search Input:** Allows filtering the list of fetched posts by title.
|
||||
- **Posts List Widget:** Displays posts fetched from the selected creators, often grouped by creator. Each post is checkable.
|
||||
- **Select All / Deselect All Buttons (for Posts):** Convenience buttons for selecting/deselecting all displayed fetched posts.
|
||||
- **"Add Selected Posts to Queue" Button:** Adds all checked posts from this view directly to the application's main download queue. The main URL input field will then show a message like "{count} items in queue from popup".
|
||||
- **"Close" Button (for Posts View):** Hides the fetched posts view and returns to the creator selection list, allowing you to use the 'Add Selected to URL' button if preferred.
|
||||
|
||||
- **Page Range (Start to End) Input Fields:**
|
||||
- **Purpose:** For creator URLs, specify a range of pages to fetch and process.
|
||||
- **Usage:** Enter the starting page number in the first field and the ending page number in the second.
|
||||
- **Behavior:**
|
||||
- If left blank, all pages for the creator are typically processed (or up to a reasonable limit).
|
||||
- Disabled for single post URLs or when **📖 Manga/Comic Mode** is active (as manga mode fetches all posts for chronological sorting).
|
||||
|
||||
- **📁 Download Location Input Field & Browse Button:**
|
||||
- **Purpose:** Specify the main directory where all downloaded files and folders will be saved.
|
||||
- **Usage:**
|
||||
- Type or paste the path directly into the field.
|
||||
- Click the **"Browse..."** button to open a system dialog to select a folder.
|
||||
- **Requirement:** This field must be filled unless you are using the "🔗 Only Links" filter mode.
|
||||
|
||||
### 2. Action Buttons
|
||||
|
||||
- **⬇️ Start Download / 🔗 Extract Links Button:**
|
||||
- **Purpose:** The primary action button to begin the downloading or link extraction process based on current settings.
|
||||
- **Behavior:**
|
||||
- If "🔗 Only Links" filter is selected, the button text changes to **"🔗 Extract Links"** and it will only gather external links from posts.
|
||||
- Otherwise, it reads **"⬇️ Start Download"** and initiates the content download.
|
||||
|
||||
- **⏸️ Pause / ▶️ Resume Download Button:**
|
||||
- **Purpose:** Temporarily halt or continue the ongoing download/extraction process.
|
||||
- **Behavior:**
|
||||
- When active, the button shows **"⏸️ Pause Download"**. Clicking it pauses the operation.
|
||||
- When paused, the button shows **"▶️ Resume Download"**. Clicking it resumes from where it left off.
|
||||
- Some UI settings can be changed while paused (e.g., filter adjustments), which will apply upon resuming.
|
||||
|
||||
- **❌ Cancel & Reset UI Button:**
|
||||
- **Purpose:** Immediately stops the current download/extraction operation and performs a "soft" reset of the UI.
|
||||
- **Behavior:**
|
||||
- Halts all active threads and processes.
|
||||
- Clears progress information and logs.
|
||||
- Preserves the content of the "🔗 Kemono Creator/Post URL" and "📁 Download Location" input fields. Other settings are reset to their defaults.
|
||||
|
||||
- **🔄 Reset Button (located in the log area):**
|
||||
- **Purpose:** Performs a "hard" reset of the UI when no operation is active.
|
||||
- **Behavior:**
|
||||
- Clears all input fields (including URL and Download Location).
|
||||
- Resets all filter settings and options to their default values.
|
||||
- Clears the log area.
|
||||
|
||||
---
|
||||
|
||||
## Filtering & Content Selection
|
||||
|
||||
These options allow you to precisely control what content is downloaded or skipped.
|
||||
|
||||
- **🎯 Filter by Character(s) Input Field:**
|
||||
- **Purpose:** Download content related to specific characters.
|
||||
- **Usage:** Enter character names, comma-separated.
|
||||
- **Advanced Syntax:**
|
||||
- `Nami`: Simple character filter. Matches "Nami".
|
||||
- `(Vivi, Ulti, Uta)`: Grouped characters. Matches "Vivi" OR "Ulti" OR "Uta". If "Separate Folders" is on, creates a shared folder for the session (e.g., "Vivi Ulti Uta"). Adds "Vivi", "Ulti", "Uta" as *separate* entries to `Known.txt` if new.
|
||||
- `(Boa, Hancock)~`: Aliased characters. Matches "Boa" OR "Hancock" but treats them as the same entity. If "Separate Folders" is on, creates a shared folder (e.g., "Boa Hancock"). Adds "Boa Hancock" as a *single group entry* to `Known.txt` if new, with "Boa" and "Hancock" as its aliases.
|
||||
|
||||
- **Filter: [Type] Button (Scope for Character Filter):**
|
||||
- **Location:** Next to the "Filter by Character(s)" input.
|
||||
- **Purpose:** Defines how the character filter is applied. Cycles through options on click.
|
||||
- **Options:**
|
||||
- `Filter: Files`: Checks individual filenames against the character filter. Only matching files from a post are downloaded.
|
||||
- `Filter: Title` (Default): Checks post titles against the character filter. If the title matches, all files from that post are downloaded.
|
||||
- `Filter: Both`: Checks the post title first. If no match, then checks individual filenames within that post.
|
||||
- `Filter: Comments (Beta)`: Checks filenames first. If no file match, then checks post comments/description. (Note: This may use more API requests).
|
||||
|
||||
- **🚫 Skip with Words Input Field:**
|
||||
- **Purpose:** Exclude posts or files containing specified keywords.
|
||||
- **Usage:** Enter words or phrases, comma-separated (e.g., `WIP, sketch, preview`).
|
||||
|
||||
- **Scope: [Type] Button (Scope for Skip with Words):**
|
||||
- **Location:** Next to the "Skip with Words" input.
|
||||
- **Purpose:** Defines how the skip words are applied. Cycles through options on click.
|
||||
- **Options:**
|
||||
- `Scope: Files`: Skips individual files if their names contain any of the skip words.
|
||||
- `Scope: Posts` (Default): Skips entire posts if their titles contain any of the skip words.
|
||||
- `Scope: Both`: Checks the post title first. If no skip words match, then checks individual filenames.
|
||||
|
||||
- **✂️ Remove Words from name Input Field:**
|
||||
- **Purpose:** Clean up downloaded filenames by removing specified unwanted words or phrases.
|
||||
- **Usage:** Enter words or phrases, comma-separated (e.g., `patreon, [HD], kemono`).
|
||||
|
||||
- **Filter Files (Radio Buttons):**
|
||||
- **Purpose:** Select the types of files to download.
|
||||
- **Options:**
|
||||
- `All`: Download all file types attached to posts.
|
||||
- `Images/GIFs`: Download only common image formats (JPG, PNG, GIF, WebP, etc.).
|
||||
- `Videos`: Download only common video formats (MP4, MOV, MKV, WebM, etc.).
|
||||
- `📦 Only Archives`: Exclusively download `.zip` and `.rar` files. This mode disables the "Skip .zip/.rar" checkboxes and the "Show External Links in Log" feature.
|
||||
- `🎧 Only Audio`: Download only common audio formats (MP3, WAV, FLAC, OGG, etc.).
|
||||
- `🔗 Only Links`: Do not download any files. Instead, extract and display external links found in post descriptions in the log area. The main action button changes to "🔗 Extract Links".
|
||||
|
||||
- **Skip .zip / Skip .rar Checkboxes:**
|
||||
- **Purpose:** Individually choose to skip downloading `.zip` files or `.rar` files.
|
||||
- **Behavior:** Disabled if the "📦 Only Archives" filter is active.
|
||||
|
||||
---
|
||||
|
||||
## Download Customization
|
||||
|
||||
Options to further refine the download process and output.
|
||||
|
||||
- **Download Thumbnails Only Checkbox:**
|
||||
- **Purpose:** Download only the small preview images (thumbnails) provided by the API, instead of full-resolution files.
|
||||
- **Behavior:** If "**Scan Content for Images**" is also active, this option's behavior changes: *only* images found by the content scan (embedded `<img>` tags) are downloaded as thumbnails (API thumbnails are ignored).
|
||||
|
||||
- **Scan Content for Images Checkbox:**
|
||||
- **Purpose:** Actively scan the HTML content of posts for `<img>` tags and direct image links. This is crucial for downloading images embedded in post descriptions that are not listed as direct attachments in the API response.
|
||||
- **Behavior:** Resolves relative image paths to absolute URLs for downloading.
|
||||
|
||||
- **Compress to WebP Checkbox:**
|
||||
- **Purpose:** Convert downloaded images to WebP format to potentially save disk space.
|
||||
- **Requirement:** Requires the `Pillow` library to be installed.
|
||||
- **Behavior:** Attempts to convert images larger than a certain threshold (e.g., 1.5MB) to WebP if the WebP version is significantly smaller. Original files are not kept if conversion is successful.
|
||||
|
||||
- **🗄️ Custom Folder Name (Single Post Only) Input Field:**
|
||||
- **Purpose:** When downloading a single post URL, allows you to specify a custom name for the folder where its contents will be saved.
|
||||
- **Visibility:** Only appears if:
|
||||
1. A single post URL is entered in the main URL field.
|
||||
2. The "**Separate Folders by Name/Title**" option is enabled.
|
||||
|
||||
---
|
||||
|
||||
## 📖 Manga/Comic Mode
|
||||
|
||||
Specialized mode for downloading creator feeds in a way suitable for sequential reading, like manga or comics. This mode is implicitly active when downloading from a creator URL and certain filename styles are chosen.
|
||||
|
||||
- **Activation:** Primarily by downloading a creator's feed (not a single post) and selecting a relevant "Filename Style".
|
||||
- **Core Behavior:** Processes and downloads posts from the creator's feed in chronological order (oldest to newest). The "Page Range" input is typically disabled as all posts are fetched for correct sorting.
|
||||
|
||||
- **Filename Style Toggle Button (located in the log area):**
|
||||
- **Purpose:** Controls how files are named when downloading in a manga/comic-like fashion. Cycles through options on click.
|
||||
- **Options:**
|
||||
- `Name: Post Title` (Default for non-manga): The first file in a post is named after the post title; subsequent files in the *same post* keep their original names.
|
||||
- `Name: Original File`: All downloaded files attempt to keep their original filenames as provided by the server. An optional "Filename Prefix" input field appears.
|
||||
- `Name: Title+G.Num`: (Global Numbering) All files across all downloaded posts for the creator get a prefix from their respective post's title, followed by a global sequential number (e.g., `Chapter 1_001.jpg`, `Chapter 1_002.jpg`, `Chapter 2_003.jpg`). This ensures strict order across posts. Disables post-level multithreading for sequential numbering.
|
||||
- `Name: Date Based`: Files are named sequentially (e.g., `001.jpg`, `002.jpg`) based on the post's publication date. An optional "Filename Prefix" input field appears. Disables post-level multithreading.
|
||||
|
||||
- **Optional Filename Prefix Input Field (Manga Mode):**
|
||||
- **Visibility:** Appears when "Filename Style" is set to `Name: Original File` or `Name: Date Based`.
|
||||
- **Purpose:** Allows you to add a custom prefix to all filenames generated using these styles (e.g., `MySeries_001.jpg`).
|
||||
|
||||
---
|
||||
|
||||
## Folder Organization
|
||||
|
||||
Controls for how downloaded content is structured into folders.
|
||||
|
||||
- **Separate Folders by Name/Title Checkbox:**
|
||||
- **Purpose:** Creates subfolders within the main "Download Location" based on matching criteria.
|
||||
- **Behavior:**
|
||||
- If "**Filter by Character(s)**" is used, folders are named after the matched character(s)/group(s).
|
||||
- If no character filter matches (or no filter is active), but the post title matches an entry in `Known.txt`, a folder named after the `Known.txt` entry is created.
|
||||
- If neither of the above, and this option is checked, folders might be created based on post titles directly (behavior can vary).
|
||||
|
||||
- **Subfolder per Post Checkbox:**
|
||||
- **Purpose:** Creates an additional layer of subfolders, where each individual post's content goes into its own subfolder.
|
||||
- **Behavior:** Only active if "**Separate Folders by Name/Title**" is also checked. The post subfolder will be created *inside* the character/title folder. Folder names are typically derived from sanitized post titles or IDs.
|
||||
|
||||
- **`Known.txt` Management UI (Bottom Left of UI):**
|
||||
- **Purpose:** Manages a local list (`Known.txt` file in the app directory) of series, characters, or general terms used for automatic folder organization and character filter suggestions.
|
||||
- **Elements:**
|
||||
- **List Display:** Shows the primary names from your `Known.txt` file.
|
||||
- **Add New Input Field:** Enter a new name or group to add to `Known.txt`.
|
||||
- Simple Name: e.g., `My Series`
|
||||
- Group (creates separate entries in `Known.txt`): e.g., `(Vivi, Ulti, Uta)`
|
||||
- Group with Aliases (single entry in `Known.txt` with `~`): e.g., `(Boa, Hancock)~`
|
||||
- **➕ Add Button:** Adds the entry from the "Add New" field to `Known.txt` and refreshes the list.
|
||||
- **⤵️ Add to Filter Button:** Opens a dialog displaying all entries from `Known.txt` (with a search bar). Select one or more entries to add them to the "**🎯 Filter by Character(s)**" input field. Grouped names from `Known.txt` are added with the `~` syntax if applicable.
|
||||
- **🗑️ Delete Selected Button:** Removes the currently selected name(s) from the list display and from the `Known.txt` file.
|
||||
- **Open Known.txt Button:** Opens your `Known.txt` file in the system's default text editor for manual editing.
|
||||
- **❓ Help Button (Known.txt):** Opens a guide or tooltip explaining the `Known.txt` feature and syntax.
|
||||
|
||||
---
|
||||
|
||||
## ⭐ Favorite Mode (Kemono.su Only)
|
||||
|
||||
Download directly from your favorited artists and posts on Kemono.su.
|
||||
|
||||
- **Enable Checkbox ("⭐ Favorite Mode"):**
|
||||
- **Location:** Usually near the "🔗 Only Links" filter option.
|
||||
- **Purpose:** Switches the downloader to operate on your Kemono.su favorites.
|
||||
- **UI Changes upon Enabling:**
|
||||
- The "🔗 Kemono Creator/Post URL" input field is disabled/replaced with a "Favorite Mode active" message.
|
||||
- The main action buttons change to "**🖼️ Favorite Artists**" and "**📄 Favorite Posts**".
|
||||
- The "**🍪 Use Cookie**" option is automatically enabled and locked, as cookies are required to access your favorites.
|
||||
|
||||
- **🖼️ Favorite Artists Button & Dialog:**
|
||||
- **Purpose:** Fetches and allows you to download content from artists you have favorited on Kemono.su.
|
||||
- **Dialog Features:**
|
||||
- Fetches the list of your favorited artists.
|
||||
- **Search Bar:** Filter artists by name.
|
||||
- **Artist List:** Displays favorited artists.
|
||||
- **Select All / Deselect All:** Convenience buttons for selection.
|
||||
- **"Download Selected" Button:** Queues all posts from the selected artists for download, respecting current filter settings.
|
||||
|
||||
- **📄 Favorite Posts Button & Dialog:**
|
||||
- **Purpose:** Fetches and allows you to download specific posts you have favorited on Kemono.su.
|
||||
- **Dialog Features:**
|
||||
- Fetches the list of your favorited posts, usually grouped by artist and sorted by date.
|
||||
- **Search Bar:** Filter posts by title, creator name, ID, or service.
|
||||
- **Post List:** Displays favorited posts. Known names from your `Known.txt` may be highlighted in post titles for easier identification.
|
||||
- **Select All / Deselect All:** Convenience buttons for selection.
|
||||
- **"Download Selected" Button:** Queues the selected individual posts for download, respecting current filter settings.
|
||||
|
||||
- **Favorite Download Scope Button (Location may vary, often near Favorite Posts button):**
|
||||
- **Purpose:** Determines the folder structure for downloads initiated via Favorite Mode.
|
||||
- **Options:**
|
||||
- `Scope: Selected Location`: All selected favorites (artists or posts) are downloaded directly into the main "📁 Download Location". Global filters apply.
|
||||
- `Scope: Artist Folders`: A subfolder is created for each artist within the main "📁 Download Location" (e.g., `DownloadLocation/ArtistName/`). Content from that artist (whether a full artist download or specific favorited posts from them) goes into their respective subfolder. Filters apply within each artist's context.
|
||||
|
||||
---
|
||||
|
||||
## Advanced & Performance
|
||||
|
||||
- **🍪 Cookie Management:**
|
||||
- **Use Cookie Checkbox:** Enables the use of browser cookies for accessing content that might be restricted or require login (e.g., certain posts, Favorite Mode).
|
||||
- **Cookie Text Field:**
|
||||
- **Purpose:** Directly paste your cookie string.
|
||||
- **Format:** Standard HTTP cookie string format (e.g., `name1=value1; name2=value2`).
|
||||
- **Browse... Button (for Cookies):**
|
||||
- **Purpose:** Select a `cookies.txt` file from your system.
|
||||
- **Format:** Must be in Netscape cookie file format.
|
||||
- **Behavior:**
|
||||
- The text field takes precedence if filled.
|
||||
- If "Use Cookie" is checked and both the text field and browsed file path are empty, the application will attempt to automatically load a `cookies.txt` file from its root directory.
|
||||
|
||||
- **Use Multithreading Checkbox & Threads Input Field:**
|
||||
- **Purpose:** Enable and configure the number of simultaneous operations to potentially speed up downloads.
|
||||
- **Behavior:**
|
||||
- **Creator Feeds:** The "Threads" input controls how many posts are processed concurrently.
|
||||
- **Single Post URLs:** The "Threads" input controls how many files from that single post are downloaded concurrently.
|
||||
- **Note:** Setting too high a number might lead to API rate-limiting or instability.
|
||||
|
||||
- **Multi-part Download Toggle Button (located in the log area):**
|
||||
- **Purpose:** Enables/disables multi-segment downloading for individual large files.
|
||||
- **Options:**
|
||||
- `Multi-part: ON`: Large files are split into multiple parts that are downloaded simultaneously and then reassembled. Can significantly speed up downloads for single large files but may increase UI choppiness or log spam with many small files.
|
||||
- `Multi-part: OFF` (Default): Files are downloaded as a single stream.
|
||||
- **Behavior:** Disabled if "🔗 Only Links" or "📦 Only Archives" mode is active.
|
||||
|
||||
---
|
||||
|
||||
## Logging & Monitoring
|
||||
|
||||
- **📜 Progress Log / Extracted Links Log Area:**
|
||||
- **Purpose:** The main text area displaying detailed messages about the ongoing process.
|
||||
- **Content:** Shows download progress for each file, errors encountered, skipped items, summary information, or extracted links (if in "🔗 Only Links" mode).
|
||||
|
||||
- **👁️ / 🙈 Log View Toggle Button:**
|
||||
- **Purpose:** Switches the content displayed in the main log area.
|
||||
- **Views:**
|
||||
- `👁️ Progress Log` (Default): Shows all download activity, errors, and general progress messages.
|
||||
- `🙈 Missed Character Log`: Shows a list of key terms intelligently extracted from post titles or content that were skipped due to the "**🎯 Filter by Character(s)**" not matching. Useful for identifying characters you might want to add to your filter or `Known.txt`.
|
||||
|
||||
- **Show External Links in Log Checkbox & Panel:**
|
||||
- **Purpose:** If checked, a secondary, smaller log panel appears (usually below the main log) that specifically displays any external links (e.g., to Mega, Google Drive) found in post descriptions.
|
||||
- **Behavior:** Disabled if "🔗 Only Links" or "📦 Only Archives" mode is active (as "Only Links" uses the main log, and archives typically don't have such external links processed).
|
||||
|
||||
- **Export Links Button:**
|
||||
- **Visibility:** Appears when the "**🔗 Only Links**" filter mode is active.
|
||||
- **Purpose:** Saves all the links extracted and displayed in the main log area to a `.txt` file.
|
||||
|
||||
- **Progress Labels/Bars:**
|
||||
- **Purpose:** Provide a visual and textual representation of the download progress.
|
||||
- **Typically Includes:**
|
||||
- Overall post progress (e.g., "Post 5 of 20").
|
||||
- Individual file download status (e.g., "Downloading file.zip... 50% at 1.2 MB/s").
|
||||
- Summary statistics at the end of a session (total downloaded, skipped, failed).
|
||||
|
||||
---
|
||||
## Error Handling & Retries
|
||||
|
||||
- **🆘 Error Button (Main UI):**
|
||||
- **Location:** Typically near the main action buttons (e.g., Start, Pause, Cancel).
|
||||
- **Purpose:** Becomes active if files failed to download during the last session (and were not successfully retried). Clicking it opens the "Files Skipped Due to Errors" dialog.
|
||||
- **"Files Skipped Due to Errors" Dialog:**
|
||||
- **File List:** Displays a list of files that encountered download errors. Each entry shows the filename, the post it was from (title and ID).
|
||||
- **Checkboxes:** Allows selection of individual files from the list.
|
||||
- **"Select All" Button:** Checks all files in the list.
|
||||
- **"Retry Selected" Button:** Attempts to re-download all checked files.
|
||||
- **"Export URLs to .txt" Button:**
|
||||
- Opens an "Export Options" dialog.
|
||||
- **"Link per line (URL only)":** Exports only the direct download URL for each failed file, one URL per line.
|
||||
- **"Export with details (URL [Post, File info])":** Exports the URL followed by details like Post Title, Post ID, and Original Filename in brackets.
|
||||
- Prompts the user to save the generated `.txt` file.
|
||||
- **"OK" Button:** Closes the dialog.
|
||||
- **Note:** Files successfully retried or skipped due to hash match during a retry attempt are removed from this error list.
|
||||
---
|
||||
|
||||
## ⚙️ Application Settings
|
||||
|
||||
These settings allow you to customize the application's appearance and language.
|
||||
|
||||
- **⚙️ Settings Button (Icon may vary, e.g., a gear ⚙️):**
|
||||
- **Location:** Typically located in a persistent area of the UI, possibly near other global controls or in a menu.
|
||||
- **Purpose:** Opens the "Settings" dialog.
|
||||
- **Tooltip Example:** "Open application settings (Theme, Language, etc.)"
|
||||
|
||||
- **"Settings" Dialog:**
|
||||
- **Title:** "Settings"
|
||||
- **Purpose:** Provides options to configure application-wide preferences.
|
||||
- **Sections:**
|
||||
- **Appearance Group (`Appearance`):**
|
||||
- **Theme Toggle Buttons/Options:**
|
||||
- `Switch to Light Mode`
|
||||
- `Switch to Dark Mode`
|
||||
- **Purpose:** Allows users to switch between a light and dark visual theme for the application.
|
||||
- **Tooltips:** Provide guidance on switching themes.
|
||||
- **Language Settings Group (`Language Settings`):**
|
||||
- **Language Selection Dropdown/List:**
|
||||
- **Label:** "Language:"
|
||||
- **Options:** Includes, but not limited to:
|
||||
- English (`English`)
|
||||
- 日本語 (`日本語 (Japanese)`)
|
||||
- Français (French)
|
||||
- Español (Spanish)
|
||||
- Deutsch (German)
|
||||
- Русский (Russian)
|
||||
- 한국어 (Korean)
|
||||
- 简体中文 (Chinese Simplified)
|
||||
- **Purpose:** Allows users to change the display language of the application interface.
|
||||
- **Restart Prompt:** After changing the language, a dialog may appear:
|
||||
- **Title:** "Language Changed"
|
||||
- **Message:** "The language has been changed. A restart is required for all changes to take full effect."
|
||||
- **Informative Text:** "Would you like to restart the application now?"
|
||||
- **Buttons:** "Restart Now", "OK" (or similar to defer restart).
|
||||
- **"OK" Button:** Saves the changes made in the Settings dialog and closes it.
|
||||
---
|
||||
|
||||
## Other UI Elements
|
||||
|
||||
- **Retry Failed Downloads Prompt:**
|
||||
- **Trigger:** Appears at the end of a download session if there were files that failed to download due to recoverable errors (e.g., network interruption, IncompleteRead).
|
||||
- **Action:** Prompts the user if they want to attempt downloading the failed files again.
|
||||
|
||||
- **New Name Confirmation Dialog (for Character Filter & `Known.txt`):**
|
||||
- **Trigger:** When new, unrecognized names or groups are used in the "**🎯 Filter by Character(s)**" field that are not present in `Known.txt`.
|
||||
- **Action:** Prompts the user to confirm if they want to add these new names/groups to `Known.txt` with the appropriate formatting (simple, grouped, or aliased).
|
||||
|
||||
- **Onboarding Tour / Help Guide Button (❓):**
|
||||
- **Purpose:** Opens a built-in help guide or an onboarding tour that explains the basic functionalities and UI elements of the application. Often linked to this detailed feature guide.
|
||||
|
||||
---
|
||||
|
||||
This guide should cover all interactive elements of the Kemono Downloader. If you have further questions or discover elements not covered, please refer to the main `readme.md` or consider opening an issue on the project's repository.
|
||||
4669
languages.py
Normal file
@@ -5,40 +5,40 @@ import hashlib
|
||||
import http.client
|
||||
import traceback
|
||||
import threading
|
||||
import queue
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
CHUNK_DOWNLOAD_RETRY_DELAY = 2 # Slightly reduced for faster retries if needed
|
||||
MAX_CHUNK_DOWNLOAD_RETRIES = 1 # Further reduced for quicker fallback if a chunk is problematic
|
||||
DOWNLOAD_CHUNK_SIZE_ITER = 1024 * 256 # 256KB for iter_content within a chunk download
|
||||
CHUNK_DOWNLOAD_RETRY_DELAY = 2
|
||||
MAX_CHUNK_DOWNLOAD_RETRIES = 1
|
||||
DOWNLOAD_CHUNK_SIZE_ITER = 1024 * 256
|
||||
|
||||
|
||||
def _download_individual_chunk(chunk_url, temp_file_path, start_byte, end_byte, headers,
|
||||
part_num, total_parts, progress_data, cancellation_event, skip_event,
|
||||
logger_func, emitter=None, api_original_filename=None): # Renamed logger, signals to emitter
|
||||
"""Downloads a single chunk of a file and writes it to the temp file."""
|
||||
part_num, total_parts, progress_data, cancellation_event, skip_event, pause_event, global_emit_time_ref, cookies_for_chunk,
|
||||
logger_func, emitter=None, api_original_filename=None):
|
||||
if cancellation_event and cancellation_event.is_set():
|
||||
logger_func(f" [Chunk {part_num + 1}/{total_parts}] Download cancelled before start.")
|
||||
return 0, False # bytes_downloaded, success
|
||||
return 0, False
|
||||
if skip_event and skip_event.is_set():
|
||||
logger_func(f" [Chunk {part_num + 1}/{total_parts}] Skip event triggered before start.")
|
||||
return 0, False
|
||||
|
||||
if pause_event and pause_event.is_set():
|
||||
logger_func(f" [Chunk {part_num + 1}/{total_parts}] Download paused before start...")
|
||||
while pause_event.is_set():
|
||||
if cancellation_event and cancellation_event.is_set():
|
||||
logger_func(f" [Chunk {part_num + 1}/{total_parts}] Download cancelled while paused.")
|
||||
return 0, False
|
||||
time.sleep(0.2)
|
||||
logger_func(f" [Chunk {part_num + 1}/{total_parts}] Download resumed.")
|
||||
|
||||
chunk_headers = headers.copy()
|
||||
# end_byte can be -1 for 0-byte files, meaning download from start_byte to end of file (which is start_byte itself)
|
||||
if end_byte != -1 : # For 0-byte files, end_byte might be -1, Range header should not be set or be 0-0
|
||||
if end_byte != -1 :
|
||||
chunk_headers['Range'] = f"bytes={start_byte}-{end_byte}"
|
||||
elif start_byte == 0 and end_byte == -1: # Specifically for 0-byte files
|
||||
# Some servers might not like Range: bytes=0--1.
|
||||
# For a 0-byte file, we might not even need a range header, or Range: bytes=0-0
|
||||
# Let's try without for 0-byte, or rely on server to handle 0-0 if Content-Length was 0.
|
||||
# If Content-Length was 0, the main function might handle it directly.
|
||||
# This chunking logic is primarily for files > 0 bytes.
|
||||
# For now, if end_byte is -1, it implies a 0-byte file, so we expect 0 bytes.
|
||||
elif start_byte == 0 and end_byte == -1:
|
||||
pass
|
||||
|
||||
|
||||
bytes_this_chunk = 0
|
||||
last_progress_emit_time_for_chunk = time.time()
|
||||
last_speed_calc_time = time.time()
|
||||
bytes_at_last_speed_calc = 0
|
||||
|
||||
@@ -49,23 +49,25 @@ def _download_individual_chunk(chunk_url, temp_file_path, start_byte, end_byte,
|
||||
if skip_event and skip_event.is_set():
|
||||
logger_func(f" [Chunk {part_num + 1}/{total_parts}] Skip event during retry loop.")
|
||||
return bytes_this_chunk, False
|
||||
if pause_event and pause_event.is_set():
|
||||
logger_func(f" [Chunk {part_num + 1}/{total_parts}] Paused during retry loop...")
|
||||
while pause_event.is_set():
|
||||
if cancellation_event and cancellation_event.is_set():
|
||||
logger_func(f" [Chunk {part_num + 1}/{total_parts}] Cancelled while paused in retry loop.")
|
||||
return bytes_this_chunk, False
|
||||
time.sleep(0.2)
|
||||
logger_func(f" [Chunk {part_num + 1}/{total_parts}] Resumed from retry loop pause.")
|
||||
|
||||
try:
|
||||
if attempt > 0:
|
||||
logger_func(f" [Chunk {part_num + 1}/{total_parts}] Retrying download (Attempt {attempt}/{MAX_CHUNK_DOWNLOAD_RETRIES})...")
|
||||
time.sleep(CHUNK_DOWNLOAD_RETRY_DELAY * (2 ** (attempt - 1)))
|
||||
# Reset speed calculation on retry
|
||||
last_speed_calc_time = time.time()
|
||||
bytes_at_last_speed_calc = bytes_this_chunk # Current progress of this chunk
|
||||
|
||||
# Enhanced log message for chunk start
|
||||
bytes_at_last_speed_calc = bytes_this_chunk
|
||||
log_msg = f" 🚀 [Chunk {part_num + 1}/{total_parts}] Starting download: bytes {start_byte}-{end_byte if end_byte != -1 else 'EOF'}"
|
||||
logger_func(log_msg)
|
||||
print(f"DEBUG_MULTIPART: {log_msg}") # Direct console print for debugging
|
||||
response = requests.get(chunk_url, headers=chunk_headers, timeout=(10, 120), stream=True)
|
||||
response = requests.get(chunk_url, headers=chunk_headers, timeout=(10, 120), stream=True, cookies=cookies_for_chunk)
|
||||
response.raise_for_status()
|
||||
|
||||
# For 0-byte files, if end_byte was -1, we expect 0 content.
|
||||
if start_byte == 0 and end_byte == -1 and int(response.headers.get('Content-Length', 0)) == 0:
|
||||
logger_func(f" [Chunk {part_num + 1}/{total_parts}] Confirmed 0-byte file.")
|
||||
with progress_data['lock']:
|
||||
@@ -73,7 +75,7 @@ def _download_individual_chunk(chunk_url, temp_file_path, start_byte, end_byte,
|
||||
progress_data['chunks_status'][part_num]['speed_bps'] = 0
|
||||
return 0, True
|
||||
|
||||
with open(temp_file_path, 'r+b') as f: # Open in read-write binary
|
||||
with open(temp_file_path, 'r+b') as f:
|
||||
f.seek(start_byte)
|
||||
for data_segment in response.iter_content(chunk_size=DOWNLOAD_CHUNK_SIZE_ITER):
|
||||
if cancellation_event and cancellation_event.is_set():
|
||||
@@ -82,72 +84,73 @@ def _download_individual_chunk(chunk_url, temp_file_path, start_byte, end_byte,
|
||||
if skip_event and skip_event.is_set():
|
||||
logger_func(f" [Chunk {part_num + 1}/{total_parts}] Skip event during data iteration.")
|
||||
return bytes_this_chunk, False
|
||||
if pause_event and pause_event.is_set():
|
||||
logger_func(f" [Chunk {part_num + 1}/{total_parts}] Paused during data iteration...")
|
||||
while pause_event.is_set():
|
||||
if cancellation_event and cancellation_event.is_set():
|
||||
logger_func(f" [Chunk {part_num + 1}/{total_parts}] Cancelled while paused in data iteration.")
|
||||
return bytes_this_chunk, False
|
||||
time.sleep(0.2)
|
||||
logger_func(f" [Chunk {part_num + 1}/{total_parts}] Resumed from data iteration pause.")
|
||||
if data_segment:
|
||||
f.write(data_segment)
|
||||
bytes_this_chunk += len(data_segment)
|
||||
|
||||
with progress_data['lock']:
|
||||
# Increment both the chunk's downloaded and the overall downloaded
|
||||
progress_data['total_downloaded_so_far'] += len(data_segment)
|
||||
progress_data['chunks_status'][part_num]['downloaded'] = bytes_this_chunk
|
||||
progress_data['chunks_status'][part_num]['active'] = True
|
||||
|
||||
current_time = time.time()
|
||||
time_delta_speed = current_time - last_speed_calc_time
|
||||
if time_delta_speed > 0.5: # Calculate speed every 0.5 seconds
|
||||
if time_delta_speed > 0.5:
|
||||
bytes_delta = bytes_this_chunk - bytes_at_last_speed_calc
|
||||
current_speed_bps = (bytes_delta * 8) / time_delta_speed if time_delta_speed > 0 else 0
|
||||
progress_data['chunks_status'][part_num]['speed_bps'] = current_speed_bps
|
||||
last_speed_calc_time = current_time
|
||||
bytes_at_last_speed_calc = bytes_this_chunk
|
||||
|
||||
# Emit progress more frequently from within the chunk download
|
||||
if current_time - last_progress_emit_time_for_chunk > 0.1: # Emit up to 10 times/sec per chunk
|
||||
if emitter:
|
||||
# Ensure we read the latest total downloaded from progress_data
|
||||
# Send a copy of the chunks_status list
|
||||
status_list_copy = [dict(s) for s in progress_data['chunks_status']] # Make a deep enough copy
|
||||
if isinstance(emitter, queue.Queue):
|
||||
emitter.put({'type': 'file_progress', 'payload': (api_original_filename, status_list_copy)})
|
||||
elif hasattr(emitter, 'file_progress_signal'): # PostProcessorSignals-like
|
||||
emitter.file_progress_signal.emit(api_original_filename, status_list_copy)
|
||||
last_progress_emit_time_for_chunk = current_time
|
||||
bytes_at_last_speed_calc = bytes_this_chunk
|
||||
if emitter and (current_time - global_emit_time_ref[0] > 0.25):
|
||||
global_emit_time_ref[0] = current_time
|
||||
status_list_copy = [dict(s) for s in progress_data['chunks_status']]
|
||||
if isinstance(emitter, queue.Queue):
|
||||
emitter.put({'type': 'file_progress', 'payload': (api_original_filename, status_list_copy)})
|
||||
elif hasattr(emitter, 'file_progress_signal'):
|
||||
emitter.file_progress_signal.emit(api_original_filename, status_list_copy)
|
||||
return bytes_this_chunk, True
|
||||
|
||||
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout, http.client.IncompleteRead) as e:
|
||||
logger_func(f" ❌ [Chunk {part_num + 1}/{total_parts}] Retryable error: {e}")
|
||||
if isinstance(e, requests.exceptions.ConnectionError) and \
|
||||
("Failed to resolve" in str(e) or "NameResolutionError" in str(e)):
|
||||
logger_func(" 💡 This looks like a DNS resolution problem. Please check your internet connection, DNS settings, or VPN.")
|
||||
if attempt == MAX_CHUNK_DOWNLOAD_RETRIES:
|
||||
logger_func(f" ❌ [Chunk {part_num + 1}/{total_parts}] Failed after {MAX_CHUNK_DOWNLOAD_RETRIES} retries.")
|
||||
return bytes_this_chunk, False
|
||||
except requests.exceptions.RequestException as e: # Includes 4xx/5xx errors after raise_for_status
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger_func(f" ❌ [Chunk {part_num + 1}/{total_parts}] Non-retryable error: {e}")
|
||||
if ("Failed to resolve" in str(e) or "NameResolutionError" in str(e)):
|
||||
logger_func(" 💡 This looks like a DNS resolution problem. Please check your internet connection, DNS settings, or VPN.")
|
||||
return bytes_this_chunk, False
|
||||
except Exception as e:
|
||||
logger_func(f" ❌ [Chunk {part_num + 1}/{total_parts}] Unexpected error: {e}\n{traceback.format_exc(limit=1)}")
|
||||
return bytes_this_chunk, False
|
||||
|
||||
# Ensure final status is marked as inactive if loop finishes due to retries
|
||||
return bytes_this_chunk, False
|
||||
with progress_data['lock']:
|
||||
progress_data['chunks_status'][part_num]['active'] = False
|
||||
progress_data['chunks_status'][part_num]['speed_bps'] = 0
|
||||
return bytes_this_chunk, False # Should be unreachable
|
||||
return bytes_this_chunk, False
|
||||
|
||||
|
||||
def download_file_in_parts(file_url, save_path, total_size, num_parts, headers, api_original_filename,
|
||||
emitter_for_multipart, cancellation_event, skip_event, logger_func): # Renamed signals, logger
|
||||
"""
|
||||
Downloads a file in multiple parts concurrently.
|
||||
Returns: (download_successful_flag, downloaded_bytes, calculated_file_hash, temp_file_handle_or_None)
|
||||
The temp_file_handle will be an open read-binary file handle to the .part file if successful, otherwise None.
|
||||
It is the responsibility of the caller to close this handle and rename/delete the .part file.
|
||||
"""
|
||||
emitter_for_multipart, cookies_for_chunk_session,
|
||||
cancellation_event, skip_event, logger_func, pause_event):
|
||||
logger_func(f"⬇️ Initializing Multi-part Download ({num_parts} parts) for: '{api_original_filename}' (Size: {total_size / (1024*1024):.2f} MB)")
|
||||
temp_file_path = save_path + ".part"
|
||||
|
||||
try:
|
||||
with open(temp_file_path, 'wb') as f_temp:
|
||||
if total_size > 0:
|
||||
f_temp.truncate(total_size) # Pre-allocate space
|
||||
f_temp.truncate(total_size)
|
||||
except IOError as e:
|
||||
logger_func(f" ❌ Error creating/truncating temp file '{temp_file_path}': {e}")
|
||||
return False, 0, None, None
|
||||
@@ -157,14 +160,14 @@ def download_file_in_parts(file_url, save_path, total_size, num_parts, headers,
|
||||
for i in range(num_parts):
|
||||
start = i * chunk_size_calc
|
||||
end = start + chunk_size_calc - 1 if i < num_parts - 1 else total_size - 1
|
||||
if start <= end: # Valid range
|
||||
if start <= end:
|
||||
chunks_ranges.append((start, end))
|
||||
elif total_size == 0 and i == 0: # Special case for 0-byte file
|
||||
chunks_ranges.append((0, -1)) # Indicates 0-byte file, download 0 bytes from offset 0
|
||||
elif total_size == 0 and i == 0:
|
||||
chunks_ranges.append((0, -1))
|
||||
|
||||
chunk_actual_sizes = []
|
||||
chunk_actual_sizes = []
|
||||
for start, end in chunks_ranges:
|
||||
if end == -1 and start == 0: # 0-byte file
|
||||
if end == -1 and start == 0:
|
||||
chunk_actual_sizes.append(0)
|
||||
else:
|
||||
chunk_actual_sizes.append(end - start + 1)
|
||||
@@ -175,18 +178,19 @@ def download_file_in_parts(file_url, save_path, total_size, num_parts, headers,
|
||||
return False, 0, None, None
|
||||
|
||||
progress_data = {
|
||||
'total_file_size': total_size, # Overall file size for reference
|
||||
'total_downloaded_so_far': 0, # New key for overall progress
|
||||
'chunks_status': [ # Status for each chunk
|
||||
'total_file_size': total_size,
|
||||
'total_downloaded_so_far': 0,
|
||||
'chunks_status': [
|
||||
{'id': i, 'downloaded': 0, 'total': chunk_actual_sizes[i] if i < len(chunk_actual_sizes) else 0, 'active': False, 'speed_bps': 0.0}
|
||||
for i in range(num_parts)
|
||||
],
|
||||
'lock': threading.Lock()
|
||||
'lock': threading.Lock(),
|
||||
'last_global_emit_time': [time.time()]
|
||||
}
|
||||
|
||||
chunk_futures = []
|
||||
all_chunks_successful = True
|
||||
total_bytes_from_chunks = 0 # Still useful to verify total downloaded against file size
|
||||
total_bytes_from_chunks = 0
|
||||
|
||||
with ThreadPoolExecutor(max_workers=num_parts, thread_name_prefix=f"MPChunk_{api_original_filename[:10]}_") as chunk_pool:
|
||||
for i, (start, end) in enumerate(chunks_ranges):
|
||||
@@ -194,8 +198,8 @@ def download_file_in_parts(file_url, save_path, total_size, num_parts, headers,
|
||||
chunk_futures.append(chunk_pool.submit(
|
||||
_download_individual_chunk, chunk_url=file_url, temp_file_path=temp_file_path,
|
||||
start_byte=start, end_byte=end, headers=headers, part_num=i, total_parts=num_parts,
|
||||
progress_data=progress_data, cancellation_event=cancellation_event, skip_event=skip_event,
|
||||
logger_func=logger_func, emitter=emitter_for_multipart, # Pass emitter
|
||||
progress_data=progress_data, cancellation_event=cancellation_event, skip_event=skip_event, global_emit_time_ref=progress_data['last_global_emit_time'],
|
||||
pause_event=pause_event, cookies_for_chunk=cookies_for_chunk_session, logger_func=logger_func, emitter=emitter_for_multipart,
|
||||
api_original_filename=api_original_filename
|
||||
))
|
||||
|
||||
@@ -209,26 +213,21 @@ def download_file_in_parts(file_url, save_path, total_size, num_parts, headers,
|
||||
if cancellation_event and cancellation_event.is_set():
|
||||
logger_func(f" Multi-part download for '{api_original_filename}' cancelled by main event.")
|
||||
all_chunks_successful = False
|
||||
|
||||
# Ensure a final progress update is sent with all chunks marked inactive (unless still active due to error)
|
||||
if emitter_for_multipart:
|
||||
with progress_data['lock']:
|
||||
# Ensure all chunks are marked inactive for the final signal if download didn't fully succeed or was cancelled
|
||||
status_list_copy = [dict(s) for s in progress_data['chunks_status']]
|
||||
if isinstance(emitter_for_multipart, queue.Queue):
|
||||
emitter_for_multipart.put({'type': 'file_progress', 'payload': (api_original_filename, status_list_copy)})
|
||||
elif hasattr(emitter_for_multipart, 'file_progress_signal'): # PostProcessorSignals-like
|
||||
elif hasattr(emitter_for_multipart, 'file_progress_signal'):
|
||||
emitter_for_multipart.file_progress_signal.emit(api_original_filename, status_list_copy)
|
||||
|
||||
if all_chunks_successful and (total_bytes_from_chunks == total_size or total_size == 0):
|
||||
logger_func(f" ✅ Multi-part download successful for '{api_original_filename}'. Total bytes: {total_bytes_from_chunks}")
|
||||
md5_hasher = hashlib.md5()
|
||||
with open(temp_file_path, 'rb') as f_hash:
|
||||
for buf in iter(lambda: f_hash.read(4096*10), b''): # Read in larger buffers for hashing
|
||||
for buf in iter(lambda: f_hash.read(4096*10), b''):
|
||||
md5_hasher.update(buf)
|
||||
calculated_hash = md5_hasher.hexdigest()
|
||||
# Return an open file handle for the caller to manage (e.g., for compression)
|
||||
# The caller is responsible for closing this handle and renaming/deleting the .part file.
|
||||
return True, total_bytes_from_chunks, calculated_hash, open(temp_file_path, 'rb')
|
||||
else:
|
||||
logger_func(f" ❌ Multi-part download failed for '{api_original_filename}'. Success: {all_chunks_successful}, Bytes: {total_bytes_from_chunks}/{total_size}. Cleaning up.")
|
||||
|
||||
357
readme.md
@@ -1,244 +1,177 @@
|
||||
<h1 align="center">Kemono Downloader v3.4.0</h1>
|
||||
<h1 align="center">Kemono Downloader v5.3.0</h1>
|
||||
|
||||
<div align="center">
|
||||
<img src="https://github.com/Yuvi9587/Kemono-Downloader/blob/main/Read.png" alt="Kemono Downloader"/>
|
||||
</div>
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<img src="Read/Read.png" alt="Post Downloader Tab" width="400"/>
|
||||
<br>
|
||||
<strong>Default</strong>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="Read/Read1.png" alt="Creator Downloader Tab" width="400"/>
|
||||
<br>
|
||||
<strong>Favorite mode</strong>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<img src="Read/Read2.png" alt="Settings Tab" width="400"/>
|
||||
<br>
|
||||
<strong>Single Post</strong>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="Read/Read3.png" alt="Settings Tab" width="400"/>
|
||||
<br>
|
||||
<strong>Manga/Comic Mode</strong>
|
||||
</td>
|
||||
<td align="center">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
---
|
||||
|
||||
A powerful, feature-rich GUI application for downloading content from **[Kemono.su](https://kemono.su)** (and its mirrors like kemono.party) and **[Coomer.party](https://coomer.party)** (and its mirrors like coomer.su).
|
||||
Built with PyQt5, this tool is designed for users who want deep filtering capabilities, customizable folder structures, efficient downloads, and intelligent automation, all within a modern and user-friendly graphical interface.
|
||||
|
||||
*This v5.0.0 release marks a significant feature milestone. Future updates are expected to be less frequent, focusing on maintenance and minor refinements.*
|
||||
*Update v5.2.0 introduces multi-language support, theme selection, and further UI refinements.*
|
||||
<p align="center">
|
||||
<a href="features.md"><strong>📚 Full Feature List</strong></a> •
|
||||
<a href="LICENSE"><strong>📝 License</strong></a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
A powerful, feature-rich GUI application for downloading content from **[Kemono.su](https://kemono.su)** and **[Coomer.party](https://coomer.party)**.
|
||||
Built with **PyQt5**, this tool is ideal for users who want deep filtering, customizable folder structures, efficient downloads, and intelligent automation — all within a modern, user-friendly graphical interface.
|
||||
## Feature Overview
|
||||
|
||||
Kemono Downloader offers a range of features to streamline your content downloading experience:
|
||||
|
||||
- **User-Friendly Interface:** A modern PyQt5 GUI for easy navigation and operation.
|
||||
- **Flexible Downloading:**
|
||||
- Download content from Kemono.su (and mirrors) and Coomer.party (and mirrors).
|
||||
- Supports creator pages (with page range selection) and individual post URLs.
|
||||
- Standard download controls: Start, Pause, Resume, and Cancel.
|
||||
- **Powerful Filtering:**
|
||||
- **Character Filtering:** Filter content by character names. Supports simple comma-separated names and grouped names for shared folders.
|
||||
- **Keyword Skipping:** Skip posts or files based on specified keywords.
|
||||
- **Filename Cleaning:** Remove unwanted words or phrases from downloaded filenames.
|
||||
- **File Type Selection:** Choose to download all files, or limit to images/GIFs, videos, audio, or archives. Can also extract external links only.
|
||||
- **Customizable Downloads:**
|
||||
- **Thumbnails Only:** Option to download only small preview images.
|
||||
- **Content Scanning:** Scan post HTML for `<img>` tags and direct image links, useful for images embedded in descriptions.
|
||||
- **WebP Conversion:** Convert images to WebP format for smaller file sizes (requires Pillow library).
|
||||
- **Organized Output:**
|
||||
- **Automatic Subfolders:** Create subfolders based on character names (from filters or `Known.txt`) or post titles.
|
||||
- **Per-Post Subfolders:** Option to create an additional subfolder for each individual post.
|
||||
- **Manga/Comic Mode:**
|
||||
- Downloads posts from a creator's feed in chronological order (oldest to newest).
|
||||
- Offers various filename styling options for sequential reading (e.g., post title, original name, global numbering).
|
||||
- **⭐ Favorite Mode:**
|
||||
- Directly download from your favorited artists and posts on Kemono.su.
|
||||
- Requires a valid cookie and adapts the UI for easy selection from your favorites.
|
||||
- Supports downloading into a single location or artist-specific subfolders.
|
||||
- **Performance & Advanced Options:**
|
||||
- **Cookie Support:** Use cookies (paste string or load from `cookies.txt`) to access restricted content.
|
||||
- **Multithreading:** Configure the number of simultaneous downloads/post processing threads for improved speed.
|
||||
- **Logging:**
|
||||
- A detailed progress log displays download activity, errors, and summaries.
|
||||
- **Multi-language Interface:** Choose from several languages for the UI (English, Japanese, French, Spanish, German, Russian, Korean, Chinese Simplified).
|
||||
- **Theme Customization:** Selectable Light and Dark themes for user comfort.
|
||||
|
||||
---
|
||||
|
||||
## ✨ What's New in v3.4.0?
|
||||
|
||||
This version brings significant enhancements to manga/comic downloading, filtering capabilities, and user experience:
|
||||
|
||||
### 📖 Enhanced Manga/Comic Mode
|
||||
|
||||
- **New "Date Based" Filename Style:**
|
||||
|
||||
- Perfect for truly sequential content! Files are named numerically (e.g., `001.jpg`, `002.jpg`, `003.ext`...) across an *entire creator's feed*, strictly following post publication order.
|
||||
|
||||
- **Smart Numbering:** Automatically resumes from the highest existing number found in the series folder (and subfolders, if "Subfolder per Post" is enabled).
|
||||
|
||||
- **Guaranteed Order:** Disables multi-threading for post processing to ensure sequential accuracy.
|
||||
|
||||
- Works alongside the existing "Post Title" and "Original File Name" styles.
|
||||
## ✨ What's New in v5.3.0
|
||||
- **Multi-Creator Post Fetching & Queuing:**
|
||||
- The **Creator Selection popup** (🎨 icon) has been significantly enhanced.
|
||||
- After selecting multiple creators, you can now click a new "**Fetch Posts**" button.
|
||||
- This will retrieve and display posts from all selected creators in a new view within the popup.
|
||||
- You can then browse these fetched posts (with search functionality) and select individual posts.
|
||||
- A new "**Add Selected Posts to Queue**" button allows you to add your chosen posts directly to the main download queue, streamlining the process of gathering content from multiple artists.
|
||||
- The traditional "**Add Selected to URL**" button is still available if you prefer to populate the main URL field with creator names.
|
||||
- **Improved Favorite Download Queue Handling:**
|
||||
- When items are added to the download queue from the Creator Selection popup, the main URL input field will now display a placeholder message (e.g., "{count} items in queue from popup").
|
||||
- The queue is now more robustly managed, especially when interacting with the main URL input field after items have been queued from the popup.
|
||||
|
||||
---
|
||||
|
||||
### ✂️ "Remove Words from Filename" Feature
|
||||
## ✨ What's New in v5.1.0
|
||||
- **Enhanced Error File Management**: The "Error" button now opens a dialog listing files that failed to download. This dialog includes:
|
||||
- An option to **retry selected** failed downloads.
|
||||
- A new **"Export URLs to .txt"** button, allowing users to save links of failed downloads either as "URL only" or "URL with details" (including post title, ID, and original filename).
|
||||
- Fixed a bug where files skipped during retry (due to existing hash match) were not correctly removed from the error list.
|
||||
- **Improved UI Stability**: Addressed issues with UI state management to more accurately reflect ongoing download activities (including retries and external link downloads). This prevents the "Cancel" button from becoming inactive prematurely while operations are still running.
|
||||
|
||||
- Specify comma-separated words or phrases (case-insensitive) that will be automatically removed from filenames.
|
||||
|
||||
- Example: `patreon, [HD], _final` transforms `AwesomeArt_patreon` `Hinata_Hd` into `AwesomeArt.jpg` `Hinata.jpg`.
|
||||
## ✨ What's New in v5.2.0
|
||||
- **Multi-language Support:** The interface now supports multiple languages: English, Japanese, French, Spanish, German, Russian, Korean, and Chinese (Simplified). Select your preferred language in the new Settings dialog.
|
||||
- **Theme Selection:** Choose between Light and Dark application themes via the Settings dialog for a personalized viewing experience.
|
||||
- **Centralized Settings:** A new Settings dialog (accessible via a settings button, often with a gear icon) provides a dedicated space for language and appearance customizations.
|
||||
- **Internal Localization:** Introduced `languages.py` for managing UI translations, streamlining the addition of new languages by contributors.
|
||||
|
||||
---
|
||||
|
||||
### 📦 New "Only Archives" File Filter Mode
|
||||
|
||||
- Exclusively downloads `.zip` and `.rar` files.
|
||||
|
||||
- Automatically disables conflicting options like "Skip .zip/.rar" and external link logging.
|
||||
|
||||
---
|
||||
|
||||
### 🗣️ Improved Character Filter Scope - "Comments (Beta)"
|
||||
|
||||
- **File-First Check:** Prioritizes matching filenames before checking post comments for character names.
|
||||
|
||||
- **Comment Fallback:** Only checks comments if no filename match is found, reducing unnecessary API calls.
|
||||
|
||||
---
|
||||
|
||||
### 🧐 Refined "Missed Character Log"
|
||||
|
||||
- Displays a capitalized, alphabetized list of key terms from skipped post titles.
|
||||
|
||||
- Makes it easier to spot patterns or characters that might be unintentionally excluded.
|
||||
|
||||
---
|
||||
|
||||
### 🚀 Enhanced Multi-part Download Progress
|
||||
|
||||
- Granular visibility into active chunk downloads and combined speed for large files.
|
||||
|
||||
---
|
||||
|
||||
### 🗺️ Updated Onboarding Tour
|
||||
|
||||
- Improved guide for new users, covering v3.4.0 features and existing core functions.
|
||||
|
||||
---
|
||||
|
||||
### 🛡️ Robust Configuration Path
|
||||
|
||||
- Settings and `Known.txt` are now stored in the system-standard application data folder (e.g., `AppData`, `~/.local/share`).
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ Core Features
|
||||
|
||||
---
|
||||
|
||||
### User Interface & Workflow
|
||||
|
||||
- **Clean PyQt5 GUI** — Simple, modern, and dark-themed.
|
||||
|
||||
- **Persistent Settings** — Saves preferences between sessions.
|
||||
|
||||
- **Download Modes:**
|
||||
- Single Post URL
|
||||
- Entire Creator Feed
|
||||
|
||||
- **Flexible Options:**
|
||||
- Specify Page Range (disabled in Manga Mode)
|
||||
- Custom Folder Name for single posts
|
||||
|
||||
---
|
||||
|
||||
### 🧠 Smart Filtering
|
||||
|
||||
- **Character Name Filtering:**
|
||||
- Use `Tifa, Aerith` or group `(Boa, Hancock)` → folder `Boa Hancock`
|
||||
|
||||
- **Filter Scopes:**
|
||||
- `Files`
|
||||
- `Title`
|
||||
- `Both (Title then Files)`
|
||||
- `Comments (Beta - Files first)`
|
||||
|
||||
- **Skip with Words:**
|
||||
- Exclude with `WIP, sketch, preview`
|
||||
|
||||
- **Skip Scopes:**
|
||||
- `Files`
|
||||
- `Posts`
|
||||
- `Both (Posts then Files)`
|
||||
|
||||
- **File Type Filters:**
|
||||
- `All`, `Images/GIFs`, `Videos`, `📦 Only Archives`, `🔗 Only Links`
|
||||
|
||||
- **Filename Cleanup:**
|
||||
- Remove illegal and unwanted characters or phrases
|
||||
|
||||
---
|
||||
|
||||
### 📚 Manga/Comic Mode (Creator Feeds Only)
|
||||
|
||||
- **Chronological Processing** — Oldest posts first
|
||||
|
||||
- **Filename Style Options:**
|
||||
- `Name: Post Title (Default)`
|
||||
- `Name: Original File`
|
||||
- `Name: Date Based (New)`
|
||||
|
||||
- **Best With:** Character filters set to manga/series title
|
||||
|
||||
---
|
||||
|
||||
### 📁 Folder Structure & Naming
|
||||
|
||||
- **Subfolders:**
|
||||
- Auto-created based on character name, post title, or `Known.txt`
|
||||
|
||||
- "Subfolder per Post" option for further nesting
|
||||
|
||||
- **Smart Naming:** Cleans invalid characters and structures logically
|
||||
|
||||
---
|
||||
|
||||
### 🖼️ Thumbnail & Compression Tools
|
||||
|
||||
- **Download Thumbnails Only**
|
||||
|
||||
- **Compress to WebP** (via Pillow)
|
||||
- Converts large images to smaller WebP versions
|
||||
|
||||
---
|
||||
|
||||
### ⚙️ Performance Features
|
||||
|
||||
- **Multithreading:**
|
||||
- For both post processing and file downloading
|
||||
|
||||
- **Multi-part Downloads:**
|
||||
- Toggleable in GUI
|
||||
- Splits large files into chunks
|
||||
- Granular chunk-level progress display
|
||||
|
||||
---
|
||||
|
||||
### 📋 Logging & Progress
|
||||
|
||||
- **Real-time Logs:** Activity, errors, skipped posts
|
||||
|
||||
- **Missed Character Log:** Shows skipped keywords in easy-to-read list
|
||||
|
||||
- **External Links Log:** Shows links (unless disabled in some modes)
|
||||
|
||||
- **Export Links:** Save `.txt` of links (Only Links mode)
|
||||
|
||||
---
|
||||
|
||||
### 🗃️ Config System
|
||||
|
||||
- **Known.txt:**
|
||||
- Stores names for smart folder suggestions
|
||||
- Supports aliases via `(alias1, alias2)`
|
||||
|
||||
- **Stored in Standard App Data Path**
|
||||
|
||||
- **Editable Within GUI**
|
||||
|
||||
---
|
||||
|
||||
## 💻 Installation
|
||||
|
||||
---
|
||||
## Installation
|
||||
|
||||
### Requirements
|
||||
- Python 3.6 or higher
|
||||
- pip (Python package installer)
|
||||
|
||||
- Python 3.6 or higher
|
||||
- pip
|
||||
### Install Dependencies
|
||||
Open your terminal or command prompt and run:
|
||||
|
||||
```bash
|
||||
pip install PyQt5 requests Pillow mega.py
|
||||
```
|
||||
|
||||
### Running the Application
|
||||
Navigate to the application's directory in your terminal and run:
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
### Optional Setup
|
||||
- **Main Inputs:**
|
||||
- Place your `cookies.txt` in the root directory (if using cookies).
|
||||
- Prepare your `Known.txt` and `creators.json` in the same directory for advanced filtering and selection features.
|
||||
|
||||
---
|
||||
|
||||
### Install Dependencies
|
||||
## Troubleshooting
|
||||
|
||||
### AttributeError: module 'asyncio' has no attribute 'coroutine'
|
||||
|
||||
If you encounter an error message similar to:
|
||||
```
|
||||
AttributeError: module 'asyncio' has no attribute 'coroutine'. Did you mean: 'coroutines'?
|
||||
```
|
||||
This usually means that a dependency, often `tenacity` (used by `mega.py`), is an older version that's incompatible with your Python version (typically Python 3.10+).
|
||||
|
||||
To fix this, activate your virtual environment and run the following commands to upgrade the libraries:
|
||||
|
||||
```bash
|
||||
pip install PyQt5 requests Pillow
|
||||
|
||||
pip install --upgrade tenacity
|
||||
pip install --upgrade mega.py
|
||||
```
|
||||
|
||||
***
|
||||
---
|
||||
|
||||
## **🛠️ Build a Standalone Executable (Optional)**
|
||||
## Contribution
|
||||
|
||||
1. Install PyInstaller:
|
||||
```bash
|
||||
pip install pyinstaller
|
||||
```
|
||||
Feel free to fork this repo and submit pull requests for bug fixes, new features, or UI improvements!
|
||||
|
||||
2. Run:
|
||||
```bash
|
||||
pyinstaller --name "Kemono Downloader" --onefile --windowed --icon="Kemono.ico" main.py
|
||||
```
|
||||
---
|
||||
|
||||
3. Output will be in the `dist/` folder.
|
||||
## License
|
||||
|
||||
***
|
||||
This project is under the Custom Licence
|
||||
|
||||
## **🗂 Config Files**
|
||||
## Star History
|
||||
|
||||
- `Known.txt` — character/show names used for folder organization
|
||||
- Supports grouped names in format: `(Name1, Name2)`
|
||||
<table align="center" style="border-collapse: collapse; border: none; margin-left: auto; margin-right: auto;">
|
||||
<tr>
|
||||
<td align="center" valign="middle" style="padding: 10px; border: none;">
|
||||
<a href="https://www.star-history.com/#Yuvi9587/Kemono-Downloader&Date">
|
||||
<img src="https://api.star-history.com/svg?repos=Yuvi9587/Kemono-Downloader&type=Date" alt="Star History Chart" width="650">
|
||||
</a>
|
||||
</table>
|
||||
|
||||
***
|
||||
|
||||
## **💬 Feedback & Support**
|
||||
|
||||
Issues? Suggestions?
|
||||
Open an issue on the [GitHub repository](https://github.com/Yuvi9587/kemono-downloader) or join our community.
|
||||
👉 See [features.md](features.md) for the full feature list.
|
||||
|
||||
19
security.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We are committed to maintaining and improving the Kemono Downloader. For the best experience and access to the latest security updates and features, we strongly recommend using the most recent versions of the application.
|
||||
|
||||
| Version | Supported Status |
|
||||
| -------------- | ------------------------------------ |
|
||||
| >= 5.0.0 | :white_check_mark: Actively Supported |
|
||||
| 4.0.0 - 4.x.x | :warning: Supported (Limited Features) |
|
||||
| < 4.0.0 | :x: End of Life (EOL) |
|
||||
|
||||
Users are encouraged to update to **v5.0.0 or newer** versions.
|
||||
|
||||
## Active Maintenance
|
||||
|
||||
The Kemono Downloader is actively maintained. We strive to address bugs, implement new features in a timely manner. If you discover any security vulnerabilities, please report them(details on reporting to be added if a formal process is established).
|
||||
|
||||
We appreciate your help in keeping Kemono Downloader secure!
|
||||