Module: VivlioStarter::CLI::Import::ImageProcessor

Defined in:
lib/vivlio_starter/cli/import/image_processor.rb

Overview

画像処理モジュール

Constant Summary collapse

MASTER_WIDTH =
CoverCommands::SIZES[:a4][:width]
MASTER_HEIGHT =
CoverCommands::SIZES[:a4][:height]

Class Method Summary collapse

Class Method Details

.command_in_path?(command) ⇒ Boolean

Returns:

  • (Boolean)


198
199
200
201
202
203
204
205
# File 'lib/vivlio_starter/cli/import/image_processor.rb', line 198

def command_in_path?(command)
  return false if command.to_s.empty?

  ENV.fetch('PATH', '').split(File::PATH_SEPARATOR).any? do |dir|
    path = File.join(dir, command)
    File.executable?(path) && !File.directory?(path)
  end
end

.convert_cover_pdf_to_master!(covers_dir, cover_filename, master) ⇒ Object

Re:VIEW の PDF を Vivlio マスター PNG へ変換する



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/vivlio_starter/cli/import/image_processor.rb', line 126

def convert_cover_pdf_to_master!(covers_dir, cover_filename, master)
  pdf_path = File.join(covers_dir, cover_filename)
  unless File.exist?(pdf_path)
    Common.log_warn("  コピー済みの表紙 PDF が見つかりません: #{pdf_path}")
    return false
  end

  convert_cmd = find_imagemagick_convert_command
  unless convert_cmd
    Common.log_warn("  ImageMagick(magick/convert)が見つからないため #{master} を生成できませんでした")
    return false
  end

  master_path = File.join(covers_dir, master)
  cmd = convert_cmd + [
    "#{pdf_path}[0]",
    '-density', '350',
    '-resize', "#{MASTER_WIDTH}x#{MASTER_HEIGHT}!",
    '-quality', '100',
    "PNG32:#{master_path}"
  ]

  if run_imagemagick_command(cmd, label: "#{master} 生成")
    Common.log_info("  #{master} を生成しました: #{master_path}")
    true
  else
    FileUtils.rm_f(master_path)
    false
  end
end

.convert_to_webp!(starter_dir) ⇒ void

This method returns an undefined value.

画像を WebP に変換してコピーする

Parameters:

  • starter_dir (String)

    Starter プロジェクトのディレクトリ



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/vivlio_starter/cli/import/image_processor.rb', line 41

def convert_to_webp!(starter_dir)
  Common.log_action('[Step 3] 画像を WebP に変換します')

  starter_images = File.join(starter_dir, 'images')
  unless Dir.exist?(starter_images)
    Common.log_warn("  images/ ディレクトリが見つかりません: #{starter_images}")
    return
  end

  files = copy_images_to_local(starter_images)
  return if files.empty?

  Common.log_info("  #{files.size} 個の画像をコピーしました")

  # ResizeCommands で WebP 変換
  ResizeCommands.execute_resize_medium('images')
  remove_original_files!
end

.copy_images_to_local(starter_images) ⇒ Array<String>

画像ファイルをローカルにコピーする

Parameters:

  • starter_images (String)

    Starter の images ディレクトリパス

Returns:

  • (Array<String>)

    コピーしたファイルのリスト



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/vivlio_starter/cli/import/image_processor.rb', line 163

def copy_images_to_local(starter_images)
  patterns = %w[png jpg jpeg gif PNG JPG JPEG GIF]
  files = patterns.flat_map { |ext| Dir.glob(File.join(starter_images, "**/*.#{ext}")) }

  if files.empty?
    Common.log_info('  変換対象の画像がありません')
    return []
  end

  files.each do |src|
    relative = src.sub("#{starter_images}/", '')
    dest_dir = File.join('images', File.dirname(relative))
    FileUtils.mkdir_p(dest_dir)
    # 取り込み時にファイル名の危険文字を除去する(markdown 参照側の
    # normalize_image_paths / convert_img_tags と同一基準で正規化し、
    # 実体と参照の食い違いを防ぐ)。W14010(アポストロフィ等)の恒久防御。
    FileUtils.cp(src, File.join(dest_dir, sanitized_image_filename(File.basename(src))))
  end

  files
end

.find_imagemagick_convert_commandObject



