jekyll-imgflow 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +661 -0
  3. data/README.md +214 -0
  4. data/lib/jekyll-imgflow/batch_manager.rb +263 -0
  5. data/lib/jekyll-imgflow/build_time_processor.rb +152 -0
  6. data/lib/jekyll-imgflow/config.rb +130 -0
  7. data/lib/jekyll-imgflow/filename_generator.rb +102 -0
  8. data/lib/jekyll-imgflow/helpers/http_downloader.rb +68 -0
  9. data/lib/jekyll-imgflow/hooks.rb +57 -0
  10. data/lib/jekyll-imgflow/html_generator.rb +335 -0
  11. data/lib/jekyll-imgflow/imgflow_tag.rb +247 -0
  12. data/lib/jekyll-imgflow/manifest_manager.rb +297 -0
  13. data/lib/jekyll-imgflow/operation_processor.rb +183 -0
  14. data/lib/jekyll-imgflow/parser.rb +199 -0
  15. data/lib/jekyll-imgflow/path_resolver.rb +130 -0
  16. data/lib/jekyll-imgflow/picture_tag_adaptor.rb +299 -0
  17. data/lib/jekyll-imgflow/picture_tag_preset_migrator.rb +210 -0
  18. data/lib/jekyll-imgflow/preset_manager.rb +149 -0
  19. data/lib/jekyll-imgflow/provider_registry.rb +106 -0
  20. data/lib/jekyll-imgflow/providers/base_provider.rb +198 -0
  21. data/lib/jekyll-imgflow/providers/flyimg.rb +187 -0
  22. data/lib/jekyll-imgflow/providers/imagemagick.rb +138 -0
  23. data/lib/jekyll-imgflow/providers/imgproxy.rb +164 -0
  24. data/lib/jekyll-imgflow/providers/libvips.rb +292 -0
  25. data/lib/jekyll-imgflow/providers/sharp.rb +213 -0
  26. data/lib/jekyll-imgflow/providers/weserv.rb +160 -0
  27. data/lib/jekyll-imgflow/tag_scanner.rb +227 -0
  28. data/lib/jekyll-imgflow/tags/base_tag.rb +52 -0
  29. data/lib/jekyll-imgflow/tags/crop_tag.rb +220 -0
  30. data/lib/jekyll-imgflow/tags/format_tag.rb +64 -0
  31. data/lib/jekyll-imgflow/tags/opacity_tag.rb +36 -0
  32. data/lib/jekyll-imgflow/tags/optimize_tag.rb +52 -0
  33. data/lib/jekyll-imgflow/tags/quality_tag.rb +31 -0
  34. data/lib/jekyll-imgflow/tags/resize_tag.rb +58 -0
  35. data/lib/jekyll-imgflow/tags/tag_registry.rb +70 -0
  36. data/lib/jekyll-imgflow/tags/watermark_tag.rb +51 -0
  37. data/lib/jekyll-imgflow/version.rb +5 -0
  38. data/lib/jekyll-imgflow.rb +30 -0
  39. metadata +261 -0
