images-convert 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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +15 -0
  4. data/.github/PULL_REQUEST_TEMPLATE.md +25 -0
  5. data/.github/workflows/ci.yml +79 -0
  6. data/.github/workflows/release.yml +122 -0
  7. data/.gitignore +14 -0
  8. data/CHANGELOG.md +146 -0
  9. data/Gemfile +9 -0
  10. data/LICENSE +41 -0
  11. data/README.md +378 -0
  12. data/RELEASE_NOTES_v0.2.12.md +66 -0
  13. data/RELEASE_NOTES_v0.2.3.md +19 -0
  14. data/RELEASE_NOTES_v0.3.0.md +66 -0
  15. data/RELEASE_NOTES_v0.4.0.md +14 -0
  16. data/RELEASE_NOTES_v0.4.1.md +13 -0
  17. data/Rakefile +13 -0
  18. data/bin/images-convert +8 -0
  19. data/bin/imgconv +8 -0
  20. data/images-convert.gemspec +39 -0
  21. data/lib/images_convert/cleanup.rb +31 -0
  22. data/lib/images_convert/configuration.rb +303 -0
  23. data/lib/images_convert/mini_magick_stub.rb +121 -0
  24. data/lib/images_convert/version.rb +5 -0
  25. data/lib/images_convert/waifu2x_test_stub.rb +93 -0
  26. data/lib/images_convert.rb +1557 -0
  27. data/lib/rubygems_plugin.rb +34 -0
  28. data/lib/waifu2x/downloader.rb +89 -0
  29. data/lib/waifu2x/pdf_builder.rb +105 -0
  30. data/lib/waifu2x/processor.rb +301 -0
  31. data/lib/waifu2x/setup.rb +127 -0
  32. data/lib/waifu2x/version.rb +5 -0
  33. data/lib/waifu2x.rb +221 -0
  34. data/test/images/autumn.jpg +0 -0
  35. data/test/images/spring.jpg +0 -0
  36. data/test/images/summer.jpg +0 -0
  37. data/test/images/winter.jpg +0 -0
  38. data/test/support/waifu2x_test_stub.rb +91 -0
  39. data/test/test_config.rb +143 -0
  40. data/test/test_fixtures.rb +144 -0
  41. data/test/test_formats.rb +213 -0
  42. data/test/test_help.rb +33 -0
  43. data/test/test_helper.rb +17 -0
  44. data/test/test_helper_mini_magick_stub.rb +4 -0
  45. data/test/test_selection.rb +142 -0
  46. data/test/test_version.rb +16 -0
  47. data/test/test_waifu2x.rb +81 -0
  48. metadata +179 -0