191
192
193
194
195
196
# File 'lib/vivlio_starter/cli/import/image_processor.rb', line 191

def find_imagemagick_convert_command
  return %w[magick convert] if command_in_path?('magick')
  return ['convert'] if command_in_path?('convert')

  nil
end

.import_master_cover!(starter_dir, cover_value, master:, label:) ⇒ Boolean

表紙 PDF をコピーし、マスター PNG を生成する

Parameters:

  • starter_dir (String)

    Starter プロジェクトのディレクトリ

  • cover_value (Object)

    config-starter.yml の frontcover_pdffile / backcover_pdffile

  • master (String)

    生成するマスター PNG(frontcover_master.png 等)

  • label (String)

    ログでの呼び名(表表紙 / 裏表紙)

Returns:

  • (Boolean)

    成功時 true



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/vivlio_starter/cli/import/image_processor.rb', line 87

def import_master_cover!(starter_dir, cover_value, master:, label:)
  cover_filename = plain_cover_filename(cover_value, label: label)
  return false if cover_filename.empty?

  unless cover_filename.downcase.end_with?('.pdf')
    Common.log_warn("  #{label}の指定 #{cover_filename} は PDF ではないため取り込めません。",
                    detail: "対処: covers/#{master} を直接置き換えてください。")
    return false
  end

  src = File.join(starter_dir, 'images', cover_filename)
  unless File.exist?(src)
    Common.log_warn("  #{label}の PDF が見つかりません: #{src}")
    return false
  end

  dest_dir = 'covers'
  FileUtils.mkdir_p(dest_dir)
  FileUtils.cp(src, File.join(dest_dir, cover_filename))
  Common.log_info("  #{label}の PDF をコピーしました: #{cover_filename} → covers/")

  convert_cover_pdf_to_master!(dest_dir, cover_filename, master)
end

.plain_cover_filename(cover_value, label:) ⇒ Object

Re:VIEW Starter の表紙指定からファイル名だけを取り出す。

配列([a.pdf, b.pdf])・ページ番号(cover.pdf<2>)・塗り足し除去(cover.pdf*)を 取りうる書き方なので、先頭 1 件の素のファイル名まで落とす。



115
116
117
118
119
120
121
122
123
# File 'lib/vivlio_starter/cli/import/image_processor.rb', line 115

def plain_cover_filename(cover_value, label:)
  names = Array(cover_value).map { it.to_s.strip }.reject(&:empty?)
  if names.size > 1
    Common.log_warn("  #{label}#{names.size} 個の PDF が指定されています。#{names.first} だけを取り込みました。",
                    detail: '対処: 複数ページの表紙が要るなら、1 枚に結合してから取り込んでください。')
  end

  names.first.to_s.sub(/<\d+>/, '').delete_suffix('*')
end

.remove_original_files!void

This method returns an undefined value.

元画像(png/jpg/gif)を削除する



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/vivlio_starter/cli/import/image_processor.rb', line 63

def remove_original_files!
  removed = 0
  Dir.glob(File.join('images', '**', '*')).each do |path|
    next unless File.file?(path)
    next unless path.match?(/\.(png|jpe?g|gif)$/i)

    FileUtils.rm_f(path)
    removed += 1
  end

  if removed.positive?
    Common.log_info("  旧画像 (png/jpg/gif) を #{removed} 個削除しました")
  else
    Common.log_info('  削除対象の旧画像はありませんでした')
  end
end

.run_imagemagick_command(cmd, label:) ⇒ Object



207
208
209
210
211
212
# File 'lib/vivlio_starter/cli/import/image_processor.rb', line 207

def run_imagemagick_command(cmd, label:)
  Common.log_action("  $ #{Shellwords.join(cmd.map(&:to_s))}")
  success = system(*cmd, out: File::NULL, err: File::NULL)
  Common.log_error("  #{label} に失敗しました") unless success
  success
end

.sanitized_image_filename(name) ⇒ Object

画像ファイル名の拡張子は保ったまま、stem の危険文字だけを除去する。



186
187
188
189
# File 'lib/vivlio_starter/cli/import/image_processor.rb', line 186

def sanitized_image_filename(name)
  ext = File.extname(name)
  "#{ImageFilenameSanitizer.sanitize(File.basename(name, ext))}#{ext}"
end