data/README.md ADDED
@@ -0,0 +1,214 @@
1
+ # ImgFlow
2
+
3
+ A [Jekyll](https://jekyllrb.com/) plugin for automatic image optimization with multiple providers and formats.
4
+
5
+ ## 🚀 Quick Start
6
+
7
+ ```bash
8
+ # Add to your Gemfile
9
+ gem 'jekyll-imgflow'
10
+
11
+ # Install
12
+ bundle install
13
+
14
+ # Configure (_config.yml)
15
+ imgflow:
16
+ originals: "assets/images/originals"
17
+ output: "assets/images/optimized"
18
+ sizes: {sm: 400, md: 800, lg: 1200}
19
+ formats: [webp, avif, jpg]
20
+
21
+ # Use in templates
22
+ {% imgflow photo.jpg resize width:800 %}
23
+ ```
24
+
25
+ ## ✨ Key Features
26
+
27
+ - **Multiple Providers**: [Sharp](https://github.com/lovell/sharp), [ImageMagick](https://github.com/ImageMagick/ImageMagick), [LibVips](https://github.com/libvips/libvips), [Imgproxy](https://github.com/imgproxy/imgproxy), [Weserv](https://github.com/weserv/images), [Flyimg](https://github.com/flyimg/flyimg)
28
+ - **Modern Formats**: WebP, AVIF, JPG, PNG with automatic fallbacks
29
+ - **Picture Tag Support**: Full [Jekyll Picture Tag](https://github.com/robwierzbowski/jekyll_picture_tag) compatibility
30
+ - **Build-Time Processing**: Pre-generate images for optimal performance
31
+ - **Docker Services**: Ready-to-use image processing containers
32
+ - **Parallel Testing**: Fast test execution with CPU optimization
33
+
34
+ ## 📚 Documentation
35
+
36
+ ### Getting Started
37
+ - **[Installation Guide](docs/installation.md)** - Detailed setup instructions
38
+ - **[Basic Usage](docs/usage/)** - Core functionality and examples
39
+ - **[Picture Tag Migration](docs/picture_tag_migration.md)** - Migrate from Picture Tag
40
+
41
+ ### Configuration
42
+ - **[Provider Setup](docs/providers.md)** - Configure image providers
43
+ - **[Docker Services](docs/docker.md)** - Using Docker containers
44
+ - **[Presets System](docs/usage/presets.md)** - Predefined configurations
45
+
46
+ ### Development
47
+ - **[Development Guide](docs/development.md)** - Contributing and setup
48
+ - **[Testing Guide](docs/testing.md)** - Running and writing tests
49
+ - **[Scripts Reference](docs/scripts.md)** - Development utilities
50
+
51
+ ### Performance
52
+ - **[Provider Comparison](docs/providers.md)** - Performance benchmarks
53
+ - **[Parallel Testing](docs/parallel_testing.md)** - Fast test execution
54
+
55
+ ## 🏗️ Architecture Overview
56
+
57
+ ImgFlow has two processing flows: **Build-Time** (pre-generation) and **Runtime** (on-demand).
58
+
59
+ **See:** [ARCHITECTURE.md](docs/ARCHITECTURE.md) for detailed component architecture and data flow
60
+
61
+ ### Quick Overview
62
+ - **Build-Time Flow** - Pre-generate images during Jekyll build
63
+ - **Runtime Flow** - Process images on-demand during template rendering
64
+ - **Providers** - Image processing backends (CLI tools, HTTP APIs)
65
+ - **Tags** - Jekyll template tags for image optimization
66
+
67
+ ## 🎯 Provider Support
68
+
69
+ ### HTTP API Services (Recommended)
70
+ - **[Imgproxy](https://github.com/imgproxy/imgproxy)** - Fast, reliable (Port 33001)
71
+ - **[Weserv](https://github.com/weserv/images)** - Battle-tested (Port 33007)
72
+ - **[Flyimg](https://github.com/flyimg/flyimg)** - On-the-fly processing (Port 33008)
73
+
74
+ ### CLI Tools (Local)
75
+ - **[Sharp](https://github.com/lovell/sharp)** - Fastest (Node.js/libvips)
76
+ - **[ImageMagick](https://github.com/ImageMagick/ImageMagick)** - Feature-rich
77
+ - **[LibVips](https://github.com/libvips/libvips)** - Memory efficient
78
+
79
+ **See:** [providers.md](docs/providers.md) for detailed provider comparison and setup
80
+
81
+ ## 🚀 Quick Commands
82
+
83
+ ```bash
84
+ # Development
85
+ bundle exec jekyll serve # Start development server
86
+ bundle exec jekyll build # Build site
87
+
88
+ # Testing
89
+ rake quick # Quick checks (style + tests)
90
+ rake test # All tests
91
+ rake parallel:test # Parallel testing (faster)
92
+
93
+ # Docker Services
94
+ docker-compose -f docker-compose.test.yml up -d # Start services
95
+ rake check_services # Verify services
96
+ ```
97
+
98
+ **See:** [testing.md](docs/testing.md) for comprehensive testing guide, [scripts.md](docs/scripts.md) for development utilities, and [rake.md](docs/rake.md) for all available Rake tasks
99
+
100
+ ## 📦 Installation
101
+
102
+ ### Quick Setup
103
+ ```bash
104
+ # Add to Gemfile
105
+ gem 'jekyll-imgflow'
106
+
107
+ # Install
108
+ bundle install
109
+
110
+ # Configure (_config.yml)
111
+ imgflow:
112
+ originals: "assets/images/originals"
113
+ output: "assets/images/optimized"
114
+ sizes: {sm: 400, md: 800, lg: 1200}
115
+ formats: [webp, avif, jpg]
116
+
117
+ # Use in templates
118
+ {% imgflow photo.jpg resize width:800 %}
119
+ ```
120
+
121
+ **See:** [installation.md](docs/installation.md) for detailed installation instructions and configuration options
122
+
123
+ ## 🐳 Docker Setup (Recommended)
124
+
125
+ ```bash
126
+ # Start all services
127
+ docker-compose -f docker-compose.test.yml --env-file .env.test up -d
128
+
129
+ # Check services
130
+ rake check_services
131
+
132
+ # Stop services
133
+ docker-compose -f docker-compose.test.yml down
134
+ ```
135
+
136
+ **See:** [docker.md](docs/docker.md) for detailed Docker configuration and services
137
+
138
+ ## 🔄 Picture Tag Migration
139
+
140
+ ImgFlow provides full compatibility with [Jekyll Picture Tag](https://github.com/robwierzbowski/jekyll_picture_tag):
141
+
142
+ ```bash
143
+ # Preview migration
144
+ bin/imgflow_migrate_presets --preview
145
+
146
+ # Migrate presets
147
+ bin/imgflow_migrate_presets
148
+ ```
149
+
150
+ **See:** [Picture Tag Migration Guide](docs/picture_tag_migration.md)
151
+
152
+ ## 📊 Performance
153
+
154
+ Latest benchmarks show **Sharp** as the fastest provider:
155
+
156
+ | Provider | Time (s) | Speed |
157
+ |----------|----------|-------|
158
+ | [Sharp](https://github.com/lovell/sharp) | 13.97 | 🏆 Fastest |
159
+ | [LibVips](https://github.com/libvips/libvips) | 21.89 | Very Fast |
160
+ | [Weserv](https://github.com/weserv/images) | 30.19 | Fast |
161
+ | [ImageMagick](https://github.com/ImageMagick/ImageMagick) | 30.62 | Fast |
162
+ | [Imgproxy](https://github.com/imgproxy/imgproxy) | 31.27 | Fast |
163
+
164
+ **See:** [Provider Comparison](docs/providers.md) for detailed benchmarks
165
+
166
+ ## 🛠️ Development
167
+
168
+ ### Quick Setup
169
+ ```bash
170
+ # Clone and setup
171
+ git clone <repository>
172
+ cd jekyll-imgflow
173
+ bundle install
174
+
175
+ # Download test images
176
+ rake download_test_images
177
+
178
+ # Run tests
179
+ rake quick
180
+ ```
181
+
182
+ ### Key Files
183
+ - **`lib/jekyll-imgflow.rb`** - Main module
184
+ - **`lib/jekyll-imgflow/imgflow_tag.rb`** - Main tag implementation
185
+ - **`lib/jekyll-imgflow/html_generator.rb`** - HTML generation
186
+ - **`lib/jekyll-imgflow/picture_tag_adaptor.rb`** - Picture Tag compatibility
187
+
188
+ **See:** [development.md](docs/development.md) for complete development setup, scripts, and workflows
189
+
190
+ ## 🤝 Contributing
191
+
192
+ 1. Fork the repository
193
+ 2. Create a feature branch
194
+ 3. Make your changes
195
+ 4. Add tests for new functionality
196
+ 5. Run `rake quality` to check all quality metrics
197
+ 6. Submit a pull request
198
+
199
+ **See:** [development.md](docs/development.md) for detailed guidelines and [scripts.md](docs/scripts.md) for development utilities
200
+
201
+ ## 📄 License
202
+
203
+ This project is licensed under the AGPL-3.0-or-later License - see the [LICENSE](LICENSE) file for details.
204
+
205
+ ## 🔗 Links
206
+
207
+ - **[Documentation](docs/README.md)** - Complete documentation
208
+ - **[GitHub Repository](https://github.com/gundestrup/jekyll-imgflow)** - Source code
209
+ - **[Issue Tracker](https://github.com/gundestrup/jekyll-imgflow/issues)** - Report issues
210
+ - **[Releases](https://github.com/gundestrup/jekyll-imgflow/releases)** - Latest versions
211
+
212
+ ---
213
+
214
+ **ImgFlow** - Fast, flexible image optimization for Jekyll sites 🚀
@@ -0,0 +1,263 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "filename_generator"
4
+
5
+ module JekyllImgFlow
6
+ # BatchManager - manages batch processing of image operations
7
+ # Queues tasks and processes them efficiently
8
+ class BatchManager
9
+ attr_reader :queue, :completed, :failed
10
+
11
+ def initialize(operation_processor)
12
+ @operation_processor = operation_processor
13
+ @filename_generator = FilenameGenerator.new
14
+ @queue = []
15
+ @completed = []
16
+ @failed = []
17
+ @mutex = Mutex.new
18
+ end
19
+
20
+ # Add a task to the batch queue
21
+ # @param task [Hash] Task definition
22
+ # @option task [String] :original_name Original image filename
23
+ # @option task [Symbol] :operation_type Type of operation
24
+ # @option task [String] :input_path Path to input image
25
+ # @option task [String] :output_path Path to output image
26
+ # @option task [Hash] :params Operation parameters
27
+ # @option task [Symbol] :version_type :default or :specialized
28
+ # @option task [String] :page_path Page using this image
29
+ def add_task(task)
30
+ @mutex.synchronize do
31
+ @queue << task
32
+ end
33
+ end
34
+
35
+ # Add multiple tasks to the queue
36
+ # @param tasks [Array<Hash>] Array of task definitions
37
+ def add_tasks(tasks)
38
+ @mutex.synchronize do
39
+ @queue.concat(tasks)
40
+ end
41
+ end
42
+
43
+ # Process all queued tasks
44
+ # @param parallel [Boolean] Whether to process in parallel (future enhancement)
45
+ # @return [Hash] Results summary
46
+ def process_all(parallel: false)
47
+ start_time = Time.now
48
+
49
+ if parallel
50
+ # Future: implement parallel processing
51
+ process_parallel
52
+ else
53
+ process_sequential
54
+ end
55
+
56
+ end_time = Time.now
57
+ duration = end_time - start_time
58
+
59
+ {
60
+ total: @queue.length + @completed.length + @failed.length,
61
+ completed: @completed.length,
62
+ failed: @failed.length,
63
+ duration: duration
64
+ }
65
+ end
66
+
67
+ # Process tasks sequentially
68
+ def process_sequential
69
+ while (task = next_task)
70
+ process_task(task)
71
+ end
72
+ end
73
+
74
+ # Process tasks in parallel (future enhancement)
75
+ def process_parallel
76
+ # Future: implement thread pool for parallel processing
77
+ # For now, fall back to sequential
78
+ process_sequential
79
+ end
80
+
81
+ # Get the next task from the queue
82
+ # @return [Hash, nil] Next task or nil if queue is empty
83
+ def next_task
84
+ @mutex.synchronize do
85
+ @queue.shift
86
+ end
87
+ end
88
+
89
+ # Process a single task
90
+ # @param task [Hash] Task to process
91
+ def process_task(task)
92
+ # Check if processing is needed
93
+ # Check if output is up-to-date
94
+ if task[:skip_if_exists] && File.exist?(task[:output_path]) && !@operation_processor.needs_processing?(
95
+ task[:input_path],
96
+ task[:output_path],
97
+ task[:params]
98
+ )
99
+ Jekyll.logger.debug "⏭️ Skipping #{task[:original_name]} - already up-to-date"
100
+ mark_completed(task, :skipped)
101
+ return
102
+ end
103
+
104
+ # Process the operation
105
+ operation = {
106
+ type: task[:operation_type],
107
+ params: task[:params]
108
+ }
109
+
110
+ Jekyll.logger.debug "🔍 BatchManager: Processing task - input: #{task[:input_path]}, original: #{task[:original_name]}"
111
+ result = @operation_processor.process_operation(
112
+ task[:original_name],
113
+ operation,
114
+ task[:input_path]
115
+ )
116
+ Jekyll.logger.debug "🔍 BatchManager: OperationProcessor returned - result: #{result}"
117
+
118
+ # Store result for manifest registration
119
+ task[:result_path] = result
120
+
121
+ mark_completed(task, :processed, result)
122
+ Jekyll.logger.debug "✅ Processed: #{task[:original_name]} → #{File.basename(result)}"
123
+ rescue StandardError => e
124
+ mark_failed(task, e)
125
+ Jekyll.logger.error "❌ Failed: #{task[:original_name]} - #{e.message}"
126
+ end
127
+
128
+ # Mark a task as completed
129
+ # @param task [Hash] Completed task
130
+ # @param status [Symbol] Completion status (:processed or :skipped)
131
+ # @param result [String] Result path
132
+ def mark_completed(task, status, result = nil)
133
+ @mutex.synchronize do
134
+ @completed << {
135
+ task: task,
136
+ status: status,
137
+ result: result,
138
+ completed_at: Time.now
139
+ }
140
+ end
141
+ end
142
+
143
+ # Mark a task as failed
144
+ # @param task [Hash] Failed task
145
+ # @param error [Exception] Error that occurred
146
+ def mark_failed(task, error)
147
+ @mutex.synchronize do
148
+ @failed << {
149
+ task: task,
150
+ error: error.message,
151
+ backtrace: error.backtrace&.first(5),
152
+ failed_at: Time.now
153
+ }
154
+ end
155
+ end
156
+
157
+ # Get queue status
158
+ # @return [Hash] Current queue status
159
+ def status
160
+ @mutex.synchronize do
161
+ {
162
+ queued: @queue.length,
163
+ completed: @completed.length,
164
+ failed: @failed.length,
165
+ total: @queue.length + @completed.length + @failed.length
166
+ }
167
+ end
168
+ end
169
+
170
+ # Clear all queues
171
+ def clear
172
+ @mutex.synchronize do
173
+ @queue.clear
174
+ @completed.clear
175
+ @failed.clear
176
+ end
177
+ end
178
+
179
+ # Get failed tasks for retry
180
+ # @return [Array<Hash>] Array of failed tasks
181
+ def failed_tasks
182
+ @mutex.synchronize do
183
+ @failed.map { |f| f[:task] }
184
+ end
185
+ end
186
+
187
+ # Retry failed tasks
188
+ # @return [Integer] Number of tasks added back to queue
189
+ def retry_failed
190
+ tasks = failed_tasks
191
+ @mutex.synchronize do
192
+ @failed.clear
193
+ @queue.concat(tasks)
194
+ end
195
+ tasks.length
196
+ end
197
+
198
+ # Build batch tasks for default image versions
199
+ # @param original_name [String] Original image filename
200
+ # @param input_path [String] Path to original image
201
+ # @param config [Config] ImgFlow configuration
202
+ # @param site [Jekyll::Site] Jekyll site object
203
+ # @return [Array<Hash>] Array of tasks
204
+ def self.build_default_tasks(original_name, input_path, config, _site)
205
+ tasks = []
206
+
207
+ # Use FilenameGenerator for consistent naming
208
+ filename_generator = JekyllImgFlow::FilenameGenerator.new
209
+ path_resolver = JekyllImgFlow::PathResolver.new(config)
210
+
211
+ config.sizes.each_value do |width|
212
+ config.formats.each do |format|
213
+ # Use FilenameGenerator to generate proper filename
214
+ operations = { width: width, format: format, quality: config.quality }
215
+ filename = filename_generator.generate_filename(original_name, operations)
216
+ # Write to _site during build (after Jekyll copies assets)
217
+ output_path = path_resolver.resolve_output_path(filename)
218
+
219
+ tasks << {
220
+ original_name: original_name,
221
+ operation_type: :resize,
222
+ input_path: input_path,
223
+ output_path: output_path,
224
+ params: {
225
+ width: width,
226
+ format: format,
227
+ quality: config.quality
228
+ },
229
+ version_type: :default,
230
+ page_path: nil, # Default versions not tied to specific page
231
+ skip_if_exists: true
232
+ }
233
+ end
234
+ end
235
+
236
+ tasks
237
+ end
238
+
239
+ # Build batch tasks for specialized image version
240
+ # @param original_name [String] Original image filename
241
+ # @param input_path [String] Path to original image
242
+ # @param operations [Array<Hash>] Operations to apply
243
+ # @param output_path [String] Path to output image
244
+ # @param page_path [String] Page using this image
245
+ # @return [Hash] Task definition
246
+ def self.build_specialized_task(original_name, input_path, operations, output_path, page_path)
247
+ # For now, assume single operation
248
+ # Future: support chaining multiple operations
249
+ operation = operations.first
250
+
251
+ {
252
+ original_name: original_name,
253
+ operation_type: operation[:type],
254
+ input_path: input_path,
255
+ output_path: output_path,
256
+ params: operation[:params],
257
+ version_type: :specialized,
258
+ page_path: page_path,
259
+ skip_if_exists: false
260
+ }
261
+ end
262
+ end
263
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JekyllImgFlow
4
+ # BuildTimeProcessor - processes images during Jekyll build
5
+ # Generates default image versions for all originals
6
+ class BuildTimeProcessor
7
+ def initialize(site)
8
+ @site = site
9
+ @config = Config.new(site)
10
+ @manifest = ManifestManager.new(site)
11
+ @path_resolver = PathResolver.new(@config)
12
+ @registry = ProviderRegistry.new(@config)
13
+
14
+ # Initialize operation processor with current provider
15
+ provider = @registry.current_provider
16
+ @operation_processor = OperationProcessor.new(provider, @path_resolver, @manifest, @config)
17
+
18
+ # Initialize batch manager
19
+ @batch_manager = BatchManager.new(@operation_processor)
20
+
21
+ # Store all components in site for sharing with ImgFlow tag
22
+ return if @site.imgflow_components
23
+
24
+ filename_generator = JekyllImgFlow::FilenameGenerator.new
25
+
26
+ @site.imgflow_components = {
27
+ config: @config,
28
+ manifest: @manifest,
29
+ path_resolver: @path_resolver,
30
+ filename_generator: filename_generator,
31
+ registry: @registry,
32
+ provider: provider,
33
+ operation_processor: @operation_processor
34
+ }
35
+ end
36
+
37
+ # Process all changed images in originals folder
38
+ def process_changed_images
39
+ Jekyll.logger.info "🔄 BuildTimeProcessor: Processing changed images"
40
+
41
+ # Get all original images
42
+ original_paths = find_original_images
43
+ Jekyll.logger.info "📸 Found #{original_paths.length} original images"
44
+
45
+ # Build tasks for each original image
46
+ original_paths.each do |path|
47
+ original_name = File.basename(path)
48
+
49
+ # Check if needs processing (file changed or provider changed)
50
+ if needs_processing?(path)
51
+ Jekyll.logger.info "🔧 Queuing default versions for: #{original_name}"
52
+
53
+ # Build default tasks
54
+ tasks = BatchManager.build_default_tasks(original_name, path, @config, @site)
55
+ @batch_manager.add_tasks(tasks)
56
+ else
57
+ Jekyll.logger.debug "⏭️ Skipping #{original_name} - already up-to-date"
58
+ end
59
+ end
60
+
61
+ # Process all queued tasks
62
+ results = @batch_manager.process_all
63
+
64
+ # Register completed tasks in manifest
65
+ register_completed_tasks(@batch_manager.completed)
66
+
67
+ # Save manifest so it's available when ImgflowTag runs
68
+ @manifest.save
69
+
70
+ Jekyll.logger.info "✅ BuildTimeProcessor complete: #{results[:completed]} completed, #{results[:failed]} failed"
71
+
72
+ results
73
+ end
74
+
75
+ private
76
+
77
+ # Register completed tasks in manifest
78
+ # @param completed_tasks [Array<Hash>] Array of completed tasks from BatchManager
79
+ def register_completed_tasks(completed_tasks)
80
+ completed_tasks.each do |task|
81
+ next unless task[:task] && task[:result]
82
+
83
+ # Get provider name
84
+ provider_name = @registry.current_provider.class.name.split("::").last.downcase
85
+
86
+ # Convert absolute path to relative for manifest storage
87
+ absolute_path = task[:result]
88
+ relative_path = absolute_path.sub(@site.dest, "")
89
+
90
+ # Register the version in manifest
91
+ @manifest.register_version(
92
+ task[:task][:original_name],
93
+ relative_path,
94
+ task[:task][:params],
95
+ :default, # BuildTimeProcessor creates default versions
96
+ nil, # page_path (default versions aren't tied to specific pages)
97
+ nil, # file_digest
98
+ provider_name
99
+ )
100
+ end
101
+
102
+ Jekyll.logger.debug "📝 Registered #{completed_tasks.length} completed tasks in manifest"
103
+ end
104
+
105
+ # Find all original images in originals folder
106
+ def find_original_images
107
+ # Use PathResolver to get the originals directory path
108
+ originals_dir = File.join(@site.source, @config.originals)
109
+ input_formats = @config.input_formats
110
+
111
+ Dir.glob(File.join(originals_dir, "**", "*.{#{input_formats.join(',')}}"))
112
+ end
113
+
114
+ # Check if image needs processing
115
+ # @param path [String] Path to original image
116
+ # @return [Boolean] True if processing needed
117
+ def needs_processing?(path)
118
+ original_name = File.basename(path)
119
+
120
+ # If no versions exist, needs processing
121
+ return true unless @manifest.versions?(original_name)
122
+
123
+ # Check if original file was modified
124
+ versions = @manifest.get_versions(original_name)
125
+ default_versions = versions["default"] || []
126
+
127
+ return true if default_versions.empty?
128
+
129
+ # Get the most recent version creation time
130
+ latest_version = default_versions.max_by { |v| v["created_at"] || 0 }
131
+ version_time = latest_version["created_at"]
132
+
133
+ # If original is newer than versions, needs processing
134
+ return true unless version_time
135
+
136
+ original_mtime = File.mtime(path).to_i
137
+ return true if original_mtime > version_time
138
+
139
+ # No timestamp means we need to process
140
+
141
+ # Check if provider changed
142
+ current_provider = @registry.current_provider.class.provider_name
143
+ version_providers = default_versions.map { |v| v["provider"] }
144
+ version_providers.uniq!
145
+
146
+ return true unless version_providers.include?(current_provider)
147
+
148
+ # Up-to-date
149
+ false
150
+ end
151
+ end
152
+ end