@@ -0,0 +1,303 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'fileutils'
5
+
6
+ module ImagesConvert
7
+ class Configuration
8
+ CONFIG_FILE = File.expand_path('~/.images-convert/config.yml')
9
+
10
+ def self.load
11
+ @config ||= begin
12
+ if File.exist?(CONFIG_FILE)
13
+ YAML.safe_load(File.read(CONFIG_FILE)) || {}
14
+ else
15
+ # 設定ファイルが存在しない場合はデフォルト設定で作成
16
+ create_default_config
17
+ end
18
+ end
19
+ end
20
+
21
+ def self.create_default_config
22
+ default_config = {
23
+ 'defaults' => {
24
+ 'from' => 'HEIC',
25
+ 'to' => 'JPG',
26
+ 'resize' => '1920x'
27
+ },
28
+ 'directories' => {
29
+ 'input' => File.expand_path('~/Downloads'),
30
+ 'output' => File.expand_path('~/Desktop')
31
+ },
32
+ 'selection' => {
33
+ 'latest_mode' => 'mtime'
34
+ },
35
+ 'waifu2x' => {
36
+ 'scale' => 2,
37
+ 'noise' => 1,
38
+ 'model' => 'photo',
39
+ 'auto_scale_a4' => true,
40
+ 'a4_print' => true,
41
+ 'image_format' => 'webp',
42
+ 'on_error' => 'stop'
43
+ },
44
+ 'output' => {
45
+ 'image_quality' => 90,
46
+ 'pdf_quality' => 90,
47
+ 'pdf_compression' => 'jpeg',
48
+ 'pdf_density' => 300
49
+ }
50
+ }
51
+
52
+ FileUtils.mkdir_p(File.dirname(CONFIG_FILE))
53
+ File.write(CONFIG_FILE, default_config.to_yaml)
54
+
55
+ # 初回メッセージ表示(重複防止のため、configコマンドでの表示を抑制)
56
+ @first_run_message_displayed = true
57
+ puts "設定ファイルを作成しました: #{CONFIG_FILE}"
58
+ puts "デフォルト設定:"
59
+ puts " 入力形式: #{default_config['defaults']['from']}"
60
+ puts " 出力形式: #{default_config['defaults']['to']}"
61
+ puts " リサイズ: #{default_config['defaults']['resize']}"
62
+ puts " 入力ディレクトリ: #{default_config['directories']['input']}"
63
+ puts " 出力ディレクトリ: #{default_config['directories']['output']}"
64
+ puts " 最新判定モード: #{default_config['selection']['latest_mode']}"
65
+ puts " waifu2x 既定倍率: #{default_config['waifu2x']['scale']}"
66
+ puts " waifu2x ノイズ除去: #{default_config['waifu2x']['noise']}"
67
+ puts " waifu2x モデル: #{default_config['waifu2x']['model']}"
68
+ puts " 出力画像品質: #{default_config['output']['image_quality']}"
69
+ puts " PDF 品質: #{default_config['output']['pdf_quality']}"
70
+ puts " PDF DPI: #{default_config['output']['pdf_density']}"
71
+ puts ""
72
+ puts "設定を変更するには:"
73
+ puts " images-convert set FROM TO RESIZE (または: imgconv set ...)"
74
+ puts " images-convert set-dir INPUT_DIR OUTPUT_DIR (または: imgconv set-dir ...)"
75
+ puts " images-convert set-latest-mode MODE (mtime / exif)"
76
+ puts ""
77
+
78
+ default_config
79
+ end
80
+
81
+ def self.reset_to_defaults
82
+ FileUtils.rm_f(CONFIG_FILE)
83
+ @config = nil
84
+ create_default_config
85
+ end
86
+
87
+ def self.default_from
88
+ cfg = load
89
+ cfg.dig('defaults', 'from') || 'HEIC'
90
+ end
91
+
92
+ def self.default_to
93
+ cfg = load
94
+ cfg.dig('defaults', 'to') || 'JPG'
95
+ end
96
+
97
+ def self.default_resize
98
+ cfg = load
99
+ cfg.dig('defaults', 'resize') || '1920x'
100
+ end
101
+
102
+ def self.default_latest_mode
103
+ cfg = load
104
+ mode = cfg.dig('selection', 'latest_mode')
105
+ %w[mtime exif].include?(mode) ? mode : 'mtime'
106
+ end
107
+
108
+ def self.default_waifu2x_scale
109
+ cfg = load
110
+ (cfg.dig('waifu2x', 'scale') || 2).to_i
111
+ end
112
+
113
+ def self.default_waifu2x_noise
114
+ cfg = load
115
+ (cfg.dig('waifu2x', 'noise') || 1).to_i
116
+ end
117
+
118
+ def self.default_waifu2x_model
119
+ cfg = load
120
+ cfg.dig('waifu2x', 'model') || 'photo'
121
+ end
122
+
123
+ def self.default_waifu2x_auto_scale_a4
124
+ cfg = load
125
+ cfg.dig('waifu2x', 'auto_scale_a4').nil? ? true : cfg.dig('waifu2x', 'auto_scale_a4')
126
+ end
127
+
128
+ def self.default_waifu2x_a4_print
129
+ cfg = load
130
+ cfg.dig('waifu2x', 'a4_print').nil? ? true : cfg.dig('waifu2x', 'a4_print')
131
+ end
132
+
133
+ def self.default_waifu2x_image_format
134
+ cfg = load
135
+ cfg.dig('waifu2x', 'image_format') || 'webp'
136
+ end
137
+
138
+ def self.default_waifu2x_on_error
139
+ cfg = load
140
+ value = cfg.dig('waifu2x', 'on_error') || 'stop'
141
+ %w[skip stop].include?(value) ? value : 'stop'
142
+ end
143
+
144
+ def self.default_output_image_quality
145
+ cfg = load
146
+ quality = cfg.dig('output', 'image_quality')
147
+ return 90 if quality.nil?
148
+ q = quality.to_i
149
+ q = 100 if q > 100
150
+ q = 1 if q < 1
151
+ q
152
+ end
153
+
154
+ def self.default_output_pdf_quality
155
+ cfg = load
156
+ quality = cfg.dig('output', 'pdf_quality')
157
+ return 90 if quality.nil?
158
+ q = quality.to_i
159
+ q = 100 if q > 100
160
+ q = 1 if q < 1
161
+ q
162
+ end
163
+
164
+ def self.default_output_pdf_compression
165
+ cfg = load
166
+ value = cfg.dig('output', 'pdf_compression')
167
+ return 'jpeg' if value.nil? || value.to_s.strip.empty?
168
+ normalized = value.to_s.strip.downcase
169
+ %w[jpeg jpeg2000 zip].include?(normalized) ? normalized : 'jpeg'
170
+ end
171
+
172
+ def self.default_output_pdf_density
173
+ cfg = load
174
+ density = cfg.dig('output', 'pdf_density')
175
+ return 300 if density.nil?
176
+ d = density.to_i
177
+ d = 1200 if d > 1200
178
+ d = 72 if d < 72
179
+ d
180
+ end
181
+
182
+ def self.default_input_dir
183
+ cfg = load
184
+ cfg.dig('directories', 'input') || File.expand_path('~/Downloads')
185
+ end
186
+
187
+ def self.default_output_dir
188
+ cfg = load
189
+ cfg.dig('directories', 'output') || File.expand_path('~/Desktop')
190
+ end
191
+
192
+ def self.save_defaults(from:, to:, resize:)
193
+ config = load
194
+ config['defaults'] ||= {}
195
+ config['defaults']['from'] = from
196
+ config['defaults']['to'] = to
197
+ config['defaults']['resize'] = resize
198
+
199
+ FileUtils.mkdir_p(File.dirname(CONFIG_FILE))
200
+ File.write(CONFIG_FILE, config.to_yaml)
201
+ # 変更を即時反映させるためキャッシュを無効化
202
+ @config = nil
203
+ end
204
+
205
+ def self.save_directories(input_dir:, output_dir:)
206
+ config = load
207
+ config['directories'] ||= {}
208
+ config['directories']['input'] = input_dir
209
+ config['directories']['output'] = output_dir
210
+
211
+ FileUtils.mkdir_p(File.dirname(CONFIG_FILE))
212
+ File.write(CONFIG_FILE, config.to_yaml)
213
+ # 変更を即時反映
214
+ @config = nil
215
+ end
216
+
217
+ def self.save_latest_mode(mode)
218
+ unless %w[mtime exif].include?(mode)
219
+ raise ArgumentError, "MODE には mtime または exif を指定してください。"
220
+ end
221
+
222
+ config = load
223
+ config['selection'] ||= {}
224
+ config['selection']['latest_mode'] = mode
225
+
226
+ FileUtils.mkdir_p(File.dirname(CONFIG_FILE))
227
+ File.write(CONFIG_FILE, config.to_yaml)
228
+ @config = nil
229
+ end
230
+
231
+ def self.save_waifu2x_settings(scale:, noise:, model:, auto_scale_a4:, a4_print:, image_format:, on_error:)
232
+ config = load
233
+ config['waifu2x'] ||= {}
234
+ config['waifu2x']['scale'] = scale
235
+ config['waifu2x']['noise'] = noise
236
+ config['waifu2x']['model'] = model
237
+ config['waifu2x']['auto_scale_a4'] = auto_scale_a4
238
+ config['waifu2x']['a4_print'] = a4_print
239
+ config['waifu2x']['image_format'] = image_format
240
+ config['waifu2x']['on_error'] = on_error
241
+
242
+ FileUtils.mkdir_p(File.dirname(CONFIG_FILE))
243
+ File.write(CONFIG_FILE, config.to_yaml)
244
+ @config = nil
245
+ end
246
+
247
+ def self.save_output_settings(image_quality:, pdf_quality:, pdf_compression: 'jpeg', pdf_density: 300)
248
+ config = load
249
+ config['output'] ||= {}
250
+ config['output']['image_quality'] = image_quality
251
+ config['output']['pdf_quality'] = pdf_quality
252
+ config['output']['pdf_compression'] = pdf_compression
253
+ config['output']['pdf_density'] = pdf_density
254
+
255
+ FileUtils.mkdir_p(File.dirname(CONFIG_FILE))
256
+ File.write(CONFIG_FILE, config.to_yaml)
257
+ @config = nil
258
+ end
259
+
260
+ def self.show_all_settings
261
+ config = load
262
+ {
263
+ defaults: {
264
+ from: default_from,
265
+ to: default_to,
266
+ resize: default_resize
267
+ },
268
+ directories: {
269
+ input: default_input_dir,
270
+ output: default_output_dir
271
+ },
272
+ selection: {
273
+ latest_mode: default_latest_mode
274
+ },
275
+ output: {
276
+ image_quality: default_output_image_quality,
277
+ pdf_quality: default_output_pdf_quality,
278
+ pdf_compression: default_output_pdf_compression,
279
+ pdf_density: default_output_pdf_density
280
+ },
281
+ waifu2x: {
282
+ scale: default_waifu2x_scale,
283
+ noise: default_waifu2x_noise,
284
+ model: default_waifu2x_model,
285
+ auto_scale_a4: default_waifu2x_auto_scale_a4,
286
+ a4_print: default_waifu2x_a4_print,
287
+ image_format: default_waifu2x_image_format,
288
+ on_error: default_waifu2x_on_error
289
+ }
290
+ }
291
+ end
292
+
293
+ def self.first_run_message_displayed?
294
+ @first_run_message_displayed ||= false
295
+ end
296
+
297
+ private
298
+
299
+ def self.config
300
+ @config
301
+ end
302
+ end
303
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'yaml'
5
+
6
+ module MiniMagick
7
+ class Image
8
+ attr_reader :source_path, :width, :height, :quality_value
9
+
10
+ class << self
11
+ def stub_exif_store
12
+ @stub_exif_store ||= {}
13
+ end
14
+
15
+ def stub_dimension_store
16
+ @stub_dimension_store ||= {}
17
+ end
18
+
19
+ def set_stub_exif(path, data)
20
+ normalized = data.transform_keys(&:to_s)
21
+ stub_exif_store[path] = normalized
22
+ File.write(sidecar_path(path), normalized.to_yaml)
23
+ end
24
+
25
+ def set_stub_dimensions(path, width:, height:)
26
+ stub_dimension_store[path] = { width: width.to_i, height: height.to_i }
27
+ end
28
+
29
+ def reset_stub!
30
+ @stub_exif_store = {}
31
+ @stub_dimension_store = {}
32
+ end
33
+
34
+ def sidecar_path(path)
35
+ "#{path}.exif"
36
+ end
37
+
38
+ def dimension_sidecar_path(path)
39
+ "#{path}.dims"
40
+ end
41
+
42
+ def ping(path)
43
+ new(path)
44
+ end
45
+ end
46
+
47
+ def self.open(path)
48
+ unless File.exist?(path)
49
+ raise Errno::ENOENT, "No such file or directory - #{path}"
50
+ end
51
+ new(path)
52
+ end
53
+
54
+ def initialize(path)
55
+ @source_path = path
56
+ @resize_value = nil
57
+ @format = File.extname(path).delete('.').to_s.downcase
58
+ @quality_value = nil
59
+ dims = self.class.stub_dimension_store[path]
60
+ unless dims
61
+ dim_sidecar = self.class.dimension_sidecar_path(path)
62
+ if File.exist?(dim_sidecar)
63
+ begin
64
+ loaded = YAML.safe_load(File.read(dim_sidecar)) || {}
65
+ width = loaded['width'] || loaded[:width]
66
+ height = loaded['height'] || loaded[:height]
67
+ dims = { width: width.to_i, height: height.to_i }
68
+ self.class.stub_dimension_store[path] = dims
69
+ rescue StandardError
70
+ dims = nil
71
+ end
72
+ end
73
+ end
74
+ @width = dims ? dims[:width].to_i : 0
75
+ @height = dims ? dims[:height].to_i : 0
76
+ end
77
+
78
+ def resize(value)
79
+ @resize_value = value
80
+ end
81
+
82
+ def format(value)
83
+ @format = value.to_s.downcase
84
+ end
85
+
86
+ def quality(value)
87
+ @quality_value = value.to_i
88
+ end
89
+
90
+ def write(destination)
91
+ FileUtils.mkdir_p(File.dirname(destination))
92
+ File.open(destination, 'wb') do |f|
93
+ f.write("mini_magick_stub\n")
94
+ f.write("source: #{@source_path}\n")
95
+ f.write("resize: #{@resize_value}\n")
96
+ f.write("format: #{@format}\n")
97
+ f.write("quality: #{@quality_value}\n") if @quality_value
98
+ end
99
+ end
100
+
101
+ def [](key)
102
+ data = self.class.stub_exif_store[@source_path]
103
+ unless data
104
+ sidecar = self.class.sidecar_path(@source_path)
105
+ if File.exist?(sidecar)
106
+ begin
107
+ loaded = YAML.safe_load(File.read(sidecar)) || {}
108
+ data = loaded.transform_keys(&:to_s)
109
+ self.class.stub_exif_store[@source_path] = data
110
+ rescue StandardError
111
+ data = {}
112
+ end
113
+ else
114
+ data = {}
115
+ end
116
+ end
117
+
118
+ data[key.to_s]
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ImagesConvert
4
+ VERSION = "0.4.1"
5
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'fileutils'
5
+ require 'tmpdir'
6
+
7
+ module Waifu2x
8
+ module TestInstrumentation
9
+ class << self
10
+ def enabled?
11
+ path = ENV['IMGCONV_TEST_WAIFU2X_CALL_LOG']
12
+ path && !path.empty?
13
+ end
14
+
15
+ def log(payload)
16
+ path = ENV['IMGCONV_TEST_WAIFU2X_CALL_LOG']
17
+ return unless path && !path.empty?
18
+
19
+ FileUtils.mkdir_p(File.dirname(path))
20
+ File.open(path, 'a') do |f|
21
+ f.write(JSON.dump(payload))
22
+ f.write("\n")
23
+ end
24
+ end
25
+
26
+ def fake_paths
27
+ dir = ENV['IMGCONV_TEST_WAIFU2X_FAKE_ROOT'] || Dir.tmpdir
28
+ {
29
+ bin: File.expand_path('waifu2x-ncnn-vulkan', dir),
30
+ models: File.expand_path('waifu2x-models', dir)
31
+ }
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ if defined?(Waifu2x::Processor)
38
+ class << Waifu2x::Processor
39
+ unless method_defined?(:process_images_without_test_stub)
40
+ alias_method :process_images_without_test_stub, :process_images
41
+ end
42
+
43
+ def process_images(input_dir, output_dir, **options)
44
+ if Waifu2x::TestInstrumentation.enabled?
45
+ FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
46
+ format = options[:image_format].to_s
47
+ format = 'png' if format.empty?
48
+ stub_path = File.join(output_dir, "waifu2x_stub_output.#{format}")
49
+ File.write(stub_path, 'waifu2x-stub')
50
+ Waifu2x::TestInstrumentation.log({ input_dir: input_dir, output_dir: output_dir, options: options })
51
+ return
52
+ end
53
+
54
+ process_images_without_test_stub(input_dir, output_dir, **options)
55
+ end
56
+ end
57
+ end
58
+
59
+ if defined?(Waifu2x::Setup)
60
+ class << Waifu2x::Setup
61
+ unless method_defined?(:resolve_paths_without_test_stub)
62
+ alias_method :resolve_paths_without_test_stub, :resolve_paths
63
+ end
64
+
65
+ def resolve_paths(**options)
66
+ if Waifu2x::TestInstrumentation.enabled?
67
+ fake = Waifu2x::TestInstrumentation.fake_paths
68
+ return { bin: fake[:bin], models: fake[:models] }
69
+ end
70
+
71
+ resolve_paths_without_test_stub(**options)
72
+ end
73
+ end
74
+ end
75
+
76
+ if defined?(Waifu2x::PDFBuilder)
77
+ class << Waifu2x::PDFBuilder
78
+ unless method_defined?(:create_pdf_without_test_stub)
79
+ alias_method :create_pdf_without_test_stub, :create_pdf
80
+ end
81
+
82
+ def create_pdf(image_paths, output_path, options = {})
83
+ if Waifu2x::TestInstrumentation.enabled?
84
+ FileUtils.mkdir_p(File.dirname(output_path))
85
+ File.write(output_path, "waifu2x-pdf-stub\n")
86
+ Waifu2x::TestInstrumentation.log({ pdf_output: output_path, images: image_paths, options: options })
87
+ return
88
+ end
89
+
90
+ create_pdf_without_test_stub(image_paths, output_path, options)
91
+ end
92
+ end
93
+ end