Module: VivlioStarter::Pdf::PdfToJpeg
- Defined in:
- lib/vivlio_starter/cli/pdf/pdf_to_jpeg.rb
Overview
PDF を pdftoppm で JPEG 群に変換する共通処理。 部分ページ指定時でもファイル名は元 PDF のページ番号に揃える。
Defined Under Namespace
Classes: Error
Class Method Summary collapse
-
.build_command(pdf_path, prefix, dpi, quality) ⇒ Object
pdftoppm のコマンドライン配列を組み立てる(shell を介さず配列で実行する).
-
.convert(pdf_path, output_dir:, dpi: 350, quality: 95, pages: nil, command_runner: Kernel) ⇒ Array<String>
PDF を JPEG 群へ変換し、生成した画像パスの配列を返す.
-
.execute!(command, command_runner: Kernel) ⇒ Object
コマンドを実行し、失敗時は Error を送出する.
-
.filter_pages!(images, pages) ⇒ Object
指定ページのみを残し、対象外の JPEG はディスクからも削除する.
-
.normalize_output_names(output_dir) ⇒ Object
出力ファイル名を page-001.jpg 形式(3桁ゼロ埋め)に統一する pdftoppm は総ページ数によって連番の桁数を変える(page-1.jpg / page-01.jpg)ため.
-
.page_number_from_path(path) ⇒ Object
JPEG パスからページ番号を取り出す(例: "page-012.jpg" → 12).
-
.parse_page_part(part) ⇒ Object
ページ指定の1要素("3" または "3-5")をページ番号配列へ展開する.
-
.parse_page_spec(spec) ⇒ Object
ページ指定文字列をページ番号配列に展開する(例: "1,3-5" → [1, 3, 4, 5]).
-
.validate_options!(dpi:, quality:) ⇒ Object
dpi / quality の値域を検証する(pdftoppm に渡す前に日本語で失敗理由を示す).
- .validate_page_range!(start_page, end_page) ⇒ Object
Class Method Details
.build_command(pdf_path, prefix, dpi, quality) ⇒ Object
pdftoppm のコマンドライン配列を組み立てる(shell を介さず配列で実行する)
50 51 52 53 54 55 56 57 58 59 |
# File 'lib/vivlio_starter/cli/pdf/pdf_to_jpeg.rb', line 50 def build_command(pdf_path, prefix, dpi, quality) [ 'pdftoppm', '-jpeg', '-jpegopt', "quality=#{quality}", '-r', dpi.to_s, pdf_path, prefix ] end |
.convert(pdf_path, output_dir:, dpi: 350, quality: 95, pages: nil, command_runner: Kernel) ⇒ Array<String>
PDF を JPEG 群へ変換し、生成した画像パスの配列を返す
pages 指定時も全ページを一度変換してから不要分を削除する。 pdftoppm の -f/-l による部分変換では出力ファイル名の連番が 1 起点に振り直され、元 PDF のページ番号と一致しなくなるため。
24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/vivlio_starter/cli/pdf/pdf_to_jpeg.rb', line 24 def convert(pdf_path, output_dir:, dpi: 350, quality: 95, pages: nil, command_runner: Kernel) (dpi:, quality:) FileUtils.mkdir_p(output_dir) FileUtils.rm_f(Dir.glob(File.join(output_dir, 'page-*.jpg'))) command = build_command(pdf_path, File.join(output_dir, 'page'), dpi, quality) execute!(command, command_runner:) normalize_output_names(output_dir) images = Dir.glob(File.join(output_dir, 'page-*.jpg')).sort_by { page_number_from_path(it) } filter_pages!(images, pages) end |
.execute!(command, command_runner: Kernel) ⇒ Object
コマンドを実行し、失敗時は Error を送出する
62 63 64 65 66 |
# File 'lib/vivlio_starter/cli/pdf/pdf_to_jpeg.rb', line 62 def execute!(command, command_runner: Kernel) return if command_runner.system(*command) raise Error, "pdftoppm の実行に失敗しました: #{command.join(' ')}" end |
.filter_pages!(images, pages) ⇒ Object
指定ページのみを残し、対象外の JPEG はディスクからも削除する
69 70 71 72 73 74 75 76 |
# File 'lib/vivlio_starter/cli/pdf/pdf_to_jpeg.rb', line 69 def filter_pages!(images, pages) return images if pages.nil? || pages.to_s.strip.empty? requested_pages = parse_page_spec(pages) selected = images.select { requested_pages.include?(page_number_from_path(it)) } (images - selected).each { FileUtils.rm_f(it) } selected end |
.normalize_output_names(output_dir) ⇒ Object
出力ファイル名を page-001.jpg 形式(3桁ゼロ埋め)に統一する pdftoppm は総ページ数によって連番の桁数を変える(page-1.jpg / page-01.jpg)ため
80 81 82 83 84 85 86 87 88 |
# File 'lib/vivlio_starter/cli/pdf/pdf_to_jpeg.rb', line 80 def normalize_output_names(output_dir) Dir.glob(File.join(output_dir, 'page-*.jpg')).each do |path| page_number = page_number_from_path(path) target = File.join(output_dir, format('page-%03d.jpg', page_number)) next if path == target FileUtils.mv(path, target) end end |
.page_number_from_path(path) ⇒ Object
JPEG パスからページ番号を取り出す(例: "page-012.jpg" → 12)
126 127 128 |
# File 'lib/vivlio_starter/cli/pdf/pdf_to_jpeg.rb', line 126 def page_number_from_path(path) File.basename(path)[/page-(\d+)\.jpg\z/, 1].to_i end |
.parse_page_part(part) ⇒ Object
ページ指定の1要素("3" または "3-5")をページ番号配列へ展開する
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/vivlio_starter/cli/pdf/pdf_to_jpeg.rb', line 91 def parse_page_part(part) raise Error, "ページ指定が不正です: #{part.inspect}" if part.empty? case part when /\A\d+\z/ page = part.to_i raise Error, 'ページ番号は 1 以上で指定してください' if page < 1 [page] when /\A(\d+)-(\d+)\z/ start_page = ::Regexp.last_match(1).to_i end_page = ::Regexp.last_match(2).to_i validate_page_range!(start_page, end_page) (start_page..end_page).to_a else raise Error, "ページ指定が不正です: #{part.inspect}" end end |
.parse_page_spec(spec) ⇒ Object
ページ指定文字列をページ番号配列に展開する(例: "1,3-5" → [1, 3, 4, 5])
40 41 42 43 44 45 46 47 |
# File 'lib/vivlio_starter/cli/pdf/pdf_to_jpeg.rb', line 40 def parse_page_spec(spec) text = spec.to_s.strip raise Error, 'ページ指定が空です' if text.empty? text.split(',').flat_map do |part| parse_page_part(part.strip) end.uniq.sort end |
.validate_options!(dpi:, quality:) ⇒ Object
dpi / quality の値域を検証する(pdftoppm に渡す前に日本語で失敗理由を示す)
116 117 118 119 120 121 122 123 |
# File 'lib/vivlio_starter/cli/pdf/pdf_to_jpeg.rb', line 116 def (dpi:, quality:) raise Error, 'dpi は 1 以上の整数で指定してください' unless dpi.to_i.positive? q = quality.to_i return if (1..100).cover?(q) raise Error, 'quality は 1〜100 の整数で指定してください' end |
.validate_page_range!(start_page, end_page) ⇒ Object
110 111 112 113 |
# File 'lib/vivlio_starter/cli/pdf/pdf_to_jpeg.rb', line 110 def validate_page_range!(start_page, end_page) raise Error, 'ページ番号は 1 以上で指定してください' if start_page < 1 || end_page < 1 raise Error, "ページ範囲が逆順です: #{start_page}-#{end_page}" if start_page > end_page end |