From 890ed94440d2aad3fef4e022c532d1e78f1ad83f Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Fri, 5 Dec 2025 12:02:07 -0500 Subject: [PATCH 01/38] rename the stage "compressor" to "compression" I want to use the name "compressor" for a different object. -ion makes more sense a stage suffix than -or. --- config/config.dlxs.yml | 4 ++-- config/config.yml | 4 ++-- lib/stage/compressor.rb | 3 +-- test/compressor_test.rb | 22 +++++++++++----------- test/config/config.dlxs.yml | 4 ++-- test/config/config.yml | 4 ++-- test/dlxs_compressor_test.rb | 2 +- test/fixtures.rb | 2 +- 8 files changed, 22 insertions(+), 23 deletions(-) diff --git a/config/config.dlxs.yml b/config/config.dlxs.yml index f709ab6..13f82a0 100644 --- a/config/config.dlxs.yml +++ b/config/config.dlxs.yml @@ -12,8 +12,8 @@ stages: - name: Tagger class: Tagger file: tagger - - name: Compressor - class: Compressor + - name: Compression + class: Compression file: compressor - name: DLXSCompressor class: DLXSCompressor diff --git a/config/config.yml b/config/config.yml index 0ca7a57..77d8c99 100644 --- a/config/config.yml +++ b/config/config.yml @@ -12,8 +12,8 @@ stages: - name: Tagger class: Tagger file: tagger - - name: Compressor - class: Compressor + - name: Compression + class: Compression file: compressor - name: Postflight class: Postflight diff --git a/lib/stage/compressor.rb b/lib/stage/compressor.rb index 3f3d9b1..7f7aa19 100755 --- a/lib/stage/compressor.rb +++ b/lib/stage/compressor.rb @@ -15,10 +15,9 @@ TIFF_DATE_FORMAT = "%Y:%m:%d %H:%M:%S" # TIFF to JP2/TIFF compression stage -class Compressor < Stage # rubocop:disable Metrics/ClassLength +class Compression < Stage # rubocop:disable Metrics/ClassLength def run(agenda) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength return unless agenda.any? - files = image_files.select { |file| agenda.include? file.objid } @bar.steps = files.count files.each_with_index do |image_file, i| diff --git a/test/compressor_test.rb b/test/compressor_test.rb index 5f70e13..d460f21 100755 --- a/test/compressor_test.rb +++ b/test/compressor_test.rb @@ -5,7 +5,7 @@ require "compressor" require "fixtures" -class CompressorTest < Minitest::Test # rubocop:disable Metrics/ClassLength +class CompressionTest < Minitest::Test # rubocop:disable Metrics/ClassLength def setup @config = Config.new({no_progress: true}) end @@ -18,7 +18,7 @@ def self.gen_new test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir) shipment = shipment_class.new(test_shipment.directory) - stage = Compressor.new(shipment, config: opts.merge(@config)) + stage = Compression.new(shipment, config: opts.merge(@config)) refute_nil stage, "stage successfully created" } generate_tests "new", test_proc @@ -28,7 +28,7 @@ def self.gen_run # rubocop:disable Metrics/AbcSize, Metrics/MethodLength test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bitonal 1 T contone 2") shipment = shipment_class.new(test_shipment.directory) - stage = Compressor.new(shipment, config: opts.merge(@config)) + stage = Compression.new(shipment, config: opts.merge(@config)) stage.run! assert_equal(0, stage.errors.count, "stage runs without errors") tiff = File.join(shipment.directory, @@ -50,7 +50,7 @@ def self.gen_set_tiff_date_time # rubocop:disable Metrics/AbcSize, Metrics/Metho tiff = File.join(shipment.directory, shipment.objid_to_path(shipment.objids[0]), "00000001.tif") - stage = Compressor.new(shipment, config: opts.merge(@config)) + stage = Compression.new(shipment, config: opts.merge(@config)) stage.send(:write_tiff_date_time, tiff) tiffinfo = `tiffinfo #{tiff}` assert_match(/DateTime:\s\d{4}:\d{2}:\d{2}\s\d{2}:\d{2}:\d{2}/, tiffinfo, @@ -67,7 +67,7 @@ def self.gen_set_jp2_date_time # rubocop:disable Metrics/AbcSize, Metrics/Method shipment.objid_to_path(shipment.objids[0]), "00000001.tif") `tiffset -s 306 '2000:11:11 11:11:11' #{tiff}` - stage = Compressor.new(shipment, config: opts.merge(@config)) + stage = Compression.new(shipment, config: opts.merge(@config)) stage.run! jp2 = File.join(shipment.directory, shipment.objid_to_path(shipment.objids[0]), @@ -83,7 +83,7 @@ def self.gen_set_jp2_document_name # rubocop:disable Metrics/AbcSize, Metrics/Me test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T contone 1") shipment = shipment_class.new(test_shipment.directory) - stage = Compressor.new(shipment, config: opts.merge(@config)) + stage = Compression.new(shipment, config: opts.merge(@config)) stage.run! jp2 = File.join(shipment.directory, shipment.objid_to_path(shipment.objids[0]), @@ -101,7 +101,7 @@ def self.gen_16bps_fails test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bad_16bps 1") shipment = shipment_class.new(test_shipment.directory) - stage = Compressor.new(shipment, config: opts.merge(@config)) + stage = Compression.new(shipment, config: opts.merge(@config)) stage.run! assert_equal(1, stage.errors.count, "stage fails with 16bps TIFF") assert_match(/invalid source tiff/i, stage.errors[0].description, @@ -114,7 +114,7 @@ def self.gen_zero_length_fails test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC F 00000001.tif") shipment = shipment_class.new(test_shipment.directory) - stage = Compressor.new(shipment, config: opts.merge(@config)) + stage = Compression.new(shipment, config: opts.merge(@config)) stage.run! # Error description may be tiffinfo exit code or something more detailed. assert_equal(1, stage.errors.count, "stage fails with zero-length TIFF") @@ -130,7 +130,7 @@ def self.gen_alpha_channel # rubocop:disable Metrics/AbcSize, Metrics/MethodLeng shipment.objid_to_path(shipment.objids[0]), "00000001.tif") `convert #{tiff} -alpha on #{tiff}` - stage = Compressor.new(shipment, config: opts.merge(@config)) + stage = Compression.new(shipment, config: opts.merge(@config)) stage.run! assert_equal(0, stage.errors.count, "stage runs without errors") } @@ -146,7 +146,7 @@ def self.gen_icc_profile # rubocop:disable Metrics/AbcSize, Metrics/MethodLength "00000001.tif") profile_path = File.join(Fixtures::TEST_FIXTURES_PATH, "sRGB2014.icc") `convert #{tiff} -profile #{profile_path} #{tiff}` - stage = Compressor.new(shipment, config: opts.merge(@config)) + stage = Compression.new(shipment, config: opts.merge(@config)) stage.run! assert_equal(0, stage.errors.count, "stage runs without errors") } @@ -161,7 +161,7 @@ def self.gen_software # rubocop:disable Metrics/AbcSize, Metrics/MethodLength shipment.objid_to_path(shipment.objids[0]), "00000001.tif") `tiffset -s 305 'BOGUS SOFTWARE v1.0' #{tiff}` - stage = Compressor.new(shipment, config: opts.merge(@config)) + stage = Compression.new(shipment, config: opts.merge(@config)) stage.run! assert_equal(0, stage.errors.count, "stage runs without errors") assert_match(/BOGUS\sSOFTWARE/, `tiffinfo #{tiff}`, diff --git a/test/config/config.dlxs.yml b/test/config/config.dlxs.yml index c19d92b..d300785 100644 --- a/test/config/config.dlxs.yml +++ b/test/config/config.dlxs.yml @@ -12,8 +12,8 @@ stages: - name: Tagger class: Tagger file: tagger - - name: Compressor - class: Compressor + - name: Compression + class: Compression file: compressor - name: DLXSCompressor class: DLXSCompressor diff --git a/test/config/config.yml b/test/config/config.yml index ed3d97b..8a5bcbd 100644 --- a/test/config/config.yml +++ b/test/config/config.yml @@ -12,8 +12,8 @@ stages: - name: Tagger class: Tagger file: tagger - - name: Compressor - class: Compressor + - name: Compression + class: Compression file: compressor - name: Postflight class: Postflight diff --git a/test/dlxs_compressor_test.rb b/test/dlxs_compressor_test.rb index 70b47e2..808a700 100755 --- a/test/dlxs_compressor_test.rb +++ b/test/dlxs_compressor_test.rb @@ -20,7 +20,7 @@ def self.gen_run # rubocop:disable Metrics/AbcSize, Metrics/MethodLength test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T contone 1") shipment = shipment_class.new(test_shipment.directory) - stage = Compressor.new(shipment, config: opts.merge(@config)) + stage = Compression.new(shipment, config: opts.merge(@config)) stage.run! assert_equal(0, stage.errors.count, "compressor runs without errors") stage = DLXSCompressor.new(shipment, config: opts.merge(@config)) diff --git a/test/fixtures.rb b/test/fixtures.rb index f2882fb..9d497a4 100755 --- a/test/fixtures.rb +++ b/test/fixtures.rb @@ -13,7 +13,7 @@ module Fixtures }, bad_16bps: { file: "10_10_1_16bps_400.tif", - description: "16bps image that will fail Image Validator and Compressor" + description: "16bps image that will fail Image Validator and Compression" } }.freeze JP2_FIXTURES = { From 2a106492f2cb31e8b540e466bd5a75d4291e92d4 Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Fri, 5 Dec 2025 13:01:22 -0500 Subject: [PATCH 02/38] rename compressor file to compression --- config/config.dlxs.yml | 2 +- config/config.yml | 2 +- lib/stage/{compressor.rb => compression.rb} | 0 test/compressor_test.rb | 2 +- test/config/config.dlxs.yml | 2 +- test/config/config.yml | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename lib/stage/{compressor.rb => compression.rb} (100%) diff --git a/config/config.dlxs.yml b/config/config.dlxs.yml index 13f82a0..322434a 100644 --- a/config/config.dlxs.yml +++ b/config/config.dlxs.yml @@ -14,7 +14,7 @@ stages: file: tagger - name: Compression class: Compression - file: compressor + file: compression - name: DLXSCompressor class: DLXSCompressor file: dlxs_compressor diff --git a/config/config.yml b/config/config.yml index 77d8c99..104643c 100644 --- a/config/config.yml +++ b/config/config.yml @@ -14,7 +14,7 @@ stages: file: tagger - name: Compression class: Compression - file: compressor + file: compression - name: Postflight class: Postflight file: postflight diff --git a/lib/stage/compressor.rb b/lib/stage/compression.rb similarity index 100% rename from lib/stage/compressor.rb rename to lib/stage/compression.rb diff --git a/test/compressor_test.rb b/test/compressor_test.rb index d460f21..574934f 100755 --- a/test/compressor_test.rb +++ b/test/compressor_test.rb @@ -2,7 +2,7 @@ # frozen_string_literal: true require "minitest/autorun" -require "compressor" +require "compression" require "fixtures" class CompressionTest < Minitest::Test # rubocop:disable Metrics/ClassLength diff --git a/test/config/config.dlxs.yml b/test/config/config.dlxs.yml index d300785..2f7b313 100644 --- a/test/config/config.dlxs.yml +++ b/test/config/config.dlxs.yml @@ -14,7 +14,7 @@ stages: file: tagger - name: Compression class: Compression - file: compressor + file: compression - name: DLXSCompressor class: DLXSCompressor file: dlxs_compressor diff --git a/test/config/config.yml b/test/config/config.yml index 8a5bcbd..b4963a7 100644 --- a/test/config/config.yml +++ b/test/config/config.yml @@ -14,7 +14,7 @@ stages: file: tagger - name: Compression class: Compression - file: compressor + file: compression - name: Postflight class: Postflight file: postflight From e474832eb58cdeb630b82a945669488fe9ed7bc5 Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Fri, 5 Dec 2025 13:53:50 -0500 Subject: [PATCH 03/38] start moving compression logic to compressor class --- lib/compressor.rb | 9 +++++++++ lib/stage/compression.rb | 20 +++++++++++++------- 2 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 lib/compressor.rb diff --git a/lib/compressor.rb b/lib/compressor.rb new file mode 100644 index 0000000..71241fb --- /dev/null +++ b/lib/compressor.rb @@ -0,0 +1,9 @@ +require "tiff" +class Compressor + attr_reader :tiffinfo, :image_file, :tmpdir + def initialize(image_file:, tmpdir:) + @image_file = image_file + @tiffinfo = TIFF.new(image_file.path).info + @tmpdir = tmpdir + end +end diff --git a/lib/stage/compression.rb b/lib/stage/compression.rb index 7f7aa19..de4fc2a 100755 --- a/lib/stage/compression.rb +++ b/lib/stage/compression.rb @@ -3,6 +3,7 @@ require "stage" require "tiff" +require "compressor" JP2_LEVEL_MIN = 5 JP2_LAYERS = 8 @@ -22,7 +23,8 @@ def run(agenda) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, @bar.steps = files.count files.each_with_index do |image_file, i| begin - tiffinfo = TIFF.new(image_file.path).info + compressor = Compressor.new(image_file: image_file, tmpdir: create_tempdir) + tiffinfo = compressor.tiffinfo rescue => e add_error Error.new(e.message, image_file.objid, image_file.file) next @@ -32,7 +34,7 @@ def run(agenda) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, # It's a contone, so we convert to JP2. @bar.step! i, "#{image_file.objid_file} JP2" begin - handle_8_bps_conversion(image_file, tiffinfo) + handle_8_bps_conversion(compressor) rescue => e add_error Error.new(e.message, image_file.objid, image_file.file) end @@ -40,7 +42,7 @@ def run(agenda) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, # It's bitonal, so we G4 compress it. @bar.step! i, "#{image_file.objid_file} G4" begin - handle_1_bps_conversion(image_file, tiffinfo) + handle_1_bps_conversion(compressor) rescue => e add_error Error.new(e.message, image_file.objid, image_file.file) end @@ -53,8 +55,10 @@ def run(agenda) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, private - def handle_8_bps_conversion(image_file, tiffinfo) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength - tmpdir = create_tempdir + def handle_8_bps_conversion(compressor) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + image_file = compressor.image_file + tiffinfo = compressor.tiffinfo + tmpdir = compressor.tmpdir sparse = File.join(tmpdir, "sparse.tif") new_image = File.join(tmpdir, "new.jp2") final_image_name = File.basename(image_file.path, ".*") + ".jp2" @@ -192,8 +196,10 @@ def copy_jp2_alphaless_metadata(source, destination) log cmd, status[:time] end - def handle_1_bps_conversion(image_file, tiffinfo) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength - tmpdir = create_tempdir + def handle_1_bps_conversion(compressor) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + image_file = compressor.image_file + tiffinfo = compressor.tiffinfo + tmpdir = compressor.tmpdir compressed = File.join(tmpdir, "#{File.basename(image_file.path)}-compressed") page1 = File.join(tmpdir, "#{File.basename(image_file.path)}-page1") From 530de8eb1476583f7f9ed0dd6ad152775e2eb61e Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Fri, 5 Dec 2025 17:18:40 -0500 Subject: [PATCH 04/38] WIP extracted Log to its own class --- lib/compressor.rb | 16 +++++++++++++++- lib/stage.rb | 34 ++++++++++++++++++++++++++++++++-- lib/stage/compression.rb | 2 +- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/lib/compressor.rb b/lib/compressor.rb index 71241fb..4d1c7f0 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -1,9 +1,23 @@ require "tiff" class Compressor attr_reader :tiffinfo, :image_file, :tmpdir - def initialize(image_file:, tmpdir:) + def initialize(image_file:, tmpdir:, shipment: "whatever", log: "whatever") @image_file = image_file @tiffinfo = TIFF.new(image_file.path).info @tmpdir = tmpdir + @shipment = shipment + @log = log + end + + def sparse_path + @sparse ||= File.join(tmpdir, "sparse.tif") + end +end + +module ExifTool + def remove_tiff_metadata(source:, destination:) + cmd = "exiftool -XMP:All= -MakerNotes:All= #{path} -o #{destination}" + status = Command.new(cmd).run + OpenStruct.new(command: cmd, time: status[:time]) end end diff --git a/lib/stage.rb b/lib/stage.rb index 3b63c85..21abbb8 100755 --- a/lib/stage.rb +++ b/lib/stage.rb @@ -222,9 +222,12 @@ def objids @objids ||= shipment.objids end + def log_collection + @data[:log] ||= Log.new + end + def log(entry, time = nil) - entry += format(" (%.3f sec)", time) unless time.nil? - (@data[:log] ||= []) << entry + log_collection.log(entry, time) end def shipment_directory @@ -242,4 +245,31 @@ def objid_directories def image_files(type = "tif") shipment.image_files(type) end + + class Log + include Enumerable + + def initialize + @log = [] + end + + def each + @log.each do |line| + yield line + end + end + + def log(entry, time) + entry += format(" (%.3f sec)", time) unless time.nil? + @log << entry + end + + def log_it(entry, time) + log(entry, time) + end + + def to_json(state = nil, *) + JSON::State.from_state(state).generate(@log) + end + end end diff --git a/lib/stage/compression.rb b/lib/stage/compression.rb index de4fc2a..2ffa024 100755 --- a/lib/stage/compression.rb +++ b/lib/stage/compression.rb @@ -23,7 +23,7 @@ def run(agenda) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, @bar.steps = files.count files.each_with_index do |image_file, i| begin - compressor = Compressor.new(image_file: image_file, tmpdir: create_tempdir) + compressor = Compressor.new(image_file: image_file, tmpdir: create_tempdir, shipment: shipment) tiffinfo = compressor.tiffinfo rescue => e add_error Error.new(e.message, image_file.objid, image_file.file) From c304af99c9a7369154f4e93d0c446d947c1595af Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Wed, 10 Dec 2025 10:35:23 -0500 Subject: [PATCH 05/38] WIP refactor --- .rspec | 1 + Gemfile | 1 + Gemfile.lock | 21 ++++++++ lib/compressor.rb | 32 +++++++++--- lib/stage.rb | 4 +- lib/stage/compression.rb | 11 ++-- spec/compression_spec.rb | 12 +++++ spec/spec_helper.rb | 110 +++++++++++++++++++++++++++++++++++++++ test/test_helper.rb | 1 + 9 files changed, 177 insertions(+), 16 deletions(-) create mode 100644 .rspec create mode 100644 spec/compression_spec.rb create mode 100644 spec/spec_helper.rb diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..c99d2e7 --- /dev/null +++ b/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/Gemfile b/Gemfile index ab11a60..353f6f7 100644 --- a/Gemfile +++ b/Gemfile @@ -11,6 +11,7 @@ gem "rake" gem "simplecov" gem "standardrb" gem "rubycritic" +gem "rspec" # This is to deal with warning in /usr/local/lib/ruby/3.4.0/readline.rb:4: # with the 3.4.5 ruby image. We should be able to take this out when we diff --git a/Gemfile.lock b/Gemfile.lock index edc5969..8a42ee6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -17,6 +17,7 @@ GEM concurrent-ruby (1.3.5) descendants_tracker (0.0.4) thread_safe (~> 0.3, >= 0.3.1) + diff-lcs (1.6.2) docile (1.4.1) dotenv (3.2.0) dry-configurable (1.3.0) @@ -90,6 +91,19 @@ GEM reline (0.6.3) io-console (~> 0.5) rexml (3.4.4) + rspec (3.13.2) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.7) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.6) rubocop (1.81.7) json (~> 2.3) language_server-protocol (~> 3.17.0.2) @@ -167,6 +181,7 @@ DEPENDENCIES minitest rake reline + rspec rubycritic simplecov standardrb @@ -181,6 +196,7 @@ CHECKSUMS coercible (1.0.0) sha256=5081ad24352cc8435ce5472bc2faa30260c7ea7f2102cc6a9f167c4d9bffaadc concurrent-ruby (1.3.5) sha256=813b3e37aca6df2a21a3b9f1d497f8cbab24a2b94cab325bffe65ee0f6cbebc6 descendants_tracker (0.0.4) sha256=e9c41dd4cfbb85829a9301ea7e7c48c2a03b26f09319db230e6479ccdc780897 + diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962 docile (1.4.1) sha256=96159be799bfa73cdb721b840e9802126e4e03dfc26863db73647204c727f21e dotenv (3.2.0) sha256=e375b83121ea7ca4ce20f214740076129ab8514cd81378161f11c03853fe619d dry-configurable (1.3.0) sha256=882d862858567fc1210d2549d4c090f34370fc1bb7c5c1933de3fe792e18afa8 @@ -214,6 +230,11 @@ CHECKSUMS regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4 reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835 rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142 + rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587 + rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d + rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836 + rspec-mocks (3.13.7) sha256=0979034e64b1d7a838aaaddf12bf065ea4dc40ef3d4c39f01f93ae2c66c62b1c + rspec-support (3.13.6) sha256=2e8de3702427eab064c9352fe74488cc12a1bfae887ad8b91cba480ec9f8afb2 rubocop (1.81.7) sha256=6fb5cc298c731691e2a414fe0041a13eb1beed7bab23aec131da1bcc527af094 rubocop-ast (1.48.0) sha256=22df9bbf3f7a6eccde0fad54e68547ae1e2a704bf8719e7c83813a99c05d2e76 rubocop-performance (1.25.0) sha256=6f7d03568a770054117a78d0a8e191cefeffb703b382871ca7743831b1a52ec1 diff --git a/lib/compressor.rb b/lib/compressor.rb index 4d1c7f0..c49b48f 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -1,4 +1,21 @@ require "tiff" +require "ostruct" +module ExifTool + def self.remove_tiff_metadata(source:, destination:) + cmd = "exiftool -XMP:All= -MakerNotes:All= #{source} -o #{destination}" + status = Command.new(cmd).run + OpenStruct.new(command: cmd, time: status[:time]) + end + + def self.remove_tiff_alpha(path) + tmp = path + ".alphaoff" + cmd = "convert #{path} -alpha off #{tmp}" + status = Command.new(cmd).run + FileUtils.mv(tmp, path) + OpenStruct.new(command: cmd, time: status[:time]) + end +end + class Compressor attr_reader :tiffinfo, :image_file, :tmpdir def initialize(image_file:, tmpdir:, shipment: "whatever", log: "whatever") @@ -9,15 +26,14 @@ def initialize(image_file:, tmpdir:, shipment: "whatever", log: "whatever") @log = log end - def sparse_path - @sparse ||= File.join(tmpdir, "sparse.tif") + def run + # We don't want any XMP metadata to be copied over on its own. If + # it's been a while since we last ran exiftool, this might take a sec. + @log.log_it ExifTool.remove_tiff_metadata(source: @image_file.path, destination: sparse_path) + # @log.log_it ExifTool.remove_tiff_alpha(sparse_path) if tiffinfo[:alpha] end -end -module ExifTool - def remove_tiff_metadata(source:, destination:) - cmd = "exiftool -XMP:All= -MakerNotes:All= #{path} -o #{destination}" - status = Command.new(cmd).run - OpenStruct.new(command: cmd, time: status[:time]) + def sparse_path + @sparse_path ||= File.join(tmpdir, "sparse.tif") end end diff --git a/lib/stage.rb b/lib/stage.rb index 21abbb8..d3bab49 100755 --- a/lib/stage.rb +++ b/lib/stage.rb @@ -264,8 +264,8 @@ def log(entry, time) @log << entry end - def log_it(entry, time) - log(entry, time) + def log_it(data) + log(data.command, data.time) end def to_json(state = nil, *) diff --git a/lib/stage/compression.rb b/lib/stage/compression.rb index 2ffa024..1526e62 100755 --- a/lib/stage/compression.rb +++ b/lib/stage/compression.rb @@ -23,7 +23,7 @@ def run(agenda) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, @bar.steps = files.count files.each_with_index do |image_file, i| begin - compressor = Compressor.new(image_file: image_file, tmpdir: create_tempdir, shipment: shipment) + compressor = Compressor.new(image_file: image_file, tmpdir: create_tempdir, shipment: shipment, log: log_collection) tiffinfo = compressor.tiffinfo rescue => e add_error Error.new(e.message, image_file.objid, image_file.file) @@ -59,7 +59,8 @@ def handle_8_bps_conversion(compressor) # rubocop:disable Metrics/AbcSize, Metri image_file = compressor.image_file tiffinfo = compressor.tiffinfo tmpdir = compressor.tmpdir - sparse = File.join(tmpdir, "sparse.tif") + sparse = compressor.sparse_path + # sparse = File.join(tmpdir, "sparse.tif") new_image = File.join(tmpdir, "new.jp2") final_image_name = File.basename(image_file.path, ".*") + ".jp2" final_image = File.join(File.dirname(image_file.path), final_image_name) @@ -68,10 +69,8 @@ def handle_8_bps_conversion(compressor) # rubocop:disable Metrics/AbcSize, Metri on_disk_temp_image = final_image.sub(shipment.directory, shipment.tmp_directory) system("mkdir -p #{File.dirname(on_disk_temp_image)}") - # We don't want any XMP metadata to be copied over on its own. If - # it's been a while since we last ran exiftool, this might take a sec. - remove_tiff_metadata(image_file.path, sparse) - + compressor.run + # need a test that looks for this remove_tiff_alpha(sparse) if tiffinfo[:alpha] strip_tiff_profiles(sparse) if tiffinfo[:icc] diff --git a/spec/compression_spec.rb b/spec/compression_spec.rb new file mode 100644 index 0000000..12df7f4 --- /dev/null +++ b/spec/compression_spec.rb @@ -0,0 +1,12 @@ +describe Compression do + before(:each) do + @config = Config.new({no_progress: true}) + end + after(:each) do + TestShipment.remove_test_shipments + end + context "#run" do + it "removes alpha when it exists" do + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..fb6aa19 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,110 @@ +require "dotenv" +require "byebug" + +Dotenv.load +require_relative "../rsvp" +TEST_ROOT = File.expand_path(__dir__) +$LOAD_PATH << TEST_ROOT + +require "test_shipment" + +# clean stuff up. TBD. Should change all of this to use tempfile... +if File.directory? TestShipment::PATH + FileUtils.rm_r(TestShipment::PATH, force: true) +end +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + + # The settings below are suggested to provide a good initial experience + # with RSpec, but feel free to customize to your heart's content. + # # This allows you to limit a spec run to individual examples or groups + # # you care about by tagging them with `:focus` metadata. When nothing + # # is tagged with `:focus`, all examples get run. RSpec also provides + # # aliases for `it`, `describe`, and `context` that include `:focus` + # # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + # config.filter_run_when_matching :focus + # + # # Allows RSpec to persist some state between runs in order to support + # # the `--only-failures` and `--next-failure` CLI options. We recommend + # # you configure your source control system to ignore this file. + # config.example_status_persistence_file_path = "spec/examples.txt" + # + # # Limits the available syntax to the non-monkey patched syntax that is + # # recommended. For more details, see: + # # https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/ + # config.disable_monkey_patching! + # + # # This setting enables warnings. It's recommended, but in some cases may + # # be too noisy due to issues in dependencies. + # config.warnings = true + # + # # Many RSpec users commonly either run the entire suite or an individual + # # file, and it's useful to allow more verbose output when running an + # # individual spec file. + # if config.files_to_run.one? + # # Use the documentation formatter for detailed output, + # # unless a formatter has already been configured + # # (e.g. via a command-line flag). + # config.default_formatter = "doc" + # end + # + # # Print the 10 slowest examples and example groups at the + # # end of the spec run, to help surface which specs are running + # # particularly slow. + # config.profile_examples = 10 + # + # # Run specs in random order to surface order dependencies. If you find an + # # order dependency and want to debug it, you can fix the order by providing + # # the seed, which is printed after each run. + # # --seed 1234 + # config.order = :random + # + # # Seed global randomization in this process using the `--seed` CLI option. + # # Setting this allows you to use `--seed` to deterministically reproduce + # # test failures related to randomization by passing the same `--seed` value + # # as the one that triggered the failure. + # Kernel.srand config.seed +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 53cfa58..d9994e7 100755 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -2,6 +2,7 @@ # frozen_string_literal: true require "dotenv" +require "byebug" Dotenv.load From be52df1b34eda0017dae2c0cb6e9367c3442c362 Mon Sep 17 00:00:00 2001 From: Anthony Thomas Date: Wed, 10 Dec 2025 10:54:57 -0500 Subject: [PATCH 06/38] test Compressor instead of Compression --- spec/{compression_spec.rb => compressor_spec.rb} | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) rename spec/{compression_spec.rb => compressor_spec.rb} (54%) diff --git a/spec/compression_spec.rb b/spec/compressor_spec.rb similarity index 54% rename from spec/compression_spec.rb rename to spec/compressor_spec.rb index 12df7f4..cacbdc6 100644 --- a/spec/compression_spec.rb +++ b/spec/compressor_spec.rb @@ -1,9 +1,7 @@ -describe Compression do +describe Compressor do before(:each) do - @config = Config.new({no_progress: true}) end after(:each) do - TestShipment.remove_test_shipments end context "#run" do it "removes alpha when it exists" do From 6438dd332e5599fc45f55b4d9d6341b54ce3d489 Mon Sep 17 00:00:00 2001 From: Anthony Thomas Date: Wed, 10 Dec 2025 11:28:51 -0500 Subject: [PATCH 07/38] copy fixtures to spec dir --- spec/fixtures/10_10_1_16bps_400.tif | Bin 0 -> 510 bytes spec/fixtures/10_10_1_600.tif | Bin 0 -> 2232 bytes spec/fixtures/10_10_8_400.jp2 | Bin 0 -> 4495 bytes spec/fixtures/10_10_8_400.tif | Bin 0 -> 410 bytes spec/fixtures/10_10_8_400_compressed.tif | Bin 0 -> 2856 bytes spec/fixtures/README.txt | 12 ++++++++++++ spec/fixtures/sRGB2014.icc | Bin 0 -> 3024 bytes 7 files changed, 12 insertions(+) create mode 100644 spec/fixtures/10_10_1_16bps_400.tif create mode 100644 spec/fixtures/10_10_1_600.tif create mode 100644 spec/fixtures/10_10_8_400.jp2 create mode 100644 spec/fixtures/10_10_8_400.tif create mode 100644 spec/fixtures/10_10_8_400_compressed.tif create mode 100644 spec/fixtures/README.txt create mode 100644 spec/fixtures/sRGB2014.icc diff --git a/spec/fixtures/10_10_1_16bps_400.tif b/spec/fixtures/10_10_1_16bps_400.tif new file mode 100644 index 0000000000000000000000000000000000000000..624997f9cdabed513d6403a2b08e7ba7dc6dd537 GIT binary patch literal 510 zcmebD)MB{6z`*c-m;fOL21aHEMxc+lfS3`9%>-o&0L7W1Y>+xOBsLckTab|jYz_xd zjS!N!C=y!?$_Cjb4pn~w$d+Pc1?yc0v_Kll-UejLAnDbBvYCMDHGymp4RkSs9Z)@p z{SK(!9>`_`viE@;3Ir2?G>8eKTVDqPc?}GQzpemDGo3x-1ELurbOQsF&(weDD@a_x QTxtP`X3}m4xmbY#0ElVd6#xJL literal 0 HcmV?d00001 diff --git a/spec/fixtures/10_10_1_600.tif b/spec/fixtures/10_10_1_600.tif new file mode 100644 index 0000000000000000000000000000000000000000..10c7f60db0f5e7d7dddf98cc1b590aa3bebd223e GIT binary patch literal 2232 zcmebD)M9wY&cGnRz`)4Nz{tSBzy-vNNNgr38zje!#AZWcb0M(>8Ck&Q+ykl?LJ}86 zVv9l9AiKn&>P3KTDMnVX-g7{{;Ak1eD~2^s2@F4V|VN^FSG&;qha2@NBl zdTgPAni6PJZ;(<~umNi?Sd8-l*`)YU45*&4ANi?PMj*#2DSo_%sg8%>k literal 0 HcmV?d00001 diff --git a/spec/fixtures/10_10_8_400.jp2 b/spec/fixtures/10_10_8_400.jp2 new file mode 100644 index 0000000000000000000000000000000000000000..0f7d004d49fcdc0b07e0bd39d683a73b8507d5f8 GIT binary patch literal 4495 zcmeH~-*4Mg6vwZ-Zrwt+u`vc4Fw6?LWwo#E^haVPnbI(BN` zG-(kMLP)%_frOCw2aqaw+Y1Q(03MKl7oL#d1@Xc^fE12zeA2GnB=Rtx;;YJ^^Y!uZ zJ?EZOI)sqXTNPP8p7|(42syLHw;Hz^rVLEd`>SxTfqB-exe?qGzYLK^MkD}o(h2=Y z5IF_r`H00b+)u;ZDSk+#q^7BYhZ=7M+-|EuJO&XmvWQWJOe3$sy)clInD%Pe-`9#>ejXxAG) zOA62}=px$?M%VJaQm(rrAmJmnmpTZWSUESXu$oU%;Vz`ZWLmqd5Xv!KdrB|}f$_KNpESL-=eY9SAy+au4J3pk6Q2ll=8V2@sq1r~>XGx^xfz~dz>&_226-aV@1&?;V= z`MnM9*LyF#wHv84rRx+k+N<}R-+qAeUj4r9+3gSbF~iTA_Wn6^n+Uq8kDQA3iRdt|8&>xSjPgG)VioB>25L56i6_f^}{A zCs=mJg*jAu-Cnodrn;HabZr`>pFsk9-ri!7Iv1>kmh6Ys74zLP^SP~-$K#Er9WhrG zv(f}iT!}nCzPbg|)Wmy}GGaznjaki@l;tVSV3%|=5pt<8gF;>i)0&wIrk)D3D8S4I zF=lo>U@8?t4n49$nANf`PV^KdM{X(S9C!O15n2_Jj4ZSSC-?<}wMef29 zq6D^pA%sH+!w82Fjv$QuvwCd!gU%fYvvYSxynhs*9Ye?M|B0csXQhE?^X5D0BnRF(&-G$wk%`mh3F!^SxRTu$q zPQnocU;qFDhh0!t#$o2a{Xj#pr1YY2mJ-_Uis6WQT!~}28&fDkK&)X*k4_d1gdTD2R zJWuYt+n9MdTpNXZ?>DYJ{W4j~-~ISKePRCLRPJ$wQh+QQNTG|HTDphBuS@~p=kZA6 z%R(B@tEKUZkjD4r(s-Few{1K3zw)7>o_8at(@LwgcztemX=#CVno~O(Z~JpJ)=(-m z%>xYJcub8~!qj-JHZ{HpQ)5eRYJ5RJHnHI*qGhTs> zs3A^*{=~_o5GS>G#L00XPDT{shX6=Zh?84#al-!a{P?$0MD-(_#k^~K$2B|e)O*DGJ!ul CfxQa= literal 0 HcmV?d00001 diff --git a/spec/fixtures/README.txt b/spec/fixtures/README.txt new file mode 100644 index 0000000..21d75b9 --- /dev/null +++ b/spec/fixtures/README.txt @@ -0,0 +1,12 @@ +Images in this directory: + +GOOD +==== +10_10_1_600.tif 10x10 1bps 600ppi +10_10_8_400.tif 10x10 8bps 400ppi +10_10_8_400_compressed.tif 10x10 8bps 400ppi +10_10_8_400.jp2 10x10 8bps 400ppi + +BAD +=== +10_10_1_16bps_400.tif 10x10 16bps 400 ppi diff --git a/spec/fixtures/sRGB2014.icc b/spec/fixtures/sRGB2014.icc new file mode 100644 index 0000000000000000000000000000000000000000..49afbfef10f22a1832590b68369d2f248ea553b9 GIT binary patch literal 3024 zcmb`Jc{r5o8^@pboqe;-klom~#=Z=)?<7n1RL0C;EQ4W?v`H$Qlq6e;oU(N2=!6`p zq_j9fq0&N*O8IqkN}I~>9j@P{b6vkb&vRYx^M3C8x$pP6pZoda{Q^K51jvAqCy}2f z2yl0zhlYjIaZeGKxM&3c7CSY0nf@_DE7pfmuw>n3hyBge}>zEF^|hhw$p<`Vm43NktlHVq|Q#Wc`bi=uVbDr*Q%R@mv7f?y!Y| z^kpAf^uhola$__g2b6(2&;bl!0xW?IZ~(5r3;2RS5C%2@Hi!j@Kmam8HrNI7Kmj-i zj(`eK4eCGxXa=pI9dv;!;5xVs2Ehmz2NPf#yasdN16Y6{2nSIhDkKM~K$?&~WCAfE zJIEDU3k5)7P$U!s@gX6U4ef>spkk;3s(~7yU!e=o73d~31U-Nzp&96J=nIU3$uJF8 zg0)~nm^L%}Fw^fA^LPfRE#29trw!<1r9Va{W&VMZ|1m=9PiRtBq$wZwX0 z!?1DKt=K~BF>DL=GIj_%g`LOYaB?_(oGs25$HJxI@^Iz2Gq_8*VcazC6P|=u!JFXS z@ZoqqJ_lclZ^U=whw(4)3j_&*Cc&EEOW+W;5Q+$OgigX8!ZcxlC`r^N+7bhaal~E3 zGGa6F8u1bF9f?FzBUzFBNj%a{QW@zi=>}<%^qDM0)+0NUBgjJX0rF|`W%2{^I|_xO zMRA~nQ_?60C=HaWlqZx=VpK5$F;6j$*bcEuu{N<`u{YubaZPbY@lE1c;-%u}#P5jD zN)RNpB%CE!65AyzB`!#eNz6-9C5m zB-1K0D)VKP(kjPQ+*SKmHLn_8^-)$q);f{g-OAzz_Y;h`d|sHYg9xK;6_V!z_NlCqM!QnFIH(p9BdWf^4$ z67?SSISmyJAB}8{CXI)h1Wl%9tmaY8KFyC>+FBu6d$roNUTVu~dunHC zH)%i8q3GD_r0CS@+|$MCGIis1kLeET!FuL;v3iwycl2R>3w@scG5w*{nAKLR`KxPJ zk1@y$M@BlMi7{y)W3bjB$DrNdjiH8NxZxqgKEv-u=0*udbw=aHQpR4!ImVsFf1Bu; zuuUpW?wL|d-As3wc9_03(>LRq9XGpgPBr&2-)r7u{>{SDLSWHsF=MG=8EIK%ImV{PDV}wr}Iu9ovod>IbU``xwyOJy9~HW zxdypbxIS@HbBl3na+`BEci-xM*#qO@?QzIs%u~se?b+Zt=Vj@&&8yd&?7iN*!u#1; zy|se1oj$OSm(O9JN9#1#@z=Hc0$)$x!@iIGwEa^2e)q@v`}tS;KMybt$PVaRPhG!x zedGEMflh%%f#X3sLBgP(VDaFH;D+FjAub`sArqm7q1!@lhslTW!aBln;lbgj!sj=* zZaA`GI>J06FJg3~_QuSOH#f;|O4xL9v-oD#=5vvl$dJg!$geD4RxN8j$}_4eYL4y9 zKFWU0ap072X1KQ8V(yD*+vwuxmoc_6hht`9?PE)0XL-)N3f|i|kGSf%kMX|or{fnB zLK0dM@rjX%7x+^Acz$n^a#Ci}P_lklKhhQM>Ze1S!z~VeUx}qcyyv{ZCOXTM)|?=uNAQsBb82-EmewD`>Q@4 z;~X14?r^-hTB*9A`pXI4iTgF~HEp$8wWTMqC(}uM0h$Hl62xH~9T@mugq#Md^!0-Nf$P?!`-4 zm*y`gU!J`Zb7iV$bIqdq~gGTRGoA)2d|5UxvdGp&}4uAE}h0aaC6}(;i zyYQXdyVLK@-uKM=%|H2&_+jB={wKLl^`Dua`@V#Hd9jf375BC5o9?&H@7~`ZEha85 z{-8k&JYAjX7RFW<77P=HG2Mk5%@QW0(M8J6IVmAYD4?%TX0f?+23;gpmIcJWHm~TE zsB!?>_W&UKaK(pgBT{F`Sk`1q_=ApIvi~>1Kja-poFc8Ycg2@f3jlK-0Mx-$UJPB7 zEaT{Co~CjhDoy^Z4|Cv`LizZ;q8ZSF~{&Hxtp1 zNS#T^TLiqA*fhE)KaDHkvqTlK5|(a9AgVDnNsz`9Ca$I)0svP6z_+5s#f6&1#cxP2P~!kx7XBBF2+<<| literal 0 HcmV?d00001 From 0085d5d48db8b54c2257c6249df7849a73c08521 Mon Sep 17 00:00:00 2001 From: Anthony Thomas Date: Wed, 10 Dec 2025 11:50:15 -0500 Subject: [PATCH 08/38] add rspec-temp_dir and ostruct to gems --- Gemfile | 2 ++ Gemfile.lock | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/Gemfile b/Gemfile index 353f6f7..3a8c8a8 100644 --- a/Gemfile +++ b/Gemfile @@ -7,11 +7,13 @@ gem "byebug" gem "dotenv" gem "minitest" gem "luhn-ruby" +gem "ostruct" gem "rake" gem "simplecov" gem "standardrb" gem "rubycritic" gem "rspec" +gem "rspec-temp_dir" # This is to deal with warning in /usr/local/lib/ruby/3.4.0/readline.rb:4: # with the 3.4.5 ruby image. We should be able to take this out when we diff --git a/Gemfile.lock b/Gemfile.lock index 8a42ee6..051f874 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -71,6 +71,7 @@ GEM logger (1.7.0) luhn-ruby (1.0.0) minitest (5.26.2) + ostruct (0.6.1) parallel (1.27.0) parser (3.3.10.0) ast (~> 2.4.1) @@ -104,6 +105,8 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-support (3.13.6) + rspec-temp_dir (1.1.2) + rspec (>= 3.0) rubocop (1.81.7) json (~> 2.3) language_server-protocol (~> 3.17.0.2) @@ -179,9 +182,11 @@ DEPENDENCIES dotenv luhn-ruby minitest + ostruct rake reline rspec + rspec-temp_dir rubycritic simplecov standardrb @@ -218,6 +223,7 @@ CHECKSUMS logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 luhn-ruby (1.0.0) sha256=bc9979c2b37eb14c0c15463aa6a9972a4fd07ddd272b8abefa2877fe5c43e4a8 minitest (5.26.2) sha256=f021118a6185b9ba9f5af71f2ba103ad770c75afde9f2ab8da512677c550cde3 + ostruct (0.6.1) parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130 parser (3.3.10.0) sha256=ce3587fa5cc55a88c4ba5b2b37621b3329aadf5728f9eafa36bbd121462aabd6 path_expander (1.1.3) sha256=bea16440dea5a770b9765312c8037931cc576f4f2872d71133a3e480028f9f67 @@ -235,6 +241,7 @@ CHECKSUMS rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836 rspec-mocks (3.13.7) sha256=0979034e64b1d7a838aaaddf12bf065ea4dc40ef3d4c39f01f93ae2c66c62b1c rspec-support (3.13.6) sha256=2e8de3702427eab064c9352fe74488cc12a1bfae887ad8b91cba480ec9f8afb2 + rspec-temp_dir (1.1.2) sha256=92ccd30b8f4c80632a67cc4bbd6701b7b9d34b410d5bfc1fb6a095c5e6d8ce0a rubocop (1.81.7) sha256=6fb5cc298c731691e2a414fe0041a13eb1beed7bab23aec131da1bcc527af094 rubocop-ast (1.48.0) sha256=22df9bbf3f7a6eccde0fad54e68547ae1e2a704bf8719e7c83813a99c05d2e76 rubocop-performance (1.25.0) sha256=6f7d03568a770054117a78d0a8e191cefeffb703b382871ca7743831b1a52ec1 From 22877cb9e22e2287da911653c46c0752174c583f Mon Sep 17 00:00:00 2001 From: Anthony Thomas Date: Wed, 10 Dec 2025 11:51:33 -0500 Subject: [PATCH 09/38] WIP start testing Compressor --- lib/compressor.rb | 7 ++++--- lib/stage/compression.rb | 2 +- spec/compressor_spec.rb | 9 +++++---- spec/spec_helper.rb | 9 ++++----- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/compressor.rb b/lib/compressor.rb index c49b48f..506f3fe 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -6,7 +6,9 @@ def self.remove_tiff_metadata(source:, destination:) status = Command.new(cmd).run OpenStruct.new(command: cmd, time: status[:time]) end +end +module ImageMagick def self.remove_tiff_alpha(path) tmp = path + ".alphaoff" cmd = "convert #{path} -alpha off #{tmp}" @@ -18,11 +20,10 @@ def self.remove_tiff_alpha(path) class Compressor attr_reader :tiffinfo, :image_file, :tmpdir - def initialize(image_file:, tmpdir:, shipment: "whatever", log: "whatever") + def initialize(image_file:, tmpdir:, log: "whatever") @image_file = image_file @tiffinfo = TIFF.new(image_file.path).info @tmpdir = tmpdir - @shipment = shipment @log = log end @@ -30,7 +31,7 @@ def run # We don't want any XMP metadata to be copied over on its own. If # it's been a while since we last ran exiftool, this might take a sec. @log.log_it ExifTool.remove_tiff_metadata(source: @image_file.path, destination: sparse_path) - # @log.log_it ExifTool.remove_tiff_alpha(sparse_path) if tiffinfo[:alpha] + # @log.log_it ImageMagick.remove_tiff_alpha(sparse_path) if tiffinfo[:alpha] end def sparse_path diff --git a/lib/stage/compression.rb b/lib/stage/compression.rb index 1526e62..c8d7fc5 100755 --- a/lib/stage/compression.rb +++ b/lib/stage/compression.rb @@ -23,7 +23,7 @@ def run(agenda) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, @bar.steps = files.count files.each_with_index do |image_file, i| begin - compressor = Compressor.new(image_file: image_file, tmpdir: create_tempdir, shipment: shipment, log: log_collection) + compressor = Compressor.new(image_file: image_file, tmpdir: create_tempdir, log: log_collection) tiffinfo = compressor.tiffinfo rescue => e add_error Error.new(e.message, image_file.objid, image_file.file) diff --git a/spec/compressor_spec.rb b/spec/compressor_spec.rb index cacbdc6..d4a3ebe 100644 --- a/spec/compressor_spec.rb +++ b/spec/compressor_spec.rb @@ -1,10 +1,11 @@ describe Compressor do - before(:each) do - end - after(:each) do - end + include_context "uses temp dir" + context "#run" do it "removes alpha when it exists" do + image_file = double("image_file", path: "spec/fixtures/10_10_8_400.tif") + compressor = Compressor.new(image_file: image_file, tmpdir: Pathname(temp_dir)) + expect(compressor).not_to be_nil end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index fb6aa19..dc9367e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,17 +1,16 @@ require "dotenv" require "byebug" +require "rspec/temp_dir" Dotenv.load require_relative "../rsvp" TEST_ROOT = File.expand_path(__dir__) $LOAD_PATH << TEST_ROOT -require "test_shipment" +require "processor" + +@config = Config.new({no_progress: true}) -# clean stuff up. TBD. Should change all of this to use tempfile... -if File.directory? TestShipment::PATH - FileUtils.rm_r(TestShipment::PATH, force: true) -end # This file was generated by the `rspec --init` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. # The generated `.rspec` file contains `--require spec_helper` which will cause From 06b722f96c68f961344758e0e3a5ebe4a78c9890 Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Mon, 15 Dec 2025 12:13:21 -0500 Subject: [PATCH 10/38] test remove_tiff_alpha in ImageMagick module --- Gemfile.lock | 2 +- spec/compressor_spec.rb | 16 +++++++++++++++- spec/fixtures/10_10_8_400_alpha.tif | Bin 0 -> 522 bytes 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 spec/fixtures/10_10_8_400_alpha.tif diff --git a/Gemfile.lock b/Gemfile.lock index 051f874..b72d0d7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -223,7 +223,7 @@ CHECKSUMS logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 luhn-ruby (1.0.0) sha256=bc9979c2b37eb14c0c15463aa6a9972a4fd07ddd272b8abefa2877fe5c43e4a8 minitest (5.26.2) sha256=f021118a6185b9ba9f5af71f2ba103ad770c75afde9f2ab8da512677c550cde3 - ostruct (0.6.1) + ostruct (0.6.1) sha256=09a3fb7ecc1fa4039f25418cc05ae9c82bd520472c5c6a6f515f03e4988cb817 parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130 parser (3.3.10.0) sha256=ce3587fa5cc55a88c4ba5b2b37621b3329aadf5728f9eafa36bbd121462aabd6 path_expander (1.1.3) sha256=bea16440dea5a770b9765312c8037931cc576f4f2872d71133a3e480028f9f67 diff --git a/spec/compressor_spec.rb b/spec/compressor_spec.rb index d4a3ebe..7885b04 100644 --- a/spec/compressor_spec.rb +++ b/spec/compressor_spec.rb @@ -3,9 +3,23 @@ context "#run" do it "removes alpha when it exists" do - image_file = double("image_file", path: "spec/fixtures/10_10_8_400.tif") + image_file = double("image_file", path: "spec/fixtures/10_10_8_400_alpha.tif") + # image_file = double("image_file", path: "input/output-alpha2.tiff") compressor = Compressor.new(image_file: image_file, tmpdir: Pathname(temp_dir)) expect(compressor).not_to be_nil end end end + +describe ImageMagick do + include_context "uses temp dir" + context "#remove_tiff_alpha" do + it "removes the alpha channel if it exists" do + tiff_path = "#{temp_dir}/input.tif" + FileUtils.copy("spec/fixtures/10_10_8_400_alpha.tif", tiff_path) + expect(TIFF.new(tiff_path).info[:alpha]).to eq(true) + ImageMagick.remove_tiff_alpha(tiff_path) + expect(TIFF.new(tiff_path).info[:alpha]).to eq(false) + end + end +end diff --git a/spec/fixtures/10_10_8_400_alpha.tif b/spec/fixtures/10_10_8_400_alpha.tif new file mode 100644 index 0000000000000000000000000000000000000000..f0f471d345ca2595f4415f36ccc81d2958f07b40 GIT binary patch literal 522 zcmebD)MB{6z`*c-m;hl021aHEMxc+lfS3`9%>-mK0mV3gkQpitQpbkG=0ai%GO~cp z;Q*=;LJ}8+vO#8vLD?X?#G&d>0NGNEtYE#nfEGwY*@u8^86 Date: Tue, 16 Dec 2025 10:57:17 -0500 Subject: [PATCH 11/38] properly test Compressor removing alpha when exists --- lib/compressor.rb | 2 +- lib/stage.rb | 5 ++++- spec/compressor_spec.rb | 16 +++++++++++++--- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/compressor.rb b/lib/compressor.rb index 506f3fe..028943e 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -31,7 +31,7 @@ def run # We don't want any XMP metadata to be copied over on its own. If # it's been a while since we last ran exiftool, this might take a sec. @log.log_it ExifTool.remove_tiff_metadata(source: @image_file.path, destination: sparse_path) - # @log.log_it ImageMagick.remove_tiff_alpha(sparse_path) if tiffinfo[:alpha] + @log.log_it ImageMagick.remove_tiff_alpha(sparse_path) if tiffinfo[:alpha] end def sparse_path diff --git a/lib/stage.rb b/lib/stage.rb index d3bab49..45ccc52 100755 --- a/lib/stage.rb +++ b/lib/stage.rb @@ -102,7 +102,6 @@ def add_error(err) end def add_warning(err) - raise "#{err.class} passed to add_warning" unless err.is_a? Error unless err.objid.nil? || objids.member?(err.objid) raise "unknown warning objid #{err.objid}" end @@ -259,6 +258,10 @@ def each end end + def entries + @log + end + def log(entry, time) entry += format(" (%.3f sec)", time) unless time.nil? @log << entry diff --git a/spec/compressor_spec.rb b/spec/compressor_spec.rb index 7885b04..fe566b5 100644 --- a/spec/compressor_spec.rb +++ b/spec/compressor_spec.rb @@ -4,9 +4,19 @@ context "#run" do it "removes alpha when it exists" do image_file = double("image_file", path: "spec/fixtures/10_10_8_400_alpha.tif") - # image_file = double("image_file", path: "input/output-alpha2.tiff") - compressor = Compressor.new(image_file: image_file, tmpdir: Pathname(temp_dir)) - expect(compressor).not_to be_nil + log = Stage::Log.new + compressor = Compressor.new(image_file: image_file, tmpdir: Pathname(temp_dir), log: log) + compressor.run + expect(log.entries).not_to be_empty + expect(log.entries).to include(match("-alpha off")) + end + + it "ignores alpha when it doesn't exist" do + image_file = double("image_file", path: "spec/fixtures/10_10_8_400.tif") + log = Stage::Log.new + compressor = Compressor.new(image_file: image_file, tmpdir: Pathname(temp_dir), log: log) + compressor.run + expect(log.entries).not_to include(match("-alpha off")) end end end From 26867b8239bef0a111bc888e429cf591cff4550e Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Tue, 16 Dec 2025 17:27:53 -0500 Subject: [PATCH 12/38] WIP warnings and errors are their own class --- lib/stage.rb | 120 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 92 insertions(+), 28 deletions(-) diff --git a/lib/stage.rb b/lib/stage.rb index 45ccc52..f19fad6 100755 --- a/lib/stage.rb +++ b/lib/stage.rb @@ -1,4 +1,4 @@ -#!/usr/bin/env ruby +# !/usr/bin/env ruby # frozen_string_literal: true require "json" @@ -46,9 +46,10 @@ def initialize(shipment, **args) # rubocop:disable Metrics/AbcSize, Metrics/Cycl @shipment = shipment @name = args[:name] || self.class.to_s @config = args[:config] || {} # Top-level Config object - @errors = args[:errors] || [] # Fatal conditions, Array of Error - @warnings = args[:warnings] || [] # Nonfatal conditions, Array of Error - @data = args[:data] || {} # Misc data structure including log + @data = args[:data] || {log: Log.new} # Misc data structure including log + if @data[:log].class.to_s == "Array" + @data[:log] = Log.new(@data[:log]) + end # Time the stage was last run @start = if args[:start].to_s == "" nil @@ -67,12 +68,15 @@ def initialize(shipment, **args) # rubocop:disable Metrics/AbcSize, Metrics/Cycl else ProgressBar.new(self.class) end + @errors = Errors.new(bar: @bar, objids: objids, list: args[:errors]) + @warnings = Warnings.new(bar: @bar, objids: objids, list: args[:errors]) + # @warnings = args[:warnings] || [] # Nonfatal conditions, Array of Error end # Get rid of errors, warnings, and anything that may have been memoized def reinitialize! - @errors = [] - @warnings = [] + @errors = Errors.new(bar: @bar, objids: objids) + @warnings = Warnings.new(bar: @bar, objids: objids) @bar.done = nil end @@ -92,22 +96,24 @@ def run(_agenda) end def add_error(err) - raise "#{err.class} passed to add_error" unless err.is_a? Error - unless err.objid.nil? || objids.member?(err.objid) - raise "unknown error objid #{err.objid}" - end + @errors.add(err) + # raise "#{err.class} passed to add_error" unless err.is_a? Error + # unless err.objid.nil? || objids.member?(err.objid) + # raise "unknown error objid #{err.objid}" + # end - @bar.error = true - @errors << err + # @bar.error = true + # @errors << err end def add_warning(err) - unless err.objid.nil? || objids.member?(err.objid) - raise "unknown warning objid #{err.objid}" - end + @warnings.add(err) + # unless err.objid.nil? || objids.member?(err.objid) + # raise "unknown warning objid #{err.objid}" + # end - @bar.warning = true - @warnings << err + # @bar.warning = true + # @warnings << err end # Map of objids + nil -> [Errors] @@ -135,14 +141,6 @@ def fatal_error? errors_by_objid.key? nil end - def delete_errors_for_objid(objid) - @errors.delete_if { |err| err.objid == objid } - end - - def delete_warnings_for_objid(objid) - @warnings.delete_if { |err| err.objid == objid } - end - # OK to make destructive changes to the shipment for this objid? # With nil objid checks for presence of any error. def make_changes?(objid = nil) @@ -218,7 +216,7 @@ def delete_on_success(path, objid = nil) end def objids - @objids ||= shipment.objids + @objids ||= shipment&.objids || [] end def log_collection @@ -248,8 +246,8 @@ def image_files(type = "tif") class Log include Enumerable - def initialize - @log = [] + def initialize(log = []) + @log = log end def each @@ -275,4 +273,70 @@ def to_json(state = nil, *) JSON::State.from_state(state).generate(@log) end end + + class Exceptions + include Enumerable + + attr_reader :list, :objids + + def initialize(bar:, objids:, list: nil) + @list = list || [] + @bar = bar + @objids = objids + end + + def each + @list.each do |line| + yield line + end + end + + def [](index) + @list[index] + end + + def []=(index, value) + @list[index] = value + end + + def to_json(state = nil, *) + JSON::State.from_state(state).generate(@list) + end + + def add(err) + unless err.objid.nil? || objids.member?(err.objid) + raise "unknown #{kind} objid #{err.objid}" + end + set_bar + @list << err + end + + def kind + raise NotImplementedError + end + + def set_bar + raise NotImplementedError + end + end + + class Errors < Exceptions + def kind + :error + end + + def set_bar + @bar.error = true + end + end + + class Warnings < Exceptions + def kind + :warning + end + + def set_bar + @bar.warning = true + end + end end From 8e400482f198e7d08da795c867a83579d269d2b2 Mon Sep 17 00:00:00 2001 From: Anthony Thomas Date: Wed, 17 Dec 2025 11:03:28 -0500 Subject: [PATCH 13/38] add icc profile test; refactor tests --- lib/compressor.rb | 9 ++++++ spec/compressor_spec.rb | 46 +++++++++++++++++++++++++----- spec/fixtures/10_10_8_400_icc.tif | Bin 0 -> 3446 bytes 3 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 spec/fixtures/10_10_8_400_icc.tif diff --git a/lib/compressor.rb b/lib/compressor.rb index 028943e..b6a9ed0 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -16,6 +16,14 @@ def self.remove_tiff_alpha(path) FileUtils.mv(tmp, path) OpenStruct.new(command: cmd, time: status[:time]) end + + def self.strip_tiff_profiles(path) + tmp = path + ".stripped" + cmd = "convert #{path} -strip #{tmp}" + status = Command.new(cmd).run + FileUtils.mv(tmp, path) + OpenStruct.new(command: cmd, time: status[:time]) + end end class Compressor @@ -32,6 +40,7 @@ def run # it's been a while since we last ran exiftool, this might take a sec. @log.log_it ExifTool.remove_tiff_metadata(source: @image_file.path, destination: sparse_path) @log.log_it ImageMagick.remove_tiff_alpha(sparse_path) if tiffinfo[:alpha] + @log.log_it ImageMagick.strip_tiff_profiles(sparse_path) if tiffinfo[:icc] end def sparse_path diff --git a/spec/compressor_spec.rb b/spec/compressor_spec.rb index fe566b5..a4254e2 100644 --- a/spec/compressor_spec.rb +++ b/spec/compressor_spec.rb @@ -2,21 +2,40 @@ include_context "uses temp dir" context "#run" do - it "removes alpha when it exists" do - image_file = double("image_file", path: "spec/fixtures/10_10_8_400_alpha.tif") - log = Stage::Log.new + let(:log) { Stage::Log.new } + + before(:each) do + @path = "spec/fixtures/10_10_8_400.tif" + end + + subject do + image_file = double("image_file", path: @path) compressor = Compressor.new(image_file: image_file, tmpdir: Pathname(temp_dir), log: log) compressor.run - expect(log.entries).not_to be_empty + end + + it "removes alpha when it exists" do + @path = "spec/fixtures/10_10_8_400_alpha.tif" + subject expect(log.entries).to include(match("-alpha off")) end it "ignores alpha when it doesn't exist" do - image_file = double("image_file", path: "spec/fixtures/10_10_8_400.tif") - log = Stage::Log.new + subject + expect(log.entries).not_to include(match("-alpha off")) + end + + it "strips tiff profile data when it exists" do + @path = "spec/fixtures/10_10_8_400_icc.tif" + subject + expect(log.entries).to include(match("-strip")) + end + + it "ignores tiff profile when it doesn't exist" do + image_file = double("image_file", path: @path) compressor = Compressor.new(image_file: image_file, tmpdir: Pathname(temp_dir), log: log) compressor.run - expect(log.entries).not_to include(match("-alpha off")) + expect(log.entries).not_to include(match("-strip")) end end end @@ -32,4 +51,17 @@ expect(TIFF.new(tiff_path).info[:alpha]).to eq(false) end end + + context "#strip_tiff_profiles" do + it "strips tiff profile data" do + tiff_path = "#{temp_dir}/input.tif" + FileUtils.copy("spec/fixtures/10_10_8_400_icc.tif", tiff_path) + expect(TIFF.new(tiff_path).info[:icc]).to eq(true) + ImageMagick.strip_tiff_profiles(tiff_path) + expect(TIFF.new(tiff_path).info[:icc]).to eq(false) + end + + xit "gives a warning if tiff profile strip fails" do + end + end end diff --git a/spec/fixtures/10_10_8_400_icc.tif b/spec/fixtures/10_10_8_400_icc.tif new file mode 100644 index 0000000000000000000000000000000000000000..29560e0b35ee56a88c618d6c6212892db15323f8 GIT binary patch literal 3446 zcmchaXH-+!7RS%MH@(mjdJDaVCMAIM8W9mhnvIYI2qi!ufW09oFrtWvfFj7Chy#ue zMMhB(#exX-F36ywJ_kn;6_s}{FiYmk`!wsFweH!!v+v&b>{HgtKU}UUNCE&%{vR3x z00;pPIvWLjpjiyhFbVY|GmOO%W-(o0O!XJW3SRISR?L)h%xs$~ik`O;n zRzzeJi})0E$zpE+;PKN_r|BR4OhuUQ+8=c6TiqcQv#baIrj#V}(+L%dKv1ptW_H;W~+Sdc70x1M|njo%tKl}lNOl?96%ajVyJPNiid081)JT(G~ z;ipP4#xAu3Kc0uiP-{^(pROMzw2*Xk18uCVZKp>6Vuzi{^yl;s03e$KK+AQ$RFXOE z!(j%GUVd4?1gby_=mBHE1~$L}I0JX!1LlDs5DFr}a=-`iAQ?zN7FYu|f?QAlc7WZW z98`f?P!AfxanK4*fpg#@xC;8fZSW8bfMM_&jDn9~0)ikSM28rVDx?h=KxU8)#DQEP zA80-l0!2d6P#h$Nq|h2@GgJT-L*-C4R1f_MorKOom!W>>J~Rl8Kz~7BU<9VYOjr{( zge_qX>;VVBq3|*|4o-tt!8!1DxD2j=kHRhRIru7k8yIp(-czYmU8*BBm@1-fpq5dOQ7=&ksh?>IG*g-vZ7EGk+fF+~J4?Gydrzm+4e9Rm zD0&8cJH4KMp8kaXNroX~A;Xms$gG#CkZF;*A@f$2ENdX^A-i05m29c(3EAtiZ{)~w zhH_qVJh^pp6>=x#?#hkHGvux1gXELt3+0cdxwX^?dc?>JKzv4O5K}jnx_lHF`A0G_^GSG*dPAYM$48qot(fu9cuw zqSc`_qRr5D)fQ=&XrIx3#ZqK>u##A1tS;85j)soEj#Q^wr&nh}*GPA2)*M%-&37w$H5J9B=MuE;g?*zsshv zJ=mG-2KJzZlEr+BO%|;dZ!PsLqbzq?c3Xb8va?FEsGLEWh8BfBTx`5py0|k;Hf(8Feb{)ocX)C5K!jz)+K8)>YLTMIwkTp$SX5oq*b<*5 zyO#_vwOg9I^v*J)W!cLvFIQWhwEXl6*%iDMC!+Du;n7E;zw&~3HN3GHZcJs&DBp{} zhyPmOE+`R<2wjB5!WXeFvBj}3<6Po)#f`+f#h1pv5qXKqMeh>)6RHx%6N3^DB~B)V zCp9ILlB1JPiWS6(;;s~}l_e%V&$TdsdpR+_v(?D&JKnet3i5hQk{v8xuEP+hnz=c+-c?;hWoYG;=oO3~lk-(vT~YE6Kf^=bTry6}MHi zwRhW`ZF{$U&lluhDX=OiE%;iU3f-dki}w0{R-hj_=Goo+i17t0r~ zDjwPuxT~dvRgz!wVfTvNmrHF+tM*X#NcRks%`a=)tG{>0-mm5H<+m$5E1D`bDz{aR z?-T61vEO}vW0huALDiQ7q67D;{i<7P3~Ndc5)Nh_d|n$?+x;`==lVLex`MiIhs1{l z4u>4>s&}ZbKcaD@r~z(}HjErydbIBs?_b&)*^SjrN=*gFz%l8uSHDL8dgpk+@y-*R z6OF$a{8rh_XwGkeT2{7KC^GH`doGYzY z?XR}<*!47Dv%A*ZYv0>)-Qjv$pJQKp|J?qL8*Vqw-SobB@fP>i)!Peh_upB3=ic3= zcL(kX?!CO9bpPFh%m)(>H$1{W%6}~Pxb%s}lbWX{Pn!np2Tl)q5B5A;^z7jff9Ule z>3@73&UsFMUiw1&Mg2>gm#0R2NBUkZc{Thx_4UM?-2ce`r}C}w+u#0l|FdT_Vs!YO zu5E*prXRA1B81KdF8?{F(E)`%Czj7ZX`uiC@dUnS5*i9`OCq zWb)+XEDd78_3>iaTRYm@vu)VcEN@{fPnwj$!jzp?N>Ykc!djHd;|p1A7A97v1;oGB z|IjHhdjV+O0)WbcA3k&%Or4=^T9ZxTEFC#D{=0)`H_MA(V)DSVg>~snd~QHKwOXGDOm3a~7&3?a==Kv2$#uYJ+Ir hsLXphIr-iY0AdLM--;$DC#okWzoC19JOtpZ^uJK`O#T1> literal 0 HcmV?d00001 From 6c725a16e660b1726eb55449ce5e98b839a3963a Mon Sep 17 00:00:00 2001 From: Anthony Thomas Date: Fri, 19 Dec 2025 15:06:20 -0500 Subject: [PATCH 14/38] test that log_it handles warnings (via Compressor) --- lib/compressor.rb | 22 ++++++++++++++-------- lib/stage.rb | 20 +++++++++++++++++--- spec/compressor_spec.rb | 29 +++++++++++++++-------------- 3 files changed, 46 insertions(+), 25 deletions(-) diff --git a/lib/compressor.rb b/lib/compressor.rb index b6a9ed0..843e1c3 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -4,7 +4,7 @@ module ExifTool def self.remove_tiff_metadata(source:, destination:) cmd = "exiftool -XMP:All= -MakerNotes:All= #{source} -o #{destination}" status = Command.new(cmd).run - OpenStruct.new(command: cmd, time: status[:time]) + OpenStruct.new(command: cmd, time: status[:time], level: :info) end end @@ -14,15 +14,21 @@ def self.remove_tiff_alpha(path) cmd = "convert #{path} -alpha off #{tmp}" status = Command.new(cmd).run FileUtils.mv(tmp, path) - OpenStruct.new(command: cmd, time: status[:time]) + OpenStruct.new(command: cmd, time: status[:time], level: :info) end def self.strip_tiff_profiles(path) tmp = path + ".stripped" cmd = "convert #{path} -strip #{tmp}" - status = Command.new(cmd).run - FileUtils.mv(tmp, path) - OpenStruct.new(command: cmd, time: status[:time]) + begin + status = Command.new(cmd).run + rescue => e + warning = "couldn't remove ICC profile (#{cmd}) (#{e.message})" + OpenStruct.new(error: Error.new(warning, objid_from_path(path), path), level: :warning) + else + FileUtils.mv(tmp, path) + OpenStruct.new(command: cmd, time: status[:time], level: :info) + end end end @@ -35,12 +41,12 @@ def initialize(image_file:, tmpdir:, log: "whatever") @log = log end - def run + def run(image_magick = ImageMagick) # We don't want any XMP metadata to be copied over on its own. If # it's been a while since we last ran exiftool, this might take a sec. @log.log_it ExifTool.remove_tiff_metadata(source: @image_file.path, destination: sparse_path) - @log.log_it ImageMagick.remove_tiff_alpha(sparse_path) if tiffinfo[:alpha] - @log.log_it ImageMagick.strip_tiff_profiles(sparse_path) if tiffinfo[:icc] + @log.log_it image_magick.remove_tiff_alpha(sparse_path) if tiffinfo[:alpha] + @log.log_it image_magick.strip_tiff_profiles(sparse_path) if tiffinfo[:icc] end def sparse_path diff --git a/lib/stage.rb b/lib/stage.rb index f19fad6..7ffb123 100755 --- a/lib/stage.rb +++ b/lib/stage.rb @@ -246,8 +246,9 @@ def image_files(type = "tif") class Log include Enumerable - def initialize(log = []) + def initialize(log: [], warnings: Warnings.new) @log = log + @warnings = warnings end def each @@ -260,13 +261,26 @@ def entries @log end + def warnings + @warnings.list + end + def log(entry, time) entry += format(" (%.3f sec)", time) unless time.nil? @log << entry end def log_it(data) - log(data.command, data.time) + case(data.level) + when :info + log(data.command, data.time) + when :warning + add_warning(data.error) + end + end + + def add_warning(warning) + @warnings.add(warning) end def to_json(state = nil, *) @@ -279,7 +293,7 @@ class Exceptions attr_reader :list, :objids - def initialize(bar:, objids:, list: nil) + def initialize(bar: SilentProgressBar.new, objids: [], list: nil) @list = list || [] @bar = bar @objids = objids diff --git a/spec/compressor_spec.rb b/spec/compressor_spec.rb index a4254e2..a934533 100644 --- a/spec/compressor_spec.rb +++ b/spec/compressor_spec.rb @@ -3,40 +3,44 @@ context "#run" do let(:log) { Stage::Log.new } + let(:compressor) do + image_file = double("image_file", path: @path) + Compressor.new(image_file: image_file, tmpdir: Pathname(temp_dir), log: log) + end before(:each) do @path = "spec/fixtures/10_10_8_400.tif" end - subject do - image_file = double("image_file", path: @path) - compressor = Compressor.new(image_file: image_file, tmpdir: Pathname(temp_dir), log: log) - compressor.run - end - it "removes alpha when it exists" do @path = "spec/fixtures/10_10_8_400_alpha.tif" - subject + compressor.run expect(log.entries).to include(match("-alpha off")) end it "ignores alpha when it doesn't exist" do - subject + compressor.run expect(log.entries).not_to include(match("-alpha off")) end it "strips tiff profile data when it exists" do @path = "spec/fixtures/10_10_8_400_icc.tif" - subject + compressor.run expect(log.entries).to include(match("-strip")) end it "ignores tiff profile when it doesn't exist" do - image_file = double("image_file", path: @path) - compressor = Compressor.new(image_file: image_file, tmpdir: Pathname(temp_dir), log: log) compressor.run expect(log.entries).not_to include(match("-strip")) end + + it "handles warnings" do + @path = "spec/fixtures/10_10_8_400_icc.tif" + error = instance_double(Error, objid: nil) + image_magick = class_double(ImageMagick, remove_tiff_alpha: nil, strip_tiff_profiles: OpenStruct.new(error: error, level: :warning)) + compressor.run(image_magick) + expect(log.warnings.first).to eq(error) + end end end @@ -60,8 +64,5 @@ ImageMagick.strip_tiff_profiles(tiff_path) expect(TIFF.new(tiff_path).info[:icc]).to eq(false) end - - xit "gives a warning if tiff profile strip fails" do - end end end From 70e3a3d741972cf7ae6687f5dd0aaf99521a7a22 Mon Sep 17 00:00:00 2001 From: Anthony Thomas Date: Fri, 19 Dec 2025 15:27:03 -0500 Subject: [PATCH 15/38] split Log into its own file (with Exceptions too) always initialize Log with Warnings; don't lazy-load it --- lib/log.rb | 111 ++++++++++++++++++++++++++++++++ lib/stage.rb | 136 ++++------------------------------------ spec/compressor_spec.rb | 2 +- 3 files changed, 125 insertions(+), 124 deletions(-) create mode 100644 lib/log.rb diff --git a/lib/log.rb b/lib/log.rb new file mode 100644 index 0000000..43463d6 --- /dev/null +++ b/lib/log.rb @@ -0,0 +1,111 @@ + +class Log + include Enumerable + + def initialize(log: nil, warnings: Warnings.new) + @log = log || [] + @warnings = warnings + end + + def each + @log.each do |line| + yield line + end + end + + def entries + @log + end + + def warnings + @warnings.list + end + + def log(entry, time) + entry += format(" (%.3f sec)", time) unless time.nil? + @log << entry + end + + def log_it(data) + case(data.level) + when :info + log(data.command, data.time) + when :warning + add_warning(data.error) + end + end + + def add_warning(warning) + @warnings.add(warning) + end + + def to_json(state = nil, *) + JSON::State.from_state(state).generate(@log) + end +end + +class Exceptions + include Enumerable + + attr_reader :list, :objids + + def initialize(bar: SilentProgressBar.new, objids: [], list: nil) + @list = list || [] + @bar = bar + @objids = objids + end + + def each + @list.each do |line| + yield line + end + end + + def [](index) + @list[index] + end + + def []=(index, value) + @list[index] = value + end + + def to_json(state = nil, *) + JSON::State.from_state(state).generate(@list) + end + + def add(err) + unless err.objid.nil? || objids.member?(err.objid) + raise "unknown #{kind} objid #{err.objid}" + end + set_bar + @list << err + end + + def kind + raise NotImplementedError + end + + def set_bar + raise NotImplementedError + end +end + +class Errors < Exceptions + def kind + :error + end + + def set_bar + @bar.error = true + end +end + +class Warnings < Exceptions + def kind + :warning + end + + def set_bar + @bar.warning = true + end +end diff --git a/lib/stage.rb b/lib/stage.rb index 7ffb123..2829b87 100755 --- a/lib/stage.rb +++ b/lib/stage.rb @@ -9,6 +9,7 @@ require "error" require "progress_bar" require "symbolize" +require "log" # Base class for conversion stages class Stage # rubocop:disable Metrics/ClassLength @@ -46,9 +47,17 @@ def initialize(shipment, **args) # rubocop:disable Metrics/AbcSize, Metrics/Cycl @shipment = shipment @name = args[:name] || self.class.to_s @config = args[:config] || {} # Top-level Config object - @data = args[:data] || {log: Log.new} # Misc data structure including log - if @data[:log].class.to_s == "Array" - @data[:log] = Log.new(@data[:log]) + @bar = if config[:no_progress] + SilentProgressBar.new(self.class) + else + ProgressBar.new(self.class) + end + @errors = Errors.new(bar: @bar, objids: objids, list: args[:errors]) + @warnings = Warnings.new(bar: @bar, objids: objids, list: args[:errors]) + + @data = args[:data] || {log: Log.new(warnings: @warnings)} # Misc data structure including log + if @data[:log].class.to_s == "Array" || @data[:log].nil? + @data[:log] = Log.new(log: @data[:log], warnings: @warnings) end # Time the stage was last run @start = if args[:start].to_s == "" @@ -63,14 +72,6 @@ def initialize(shipment, **args) # rubocop:disable Metrics/AbcSize, Metrics/Cycl else Time.parse args[:end] end - @bar = if config[:no_progress] - SilentProgressBar.new(self.class) - else - ProgressBar.new(self.class) - end - @errors = Errors.new(bar: @bar, objids: objids, list: args[:errors]) - @warnings = Warnings.new(bar: @bar, objids: objids, list: args[:errors]) - # @warnings = args[:warnings] || [] # Nonfatal conditions, Array of Error end # Get rid of errors, warnings, and anything that may have been memoized @@ -220,7 +221,7 @@ def objids end def log_collection - @data[:log] ||= Log.new + @data[:log] end def log(entry, time = nil) @@ -242,115 +243,4 @@ def objid_directories def image_files(type = "tif") shipment.image_files(type) end - - class Log - include Enumerable - - def initialize(log: [], warnings: Warnings.new) - @log = log - @warnings = warnings - end - - def each - @log.each do |line| - yield line - end - end - - def entries - @log - end - - def warnings - @warnings.list - end - - def log(entry, time) - entry += format(" (%.3f sec)", time) unless time.nil? - @log << entry - end - - def log_it(data) - case(data.level) - when :info - log(data.command, data.time) - when :warning - add_warning(data.error) - end - end - - def add_warning(warning) - @warnings.add(warning) - end - - def to_json(state = nil, *) - JSON::State.from_state(state).generate(@log) - end - end - - class Exceptions - include Enumerable - - attr_reader :list, :objids - - def initialize(bar: SilentProgressBar.new, objids: [], list: nil) - @list = list || [] - @bar = bar - @objids = objids - end - - def each - @list.each do |line| - yield line - end - end - - def [](index) - @list[index] - end - - def []=(index, value) - @list[index] = value - end - - def to_json(state = nil, *) - JSON::State.from_state(state).generate(@list) - end - - def add(err) - unless err.objid.nil? || objids.member?(err.objid) - raise "unknown #{kind} objid #{err.objid}" - end - set_bar - @list << err - end - - def kind - raise NotImplementedError - end - - def set_bar - raise NotImplementedError - end - end - - class Errors < Exceptions - def kind - :error - end - - def set_bar - @bar.error = true - end - end - - class Warnings < Exceptions - def kind - :warning - end - - def set_bar - @bar.warning = true - end - end end diff --git a/spec/compressor_spec.rb b/spec/compressor_spec.rb index a934533..e69be54 100644 --- a/spec/compressor_spec.rb +++ b/spec/compressor_spec.rb @@ -2,7 +2,7 @@ include_context "uses temp dir" context "#run" do - let(:log) { Stage::Log.new } + let(:log) { Log.new } let(:compressor) do image_file = double("image_file", path: @path) Compressor.new(image_file: image_file, tmpdir: Pathname(temp_dir), log: log) From c3323b59f81f8a8a3c3863f3b6e0129ccc099220 Mon Sep 17 00:00:00 2001 From: Anthony Thomas Date: Tue, 6 Jan 2026 10:53:01 -0500 Subject: [PATCH 16/38] fix standardrb errors --- lib/compressor.rb | 3 +++ lib/log.rb | 3 +-- lib/stage.rb | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/compressor.rb b/lib/compressor.rb index 843e1c3..60d4349 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -47,6 +47,9 @@ def run(image_magick = ImageMagick) @log.log_it ExifTool.remove_tiff_metadata(source: @image_file.path, destination: sparse_path) @log.log_it image_magick.remove_tiff_alpha(sparse_path) if tiffinfo[:alpha] @log.log_it image_magick.strip_tiff_profiles(sparse_path) if tiffinfo[:icc] + # compress_jp2(sparse, new_image, tiffinfo) + # copy_jp2_metadata(image_file.path, new_image, document_name, tiffinfo) + # copy_jp2_alphaless_metadata(sparse, new_image) if tiffinfo[:alpha] end def sparse_path diff --git a/lib/log.rb b/lib/log.rb index 43463d6..cacfa61 100644 --- a/lib/log.rb +++ b/lib/log.rb @@ -1,4 +1,3 @@ - class Log include Enumerable @@ -27,7 +26,7 @@ def log(entry, time) end def log_it(data) - case(data.level) + case data.level when :info log(data.command, data.time) when :warning diff --git a/lib/stage.rb b/lib/stage.rb index 2829b87..63a8fe2 100755 --- a/lib/stage.rb +++ b/lib/stage.rb @@ -56,7 +56,7 @@ def initialize(shipment, **args) # rubocop:disable Metrics/AbcSize, Metrics/Cycl @warnings = Warnings.new(bar: @bar, objids: objids, list: args[:errors]) @data = args[:data] || {log: Log.new(warnings: @warnings)} # Misc data structure including log - if @data[:log].class.to_s == "Array" || @data[:log].nil? + if @data[:log].instance_of?(::Array) || @data[:log].nil? @data[:log] = Log.new(log: @data[:log], warnings: @warnings) end # Time the stage was last run From 9f61d682e042de760e38e6f9ad10206624cc6f6b Mon Sep 17 00:00:00 2001 From: Anthony Thomas Date: Wed, 7 Jan 2026 10:53:22 -0500 Subject: [PATCH 17/38] factor out LogEntry with log levels etc --- lib/compressor.rb | 8 ++++---- lib/log.rb | 19 +++++++++++++++++++ lib/stage.rb | 2 +- spec/compressor_spec.rb | 14 ++++++-------- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/lib/compressor.rb b/lib/compressor.rb index 60d4349..9d76e82 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -4,7 +4,7 @@ module ExifTool def self.remove_tiff_metadata(source:, destination:) cmd = "exiftool -XMP:All= -MakerNotes:All= #{source} -o #{destination}" status = Command.new(cmd).run - OpenStruct.new(command: cmd, time: status[:time], level: :info) + LogEntry.info(command: cmd, time: status[:time]) end end @@ -14,7 +14,7 @@ def self.remove_tiff_alpha(path) cmd = "convert #{path} -alpha off #{tmp}" status = Command.new(cmd).run FileUtils.mv(tmp, path) - OpenStruct.new(command: cmd, time: status[:time], level: :info) + LogEntry.info(command: cmd, time: status[:time]) end def self.strip_tiff_profiles(path) @@ -24,10 +24,10 @@ def self.strip_tiff_profiles(path) status = Command.new(cmd).run rescue => e warning = "couldn't remove ICC profile (#{cmd}) (#{e.message})" - OpenStruct.new(error: Error.new(warning, objid_from_path(path), path), level: :warning) + LogEntry.warning(error: Error.new(warning, nil, path)) else FileUtils.mv(tmp, path) - OpenStruct.new(command: cmd, time: status[:time], level: :info) + LogEntry.info(command: cmd, time: status[:time]) end end end diff --git a/lib/log.rb b/lib/log.rb index cacfa61..625c66b 100644 --- a/lib/log.rb +++ b/lib/log.rb @@ -108,3 +108,22 @@ def set_bar @bar.warning = true end end + +class LogEntry + def self.info(command:, time:) + new(level: :info, command: command, time: time) + end + + def self.warning(error:) + new(level: :warning, error: error) + end + + attr_reader :level, :command, :time, :error + + def initialize(level:, command: nil, time: nil, error: nil) + @level = level + @command = command + @time = time + @error = error + end +end diff --git a/lib/stage.rb b/lib/stage.rb index 63a8fe2..83ae1e0 100755 --- a/lib/stage.rb +++ b/lib/stage.rb @@ -1,4 +1,4 @@ -# !/usr/bin/env ruby +#!/usr/bin/env ruby # frozen_string_literal: true require "json" diff --git a/spec/compressor_spec.rb b/spec/compressor_spec.rb index e69be54..65a859f 100644 --- a/spec/compressor_spec.rb +++ b/spec/compressor_spec.rb @@ -33,14 +33,6 @@ compressor.run expect(log.entries).not_to include(match("-strip")) end - - it "handles warnings" do - @path = "spec/fixtures/10_10_8_400_icc.tif" - error = instance_double(Error, objid: nil) - image_magick = class_double(ImageMagick, remove_tiff_alpha: nil, strip_tiff_profiles: OpenStruct.new(error: error, level: :warning)) - compressor.run(image_magick) - expect(log.warnings.first).to eq(error) - end end end @@ -64,5 +56,11 @@ ImageMagick.strip_tiff_profiles(tiff_path) expect(TIFF.new(tiff_path).info[:icc]).to eq(false) end + + it "handles warnings" do + tiff_path = "spec/fixtures/10_10_8_400_fake.tif" + log_entry = ImageMagick.strip_tiff_profiles(tiff_path) + expect(log_entry.level).to eq(:warning) + end end end From 80d38788acf78c96e1d398116c1ae2f43d470dd6 Mon Sep 17 00:00:00 2001 From: Anthony Thomas Date: Wed, 7 Jan 2026 11:16:45 -0500 Subject: [PATCH 18/38] have Compressor call Kakadu --- lib/compressor.rb | 37 +++++++++++++++++++++++++++++++++---- lib/stage/compression.rb | 2 +- spec/compressor_spec.rb | 14 ++++++++++---- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/lib/compressor.rb b/lib/compressor.rb index 9d76e82..eaabcc8 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -1,5 +1,30 @@ require "tiff" require "ostruct" + +module Kakadu + def self.compress(source, destination, tiffinfo) + clevels = jp2_clevels(tiffinfo) + cmd = "kdu_compress -quiet -i #{source} -o #{destination}" \ + " 'Clevels=#{clevels}'" \ + " 'Clayers=#{JP2_LAYERS}'" \ + " 'Corder=#{JP2_ORDER}'" \ + " 'Cuse_sop=#{JP2_USE_SOP}'" \ + " 'Cuse_eph=#{JP2_USE_EPH}'" \ + " Cmodes=#{JP2_MODES}" \ + " -no_weights -slope '#{JP2_SLOPE}'" + status = Command.new(cmd).run + LogEntry.info(command: cmd, time: status[:time]) + end + + def self.jp2_clevels(tiffinfo) + # Get the width and height, figure out which is larger. + size = [tiffinfo[:width], tiffinfo[:height]].max + # Calculate appropriate Clevels. + clevels = (Math.log(size.to_i / 100.0) / Math.log(2)).to_i + (clevels < JP2_LEVEL_MIN) ? JP2_LEVEL_MIN : clevels + end +end + module ExifTool def self.remove_tiff_metadata(source:, destination:) cmd = "exiftool -XMP:All= -MakerNotes:All= #{source} -o #{destination}" @@ -41,13 +66,13 @@ def initialize(image_file:, tmpdir:, log: "whatever") @log = log end - def run(image_magick = ImageMagick) + def run(compression_tool = Kakadu) # We don't want any XMP metadata to be copied over on its own. If # it's been a while since we last ran exiftool, this might take a sec. @log.log_it ExifTool.remove_tiff_metadata(source: @image_file.path, destination: sparse_path) - @log.log_it image_magick.remove_tiff_alpha(sparse_path) if tiffinfo[:alpha] - @log.log_it image_magick.strip_tiff_profiles(sparse_path) if tiffinfo[:icc] - # compress_jp2(sparse, new_image, tiffinfo) + @log.log_it ImageMagick.remove_tiff_alpha(sparse_path) if tiffinfo[:alpha] + @log.log_it ImageMagick.strip_tiff_profiles(sparse_path) if tiffinfo[:icc] + @log.log_it compression_tool.compress(sparse_path, new_path, tiffinfo) # copy_jp2_metadata(image_file.path, new_image, document_name, tiffinfo) # copy_jp2_alphaless_metadata(sparse, new_image) if tiffinfo[:alpha] end @@ -55,4 +80,8 @@ def run(image_magick = ImageMagick) def sparse_path @sparse_path ||= File.join(tmpdir, "sparse.tif") end + + def new_path + @new_path ||= File.join(tmpdir, "new.jp2") + end end diff --git a/lib/stage/compression.rb b/lib/stage/compression.rb index c8d7fc5..501842c 100755 --- a/lib/stage/compression.rb +++ b/lib/stage/compression.rb @@ -61,7 +61,7 @@ def handle_8_bps_conversion(compressor) # rubocop:disable Metrics/AbcSize, Metri tmpdir = compressor.tmpdir sparse = compressor.sparse_path # sparse = File.join(tmpdir, "sparse.tif") - new_image = File.join(tmpdir, "new.jp2") + new_image = compressor.new_path final_image_name = File.basename(image_file.path, ".*") + ".jp2" final_image = File.join(File.dirname(image_file.path), final_image_name) document_name = File.join(shipment.objid_to_path(image_file.objid), diff --git a/spec/compressor_spec.rb b/spec/compressor_spec.rb index 65a859f..4d0e3e3 100644 --- a/spec/compressor_spec.rb +++ b/spec/compressor_spec.rb @@ -3,6 +3,7 @@ context "#run" do let(:log) { Log.new } + let(:compression_tool) { class_double(Kakadu, compress: LogEntry.info(command: nil, time: nil)) } let(:compressor) do image_file = double("image_file", path: @path) Compressor.new(image_file: image_file, tmpdir: Pathname(temp_dir), log: log) @@ -14,25 +15,30 @@ it "removes alpha when it exists" do @path = "spec/fixtures/10_10_8_400_alpha.tif" - compressor.run + compressor.run(compression_tool) expect(log.entries).to include(match("-alpha off")) end it "ignores alpha when it doesn't exist" do - compressor.run + compressor.run(compression_tool) expect(log.entries).not_to include(match("-alpha off")) end it "strips tiff profile data when it exists" do @path = "spec/fixtures/10_10_8_400_icc.tif" - compressor.run + compressor.run(compression_tool) expect(log.entries).to include(match("-strip")) end it "ignores tiff profile when it doesn't exist" do - compressor.run + compressor.run(compression_tool) expect(log.entries).not_to include(match("-strip")) end + + it "runs the compression tool" do + compressor.run + expect(log.entries).to include(match("kdu_compress")) + end end end From bf8416068c7fa7371ff4b573b806370e92be6a26 Mon Sep 17 00:00:00 2001 From: Anthony Thomas Date: Wed, 7 Jan 2026 11:18:22 -0500 Subject: [PATCH 19/38] comment out in Compression those things being done in Compressor --- lib/stage/compression.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/stage/compression.rb b/lib/stage/compression.rb index 501842c..650addf 100755 --- a/lib/stage/compression.rb +++ b/lib/stage/compression.rb @@ -71,9 +71,9 @@ def handle_8_bps_conversion(compressor) # rubocop:disable Metrics/AbcSize, Metri compressor.run # need a test that looks for this - remove_tiff_alpha(sparse) if tiffinfo[:alpha] + # remove_tiff_alpha(sparse) if tiffinfo[:alpha] - strip_tiff_profiles(sparse) if tiffinfo[:icc] + # strip_tiff_profiles(sparse) if tiffinfo[:icc] # FIXME: process-tiffs.sh defines this variable but does not # use it. Check the original on tang. @@ -87,7 +87,7 @@ def handle_8_bps_conversion(compressor) # rubocop:disable Metrics/AbcSize, Metri # This will always take a second. Other than the initial loading # of exiftool libraries, this is the only JP2 step that takes # noticeable time. - compress_jp2(sparse, new_image, tiffinfo) + # compress_jp2(sparse, new_image, tiffinfo) # We have our JP2; we can remove the middle TIFF. Then we try # to grab metadata from the original TIFF. This should be very # quick since we just used exiftool a few lines back. From 099752db0ccd922fefc513a9b808bb3786fcfaeb Mon Sep 17 00:00:00 2001 From: Anthony Thomas Date: Wed, 7 Jan 2026 11:38:02 -0500 Subject: [PATCH 20/38] remove rubocop remnants --- .gitignore | 2 + README.md | 8 ++- bin/jhove | 2 +- bin/process | 2 +- lib/agenda.rb | 2 +- lib/jhove.rb | 2 +- lib/jp2.rb | 2 +- lib/processor.rb | 10 ++-- lib/query_tool.rb | 12 ++-- lib/shipment.rb | 12 ++-- lib/stage.rb | 4 +- lib/stage/compression.rb | 16 ++--- lib/stage/dlxs_compressor.rb | 10 ++-- lib/stage/image_validator.rb | 8 +-- lib/stage/pagination_check.rb | 2 +- lib/stage/postflight.rb | 4 +- lib/stage/preflight.rb | 6 +- lib/stage/tagger.rb | 6 +- lib/string_color.rb | 108 ++++++++++++++++++++++++++-------- lib/symbolize.rb | 2 +- lib/tiff.rb | 6 +- test/agenda_test.rb | 4 +- test/compressor_test.rb | 16 ++--- test/dlxs_compressor_test.rb | 2 +- test/error_test.rb | 2 - test/image_validator_test.rb | 14 ++--- test/jhove_test.rb | 4 +- test/jp2_test.rb | 2 +- test/pagination_check_test.rb | 6 +- test/postflight_test.rb | 18 +++--- test/preflight_test.rb | 16 ++--- test/processor_test.rb | 20 +++---- test/query_tool_test.rb | 16 ++--- test/shipment_test.rb | 22 +++---- test/stage_test.rb | 2 +- test/tagger_test.rb | 16 ++--- test/test_helper.rb | 2 +- test/test_shipment.rb | 10 ++-- test/test_shipment_test.rb | 6 +- test/tiff_test.rb | 2 +- 40 files changed, 232 insertions(+), 174 deletions(-) diff --git a/.gitignore b/.gitignore index ad2933f..1cd0923 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,5 @@ /coverage /config/*.local.yml /vendor + +/test/shipments/* diff --git a/README.md b/README.md index 3451849..83af28d 100644 --- a/README.md +++ b/README.md @@ -41,13 +41,15 @@ $ docker-compose build ### 3. Running tests ``` -$ docker-compose run --rm test -$ docker-compose run --rm test bundle exec rubocop +$ docker-compose run --rm app bundle exec rake test +$ docker-compose run --rm app bundle exec rspec +$ docker-compose run --rm app bundle exec standardrb ``` or ``` $ bundle exec rake test -$ bundle exec rubocop +$ bundle exec rspec +$ bundle exec standardrb ``` diff --git a/bin/jhove b/bin/jhove index 4f31b1b..f220fd7 100755 --- a/bin/jhove +++ b/bin/jhove @@ -53,7 +53,7 @@ statuses = {} err_file = ['jhove', Time.now.strftime('%Y%m%d_%H%M%S'), 'errors.txt'].join '_' outfile = File.open err_file, 'w' -args.each do |arg| # rubocop:disable Metrics/BlockLength +args.each do |arg| dir = Pathname.new(arg).cleanpath.to_s unless File.exist? dir statuses[arg] = 'No such directory'.red diff --git a/bin/process b/bin/process index 48a1f1d..e89b67e 100755 --- a/bin/process +++ b/bin/process @@ -55,7 +55,7 @@ if ARGV.count.zero? exit 1 end -ARGV.each do |arg| # rubocop:disable Metrics/BlockLength +ARGV.each do |arg| dir = Pathname.new(arg).realpath.to_s unless File.exist?(dir) && File.directory?(dir) puts "Shipment directory #{dir.bold} does not exist, skipping".red diff --git a/lib/agenda.rb b/lib/agenda.rb index 6067013..5aae7d4 100755 --- a/lib/agenda.rb +++ b/lib/agenda.rb @@ -15,7 +15,7 @@ def to_s end # Propagate stage errors onto subsequent stages. - def update(source_stage) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def update(source_stage) @stages.each do |stage| next if @stage_to_index[stage.name.to_sym] <= @stage_to_index[source_stage.name.to_sym] diff --git a/lib/jhove.rb b/lib/jhove.rb index c75755b..3c1752f 100755 --- a/lib/jhove.rb +++ b/lib/jhove.rb @@ -8,7 +8,7 @@ require "symbolize" # Wrapper for feed validate Perl script which invokes JHOVE -class JHOVE # rubocop:disable Metrics/ClassLength +class JHOVE attr_reader :errors, :raw_output UNUSED_FIELDS = %i[description file namespace objid diff --git a/lib/jp2.rb b/lib/jp2.rb index b7289ed..e391715 100755 --- a/lib/jp2.rb +++ b/lib/jp2.rb @@ -10,7 +10,7 @@ def initialize(path) end # Run tiffinfo command and return output text block - def info # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def info cmd = "exiftool #{@path}" status = Command.new(cmd).run jp2info = extract_fields(status[:stdout]) diff --git a/lib/processor.rb b/lib/processor.rb index 61d0b4f..1b0c81c 100755 --- a/lib/processor.rb +++ b/lib/processor.rb @@ -18,7 +18,7 @@ Dir[File.join(__dir__, "shipment", "*.rb")].sort.each { |file| require file } # Processor -class Processor # rubocop:disable Metrics/ClassLength +class Processor attr_reader :dir, :config, :shipment # Can take a Shipment instead of a directory. @@ -36,7 +36,7 @@ def shipment_class Object.const_get(@config[:shipment_class] || "Shipment") end - def run # rubocop:disable Metrics/MethodLength + def run raise FinalizedShipmentError if shipment.finalized? if config[:restart_all] @@ -174,7 +174,7 @@ def run_stages end end - def run_stage(stage) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def run_stage(stage) stage_agenda = agenda.for_stage stage return if stage_agenda.none? @@ -194,14 +194,12 @@ def run_stage(stage) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength end end - def init_status_file # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity + def init_status_file return false unless File.exist? status_file # In order to reconstitute Error objects we need to use JSON.load # instead of JSON.parse. So we explicitly symbolize the output. - # rubocop:disable Security/JSONLoad status = Symbolize.symbolize JSON.unsafe_load File.new(status_file) - # rubocop:enable Security/JSONLoad raise JSON::ParserError, "unable to parse #{status_file}" if status.nil? unless status.key?(:shipment) && status[:shipment].is_a?(Shipment) diff --git a/lib/query_tool.rb b/lib/query_tool.rb index 61b593c..cdc4650 100755 --- a/lib/query_tool.rb +++ b/lib/query_tool.rb @@ -8,14 +8,14 @@ require "processor" # Facility for running command-line processor/shipment queries and commands -class QueryTool # rubocop:disable Metrics/ClassLength +class QueryTool attr_reader :processor def initialize(processor) @processor = processor end - def agenda_cmd(*_args) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def agenda_cmd(*_args) if processor.agenda.any? processor.stages.each do |stage| puts stage.name.bold @@ -43,7 +43,7 @@ def objids_cmd end end - def errors_cmd(*args) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength + def errors_cmd(*args) errs = processor.errors_by_objid_by_stage errs.each_key do |objid| next if args.count.positive? && !args.include?(objid) @@ -63,7 +63,7 @@ def errors_cmd(*args) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticCompl end end - def warnings_cmd(*args) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength + def warnings_cmd(*args) warnings = processor.warnings_by_objid_by_stage warnings.each_key do |objid| next if args.count.positive? && !args.include?(objid) @@ -91,7 +91,7 @@ def status_cmd end end - def fixity_cmd # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def fixity_cmd unless File.directory? processor.shipment.source_directory puts "Source directory not yet populated" end @@ -141,7 +141,7 @@ def status_status(stage) end end - def status_detail(stage) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def status_detail(stage) statuses = [] bad = stage.error_objids.count total = stage.objids.count diff --git a/lib/shipment.rb b/lib/shipment.rb index 9f3d279..3c19e20 100755 --- a/lib/shipment.rb +++ b/lib/shipment.rb @@ -12,7 +12,7 @@ class FinalizedShipmentError < StandardError ImageFile = Struct.new(:objid, :path, :objid_file, :file) # Shipment directory class -class Shipment # rubocop:disable Metrics/ClassLength +class Shipment PATH_COMPONENTS = 1 OBJID_SEPARATOR = "/" attr_reader :metadata @@ -121,7 +121,7 @@ def validate_objid(objid) Luhn.valid?(objid) ? nil : "Luhn checksum failed" end - def image_files(type = "tif", dir = @dir) # rubocop:disable Metrics/MethodLength + def image_files(type = "tif", dir = @dir) files = [] find_objids(dir).each do |objid| objid_path = objid_to_path objid @@ -147,7 +147,7 @@ def source_image_files(type = "tif") # every other directory in @dir into it. # We will potentially remove and re-copy directories from source/ # but that depends on the options passed to the processor. - def setup_source_directory # rubocop:disable Metrics/AbcSize + def setup_source_directory raise FinalizedShipmentError if finalized? return if File.exist? source_directory @@ -164,7 +164,7 @@ def setup_source_directory # rubocop:disable Metrics/AbcSize # Copy clean or remediated objid directories from source. # Called with nil to replaces all objids, or an Array of objids. - def restore_from_source_directory(objid_array = nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def restore_from_source_directory(objid_array = nil) raise FinalizedShipmentError if finalized? unless File.directory? source_directory raise Errno::ENOENT, "source directory #{source_directory} not found" @@ -213,7 +213,7 @@ def checksum_source_directory end # Returns Hash with keys {added, changed, removed} -> Array of ImageFile - def fixity_check # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity + def fixity_check fixity = {added: [], removed: [], changed: []} return fixity if metadata[:checksums].nil? @@ -250,7 +250,7 @@ def find_objids(dir = @dir) bars.sort end - def find_objids_with_components(dir, components) # rubocop:disable Metrics/MethodLength + def find_objids_with_components(dir, components) bars = [] if components.count < self.class::PATH_COMPONENTS subdir = File.join(dir, components) diff --git a/lib/stage.rb b/lib/stage.rb index 83ae1e0..c7ff51f 100755 --- a/lib/stage.rb +++ b/lib/stage.rb @@ -12,7 +12,7 @@ require "log" # Base class for conversion stages -class Stage # rubocop:disable Metrics/ClassLength +class Stage attr_reader :errors, :warnings, :start, :end attr_accessor :name, :config, :shipment @@ -39,7 +39,7 @@ def to_json(*args) # Can be created without a shipment, but that field needs to be set # before the #run method can be called. - def initialize(shipment, **args) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity + def initialize(shipment, **args) unless shipment.nil? || shipment.is_a?(Shipment) raise StandardError, "unknown shipment class #{shipment.class}" end diff --git a/lib/stage/compression.rb b/lib/stage/compression.rb index 650addf..3ad5b6c 100755 --- a/lib/stage/compression.rb +++ b/lib/stage/compression.rb @@ -16,8 +16,8 @@ TIFF_DATE_FORMAT = "%Y:%m:%d %H:%M:%S" # TIFF to JP2/TIFF compression stage -class Compression < Stage # rubocop:disable Metrics/ClassLength - def run(agenda) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength +class Compression < Stage + def run(agenda) return unless agenda.any? files = image_files.select { |file| agenda.include? file.objid } @bar.steps = files.count @@ -55,7 +55,7 @@ def run(agenda) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, private - def handle_8_bps_conversion(compressor) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def handle_8_bps_conversion(compressor) image_file = compressor.image_file tiffinfo = compressor.tiffinfo tmpdir = compressor.tmpdir @@ -124,7 +124,7 @@ def remove_tiff_alpha(path) FileUtils.mv(tmp, path) end - def strip_tiff_profiles(path) # rubocop:disable Metrics/MethodLength + def strip_tiff_profiles(path) tmp = path + ".stripped" cmd = "convert #{path} -strip #{tmp}" begin @@ -138,7 +138,7 @@ def strip_tiff_profiles(path) # rubocop:disable Metrics/MethodLength end end - def compress_jp2(source, destination, tiffinfo) # rubocop:disable Metrics/MethodLength + def compress_jp2(source, destination, tiffinfo) clevels = jp2_clevels(tiffinfo) cmd = "kdu_compress -quiet -i #{source} -o #{destination}" \ " 'Clevels=#{clevels}'" \ @@ -152,7 +152,7 @@ def compress_jp2(source, destination, tiffinfo) # rubocop:disable Metrics/Method log cmd, status[:time] end - def copy_jp2_metadata(source, destination, document_name, tiffinfo) # rubocop:disable Metrics/MethodLength + def copy_jp2_metadata(source, destination, document_name, tiffinfo) # If the original image has a date, we want it. If not, we # want to add the current date. # date "%Y-%m-%dT%H:%M:%S" @@ -195,7 +195,7 @@ def copy_jp2_alphaless_metadata(source, destination) log cmd, status[:time] end - def handle_1_bps_conversion(compressor) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def handle_1_bps_conversion(compressor) image_file = compressor.image_file tiffinfo = compressor.tiffinfo tmpdir = compressor.tmpdir @@ -226,7 +226,7 @@ def compress_tiff(path, destination) log cmd, status[:time] end - def copy_tiff_metadata(path, destination) # rubocop:disable Metrics/MethodLength + def copy_tiff_metadata(path, destination) cmd = "exiftool -tagsFromFile #{path}" \ " '-IFD0:DocumentName'" \ " '-IFD0:ImageDescription='" \ diff --git a/lib/stage/dlxs_compressor.rb b/lib/stage/dlxs_compressor.rb index 1c27b7f..6c8a333 100755 --- a/lib/stage/dlxs_compressor.rb +++ b/lib/stage/dlxs_compressor.rb @@ -5,8 +5,8 @@ require "stage" # JP2-to-TIFF conversion stage for DLXS -class DLXSCompressor < Stage # rubocop:disable Metrics/ClassLength - def run(agenda) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength +class DLXSCompressor < Stage + def run(agenda) return unless agenda.any? files = image_files("jp2").select { |file| agenda.include? file.objid } @@ -23,7 +23,7 @@ def run(agenda) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength private - def handle_conversion(image_file) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def handle_conversion(image_file) basename = File.basename(image_file.path, ".*") tmpdir = create_tempdir basename source = File.join(tmpdir, "source.tif") @@ -42,7 +42,7 @@ def handle_conversion(image_file) # rubocop:disable Metrics/AbcSize, Metrics/Met delete_on_success image_file.path end - def copy_metadata(source, destination, source_image) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def copy_metadata(source, destination, source_image) cmd = <<~CMD.tr("\n", " ") exiftool -tagsFromFile #{source} '-IFD0:DocumentName=#{source_image}' @@ -87,7 +87,7 @@ def get_y_resolution(path) end # Converts 'source.tif' to 'source.pgm' in temporary directory - def tiff_to_pgm(dir) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def tiff_to_pgm(dir) source = File.join(dir, "source.tif") pnm = File.join(dir, "source.pnm") pgm = File.join(dir, "source.pgm") diff --git a/lib/stage/image_validator.rb b/lib/stage/image_validator.rb index cd1213c..678ddae 100755 --- a/lib/stage/image_validator.rb +++ b/lib/stage/image_validator.rb @@ -11,7 +11,7 @@ class ImageValidator < Stage BITONAL_RES = 600 CONTONE_RES = 400 - def run(agenda) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength + def run(agenda) return unless agenda.any? tiff_files = image_files.select { |file| agenda.include? file.objid } @@ -36,7 +36,7 @@ def run(agenda) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, private # Run tiffinfo command and return output text block - def run_tiffinfo(image_file) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def run_tiffinfo(image_file) begin info = TIFF.new(image_file.path).info rescue => e @@ -58,7 +58,7 @@ def run_tiffinfo(image_file) # rubocop:disable Metrics/AbcSize, Metrics/MethodLe # 'Samples/Pixel' line -> spp # bps of 1 requires spp=1 and xres=BITONAL_RES and yres=BITONAL_RES # bps of 8 requires spp in [1,3,4] and xres=CONTONE_RES and yres=CONTONE_RES - def evaluate_tiff(image_file, info) # rubocop:disable Metrics/MethodLength + def evaluate_tiff(image_file, info) if info[:res_unit] != "pixels/inch" image_error image_file, "must have pixels/inch, not #{info[:res_unit]}" end @@ -91,7 +91,7 @@ def evaluate_tiff_8_bps(image_file, info) end end - def run_jp2info(image_file) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def run_jp2info(image_file) begin info = JP2.new(image_file.path).info rescue => e diff --git a/lib/stage/pagination_check.rb b/lib/stage/pagination_check.rb index 8151a7d..20febeb 100755 --- a/lib/stage/pagination_check.rb +++ b/lib/stage/pagination_check.rb @@ -41,7 +41,7 @@ def pages_in_dir(dir) # Given sorted pages array of integers from 1 to n inclusive, # returns array of integer and integer range strings missing from list - def missing_pages(pages) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity + def missing_pages(pages) missing = (1..pages.max).to_a - pages ranges = [] missing.each do |m| diff --git a/lib/stage/postflight.rb b/lib/stage/postflight.rb index cda3232..da0d4df 100755 --- a/lib/stage/postflight.rb +++ b/lib/stage/postflight.rb @@ -29,7 +29,7 @@ def steps(agenda) shipment.checksums.keys.count end - def check_objid_lists # rubocop:disable Metrics/AbcSize + def check_objid_lists s1 = Set.new shipment.metadata[:initial_barcodes] s2 = Set.new shipment.objids if (s1 - s2).any? @@ -40,7 +40,7 @@ def check_objid_lists # rubocop:disable Metrics/AbcSize add_error Error.new("objids added: #{(s2 - s1).to_a.join(", ")}") end - def verify_source_checksums # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def verify_source_checksums fixity = shipment.fixity_check do |image_file| @bar.next! image_file.objid_file end diff --git a/lib/stage/preflight.rb b/lib/stage/preflight.rb index 4e586af..8f9565d 100755 --- a/lib/stage/preflight.rb +++ b/lib/stage/preflight.rb @@ -31,7 +31,7 @@ def self.ignorable_files IGNORABLE_FILES end - def run(agenda) # rubocop:disable Metrics/AbcSize + def run(agenda) shipment.metadata[:initial_barcodes] = shipment.objids if shipment.metadata[:initial_barcodes].none? add_error Error.new("no objids in #{shipment_directory}") @@ -63,7 +63,7 @@ def validate_objects(agenda) # A shipment directory is valid if it contains only objid directories, # a source directory, and status.json - def validate_shipment_directory # rubocop:disable Metrics/MethodLength + def validate_shipment_directory Dir.entries(shipment_directory).sort.each do |entry| next if %w[. .. source tmp status.json].include? entry @@ -82,7 +82,7 @@ def validate_shipment_directory # rubocop:disable Metrics/MethodLength # objid directory must include one or more TIFF files, # and a few other exceptions grandfathered by just_do_everything.sh # No directories are allowed - def validate_objid_directory(objid) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity + def validate_objid_directory(objid) have_image = false objid_directory = shipment.objid_directory(objid) Dir.entries(objid_directory).sort.each do |entry| diff --git a/lib/stage/tagger.rb b/lib/stage/tagger.rb index 83f40c0..dce81ab 100755 --- a/lib/stage/tagger.rb +++ b/lib/stage/tagger.rb @@ -25,7 +25,7 @@ def run(agenda) private - def calculate_tags # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity + def calculate_tags artist = config[:tagger_artist] || "dcu" @artist_tag ||= if TagData::ARTIST[artist].nil? add_warning Error.new("using custom artist '#{artist}'") @@ -58,7 +58,7 @@ def calculate_tags # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexi end end - def tag(image_file) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def tag(image_file) tagged_name = image_file.path.split(File::SEPARATOR)[-1] + ".tagged" tagged_path = File.join(tempdir_for_file(image_file), tagged_name) tagged = ImageFile.new(image_file.objid, tagged_path, @@ -101,7 +101,7 @@ def tempdir_for_file(image_file) @objid_to_tempdir[image_file.objid] = create_tempdir end - def run_tiffset(image_file, tag, value) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def run_tiffset(image_file, tag, value) begin info = TIFF.new(image_file.path).set(tag, value) rescue => e diff --git a/lib/string_color.rb b/lib/string_color.rb index 8541726..811ca9e 100755 --- a/lib/string_color.rb +++ b/lib/string_color.rb @@ -4,34 +4,92 @@ # String color methods # https://github.com/puppetlabs/quest/blob/master/lib/quest/colorization.rb -# rubocop:disable Style/SingleLineMethods, Layout/EmptyLineBetweenDefs class String - def black; "\e[30m#{self}\e[0m" end - def red; "\e[31m#{self}\e[0m" end - def green; "\e[32m#{self}\e[0m" end - def brown; "\e[33m#{self}\e[0m" end - def blue; "\e[34m#{self}\e[0m" end - def magenta; "\e[35m#{self}\e[0m" end - def cyan; "\e[36m#{self}\e[0m" end - def gray; "\e[37m#{self}\e[0m" end - - def bg_black; "\e[40m#{self}\e[0m" end - def bg_red; "\e[41m#{self}\e[0m" end - def bg_green; "\e[42m#{self}\e[0m" end - def bg_brown; "\e[43m#{self}\e[0m" end - def bg_blue; "\e[44m#{self}\e[0m" end - def bg_magenta; "\e[45m#{self}\e[0m" end - def bg_cyan; "\e[46m#{self}\e[0m" end - def bg_gray; "\e[47m#{self}\e[0m" end - - def bold; "\e[1m#{self}\e[22m" end - def italic; "\e[3m#{self}\e[23m" end - def underline; "\e[4m#{self}\e[24m" end - def blink; "\e[5m#{self}\e[25m" end - def reverse_color; "\e[7m#{self}\e[27m" end + def black + "\e[30m#{self}\e[0m" + end + + def red + "\e[31m#{self}\e[0m" + end + + def green + "\e[32m#{self}\e[0m" + end + + def brown + "\e[33m#{self}\e[0m" + end + + def blue + "\e[34m#{self}\e[0m" + end + + def magenta + "\e[35m#{self}\e[0m" + end + + def cyan + "\e[36m#{self}\e[0m" + end + + def gray + "\e[37m#{self}\e[0m" + end + + def bg_black + "\e[40m#{self}\e[0m" + end + + def bg_red + "\e[41m#{self}\e[0m" + end + + def bg_green + "\e[42m#{self}\e[0m" + end + + def bg_brown + "\e[43m#{self}\e[0m" + end + + def bg_blue + "\e[44m#{self}\e[0m" + end + + def bg_magenta + "\e[45m#{self}\e[0m" + end + + def bg_cyan + "\e[46m#{self}\e[0m" + end + + def bg_gray + "\e[47m#{self}\e[0m" + end + + def bold + "\e[1m#{self}\e[22m" + end + + def italic + "\e[3m#{self}\e[23m" + end + + def underline + "\e[4m#{self}\e[24m" + end + + def blink + "\e[5m#{self}\e[25m" + end + + def reverse_color + "\e[7m#{self}\e[27m" + end def decolorize gsub(/\e\[\d\d?(;\d\d?)?m/, "") end end -# rubocop:enable Style/SingleLineMethods, Layout/EmptyLineBetweenDefs diff --git a/lib/symbolize.rb b/lib/symbolize.rb index 54f723f..440c59c 100755 --- a/lib/symbolize.rb +++ b/lib/symbolize.rb @@ -3,7 +3,7 @@ # Based on https://gist.github.com/Integralist/9503099 module Symbolize - def self.symbolize(obj) # rubocop:disable Metrics/MethodLength + def self.symbolize(obj) case obj when Hash return obj.each_with_object({}) do |(k, v), memo| diff --git a/lib/tiff.rb b/lib/tiff.rb index bb08103..e714cd7 100755 --- a/lib/tiff.rb +++ b/lib/tiff.rb @@ -17,7 +17,7 @@ def initialize(path) end # Run tiffinfo command and return output text block - def info # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def info cmd = "tiffinfo #{@path}" status = Command.new(cmd).run tiffinfo = extract_fields(status[:stdout]) @@ -35,7 +35,7 @@ def info # rubocop:disable Metrics/AbcSize, Metrics/MethodLength tiffinfo end - def set(tag, value) # rubocop:disable Metrics/MethodLength + def set(tag, value) cmd = "tiffset -s #{tag} '#{value}' #{@path}" status = Command.new(cmd).run tiffset = {cmd: cmd, @@ -54,7 +54,7 @@ def set(tag, value) # rubocop:disable Metrics/MethodLength private - def extract_fields(info) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def extract_fields(info) h = {} m = info.match(/Resolution:\s(\d+(?:\.\d+)?),\s*(\d+(?:\.\d+)?)\s+(.*)/) unless m.nil? diff --git a/test/agenda_test.rb b/test/agenda_test.rb index 890994e..0ded431 100755 --- a/test/agenda_test.rb +++ b/test/agenda_test.rb @@ -32,7 +32,7 @@ def self.gen_for_stage generate_tests "for_stage", test_proc end - def self.gen_update # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_update test_proc = proc { |_shipment_class, test_shipment_class, dir, opts| spec = "BC T contone 1 BC T contone 1" test_shipment = test_shipment_class.new(dir, spec) @@ -49,7 +49,7 @@ def self.gen_update # rubocop:disable Metrics/AbcSize, Metrics/MethodLength generate_tests "update", test_proc end - def self.gen_update_fatal_error # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_update_fatal_error test_proc = proc { |_shipment_class, test_shipment_class, dir, opts| spec = "BC T contone 1 BC T contone 1" test_shipment = test_shipment_class.new(dir, spec) diff --git a/test/compressor_test.rb b/test/compressor_test.rb index 574934f..d7bd1f5 100755 --- a/test/compressor_test.rb +++ b/test/compressor_test.rb @@ -5,7 +5,7 @@ require "compression" require "fixtures" -class CompressionTest < Minitest::Test # rubocop:disable Metrics/ClassLength +class CompressionTest < Minitest::Test def setup @config = Config.new({no_progress: true}) end @@ -24,7 +24,7 @@ def self.gen_new generate_tests "new", test_proc end - def self.gen_run # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_run test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bitonal 1 T contone 2") shipment = shipment_class.new(test_shipment.directory) @@ -43,7 +43,7 @@ def self.gen_run # rubocop:disable Metrics/AbcSize, Metrics/MethodLength generate_tests "run", test_proc end - def self.gen_set_tiff_date_time # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_set_tiff_date_time test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bitonal 1") shipment = shipment_class.new(test_shipment.directory) @@ -59,7 +59,7 @@ def self.gen_set_tiff_date_time # rubocop:disable Metrics/AbcSize, Metrics/Metho generate_tests "set_tiff_date_time", test_proc end - def self.gen_set_jp2_date_time # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_set_jp2_date_time test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T contone 1") shipment = shipment_class.new(test_shipment.directory) @@ -79,7 +79,7 @@ def self.gen_set_jp2_date_time # rubocop:disable Metrics/AbcSize, Metrics/Method generate_tests "set_jp2_date_time", test_proc end - def self.gen_set_jp2_document_name # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_set_jp2_document_name test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T contone 1") shipment = shipment_class.new(test_shipment.directory) @@ -122,7 +122,7 @@ def self.gen_zero_length_fails generate_tests "zero_length_fails", test_proc end - def self.gen_alpha_channel # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_alpha_channel test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T contone 1") shipment = shipment_class.new(test_shipment.directory) @@ -137,7 +137,7 @@ def self.gen_alpha_channel # rubocop:disable Metrics/AbcSize, Metrics/MethodLeng generate_tests "alpha_channel", test_proc end - def self.gen_icc_profile # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_icc_profile test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T contone 1") shipment = shipment_class.new(test_shipment.directory) @@ -153,7 +153,7 @@ def self.gen_icc_profile # rubocop:disable Metrics/AbcSize, Metrics/MethodLength generate_tests "icc_profile", test_proc end - def self.gen_software # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_software test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bitonal 1") shipment = shipment_class.new(test_shipment.directory) diff --git a/test/dlxs_compressor_test.rb b/test/dlxs_compressor_test.rb index 808a700..4f793bd 100755 --- a/test/dlxs_compressor_test.rb +++ b/test/dlxs_compressor_test.rb @@ -16,7 +16,7 @@ def self.gen_new generate_tests "new", test_proc end - def self.gen_run # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_run test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T contone 1") shipment = shipment_class.new(test_shipment.directory) diff --git a/test/error_test.rb b/test/error_test.rb index f543f31..f62dee0 100755 --- a/test/error_test.rb +++ b/test/error_test.rb @@ -31,9 +31,7 @@ def test_to_s def test_json err = Error.new("some error", "12345678", "00000001.tif") - # rubocop:disable Security/JSONLoad err2 = JSON.unsafe_load(err.to_json) - # rubocop:enable Security/JSONLoad assert err.description == err2.description && err.objid == err2.objid && err.path == err2.path, diff --git a/test/image_validator_test.rb b/test/image_validator_test.rb index 053f2df..46aa2cc 100755 --- a/test/image_validator_test.rb +++ b/test/image_validator_test.rb @@ -4,7 +4,7 @@ require "minitest/autorun" require "image_validator" -class ImageValidatorTest < Minitest::Test # rubocop:disable Metrics/ClassLength +class ImageValidatorTest < Minitest::Test def setup @config = Config.new({no_progress: true}) end @@ -45,7 +45,7 @@ def self.gen_16bps_fails generate_tests "16bps_fails", test_proc end - def self.gen_pixelspercentimeter_fails # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_pixelspercentimeter_fails test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bitonal 1") shipment = shipment_class.new(test_shipment.directory) @@ -61,7 +61,7 @@ def self.gen_pixelspercentimeter_fails # rubocop:disable Metrics/AbcSize, Metric generate_tests "pixelspercentimeter_fails", test_proc end - def self.gen_bitonal_3spp_fails # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_bitonal_3spp_fails test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bitonal 1") shipment = shipment_class.new(test_shipment.directory) @@ -77,7 +77,7 @@ def self.gen_bitonal_3spp_fails # rubocop:disable Metrics/AbcSize, Metrics/Metho generate_tests "bitonal_3spp_fails", test_proc end - def self.gen_bitonal_resolution_fails # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_bitonal_resolution_fails test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bitonal 1") shipment = shipment_class.new(test_shipment.directory) @@ -93,7 +93,7 @@ def self.gen_bitonal_resolution_fails # rubocop:disable Metrics/AbcSize, Metrics generate_tests "bitonal_resolution_fails", test_proc end - def self.gen_contone_2spp_fails # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_contone_2spp_fails test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T contone 1") shipment = shipment_class.new(test_shipment.directory) @@ -109,7 +109,7 @@ def self.gen_contone_2spp_fails # rubocop:disable Metrics/AbcSize, Metrics/Metho generate_tests "contone_2spp_fails", test_proc end - def self.gen_contone_resolution_fails # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_contone_resolution_fails test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T contone 1") shipment = shipment_class.new(test_shipment.directory) @@ -125,7 +125,7 @@ def self.gen_contone_resolution_fails # rubocop:disable Metrics/AbcSize, Metrics generate_tests "contone_resolution_fails", test_proc end - def self.gen_garbage_tiff_fails # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_garbage_tiff_fails test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T contone 1") shipment = shipment_class.new(test_shipment.directory) diff --git a/test/jhove_test.rb b/test/jhove_test.rb index 3693f97..e283b3e 100755 --- a/test/jhove_test.rb +++ b/test/jhove_test.rb @@ -43,7 +43,7 @@ def self.gen_run generate_tests "run", test_proc end - def self.gen_error # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_error test_proc = proc { |shipment_class, test_shipment_class, dir, opts| setup_test(shipment_class, test_shipment_class, dir, opts) tiff = File.join(@shipment.objid_to_path(@shipment.objids[0]), @@ -64,7 +64,7 @@ def self.gen_error # rubocop:disable Metrics/AbcSize, Metrics/MethodLength generate_tests "error", test_proc end - def self.gen_error_fields # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_error_fields test_proc = proc { |shipment_class, test_shipment_class, dir, opts| setup_test(shipment_class, test_shipment_class, dir, opts) ENV["FAKE_FEED_VALIDATE_LONG"] = "1" diff --git a/test/jp2_test.rb b/test/jp2_test.rb index ad6305f..2f35e15 100755 --- a/test/jp2_test.rb +++ b/test/jp2_test.rb @@ -11,7 +11,7 @@ def test_new refute_nil jp2, "JP2 is not nil" end - def test_info # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def test_info shipment = TestShipment.new(test_name, "BC J contone 1") jp2 = JP2.new(shipment.image_files.first.path) info = jp2.info diff --git a/test/pagination_check_test.rb b/test/pagination_check_test.rb index eea2d40..d7ce891 100755 --- a/test/pagination_check_test.rb +++ b/test/pagination_check_test.rb @@ -27,7 +27,7 @@ def self.gen_run generate_tests "run", test_proc end - def self.gen_missing # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_missing test_proc = proc { |shipment_class, test_shipment_class, dir, opts| spec = "BC T bitonal 1-2 T bitonal 4-5" test_shipment = test_shipment_class.new(dir, spec) @@ -43,7 +43,7 @@ def self.gen_missing # rubocop:disable Metrics/AbcSize, Metrics/MethodLength generate_tests "missing", test_proc end - def self.gen_missing_range # rubocop:disable Metrics/AbcSize + def self.gen_missing_range test_proc = proc { |shipment_class, test_shipment_class, dir, opts| spec = "BC T bitonal 1 T bitonal 5" test_shipment = test_shipment_class.new(dir, spec) @@ -56,7 +56,7 @@ def self.gen_missing_range # rubocop:disable Metrics/AbcSize generate_tests "missing_range", test_proc end - def self.gen_duplicate # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_duplicate test_proc = proc { |shipment_class, test_shipment_class, dir, opts| spec = "BC T bitonal 1 J contone 1" test_shipment = test_shipment_class.new(dir, spec) diff --git a/test/postflight_test.rb b/test/postflight_test.rb index 0b3b909..d3e40c7 100755 --- a/test/postflight_test.rb +++ b/test/postflight_test.rb @@ -4,7 +4,7 @@ require "minitest/autorun" require "postflight" -class PostflightTest < Minitest::Test # rubocop:disable Metrics/ClassLength +class PostflightTest < Minitest::Test def setup opts = {no_progress: true, feed_validate_script: "test/bin/fake_feed_validate.pl"} @@ -25,7 +25,7 @@ def self.gen_new generate_tests "new", test_proc end - def self.gen_run # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_run test_proc = proc { |shipment_class, test_shipment_class, dir, opts| spec = "BC T bitonal 1 T contone 2" test_shipment = test_shipment_class.new(dir, spec) @@ -39,7 +39,7 @@ def self.gen_run # rubocop:disable Metrics/AbcSize, Metrics/MethodLength generate_tests "run", test_proc end - def self.gen_metadata_mismatch_removed # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_metadata_mismatch_removed test_proc = proc { |shipment_class, test_shipment_class, dir, opts| spec = "BC T bitonal 1 BC T bitonal 1" test_shipment = test_shipment_class.new(dir, spec) @@ -57,7 +57,7 @@ def self.gen_metadata_mismatch_removed # rubocop:disable Metrics/AbcSize, Metric generate_tests "metadata_mismatch_removed", test_proc end - def self.gen_metadata_mismatch_added # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_metadata_mismatch_added test_proc = proc { |shipment_class, test_shipment_class, dir, opts| spec = "BC T bitonal 1 BC T bitonal 1" test_shipment = test_shipment_class.new(dir, spec) @@ -75,7 +75,7 @@ def self.gen_metadata_mismatch_added # rubocop:disable Metrics/AbcSize, Metrics/ generate_tests "metadata_mismatch_added", test_proc end - def self.gen_feed_validate_error # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_feed_validate_error test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bitonal 1 T contone 2") shipment = shipment_class.new(test_shipment.directory) @@ -100,7 +100,7 @@ def self.gen_feed_validate_error # rubocop:disable Metrics/AbcSize, Metrics/Meth generate_tests "feed_validate_error", test_proc end - def self.gen_feed_validate_crash # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_feed_validate_crash test_proc = proc { |shipment_class, test_shipment_class, dir, opts| ENV["FAKE_FEED_VALIDATE_CRASH"] = "1" test_shipment = test_shipment_class.new(dir, "BC T bitonal 1 T contone 2") @@ -116,7 +116,7 @@ def self.gen_feed_validate_crash # rubocop:disable Metrics/AbcSize, Metrics/Meth generate_tests "feed_validate_crash", test_proc end - def self.gen_checksum_mismatch # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_checksum_mismatch test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bitonal 1 T contone 2") shipment = shipment_class.new(test_shipment.directory) @@ -134,7 +134,7 @@ def self.gen_checksum_mismatch # rubocop:disable Metrics/AbcSize, Metrics/Method generate_tests "checksum_mismatch", test_proc end - def self.gen_file_missing # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_file_missing test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bitonal 1 T contone 2") shipment = shipment_class.new(test_shipment.directory) @@ -152,7 +152,7 @@ def self.gen_file_missing # rubocop:disable Metrics/AbcSize, Metrics/MethodLengt generate_tests "file_missing", test_proc end - def self.gen_file_added # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_file_added test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bitonal 1 T contone 2") shipment = shipment_class.new(test_shipment.directory) diff --git a/test/preflight_test.rb b/test/preflight_test.rb index 6de3e9b..bd8f5b0 100755 --- a/test/preflight_test.rb +++ b/test/preflight_test.rb @@ -4,7 +4,7 @@ require "minitest/autorun" require "preflight" -class PreflightTest < Minitest::Test # rubocop:disable Metrics/ClassLength +class PreflightTest < Minitest::Test def setup @config = Config.new({no_progress: true}) end @@ -23,7 +23,7 @@ def self.gen_new generate_tests "new", test_proc end - def self.gen_run # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_run test_proc = proc { |shipment_class, test_shipment_class, dir, opts| spec = "BC T bitonal 1 BC T bitonal 1" test_shipment = test_shipment_class.new(dir, spec) @@ -52,7 +52,7 @@ def self.gen_validate_objid generate_tests "validate_objid", test_proc end - def self.gen_remove_ds_store # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_remove_ds_store test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bitonal 1") shipment = shipment_class.new(test_shipment.directory) @@ -70,7 +70,7 @@ def self.gen_remove_ds_store # rubocop:disable Metrics/AbcSize, Metrics/MethodLe generate_tests "remove_ds_store", test_proc end - def self.gen_remove_thumbs_db # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_remove_thumbs_db test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bitonal 1") shipment = shipment_class.new(test_shipment.directory) @@ -88,7 +88,7 @@ def self.gen_remove_thumbs_db # rubocop:disable Metrics/AbcSize, Metrics/MethodL generate_tests "remove_thumbs_db", test_proc end - def self.gen_remove_toplevel_ds_store # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_remove_toplevel_ds_store test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bitonal 1") shipment = shipment_class.new(test_shipment.directory) @@ -104,7 +104,7 @@ def self.gen_remove_toplevel_ds_store # rubocop:disable Metrics/AbcSize, Metrics generate_tests "remove_toplevel_ds_store", test_proc end - def self.gen_remove_toplevel_thumbs_db # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_remove_toplevel_thumbs_db test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bitonal 1") shipment = shipment_class.new(test_shipment.directory) @@ -122,7 +122,7 @@ def self.gen_remove_toplevel_thumbs_db # rubocop:disable Metrics/AbcSize, Metric generate_tests "remove_toplevel_thumbs_db", test_proc end - def self.gen_objid_directory_errors_and_warnings # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_objid_directory_errors_and_warnings test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC F spurious_file") shipment = shipment_class.new(test_shipment.directory) @@ -146,7 +146,7 @@ def self.gen_objid_directory_errors_and_warnings # rubocop:disable Metrics/AbcSi generate_tests "objid_directory_errors_and_warnings", test_proc end - def self.gen_shipment_directory_errors # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_shipment_directory_errors test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "F spurious_file") shipment = shipment_class.new(test_shipment.directory) diff --git a/test/processor_test.rb b/test/processor_test.rb index 7450232..fb11675 100755 --- a/test/processor_test.rb +++ b/test/processor_test.rb @@ -7,7 +7,7 @@ require "processor" require "fixtures" -class ProcessorTest < Minitest::Test # rubocop:disable Metrics/ClassLength +class ProcessorTest < Minitest::Test def setup @options = {config_dir: File.join(TEST_ROOT, "config")} end @@ -16,7 +16,7 @@ def teardown TestShipment.remove_test_shipments end - def self.gen_new # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_new test_proc = proc { |_shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir) processor = Processor.new(test_shipment.directory, opts.merge(@options)) @@ -53,7 +53,7 @@ def self.gen_stages generate_tests "stages", test_proc end - def self.gen_run # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_run test_proc = proc { |_shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC F .DS_Store") options = {no_progress: true} @@ -86,7 +86,7 @@ def self.gen_invalid_status_file # Don't pass TestShipment to anything we want to serialize -- # the initializer isn't JSON-aware - def self.gen_reload_status_file # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_reload_status_file test_proc = proc { |_shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bad_16bps 1") processor = Processor.new(test_shipment.directory, opts.merge(@options)) @@ -104,7 +104,7 @@ def self.gen_reload_status_file # rubocop:disable Metrics/AbcSize, Metrics/Metho # Don't pass TestShipment to anything we want to serialize -- # the initializer isn't JSON-aware - def self.gen_move_status_file # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_move_status_file test_proc = proc { |_shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC") processor = Processor.new(test_shipment.directory, opts.merge(@options)) @@ -120,7 +120,7 @@ def self.gen_move_status_file # rubocop:disable Metrics/AbcSize, Metrics/MethodL generate_tests "move_status_file", test_proc end - def self.gen_restore_from_source_directory # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_restore_from_source_directory test_proc = proc { |_shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T contone 1") processor = Processor.new(test_shipment.directory, opts.merge(@options)) @@ -141,7 +141,7 @@ def self.gen_restore_from_source_directory # rubocop:disable Metrics/AbcSize, Me generate_tests "restore_from_source_directory", test_proc end - def self.gen_finalize # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_finalize test_proc = proc { |_shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T contone 1") processor = Processor.new(test_shipment.directory, opts.merge(@options)) @@ -159,7 +159,7 @@ def self.gen_finalize # rubocop:disable Metrics/AbcSize, Metrics/MethodLength generate_tests "finalize", test_proc end - def self.gen_finalize_does_nothing # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_finalize_does_nothing test_proc = proc { |_shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bad_16bps 1") processor = Processor.new(test_shipment.directory, opts.merge(@options)) @@ -177,7 +177,7 @@ def self.gen_finalize_does_nothing # rubocop:disable Metrics/AbcSize, Metrics/Me generate_tests "finalize_does_nothing", test_proc end - def self.gen_restart_finalized # rubocop:disable Metrics/MethodLength + def self.gen_restart_finalized test_proc = proc { |_shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T contone 1") processor = Processor.new(test_shipment.directory, opts.merge(@options)) @@ -208,7 +208,7 @@ def teardown # Initial run detects bogus file, replacement allows second run to pass, # and fixity is updated with the new file. - def self.gen_error_correction # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_error_correction test_proc = proc { |_shipment_class, test_shipment_class, dir, opts| spec = "BC T contone 1 BC T bad_16bps 1" test_shipment = test_shipment_class.new(dir, spec) diff --git a/test/query_tool_test.rb b/test/query_tool_test.rb index 8ef9ece..6ca6a44 100755 --- a/test/query_tool_test.rb +++ b/test/query_tool_test.rb @@ -5,7 +5,7 @@ require "stringio" require "query_tool" -class QueryToolTest < Minitest::Test # rubocop:disable Metrics/ClassLength +class QueryToolTest < Minitest::Test def setup @options = {config_dir: File.join(TEST_ROOT, "config"), no_progress: true} @@ -25,7 +25,7 @@ def self.gen_new generate_tests "new", test_proc end - def self.gen_agenda_cmd # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_agenda_cmd test_proc = proc { |_shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T contone 1") processor = Processor.new(test_shipment.directory, opts.merge(@options)) @@ -42,7 +42,7 @@ def self.gen_agenda_cmd # rubocop:disable Metrics/AbcSize, Metrics/MethodLength generate_tests "agenda_cmd", test_proc end - def self.gen_agenda_cmd_no_agenda # rubocop:disable Metrics/MethodLength + def self.gen_agenda_cmd_no_agenda test_proc = proc { |_shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T contone 1") processor = Processor.new(test_shipment.directory, opts.merge(@options)) @@ -71,7 +71,7 @@ def self.gen_objids_cmd generate_tests "objids_cmd", test_proc end - def self.gen_objids_cmd_with_errors # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_objids_cmd_with_errors test_proc = proc { |_shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bad_16bps 1") processor = Processor.new(test_shipment.directory, opts.merge(@options)) @@ -88,7 +88,7 @@ def self.gen_objids_cmd_with_errors # rubocop:disable Metrics/AbcSize, Metrics/M generate_tests "objids_cmd_with_errors", test_proc end - def self.gen_errors_cmd # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_errors_cmd test_proc = proc { |_shipment_class, test_shipment_class, dir, opts| spec = "BC T contone 1 BC T contone 1" test_shipment = test_shipment_class.new(dir, spec) @@ -116,7 +116,7 @@ def self.gen_errors_cmd # rubocop:disable Metrics/AbcSize, Metrics/MethodLength generate_tests "errors_cmd", test_proc end - def self.gen_warnings_cmd # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_warnings_cmd test_proc = proc { |_shipment_class, test_shipment_class, dir, opts| spec = "BC T contone 1 BC T contone 1" test_shipment = test_shipment_class.new(dir, spec) @@ -167,7 +167,7 @@ def self.gen_status_cmd_err generate_tests "status_cmd_err", test_proc end - def self.gen_fixity_cmd # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_fixity_cmd test_proc = proc { |_shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T contone 2-3") processor = Processor.new(test_shipment.directory, opts.merge(@options)) @@ -199,7 +199,7 @@ def self.gen_fixity_cmd # rubocop:disable Metrics/AbcSize, Metrics/MethodLength generate_tests "fixity_cmd", test_proc end - def self.gen_fixity_cmd_not_yet_populated # rubocop:disable Metrics/MethodLength + def self.gen_fixity_cmd_not_yet_populated test_proc = proc { |_shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC") processor = Processor.new(test_shipment.directory, opts.merge(@options)) diff --git a/test/shipment_test.rb b/test/shipment_test.rb index 8dd6b58..ef6245c 100755 --- a/test/shipment_test.rb +++ b/test/shipment_test.rb @@ -4,7 +4,7 @@ require "minitest/autorun" require "shipment" -class ShipmentTest < Minitest::Test # rubocop:disable Metrics/ClassLength +class ShipmentTest < Minitest::Test def teardown TestShipment.remove_test_shipments end @@ -31,7 +31,7 @@ def self.gen_directory generate_tests "directory", test_proc end - def self.gen_path_components_from_objid # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_path_components_from_objid test_proc = proc { |shipment_class, test_shipment_class, dir, _opts| test_shipment = test_shipment_class.new(dir, "BC") shipment = shipment_class.new(test_shipment.directory) @@ -48,7 +48,7 @@ def self.gen_path_components_from_objid # rubocop:disable Metrics/AbcSize, Metri generate_tests "objid_to_path", test_proc end - def self.gen_source_directory # rubocop:disable Metrics/MethodLength + def self.gen_source_directory test_proc = proc { |shipment_class, test_shipment_class, dir, _opts| test_shipment = test_shipment_class.new(dir) shipment = shipment_class.new(test_shipment.directory) @@ -62,7 +62,7 @@ def self.gen_source_directory # rubocop:disable Metrics/MethodLength generate_tests "source_directory", test_proc end - def self.gen_tmp_directory # rubocop:disable Metrics/MethodLength + def self.gen_tmp_directory test_proc = proc { |shipment_class, test_shipment_class, dir, _opts| test_shipment = test_shipment_class.new(dir) shipment = shipment_class.new(test_shipment.directory) @@ -88,7 +88,7 @@ def self.gen_source_objids generate_tests "source_objids", test_proc end - def self.gen_image_files # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_image_files test_proc = proc { |shipment_class, test_shipment_class, dir, _opts| spec = "BC T contone 1 T contone 2 BC T contone 1 BC" test_shipment = test_shipment_class.new(dir, spec) @@ -102,7 +102,7 @@ def self.gen_image_files # rubocop:disable Metrics/AbcSize, Metrics/MethodLength generate_tests "image_files", test_proc end - def self.gen_setup_source_directory # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_setup_source_directory test_proc = proc { |shipment_class, test_shipment_class, dir, _opts| test_shipment = test_shipment_class.new(dir, "BC T contone 1") shipment = shipment_class.new(test_shipment.directory) @@ -118,7 +118,7 @@ def self.gen_setup_source_directory # rubocop:disable Metrics/AbcSize, Metrics/M generate_tests "setup_source_directory", test_proc end - def self.gen_restore_from_source_directory # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_restore_from_source_directory test_proc = proc { |shipment_class, test_shipment_class, dir, _opts| test_shipment = test_shipment_class.new(dir, "BC T contone 1") shipment = shipment_class.new(test_shipment.directory) @@ -136,7 +136,7 @@ def self.gen_restore_from_source_directory # rubocop:disable Metrics/AbcSize, Me generate_tests "restore_from_source_directory", test_proc end - def self.gen_partial_restore_from_source_directory # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_partial_restore_from_source_directory test_proc = proc { |shipment_class, test_shipment_class, dir, _opts| spec = "BC T contone 1 BC T contone 1" test_shipment = test_shipment_class.new(dir, spec) @@ -171,8 +171,8 @@ def self.gen_restore_from_nonexistent_source_directory generate_tests "restore_from_nonexistent_source_directory", test_proc end - def self.gen_fixity_check # rubocop:disable Metrics/AbcSize, Metrics/MethodLength - test_proc = proc { |shipment_class, test_shipment_class, dir, _opts| # rubocop:disable Metrics/BlockLength + def self.gen_fixity_check + test_proc = proc { |shipment_class, test_shipment_class, dir, _opts| test_shipment = test_shipment_class.new(dir, "BC T contone 2-3") shipment = shipment_class.new(test_shipment.directory) shipment.setup_source_directory @@ -211,7 +211,7 @@ def self.gen_fixity_check # rubocop:disable Metrics/AbcSize, Metrics/MethodLengt generate_tests "fixity_check", test_proc end - def self.gen_finalize # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_finalize test_proc = proc { |shipment_class, test_shipment_class, dir| test_shipment = test_shipment_class.new(dir, "BC T contone 1") shipment = shipment_class.new(test_shipment.directory) diff --git a/test/stage_test.rb b/test/stage_test.rb index 56850a2..58e515f 100755 --- a/test/stage_test.rb +++ b/test/stage_test.rb @@ -46,7 +46,7 @@ def self.gen_cleanup_tempdirs generate_tests "cleanup_tempdirs", test_proc end - def self.gen_cleanup_delete_on_success # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_cleanup_delete_on_success test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC") shipment = shipment_class.new(test_shipment.directory) diff --git a/test/tagger_test.rb b/test/tagger_test.rb index e434ed8..8aba380 100755 --- a/test/tagger_test.rb +++ b/test/tagger_test.rb @@ -23,7 +23,7 @@ def self.gen_new generate_tests "new", test_proc end - def self.gen_default_tags # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_default_tags test_proc = proc { |shipment_class, test_shipment_class, dir, opts| spec = "BC T bitonal 1 BC T bitonal 1" test_shipment = test_shipment_class.new(dir, spec) @@ -43,7 +43,7 @@ def self.gen_default_tags # rubocop:disable Metrics/AbcSize, Metrics/MethodLengt generate_tests "default_tags", test_proc end - def self.gen_artist_tag # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_artist_tag test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bitonal 1") shipment = shipment_class.new(test_shipment.directory) @@ -59,7 +59,7 @@ def self.gen_artist_tag # rubocop:disable Metrics/AbcSize, Metrics/MethodLength generate_tests "artist_tag", test_proc end - def self.gen_scanner_tag # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_scanner_tag test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bitonal 1") shipment = shipment_class.new(test_shipment.directory) @@ -78,7 +78,7 @@ def self.gen_scanner_tag # rubocop:disable Metrics/AbcSize, Metrics/MethodLength generate_tests "scanner_tag", test_proc end - def self.gen_software_tag # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_software_tag test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bitonal 1") shipment = shipment_class.new(test_shipment.directory) @@ -106,7 +106,7 @@ def teardown TestShipment.remove_test_shipments end - def self.gen_custom_artist_tag # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_custom_artist_tag test_proc = proc { |shipment_class, test_shipment_class, dir, opts| artist = "University of Michigan: Secret Vaults" test_shipment = test_shipment_class.new(dir, "BC T bitonal 1") @@ -126,7 +126,7 @@ def self.gen_custom_artist_tag # rubocop:disable Metrics/AbcSize, Metrics/Method generate_tests "custom_artist_tag", test_proc end - def self.gen_custom_scanner_tag # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_custom_scanner_tag test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bitonal 1") shipment = shipment_class.new(test_shipment.directory) @@ -149,7 +149,7 @@ def self.gen_custom_scanner_tag # rubocop:disable Metrics/AbcSize, Metrics/Metho generate_tests "custom_scanner_tag", test_proc end - def self.gen_bogus_custom_scanner_tag # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_bogus_custom_scanner_tag test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T bitonal 1") shipment = shipment_class.new(test_shipment.directory) @@ -163,7 +163,7 @@ def self.gen_bogus_custom_scanner_tag # rubocop:disable Metrics/AbcSize, Metrics generate_tests "bogus_custom_scanner_tag", test_proc end - def self.gen_custom_software_tag # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def self.gen_custom_software_tag test_proc = proc { |shipment_class, test_shipment_class, dir, opts| software = "WhizzySoft ScanR v33" test_shipment = test_shipment_class.new(dir, "BC T bitonal 1") diff --git a/test/test_helper.rb b/test/test_helper.rb index d9994e7..610de31 100755 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -50,7 +50,7 @@ def self.add_test(name) # for the Minitest::Test class invoking it. # Each test case in the Minitest::Test creates test pairs using this # routine and then invokes them with this at the end of the file: - def self.generate_tests(name, block) # rubocop:disable Metrics/MethodLength + def self.generate_tests(name, block) add_test name ["", "DLXS"].each do |type| method_name = "test_#{name}" diff --git a/test/test_shipment.rb b/test/test_shipment.rb index 5d51919..9d6e1cb 100755 --- a/test/test_shipment.rb +++ b/test/test_shipment.rb @@ -5,14 +5,14 @@ require "fixtures" require_relative "../lib/shipment" -class TestShipment < Shipment # rubocop:disable Metrics/ClassLength +class TestShipment < Shipment attr_reader :ordered_objids PATH = File.join(__dir__, "shipments").freeze # Yes, we want this shared with subclasses def self.test_shipments - @@test_shipments ||= [] # rubocop:disable Style/ClassVars + @@test_shipments ||= [] end def self.remove_test_shipments @@ -56,7 +56,7 @@ def initialize(name, spec = "") process_spec spec end - def process_spec(spec) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength + def process_spec(spec) @current_dir = @dir elements = spec.split(/\s+/) while elements.any? @@ -95,7 +95,7 @@ def handle_bogus_objid_op @ordered_objids << objid end - def handle_tiff_op(name, dest) # rubocop:disable Metrics/MethodLength + def handle_tiff_op(name, dest) fixture = Fixtures.tiff_fixture(name) case dest when /^\d+$/ @@ -113,7 +113,7 @@ def handle_tiff_op(name, dest) # rubocop:disable Metrics/MethodLength end end - def handle_jp2_op(name, dest) # rubocop:disable Metrics/MethodLength + def handle_jp2_op(name, dest) fixture = Fixtures.jp2_fixture(name) case dest when /^\d+$/ diff --git a/test/test_shipment_test.rb b/test/test_shipment_test.rb index 364a0af..ae60608 100755 --- a/test/test_shipment_test.rb +++ b/test/test_shipment_test.rb @@ -23,7 +23,7 @@ def test_invalid_objids end end - def test_generate_test_shipment_objid # rubocop:disable Metrics/AbcSize + def test_generate_test_shipment_objid shipment = TestShipment.new(test_name, "BC") assert_equal 1, shipment.objids.count, "correct number of objids" assert File.directory?(shipment.directory), "#{test_name} is directory" @@ -34,7 +34,7 @@ def test_generate_test_shipment_objid # rubocop:disable Metrics/AbcSize "objid #{shipment.objids[0]} valid" end - def test_generate_test_shipment_bogus_objid # rubocop:disable Metrics/AbcSize + def test_generate_test_shipment_bogus_objid shipment = TestShipment.new(test_name, "BBC") assert_equal 1, shipment.objids.count, "correct number of objids" assert File.directory?(shipment.directory), "#{test_name} is directory" @@ -104,7 +104,7 @@ def test_unknown_jp2_format end class DLXSTestShipmentTest < Minitest::Test - def test_generate_test_shipment_dlxs_objid # rubocop:disable Metrics/AbcSize + def test_generate_test_shipment_dlxs_objid shipment = DLXSTestShipment.new(test_name, "BC") assert_equal 1, shipment.ordered_objids.count, "correct number of ordered objids" diff --git a/test/tiff_test.rb b/test/tiff_test.rb index 781d69c..d2a00bf 100755 --- a/test/tiff_test.rb +++ b/test/tiff_test.rb @@ -11,7 +11,7 @@ def test_new refute_nil tiff, "TIFF is not nil" end - def test_info # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def test_info shipment = TestShipment.new(test_name, "BC T contone 1") tiff = TIFF.new(shipment.image_files.first.path) info = tiff.info From cba59905c1213e62ac9fb8339ecc0ce57d5049d8 Mon Sep 17 00:00:00 2001 From: Anthony Thomas Date: Thu, 8 Jan 2026 11:29:48 -0500 Subject: [PATCH 21/38] add jp2 metadata in Compressor --- lib/compressor.rb | 44 ++++++++++++++++++++++++++++++++++++++-- lib/stage/compression.rb | 1 - spec/compressor_spec.rb | 28 ++++++++++++++++++++----- 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/lib/compressor.rb b/lib/compressor.rb index eaabcc8..c1fc80b 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -31,6 +31,38 @@ def self.remove_tiff_metadata(source:, destination:) status = Command.new(cmd).run LogEntry.info(command: cmd, time: status[:time]) end + + def self.copy_jp2_metadata(source, destination, document_name, tiffinfo) + # If the original image has a date, we want it. If not, we + # want to add the current date. + # date "%Y-%m-%dT%H:%M:%S" + datetime = if tiffinfo[:date_time] + "-IFD0:ModifyDate>XMP-tiff:DateTime" + else + "-XMP-tiff:DateTime=#{Time.now.strftime("%FT%T")}" + end + cmd = "exiftool -tagsFromFile #{source}" \ + " '-XMP-dc:source=#{document_name}'" \ + " '-XMP-tiff:Compression=JPEG 2000'" \ + " '-IFD0:ImageWidth>XMP-tiff:ImageWidth'" \ + " '-IFD0:ImageHeight>XMP-tiff:ImageHeight'" \ + " '-IFD0:BitsPerSample>XMP-tiff:BitsPerSample'" \ + " '-IFD0:PhotometricInterpretation>XMP-tiff:" \ + "PhotometricInterpretation'" \ + " '-IFD0:Orientation>XMP-tiff:Orientation'" \ + " '-IFD0:SamplesPerPixel>XMP-tiff:SamplesPerPixel'" \ + " '-IFD0:XResolution>XMP-tiff:XResolution'" \ + " '-IFD0:YResolution>XMP-tiff:YResolution'" \ + " '-IFD0:ResolutionUnit>XMP-tiff:ResolutionUnit'" \ + " '-IFD0:Artist>XMP-tiff:Artist'" \ + " '-IFD0:Make>XMP-tiff:Make'" \ + " '-IFD0:Model>XMP-tiff:Model'" \ + " '-IFD0:Software>XMP-tiff:Software'" \ + " '#{datetime}'" \ + " -overwrite_original #{destination}" + status = Command.new(cmd).run + LogEntry.info(command: cmd, time: status[:time]) + end end module ImageMagick @@ -69,14 +101,22 @@ def initialize(image_file:, tmpdir:, log: "whatever") def run(compression_tool = Kakadu) # We don't want any XMP metadata to be copied over on its own. If # it's been a while since we last ran exiftool, this might take a sec. - @log.log_it ExifTool.remove_tiff_metadata(source: @image_file.path, destination: sparse_path) + @log.log_it ExifTool.remove_tiff_metadata(source: image_file.path, destination: sparse_path) @log.log_it ImageMagick.remove_tiff_alpha(sparse_path) if tiffinfo[:alpha] @log.log_it ImageMagick.strip_tiff_profiles(sparse_path) if tiffinfo[:icc] @log.log_it compression_tool.compress(sparse_path, new_path, tiffinfo) - # copy_jp2_metadata(image_file.path, new_image, document_name, tiffinfo) + @log.log_it ExifTool.copy_jp2_metadata(image_file.path, new_path, document_name, tiffinfo) + # copy_jp2_alphaless_metadata(sparse, new_image) if tiffinfo[:alpha] end + def document_name + objid_file_parts = image_file.objid_file.split("/") + final_image_name = File.basename(objid_file_parts.last, ".*") + ".jp2" + objid_file_parts[-1] = final_image_name + File.join(objid_file_parts) + end + def sparse_path @sparse_path ||= File.join(tmpdir, "sparse.tif") end diff --git a/lib/stage/compression.rb b/lib/stage/compression.rb index 3ad5b6c..93d65d0 100755 --- a/lib/stage/compression.rb +++ b/lib/stage/compression.rb @@ -60,7 +60,6 @@ def handle_8_bps_conversion(compressor) tiffinfo = compressor.tiffinfo tmpdir = compressor.tmpdir sparse = compressor.sparse_path - # sparse = File.join(tmpdir, "sparse.tif") new_image = compressor.new_path final_image_name = File.basename(image_file.path, ".*") + ".jp2" final_image = File.join(File.dirname(image_file.path), final_image_name) diff --git a/spec/compressor_spec.rb b/spec/compressor_spec.rb index 4d0e3e3..ccf6e36 100644 --- a/spec/compressor_spec.rb +++ b/spec/compressor_spec.rb @@ -3,18 +3,31 @@ context "#run" do let(:log) { Log.new } - let(:compression_tool) { class_double(Kakadu, compress: LogEntry.info(command: nil, time: nil)) } + # let(:compression_tool) { class_double(Kakadu, compress: LogEntry.info(command: nil, time: nil)) } + let(:compression_tool) { Kakadu } + # image file has path, objid, objid_file, file + # objid="omzhx8s5.0074.149" + # path="/usr/src/app/test/shipments/DLXSCompressorTest_test_run_DLXS/omzhx8s5/0074/149/00000001.tif" + # objid_file="omzhx8s5/0074/149/00000001.tif" + # file="00000001.tif" + let(:path) { File.join("spec/fixtures", @image_file) } + let(:objid) { "some_barcode" } + let(:objid_file) { File.join(objid, @image_file) } + let(:image_file) { double("image_file", path: path, objid: objid, objid_file: objid_file, file: @image_file) } let(:compressor) do - image_file = double("image_file", path: @path) Compressor.new(image_file: image_file, tmpdir: Pathname(temp_dir), log: log) end before(:each) do - @path = "spec/fixtures/10_10_8_400.tif" + @image_file = "10_10_8_400.tif" + end + + it "generates final document name" do + expect(compressor.document_name).to eq("some_barcode/10_10_8_400.jp2") end it "removes alpha when it exists" do - @path = "spec/fixtures/10_10_8_400_alpha.tif" + @image_file = "10_10_8_400_alpha.tif" compressor.run(compression_tool) expect(log.entries).to include(match("-alpha off")) end @@ -25,7 +38,7 @@ end it "strips tiff profile data when it exists" do - @path = "spec/fixtures/10_10_8_400_icc.tif" + @image_file = "10_10_8_400_icc.tif" compressor.run(compression_tool) expect(log.entries).to include(match("-strip")) end @@ -39,6 +52,11 @@ compressor.run expect(log.entries).to include(match("kdu_compress")) end + + it "copies original metadata to the jpeg2000" do + compressor.run + expect(log.entries).to include(match("exiftool -tagsFromFile")) + end end end From e0e893eb16a665eb2410822968309eb140148a1a Mon Sep 17 00:00:00 2001 From: Anthony Thomas Date: Thu, 8 Jan 2026 13:37:23 -0500 Subject: [PATCH 22/38] add copy-alphaless-metadata to Compressor --- lib/compressor.rb | 14 ++++++++++++-- spec/compressor_spec.rb | 21 ++++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/lib/compressor.rb b/lib/compressor.rb index c1fc80b..9f5e2dc 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -63,6 +63,17 @@ def self.copy_jp2_metadata(source, destination, document_name, tiffinfo) status = Command.new(cmd).run LogEntry.info(command: cmd, time: status[:time]) end + + def self.copy_jp2_alphaless_metadata(source, destination) + cmd = "exiftool -tagsFromFile #{source}" \ + " '-IFD0:BitsPerSample>XMP-tiff:BitsPerSample'" \ + " '-IFD0:SamplesPerPixel>XMP-tiff:SamplesPerPixel'" \ + " '-IFD0:PhotometricInterpretation>XMP-tiff:" \ + "PhotometricInterpretation'" \ + " -overwrite_original '#{destination}'" + status = Command.new(cmd).run + LogEntry.info(command: cmd, time: status[:time]) + end end module ImageMagick @@ -106,8 +117,7 @@ def run(compression_tool = Kakadu) @log.log_it ImageMagick.strip_tiff_profiles(sparse_path) if tiffinfo[:icc] @log.log_it compression_tool.compress(sparse_path, new_path, tiffinfo) @log.log_it ExifTool.copy_jp2_metadata(image_file.path, new_path, document_name, tiffinfo) - - # copy_jp2_alphaless_metadata(sparse, new_image) if tiffinfo[:alpha] + @log.log_it ExifTool.copy_jp2_alphaless_metadata(sparse_path, new_path) if tiffinfo[:alpha] end def document_name diff --git a/spec/compressor_spec.rb b/spec/compressor_spec.rb index ccf6e36..e099cb3 100644 --- a/spec/compressor_spec.rb +++ b/spec/compressor_spec.rb @@ -1,10 +1,16 @@ +class FakeCompressionTool + def self.compress(sparse_path, new_path, tiffinfo) + FileUtils.cp(File.join("spec/fixtures/10_10_8_400.jp2"), new_path) + LogEntry.info(command: nil, time: nil) + end +end + describe Compressor do include_context "uses temp dir" context "#run" do let(:log) { Log.new } - # let(:compression_tool) { class_double(Kakadu, compress: LogEntry.info(command: nil, time: nil)) } - let(:compression_tool) { Kakadu } + let(:compression_tool) { FakeCompressionTool } # image file has path, objid, objid_file, file # objid="omzhx8s5.0074.149" # path="/usr/src/app/test/shipments/DLXSCompressorTest_test_run_DLXS/omzhx8s5/0074/149/00000001.tif" @@ -55,7 +61,16 @@ it "copies original metadata to the jpeg2000" do compressor.run - expect(log.entries).to include(match("exiftool -tagsFromFile")) + expect(log.entries).to include(match("tiff:Compression=JPEG 2000")) + end + + xit "copies original image datetime when present" do + end + + it "copies alphaless metadata to the jp2 when tiff has alpha" do + @image_file = "10_10_8_400_alpha.tif" + compressor.run(compression_tool) + expect(log.entries).to include(match("PhotometricInterpretation>XMP-tiff")).twice end end end From d0d41f2d06ee1ab2792f6fa11c70a18ae18a4a8e Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Thu, 8 Jan 2026 14:13:54 -0500 Subject: [PATCH 23/38] moves comments to compressor; Replace Temp with Query info about file paths to compressor object. Replaces local variables in Compression with queries to the compressor object. --- lib/compressor.rb | 33 ++++++++++++++++++++++++++++++++- lib/stage/compression.rb | 39 ++++----------------------------------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/lib/compressor.rb b/lib/compressor.rb index 9f5e2dc..3bde770 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -115,14 +115,45 @@ def run(compression_tool = Kakadu) @log.log_it ExifTool.remove_tiff_metadata(source: image_file.path, destination: sparse_path) @log.log_it ImageMagick.remove_tiff_alpha(sparse_path) if tiffinfo[:alpha] @log.log_it ImageMagick.strip_tiff_profiles(sparse_path) if tiffinfo[:icc] + + # mrio: copying this note over from Compression.rb. Not sure what it means + # or implies yet. + # + # FIXME: process-tiffs.sh defines this variable but does not + # use it. Check the original on tang. + # if /Samples\/Pixel:\s3/.match? metadata + # jp2_space = 'sRGB' + # else + # jp2_space = 'sLUM' + # end + + # We have a TIFF with no XMP now. We try to convert it to JP2. + # This will always take a second. Other than the initial loading + # of exiftool libraries, this is the only JP2 step that takes + # noticeable time. @log.log_it compression_tool.compress(sparse_path, new_path, tiffinfo) + + # We have our JP2; we can remove the middle TIFF. Then we try + # to grab metadata from the original TIFF. This should be very + # quick since we just used exiftool a few lines back. @log.log_it ExifTool.copy_jp2_metadata(image_file.path, new_path, document_name, tiffinfo) + + # If our image had an alpha channel, it'll be gone now, and + # the XMP data needs to reflect that (previously, we were + # taking that info from the original image). @log.log_it ExifTool.copy_jp2_alphaless_metadata(sparse_path, new_path) if tiffinfo[:alpha] end + def final_image_name + File.basename(image_file.file, ".*") + ".jp2" + end + + def final_image_path + File.join(File.dirname(image_file.path), final_image_name) + end + def document_name objid_file_parts = image_file.objid_file.split("/") - final_image_name = File.basename(objid_file_parts.last, ".*") + ".jp2" objid_file_parts[-1] = final_image_name File.join(objid_file_parts) end diff --git a/lib/stage/compression.rb b/lib/stage/compression.rb index 93d65d0..6189a6a 100755 --- a/lib/stage/compression.rb +++ b/lib/stage/compression.rb @@ -57,47 +57,16 @@ def run(agenda) def handle_8_bps_conversion(compressor) image_file = compressor.image_file - tiffinfo = compressor.tiffinfo tmpdir = compressor.tmpdir - sparse = compressor.sparse_path - new_image = compressor.new_path - final_image_name = File.basename(image_file.path, ".*") + ".jp2" - final_image = File.join(File.dirname(image_file.path), final_image_name) - document_name = File.join(shipment.objid_to_path(image_file.objid), - final_image_name) - on_disk_temp_image = final_image.sub(shipment.directory, shipment.tmp_directory) + + on_disk_temp_image = compressor.final_image_path.sub(shipment.directory, shipment.tmp_directory) system("mkdir -p #{File.dirname(on_disk_temp_image)}") compressor.run - # need a test that looks for this - # remove_tiff_alpha(sparse) if tiffinfo[:alpha] - - # strip_tiff_profiles(sparse) if tiffinfo[:icc] - - # FIXME: process-tiffs.sh defines this variable but does not - # use it. Check the original on tang. - # if /Samples\/Pixel:\s3/.match? metadata - # jp2_space = 'sRGB' - # else - # jp2_space = 'sLUM' - # end - # We have a TIFF with no XMP now. We try to convert it to JP2. - # This will always take a second. Other than the initial loading - # of exiftool libraries, this is the only JP2 step that takes - # noticeable time. - # compress_jp2(sparse, new_image, tiffinfo) - # We have our JP2; we can remove the middle TIFF. Then we try - # to grab metadata from the original TIFF. This should be very - # quick since we just used exiftool a few lines back. - copy_jp2_metadata(image_file.path, new_image, document_name, tiffinfo) - # If our image had an alpha channel, it'll be gone now, and - # the XMP data needs to reflect that (previously, we were - # taking that info from the original image). - copy_jp2_alphaless_metadata(sparse, new_image) if tiffinfo[:alpha] - system("cp #{new_image} #{on_disk_temp_image}") + system("cp #{compressor.new_path} #{on_disk_temp_image}") system("rm -r #{tmpdir}/*") - copy_on_success on_disk_temp_image, final_image, image_file.objid + copy_on_success on_disk_temp_image, compressor.final_image_path, image_file.objid delete_on_success image_file.path, image_file.objid end From e76c49ed9917044d9a165b81a4c22345d92cc92a Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Thu, 8 Jan 2026 14:44:48 -0500 Subject: [PATCH 24/38] removes unused methods --- lib/stage/compression.rb | 98 ++-------------------------------------- 1 file changed, 5 insertions(+), 93 deletions(-) diff --git a/lib/stage/compression.rb b/lib/stage/compression.rb index 6189a6a..4dce230 100755 --- a/lib/stage/compression.rb +++ b/lib/stage/compression.rb @@ -70,112 +70,24 @@ def handle_8_bps_conversion(compressor) delete_on_success image_file.path, image_file.objid end - def jp2_clevels(tiffinfo) - # Get the width and height, figure out which is larger. - size = [tiffinfo[:width], tiffinfo[:height]].max - # Calculate appropriate Clevels. - clevels = (Math.log(size.to_i / 100.0) / Math.log(2)).to_i - (clevels < JP2_LEVEL_MIN) ? JP2_LEVEL_MIN : clevels - end - - def remove_tiff_metadata(path, destination) - cmd = "exiftool -XMP:All= -MakerNotes:All= #{path} -o #{destination}" - status = Command.new(cmd).run - log cmd, status[:time] - end - - def remove_tiff_alpha(path) - tmp = path + ".alphaoff" - cmd = "convert #{path} -alpha off #{tmp}" - status = Command.new(cmd).run - log cmd, status[:time] - FileUtils.mv(tmp, path) - end - - def strip_tiff_profiles(path) - tmp = path + ".stripped" - cmd = "convert #{path} -strip #{tmp}" - begin - status = Command.new(cmd).run - rescue => e - warning = "couldn't remove ICC profile (#{cmd}) (#{e.message})" - add_warning Error.new(warning, objid_from_path(path), path) - else - log cmd, status[:time] - FileUtils.mv(tmp, path) - end - end - - def compress_jp2(source, destination, tiffinfo) - clevels = jp2_clevels(tiffinfo) - cmd = "kdu_compress -quiet -i #{source} -o #{destination}" \ - " 'Clevels=#{clevels}'" \ - " 'Clayers=#{JP2_LAYERS}'" \ - " 'Corder=#{JP2_ORDER}'" \ - " 'Cuse_sop=#{JP2_USE_SOP}'" \ - " 'Cuse_eph=#{JP2_USE_EPH}'" \ - " Cmodes=#{JP2_MODES}" \ - " -no_weights -slope '#{JP2_SLOPE}'" - status = Command.new(cmd).run - log cmd, status[:time] - end - - def copy_jp2_metadata(source, destination, document_name, tiffinfo) - # If the original image has a date, we want it. If not, we - # want to add the current date. - # date "%Y-%m-%dT%H:%M:%S" - datetime = if tiffinfo[:date_time] - "-IFD0:ModifyDate>XMP-tiff:DateTime" - else - "-XMP-tiff:DateTime=#{Time.now.strftime("%FT%T")}" - end - cmd = "exiftool -tagsFromFile #{source}" \ - " '-XMP-dc:source=#{document_name}'" \ - " '-XMP-tiff:Compression=JPEG 2000'" \ - " '-IFD0:ImageWidth>XMP-tiff:ImageWidth'" \ - " '-IFD0:ImageHeight>XMP-tiff:ImageHeight'" \ - " '-IFD0:BitsPerSample>XMP-tiff:BitsPerSample'" \ - " '-IFD0:PhotometricInterpretation>XMP-tiff:" \ - "PhotometricInterpretation'" \ - " '-IFD0:Orientation>XMP-tiff:Orientation'" \ - " '-IFD0:SamplesPerPixel>XMP-tiff:SamplesPerPixel'" \ - " '-IFD0:XResolution>XMP-tiff:XResolution'" \ - " '-IFD0:YResolution>XMP-tiff:YResolution'" \ - " '-IFD0:ResolutionUnit>XMP-tiff:ResolutionUnit'" \ - " '-IFD0:Artist>XMP-tiff:Artist'" \ - " '-IFD0:Make>XMP-tiff:Make'" \ - " '-IFD0:Model>XMP-tiff:Model'" \ - " '-IFD0:Software>XMP-tiff:Software'" \ - " '#{datetime}'" \ - " -overwrite_original #{destination}" - status = Command.new(cmd).run - log cmd, status[:time] - end - - def copy_jp2_alphaless_metadata(source, destination) - cmd = "exiftool -tagsFromFile #{source}" \ - " '-IFD0:BitsPerSample>XMP-tiff:BitsPerSample'" \ - " '-IFD0:SamplesPerPixel>XMP-tiff:SamplesPerPixel'" \ - " '-IFD0:PhotometricInterpretation>XMP-tiff:" \ - "PhotometricInterpretation'" \ - " -overwrite_original '#{destination}'" - status = Command.new(cmd).run - log cmd, status[:time] - end - def handle_1_bps_conversion(compressor) image_file = compressor.image_file tiffinfo = compressor.tiffinfo tmpdir = compressor.tmpdir + compressed = File.join(tmpdir, "#{File.basename(image_file.path)}-compressed") + page1 = File.join(tmpdir, "#{File.basename(image_file.path)}-page1") + compress_tiff(image_file.path, compressed) copy_tiff_metadata(image_file.path, compressed) copy_tiff_page1(compressed, page1) FileUtils.rm(compressed) + write_tiff_date_time page1 unless tiffinfo[:date_time] write_tiff_document_name(image_file, page1) + if tiffinfo[:software] write_tiff_software(page1, tiffinfo[:software]) else From 7efe06054e55e104aa0f56de8339832ef50ed137 Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Thu, 8 Jan 2026 16:24:24 -0500 Subject: [PATCH 25/38] enable Color and Bitonal compressors --- lib/compressor.rb | 36 +++++++++++++++++++++++++----- lib/stage/compression.rb | 3 ++- spec/compressor_spec.rb | 47 ++++++++++++++++++++-------------------- 3 files changed, 57 insertions(+), 29 deletions(-) diff --git a/lib/compressor.rb b/lib/compressor.rb index 3bde770..52ccb19 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -1,5 +1,4 @@ require "tiff" -require "ostruct" module Kakadu def self.compress(source, destination, tiffinfo) @@ -102,6 +101,20 @@ def self.strip_tiff_profiles(path) class Compressor attr_reader :tiffinfo, :image_file, :tmpdir + + def self.for(image_file:, tmpdir:, log: "whatever") + tiffinfo = TIFF.new(image_file.path).info + klass = case tiffinfo[:bps] + when 8 + Compressor::Color + when 1 + Compressor::Bitonal + else + Compressor + end + klass.new(image_file:, tmpdir:, log: log) + end + def initialize(image_file:, tmpdir:, log: "whatever") @image_file = image_file @tiffinfo = TIFF.new(image_file.path).info @@ -109,6 +122,16 @@ def initialize(image_file:, tmpdir:, log: "whatever") @log = log end + def run + raise NotImplementedError + end + + def bps + @tiffinfo[:bps] + end +end + +class Compressor::Color < Compressor def run(compression_tool = Kakadu) # We don't want any XMP metadata to be copied over on its own. If # it's been a while since we last ran exiftool, this might take a sec. @@ -144,10 +167,6 @@ def run(compression_tool = Kakadu) @log.log_it ExifTool.copy_jp2_alphaless_metadata(sparse_path, new_path) if tiffinfo[:alpha] end - def final_image_name - File.basename(image_file.file, ".*") + ".jp2" - end - def final_image_path File.join(File.dirname(image_file.path), final_image_name) end @@ -158,6 +177,10 @@ def document_name File.join(objid_file_parts) end + def final_image_name + File.basename(image_file.file, ".*") + ".jp2" + end + def sparse_path @sparse_path ||= File.join(tmpdir, "sparse.tif") end @@ -166,3 +189,6 @@ def new_path @new_path ||= File.join(tmpdir, "new.jp2") end end + +class Compressor::Bitonal < Compressor +end diff --git a/lib/stage/compression.rb b/lib/stage/compression.rb index 4dce230..54cebf3 100755 --- a/lib/stage/compression.rb +++ b/lib/stage/compression.rb @@ -23,7 +23,7 @@ def run(agenda) @bar.steps = files.count files.each_with_index do |image_file, i| begin - compressor = Compressor.new(image_file: image_file, tmpdir: create_tempdir, log: log_collection) + compressor = Compressor.for(image_file: image_file, tmpdir: create_tempdir, log: log_collection) tiffinfo = compressor.tiffinfo rescue => e add_error Error.new(e.message, image_file.objid, image_file.file) @@ -83,6 +83,7 @@ def handle_1_bps_conversion(compressor) compress_tiff(image_file.path, compressed) copy_tiff_metadata(image_file.path, compressed) copy_tiff_page1(compressed, page1) + FileUtils.rm(compressed) write_tiff_date_time page1 unless tiffinfo[:date_time] diff --git a/spec/compressor_spec.rb b/spec/compressor_spec.rb index e099cb3..1ebbf51 100644 --- a/spec/compressor_spec.rb +++ b/spec/compressor_spec.rb @@ -7,31 +7,32 @@ def self.compress(sparse_path, new_path, tiffinfo) describe Compressor do include_context "uses temp dir" + let(:log) { Log.new } + let(:compression_tool) { FakeCompressionTool } + # image file has path, objid, objid_file, file + # objid="omzhx8s5.0074.149" + # path="/usr/src/app/test/shipments/DLXSCompressorTest_test_run_DLXS/omzhx8s5/0074/149/00000001.tif" + # objid_file="omzhx8s5/0074/149/00000001.tif" + # file="00000001.tif" + let(:path) { File.join("spec/fixtures", @image_file) } + let(:objid) { "some_barcode" } + let(:objid_file) { File.join(objid, @image_file) } + let(:image_file) { double("image_file", path: path, objid: objid, objid_file: objid_file, file: @image_file) } + let(:compressor) do + Compressor.for(image_file: image_file, tmpdir: Pathname(temp_dir), log: log) + end + before(:each) do + @image_file = "10_10_8_400.tif" + end + it "generates final document name" do + expect(compressor.document_name).to eq("some_barcode/10_10_8_400.jp2") + end - context "#run" do - let(:log) { Log.new } - let(:compression_tool) { FakeCompressionTool } - # image file has path, objid, objid_file, file - # objid="omzhx8s5.0074.149" - # path="/usr/src/app/test/shipments/DLXSCompressorTest_test_run_DLXS/omzhx8s5/0074/149/00000001.tif" - # objid_file="omzhx8s5/0074/149/00000001.tif" - # file="00000001.tif" - let(:path) { File.join("spec/fixtures", @image_file) } - let(:objid) { "some_barcode" } - let(:objid_file) { File.join(objid, @image_file) } - let(:image_file) { double("image_file", path: path, objid: objid, objid_file: objid_file, file: @image_file) } - let(:compressor) do - Compressor.new(image_file: image_file, tmpdir: Pathname(temp_dir), log: log) - end - - before(:each) do - @image_file = "10_10_8_400.tif" - end - - it "generates final document name" do - expect(compressor.document_name).to eq("some_barcode/10_10_8_400.jp2") - end + it "is a Color compressor when initialized with an 8bps image" do + expect(compressor.class.to_s).to eq("Compressor::Color") + end + context "#run" do it "removes alpha when it exists" do @image_file = "10_10_8_400_alpha.tif" compressor.run(compression_tool) From 57d929b8a0ea9a22f93b9d55891cdaa186d34ccb Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Thu, 8 Jan 2026 16:59:41 -0500 Subject: [PATCH 26/38] moves tiff compression and copy tiff metadata to compressor creates TiffToPnm module for doing the compression --- lib/compressor.rb | 42 ++++++++++++++++ lib/stage/compression.rb | 35 ++----------- spec/compressor_spec.rb | 104 +++++++++++++++++++++++---------------- 3 files changed, 107 insertions(+), 74 deletions(-) diff --git a/lib/compressor.rb b/lib/compressor.rb index 52ccb19..83f6534 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -73,6 +73,24 @@ def self.copy_jp2_alphaless_metadata(source, destination) status = Command.new(cmd).run LogEntry.info(command: cmd, time: status[:time]) end + + def self.copy_tiff_metadata(source, destination) + cmd = "exiftool -tagsFromFile #{source}" \ + " '-IFD0:DocumentName'" \ + " '-IFD0:ImageDescription='" \ + " '-IFD0:Orientation'" \ + " '-IFD0:XResolution'" \ + " '-IFD0:YResolution'" \ + " '-IFD0:ResolutionUnit'" \ + " '-IFD0:ModifyDate'" \ + " '-IFD0:Artist'" \ + " '-IFD0:Make'" \ + " '-IFD0:Model'" \ + " '-IFD0:Software'" \ + " -overwrite_original '#{destination}'" + status = Command.new(cmd).run + LogEntry.info(command: cmd, time: status[:time]) + end end module ImageMagick @@ -99,6 +117,15 @@ def self.strip_tiff_profiles(path) end end +module TiffToPnm + def self.compress(source, destination) + cmd = "tifftopnm #{source} | pnmtotiff -g4 -rowsperstrip" \ + " 196136698 > #{destination}" + status = Command.new(cmd).run + LogEntry.info(command: cmd, time: status[:time]) + end +end + class Compressor attr_reader :tiffinfo, :image_file, :tmpdir @@ -191,4 +218,19 @@ def new_path end class Compressor::Bitonal < Compressor + def run + # Try to compress the image. This is the only part of this step + # that should take any time. It should take a second or so. + @log.log_it TiffToPnm.compress(image_file.path, compressed_path) + + @log.log_it ExifTool.copy_tiff_metadata(image_file.path, compressed_path) + end + + def compressed_path + @compressed_path ||= File.join(tmpdir, "#{File.basename(image_file.path)}-compressed") + end + + def page1_path + @page1_path ||= File.join(tmpdir, "#{File.basename(image_file.path)}-page1") + end end diff --git a/lib/stage/compression.rb b/lib/stage/compression.rb index 54cebf3..a1b48bc 100755 --- a/lib/stage/compression.rb +++ b/lib/stage/compression.rb @@ -75,13 +75,11 @@ def handle_1_bps_conversion(compressor) tiffinfo = compressor.tiffinfo tmpdir = compressor.tmpdir - compressed = File.join(tmpdir, - "#{File.basename(image_file.path)}-compressed") + compressed = compressor.compressed_path + page1 = compressor.page1_path - page1 = File.join(tmpdir, "#{File.basename(image_file.path)}-page1") + compressor.run - compress_tiff(image_file.path, compressed) - copy_tiff_metadata(image_file.path, compressed) copy_tiff_page1(compressed, page1) FileUtils.rm(compressed) @@ -98,33 +96,6 @@ def handle_1_bps_conversion(compressor) copy_on_success page1, image_file.path, image_file.objid end - # Try to compress the image. This is the only part of this step - # that should take any time. It should take a second or so. - def compress_tiff(path, destination) - cmd = "tifftopnm #{path} | pnmtotiff -g4 -rowsperstrip" \ - " 196136698 > #{destination}" - status = Command.new(cmd).run - log cmd, status[:time] - end - - def copy_tiff_metadata(path, destination) - cmd = "exiftool -tagsFromFile #{path}" \ - " '-IFD0:DocumentName'" \ - " '-IFD0:ImageDescription='" \ - " '-IFD0:Orientation'" \ - " '-IFD0:XResolution'" \ - " '-IFD0:YResolution'" \ - " '-IFD0:ResolutionUnit'" \ - " '-IFD0:ModifyDate'" \ - " '-IFD0:Artist'" \ - " '-IFD0:Make'" \ - " '-IFD0:Model'" \ - " '-IFD0:Software'" \ - " -overwrite_original '#{destination}'" - status = Command.new(cmd).run - log cmd, status[:time] - end - def copy_tiff_page1(path, destination) cmd = "tiffcp #{path},0 #{destination}" status = Command.new(cmd).run diff --git a/spec/compressor_spec.rb b/spec/compressor_spec.rb index 1ebbf51..73bc62a 100644 --- a/spec/compressor_spec.rb +++ b/spec/compressor_spec.rb @@ -21,57 +21,77 @@ def self.compress(sparse_path, new_path, tiffinfo) let(:compressor) do Compressor.for(image_file: image_file, tmpdir: Pathname(temp_dir), log: log) end - before(:each) do - @image_file = "10_10_8_400.tif" - end - it "generates final document name" do - expect(compressor.document_name).to eq("some_barcode/10_10_8_400.jp2") - end - - it "is a Color compressor when initialized with an 8bps image" do - expect(compressor.class.to_s).to eq("Compressor::Color") - end - - context "#run" do - it "removes alpha when it exists" do - @image_file = "10_10_8_400_alpha.tif" - compressor.run(compression_tool) - expect(log.entries).to include(match("-alpha off")) + context "color tif" do + before(:each) do + @image_file = "10_10_8_400.tif" end - - it "ignores alpha when it doesn't exist" do - compressor.run(compression_tool) - expect(log.entries).not_to include(match("-alpha off")) + it "generates final document name" do + expect(compressor.document_name).to eq("some_barcode/10_10_8_400.jp2") end - it "strips tiff profile data when it exists" do - @image_file = "10_10_8_400_icc.tif" - compressor.run(compression_tool) - expect(log.entries).to include(match("-strip")) + it "is a Color compressor when initialized with an 8bps image" do + expect(compressor.class.to_s).to eq("Compressor::Color") end - it "ignores tiff profile when it doesn't exist" do - compressor.run(compression_tool) - expect(log.entries).not_to include(match("-strip")) - end + context "#run" do + it "removes alpha when it exists" do + @image_file = "10_10_8_400_alpha.tif" + compressor.run(compression_tool) + expect(log.entries).to include(match("-alpha off")) + end - it "runs the compression tool" do - compressor.run - expect(log.entries).to include(match("kdu_compress")) - end + it "ignores alpha when it doesn't exist" do + compressor.run(compression_tool) + expect(log.entries).not_to include(match("-alpha off")) + end - it "copies original metadata to the jpeg2000" do - compressor.run - expect(log.entries).to include(match("tiff:Compression=JPEG 2000")) - end + it "strips tiff profile data when it exists" do + @image_file = "10_10_8_400_icc.tif" + compressor.run(compression_tool) + expect(log.entries).to include(match("-strip")) + end - xit "copies original image datetime when present" do - end + it "ignores tiff profile when it doesn't exist" do + compressor.run(compression_tool) + expect(log.entries).not_to include(match("-strip")) + end + + it "runs the compression tool" do + compressor.run + expect(log.entries).to include(match("kdu_compress")) + end + + it "copies original metadata to the jpeg2000" do + compressor.run + expect(log.entries).to include(match("tiff:Compression=JPEG 2000")) + end + + xit "copies original image datetime when present" do + end - it "copies alphaless metadata to the jp2 when tiff has alpha" do - @image_file = "10_10_8_400_alpha.tif" - compressor.run(compression_tool) - expect(log.entries).to include(match("PhotometricInterpretation>XMP-tiff")).twice + it "copies alphaless metadata to the jp2 when tiff has alpha" do + @image_file = "10_10_8_400_alpha.tif" + compressor.run(compression_tool) + expect(log.entries).to include(match("PhotometricInterpretation>XMP-tiff")).twice + end + end + end + context "bitonal tif" do + before(:each) do + @image_file = "10_10_1_600.tif" + end + it "is a Bitonal compressor when initialized with a 1bps image" do + expect(compressor.class.to_s).to eq("Compressor::Bitonal") + end + context "#run" do + it "runs the compression tool" do + compressor.run + expect(log.entries).to include(match("tifftopnm")) + end + it "runs copies the metadata from the original tiff to the compressed one" do + compressor.run + expect(log.entries).to include(match("exiftool -tagsFromFile #{compressor.image_file.path}")) + end end end end From 73157094ca8a4ea31353865d51feab4fdb5a3724 Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Thu, 8 Jan 2026 17:06:33 -0500 Subject: [PATCH 27/38] log_it convenience method in Compressor --- lib/compressor.rb | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/compressor.rb b/lib/compressor.rb index 83f6534..772c594 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -156,15 +156,21 @@ def run def bps @tiffinfo[:bps] end + + private + + def log_it(log_entry) + @log.log_it log_entry + end end class Compressor::Color < Compressor def run(compression_tool = Kakadu) # We don't want any XMP metadata to be copied over on its own. If # it's been a while since we last ran exiftool, this might take a sec. - @log.log_it ExifTool.remove_tiff_metadata(source: image_file.path, destination: sparse_path) - @log.log_it ImageMagick.remove_tiff_alpha(sparse_path) if tiffinfo[:alpha] - @log.log_it ImageMagick.strip_tiff_profiles(sparse_path) if tiffinfo[:icc] + log_it ExifTool.remove_tiff_metadata(source: image_file.path, destination: sparse_path) + log_it ImageMagick.remove_tiff_alpha(sparse_path) if tiffinfo[:alpha] + log_it ImageMagick.strip_tiff_profiles(sparse_path) if tiffinfo[:icc] # mrio: copying this note over from Compression.rb. Not sure what it means # or implies yet. @@ -181,17 +187,17 @@ def run(compression_tool = Kakadu) # This will always take a second. Other than the initial loading # of exiftool libraries, this is the only JP2 step that takes # noticeable time. - @log.log_it compression_tool.compress(sparse_path, new_path, tiffinfo) + log_it compression_tool.compress(sparse_path, new_path, tiffinfo) # We have our JP2; we can remove the middle TIFF. Then we try # to grab metadata from the original TIFF. This should be very # quick since we just used exiftool a few lines back. - @log.log_it ExifTool.copy_jp2_metadata(image_file.path, new_path, document_name, tiffinfo) + log_it ExifTool.copy_jp2_metadata(image_file.path, new_path, document_name, tiffinfo) # If our image had an alpha channel, it'll be gone now, and # the XMP data needs to reflect that (previously, we were # taking that info from the original image). - @log.log_it ExifTool.copy_jp2_alphaless_metadata(sparse_path, new_path) if tiffinfo[:alpha] + log_it ExifTool.copy_jp2_alphaless_metadata(sparse_path, new_path) if tiffinfo[:alpha] end def final_image_path @@ -221,9 +227,9 @@ class Compressor::Bitonal < Compressor def run # Try to compress the image. This is the only part of this step # that should take any time. It should take a second or so. - @log.log_it TiffToPnm.compress(image_file.path, compressed_path) + log_it TiffToPnm.compress(image_file.path, compressed_path) - @log.log_it ExifTool.copy_tiff_metadata(image_file.path, compressed_path) + log_it ExifTool.copy_tiff_metadata(image_file.path, compressed_path) end def compressed_path From d1e8a3e44f8e8cd2b441288529d8ed4b14b33f59 Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Mon, 12 Jan 2026 15:13:29 -0500 Subject: [PATCH 28/38] dockerfile change to bookworm --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e37825a..c98c9de 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ ################################################################################ # BASE ################################################################################ -FROM ruby:3.4-bullseye AS base +FROM ruby:3.4-bookworm AS base ARG KAKADU_FILE=KDU841_Demo_Apps_for_Linux-x86-64_231117.zip ARG FEED_VERSION=feed_v1.14.1 From c522deb3f7ffe0f5a73e5f8651cc38932b38e7b2 Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Mon, 12 Jan 2026 16:05:54 -0500 Subject: [PATCH 29/38] handles a 2 page bitonal tiff It only takes the first page. Also puts tifftopnm call in a TiffTools module because compress is a more complicated action and tiffcp is only used here. --- lib/compressor.rb | 12 ++++++++++-- spec/compressor_spec.rb | 9 +++++++++ spec/fixtures/10_10_1_600_2pg.tif | Bin 0 -> 476 bytes 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 spec/fixtures/10_10_1_600_2pg.tif diff --git a/lib/compressor.rb b/lib/compressor.rb index 772c594..8930f59 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -117,13 +117,19 @@ def self.strip_tiff_profiles(path) end end -module TiffToPnm +module TiffTools def self.compress(source, destination) cmd = "tifftopnm #{source} | pnmtotiff -g4 -rowsperstrip" \ " 196136698 > #{destination}" status = Command.new(cmd).run LogEntry.info(command: cmd, time: status[:time]) end + + def self.copy_page_1(source, destination) + cmd = "tiffcp #{source},0 #{destination}" + status = Command.new(cmd).run + LogEntry.info(command: cmd, time: status[:time]) + end end class Compressor @@ -227,9 +233,11 @@ class Compressor::Bitonal < Compressor def run # Try to compress the image. This is the only part of this step # that should take any time. It should take a second or so. - log_it TiffToPnm.compress(image_file.path, compressed_path) + log_it TiffTools.compress(image_file.path, compressed_path) log_it ExifTool.copy_tiff_metadata(image_file.path, compressed_path) + + log_it TiffTools.copy_page_1(compressed_path, page1_path) end def compressed_path diff --git a/spec/compressor_spec.rb b/spec/compressor_spec.rb index 73bc62a..a19f0ed 100644 --- a/spec/compressor_spec.rb +++ b/spec/compressor_spec.rb @@ -92,6 +92,15 @@ def self.compress(sparse_path, new_path, tiffinfo) compressor.run expect(log.entries).to include(match("exiftool -tagsFromFile #{compressor.image_file.path}")) end + it "copies the first page of the tiff" do + @image_file = "10_10_1_600_2pg.tif" + starting_image_info = `tiffinfo #{path}` + expect(starting_image_info).to include("directory 1") + compressor.run + page1_info = `tiffinfo #{compressor.page1_path}` + expect(page1_info).not_to include("directory 1") + expect(log.entries).to include(match("tiffcp")) + end end end end diff --git a/spec/fixtures/10_10_1_600_2pg.tif b/spec/fixtures/10_10_1_600_2pg.tif new file mode 100644 index 0000000000000000000000000000000000000000..c313e29ce928c0340b411a61637cb73dc3fd4f0c GIT binary patch literal 476 zcmebD)MAifU|{%v022x@FfcMRFal+{fS3`9%>-qG?LP&N}#TocF!(ICJEbYKLK19B&f l#`Nd^1Jw8FC!mew`t%IYxiFtz0kR4C6y!S)fcX$c0|4>HEgt{? literal 0 HcmV?d00001 From 819415bc3eaf08e05d8d10db54d1addf6db00f2e Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Mon, 12 Jan 2026 17:28:40 -0500 Subject: [PATCH 30/38] handles date and document name for bitonal tif --- lib/compressor.rb | 25 ++++++++++++++++++++++--- lib/stage/compression.rb | 5 ----- spec/compressor_spec.rb | 34 +++++++++++++++++++++++++++++++--- 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/lib/compressor.rb b/lib/compressor.rb index 8930f59..d7d17d9 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -118,6 +118,15 @@ def self.strip_tiff_profiles(path) end module TiffTools + TIFFTAGS = { + document_name: 269, + date_time: 306 + } + + def self.date_time_format(datetime) + datetime.strftime("%Y:%m:%d %H:%M:%S") + end + def self.compress(source, destination) cmd = "tifftopnm #{source} | pnmtotiff -g4 -rowsperstrip" \ " 196136698 > #{destination}" @@ -130,12 +139,18 @@ def self.copy_page_1(source, destination) status = Command.new(cmd).run LogEntry.info(command: cmd, time: status[:time]) end + + def self.set_tag(path:, tag:, value:) + cmd = "tiffset -s #{TIFFTAGS[tag]}, '#{value}' #{path}" + status = Command.new(cmd).run + LogEntry.info(command: cmd, time: status[:time]) + end end class Compressor attr_reader :tiffinfo, :image_file, :tmpdir - def self.for(image_file:, tmpdir:, log: "whatever") + def self.for(image_file:, tmpdir:, log: "whatever", now: Time.now) tiffinfo = TIFF.new(image_file.path).info klass = case tiffinfo[:bps] when 8 @@ -145,14 +160,15 @@ def self.for(image_file:, tmpdir:, log: "whatever") else Compressor end - klass.new(image_file:, tmpdir:, log: log) + klass.new(image_file:, tmpdir:, log: log, now: now) end - def initialize(image_file:, tmpdir:, log: "whatever") + def initialize(image_file:, tmpdir:, log:, now:) @image_file = image_file @tiffinfo = TIFF.new(image_file.path).info @tmpdir = tmpdir @log = log + @now = now end def run @@ -238,6 +254,9 @@ def run log_it ExifTool.copy_tiff_metadata(image_file.path, compressed_path) log_it TiffTools.copy_page_1(compressed_path, page1_path) + log_it TiffTools.set_tag(path: page1_path, tag: :date_time, value: TiffTools.date_time_format(@now)) unless tiffinfo[:date_time] + # set the document name to the original name + log_it TiffTools.set_tag(path: page1_path, tag: :document_name, value: image_file.objid_file) end def compressed_path diff --git a/lib/stage/compression.rb b/lib/stage/compression.rb index a1b48bc..fbc7f9f 100755 --- a/lib/stage/compression.rb +++ b/lib/stage/compression.rb @@ -80,13 +80,8 @@ def handle_1_bps_conversion(compressor) compressor.run - copy_tiff_page1(compressed, page1) - FileUtils.rm(compressed) - write_tiff_date_time page1 unless tiffinfo[:date_time] - write_tiff_document_name(image_file, page1) - if tiffinfo[:software] write_tiff_software(page1, tiffinfo[:software]) else diff --git a/spec/compressor_spec.rb b/spec/compressor_spec.rb index a19f0ed..c996edc 100644 --- a/spec/compressor_spec.rb +++ b/spec/compressor_spec.rb @@ -7,6 +7,9 @@ def self.compress(sparse_path, new_path, tiffinfo) describe Compressor do include_context "uses temp dir" + def tiffinfo(path) + `tiffinfo #{path}` + end let(:log) { Log.new } let(:compression_tool) { FakeCompressionTool } # image file has path, objid, objid_file, file @@ -18,8 +21,9 @@ def self.compress(sparse_path, new_path, tiffinfo) let(:objid) { "some_barcode" } let(:objid_file) { File.join(objid, @image_file) } let(:image_file) { double("image_file", path: path, objid: objid, objid_file: objid_file, file: @image_file) } + let(:now) { Time.now } let(:compressor) do - Compressor.for(image_file: image_file, tmpdir: Pathname(temp_dir), log: log) + Compressor.for(image_file: image_file, tmpdir: Pathname(temp_dir), log: log, now: now) end context "color tif" do before(:each) do @@ -97,10 +101,34 @@ def self.compress(sparse_path, new_path, tiffinfo) starting_image_info = `tiffinfo #{path}` expect(starting_image_info).to include("directory 1") compressor.run - page1_info = `tiffinfo #{compressor.page1_path}` - expect(page1_info).not_to include("directory 1") + result_info = tiffinfo(compressor.page1_path) + expect(result_info).not_to include("directory 1") expect(log.entries).to include(match("tiffcp")) end + + it "keeps the original datetime when the original image has one" do + tmpdir_image_path = File.join(Pathname(temp_dir), @image_file) + FileUtils.cp(path, tmpdir_image_path) + one_hour_ago = Time.now - 3600 + allow(image_file).to receive(:path).and_return(tmpdir_image_path) + TiffTools.set_tag(path: tmpdir_image_path, tag: :date_time, value: TiffTools.date_time_format(one_hour_ago)) + + compressor.run + result_info = tiffinfo(compressor.page1_path) + expect(result_info).to include("DateTime: #{TiffTools.date_time_format(one_hour_ago)}") + end + + it "has now as the datetime when original tiff does not have a datetime" do + compressor.run + result_info = tiffinfo(compressor.page1_path) + expect(result_info).to include("DateTime: #{now.strftime("%Y:%m:%d %H:%M:%S")}") + end + + it "has a document name of the original file" do + compressor.run + result_info = tiffinfo(compressor.page1_path) + expect(result_info).to include("DocumentName: #{objid_file}") + end end end end From c7bec4f4d28fc1ca073ca6a8a1a20ba3e259bf23 Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Mon, 12 Jan 2026 17:44:29 -0500 Subject: [PATCH 31/38] remove orphan code remove minitest for tiff date because it's covered in the compressor test --- lib/compressor.rb | 3 ++- lib/stage/compression.rb | 20 -------------------- test/compressor_test.rb | 16 ---------------- 3 files changed, 2 insertions(+), 37 deletions(-) diff --git a/lib/compressor.rb b/lib/compressor.rb index d7d17d9..28b81e1 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -255,7 +255,8 @@ def run log_it TiffTools.copy_page_1(compressed_path, page1_path) log_it TiffTools.set_tag(path: page1_path, tag: :date_time, value: TiffTools.date_time_format(@now)) unless tiffinfo[:date_time] - # set the document name to the original name + + # Set the document name with objid/image.tif log_it TiffTools.set_tag(path: page1_path, tag: :document_name, value: image_file.objid_file) end diff --git a/lib/stage/compression.rb b/lib/stage/compression.rb index fbc7f9f..d642486 100755 --- a/lib/stage/compression.rb +++ b/lib/stage/compression.rb @@ -91,19 +91,6 @@ def handle_1_bps_conversion(compressor) copy_on_success page1, image_file.path, image_file.objid end - def copy_tiff_page1(path, destination) - cmd = "tiffcp #{path},0 #{destination}" - status = Command.new(cmd).run - log cmd, status[:time] - end - - # Set the document name with objid/image.tif - def write_tiff_document_name(image_file, destination) - tiff = TIFF.new(destination) - tiffset = tiff.set(TIFF::TIFFTAG_DOCUMENTNAME, image_file.objid_file) - log tiffset[:cmd], tiffset[:time] - end - # Remove ImageMagick software tag (if it exists) and replace with original def write_tiff_software(path, software) cmd = "exiftool -IFD0:Software= -overwrite_original #{path}" @@ -113,11 +100,4 @@ def write_tiff_software(path, software) status = Command.new(cmd).run log cmd, status[:time] end - - def write_tiff_date_time(path) - date = Time.now.strftime(TIFF_DATE_FORMAT) - cmd = "tiffset -s 306 '#{date}' #{path}" - status = Command.new(cmd).run - log cmd, status[:time] - end end diff --git a/test/compressor_test.rb b/test/compressor_test.rb index d7bd1f5..6ed7a03 100755 --- a/test/compressor_test.rb +++ b/test/compressor_test.rb @@ -43,22 +43,6 @@ def self.gen_run generate_tests "run", test_proc end - def self.gen_set_tiff_date_time - test_proc = proc { |shipment_class, test_shipment_class, dir, opts| - test_shipment = test_shipment_class.new(dir, "BC T bitonal 1") - shipment = shipment_class.new(test_shipment.directory) - tiff = File.join(shipment.directory, - shipment.objid_to_path(shipment.objids[0]), - "00000001.tif") - stage = Compression.new(shipment, config: opts.merge(@config)) - stage.send(:write_tiff_date_time, tiff) - tiffinfo = `tiffinfo #{tiff}` - assert_match(/DateTime:\s\d{4}:\d{2}:\d{2}\s\d{2}:\d{2}:\d{2}/, tiffinfo, - "TIFF DateTime in %Y:%m:%d %H:%M:%S format") - } - generate_tests "set_tiff_date_time", test_proc - end - def self.gen_set_jp2_date_time test_proc = proc { |shipment_class, test_shipment_class, dir, opts| test_shipment = test_shipment_class.new(dir, "BC T contone 1") From 6654764c459faa9decbcba5adc8d1c97c3bd09e1 Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Tue, 13 Jan 2026 09:37:06 -0500 Subject: [PATCH 32/38] move kakadu constants to kakadu module --- lib/compressor.rb | 7 +++++++ lib/stage/compression.rb | 10 ---------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/compressor.rb b/lib/compressor.rb index 28b81e1..d8a7ed5 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -1,6 +1,13 @@ require "tiff" module Kakadu + JP2_LEVEL_MIN = 5 + JP2_LAYERS = 8 + JP2_ORDER = "RLCP" + JP2_USE_SOP = "yes" + JP2_USE_EPH = "yes" + JP2_MODES = '"RESET|RESTART|CAUSAL|ERTERM|SEGMARK"' + JP2_SLOPE = 42_988 def self.compress(source, destination, tiffinfo) clevels = jp2_clevels(tiffinfo) cmd = "kdu_compress -quiet -i #{source} -o #{destination}" \ diff --git a/lib/stage/compression.rb b/lib/stage/compression.rb index d642486..005254a 100755 --- a/lib/stage/compression.rb +++ b/lib/stage/compression.rb @@ -5,16 +5,6 @@ require "tiff" require "compressor" -JP2_LEVEL_MIN = 5 -JP2_LAYERS = 8 -JP2_ORDER = "RLCP" -JP2_USE_SOP = "yes" -JP2_USE_EPH = "yes" -JP2_MODES = '"RESET|RESTART|CAUSAL|ERTERM|SEGMARK"' -JP2_SLOPE = 42_988 - -TIFF_DATE_FORMAT = "%Y:%m:%d %H:%M:%S" - # TIFF to JP2/TIFF compression stage class Compression < Stage def run(agenda) From df2517befec25f026fddc3c52a553400633b2445 Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Tue, 13 Jan 2026 12:28:21 -0500 Subject: [PATCH 33/38] Bitonal handles adding software tiff tag --- lib/compressor.rb | 15 ++++++++++++++- lib/log.rb | 2 +- lib/stage/compression.rb | 16 ---------------- spec/compressor_spec.rb | 21 ++++++++++++++++++++- 4 files changed, 35 insertions(+), 19 deletions(-) diff --git a/lib/compressor.rb b/lib/compressor.rb index d8a7ed5..cbff09d 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -98,6 +98,12 @@ def self.copy_tiff_metadata(source, destination) status = Command.new(cmd).run LogEntry.info(command: cmd, time: status[:time]) end + + def self.clear_software_tag(path) + cmd = "exiftool -IFD0:Software= -overwrite_original #{path}" + status = Command.new(cmd).run + LogEntry.info(command: cmd, time: status[:time]) + end end module ImageMagick @@ -127,7 +133,8 @@ def self.strip_tiff_profiles(path) module TiffTools TIFFTAGS = { document_name: 269, - date_time: 306 + date_time: 306, + software: 305 } def self.date_time_format(datetime) @@ -265,6 +272,12 @@ def run # Set the document name with objid/image.tif log_it TiffTools.set_tag(path: page1_path, tag: :document_name, value: image_file.objid_file) + if tiffinfo[:software] + log_it ExifTool.clear_software_tag(page1_path) + log_it TiffTools.set_tag(path: page1_path, tag: :software, value: tiffinfo[:software]) + else + log_it LogEntry.warning(error: Error.new("could not extract software", image_file.objid, image_file.path)) + end end def compressed_path diff --git a/lib/log.rb b/lib/log.rb index 625c66b..fc5fc0a 100644 --- a/lib/log.rb +++ b/lib/log.rb @@ -1,7 +1,7 @@ class Log include Enumerable - def initialize(log: nil, warnings: Warnings.new) + def initialize(log: nil, objids: [], warnings: Warnings.new(objids: objids)) @log = log || [] @warnings = warnings end diff --git a/lib/stage/compression.rb b/lib/stage/compression.rb index 005254a..df38b3d 100755 --- a/lib/stage/compression.rb +++ b/lib/stage/compression.rb @@ -72,22 +72,6 @@ def handle_1_bps_conversion(compressor) FileUtils.rm(compressed) - if tiffinfo[:software] - write_tiff_software(page1, tiffinfo[:software]) - else - add_warning Error.new("could not extract software", image_file.objid, - image_file.path) - end copy_on_success page1, image_file.path, image_file.objid end - - # Remove ImageMagick software tag (if it exists) and replace with original - def write_tiff_software(path, software) - cmd = "exiftool -IFD0:Software= -overwrite_original #{path}" - status = Command.new(cmd).run - log cmd, status[:time] - cmd = "tiffset -s 305 '#{software}' #{path}" - status = Command.new(cmd).run - log cmd, status[:time] - end end diff --git a/spec/compressor_spec.rb b/spec/compressor_spec.rb index c996edc..ebca93f 100644 --- a/spec/compressor_spec.rb +++ b/spec/compressor_spec.rb @@ -10,7 +10,7 @@ def self.compress(sparse_path, new_path, tiffinfo) def tiffinfo(path) `tiffinfo #{path}` end - let(:log) { Log.new } + let(:log) { Log.new(objids: [objid]) } let(:compression_tool) { FakeCompressionTool } # image file has path, objid, objid_file, file # objid="omzhx8s5.0074.149" @@ -28,6 +28,7 @@ def tiffinfo(path) context "color tif" do before(:each) do @image_file = "10_10_8_400.tif" + @log = Log.new end it "generates final document name" do expect(compressor.document_name).to eq("some_barcode/10_10_8_400.jp2") @@ -129,6 +130,24 @@ def tiffinfo(path) result_info = tiffinfo(compressor.page1_path) expect(result_info).to include("DocumentName: #{objid_file}") end + + it "copies software from the original tiff to the output file" do + tmpdir_image_path = File.join(Pathname(temp_dir), @image_file) + FileUtils.cp(path, tmpdir_image_path) + one_hour_ago = Time.now - 3600 + allow(image_file).to receive(:path).and_return(tmpdir_image_path) + TiffTools.set_tag(path: tmpdir_image_path, tag: :software, value: "My Software") + compressor.run + result_software = TIFF.new(compressor.page1_path).info[:software] + expect(result_software).to eq("My Software") + expect(log.entries).to include(match("exiftool -IFD0:Software=")) + end + it "logs a warning if there is no software in the original" do + compressor.run + result_software = TIFF.new(compressor.page1_path).info[:software] + expect(result_software).to be_nil + expect(log.warnings).to_json include(match("could not extract software")) + end end end end From a81567404755a7bfd58c7ca5213fc1e73d7750dc Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Tue, 13 Jan 2026 12:59:55 -0500 Subject: [PATCH 34/38] standardize on output_path new.jp2 -> output.jp2 | new_path -> output_path filename.tif-page1 -> output.tif | page1_path -> output_path --- lib/compressor.rb | 28 ++++++++++++++++------------ lib/stage/compression.rb | 11 +++-------- spec/compressor_spec.rb | 12 ++++++------ 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/lib/compressor.rb b/lib/compressor.rb index cbff09d..a27ae1a 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -223,17 +223,17 @@ def run(compression_tool = Kakadu) # This will always take a second. Other than the initial loading # of exiftool libraries, this is the only JP2 step that takes # noticeable time. - log_it compression_tool.compress(sparse_path, new_path, tiffinfo) + log_it compression_tool.compress(sparse_path, output_path, tiffinfo) # We have our JP2; we can remove the middle TIFF. Then we try # to grab metadata from the original TIFF. This should be very # quick since we just used exiftool a few lines back. - log_it ExifTool.copy_jp2_metadata(image_file.path, new_path, document_name, tiffinfo) + log_it ExifTool.copy_jp2_metadata(image_file.path, output_path, document_name, tiffinfo) # If our image had an alpha channel, it'll be gone now, and # the XMP data needs to reflect that (previously, we were # taking that info from the original image). - log_it ExifTool.copy_jp2_alphaless_metadata(sparse_path, new_path) if tiffinfo[:alpha] + log_it ExifTool.copy_jp2_alphaless_metadata(sparse_path, output_path) if tiffinfo[:alpha] end def final_image_path @@ -254,8 +254,8 @@ def sparse_path @sparse_path ||= File.join(tmpdir, "sparse.tif") end - def new_path - @new_path ||= File.join(tmpdir, "new.jp2") + def output_path + @output_path ||= File.join(tmpdir, "output.jp2") end end @@ -267,14 +267,14 @@ def run log_it ExifTool.copy_tiff_metadata(image_file.path, compressed_path) - log_it TiffTools.copy_page_1(compressed_path, page1_path) - log_it TiffTools.set_tag(path: page1_path, tag: :date_time, value: TiffTools.date_time_format(@now)) unless tiffinfo[:date_time] + log_it TiffTools.copy_page_1(compressed_path, output_path) + log_it TiffTools.set_tag(path: output_path, tag: :date_time, value: TiffTools.date_time_format(@now)) unless tiffinfo[:date_time] # Set the document name with objid/image.tif - log_it TiffTools.set_tag(path: page1_path, tag: :document_name, value: image_file.objid_file) + log_it TiffTools.set_tag(path: output_path, tag: :document_name, value: image_file.objid_file) if tiffinfo[:software] - log_it ExifTool.clear_software_tag(page1_path) - log_it TiffTools.set_tag(path: page1_path, tag: :software, value: tiffinfo[:software]) + log_it ExifTool.clear_software_tag(output_path) + log_it TiffTools.set_tag(path: output_path, tag: :software, value: tiffinfo[:software]) else log_it LogEntry.warning(error: Error.new("could not extract software", image_file.objid, image_file.path)) end @@ -284,7 +284,11 @@ def compressed_path @compressed_path ||= File.join(tmpdir, "#{File.basename(image_file.path)}-compressed") end - def page1_path - @page1_path ||= File.join(tmpdir, "#{File.basename(image_file.path)}-page1") + def final_image_path + image_file.path + end + + def output_path + @output_path ||= File.join(tmpdir, "output.tif") end end diff --git a/lib/stage/compression.rb b/lib/stage/compression.rb index df38b3d..8a456da 100755 --- a/lib/stage/compression.rb +++ b/lib/stage/compression.rb @@ -54,7 +54,7 @@ def handle_8_bps_conversion(compressor) compressor.run - system("cp #{compressor.new_path} #{on_disk_temp_image}") + system("cp #{compressor.output_path} #{on_disk_temp_image}") system("rm -r #{tmpdir}/*") copy_on_success on_disk_temp_image, compressor.final_image_path, image_file.objid delete_on_success image_file.path, image_file.objid @@ -62,16 +62,11 @@ def handle_8_bps_conversion(compressor) def handle_1_bps_conversion(compressor) image_file = compressor.image_file - tiffinfo = compressor.tiffinfo - tmpdir = compressor.tmpdir - - compressed = compressor.compressed_path - page1 = compressor.page1_path compressor.run - FileUtils.rm(compressed) + FileUtils.rm(compressor.compressed_path) - copy_on_success page1, image_file.path, image_file.objid + copy_on_success compressor.output_path, compressor.final_image_path, image_file.objid end end diff --git a/spec/compressor_spec.rb b/spec/compressor_spec.rb index ebca93f..c39de7d 100644 --- a/spec/compressor_spec.rb +++ b/spec/compressor_spec.rb @@ -102,7 +102,7 @@ def tiffinfo(path) starting_image_info = `tiffinfo #{path}` expect(starting_image_info).to include("directory 1") compressor.run - result_info = tiffinfo(compressor.page1_path) + result_info = tiffinfo(compressor.output_path) expect(result_info).not_to include("directory 1") expect(log.entries).to include(match("tiffcp")) end @@ -115,19 +115,19 @@ def tiffinfo(path) TiffTools.set_tag(path: tmpdir_image_path, tag: :date_time, value: TiffTools.date_time_format(one_hour_ago)) compressor.run - result_info = tiffinfo(compressor.page1_path) + result_info = tiffinfo(compressor.output_path) expect(result_info).to include("DateTime: #{TiffTools.date_time_format(one_hour_ago)}") end it "has now as the datetime when original tiff does not have a datetime" do compressor.run - result_info = tiffinfo(compressor.page1_path) + result_info = tiffinfo(compressor.output_path) expect(result_info).to include("DateTime: #{now.strftime("%Y:%m:%d %H:%M:%S")}") end it "has a document name of the original file" do compressor.run - result_info = tiffinfo(compressor.page1_path) + result_info = tiffinfo(compressor.output_path) expect(result_info).to include("DocumentName: #{objid_file}") end @@ -138,13 +138,13 @@ def tiffinfo(path) allow(image_file).to receive(:path).and_return(tmpdir_image_path) TiffTools.set_tag(path: tmpdir_image_path, tag: :software, value: "My Software") compressor.run - result_software = TIFF.new(compressor.page1_path).info[:software] + result_software = TIFF.new(compressor.output_path).info[:software] expect(result_software).to eq("My Software") expect(log.entries).to include(match("exiftool -IFD0:Software=")) end it "logs a warning if there is no software in the original" do compressor.run - result_software = TIFF.new(compressor.page1_path).info[:software] + result_software = TIFF.new(compressor.output_path).info[:software] expect(result_software).to be_nil expect(log.warnings).to_json include(match("could not extract software")) end From dd1f42c7080ff8c47d87f0b85b87e7c449f76bc2 Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Tue, 13 Jan 2026 13:48:49 -0500 Subject: [PATCH 35/38] bitonal copies output image to on disk temp dir this makes it match what's going on for jp2 and paves the way for being able to use the build in temp dir for the real deal --- lib/compressor.rb | 4 ++++ lib/stage/compression.rb | 24 +++++++++++------------- test/compressor_test.rb | 1 + 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/compressor.rb b/lib/compressor.rb index a27ae1a..2fa2748 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -193,6 +193,10 @@ def bps @tiffinfo[:bps] end + def final_image_path + raise NotImplementedError + end + private def log_it(log_entry) diff --git a/lib/stage/compression.rb b/lib/stage/compression.rb index 8a456da..09abf4b 100755 --- a/lib/stage/compression.rb +++ b/lib/stage/compression.rb @@ -46,27 +46,25 @@ def run(agenda) private def handle_8_bps_conversion(compressor) - image_file = compressor.image_file - tmpdir = compressor.tmpdir - - on_disk_temp_image = compressor.final_image_path.sub(shipment.directory, shipment.tmp_directory) - system("mkdir -p #{File.dirname(on_disk_temp_image)}") + on_disk_temp_image_path = compressor.final_image_path.sub(shipment.directory, shipment.tmp_directory) + system("mkdir -p #{File.dirname(on_disk_temp_image_path)}") compressor.run - system("cp #{compressor.output_path} #{on_disk_temp_image}") - system("rm -r #{tmpdir}/*") - copy_on_success on_disk_temp_image, compressor.final_image_path, image_file.objid - delete_on_success image_file.path, image_file.objid + system("cp #{compressor.output_path} #{on_disk_temp_image_path}") + system("rm -r #{compressor.tmpdir}/*") + copy_on_success on_disk_temp_image_path, compressor.final_image_path, compressor.image_file.objid + delete_on_success compressor.image_file.path, compressor.image_file.objid end def handle_1_bps_conversion(compressor) - image_file = compressor.image_file + on_disk_temp_image_path = compressor.final_image_path.sub(shipment.directory, shipment.tmp_directory) + system("mkdir -p #{File.dirname(on_disk_temp_image_path)}") compressor.run - FileUtils.rm(compressor.compressed_path) - - copy_on_success compressor.output_path, compressor.final_image_path, image_file.objid + system("cp #{compressor.output_path} #{on_disk_temp_image_path}") + system("rm -r #{compressor.tmpdir}/*") + copy_on_success on_disk_temp_image_path, compressor.final_image_path, compressor.image_file.objid end end diff --git a/test/compressor_test.rb b/test/compressor_test.rb index 6ed7a03..b933c02 100755 --- a/test/compressor_test.rb +++ b/test/compressor_test.rb @@ -2,6 +2,7 @@ # frozen_string_literal: true require "minitest/autorun" +require_relative "test_helper" require "compression" require "fixtures" From 9517662e17c72ca77d7141ae1183ecd41b42ed48 Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Tue, 13 Jan 2026 14:36:43 -0500 Subject: [PATCH 36/38] compression stage does not have conditionals --- lib/compressor.rb | 18 ++++++++++-- lib/stage/compression.rb | 62 +++++++++------------------------------- spec/compressor_spec.rb | 4 +-- 3 files changed, 30 insertions(+), 54 deletions(-) diff --git a/lib/compressor.rb b/lib/compressor.rb index 2fa2748..a2fa70f 100644 --- a/lib/compressor.rb +++ b/lib/compressor.rb @@ -168,11 +168,11 @@ def self.for(image_file:, tmpdir:, log: "whatever", now: Time.now) tiffinfo = TIFF.new(image_file.path).info klass = case tiffinfo[:bps] when 8 - Compressor::Color + Compressor::Contone when 1 Compressor::Bitonal else - Compressor + raise "invalid source TIFF BPS #{tiffinfo[:bps]}" end klass.new(image_file:, tmpdir:, log: log, now: now) end @@ -189,6 +189,10 @@ def run raise NotImplementedError end + def compression_type + raise NotImplementedError + end + def bps @tiffinfo[:bps] end @@ -204,7 +208,11 @@ def log_it(log_entry) end end -class Compressor::Color < Compressor +class Compressor::Contone < Compressor + def compression_type + "JP2" + end + def run(compression_tool = Kakadu) # We don't want any XMP metadata to be copied over on its own. If # it's been a while since we last ran exiftool, this might take a sec. @@ -264,6 +272,10 @@ def output_path end class Compressor::Bitonal < Compressor + def compression_type + "G4" + end + def run # Try to compress the image. This is the only part of this step # that should take any time. It should take a second or so. diff --git a/lib/stage/compression.rb b/lib/stage/compression.rb index 09abf4b..dcd3a87 100755 --- a/lib/stage/compression.rb +++ b/lib/stage/compression.rb @@ -12,59 +12,23 @@ def run(agenda) files = image_files.select { |file| agenda.include? file.objid } @bar.steps = files.count files.each_with_index do |image_file, i| - begin - compressor = Compressor.for(image_file: image_file, tmpdir: create_tempdir, log: log_collection) - tiffinfo = compressor.tiffinfo - rescue => e - add_error Error.new(e.message, image_file.objid, image_file.file) - next - end - case tiffinfo[:bps] - when 8 - # It's a contone, so we convert to JP2. - @bar.step! i, "#{image_file.objid_file} JP2" - begin - handle_8_bps_conversion(compressor) - rescue => e - add_error Error.new(e.message, image_file.objid, image_file.file) - end - when 1 - # It's bitonal, so we G4 compress it. - @bar.step! i, "#{image_file.objid_file} G4" - begin - handle_1_bps_conversion(compressor) - rescue => e - add_error Error.new(e.message, image_file.objid, image_file.file) - end - else - add_error Error.new("invalid source TIFF BPS #{tiffinfo[:bps]}", - image_file.objid, image_file.file) - end - end - end - - private + compressor = Compressor.for(image_file: image_file, tmpdir: create_tempdir, log: log_collection) - def handle_8_bps_conversion(compressor) - on_disk_temp_image_path = compressor.final_image_path.sub(shipment.directory, shipment.tmp_directory) - system("mkdir -p #{File.dirname(on_disk_temp_image_path)}") + @bar.step! i, "#{image_file.objid_file} #{compressor.compression_type}" - compressor.run + compressor.run - system("cp #{compressor.output_path} #{on_disk_temp_image_path}") - system("rm -r #{compressor.tmpdir}/*") - copy_on_success on_disk_temp_image_path, compressor.final_image_path, compressor.image_file.objid - delete_on_success compressor.image_file.path, compressor.image_file.objid - end - - def handle_1_bps_conversion(compressor) - on_disk_temp_image_path = compressor.final_image_path.sub(shipment.directory, shipment.tmp_directory) - system("mkdir -p #{File.dirname(on_disk_temp_image_path)}") + on_disk_temp_image_path = compressor.final_image_path.sub(shipment.directory, shipment.tmp_directory) + system("mkdir -p #{File.dirname(on_disk_temp_image_path)}") + system("cp #{compressor.output_path} #{on_disk_temp_image_path}") - compressor.run + copy_on_success on_disk_temp_image_path, compressor.final_image_path, compressor.image_file.objid + delete_on_success compressor.image_file.path, compressor.image_file.objid if compressor.bps == 8 - system("cp #{compressor.output_path} #{on_disk_temp_image_path}") - system("rm -r #{compressor.tmpdir}/*") - copy_on_success on_disk_temp_image_path, compressor.final_image_path, compressor.image_file.objid + system("rm -r #{compressor.tmpdir}/*") + rescue => e + add_error Error.new(e.message, image_file.objid, image_file.file) + next + end end end diff --git a/spec/compressor_spec.rb b/spec/compressor_spec.rb index c39de7d..fcace2a 100644 --- a/spec/compressor_spec.rb +++ b/spec/compressor_spec.rb @@ -34,8 +34,8 @@ def tiffinfo(path) expect(compressor.document_name).to eq("some_barcode/10_10_8_400.jp2") end - it "is a Color compressor when initialized with an 8bps image" do - expect(compressor.class.to_s).to eq("Compressor::Color") + it "is a Cotone compressor when initialized with an 8bps image" do + expect(compressor.class.to_s).to eq("Compressor::Contone") end context "#run" do From 5fc22febb4e8b66a03dc21b00389d1d4cac3acc8 Mon Sep 17 00:00:00 2001 From: Monique Rio Date: Tue, 13 Jan 2026 16:09:36 -0500 Subject: [PATCH 37/38] remove problem line --- spec/compressor_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/compressor_spec.rb b/spec/compressor_spec.rb index fcace2a..a2db08b 100644 --- a/spec/compressor_spec.rb +++ b/spec/compressor_spec.rb @@ -134,7 +134,6 @@ def tiffinfo(path) it "copies software from the original tiff to the output file" do tmpdir_image_path = File.join(Pathname(temp_dir), @image_file) FileUtils.cp(path, tmpdir_image_path) - one_hour_ago = Time.now - 3600 allow(image_file).to receive(:path).and_return(tmpdir_image_path) TiffTools.set_tag(path: tmpdir_image_path, tag: :software, value: "My Software") compressor.run From 4fe77d028d60a8544be0ce994f90ee608f32cff7 Mon Sep 17 00:00:00 2001 From: Anthony Thomas Date: Thu, 15 Jan 2026 10:38:36 -0500 Subject: [PATCH 38/38] typo --- spec/compressor_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/compressor_spec.rb b/spec/compressor_spec.rb index a2db08b..65ade81 100644 --- a/spec/compressor_spec.rb +++ b/spec/compressor_spec.rb @@ -34,7 +34,7 @@ def tiffinfo(path) expect(compressor.document_name).to eq("some_barcode/10_10_8_400.jp2") end - it "is a Cotone compressor when initialized with an 8bps image" do + it "is a Contone compressor when initialized with an 8bps image" do expect(compressor.class.to_s).to eq("Compressor::Contone") end