jekyll-imgflow 0.1.5 → 0.1.7

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 41492bf948a18c7820bd6251596966353e63411cc891f2808671cb15d8536d90
4
- data.tar.gz: 838d154fcc3bd85b9e77d15e62a11cf0b14b84fea4d18504ff131221a62d57a9
3
+ metadata.gz: 07f3c2dfe3416810827699ab3b82a29012283ad308ad215f034f1bb86d8d8a79
4
+ data.tar.gz: 6ee5f237e22490069f7b3dc6ef0d8ba63a2ad8ecd1fe8a90754034a03192b11f
5
5
  SHA512:
6
- metadata.gz: 02a8519c3b0dd50f28c71989ba60925c1aa091529d2a10ca30dff688abf0713170601a5c90ad8595da4917ca86677b592d4af3f4d17b729e55d472b719b7d87f
7
- data.tar.gz: fa306b6f147c4ee45cc0c92fa0fe586f9630a0ca1d6f11515309d91461860b9b4841a0cd43fdeaefb467fbf798c8b8cfb7760ba1b847ada3ebf970ad4136183f
6
+ metadata.gz: e8da1e7ca9f599f6e1dcfbe5b2a44277ec53edd42b57e34159af1090e0fafcf068b361ac0713ba8932e4acb213f3569a268ffc1471c43c21ffd5845734769315
7
+ data.tar.gz: 4c7014c3888b5d4a2c94022f89eab3a383edf315fe34b556f3b727d7483f883b0b40ac772ab05fd51d37ab5d900122385d0887052bc6508085a2513dce339d0d
data/README.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  A [Jekyll](https://jekyllrb.com/) plugin for automatic image optimization with multiple providers and formats.
4
4
 
5
+ [![DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/gundestrup/jekyll-imgflow)
6
+ [![Status](https://img.shields.io/badge/status-active-success)](https://github.com/gundestrup/jekyll-imgflow)
7
+ [![codecov](https://codecov.io/gh/gundestrup/jekyll-imgflow/branch/main/graph/badge.svg)](https://codecov.io/gh/gundestrup/jekyll-imgflow)
8
+ [![License](https://img.shields.io/github/license/gundestrup/jekyll-imgflow)](LICENSE)
9
+
5
10
  ## 🚀 Quick Start
6
11
 
7
12
  ```bash
@@ -11,17 +16,26 @@ gem 'jekyll-imgflow'
11
16
  # Install
12
17
  bundle install
13
18
 
14
- # Configure (_config.yml)
19
+ # Configure (_config.yml) — only originals and output are required,
20
+ # everything else has sensible defaults:
15
21
  imgflow:
16
22
  originals: "assets/images/originals"
17
23
  output: "assets/images/optimized"
18
- sizes: {sm: 400, md: 800, lg: 1200}
19
- formats: [webp, avif, jpg]
20
24
 
21
25
  # Use in templates
22
26
  {% imgflow photo.jpg resize width:800 %}
23
27
  ```
24
28
 
29
+ > **Full configuration options:** `quality`, `backend_priority`, `formats`,
30
+ > `fallback_format`, `sizes`, provider URLs, and more — see the
31
+ > [Installation Guide](docs/installation.md#4-add-to-_configyml)
32
+ > and [Configuration Reference](docs/ARCHITECTURE.md).
33
+
34
+ > **Note:** Image references require exact filenames (or paths relative to
35
+ > `originals`). There is no fuzzy matching or autocomplete yet — if you type
36
+ > `photo.jpg` but the file is `photo.jpeg`, the build will fail with an error.
37
+ > Future versions may add "did you mean" suggestions.
38
+
25
39
  ## ✨ Key Features
26
40
 
27
41
  - **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)
@@ -107,18 +121,17 @@ gem 'jekyll-imgflow'
107
121
  # Install
108
122
  bundle install
109
123
 
110
- # Configure (_config.yml)
124
+ # Configure (_config.yml) — only originals and output required:
111
125
  imgflow:
112
126
  originals: "assets/images/originals"
113
127
  output: "assets/images/optimized"
114
- sizes: {sm: 400, md: 800, lg: 1200}
115
- formats: [webp, avif, jpg]
116
128
 
117
129
  # Use in templates
118
130
  {% imgflow photo.jpg resize width:800 %}
119
131
  ```
120
132
 
121
- **See:** [installation.md](docs/installation.md) for detailed installation instructions and configuration options
133
+ **See:** [installation.md](docs/installation.md) for detailed installation and
134
+ [all configuration options](docs/installation.md#4-add-to-_configyml)
122
135
 
123
136
  ## 🐳 Docker Setup (Recommended)
124
137
 
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JekyllImgFlow
4
+ # Detects whether a GIF file is animated (contains multiple frames)
5
+ #
6
+ # Animated GIFs must not be resized or format-converted because the
7
+ # operation would destroy the animation. This class parses the GIF
8
+ # binary format and counts Image Descriptor blocks to determine
9
+ # whether more than one frame is present.
10
+ class AnimatedGifDetector
11
+ # GIF block introducer bytes
12
+ EXTENSION_INTRODUCER = 0x21
13
+ IMAGE_DESCRIPTOR = 0x2C
14
+ TRAILER = 0x3B
15
+
16
+ # Check whether the file at +path+ is an animated GIF.
17
+ #
18
+ # @param path [String] Path to the image file
19
+ # @return [Boolean] true if the file is a GIF with more than one frame
20
+ def self.animated?(path)
21
+ new(path).animated?
22
+ end
23
+
24
+ # @param path [String] Path to the image file
25
+ def initialize(path)
26
+ @path = path
27
+ end
28
+
29
+ def animated?
30
+ return false unless gif?
31
+
32
+ frame_count > 1
33
+ end
34
+
35
+ private
36
+
37
+ def gif?
38
+ return false unless File.exist?(@path)
39
+
40
+ magic = File.binread(@path, 6)
41
+ %w[GIF87a GIF89a].include?(magic)
42
+ rescue StandardError
43
+ false
44
+ end
45
+
46
+ # Count the number of Image Descriptor (0x2C) blocks in the GIF.
47
+ # Each Image Descriptor marks the start of a frame.
48
+ # @return [Integer] Number of frames found
49
+ def frame_count
50
+ data = File.binread(@path)
51
+ reader = GifReader.new(data)
52
+
53
+ reader.skip_header_and_global_color_table
54
+ reader.count_image_descriptors
55
+ end
56
+
57
+ # Minimal forward-only GIF block reader.
58
+ # Walks the block structure to locate Image Descriptor markers
59
+ # without misinterpreting bytes that appear inside compressed data.
60
+ class GifReader
61
+ def initialize(data)
62
+ @data = data
63
+ @pos = 0
64
+ end
65
+
66
+ # Advance past the GIF header + Logical Screen Descriptor and
67
+ # the optional Global Color Table.
68
+ def skip_header_and_global_color_table
69
+ # Signature (3) + Version (3) already validated by caller;
70
+ # skip to Logical Screen Descriptor packed byte.
71
+ @pos = 10 # 6 (magic) + 4 (width/height)
72
+ packed = @data.getbyte(@pos)
73
+ @pos += 1 # consume packed byte
74
+
75
+ global_color_table_flag = packed.anybits?(0x80)
76
+ return unless global_color_table_flag
77
+
78
+ size_of_global_color_table = packed & 0x07
79
+ table_size = 3 * (2**(size_of_global_color_table + 1))
80
+ @pos += table_size
81
+ end
82
+
83
+ # Walk remaining blocks, counting Image Descriptors.
84
+ # @return [Integer] frame count
85
+ def count_image_descriptors
86
+ count = 0
87
+
88
+ while @pos < @data.length
89
+ introducer = @data.getbyte(@pos)
90
+
91
+ case introducer
92
+ when IMAGE_DESCRIPTOR
93
+ count += 1
94
+ skip_image_data
95
+ when EXTENSION_INTRODUCER
96
+ skip_extension
97
+ when TRAILER
98
+ break
99
+ else
100
+ # Unknown block — advance one byte to avoid infinite loop
101
+ @pos += 1
102
+ end
103
+ end
104
+
105
+ count
106
+ end
107
+
108
+ private
109
+
110
+ # Skip an Image Descriptor block + its Local Color Table +
111
+ # the LZW-compressed image data sub-blocks.
112
+ def skip_image_data
113
+ # Image Descriptor is 10 bytes (introducer + 9)
114
+ @pos += 10
115
+
116
+ packed = @data.getbyte(@pos - 1)
117
+ local_color_table_flag = packed.anybits?(0x80)
118
+
119
+ if local_color_table_flag
120
+ size_of_local_color_table = packed & 0x07
121
+ table_size = 3 * (2**(size_of_local_color_table + 1))
122
+ @pos += table_size
123
+ end
124
+
125
+ # LZW Minimum Code Size (1 byte)
126
+ @pos += 1
127
+
128
+ skip_sub_blocks
129
+ end
130
+
131
+ # Skip an Extension block (0x21 + label + sub-blocks).
132
+ def skip_extension
133
+ @pos += 2 # introducer + label
134
+ skip_sub_blocks
135
+ end
136
+
137
+ # Skip a sequence of sub-blocks: each starts with a size byte
138
+ # (1-255) followed by that many data bytes. A size of 0
139
+ # terminates the sequence.
140
+ def skip_sub_blocks
141
+ loop do
142
+ break if @pos >= @data.length
143
+
144
+ size = @data.getbyte(@pos)
145
+ @pos += 1
146
+ break if size.zero?
147
+
148
+ @pos += size
149
+ end
150
+ end
151
+ end
152
+
153
+ private_constant :GifReader
154
+ end
155
+ end
@@ -213,8 +213,8 @@ module JekyllImgFlow
213
213
  # Use FilenameGenerator to generate proper filename
214
214
  operations = { width: width, format: format, quality: config.quality }
215
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)
216
+ # Write to source directory so Jekyll copies files to _site during write phase
217
+ output_path = path_resolver.resolve_source_output_path(filename)
218
218
 
219
219
  tasks << {
220
220
  original_name: original_name,
@@ -109,6 +109,7 @@ module JekyllImgFlow
109
109
  input_formats = @config.input_formats
110
110
 
111
111
  Dir.glob(File.join(originals_dir, "**", "*.{#{input_formats.join(',')}}"))
112
+ .reject { |path| AnimatedGifDetector.animated?(path) }
112
113
  end
113
114
 
114
115
  # Check if image needs processing
@@ -2,10 +2,29 @@
2
2
 
3
3
  module JekyllImgFlow
4
4
  class Config
5
+ # Sensible defaults — users only need to configure `originals` and `output`
6
+ # (or even nothing at all). All other values fall back to these.
7
+ DEFAULT_ORIGINALS = "assets/images/originals"
8
+ DEFAULT_OUTPUT = "assets/images/optimized"
9
+ DEFAULT_INPUT_FORMATS = %w[jpg jpeg png webp avif gif tiff tif svg].freeze
10
+ # Output format priority: avif > webp > png > jpg (fallback).
11
+ # The browser picks the first <source> format it supports, so avif is
12
+ # served to modern browsers, webp as a fallback for avif, png for
13
+ # transparency, and jpg as the universal <img> fallback.
14
+ DEFAULT_FORMATS = %w[avif webp png jpg].freeze
15
+ DEFAULT_SIZES = { "sm" => 400, "md" => 800, "lg" => 1200, "xl" => 2000 }.freeze
16
+ DEFAULT_QUALITY = 85
17
+ # Ordered by speed (see docs/providers.md benchmark):
18
+ # sharp (14s) → libvips (22s) → imagemagick (31s) → imgproxy (31s) → weserv (30s) → flyimg
19
+ DEFAULT_BACKEND_PRIORITY = %w[sharp libvips imagemagick imgproxy weserv flyimg].freeze
20
+ # Format used for the <img> fallback in <picture> elements.
21
+ # All other formats in `formats` get <source> tags for browsers that support them.
22
+ DEFAULT_FALLBACK_FORMAT = "jpg"
23
+
5
24
  attr_reader :site, :originals, :output, :input_formats, :sizes, :formats,
6
25
  :quality, :backend_priority, :imgproxy_url,
7
26
  :image_compressor_url, :weserv_url, :flyimg_url,
8
- :optimize_qualities
27
+ :optimize_qualities, :fallback_format
9
28
 
10
29
  def initialize(site)
11
30
  shared = site.config["shared_images_configs"] || {}
@@ -13,20 +32,14 @@ module JekyllImgFlow
13
32
  cfg = shared.merge(overrides)
14
33
 
15
34
  @site = site
16
- @originals = cfg["originals"]
17
- @output = cfg["output"]
18
- @input_formats = cfg["input_formats"]
19
- @sizes = cfg["sizes"]
20
- @formats = cfg["formats"]
21
- @quality = cfg["quality"]
22
- @backend_priority = cfg["backend_priority"]
23
-
24
- # Validate required config fields
25
- raise ArgumentError, "No originals configured in unified config" unless @originals
26
- raise ArgumentError, "No output configured in unified config" unless @output
27
- raise ArgumentError, "No input_formats configured in unified config" unless @input_formats
28
- raise ArgumentError, "No formats configured in unified config" unless @formats
29
- raise ArgumentError, "No sizes configured in unified config" unless @sizes
35
+ @originals = cfg["originals"] || DEFAULT_ORIGINALS
36
+ @output = cfg["output"] || DEFAULT_OUTPUT
37
+ @input_formats = cfg["input_formats"] || DEFAULT_INPUT_FORMATS
38
+ @sizes = cfg["sizes"] || DEFAULT_SIZES
39
+ @formats = cfg["formats"] || DEFAULT_FORMATS
40
+ @quality = cfg["quality"] || DEFAULT_QUALITY
41
+ @backend_priority = cfg["backend_priority"] || DEFAULT_BACKEND_PRIORITY
42
+ @fallback_format = cfg["fallback_format"] || DEFAULT_FALLBACK_FORMAT
30
43
 
31
44
  @imgproxy_url = cfg["imgproxy_url"]
32
45
  @image_compressor_url = cfg["image_compressor_url"]
@@ -58,13 +58,13 @@ module JekyllImgFlow
58
58
 
59
59
  private
60
60
 
61
- # Get fallback formats from config or use sensible defaults
61
+ # Get fallback formats only the configured fallback format (e.g. jpg).
62
+ # These are skipped in the <source> loop and used for the <img> fallback.
63
+ # All other formats (webp, avif, etc.) get <source> tags.
62
64
  def fallback_formats
63
- if @config&.formats
64
- # Convert format names to extensions and add dots
65
- @config.formats.map { |fmt| ".#{fmt}" }
65
+ if @config&.fallback_format
66
+ [".#{@config.fallback_format}"]
66
67
  else
67
- # Fallback to common formats if no config available
68
68
  %w[.jpg .jpeg .png]
69
69
  end
70
70
  end
@@ -242,13 +242,40 @@ module JekyllImgFlow
242
242
  "<div#{parent_attrs}>#{html}</div>"
243
243
  end
244
244
 
245
- # Group results by file extension/format
245
+ # Group results by file extension, ordered by config format priority
246
+ # (avif → webp → png → jpg). The browser picks the first <source> format
247
+ # it supports, so the order of <source> tags determines format priority.
246
248
  def group_by_format(results)
247
- results.group_by { |r| File.extname(r).downcase }
249
+ grouped = results.group_by { |r| File.extname(r).downcase }
250
+ sort_format_groups(grouped)
248
251
  end
249
252
 
250
- # Find fallback image (prefer jpg/jpeg/png)
253
+ # Sort format groups by config format priority (first = highest priority).
254
+ # Formats not in config.formats are appended at the end alphabetically.
255
+ def sort_format_groups(grouped)
256
+ priority = build_format_priority
257
+ grouped.sort_by { |ext, _| priority.index(ext) || Float::INFINITY }
258
+ end
259
+
260
+ # Build a lookup of format extensions ordered by config.formats priority.
261
+ # E.g. ["avif", "webp", "png", "jpg"] → [".avif", ".webp", ".png", ".jpg"]
262
+ def build_format_priority
263
+ return %w[.avif .webp .png .jpg] unless @config&.formats
264
+
265
+ @config.formats.map { |fmt| ".#{fmt.downcase}" }
266
+ end
267
+
268
+ # Find fallback image — prefer the configured fallback format,
269
+ # then any legacy format (jpg/jpeg/png), then first result.
251
270
  def find_fallback_image(results)
271
+ return results.first if results.empty?
272
+
273
+ preferred = @config&.fallback_format
274
+ if preferred
275
+ found = results.find { |r| r.end_with?(".#{preferred}") }
276
+ return found if found
277
+ end
278
+
252
279
  results.find { |r| r.match?(/\.(jpe?g|png)$/i) } || results.first
253
280
  end
254
281
 
@@ -97,7 +97,7 @@ module Jekyll
97
97
  end
98
98
 
99
99
  filename = components[:filename_generator].generate_filename(input_path, params)
100
- output_path = components[:path_resolver].resolve_output_path(filename)
100
+ output_path = components[:path_resolver].resolve_source_output_path(filename)
101
101
 
102
102
  # Determine version type
103
103
  version_type = determine_version_type(params, components[:config])
@@ -114,7 +114,7 @@ module JekyllImgFlow
114
114
 
115
115
  # Get optimized directory path
116
116
  config = JekyllImgFlow::Config.new(@site)
117
- optimized_dir = File.join(@site.dest, config.output)
117
+ optimized_dir = File.join(@site.source, config.output)
118
118
 
119
119
  # Safety guard: prevent deletion of _site itself
120
120
  raise "Refusing to delete optimized directory: path resolves to site dest or is empty" if optimized_dir == @site.dest || optimized_dir.empty?
@@ -261,12 +261,7 @@ module JekyllImgFlow
261
261
  cleaned = []
262
262
 
263
263
  orphans.each do |orphan|
264
- output_file = if orphan["output"].start_with?("/")
265
- orphan["output"]
266
- else
267
- File.join(@site.dest,
268
- orphan["output"])
269
- end
264
+ output_file = File.join(@site.source, orphan["output"])
270
265
  if File.exist?(output_file)
271
266
  File.delete(output_file)
272
267
  cleaned << orphan["output"]
@@ -44,8 +44,8 @@ module JekyllImgFlow
44
44
 
45
45
  # Generate filename using FilenameGenerator (JPT compatible)
46
46
  filename = @filename_generator.generate_filename(input_path, params)
47
- # Write to _site during build (after Jekyll copies assets)
48
- actual_output_path = @path_resolver.resolve_output_path(filename)
47
+ # Write to source directory so Jekyll copies files to _site during write phase
48
+ actual_output_path = @path_resolver.resolve_source_output_path(filename)
49
49
 
50
50
  # Determine version type
51
51
  version_type = determine_version_type(params)
@@ -53,13 +53,23 @@ module JekyllImgFlow
53
53
  # Ensure output directory exists before processing
54
54
  FileUtils.mkdir_p(File.dirname(actual_output_path))
55
55
 
56
- # Process the operation (create the image)
57
- process_single_operation(type, input_path, actual_output_path, params)
56
+ # Animated GIFs must not be resized or converted — the operation
57
+ # would destroy the animation. Copy the original file as-is so the
58
+ # manifest can track it and HTML references stay valid.
59
+ if AnimatedGifDetector.animated?(input_path)
60
+ Jekyll.logger.warn "🖼️ ImgFlow: Skipping resize for animated GIF " \
61
+ "'#{original_name}' — copying original as-is to " \
62
+ "preserve animation."
63
+ FileUtils.cp(input_path, actual_output_path)
64
+ else
65
+ # Process the operation (create the image)
66
+ process_single_operation(type, input_path, actual_output_path, params)
67
+ end
58
68
 
59
69
  # Register in manifest
60
70
  if @manifest
61
- # Convert absolute path to relative for manifest storage
62
- relative_path = actual_output_path.sub(@path_resolver.site_dest, "")
71
+ # Store relative path (with leading /) for manifest storage
72
+ relative_path = "/#{@path_resolver.resolve_relative_output_path(filename)}"
63
73
 
64
74
  provider_name = @provider.class.provider_name
65
75
  @manifest.register_version(
@@ -73,9 +83,43 @@ module JekyllImgFlow
73
83
  )
74
84
  end
75
85
 
86
+ # Register as Jekyll static file so Jekyll copies it to _site during
87
+ # the write phase. Without this, files created during pre_render (or
88
+ # during render via imgflow tags) are not picked up by Jekyll's static
89
+ # file reader, which runs before pre_render. This would leave _site
90
+ # without optimized images until a second build.
91
+ register_jekyll_static_file(actual_output_path)
92
+
76
93
  actual_output_path
77
94
  end
78
95
 
96
+ # Register a generated file as a Jekyll::StaticFile so Jekyll copies it
97
+ # to _site during the write phase. No-op for mock/test sites.
98
+ # @param file_path [String] Absolute path to the generated file in source
99
+ def register_jekyll_static_file(file_path)
100
+ site = @config&.site
101
+ return unless site.is_a?(Jekyll::Site)
102
+
103
+ relative = file_path.delete_prefix("#{site.source}/")
104
+ return if relative == file_path # not under site source
105
+
106
+ add_static_file(site, relative) unless static_file_registered?(site, relative)
107
+ end
108
+
109
+ # @param site [Jekyll::Site] Jekyll site object
110
+ # @param relative [String] Path relative to site source
111
+ def static_file_registered?(site, relative)
112
+ site.static_files.any? { |sf| sf.relative_path == "/#{relative}" }
113
+ end
114
+
115
+ # @param site [Jekyll::Site] Jekyll site object
116
+ # @param relative [String] Path relative to site source
117
+ def add_static_file(site, relative)
118
+ site.static_files << Jekyll::StaticFile.new(
119
+ site, site.source, File.dirname(relative), File.basename(relative)
120
+ )
121
+ end
122
+
79
123
  # Process multiple operations on an image in sequence (batch)
80
124
  # @param operations [Array<Hash>] Array of operations to process
81
125
  # @param input_path [String] Path to input image
@@ -60,7 +60,6 @@ module JekyllImgFlow
60
60
  crop_height = opts[:calculated_height]
61
61
 
62
62
  operations << "g:sm"
63
- operations << "c:#{crop_width}:#{crop_height}"
64
63
  else
65
64
  # Use basic cropping with explicit coordinates
66
65
  if operation[:ratio]
@@ -77,8 +76,8 @@ module JekyllImgFlow
77
76
  # imgproxy crop format: c:width:height:gravity
78
77
  # Use nowe (north-west) gravity with x/y offsets for pixel-precise cropping
79
78
  operations << "g:nowe:#{crop_x}:#{crop_y}"
80
- operations << "c:#{crop_width}:#{crop_height}"
81
79
  end
80
+ operations << "c:#{crop_width}:#{crop_height}"
82
81
 
83
82
  when :quality
84
83
  imgproxy_quality = translate_quality_to_imgproxy(operation[:quality])
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JekyllImgFlow
4
- VERSION = "0.1.5"
4
+ VERSION = "0.1.7"
5
5
  end
@@ -12,6 +12,7 @@ require_relative "jekyll-imgflow/manifest_manager"
12
12
  require_relative "jekyll-imgflow/tag_scanner"
13
13
  require_relative "jekyll-imgflow/path_resolver"
14
14
  require_relative "jekyll-imgflow/filename_generator"
15
+ require_relative "jekyll-imgflow/animated_gif_detector"
15
16
  require_relative "jekyll-imgflow/operation_processor"
16
17
  require_relative "jekyll-imgflow/batch_manager"
17
18
  require_relative "jekyll-imgflow/preset_manager"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-imgflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Svend Gundestrup
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-07-29 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: fastimage
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '2.4'
18
+ version: 2.4.1
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '2.4'
25
+ version: 2.4.1
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: jekyll
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -43,154 +43,154 @@ dependencies:
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '0.9'
46
+ version: 0.9.3
47
47
  type: :development
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '0.9'
53
+ version: 0.9.3
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: rake
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '13.0'
60
+ version: 13.4.2
61
61
  type: :development
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: '13.0'
67
+ version: 13.4.2
68
68
  - !ruby/object:Gem::Dependency
69
69
  name: reek
70
70
  requirement: !ruby/object:Gem::Requirement
71
71
  requirements:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: '6.0'
74
+ version: 6.5.0
75
75
  type: :development
76
76
  prerelease: false
77
77
  version_requirements: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: '6.0'
81
+ version: 6.5.0
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: rspec
84
84
  requirement: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
- version: '3.13'
88
+ version: 3.13.2
89
89
  type: :development
90
90
  prerelease: false
91
91
  version_requirements: !ruby/object:Gem::Requirement
92
92
  requirements:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
- version: '3.13'
95
+ version: 3.13.2
96
96
  - !ruby/object:Gem::Dependency
97
97
  name: rubocop
98
98
  requirement: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
- version: '1.88'
102
+ version: 1.89.0
103
103
  type: :development
104
104
  prerelease: false
105
105
  version_requirements: !ruby/object:Gem::Requirement
106
106
  requirements:
107
107
  - - "~>"
108
108
  - !ruby/object:Gem::Version
109
- version: '1.88'
109
+ version: 1.89.0
110
110
  - !ruby/object:Gem::Dependency
111
111
  name: rubocop-markdown
112
112
  requirement: !ruby/object:Gem::Requirement
113
113
  requirements:
114
114
  - - "~>"
115
115
  - !ruby/object:Gem::Version
116
- version: '0.2'
116
+ version: 0.2.0
117
117
  type: :development
118
118
  prerelease: false
119
119
  version_requirements: !ruby/object:Gem::Requirement
120
120
  requirements:
121
121
  - - "~>"
122
122
  - !ruby/object:Gem::Version
123
- version: '0.2'
123
+ version: 0.2.0
124
124
  - !ruby/object:Gem::Dependency
125
125
  name: rubocop-performance
126
126
  requirement: !ruby/object:Gem::Requirement
127
127
  requirements:
128
128
  - - "~>"
129
129
  - !ruby/object:Gem::Version
130
- version: '1.26'
130
+ version: 1.26.1
131
131
  type: :development
132
132
  prerelease: false
133
133
  version_requirements: !ruby/object:Gem::Requirement
134
134
  requirements:
135
135
  - - "~>"
136
136
  - !ruby/object:Gem::Version
137
- version: '1.26'
137
+ version: 1.26.1
138
138
  - !ruby/object:Gem::Dependency
139
139
  name: rubocop-rake
140
140
  requirement: !ruby/object:Gem::Requirement
141
141
  requirements:
142
142
  - - "~>"
143
143
  - !ruby/object:Gem::Version
144
- version: '0.7'
144
+ version: 0.7.1
145
145
  type: :development
146
146
  prerelease: false
147
147
  version_requirements: !ruby/object:Gem::Requirement
148
148
  requirements:
149
149
  - - "~>"
150
150
  - !ruby/object:Gem::Version
151
- version: '0.7'
151
+ version: 0.7.1
152
152
  - !ruby/object:Gem::Dependency
153
153
  name: rubocop-rspec
154
154
  requirement: !ruby/object:Gem::Requirement
155
155
  requirements:
156
156
  - - "~>"
157
157
  - !ruby/object:Gem::Version
158
- version: '3.10'
158
+ version: 3.10.2
159
159
  type: :development
160
160
  prerelease: false
161
161
  version_requirements: !ruby/object:Gem::Requirement
162
162
  requirements:
163
163
  - - "~>"
164
164
  - !ruby/object:Gem::Version
165
- version: '3.10'
165
+ version: 3.10.2
166
166
  - !ruby/object:Gem::Dependency
167
167
  name: simplecov
168
168
  requirement: !ruby/object:Gem::Requirement
169
169
  requirements:
170
170
  - - "~>"
171
171
  - !ruby/object:Gem::Version
172
- version: '1.0'
172
+ version: 1.1.1
173
173
  type: :development
174
174
  prerelease: false
175
175
  version_requirements: !ruby/object:Gem::Requirement
176
176
  requirements:
177
177
  - - "~>"
178
178
  - !ruby/object:Gem::Version
179
- version: '1.0'
179
+ version: 1.1.1
180
180
  - !ruby/object:Gem::Dependency
181
181
  name: yard
182
182
  requirement: !ruby/object:Gem::Requirement
183
183
  requirements:
184
184
  - - "~>"
185
185
  - !ruby/object:Gem::Version
186
- version: '0.9'
186
+ version: 0.9.45
187
187
  type: :development
188
188
  prerelease: false
189
189
  version_requirements: !ruby/object:Gem::Requirement
190
190
  requirements:
191
191
  - - "~>"
192
192
  - !ruby/object:Gem::Version
193
- version: '0.9'
193
+ version: 0.9.45
194
194
  description: ImgFlow provides automatic image optimization for Jekyll with support
195
195
  for multiple providers (Sharp, Imgproxy, ImageMagick, LibVips), multiple formats
196
196
  (AVIF, WebP, PNG, JPG), and responsive image generation with modern picture tags.
@@ -202,6 +202,7 @@ files:
202
202
  - LICENSE
203
203
  - README.md
204
204
  - lib/jekyll-imgflow.rb
205
+ - lib/jekyll-imgflow/animated_gif_detector.rb
205
206
  - lib/jekyll-imgflow/batch_manager.rb
206
207
  - lib/jekyll-imgflow/build_time_processor.rb
207
208
  - lib/jekyll-imgflow/config.rb
@@ -248,14 +249,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
248
249
  requirements:
249
250
  - - ">="
250
251
  - !ruby/object:Gem::Version
251
- version: 3.3.0
252
+ version: 3.4.0
252
253
  required_rubygems_version: !ruby/object:Gem::Requirement
253
254
  requirements:
254
255
  - - ">="
255
256
  - !ruby/object:Gem::Version
256
257
  version: '0'
257
258
  requirements: []
258
- rubygems_version: 3.6.2
259
+ rubygems_version: 3.6.9
259
260
  specification_version: 4
260
261
  summary: A modern, multi-provider, multi-format image optimization engine for Jekyll
261
262
  test_files: []