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.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +15 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +25 -0
- data/.github/workflows/ci.yml +79 -0
- data/.github/workflows/release.yml +122 -0
- data/.gitignore +14 -0
- data/CHANGELOG.md +146 -0
- data/Gemfile +9 -0
- data/LICENSE +41 -0
- data/README.md +378 -0
- data/RELEASE_NOTES_v0.2.12.md +66 -0
- data/RELEASE_NOTES_v0.2.3.md +19 -0
- data/RELEASE_NOTES_v0.3.0.md +66 -0
- data/RELEASE_NOTES_v0.4.0.md +14 -0
- data/RELEASE_NOTES_v0.4.1.md +13 -0
- data/Rakefile +13 -0
- data/bin/images-convert +8 -0
- data/bin/imgconv +8 -0
- data/images-convert.gemspec +39 -0
- data/lib/images_convert/cleanup.rb +31 -0
- data/lib/images_convert/configuration.rb +303 -0
- data/lib/images_convert/mini_magick_stub.rb +121 -0
- data/lib/images_convert/version.rb +5 -0
- data/lib/images_convert/waifu2x_test_stub.rb +93 -0
- data/lib/images_convert.rb +1557 -0
- data/lib/rubygems_plugin.rb +34 -0
- data/lib/waifu2x/downloader.rb +89 -0
- data/lib/waifu2x/pdf_builder.rb +105 -0
- data/lib/waifu2x/processor.rb +301 -0
- data/lib/waifu2x/setup.rb +127 -0
- data/lib/waifu2x/version.rb +5 -0
- data/lib/waifu2x.rb +221 -0
- data/test/images/autumn.jpg +0 -0
- data/test/images/spring.jpg +0 -0
- data/test/images/summer.jpg +0 -0
- data/test/images/winter.jpg +0 -0
- data/test/support/waifu2x_test_stub.rb +91 -0
- data/test/test_config.rb +143 -0
- data/test/test_fixtures.rb +144 -0
- data/test/test_formats.rb +213 -0
- data/test/test_help.rb +33 -0
- data/test/test_helper.rb +17 -0
- data/test/test_helper_mini_magick_stub.rb +4 -0
- data/test/test_selection.rb +142 -0
- data/test/test_version.rb +16 -0
- data/test/test_waifu2x.rb +81 -0
- metadata +179 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# RubyGems plugin hooks for automatic cleanup on gem uninstall
|
4
|
+
# This file is loaded by RubyGems when any gem command runs.
|
5
|
+
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
Gem.post_uninstall do |uninstaller|
|
9
|
+
begin
|
10
|
+
spec = uninstaller.respond_to?(:spec) ? uninstaller.spec : nil
|
11
|
+
next unless spec && spec.name == 'images-convert'
|
12
|
+
|
13
|
+
config_dir = File.expand_path('~/.images-convert')
|
14
|
+
next unless Dir.exist?(config_dir)
|
15
|
+
|
16
|
+
# Ask for confirmation interactively in TTY environments
|
17
|
+
confirm = 'y'
|
18
|
+
if $stdout.isatty && $stdin.isatty
|
19
|
+
puts "images-convert: 設定ディレクトリ (#{config_dir}) を削除しますか? [Y/n]"
|
20
|
+
print '> '
|
21
|
+
ans = ($stdin.gets || '').strip
|
22
|
+
confirm = ans.empty? ? 'y' : ans
|
23
|
+
end
|
24
|
+
|
25
|
+
if confirm.to_s.downcase.start_with?('y')
|
26
|
+
FileUtils.rm_rf(config_dir)
|
27
|
+
puts "images-convert: 設定ディレクトリを削除しました: #{config_dir}"
|
28
|
+
else
|
29
|
+
puts "images-convert: 設定ディレクトリの削除をスキップしました"
|
30
|
+
end
|
31
|
+
rescue => e
|
32
|
+
warn "images-convert: アンインストール後のクリーンアップに失敗しました: #{e.class}: #{e.message}"
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'tmpdir'
|
5
|
+
require 'open3'
|
6
|
+
|
7
|
+
module Waifu2x
|
8
|
+
module Downloader
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def download_and_install(download_url)
|
12
|
+
raise 'download_url が空です' if download_url.to_s.strip.empty?
|
13
|
+
|
14
|
+
cache_root = File.expand_path('~/.cache/waifu2x-pdf')
|
15
|
+
FileUtils.mkdir_p(cache_root)
|
16
|
+
|
17
|
+
Dir.mktmpdir('waifu2x-dl') do |tmp|
|
18
|
+
archive_path = File.join(tmp, File.basename(download_url))
|
19
|
+
|
20
|
+
run!(%W[curl -L -o #{archive_path} #{download_url}])
|
21
|
+
|
22
|
+
extract_dir = File.join(tmp, 'extract')
|
23
|
+
FileUtils.mkdir_p(extract_dir)
|
24
|
+
|
25
|
+
if archive_path =~ /\.zip\z/i
|
26
|
+
run!(%W[unzip -q #{archive_path} -d #{extract_dir}])
|
27
|
+
else
|
28
|
+
run!(%W[tar -xf #{archive_path} -C #{extract_dir}])
|
29
|
+
end
|
30
|
+
|
31
|
+
bin_path = nil
|
32
|
+
models_path = nil
|
33
|
+
|
34
|
+
Dir.glob(File.join(extract_dir, '**', 'waifu2x-ncnn-vulkan')).each do |p|
|
35
|
+
next unless File.file?(p)
|
36
|
+
bin_path = p
|
37
|
+
break
|
38
|
+
end
|
39
|
+
|
40
|
+
if bin_path
|
41
|
+
candidate = File.join(File.dirname(bin_path), 'models')
|
42
|
+
models_path = candidate if File.directory?(candidate)
|
43
|
+
end
|
44
|
+
|
45
|
+
if models_path.nil?
|
46
|
+
Dir.glob(File.join(extract_dir, '**', 'models')).each do |p|
|
47
|
+
next unless File.directory?(p)
|
48
|
+
models_path = p
|
49
|
+
break
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
raise 'ダウンロードしたアーカイブから waifu2x-ncnn-vulkan が見つかりません' unless bin_path
|
54
|
+
|
55
|
+
FileUtils.chmod('+x', bin_path)
|
56
|
+
|
57
|
+
install_dir = File.join(cache_root, 'waifu2x-ncnn-vulkan')
|
58
|
+
FileUtils.mkdir_p(install_dir)
|
59
|
+
|
60
|
+
unique = File.basename(archive_path).gsub(/[^a-zA-Z0-9_.-]/, '_')
|
61
|
+
target_dir = File.join(install_dir, unique)
|
62
|
+
FileUtils.mkdir_p(target_dir)
|
63
|
+
|
64
|
+
src_root = File.dirname(bin_path)
|
65
|
+
FileUtils.cp_r(Dir.glob(File.join(src_root, '*')), target_dir)
|
66
|
+
|
67
|
+
resolved_bin = File.join(target_dir, 'waifu2x-ncnn-vulkan')
|
68
|
+
resolved_models = File.directory?(File.join(target_dir, 'models')) ? File.join(target_dir, 'models') : models_path
|
69
|
+
|
70
|
+
current_link = File.join(install_dir, 'current')
|
71
|
+
begin
|
72
|
+
FileUtils.rm_f(current_link)
|
73
|
+
FileUtils.ln_s(target_dir, current_link)
|
74
|
+
rescue StandardError
|
75
|
+
end
|
76
|
+
|
77
|
+
{ bin: resolved_bin, models: resolved_models }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def run!(argv)
|
82
|
+
stdout_str, stderr_str, status = Open3.capture3(*argv)
|
83
|
+
unless status.success?
|
84
|
+
raise "Command failed (#{argv.join(' ')}): #{stderr_str}\n#{stdout_str}"
|
85
|
+
end
|
86
|
+
stdout_str
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'combine_pdf'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'tmpdir'
|
6
|
+
require 'shellwords'
|
7
|
+
|
8
|
+
module Waifu2x
|
9
|
+
class PDFBuilder
|
10
|
+
class << self
|
11
|
+
def create_pdf(image_paths, output_path, options = {})
|
12
|
+
return if image_paths.nil? || image_paths.empty?
|
13
|
+
|
14
|
+
Dir.mktmpdir('waifu2x-pdf-pages') do |pages_dir|
|
15
|
+
pdf_paths = images_to_single_pdfs(image_paths, pages_dir, options)
|
16
|
+
merge_pdfs(pdf_paths, output_path)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def images_to_single_pdfs(image_paths, output_dir, options = {})
|
21
|
+
FileUtils.mkdir_p(output_dir)
|
22
|
+
pdfs = []
|
23
|
+
|
24
|
+
compression = (options[:pdf_compression] || '').to_s.downcase
|
25
|
+
quality = options[:pdf_quality]
|
26
|
+
density = options[:pdf_density]
|
27
|
+
a4_print = options.key?(:a4_print) ? options[:a4_print] : true
|
28
|
+
|
29
|
+
tool = find_imagemagick_tool
|
30
|
+
raise 'ImageMagick CLI (magick or convert) が見つかりません' unless tool
|
31
|
+
|
32
|
+
image_paths.each_with_index do |path, idx|
|
33
|
+
base = format('%04d', idx + 1)
|
34
|
+
pdf_path = File.join(output_dir, "page_#{base}.pdf")
|
35
|
+
d = (density && density.to_i > 0) ? density.to_i : 300
|
36
|
+
compress_args = imagemagick_compress_args(compression: compression, quality: quality)
|
37
|
+
|
38
|
+
if a4_print
|
39
|
+
a4_width_in = 8.26771653543307
|
40
|
+
a4_height_in = 11.6929133858268
|
41
|
+
canvas_w = (a4_width_in * d).round
|
42
|
+
canvas_h = (a4_height_in * d).round
|
43
|
+
base_cmd = (tool == 'magick') ? ['magick', path] : ['convert', path]
|
44
|
+
cmd = base_cmd + [
|
45
|
+
'-define', 'png:ignore-crc=true',
|
46
|
+
'-auto-orient', '-strip', '-units', 'PixelsPerInch', '-density', d.to_s,
|
47
|
+
'-resize', "#{canvas_w}x#{canvas_h}>",
|
48
|
+
'-background', 'white', '-gravity', 'center', '-extent', "#{canvas_w}x#{canvas_h}"
|
49
|
+
]
|
50
|
+
cmd += compress_args
|
51
|
+
cmd += [pdf_path]
|
52
|
+
run_cmd!(cmd)
|
53
|
+
else
|
54
|
+
base_cmd = (tool == 'magick') ? ['magick', path] : ['convert', path]
|
55
|
+
cmd = base_cmd + ['-define', 'png:ignore-crc=true', '-auto-orient', '-strip', '-units', 'PixelsPerInch', '-density', d.to_s]
|
56
|
+
cmd += compress_args
|
57
|
+
cmd += [pdf_path]
|
58
|
+
run_cmd!(cmd)
|
59
|
+
end
|
60
|
+
|
61
|
+
pdfs << pdf_path
|
62
|
+
end
|
63
|
+
pdfs
|
64
|
+
end
|
65
|
+
|
66
|
+
def merge_pdfs(pdf_paths, output_path)
|
67
|
+
combined = CombinePDF.new
|
68
|
+
pdf_paths.each do |pdf|
|
69
|
+
combined << CombinePDF.load(pdf)
|
70
|
+
end
|
71
|
+
FileUtils.mkdir_p(File.dirname(output_path)) unless Dir.exist?(File.dirname(output_path))
|
72
|
+
combined.save(output_path)
|
73
|
+
end
|
74
|
+
|
75
|
+
def imagemagick_compress_args(compression:, quality: nil)
|
76
|
+
args = []
|
77
|
+
case compression
|
78
|
+
when 'jpeg'
|
79
|
+
args += ['-compress', 'JPEG']
|
80
|
+
when 'jpeg2000'
|
81
|
+
args += ['-compress', 'JPEG2000']
|
82
|
+
when 'zip'
|
83
|
+
args += ['-compress', 'Zip']
|
84
|
+
end
|
85
|
+
if quality && quality.to_i > 0
|
86
|
+
args += ['-quality', quality.to_i.to_s]
|
87
|
+
end
|
88
|
+
args
|
89
|
+
end
|
90
|
+
|
91
|
+
def run_cmd!(cmd)
|
92
|
+
success = system(*cmd, out: File::NULL, err: File::NULL)
|
93
|
+
return true if success
|
94
|
+
code = $?.respond_to?(:exitstatus) ? $?.exitstatus : nil
|
95
|
+
raise "ImageMagick command failed (exit #{code}): #{cmd.join(' ')}"
|
96
|
+
end
|
97
|
+
|
98
|
+
def find_imagemagick_tool
|
99
|
+
return 'magick' if system('which magick > /dev/null 2>&1')
|
100
|
+
return 'convert' if system('which convert > /dev/null 2>&1')
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,301 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'tmpdir'
|
5
|
+
require 'parallel'
|
6
|
+
require 'shellwords'
|
7
|
+
|
8
|
+
module Waifu2x
|
9
|
+
class Processor
|
10
|
+
class Waifu2xNotFoundError < StandardError; end
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def process_images(input_dir, output_dir, scale: 2, noise: 1, model: 'anime_style_art_rgb', processor: :waifu2x_ncnn_vulkan, waifu2x_bin: nil, models_path: nil, recursive: false, auto_scale_a4: false, pdf_density: nil, verbose: false, image_format: 'png', on_error: 'stop')
|
14
|
+
FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
|
15
|
+
|
16
|
+
image_paths = find_images(input_dir, recursive: recursive)
|
17
|
+
return if image_paths.empty?
|
18
|
+
|
19
|
+
Parallel.each(image_paths, in_threads: [Parallel.processor_count, 1].max) do |image_path|
|
20
|
+
local_scale = if auto_scale_a4
|
21
|
+
choose_scale_for_a4(image_path, target_dpi: (pdf_density || 300))
|
22
|
+
else
|
23
|
+
scale
|
24
|
+
end
|
25
|
+
begin
|
26
|
+
process_single_image(image_path, output_dir, local_scale, noise, model, processor, waifu2x_bin, models_path, verbose, image_format)
|
27
|
+
rescue StandardError => e
|
28
|
+
safe_path = begin
|
29
|
+
image_path.to_s.encode('UTF-8', invalid: :replace, undef: :replace)
|
30
|
+
rescue StandardError
|
31
|
+
image_path.to_s
|
32
|
+
end
|
33
|
+
safe_err = begin
|
34
|
+
e.message.to_s.encode('UTF-8', invalid: :replace, undef: :replace)
|
35
|
+
rescue StandardError
|
36
|
+
e.message.to_s
|
37
|
+
end
|
38
|
+
warn "[waifu2x] failed: #{safe_path} (#{e.class}: #{safe_err})"
|
39
|
+
raise e if on_error != 'skip'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def choose_scale_for_a4(image_path, target_dpi: 300)
|
47
|
+
a4_width_in = 8.26771653543307
|
48
|
+
a4_height_in = 11.6929133858268
|
49
|
+
target_w = (a4_width_in * target_dpi).round
|
50
|
+
target_h = (a4_height_in * target_dpi).round
|
51
|
+
|
52
|
+
w, h = safe_image_dimensions(image_path)
|
53
|
+
|
54
|
+
return 2 if w.to_i <= 0 || h.to_i <= 0
|
55
|
+
scale_w = target_w.to_f / w
|
56
|
+
scale_h = target_h.to_f / h
|
57
|
+
needed = [scale_w, scale_h].max
|
58
|
+
|
59
|
+
return 1 if needed <= 1.0
|
60
|
+
return 2 if needed <= 2.0
|
61
|
+
4
|
62
|
+
rescue StandardError
|
63
|
+
2
|
64
|
+
end
|
65
|
+
|
66
|
+
def safe_image_dimensions(path)
|
67
|
+
cmd = identify_command
|
68
|
+
if cmd
|
69
|
+
out = `#{cmd} -format "%w %h" #{Shellwords.escape(path)} 2>/dev/null`
|
70
|
+
if $?.success?
|
71
|
+
parts = out.strip.split
|
72
|
+
if parts.size == 2
|
73
|
+
w = Integer(parts[0]) rescue nil
|
74
|
+
h = Integer(parts[1]) rescue nil
|
75
|
+
return [w, h] if w && h
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
if system('which sips > /dev/null 2>&1')
|
80
|
+
out = `sips -g pixelWidth -g pixelHeight #{Shellwords.escape(path)} 2>/dev/null`
|
81
|
+
if $?.success?
|
82
|
+
w = out[/pixelWidth:\s*(\d+)/, 1]
|
83
|
+
h = out[/pixelHeight:\s*(\d+)/, 1]
|
84
|
+
return [w.to_i, h.to_i] if w && h
|
85
|
+
end
|
86
|
+
end
|
87
|
+
[0, 0]
|
88
|
+
end
|
89
|
+
|
90
|
+
def identify_command
|
91
|
+
if system('which magick > /dev/null 2>&1')
|
92
|
+
'magick identify'
|
93
|
+
elsif system('which identify > /dev/null 2>&1')
|
94
|
+
'identify'
|
95
|
+
else
|
96
|
+
nil
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def find_images(directory, recursive: false)
|
101
|
+
exts = %w[jpg JPG jpeg JPEG png PNG gif GIF webp WEBP]
|
102
|
+
brace = "{#{exts.join(',')}}"
|
103
|
+
pattern = recursive ? File.join(directory, '**', "*.#{brace}") : File.join(directory, "*.#{brace}")
|
104
|
+
paths = Dir.glob(pattern)
|
105
|
+
paths.select! { |p| File.file?(p) }
|
106
|
+
paths.reject! do |p|
|
107
|
+
base = File.basename(p)
|
108
|
+
base.start_with?('._') || base.casecmp('thumbs.db').zero?
|
109
|
+
end
|
110
|
+
paths.sort
|
111
|
+
end
|
112
|
+
|
113
|
+
def process_single_image(input_path, output_dir, scale, noise, model, processor, waifu2x_bin, models_path, verbose, image_format)
|
114
|
+
base_name = File.basename(input_path, '.*')
|
115
|
+
requested_ext = image_format.to_s.downcase
|
116
|
+
requested_ext = 'png' unless %w[png jpg jpeg webp].include?(requested_ext)
|
117
|
+
requested_ext = 'jpg' if requested_ext == 'jpeg'
|
118
|
+
output_path = File.join(output_dir, base_name + ".#{requested_ext}")
|
119
|
+
png_path = File.join(output_dir, base_name + '.png')
|
120
|
+
|
121
|
+
direct_format = (requested_ext == 'png' ? nil : requested_ext)
|
122
|
+
direct_quality = (requested_ext == 'webp' || requested_ext == 'jpg') ? 95 : nil
|
123
|
+
waifu_out_path = direct_format ? output_path : png_path
|
124
|
+
|
125
|
+
case processor
|
126
|
+
when :waifu2x_ncnn_vulkan
|
127
|
+
ok, status = process_with_waifu2x_ncnn_vulkan(
|
128
|
+
input_path,
|
129
|
+
waifu_out_path,
|
130
|
+
scale,
|
131
|
+
noise,
|
132
|
+
model,
|
133
|
+
waifu2x_bin,
|
134
|
+
models_path,
|
135
|
+
verbose,
|
136
|
+
format: direct_format,
|
137
|
+
quality: direct_quality
|
138
|
+
)
|
139
|
+
unless ok
|
140
|
+
cleaned = clean_input_image(input_path, verbose: verbose)
|
141
|
+
if cleaned && File.exist?(cleaned)
|
142
|
+
ok2, status2 = process_with_waifu2x_ncnn_vulkan(
|
143
|
+
cleaned,
|
144
|
+
waifu_out_path,
|
145
|
+
scale,
|
146
|
+
noise,
|
147
|
+
model,
|
148
|
+
waifu2x_bin,
|
149
|
+
models_path,
|
150
|
+
verbose,
|
151
|
+
format: direct_format,
|
152
|
+
quality: direct_quality
|
153
|
+
)
|
154
|
+
ok = ok2
|
155
|
+
status = status2
|
156
|
+
end
|
157
|
+
unless ok
|
158
|
+
raise RuntimeError, "waifu2x exited with status #{status || 'unknown'}"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
else
|
162
|
+
raise "Unsupported processor: #{processor}"
|
163
|
+
end
|
164
|
+
|
165
|
+
if File.exist?(png_path) && (File.extname(output_path).downcase != '.png') && direct_format.nil?
|
166
|
+
begin
|
167
|
+
convert_with_imagemagick_cli!(png_path, output_path, verbose: verbose)
|
168
|
+
File.delete(png_path) if File.exist?(output_path) && File.exist?(png_path)
|
169
|
+
rescue StandardError => e
|
170
|
+
safe_path = begin
|
171
|
+
output_path.to_s.encode('UTF-8', invalid: :replace, undef: :replace)
|
172
|
+
rescue StandardError
|
173
|
+
output_path.to_s
|
174
|
+
end
|
175
|
+
safe_err = begin
|
176
|
+
e.message.to_s.encode('UTF-8', invalid: :replace, undef: :replace)
|
177
|
+
rescue StandardError
|
178
|
+
e.message.to_s
|
179
|
+
end
|
180
|
+
warn "[imagemagick] convert skipped, keep PNG: #{safe_path} (#{e.class}: #{safe_err})"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
unless File.exist?(output_path) || File.exist?(png_path)
|
185
|
+
raise "出力画像の作成に失敗しました: #{output_path}"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def process_with_waifu2x_ncnn_vulkan(input_path, output_path, scale, noise, model, waifu2x_bin, models_path, verbose, format: nil, quality: nil)
|
190
|
+
bin = waifu2x_bin || 'waifu2x-ncnn-vulkan'
|
191
|
+
cmd = [bin, '-i', input_path, '-o', output_path, '-s', scale.to_s, '-n', noise.to_s]
|
192
|
+
cmd += ['-f', format] if format
|
193
|
+
cmd += ['-q', quality.to_i.to_s] if quality
|
194
|
+
cmd += ['-m', models_path] if models_path && !models_path.empty?
|
195
|
+
cmd += ['-g', '0', '-j', '1:2:2']
|
196
|
+
|
197
|
+
success = if verbose
|
198
|
+
system(*cmd)
|
199
|
+
else
|
200
|
+
system(*cmd, out: File::NULL, err: File::NULL)
|
201
|
+
end
|
202
|
+
status = $?.respond_to?(:exitstatus) ? $?.exitstatus : (success ? 0 : nil)
|
203
|
+
[success, status]
|
204
|
+
end
|
205
|
+
|
206
|
+
def convert_with_imagemagick_cli!(src_path, dst_path, verbose: false)
|
207
|
+
tool = find_imagemagick_tool
|
208
|
+
raise 'ImageMagick CLI (magick or convert) が見つかりません' unless tool
|
209
|
+
|
210
|
+
dst_ext = File.extname(dst_path).downcase
|
211
|
+
quality = 90
|
212
|
+
args = []
|
213
|
+
args += ['-define', 'png:ignore-crc=true', '-auto-orient', '-strip']
|
214
|
+
|
215
|
+
case dst_ext
|
216
|
+
when '.jpg', '.jpeg'
|
217
|
+
args += ['-quality', quality.to_s]
|
218
|
+
when '.webp'
|
219
|
+
args += ['-quality', quality.to_s]
|
220
|
+
end
|
221
|
+
cmd = if tool == 'magick'
|
222
|
+
['magick', src_path] + args + [dst_path]
|
223
|
+
else
|
224
|
+
['convert', src_path] + args + [dst_path]
|
225
|
+
end
|
226
|
+
success = run_im_cmd(cmd, verbose: verbose)
|
227
|
+
return true if success
|
228
|
+
|
229
|
+
Dir.mktmpdir('w2x-fix') do |tmp|
|
230
|
+
cleaned = File.join(tmp, 'clean.png')
|
231
|
+
fix_cmd = if tool == 'magick'
|
232
|
+
['magick', src_path, '-define', 'png:ignore-crc=true', '-auto-orient', '-strip', '-colorspace', 'sRGB', 'PNG32:' + cleaned]
|
233
|
+
else
|
234
|
+
['convert', src_path, '-define', 'png:ignore-crc=true', '-auto-orient', '-strip', '-colorspace', 'sRGB', 'PNG32:' + cleaned]
|
235
|
+
end
|
236
|
+
run_im_cmd(fix_cmd, verbose: verbose)
|
237
|
+
retry_cmd = if tool == 'magick'
|
238
|
+
['magick', cleaned] + args + [dst_path]
|
239
|
+
else
|
240
|
+
['convert', cleaned] + args + [dst_path]
|
241
|
+
end
|
242
|
+
success2 = run_im_cmd(retry_cmd, verbose: verbose)
|
243
|
+
return true if success2
|
244
|
+
|
245
|
+
if system('which sips > /dev/null 2>&1')
|
246
|
+
sips_png = File.join(tmp, 'sips.png')
|
247
|
+
sips_ok = system('sips', '-s', 'format', 'png', src_path, '--out', sips_png, out: File::NULL, err: File::NULL)
|
248
|
+
if sips_ok && File.exist?(sips_png)
|
249
|
+
retry_cmd2 = if tool == 'magick'
|
250
|
+
['magick', sips_png] + args + [dst_path]
|
251
|
+
else
|
252
|
+
['convert', sips_png] + args + [dst_path]
|
253
|
+
end
|
254
|
+
success3 = run_im_cmd(retry_cmd2, verbose: verbose)
|
255
|
+
return true if success3
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
code = $?.respond_to?(:exitstatus) ? $?.exitstatus : nil
|
261
|
+
raise "ImageMagick convert failed (exit #{code})"
|
262
|
+
end
|
263
|
+
|
264
|
+
def find_imagemagick_tool
|
265
|
+
return 'magick' if system('which magick > /dev/null 2>&1')
|
266
|
+
return 'convert' if system('which convert > /dev/null 2>&1')
|
267
|
+
nil
|
268
|
+
end
|
269
|
+
|
270
|
+
def run_im_cmd(cmd, verbose: false)
|
271
|
+
if verbose
|
272
|
+
system(*cmd)
|
273
|
+
else
|
274
|
+
system(*cmd, out: File::NULL, err: File::NULL)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def clean_input_image(src_path, verbose: false)
|
279
|
+
tool = find_imagemagick_tool
|
280
|
+
Dir.mktmpdir('w2x-clean') do |tmp|
|
281
|
+
cleaned = File.join(tmp, 'clean.png')
|
282
|
+
if tool
|
283
|
+
cmd = if tool == 'magick'
|
284
|
+
['magick', src_path, '-define', 'png:ignore-crc=true', '-auto-orient', '-strip', '-colorspace', 'sRGB', 'PNG32:' + cleaned]
|
285
|
+
else
|
286
|
+
['convert', src_path, '-define', 'png:ignore-crc=true', '-auto-orient', '-strip', '-colorspace', 'sRGB', 'PNG32:' + cleaned]
|
287
|
+
end
|
288
|
+
return cleaned if run_im_cmd(cmd, verbose: verbose) && File.exist?(cleaned)
|
289
|
+
end
|
290
|
+
if system('which sips > /dev/null 2>&1')
|
291
|
+
sips_ok = system('sips', '-s', 'format', 'png', src_path, '--out', cleaned, out: File::NULL, err: File::NULL)
|
292
|
+
return cleaned if sips_ok && File.exist?(cleaned)
|
293
|
+
end
|
294
|
+
nil
|
295
|
+
end
|
296
|
+
rescue StandardError
|
297
|
+
nil
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'open3'
|
4
|
+
require 'fileutils'
|
5
|
+
require_relative 'downloader'
|
6
|
+
|
7
|
+
module Waifu2x
|
8
|
+
module Setup
|
9
|
+
DEFAULT_MACOS_URL = 'https://github.com/nihui/waifu2x-ncnn-vulkan/releases/download/20250915/waifu2x-ncnn-vulkan-20250915-macos.zip'
|
10
|
+
|
11
|
+
module_function
|
12
|
+
|
13
|
+
def resolve_paths(options = {})
|
14
|
+
bin = options[:waifu2x_bin] || ENV['WAIFU2X_BIN'] || which('waifu2x-ncnn-vulkan')
|
15
|
+
models = options[:models_path] || ENV['WAIFU2X_MODELS']
|
16
|
+
source = nil
|
17
|
+
|
18
|
+
if options[:waifu2x_bin]
|
19
|
+
source = :option
|
20
|
+
elsif ENV['WAIFU2X_BIN']
|
21
|
+
source = :env
|
22
|
+
elsif bin && !bin.empty?
|
23
|
+
source = :path
|
24
|
+
end
|
25
|
+
|
26
|
+
if bin.nil? || bin.empty?
|
27
|
+
cached = find_cached_install
|
28
|
+
if cached
|
29
|
+
bin = cached[:bin]
|
30
|
+
models ||= cached[:models]
|
31
|
+
source = :cached
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
if bin.nil? || bin.empty?
|
36
|
+
if options[:auto_download]
|
37
|
+
download_url = options[:download_url]
|
38
|
+
download_url = DEFAULT_MACOS_URL if download_url.to_s.strip.empty?
|
39
|
+
|
40
|
+
installed = Downloader.download_and_install(download_url)
|
41
|
+
bin = installed[:bin]
|
42
|
+
models ||= installed[:models]
|
43
|
+
source = :download
|
44
|
+
else
|
45
|
+
raise 'waifu2x-ncnn-vulkan が見つかりませんでした。--waifu2x-bin でパスを指定するか、--auto-download と --download-url を指定してください。'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
models ||= begin
|
50
|
+
candidate = File.join(File.dirname(bin), 'models')
|
51
|
+
File.directory?(candidate) ? candidate : nil
|
52
|
+
end
|
53
|
+
|
54
|
+
if models && options[:model]
|
55
|
+
selected = select_model_dir(models, options[:model].to_s)
|
56
|
+
models = selected if selected
|
57
|
+
end
|
58
|
+
|
59
|
+
case source
|
60
|
+
when :option
|
61
|
+
puts "Using waifu2x binary (option): #{bin}"
|
62
|
+
when :env
|
63
|
+
puts "Using waifu2x binary (env WAIFU2X_BIN): #{bin}"
|
64
|
+
when :path
|
65
|
+
puts "Using waifu2x binary (PATH): #{bin}"
|
66
|
+
when :cached
|
67
|
+
puts "Using waifu2x binary (cached): #{bin}"
|
68
|
+
when :download
|
69
|
+
puts "Downloaded waifu2x binary: #{bin}"
|
70
|
+
end
|
71
|
+
|
72
|
+
{ bin: bin, models: models }
|
73
|
+
end
|
74
|
+
|
75
|
+
def select_model_dir(models_root, alias_name)
|
76
|
+
return nil unless File.directory?(models_root)
|
77
|
+
|
78
|
+
key = alias_name.to_s.downcase
|
79
|
+
patterns = case key
|
80
|
+
when 'photo'
|
81
|
+
[/upconv_7_photo\z/, /photo\z/]
|
82
|
+
when 'anime_rgb'
|
83
|
+
[/upconv_7_anime_style_art_rgb\z/]
|
84
|
+
when 'anime'
|
85
|
+
[/upconv_7_anime_style_art\z/, /anime_style_art\z/]
|
86
|
+
when 'cunet'
|
87
|
+
[/cunet\z/]
|
88
|
+
when 'upresnet10'
|
89
|
+
[/upresnet10\z/]
|
90
|
+
else
|
91
|
+
[]
|
92
|
+
end
|
93
|
+
|
94
|
+
return nil if patterns.empty?
|
95
|
+
|
96
|
+
entries = Dir.children(models_root)
|
97
|
+
dirnames = entries.select { |e| File.directory?(File.join(models_root, e)) }
|
98
|
+
|
99
|
+
patterns.each do |pat|
|
100
|
+
match = dirnames.find { |name| name.match?(pat) || name.match?(/models-.*#{pat.source}/) }
|
101
|
+
return File.join(models_root, match) if match
|
102
|
+
end
|
103
|
+
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
|
107
|
+
def which(cmd)
|
108
|
+
stdout, _stderr, status = Open3.capture3('bash', '-lc', "command -v #{cmd}")
|
109
|
+
status.success? ? stdout.strip : nil
|
110
|
+
end
|
111
|
+
|
112
|
+
def find_cached_install
|
113
|
+
cache_root = File.expand_path('~/.cache/waifu2x-pdf/waifu2x-ncnn-vulkan')
|
114
|
+
return nil unless Dir.exist?(cache_root)
|
115
|
+
|
116
|
+
candidates = Dir.children(cache_root).map { |d| File.join(cache_root, d) }.select { |p| File.directory?(p) }
|
117
|
+
candidates.sort_by! { |p| File.mtime(p) }.reverse!
|
118
|
+
candidates.each do |dir|
|
119
|
+
bin = File.join(dir, 'waifu2x-ncnn-vulkan')
|
120
|
+
next unless File.exist?(bin)
|
121
|
+
models = File.join(dir, 'models')
|
122
|
+
return { bin: bin, models: File.directory?(models) ? models : nil }
|
123
|
+
end
|
124
|
+
nil
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|