From b3af6c1c15053452517d12bd37710f3c97786187 Mon Sep 17 00:00:00 2001 From: Yuvi9587 Date: Sun, 25 May 2025 21:21:00 +0530 Subject: [PATCH] Commit --- downloader_utils.py | 34 ++++++++++++++++--- main.py | 82 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 93 insertions(+), 23 deletions(-) diff --git a/downloader_utils.py b/downloader_utils.py index f6ec9b1..5bcccce 100644 --- a/downloader_utils.py +++ b/downloader_utils.py @@ -31,6 +31,7 @@ from io import BytesIO STYLE_POST_TITLE = "post_title" STYLE_ORIGINAL_NAME = "original_name" STYLE_DATE_BASED = "date_based" # For manga date-based sequential naming +MANGA_DATE_PREFIX_DEFAULT = "" # Default for the new prefix STYLE_POST_TITLE_GLOBAL_NUMBERING = "post_title_global_numbering" # For manga post title + global counter SKIP_SCOPE_FILES = "files" @@ -140,8 +141,8 @@ def clean_folder_name(name): def clean_filename(name): if not isinstance(name, str): name = str(name) cleaned = re.sub(r'[^\w\s\-\_\.\(\)]', '', name) - cleaned = cleaned.strip() - cleaned = re.sub(r'\s+', '_', cleaned) + cleaned = cleaned.strip() # Remove leading/trailing spaces first + cleaned = re.sub(r'\s+', ' ', cleaned) # Replace multiple internal spaces with a single space return cleaned if cleaned else "untitled_file" def strip_html_tags(html_text): @@ -604,6 +605,7 @@ class PostProcessorWorker: use_cookie=False, # Added missing parameter selected_cookie_file=None, # Added missing parameter app_base_dir=None, # New parameter for app's base directory + manga_date_prefix=MANGA_DATE_PREFIX_DEFAULT, # New parameter for date-based prefix manga_date_file_counter_ref=None, # New parameter for date-based manga naming manga_global_file_counter_ref=None, # New parameter for global numbering ): # type: ignore @@ -652,6 +654,7 @@ class PostProcessorWorker: self.selected_cookie_file = selected_cookie_file # Store selected cookie file path self.app_base_dir = app_base_dir # Store app base dir self.cookie_text = cookie_text # Store cookie text + self.manga_date_prefix = manga_date_prefix # Store the prefix self.manga_global_file_counter_ref = manga_global_file_counter_ref # Store global counter self.use_cookie = use_cookie # Store cookie setting @@ -734,6 +737,14 @@ class PostProcessorWorker: if self.manga_mode_active: # Note: duplicate_file_mode is overridden to "Delete" in main.py if manga_mode is on if self.manga_filename_style == STYLE_ORIGINAL_NAME: filename_to_save_in_main_path = clean_filename(api_original_filename) + # Apply prefix if provided for Original Name style + if self.manga_date_prefix and self.manga_date_prefix.strip(): + cleaned_prefix = clean_filename(self.manga_date_prefix.strip()) + if cleaned_prefix: + filename_to_save_in_main_path = f"{cleaned_prefix} {filename_to_save_in_main_path}" + else: + self.logger(f"⚠️ Manga Original Name Mode: Provided prefix '{self.manga_date_prefix}' was empty after cleaning. Using original name only.") + was_original_name_kept_flag = True elif self.manga_filename_style == STYLE_POST_TITLE: if post_title and post_title.strip(): @@ -759,7 +770,15 @@ class PostProcessorWorker: counter_val_for_filename = manga_date_file_counter_ref[0] manga_date_file_counter_ref[0] += 1 - filename_to_save_in_main_path = f"{counter_val_for_filename:03d}{original_ext}" + base_numbered_name = f"{counter_val_for_filename:03d}" + if self.manga_date_prefix and self.manga_date_prefix.strip(): + cleaned_prefix = clean_filename(self.manga_date_prefix.strip()) + if cleaned_prefix: # Ensure prefix is not empty after cleaning + filename_to_save_in_main_path = f"{cleaned_prefix} {base_numbered_name}{original_ext}" + else: # Prefix became empty after cleaning + filename_to_save_in_main_path = f"{base_numbered_name}{original_ext}"; self.logger(f"⚠️ Manga Date Mode: Provided prefix '{self.manga_date_prefix}' was empty after cleaning. Using number only.") + else: # No prefix provided + filename_to_save_in_main_path = f"{base_numbered_name}{original_ext}" else: self.logger(f"⚠️ Manga Date Mode: Counter ref not provided or malformed for '{api_original_filename}'. Using original. Ref: {manga_date_file_counter_ref}") filename_to_save_in_main_path = clean_filename(api_original_filename) @@ -796,8 +815,10 @@ class PostProcessorWorker: if not word_to_remove: continue pattern = re.compile(re.escape(word_to_remove), re.IGNORECASE) modified_base_name = pattern.sub("", modified_base_name) - modified_base_name = re.sub(r'[_.\s-]+', '_', modified_base_name) - modified_base_name = modified_base_name.strip('_') + # After removals, normalize all seps (underscore, dot, multiple spaces, hyphen) to a single space, then strip. + modified_base_name = re.sub(r'[_.\s-]+', ' ', modified_base_name) # Convert all separators to spaces + modified_base_name = re.sub(r'\s+', ' ', modified_base_name) # Condense multiple spaces to one + modified_base_name = modified_base_name.strip() # Remove leading/trailing spaces if modified_base_name and modified_base_name != ext_for_removal.lstrip('.'): filename_to_save_in_main_path = modified_base_name + ext_for_removal else: @@ -1548,6 +1569,7 @@ class DownloadThread(QThread): manga_filename_style=STYLE_POST_TITLE, char_filter_scope=CHAR_SCOPE_FILES, # manga_date_file_counter_ref removed from here remove_from_filename_words_list=None, + manga_date_prefix=MANGA_DATE_PREFIX_DEFAULT, # New parameter allow_multipart_download=True, selected_cookie_file=None, # New parameter for selected cookie file app_base_dir=None, # New parameter @@ -1597,6 +1619,7 @@ class DownloadThread(QThread): self.manga_filename_style = manga_filename_style self.char_filter_scope = char_filter_scope self.remove_from_filename_words_list = remove_from_filename_words_list + self.manga_date_prefix = manga_date_prefix # Store the prefix self.allow_multipart_download = allow_multipart_download self.selected_cookie_file = selected_cookie_file # Store selected cookie file self.app_base_dir = app_base_dir # Store app base dir @@ -1726,6 +1749,7 @@ class DownloadThread(QThread): skip_current_file_flag=self.skip_current_file_flag, manga_mode_active=self.manga_mode_active, manga_filename_style=self.manga_filename_style, + manga_date_prefix=self.manga_date_prefix, # Pass the prefix char_filter_scope=self.char_filter_scope, remove_from_filename_words_list=self.remove_from_filename_words_list, allow_multipart_download=self.allow_multipart_download, diff --git a/main.py b/main.py index 2f92dca..1107d0c 100644 --- a/main.py +++ b/main.py @@ -54,7 +54,8 @@ try: CHAR_SCOPE_FILES, # Ensure this is imported CHAR_SCOPE_BOTH, CHAR_SCOPE_COMMENTS, - FILE_DOWNLOAD_STATUS_FAILED_RETRYABLE_LATER, # Import the new status + FILE_DOWNLOAD_STATUS_FAILED_RETRYABLE_LATER, + STYLE_DATE_BASED, # Import new manga style STYLE_POST_TITLE_GLOBAL_NUMBERING # Import new manga style ) print("Successfully imported names from downloader_utils.") @@ -88,6 +89,7 @@ except ImportError as e: CHAR_SCOPE_BOTH = "both" CHAR_SCOPE_COMMENTS = "comments" FILE_DOWNLOAD_STATUS_FAILED_RETRYABLE_LATER = "failed_retry_later" + STYLE_DATE_BASED = "date_based" STYLE_POST_TITLE_GLOBAL_NUMBERING = "post_title_global_numbering" # Mock for safety except Exception as e: @@ -112,7 +114,7 @@ HTML_PREFIX = "" CONFIG_ORGANIZATION_NAME = "KemonoDownloader" CONFIG_APP_NAME_MAIN = "ApplicationSettings" MANGA_FILENAME_STYLE_KEY = "mangaFilenameStyleV1" -STYLE_POST_TITLE = "post_title" +STYLE_POST_TITLE = "post_title" # Already defined, but ensure it's STYLE_POST_TITLE STYLE_ORIGINAL_NAME = "original_name" STYLE_DATE_BASED = "date_based" # New style for date-based naming STYLE_POST_TITLE_GLOBAL_NUMBERING = STYLE_POST_TITLE_GLOBAL_NUMBERING # Use imported or mocked @@ -585,9 +587,9 @@ class TourDialog(QDialog): "
  • A filename style toggle button (e.g., 'Name: Post Title') appears in the top-right of the log area when this mode is active for a creator feed. Click it to cycle through naming styles:" " " "

  • " "
  • For best results with 'Name: Post Title', 'Name: Title+G.Num', or 'Name: Date Based' styles, use the 'Filter by Character(s)' field with the manga/series title for folder organization.
  • " @@ -1564,6 +1566,14 @@ class DownloaderApp(QWidget): self._update_manga_filename_style_button_text() log_title_layout.addWidget(self.manga_rename_toggle_button) + # NEW: Manga Date Prefix Input + self.manga_date_prefix_input = QLineEdit() + self.manga_date_prefix_input.setPlaceholderText("Prefix for Manga Filenames") # Generalized + self.manga_date_prefix_input.setToolTip("Optional prefix for 'Date Based' or 'Original File' manga filenames (e.g., 'Series Name').\nIf empty, files will be named based on the style without a prefix.") # Generalized + self.manga_date_prefix_input.setVisible(False) # Initially hidden + self.manga_date_prefix_input.setFixedWidth(160) # Adjust as needed + log_title_layout.addWidget(self.manga_date_prefix_input) # Add to layout + self.multipart_toggle_button = QPushButton() self.multipart_toggle_button.setToolTip("Toggle between Multi-part and Single-stream downloads for large files.") self.multipart_toggle_button.setFixedWidth(130) # Adjust width as needed @@ -2553,10 +2563,10 @@ class DownloaderApp(QWidget): elif self.manga_filename_style == STYLE_ORIGINAL_NAME: self.manga_rename_toggle_button.setText("Name: Original File") self.manga_rename_toggle_button.setToolTip( - "Manga Filename Style: Original File Name\n\n" + "Manga Filename Style: Original File Name\n\n" # Updated tooltip "When Manga/Comic Mode is active for a creator feed:\n" "- *All* files in a post will attempt to keep their original filenames as provided by the site (e.g., \"001.jpg\", \"page_02.png\").\n" - "- This can be useful if original names are already well-structured and sequential.\n" + "- An optional prefix can be entered in the field next to this button (e.g., 'MySeries_001.jpg').\n" "- If original names are inconsistent, using \"Post Title\" style is often better.\n" "- Example: Post \"Chapter 1: The Beginning\" with files \"001.jpg\", \"002.jpg\".\n" " Downloads as: \"001.jpg\", \"002.jpg\".\n\n" @@ -2579,7 +2589,8 @@ class DownloaderApp(QWidget): self.manga_rename_toggle_button.setToolTip( "Manga Filename Style: Date Based\n\n" "When Manga/Comic Mode is active for a creator feed:\n" - "- Files will be named sequentially (001.ext, 002.ext, ...) based on post publication order (oldest to newest).\n" + "- Files will be named sequentially (001.ext, 002.ext, ...) based on post publication order.\n" + "- An optional prefix can be entered in the field next to this button (e.g., 'MySeries_001.jpg').\n" "- To ensure correct numbering, multithreading for post processing is automatically disabled when this style is active.\n\n" "Click to change to: Post Title" ) @@ -2613,7 +2624,7 @@ class DownloaderApp(QWidget): self.settings.setValue(MANGA_FILENAME_STYLE_KEY, self.manga_filename_style) self.settings.sync() self._update_manga_filename_style_button_text() - self._update_multithreading_for_date_mode() # Update multithreading state based on new style + self.update_ui_for_manga_mode(self.manga_mode_checkbox.isChecked() if self.manga_mode_checkbox else False) # Update UI based on new style self.log_signal.emit(f"ℹ️ Manga filename style changed to: '{self.manga_filename_style}'") @@ -2638,7 +2649,9 @@ class DownloaderApp(QWidget): # Always update page range enabled state, as it depends on URL type, not directly manga mode. self.update_page_range_enabled_state() - + + current_filename_style = self.manga_filename_style + file_download_mode_active = not (self.radio_only_links and self.radio_only_links.isChecked()) # Character filter widgets should be enabled if it's a file download mode enable_char_filter_widgets = file_download_mode_active and not (self.radio_only_archives and self.radio_only_archives.isChecked()) @@ -2650,6 +2663,23 @@ class DownloaderApp(QWidget): self.char_filter_scope_toggle_button.setEnabled(enable_char_filter_widgets) if self.character_filter_widget: # Also ensure the main widget visibility is correct self.character_filter_widget.setVisible(enable_char_filter_widgets) + + # Visibility for manga date prefix input + show_date_prefix_input = ( + manga_mode_effectively_on and + (current_filename_style == STYLE_DATE_BASED or current_filename_style == STYLE_ORIGINAL_NAME) and # MODIFIED + not (is_only_links_mode or is_only_archives_mode) + ) + if hasattr(self, 'manga_date_prefix_input'): + self.manga_date_prefix_input.setVisible(show_date_prefix_input) + if not show_date_prefix_input: # Clear if not visible + self.manga_date_prefix_input.clear() + + # Visibility for multipart toggle button + if hasattr(self, 'multipart_toggle_button'): + show_multipart_button = not (show_date_prefix_input or is_only_links_mode or is_only_archives_mode) + self.multipart_toggle_button.setVisible(show_multipart_button) + self._update_multithreading_for_date_mode() # Update multithreading state based on manga mode @@ -2820,7 +2850,7 @@ class DownloaderApp(QWidget): if not extract_links_only and not os.path.isdir(output_dir): reply = QMessageBox.question(self, "Create Directory?", f"The directory '{output_dir}' does not exist.\nCreate it now?", - QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) + QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) # type: ignore if reply == QMessageBox.Yes: try: os.makedirs(output_dir, exist_ok=True); self.log_signal.emit(f"ℹ️ Created directory: {output_dir}") except Exception as e: QMessageBox.critical(self, "Directory Error", f"Could not create directory: {e}"); return @@ -2830,8 +2860,18 @@ class DownloaderApp(QWidget): QMessageBox.warning(self, "Missing Dependency", "Pillow library (for image compression) not found. Compression will be disabled.") compress_images = False; self.compress_images_checkbox.setChecked(False) + # Initialize log_messages here, before it's potentially used by manga_date_prefix_text logging + log_messages = ["="*40, f"🚀 Starting {'Link Extraction' if extract_links_only else ('Archive Download' if backend_filter_mode == 'archive' else 'Download')} @ {time.strftime('%Y-%m-%d %H:%M:%S')}", f" URL: {api_url}"] + manga_mode = manga_mode_is_checked and not post_id_from_url + manga_date_prefix_text = "" + if manga_mode and \ + (self.manga_filename_style == STYLE_DATE_BASED or self.manga_filename_style == STYLE_ORIGINAL_NAME) and \ + hasattr(self, 'manga_date_prefix_input'): + manga_date_prefix_text = self.manga_date_prefix_input.text().strip() + if manga_date_prefix_text: # Log only if prefix is provided (log_messages is now initialized) + log_messages.append(f" ↳ Manga Date Prefix: '{manga_date_prefix_text}'") start_page_str, end_page_str = self.start_page_input.text().strip(), self.end_page_input.text().strip() start_page, end_page = None, None @@ -3032,7 +3072,7 @@ class DownloaderApp(QWidget): effective_num_post_workers = max(1, min(num_threads_from_gui, MAX_THREADS)) # For posts effective_num_file_threads_per_worker = 1 # Files within each post worker are sequential - log_messages = ["="*40, f"🚀 Starting {'Link Extraction' if extract_links_only else ('Archive Download' if backend_filter_mode == 'archive' else 'Download')} @ {time.strftime('%Y-%m-%d %H:%M:%S')}", f" URL: {api_url}"] + # log_messages initialization was moved earlier if not extract_links_only: log_messages.append(f" Save Location: {output_dir}") if post_id_from_url: @@ -3139,6 +3179,7 @@ class DownloaderApp(QWidget): 'manga_mode_active': manga_mode, 'unwanted_keywords': unwanted_keywords_for_folders, 'cancellation_event': self.cancellation_event, + 'manga_date_prefix': manga_date_prefix_text, # NEW ARGUMENT 'dynamic_character_filter_holder': self.dynamic_character_filter_holder, # Pass the holder 'pause_event': self.pause_event, # Explicitly add pause_event here 'manga_filename_style': self.manga_filename_style, @@ -3170,7 +3211,7 @@ class DownloaderApp(QWidget): 'show_external_links', 'extract_links_only', 'num_file_threads_for_worker', 'start_page', 'end_page', 'target_post_id_from_initial_url', 'manga_date_file_counter_ref', - 'manga_global_file_counter_ref', # Pass new counter for single thread mode + 'manga_global_file_counter_ref', 'manga_date_prefix', # Pass new counter and prefix for single thread mode 'manga_mode_active', 'unwanted_keywords', 'manga_filename_style', 'allow_multipart_download', 'use_cookie', 'cookie_text', 'app_base_dir', 'selected_cookie_file' # Added selected_cookie_file ] @@ -3367,14 +3408,14 @@ class DownloaderApp(QWidget): 'skip_words_list', 'skip_words_scope', 'char_filter_scope', 'show_external_links', 'extract_links_only', 'allow_multipart_download', 'use_cookie', 'cookie_text', 'app_base_dir', 'selected_cookie_file', # Added selected_cookie_file 'num_file_threads', 'skip_current_file_flag', 'manga_date_file_counter_ref', - 'manga_mode_active', 'manga_filename_style', + 'manga_mode_active', 'manga_filename_style', 'manga_date_prefix', # ADD manga_date_prefix 'manga_global_file_counter_ref' # Add new counter here ] ppw_optional_keys_with_defaults = { 'skip_words_list', 'skip_words_scope', 'char_filter_scope', 'remove_from_filename_words_list', 'show_external_links', 'extract_links_only', 'duplicate_file_mode', # Added duplicate_file_mode here - 'num_file_threads', 'skip_current_file_flag', 'manga_mode_active', 'manga_filename_style', - 'manga_date_file_counter_ref', 'use_cookie', 'cookie_text', 'app_base_dir', 'selected_cookie_file' # Added selected_cookie_file + 'num_file_threads', 'skip_current_file_flag', 'manga_mode_active', 'manga_filename_style', 'manga_date_prefix', # ADD manga_date_prefix + 'manga_date_file_counter_ref', 'use_cookie', 'cookie_text', 'app_base_dir', 'selected_cookie_file' } # Batching is generally for high worker counts. # If num_post_workers is low (e.g., 1), the num_post_workers > POST_WORKER_BATCH_THRESHOLD condition will prevent batching. @@ -3620,6 +3661,8 @@ class DownloaderApp(QWidget): self.skip_words_scope = SKIP_SCOPE_POSTS # Default self._update_skip_scope_button_text() + if hasattr(self, 'manga_date_prefix_input'): self.manga_date_prefix_input.clear() # Clear prefix input + self.char_filter_scope = CHAR_SCOPE_TITLE # Default self._update_char_filter_scope_button_text() @@ -3966,6 +4009,7 @@ class DownloaderApp(QWidget): self.missed_title_key_terms_examples.clear() self.logged_summary_for_key_term.clear() self.already_logged_bold_key_terms.clear() + if hasattr(self, 'manga_date_prefix_input'): self.manga_date_prefix_input.clear() # Clear prefix input if self.pause_event: self.pause_event.clear() self.is_paused = False # Reset pause state self.missed_key_terms_buffer.clear() @@ -4128,8 +4172,9 @@ class DownloaderApp(QWidget):
  • For best results with 'Name: Post Title', 'Name: Title+G.Num', or 'Name: Date Based' styles, use the 'Filter by Character(s)' field with the manga/series title for folder organization.
  • @@ -4175,7 +4220,8 @@ class DownloaderApp(QWidget):
  • Name: [Style] Button (Manga Filename Style): +
  • When 'Original File' or 'Date Based' style is active, an input field for an optional filename prefix will appear next to this button.
  • +
  • Multi-part: [ON/OFF] Button: