jekyll-imgflow 0.3.3 → 0.4.1
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 +4 -4
- data/README.md +23 -0
- data/lib/jekyll-imgflow/build_time_processor.rb +42 -29
- data/lib/jekyll-imgflow/config.rb +15 -8
- data/lib/jekyll-imgflow/modal_assets.rb +12 -6
- data/lib/jekyll-imgflow/parser.rb +32 -25
- data/lib/jekyll-imgflow/picture_tag_preset_migrator.rb +73 -69
- data/lib/jekyll-imgflow/preset_manager.rb +48 -38
- data/lib/jekyll-imgflow/providers/base_provider.rb +216 -11
- data/lib/jekyll-imgflow/providers/flyimg.rb +71 -141
- data/lib/jekyll-imgflow/providers/imagemagick.rb +57 -97
- data/lib/jekyll-imgflow/providers/imgproxy.rb +53 -126
- data/lib/jekyll-imgflow/providers/libvips.rb +53 -81
- data/lib/jekyll-imgflow/providers/sharp.rb +57 -100
- data/lib/jekyll-imgflow/providers/weserv.rb +69 -124
- data/lib/jekyll-imgflow/tag_scanner.rb +19 -24
- data/lib/jekyll-imgflow/tags/base_tag.rb +33 -0
- data/lib/jekyll-imgflow/tags/crop_tag.rb +24 -47
- data/lib/jekyll-imgflow/tags/opacity_tag.rb +1 -20
- data/lib/jekyll-imgflow/tags/optimize_tag.rb +0 -9
- data/lib/jekyll-imgflow/tags/quality_tag.rb +0 -11
- data/lib/jekyll-imgflow/tags/resize_tag.rb +30 -23
- data/lib/jekyll-imgflow/tags/watermark_tag.rb +0 -17
- data/lib/jekyll-imgflow/version.rb +1 -1
- metadata +1 -1
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "open3"
|
|
4
4
|
require "pathname"
|
|
5
|
+
require "fileutils"
|
|
5
6
|
|
|
6
7
|
module JekyllImgFlow
|
|
7
8
|
module Providers
|
|
@@ -10,6 +11,15 @@ module JekyllImgFlow
|
|
|
10
11
|
# Valid smartcrop position values
|
|
11
12
|
SMARTCROP_POSITIONS = %w[attention entropy center centre].freeze
|
|
12
13
|
|
|
14
|
+
# Short compass → provider position mapping used by HTTP providers.
|
|
15
|
+
COMPASS_TO_SHORT = {
|
|
16
|
+
"northwest" => "tl",
|
|
17
|
+
"northeast" => "tr",
|
|
18
|
+
"southwest" => "bl",
|
|
19
|
+
"southeast" => "br",
|
|
20
|
+
"center" => "c"
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
13
23
|
attr_accessor :config
|
|
14
24
|
|
|
15
25
|
def initialize(config = {})
|
|
@@ -17,6 +27,91 @@ module JekyllImgFlow
|
|
|
17
27
|
@operations = []
|
|
18
28
|
end
|
|
19
29
|
|
|
30
|
+
# Operation lookup helpers — used by all providers to avoid
|
|
31
|
+
# repeating `@operations.find { |op| op[:type] == :xxx }`.
|
|
32
|
+
def find_op(type)
|
|
33
|
+
@operations.find { |op| op[:type] == type }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def op?(type)
|
|
37
|
+
@operations.any? { |op| op[:type] == type }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Normalize a crop operation into a geometry hash.
|
|
41
|
+
# Returns { smartcrop:, keep:, x:, y:, width:, height: } where
|
|
42
|
+
# smartcrop is true when ratio + keep + valid smartcrop position.
|
|
43
|
+
def crop_geometry(operation)
|
|
44
|
+
opts = operation[:options] || {}
|
|
45
|
+
params = operation[:params] || {}
|
|
46
|
+
keep = opts[:keep] || params[:keep] || params[:position]
|
|
47
|
+
smartcrop = operation[:ratio] && keep && SMARTCROP_POSITIONS.include?(keep.to_s)
|
|
48
|
+
crop_coords(operation, opts).merge(smartcrop: smartcrop, keep: keep)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Coordinates for a crop operation: ratio crops use calculated values,
|
|
52
|
+
# explicit crops use x/y/width/height options (defaulting x/y to 0).
|
|
53
|
+
def crop_coords(operation, opts)
|
|
54
|
+
if operation[:ratio]
|
|
55
|
+
{ x: opts[:calculated_x], y: opts[:calculated_y],
|
|
56
|
+
width: opts[:calculated_width], height: opts[:calculated_height] }
|
|
57
|
+
else
|
|
58
|
+
{ x: opts[:x] || 0, y: opts[:y] || 0,
|
|
59
|
+
width: opts[:width], height: opts[:height] }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Extract watermark operation parts into a hash.
|
|
64
|
+
def watermark_parts(operation)
|
|
65
|
+
{
|
|
66
|
+
watermark_path: operation[:watermark_path],
|
|
67
|
+
position: operation[:options][:position],
|
|
68
|
+
opacity: operation[:options][:opacity]
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Shared compass → short position mapping (tl/tr/bl/br/c).
|
|
73
|
+
# HTTP providers (imgproxy, weserv, flyimg) all use this mapping.
|
|
74
|
+
def compass_to_short(position)
|
|
75
|
+
COMPASS_TO_SHORT[position.to_s] || position.to_s
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Input format extension (lowercased, no dot) for format preservation.
|
|
79
|
+
def input_format_ext(input_path)
|
|
80
|
+
File.extname(input_path).delete(".").downcase
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Convert a 0..1 opacity float to a 0..255 integer (used by all
|
|
84
|
+
# providers that encode alpha as an 8-bit value).
|
|
85
|
+
def alpha_byte_value(opacity)
|
|
86
|
+
(opacity * 255).round
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Generate a temporary file path by replacing the extension of
|
|
90
|
+
# +input_path+ with +suffix+ (e.g. temp_path("a.jpg", "tmp_base.jpg")
|
|
91
|
+
# → "a.tmp_base.jpg").
|
|
92
|
+
def temp_path(input_path, suffix)
|
|
93
|
+
input_path.gsub(/\.[^.]+$/, ".#{suffix}")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Map a smartcrop +keep+ value to the libvips interestingness string
|
|
97
|
+
# used by Sharp and LibVips smartcrop/thumbnail --crop operations.
|
|
98
|
+
def smartcrop_interestingness(keep)
|
|
99
|
+
case keep.to_s
|
|
100
|
+
when "entropy" then "entropy"
|
|
101
|
+
when "center", "centre" then "centre"
|
|
102
|
+
else "attention"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Check whether any of the given CLI commands is available on PATH.
|
|
107
|
+
# Used by CLI providers (sharp, libvips, imagemagick).
|
|
108
|
+
def cli_available?(*commands)
|
|
109
|
+
commands.any? do |cmd|
|
|
110
|
+
_, _, status = Open3.capture3("which", cmd)
|
|
111
|
+
status.success?
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
20
115
|
# Check if this provider is available (must be implemented by subclasses)
|
|
21
116
|
def available?
|
|
22
117
|
# Default: not available unless subclass implements actual check
|
|
@@ -55,17 +150,7 @@ module JekyllImgFlow
|
|
|
55
150
|
|
|
56
151
|
# For HTTP providers, construct proper HTTP URL
|
|
57
152
|
site = @config.site
|
|
58
|
-
|
|
59
|
-
# Get the relative path from site source using Pathname
|
|
60
|
-
source_path = Pathname.new(site.source)
|
|
61
|
-
file_pathname = Pathname.new(file_path)
|
|
62
|
-
relative_path = if file_pathname.absolute? && file_path.to_s.start_with?(site.source)
|
|
63
|
-
file_pathname.relative_path_from(source_path).to_s
|
|
64
|
-
elsif file_path.start_with?("/")
|
|
65
|
-
file_path[1..]
|
|
66
|
-
else
|
|
67
|
-
file_path
|
|
68
|
-
end
|
|
153
|
+
relative_path = relative_file_path(file_path, site)
|
|
69
154
|
|
|
70
155
|
# URL-encode each path segment to handle spaces and special characters
|
|
71
156
|
encoded_path = relative_path.split("/").map do |segment|
|
|
@@ -81,6 +166,18 @@ module JekyllImgFlow
|
|
|
81
166
|
"#{base_url}#{baseurl}/#{encoded_path}"
|
|
82
167
|
end
|
|
83
168
|
|
|
169
|
+
# Get the relative path from site source using Pathname
|
|
170
|
+
def relative_file_path(file_path, site)
|
|
171
|
+
file_pathname = Pathname.new(file_path)
|
|
172
|
+
if file_pathname.absolute? && file_path.to_s.start_with?(site.source)
|
|
173
|
+
file_pathname.relative_path_from(Pathname.new(site.source)).to_s
|
|
174
|
+
elsif file_path.start_with?("/")
|
|
175
|
+
file_path[1..]
|
|
176
|
+
else
|
|
177
|
+
file_path
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
84
181
|
# Operation collection methods - these should NOT execute immediately
|
|
85
182
|
def resize(width, height, options = {})
|
|
86
183
|
@operations << { type: :resize, width: width, height: height,
|
|
@@ -206,5 +303,113 @@ module JekyllImgFlow
|
|
|
206
303
|
end
|
|
207
304
|
end
|
|
208
305
|
end
|
|
306
|
+
|
|
307
|
+
# Shared base for HTTP-based providers (imgproxy, weserv, flyimg).
|
|
308
|
+
# Subclasses must implement `service_url`, `build_combined_url`, and
|
|
309
|
+
# define a `TIMEOUT` constant. They may optionally override
|
|
310
|
+
# `provider_label` for error messages.
|
|
311
|
+
class HttpBase < BaseProvider
|
|
312
|
+
def available?
|
|
313
|
+
url = service_url
|
|
314
|
+
return false unless url
|
|
315
|
+
|
|
316
|
+
check_http_service(url)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def execute(input_path, output_path)
|
|
320
|
+
return if @operations.empty?
|
|
321
|
+
|
|
322
|
+
url = build_combined_url(input_path)
|
|
323
|
+
fetch_and_save(url, output_path)
|
|
324
|
+
output_path
|
|
325
|
+
ensure
|
|
326
|
+
reset_operations
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
protected
|
|
330
|
+
|
|
331
|
+
# Subclasses override to return the configured service URL or nil.
|
|
332
|
+
def service_url
|
|
333
|
+
raise NotImplementedError
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# Subclasses override to build the full request URL.
|
|
337
|
+
def build_combined_url(_input_path)
|
|
338
|
+
raise NotImplementedError
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Label used in error messages (defaults to class name).
|
|
342
|
+
def provider_label
|
|
343
|
+
self.class.name.split("::").last
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def timeout
|
|
347
|
+
self.class::TIMEOUT
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def fetch_and_save(url, output_path)
|
|
351
|
+
response = fetch_with_timeout(url)
|
|
352
|
+
raise "#{provider_label} request failed" if response.empty?
|
|
353
|
+
|
|
354
|
+
FileUtils.mkdir_p(File.dirname(output_path))
|
|
355
|
+
File.write(output_path, response)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def fetch_with_timeout(url)
|
|
359
|
+
uri = URI(url)
|
|
360
|
+
Net::HTTP.start(uri.host, uri.port,
|
|
361
|
+
use_ssl: uri.scheme == "https",
|
|
362
|
+
open_timeout: timeout,
|
|
363
|
+
read_timeout: timeout) do |http|
|
|
364
|
+
request = Net::HTTP::Get.new(uri)
|
|
365
|
+
response = http.request(request)
|
|
366
|
+
|
|
367
|
+
raise "HTTP #{response.code}: #{response.message}" unless response.is_a?(Net::HTTPSuccess)
|
|
368
|
+
|
|
369
|
+
response.body
|
|
370
|
+
end
|
|
371
|
+
rescue StandardError => e
|
|
372
|
+
raise "#{provider_label} request failed: #{e.message}"
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# Shared base for CLI-based providers (sharp, imagemagick, libvips).
|
|
377
|
+
# Subclasses implement `build_commands(input_path, output_path)` to
|
|
378
|
+
# return an array of command entries (strings or arrays), and
|
|
379
|
+
# `run_command(cmd)` to execute a single entry.
|
|
380
|
+
class CliBase < BaseProvider
|
|
381
|
+
def execute(input_path, output_path)
|
|
382
|
+
return if @operations.empty?
|
|
383
|
+
|
|
384
|
+
before_execute(input_path)
|
|
385
|
+
commands = build_commands(input_path, output_path)
|
|
386
|
+
Array(commands).each { |cmd| run_command(cmd) }
|
|
387
|
+
output_path
|
|
388
|
+
ensure
|
|
389
|
+
reset_operations
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
protected
|
|
393
|
+
|
|
394
|
+
# Hook for subclasses to perform pre-execution work (e.g. SVG warnings).
|
|
395
|
+
def before_execute(_input_path); end
|
|
396
|
+
|
|
397
|
+
# Subclasses override to return command(s) to execute.
|
|
398
|
+
# May return a single command or an array of commands.
|
|
399
|
+
def build_commands(_input_path, _output_path)
|
|
400
|
+
raise NotImplementedError
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
# Execute a single command. Default uses the shell-based
|
|
404
|
+
# `execute_command` from BaseProvider. Subclasses that use
|
|
405
|
+
# argument arrays (no shell) should override this.
|
|
406
|
+
def run_command(cmd)
|
|
407
|
+
if cmd.is_a?(Array) && cmd.first == :cleanup
|
|
408
|
+
FileUtils.rm_f(cmd[1])
|
|
409
|
+
return
|
|
410
|
+
end
|
|
411
|
+
execute_command(cmd)
|
|
412
|
+
end
|
|
413
|
+
end
|
|
209
414
|
end
|
|
210
415
|
end
|
|
@@ -2,144 +2,98 @@
|
|
|
2
2
|
|
|
3
3
|
require "net/http"
|
|
4
4
|
require "uri"
|
|
5
|
-
require "tempfile"
|
|
6
5
|
require_relative "base_provider"
|
|
7
6
|
|
|
8
7
|
module JekyllImgFlow
|
|
9
8
|
module Providers
|
|
10
9
|
# Flyimg provider implementation using the standardized tag interface
|
|
11
|
-
class Flyimg <
|
|
10
|
+
class Flyimg < HttpBase
|
|
12
11
|
TIMEOUT = 60
|
|
13
12
|
|
|
14
|
-
def
|
|
15
|
-
|
|
16
|
-
return false unless @config.respond_to?(:flyimg_url) && @config.flyimg_url
|
|
17
|
-
|
|
18
|
-
check_http_service(@config.flyimg_url)
|
|
13
|
+
def service_url
|
|
14
|
+
@config.respond_to?(:flyimg_url) ? @config.flyimg_url : nil
|
|
19
15
|
end
|
|
20
16
|
|
|
21
|
-
def
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
# Build single Flyimg URL with all operations combined
|
|
25
|
-
url = build_combined_flyimg_url(input_path)
|
|
26
|
-
|
|
27
|
-
# Fetch the result in one request
|
|
28
|
-
fetch_and_save(url, output_path)
|
|
29
|
-
output_path
|
|
30
|
-
ensure
|
|
31
|
-
reset_operations
|
|
17
|
+
def build_combined_flyimg_url(input_path)
|
|
18
|
+
build_combined_url(input_path)
|
|
32
19
|
end
|
|
33
20
|
|
|
34
|
-
def
|
|
35
|
-
raise "flyimg_url not set" unless
|
|
21
|
+
def build_combined_url(input_path)
|
|
22
|
+
raise "flyimg_url not set" unless service_url
|
|
36
23
|
|
|
37
24
|
encoded_url = encode_file_url(input_path)
|
|
38
25
|
options = []
|
|
39
26
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
# c_1 forces the image to fill the exact width x height area.
|
|
46
|
-
# pns_0 allows upscaling when the source is smaller than the target
|
|
47
|
-
# (Flyimg's pns/preserve-natural-size defaults to 1, blocking enlarge).
|
|
48
|
-
options << "w_#{operation[:width]}"
|
|
49
|
-
options << "pns_0"
|
|
50
|
-
if operation[:height]
|
|
51
|
-
options << "h_#{operation[:height]}"
|
|
52
|
-
options << "c_1"
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
when :crop
|
|
56
|
-
opts = operation[:options]
|
|
57
|
-
params = operation[:params] || {}
|
|
58
|
-
|
|
59
|
-
# Check for keep parameter (smartcrop support)
|
|
60
|
-
keep = opts[:keep] || params[:keep] || params[:position]
|
|
61
|
-
|
|
62
|
-
if operation[:ratio] && keep && SMARTCROP_POSITIONS.include?(keep.to_s)
|
|
63
|
-
# Use smartcrop (Flyimg's smc option picks the best crop area)
|
|
64
|
-
crop_width = opts[:calculated_width]
|
|
65
|
-
crop_height = opts[:calculated_height]
|
|
66
|
-
|
|
67
|
-
options << "w_#{crop_width}"
|
|
68
|
-
options << "h_#{crop_height}"
|
|
69
|
-
options << "c_1"
|
|
70
|
-
options << "smc_1"
|
|
71
|
-
else
|
|
72
|
-
# Use basic cropping via extract (top-left / bottom-right coordinates)
|
|
73
|
-
if operation[:ratio]
|
|
74
|
-
crop_x = opts[:calculated_x]
|
|
75
|
-
crop_y = opts[:calculated_y]
|
|
76
|
-
crop_width = opts[:calculated_width]
|
|
77
|
-
crop_height = opts[:calculated_height]
|
|
78
|
-
else
|
|
79
|
-
crop_x = opts[:x]
|
|
80
|
-
crop_y = opts[:y]
|
|
81
|
-
crop_width = opts[:width]
|
|
82
|
-
crop_height = opts[:height]
|
|
83
|
-
end
|
|
84
|
-
options << "e_1"
|
|
85
|
-
options << "p1x_#{crop_x}"
|
|
86
|
-
options << "p1y_#{crop_y}"
|
|
87
|
-
options << "p2x_#{crop_x + crop_width}"
|
|
88
|
-
options << "p2y_#{crop_y + crop_height}"
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
when :quality
|
|
92
|
-
flyimg_quality = translate_quality_to_flyimg(operation[:quality])
|
|
93
|
-
options << "q_#{flyimg_quality}"
|
|
94
|
-
|
|
95
|
-
when :format
|
|
96
|
-
# Assume validated input from tags
|
|
97
|
-
options << "o_#{operation[:format]}"
|
|
98
|
-
|
|
99
|
-
when :watermark
|
|
100
|
-
watermark_path = operation[:watermark_path]
|
|
101
|
-
position = operation[:options][:position]
|
|
102
|
-
opacity = operation[:options][:opacity]
|
|
103
|
-
|
|
104
|
-
# Flyimg watermark parameters
|
|
105
|
-
encoded_watermark = encode_file_url(watermark_path)
|
|
106
|
-
position_param = translate_flyimg_position(position)
|
|
107
|
-
opacity_param = (opacity * 100).round
|
|
108
|
-
options << "wm_#{encoded_watermark}_#{position_param}_#{opacity_param}"
|
|
109
|
-
|
|
110
|
-
when :alpha_opacity
|
|
111
|
-
opacity = operation[:opacity]
|
|
112
|
-
options << "a_#{(opacity * 255).round}"
|
|
113
|
-
end
|
|
114
|
-
end
|
|
27
|
+
@operations.each { |operation| append_flyimg_operation(operation, options) }
|
|
28
|
+
|
|
29
|
+
preserve_input_format(input_path, options)
|
|
30
|
+
"#{service_url}/upload/#{options.join(',')}/#{encoded_url}"
|
|
31
|
+
end
|
|
115
32
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
options
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def append_flyimg_operation(operation, options)
|
|
36
|
+
case operation[:type]
|
|
37
|
+
when :resize
|
|
38
|
+
append_flyimg_resize(operation, options)
|
|
39
|
+
when :crop
|
|
40
|
+
append_flyimg_crop(operation, options)
|
|
41
|
+
when :quality
|
|
42
|
+
options << "q_#{operation[:quality]}"
|
|
43
|
+
when :format
|
|
44
|
+
options << "o_#{operation[:format]}"
|
|
45
|
+
when :watermark
|
|
46
|
+
append_flyimg_watermark(operation, options)
|
|
47
|
+
when :alpha_opacity
|
|
48
|
+
options << "a_#{alpha_byte_value(operation[:opacity])}"
|
|
122
49
|
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def append_flyimg_resize(operation, options)
|
|
53
|
+
# c_1 forces the image to fill the exact width x height area.
|
|
54
|
+
# pns_0 allows upscaling when the source is smaller than the target
|
|
55
|
+
# (Flyimg's pns/preserve-natural-size defaults to 1, blocking enlarge).
|
|
56
|
+
options << "w_#{operation[:width]}"
|
|
57
|
+
options << "pns_0"
|
|
58
|
+
return unless operation[:height]
|
|
123
59
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
"#{@config.flyimg_url}/upload/#{options_string}/#{encoded_url}"
|
|
60
|
+
options << "h_#{operation[:height]}"
|
|
61
|
+
options << "c_1"
|
|
127
62
|
end
|
|
128
63
|
|
|
129
|
-
|
|
64
|
+
def append_flyimg_crop(operation, options)
|
|
65
|
+
geo = crop_geometry(operation)
|
|
130
66
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
67
|
+
if geo[:smartcrop]
|
|
68
|
+
options << "w_#{geo[:width]}"
|
|
69
|
+
options << "h_#{geo[:height]}"
|
|
70
|
+
options << "c_1"
|
|
71
|
+
options << "smc_1"
|
|
72
|
+
else
|
|
73
|
+
# Use basic cropping via extract (top-left / bottom-right coordinates)
|
|
74
|
+
options << "e_1"
|
|
75
|
+
options << "p1x_#{geo[:x]}"
|
|
76
|
+
options << "p1y_#{geo[:y]}"
|
|
77
|
+
options << "p2x_#{geo[:x] + geo[:width]}"
|
|
78
|
+
options << "p2y_#{geo[:y] + geo[:height]}"
|
|
140
79
|
end
|
|
141
80
|
end
|
|
142
81
|
|
|
82
|
+
def append_flyimg_watermark(operation, options)
|
|
83
|
+
parts = watermark_parts(operation)
|
|
84
|
+
encoded_watermark = encode_file_url(parts[:watermark_path])
|
|
85
|
+
position_param = compass_to_short(parts[:position])
|
|
86
|
+
opacity_param = (parts[:opacity] * 100).round
|
|
87
|
+
options << "wm_#{encoded_watermark}_#{position_param}_#{opacity_param}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def preserve_input_format(input_path, options)
|
|
91
|
+
return if op?(:format)
|
|
92
|
+
|
|
93
|
+
ext = input_format_ext(input_path)
|
|
94
|
+
options << "o_#{map_format(ext)}" unless ext.empty?
|
|
95
|
+
end
|
|
96
|
+
|
|
143
97
|
def map_format(format)
|
|
144
98
|
case format&.downcase
|
|
145
99
|
when "jpeg", "jpg"
|
|
@@ -155,33 +109,9 @@ module JekyllImgFlow
|
|
|
155
109
|
end
|
|
156
110
|
end
|
|
157
111
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
FileUtils.mkdir_p(File.dirname(output_path))
|
|
163
|
-
File.write(output_path, response)
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def fetch_with_timeout(url)
|
|
167
|
-
uri = URI(url)
|
|
168
|
-
Net::HTTP.start(uri.host, uri.port,
|
|
169
|
-
use_ssl: uri.scheme == "https",
|
|
170
|
-
open_timeout: TIMEOUT,
|
|
171
|
-
read_timeout: TIMEOUT) do |http|
|
|
172
|
-
request = Net::HTTP::Get.new(uri)
|
|
173
|
-
response = http.request(request)
|
|
174
|
-
|
|
175
|
-
raise "HTTP #{response.code}: #{response.message}" unless response.is_a?(Net::HTTPSuccess)
|
|
176
|
-
|
|
177
|
-
response.body
|
|
178
|
-
end
|
|
179
|
-
rescue StandardError => e
|
|
180
|
-
raise "Flyimg request failed: #{e.message}"
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
def translate_quality_to_flyimg(quality)
|
|
184
|
-
quality
|
|
112
|
+
# Thin wrapper kept for test compatibility.
|
|
113
|
+
def translate_flyimg_position(position)
|
|
114
|
+
compass_to_short(position)
|
|
185
115
|
end
|
|
186
116
|
end
|
|
187
117
|
end
|