diff --git a/py/api.py b/py/api.py index 7791e87..74ed278 100644 --- a/py/api.py +++ b/py/api.py @@ -1951,30 +1951,53 @@ class PromptManagerAPI: # Send scanning event await send_progress('status', { 'phase': 'scanning', - 'message': 'Scanning for images to process...' + 'message': f'Scanning {output_path} for images and videos to process...' }) + self.logger.info(f"Starting thumbnail generation scan in: {output_path}") + # Find all media files (images and videos) image_extensions = ['.png', '.jpg', '.jpeg', '.webp', '.gif'] video_extensions = ['.mp4', '.webm', '.avi', '.mov', '.mkv', '.m4v', '.wmv'] media_extensions = image_extensions + video_extensions media_files = [] + scanned_dirs = 0 + for root, dirs, files in os.walk(output_path): # Skip thumbnails directory if 'thumbnails' in Path(root).parts: continue + + scanned_dirs += 1 + # Send scanning progress for every directory + if scanned_dirs % 5 == 0: # Update every 5 directories + await send_progress('status', { + 'phase': 'scanning', + 'message': f'Scanning directories... ({scanned_dirs} checked, {len(media_files)} files found)' + }) + for file in files: if any(file.lower().endswith(ext) for ext in media_extensions): media_files.append(Path(root) / file) + self.logger.info(f"Scan complete: Found {len(media_files)} media files in {scanned_dirs} directories") + total_images = len(media_files) + # Count images vs videos for more detail + image_count = sum(1 for f in media_files if any(f.name.lower().endswith(ext) for ext in image_extensions)) + video_count = total_images - image_count + await send_progress('start', { 'total_images': total_images, 'phase': 'processing', - 'message': f'Found {total_images} media files to process' + 'message': f'Found {image_count} images and {video_count} videos to process', + 'image_count': image_count, + 'video_count': video_count }) + self.logger.info(f"Starting thumbnail generation for {image_count} images and {video_count} videos") + if total_images == 0: await send_progress('complete', { 'count': 0, @@ -2058,13 +2081,22 @@ class PromptManagerAPI: img.save(thumbnail_path, **save_kwargs) generated_count += 1 - # Send progress update every 10 images or every 2% - if (i + 1) % 10 == 0 or (i + 1) % max(1, total_images // 50) == 0 or i == total_images - 1: + # Send progress update more frequently for better feedback + # Update every 5 images or every 1% for large sets + if (i + 1) % 5 == 0 or (i + 1) % max(1, total_images // 100) == 0 or i == total_images - 1: elapsed = time.time() - start_time progress_percent = ((i + 1) / total_images) * 100 rate = (i + 1) / elapsed if elapsed > 0 else 0 eta = ((total_images - i - 1) / rate) if rate > 0 else 0 + # Include more detailed file info + file_info = { + 'name': media_file.name, + 'dir': media_file.parent.name, + 'type': 'video' if is_video else 'image', + 'action': 'skipped' if thumbnail_path.exists() else 'generating' + } + await send_progress('progress', { 'processed': i + 1, 'total_images': total_images, @@ -2074,27 +2106,47 @@ class PromptManagerAPI: 'rate': round(rate, 1), 'eta': round(eta, 0), 'elapsed': round(elapsed, 1), - 'current_file': media_file.name + 'current_file': f"{file_info['dir']}/{file_info['name']}", + 'file_type': file_info['type'], + 'action': file_info['action'] }) + + # Log progress + if (i + 1) % 50 == 0: # Log every 50 files + self.logger.info(f"Thumbnail progress: {i+1}/{total_images} ({progress_percent:.1f}%) - Generated: {generated_count}, Skipped: {skipped_count}") except Exception as e: error_msg = f"Failed to generate thumbnail for {media_file.name}: {str(e)}" errors.append(error_msg) self.logger.warning(error_msg) + + # Send error notification for immediate feedback + if len(errors) <= 5: # Only send first 5 errors to avoid spam + await send_progress('status', { + 'phase': 'processing', + 'message': f'Error processing {media_file.name}: {str(e)}' + }) elapsed_time = time.time() - start_time - # Send completion event + # Send detailed completion event + completion_message = f'Successfully generated {generated_count} new thumbnails, skipped {skipped_count} existing' + if errors: + completion_message += f' ({len(errors)} errors occurred)' + await send_progress('complete', { 'count': generated_count, 'skipped': skipped_count, 'total_images': total_images, - 'errors': errors, + 'errors': errors[:10], # Limit errors to first 10 + 'error_count': len(errors), 'elapsed_time': round(elapsed_time, 2), 'processing_rate': round((total_images / elapsed_time) if elapsed_time > 0 else 0, 2), - 'message': f'Generated {generated_count} new thumbnails, skipped {skipped_count} existing' + 'message': completion_message }) + self.logger.info(f"Thumbnail generation completed: {generated_count} generated, {skipped_count} skipped, {len(errors)} errors in {elapsed_time:.2f}s") + except Exception as e: await send_progress('error', { 'error': str(e), diff --git a/web/gallery.html b/web/gallery.html index 8e2f1dc..9103432 100644 --- a/web/gallery.html +++ b/web/gallery.html @@ -2288,12 +2288,20 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`; const progressText = document.getElementById('progressText'); const progressPercent = document.getElementById('progressPercent'); const progressDetails = document.getElementById('progressDetails'); + const progressETA = document.getElementById('progressETA'); btn.disabled = true; btn.textContent = 'Generating...'; - status.textContent = 'Initializing...'; + status.textContent = 'Initializing thumbnail generation...'; progressContainer.classList.remove('hidden'); + // Initialize progress display + progressBar.style.width = '0%'; + progressPercent.textContent = 'Starting...'; + progressText.textContent = 'Connecting to server...'; + progressDetails.textContent = 'Preparing to scan output directory...'; + progressETA.textContent = ''; + try { // Start thumbnail generation with progress updates const result = await this.generateThumbnailsWithProgress({ @@ -2318,11 +2326,13 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`; } catch (error) { console.error('Thumbnail generation error:', error); + console.error('Error details:', error.stack); status.textContent = 'Generation failed'; progressText.textContent = 'Failed'; progressDetails.textContent = error.message; progressBar.style.width = '0%'; progressBar.classList.add('bg-red-600'); + progressPercent.textContent = 'Error'; this.showNotification('Failed to generate thumbnails: ' + error.message, 'error'); @@ -2349,9 +2359,14 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`; // Show cancel button cancelBtn.classList.remove('hidden'); + console.log(`Starting thumbnail generation with quality: ${options.quality}`); + // Set up Server-Sent Events const eventSource = new EventSource(`/prompt_manager/images/generate-thumbnails/progress?quality=${options.quality}`); + // Log connection + console.log('EventSource connected for thumbnail generation progress'); + let cancelled = false; let resultData = null; @@ -2369,15 +2384,38 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`; if (cancelled) return; const data = JSON.parse(event.data); progressText.textContent = data.message || 'Processing...'; - progressDetails.textContent = data.phase === 'scanning' ? 'Scanning output folder...' : 'Preparing...'; + if (data.phase === 'scanning') { + progressDetails.textContent = 'Scanning output folder for images and videos...'; + progressBar.style.width = '0%'; + progressPercent.textContent = 'Scanning...'; + } else { + progressDetails.textContent = 'Preparing thumbnail generation...'; + } + + // Log status updates + if (this.getSettings().debugMode) { + console.log(`Thumbnail generation status: ${data.phase} - ${data.message}`); + } }); // Handle start event eventSource.addEventListener('start', (event) => { if (cancelled) return; const data = JSON.parse(event.data); - progressText.textContent = `Processing ${data.total_images} images...`; - progressDetails.textContent = data.message || `Found ${data.total_images} images to process`; + // Show detailed file type breakdown if available + const fileBreakdown = (data.image_count !== undefined && data.video_count !== undefined) + ? ` (${data.image_count} images, ${data.video_count} videos)` + : ''; + progressText.textContent = `Starting to process ${data.total_images} media files${fileBreakdown}...`; + progressDetails.textContent = data.message || `Found ${data.total_images} images and videos to process`; + progressBar.style.width = '0%'; + progressPercent.textContent = '0%'; + progressETA.textContent = 'Calculating...'; + + // Log start event + if (this.getSettings().debugMode) { + console.log(`Thumbnail generation started: ${data.total_images} files to process`); + } }); // Handle progress updates @@ -2389,9 +2427,14 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`; progressBar.style.width = `${data.percentage}%`; progressPercent.textContent = `${data.percentage}%`; - // Update text - progressText.textContent = `Processing... (${data.processed}/${data.total_images})`; - progressDetails.textContent = `Generated: ${data.generated}, Skipped: ${data.skipped} | Rate: ${data.rate} img/s`; + // Update text with current file being processed and action + const fileInfo = data.current_file ? ` - ${data.current_file}` : ''; + const actionText = data.action === 'skipped' ? ' [Skipping]' : data.action === 'generating' ? ' [Generating]' : ''; + progressText.textContent = `Processing ${data.file_type || 'file'}... (${data.processed}/${data.total_images})${fileInfo}${actionText}`; + + // More detailed progress information + const elapsedText = data.elapsed ? ` | Time: ${Math.floor(data.elapsed)}s` : ''; + progressDetails.textContent = `Generated: ${data.generated}, Skipped: ${data.skipped} | Rate: ${data.rate} img/s${elapsedText}`; // Update ETA if (data.eta > 0 && data.eta < 3600) { @@ -2400,6 +2443,11 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`; } else { progressETA.textContent = ''; } + + // Log to console for debugging if debug mode is enabled + if (this.getSettings().debugMode) { + console.log(`Thumbnail generation progress: ${data.processed}/${data.total_images} - ${data.current_file}`); + } }); // Handle completion @@ -2411,9 +2459,27 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`; progressBar.style.width = '100%'; progressPercent.textContent = '100%'; progressText.textContent = 'Completed!'; - progressDetails.textContent = resultData.message || `Generated ${resultData.count} new thumbnails`; + + // Show detailed completion message + const errorCount = resultData.error_count || (resultData.errors ? resultData.errors.length : 0); + const errors = errorCount > 0 ? ` (${errorCount} errors)` : ''; + const rateText = resultData.processing_rate ? ` at ${resultData.processing_rate} img/s` : ''; + progressDetails.textContent = `Generated: ${resultData.count} new, Skipped: ${resultData.skipped} existing | Total: ${resultData.total_images} files | Time: ${resultData.elapsed_time}s${rateText}${errors}`; + + // Show first error if any occurred + if (resultData.errors && resultData.errors.length > 0 && this.getSettings().debugMode) { + console.error('First thumbnail error:', resultData.errors[0]); + } progressETA.textContent = 'Done'; + // Log completion details + if (this.getSettings().debugMode) { + console.log('Thumbnail generation completed:', resultData); + if (resultData.errors && resultData.errors.length > 0) { + console.warn('Thumbnail generation errors:', resultData.errors); + } + } + // Clean up eventSource.close(); cancelBtn.removeEventListener('click', cancelHandler); @@ -2439,13 +2505,24 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`; eventSource.onerror = (error) => { if (cancelled) return; console.error('EventSource error:', error); + console.error('EventSource readyState:', eventSource.readyState); + + // Check if connection is closing normally + if (eventSource.readyState === EventSource.CLOSED) { + console.log('EventSource connection closed'); + } else { + console.error('EventSource connection failed'); + } // Clean up eventSource.close(); cancelBtn.removeEventListener('click', cancelHandler); cancelBtn.classList.add('hidden'); - reject(new Error('Connection to server lost during thumbnail generation')); + // Only reject if we don't have result data + if (!resultData) { + reject(new Error('Connection to server lost during thumbnail generation')); + } }; }); } @@ -2835,7 +2912,7 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`; const modal = document.createElement('div'); modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50'; modal.innerHTML = ` -