mirror of
https://github.com/Yuvi9587/Kemono-Downloader.git
synced 2025-12-29 16:14:44 +00:00
commit
This commit is contained in:
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
40
features.md
40
features.md
@@ -323,6 +323,46 @@ Download directly from your favorited artists and posts on Kemono.su.
|
|||||||
- **Note:** Files successfully retried or skipped due to hash match during a retry attempt are removed from this error list.
|
- **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
|
## Other UI Elements
|
||||||
|
|
||||||
- **Retry Failed Downloads Prompt:**
|
- **Retry Failed Downloads Prompt:**
|
||||||
|
|||||||
87
main.py
87
main.py
@@ -119,6 +119,35 @@ except ImportError :
|
|||||||
sys .exit (1 )
|
sys .exit (1 )
|
||||||
|
|
||||||
|
|
||||||
|
_app_icon_cache = None # Module-level cache
|
||||||
|
|
||||||
|
def get_app_icon_object():
|
||||||
|
"""
|
||||||
|
Loads and caches the application icon.
|
||||||
|
Returns a QIcon object.
|
||||||
|
"""
|
||||||
|
global _app_icon_cache
|
||||||
|
if _app_icon_cache is not None and not _app_icon_cache.isNull():
|
||||||
|
return _app_icon_cache
|
||||||
|
|
||||||
|
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
|
||||||
|
base_dir = sys._MEIPASS
|
||||||
|
else:
|
||||||
|
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
icon_path = os.path.join(base_dir, 'assets', 'Kemono.ico')
|
||||||
|
|
||||||
|
if os.path.exists(icon_path):
|
||||||
|
_app_icon_cache = QIcon(icon_path)
|
||||||
|
if _app_icon_cache.isNull():
|
||||||
|
print(f"Warning: QIcon created from '{icon_path}' is null. Icon might be invalid.")
|
||||||
|
_app_icon_cache = QIcon() # Store an empty icon to avoid re-processing
|
||||||
|
else:
|
||||||
|
print(f"Warning: Application icon 'assets/Kemono.ico' not found at {icon_path} (in get_app_icon_object)")
|
||||||
|
_app_icon_cache = QIcon() # Store an empty icon
|
||||||
|
|
||||||
|
return _app_icon_cache
|
||||||
|
|
||||||
MAX_THREADS =200
|
MAX_THREADS =200
|
||||||
RECOMMENDED_MAX_THREADS =50
|
RECOMMENDED_MAX_THREADS =50
|
||||||
MAX_FILE_THREADS_PER_POST_OR_WORKER =10
|
MAX_FILE_THREADS_PER_POST_OR_WORKER =10
|
||||||
@@ -168,6 +197,10 @@ class DownloadExtractedLinksDialog (QDialog ):
|
|||||||
super ().__init__ (parent )
|
super ().__init__ (parent )
|
||||||
self .links_data =links_data
|
self .links_data =links_data
|
||||||
|
|
||||||
|
app_icon = get_app_icon_object()
|
||||||
|
if not app_icon.isNull():
|
||||||
|
self.setWindowIcon(app_icon)
|
||||||
|
|
||||||
|
|
||||||
if parent :
|
if parent :
|
||||||
parent_width =parent .width ()
|
parent_width =parent .width ()
|
||||||
@@ -299,6 +332,10 @@ class ConfirmAddAllDialog (QDialog ):
|
|||||||
self .user_choice =CONFIRM_ADD_ALL_CANCEL_DOWNLOAD
|
self .user_choice =CONFIRM_ADD_ALL_CANCEL_DOWNLOAD
|
||||||
self .setWindowTitle (self ._tr ("confirm_add_all_dialog_title","Confirm Adding New Names"))
|
self .setWindowTitle (self ._tr ("confirm_add_all_dialog_title","Confirm Adding New Names"))
|
||||||
|
|
||||||
|
app_icon = get_app_icon_object()
|
||||||
|
if not app_icon.isNull():
|
||||||
|
self.setWindowIcon(app_icon)
|
||||||
|
|
||||||
main_layout =QVBoxLayout (self )
|
main_layout =QVBoxLayout (self )
|
||||||
|
|
||||||
info_label =QLabel (
|
info_label =QLabel (
|
||||||
@@ -416,6 +453,10 @@ class ExportOptionsDialog (QDialog ):
|
|||||||
self .setModal (True )
|
self .setModal (True )
|
||||||
self .selected_option =self .EXPORT_MODE_LINK_ONLY
|
self .selected_option =self .EXPORT_MODE_LINK_ONLY
|
||||||
|
|
||||||
|
app_icon = get_app_icon_object()
|
||||||
|
if not app_icon.isNull():
|
||||||
|
self.setWindowIcon(app_icon)
|
||||||
|
|
||||||
layout =QVBoxLayout (self )
|
layout =QVBoxLayout (self )
|
||||||
|
|
||||||
self .description_label =QLabel ()
|
self .description_label =QLabel ()
|
||||||
@@ -487,6 +528,10 @@ class ErrorFilesDialog (QDialog ):
|
|||||||
self .setModal (True )
|
self .setModal (True )
|
||||||
self .error_files =error_files_info_list
|
self .error_files =error_files_info_list
|
||||||
|
|
||||||
|
app_icon = get_app_icon_object()
|
||||||
|
if not app_icon.isNull():
|
||||||
|
self.setWindowIcon(app_icon)
|
||||||
|
|
||||||
main_layout =QVBoxLayout (self )
|
main_layout =QVBoxLayout (self )
|
||||||
|
|
||||||
if not self .error_files :
|
if not self .error_files :
|
||||||
@@ -625,6 +670,10 @@ class FutureSettingsDialog (QDialog ):
|
|||||||
self .parent_app =parent_app_ref
|
self .parent_app =parent_app_ref
|
||||||
self .setModal (True )
|
self .setModal (True )
|
||||||
|
|
||||||
|
app_icon = get_app_icon_object()
|
||||||
|
if not app_icon.isNull():
|
||||||
|
self.setWindowIcon(app_icon)
|
||||||
|
|
||||||
layout =QVBoxLayout (self )
|
layout =QVBoxLayout (self )
|
||||||
|
|
||||||
|
|
||||||
@@ -759,6 +808,10 @@ class EmptyPopupDialog (QDialog ):
|
|||||||
self .current_scope_mode =self .SCOPE_CHARACTERS
|
self .current_scope_mode =self .SCOPE_CHARACTERS
|
||||||
self .app_base_dir =app_base_dir
|
self .app_base_dir =app_base_dir
|
||||||
self .all_creators_data =[]
|
self .all_creators_data =[]
|
||||||
|
|
||||||
|
app_icon = get_app_icon_object()
|
||||||
|
if not app_icon.isNull():
|
||||||
|
self.setWindowIcon(app_icon)
|
||||||
self .selected_creators_for_queue =[]
|
self .selected_creators_for_queue =[]
|
||||||
self .globally_selected_creators ={}
|
self .globally_selected_creators ={}
|
||||||
|
|
||||||
@@ -1122,6 +1175,10 @@ class CookieHelpDialog (QDialog ):
|
|||||||
self .parent_app =parent_app
|
self .parent_app =parent_app
|
||||||
self .setModal (True )
|
self .setModal (True )
|
||||||
self .offer_download_without_option =offer_download_without_option
|
self .offer_download_without_option =offer_download_without_option
|
||||||
|
|
||||||
|
app_icon = get_app_icon_object()
|
||||||
|
if not app_icon.isNull():
|
||||||
|
self.setWindowIcon(app_icon)
|
||||||
self .user_choice =None
|
self .user_choice =None
|
||||||
main_layout =QVBoxLayout (self )
|
main_layout =QVBoxLayout (self )
|
||||||
|
|
||||||
@@ -1205,6 +1262,10 @@ class KnownNamesFilterDialog (QDialog ):
|
|||||||
self .parent_app =parent_app_ref
|
self .parent_app =parent_app_ref
|
||||||
self .setModal (True )
|
self .setModal (True )
|
||||||
self .all_known_name_entries =sorted (known_names_list ,key =lambda x :x ['name'].lower ())
|
self .all_known_name_entries =sorted (known_names_list ,key =lambda x :x ['name'].lower ())
|
||||||
|
|
||||||
|
app_icon = get_app_icon_object()
|
||||||
|
if not app_icon.isNull():
|
||||||
|
self.setWindowIcon(app_icon)
|
||||||
self .selected_entries_to_return =[]
|
self .selected_entries_to_return =[]
|
||||||
|
|
||||||
main_layout =QVBoxLayout (self )
|
main_layout =QVBoxLayout (self )
|
||||||
@@ -1307,6 +1368,10 @@ class FavoriteArtistsDialog (QDialog ):
|
|||||||
self .parent_app =parent_app
|
self .parent_app =parent_app
|
||||||
self .cookies_config =cookies_config
|
self .cookies_config =cookies_config
|
||||||
self .all_fetched_artists =[]
|
self .all_fetched_artists =[]
|
||||||
|
|
||||||
|
app_icon = get_app_icon_object()
|
||||||
|
if not app_icon.isNull():
|
||||||
|
self.setWindowIcon(app_icon)
|
||||||
self .selected_artist_urls =[]
|
self .selected_artist_urls =[]
|
||||||
|
|
||||||
self .setModal (True )
|
self .setModal (True )
|
||||||
@@ -1787,6 +1852,10 @@ class FavoritePostsDialog (QDialog ):
|
|||||||
self .displayable_grouped_posts ={}
|
self .displayable_grouped_posts ={}
|
||||||
self .fetcher_thread =None
|
self .fetcher_thread =None
|
||||||
|
|
||||||
|
app_icon = get_app_icon_object()
|
||||||
|
if not app_icon.isNull():
|
||||||
|
self.setWindowIcon(app_icon)
|
||||||
|
|
||||||
self .setModal (True )
|
self .setModal (True )
|
||||||
self .setMinimumSize (600 ,600 )
|
self .setMinimumSize (600 ,600 )
|
||||||
if hasattr (self .parent_app ,'get_dark_theme'):
|
if hasattr (self .parent_app ,'get_dark_theme'):
|
||||||
@@ -2181,6 +2250,10 @@ class HelpGuideDialog (QDialog ):
|
|||||||
self .steps_data =steps_data
|
self .steps_data =steps_data
|
||||||
self .parent_app =parent_app
|
self .parent_app =parent_app
|
||||||
|
|
||||||
|
app_icon = get_app_icon_object()
|
||||||
|
if not app_icon.isNull():
|
||||||
|
self.setWindowIcon(app_icon)
|
||||||
|
|
||||||
self .setModal (True )
|
self .setModal (True )
|
||||||
self .setFixedSize (650 ,600 )
|
self .setFixedSize (650 ,600 )
|
||||||
|
|
||||||
@@ -2350,7 +2423,7 @@ class TourDialog (QDialog ):
|
|||||||
|
|
||||||
CONFIG_ORGANIZATION_NAME ="KemonoDownloader"
|
CONFIG_ORGANIZATION_NAME ="KemonoDownloader"
|
||||||
CONFIG_APP_NAME_TOUR ="ApplicationTour"
|
CONFIG_APP_NAME_TOUR ="ApplicationTour"
|
||||||
TOUR_SHOWN_KEY ="neverShowTourAgainV18"
|
TOUR_SHOWN_KEY ="neverShowTourAgainV19"
|
||||||
|
|
||||||
def __init__ (self ,parent =None ):
|
def __init__ (self ,parent =None ):
|
||||||
super ().__init__ (parent )
|
super ().__init__ (parent )
|
||||||
@@ -2358,6 +2431,10 @@ class TourDialog (QDialog ):
|
|||||||
self .current_step =0
|
self .current_step =0
|
||||||
self .parent_app =parent
|
self .parent_app =parent
|
||||||
|
|
||||||
|
app_icon = get_app_icon_object()
|
||||||
|
if not app_icon.isNull():
|
||||||
|
self.setWindowIcon(app_icon)
|
||||||
|
|
||||||
self .setModal (True )
|
self .setModal (True )
|
||||||
self .setFixedSize (600 ,620 )
|
self .setFixedSize (600 ,620 )
|
||||||
self .setStyleSheet ("""
|
self .setStyleSheet ("""
|
||||||
@@ -2804,11 +2881,11 @@ class DownloaderApp (QWidget ):
|
|||||||
# Running as a script
|
# Running as a script
|
||||||
base_dir_for_icon = os.path.dirname(os.path.abspath(__file__))
|
base_dir_for_icon = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
icon_path_for_window = os.path.join(base_dir_for_icon, 'Kemono.ico')
|
icon_path_for_window = os.path.join(base_dir_for_icon, 'assets', 'Kemono.ico') # <--- This is for QWidget
|
||||||
if os.path.exists(icon_path_for_window):
|
if os.path.exists(icon_path_for_window):
|
||||||
self.setWindowIcon(QIcon(icon_path_for_window))
|
self.setWindowIcon(QIcon(icon_path_for_window))
|
||||||
else:
|
else:
|
||||||
self.log_signal.emit(f"⚠️ Main window icon 'Kemono.ico' not found at {icon_path_for_window} (tried in DownloaderApp init)")
|
self.log_signal.emit(f"⚠️ Main window icon 'assets/Kemono.ico' not found at {icon_path_for_window} (tried in DownloaderApp init)")
|
||||||
except Exception as e_icon_app:
|
except Exception as e_icon_app:
|
||||||
self.log_signal.emit(f"❌ Error setting main window icon in DownloaderApp init: {e_icon_app}")
|
self.log_signal.emit(f"❌ Error setting main window icon in DownloaderApp init: {e_icon_app}")
|
||||||
|
|
||||||
@@ -7145,9 +7222,9 @@ if __name__ =='__main__':
|
|||||||
qt_app =QApplication (sys .argv )
|
qt_app =QApplication (sys .argv )
|
||||||
if getattr (sys ,'frozen',False ):base_dir =sys ._MEIPASS
|
if getattr (sys ,'frozen',False ):base_dir =sys ._MEIPASS
|
||||||
else :base_dir =os .path .dirname (os .path .abspath (__file__ ))
|
else :base_dir =os .path .dirname (os .path .abspath (__file__ ))
|
||||||
icon_path =os .path .join (base_dir ,'Kemono.ico')
|
icon_path =os .path .join (base_dir , 'assets', 'Kemono.ico')
|
||||||
if os .path .exists (icon_path ):qt_app .setWindowIcon (QIcon (icon_path ))
|
if os .path .exists (icon_path ):qt_app .setWindowIcon (QIcon (icon_path ))
|
||||||
else :print (f"Warning: Application icon 'Kemono.ico' not found at {icon_path }")
|
else :print (f"Warning: Application icon 'assets/Kemono.ico' not found at {icon_path }")
|
||||||
|
|
||||||
downloader_app_instance =DownloaderApp ()
|
downloader_app_instance =DownloaderApp ()
|
||||||
primary_screen =QApplication .primaryScreen ()
|
primary_screen =QApplication .primaryScreen ()
|
||||||
|
|||||||
13
readme.md
13
readme.md
@@ -1,4 +1,4 @@
|
|||||||
<h1 align="center">Kemono Downloader v5.1.0</h1>
|
<h1 align="center">Kemono Downloader v5.2.0</h1>
|
||||||
|
|
||||||
<table align="center">
|
<table align="center">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -34,7 +34,7 @@ A powerful, feature-rich GUI application for downloading content from **[Kemono.
|
|||||||
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.
|
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.*
|
*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.1.0 enhances error handling and UI responsiveness.*
|
*Update v5.2.0 introduces multi-language support, theme selection, and further UI refinements.*
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="features.md"><strong>📚 Full Feature List</strong></a> •
|
<a href="features.md"><strong>📚 Full Feature List</strong></a> •
|
||||||
<a href="LICENSE"><strong>📝 License</strong></a>
|
<a href="LICENSE"><strong>📝 License</strong></a>
|
||||||
@@ -75,6 +75,8 @@ Kemono Downloader offers a range of features to streamline your content download
|
|||||||
- **Multithreading:** Configure the number of simultaneous downloads/post processing threads for improved speed.
|
- **Multithreading:** Configure the number of simultaneous downloads/post processing threads for improved speed.
|
||||||
- **Logging:**
|
- **Logging:**
|
||||||
- A detailed progress log displays download activity, errors, and summaries.
|
- 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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -84,6 +86,13 @@ Kemono Downloader offers a range of features to streamline your content download
|
|||||||
- 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).
|
- 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.
|
- 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.
|
- **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.
|
||||||
|
|
||||||
|
## ✨ 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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|||||||
Reference in New Issue
Block a user