diff --git a/Kemono.ico b/assets/Kemono.ico similarity index 100% rename from Kemono.ico rename to assets/Kemono.ico diff --git a/Kemono.png b/assets/Kemono.png similarity index 100% rename from Kemono.png rename to assets/Kemono.png diff --git a/features.md b/features.md index be7d9d3..28d9981 100644 --- a/features.md +++ b/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. --- +## ⚙️ 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:** diff --git a/main.py b/main.py index 3300079..5d4cc4e 100644 --- a/main.py +++ b/main.py @@ -119,6 +119,35 @@ except ImportError : 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 RECOMMENDED_MAX_THREADS =50 MAX_FILE_THREADS_PER_POST_OR_WORKER =10 @@ -168,6 +197,10 @@ class DownloadExtractedLinksDialog (QDialog ): super ().__init__ (parent ) self .links_data =links_data + app_icon = get_app_icon_object() + if not app_icon.isNull(): + self.setWindowIcon(app_icon) + if parent : parent_width =parent .width () @@ -299,6 +332,10 @@ class ConfirmAddAllDialog (QDialog ): self .user_choice =CONFIRM_ADD_ALL_CANCEL_DOWNLOAD 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 ) info_label =QLabel ( @@ -416,6 +453,10 @@ class ExportOptionsDialog (QDialog ): self .setModal (True ) 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 ) self .description_label =QLabel () @@ -487,6 +528,10 @@ class ErrorFilesDialog (QDialog ): self .setModal (True ) 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 ) if not self .error_files : @@ -625,6 +670,10 @@ class FutureSettingsDialog (QDialog ): self .parent_app =parent_app_ref self .setModal (True ) + app_icon = get_app_icon_object() + if not app_icon.isNull(): + self.setWindowIcon(app_icon) + layout =QVBoxLayout (self ) @@ -759,6 +808,10 @@ class EmptyPopupDialog (QDialog ): self .current_scope_mode =self .SCOPE_CHARACTERS self .app_base_dir =app_base_dir 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 .globally_selected_creators ={} @@ -1122,6 +1175,10 @@ class CookieHelpDialog (QDialog ): self .parent_app =parent_app self .setModal (True ) 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 main_layout =QVBoxLayout (self ) @@ -1205,6 +1262,10 @@ class KnownNamesFilterDialog (QDialog ): self .parent_app =parent_app_ref self .setModal (True ) 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 =[] main_layout =QVBoxLayout (self ) @@ -1307,6 +1368,10 @@ class FavoriteArtistsDialog (QDialog ): self .parent_app =parent_app self .cookies_config =cookies_config self .all_fetched_artists =[] + + app_icon = get_app_icon_object() + if not app_icon.isNull(): + self.setWindowIcon(app_icon) self .selected_artist_urls =[] self .setModal (True ) @@ -1787,6 +1852,10 @@ class FavoritePostsDialog (QDialog ): self .displayable_grouped_posts ={} self .fetcher_thread =None + app_icon = get_app_icon_object() + if not app_icon.isNull(): + self.setWindowIcon(app_icon) + self .setModal (True ) self .setMinimumSize (600 ,600 ) if hasattr (self .parent_app ,'get_dark_theme'): @@ -2181,6 +2250,10 @@ class HelpGuideDialog (QDialog ): self .steps_data =steps_data self .parent_app =parent_app + app_icon = get_app_icon_object() + if not app_icon.isNull(): + self.setWindowIcon(app_icon) + self .setModal (True ) self .setFixedSize (650 ,600 ) @@ -2350,7 +2423,7 @@ class TourDialog (QDialog ): CONFIG_ORGANIZATION_NAME ="KemonoDownloader" CONFIG_APP_NAME_TOUR ="ApplicationTour" - TOUR_SHOWN_KEY ="neverShowTourAgainV18" + TOUR_SHOWN_KEY ="neverShowTourAgainV19" def __init__ (self ,parent =None ): super ().__init__ (parent ) @@ -2358,6 +2431,10 @@ class TourDialog (QDialog ): self .current_step =0 self .parent_app =parent + app_icon = get_app_icon_object() + if not app_icon.isNull(): + self.setWindowIcon(app_icon) + self .setModal (True ) self .setFixedSize (600 ,620 ) self .setStyleSheet (""" @@ -2803,12 +2880,12 @@ class DownloaderApp (QWidget ): else: # Running as a script 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): self.setWindowIcon(QIcon(icon_path_for_window)) 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: 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 ) if getattr (sys ,'frozen',False ):base_dir =sys ._MEIPASS 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 )) - 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 () primary_screen =QApplication .primaryScreen () diff --git a/readme.md b/readme.md index 9dd8237..188d805 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -

Kemono Downloader v5.1.0

+

Kemono Downloader v5.2.0

@@ -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. *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.*

📚 Full Feature List📝 License @@ -69,12 +69,14 @@ Kemono Downloader offers a range of features to streamline your content download - **⭐ 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. + - 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. --- @@ -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). - 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. + +## ✨ 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