ruby_spriter 0.6.5 → 0.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +188 -0
- data/README.md +374 -33
- data/lib/ruby_spriter/batch_processor.rb +212 -0
- data/lib/ruby_spriter/cli.rb +369 -6
- data/lib/ruby_spriter/compression_manager.rb +101 -0
- data/lib/ruby_spriter/consolidator.rb +33 -0
- data/lib/ruby_spriter/gimp_processor.rb +6 -3
- data/lib/ruby_spriter/processor.rb +661 -26
- data/lib/ruby_spriter/utils/file_helper.rb +25 -0
- data/lib/ruby_spriter/utils/spritesheet_splitter.rb +86 -0
- data/lib/ruby_spriter/version.rb +2 -2
- data/lib/ruby_spriter/video_processor.rb +7 -7
- data/lib/ruby_spriter.rb +3 -0
- data/spec/ruby_spriter/batch_processor_spec.rb +200 -0
- data/spec/ruby_spriter/cli_spec.rb +750 -0
- data/spec/ruby_spriter/compression_manager_spec.rb +157 -0
- data/spec/ruby_spriter/consolidator_spec.rb +163 -0
- data/spec/ruby_spriter/processor_spec.rb +735 -0
- data/spec/ruby_spriter/utils/file_helper_spec.rb +80 -1
- data/spec/ruby_spriter/utils/spritesheet_splitter_spec.rb +104 -0
- data/spec/ruby_spriter/video_processor_spec.rb +29 -0
- metadata +8 -2
|
@@ -77,6 +77,21 @@ RSpec.describe RubySpriter::CLI do
|
|
|
77
77
|
expect { described_class.start(['-h']) }.to output(/Usage: ruby_spriter/).to_stdout
|
|
78
78
|
end.to raise_error(SystemExit)
|
|
79
79
|
end
|
|
80
|
+
|
|
81
|
+
it 'shows mode-specific help hints' do
|
|
82
|
+
output = StringIO.new
|
|
83
|
+
$stdout = output
|
|
84
|
+
|
|
85
|
+
begin
|
|
86
|
+
described_class.start(['--help'])
|
|
87
|
+
rescue SystemExit
|
|
88
|
+
# Expected
|
|
89
|
+
ensure
|
|
90
|
+
$stdout = STDOUT
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
expect(output.string).to include('Get mode-specific help:')
|
|
94
|
+
end
|
|
80
95
|
end
|
|
81
96
|
|
|
82
97
|
describe '--version flag' do
|
|
@@ -155,6 +170,32 @@ RSpec.describe RubySpriter::CLI do
|
|
|
155
170
|
end.to raise_error(SystemExit)
|
|
156
171
|
end
|
|
157
172
|
end
|
|
173
|
+
|
|
174
|
+
describe '--overwrite flag' do
|
|
175
|
+
it 'sets overwrite option to true' do
|
|
176
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
177
|
+
allow(processor_double).to receive(:run)
|
|
178
|
+
|
|
179
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
180
|
+
expect(options[:overwrite]).to eq(true)
|
|
181
|
+
processor_double
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
described_class.start(['--video', 'test.mp4', '--overwrite'])
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
it 'defaults to false when not specified' do
|
|
188
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
189
|
+
allow(processor_double).to receive(:run)
|
|
190
|
+
|
|
191
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
192
|
+
expect(options[:overwrite]).to be_nil
|
|
193
|
+
processor_double
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
described_class.start(['--video', 'test.mp4'])
|
|
197
|
+
end
|
|
198
|
+
end
|
|
158
199
|
end
|
|
159
200
|
|
|
160
201
|
describe '--image flag' do
|
|
@@ -425,12 +466,366 @@ RSpec.describe RubySpriter::CLI do
|
|
|
425
466
|
|
|
426
467
|
described_class.start(['--image', fixture_with_meta, '--output', 'custom_output.png'])
|
|
427
468
|
end
|
|
469
|
+
|
|
470
|
+
it 'works with --overwrite option' do
|
|
471
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
472
|
+
allow(processor_double).to receive(:run)
|
|
473
|
+
|
|
474
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
475
|
+
expect(options[:image]).to eq(fixture_with_meta)
|
|
476
|
+
expect(options[:remove_bg]).to eq(true)
|
|
477
|
+
expect(options[:overwrite]).to eq(true)
|
|
478
|
+
processor_double
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
described_class.start(['--image', fixture_with_meta, '--remove-bg', '--overwrite'])
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
it 'works with --overwrite and --output options combined' do
|
|
485
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
486
|
+
allow(processor_double).to receive(:run)
|
|
487
|
+
|
|
488
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
489
|
+
expect(options[:image]).to eq(fixture_with_meta)
|
|
490
|
+
expect(options[:scale_percent]).to eq(50)
|
|
491
|
+
expect(options[:output]).to eq('custom.png')
|
|
492
|
+
expect(options[:overwrite]).to eq(true)
|
|
493
|
+
processor_double
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
described_class.start(['--image', fixture_with_meta, '--scale', '50', '--output', 'custom.png', '--overwrite'])
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
describe 'output filename behavior with processing' do
|
|
501
|
+
it 'generates unique filename by default when processing without --output' do
|
|
502
|
+
# Mock all dependencies
|
|
503
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
504
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
|
|
505
|
+
|
|
506
|
+
# Mock GimpProcessor to return a processed file
|
|
507
|
+
gimp_double = instance_double(RubySpriter::GimpProcessor)
|
|
508
|
+
allow(RubySpriter::GimpProcessor).to receive(:new).and_return(gimp_double)
|
|
509
|
+
allow(gimp_double).to receive(:process).and_return('input-nobg-fuzzy_20251023_123456_789.png')
|
|
510
|
+
|
|
511
|
+
processor = RubySpriter::Processor.new(
|
|
512
|
+
image: fixture_with_meta,
|
|
513
|
+
remove_bg: true,
|
|
514
|
+
overwrite: false
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
allow(processor).to receive(:check_dependencies!)
|
|
518
|
+
allow(processor).to receive(:setup_temp_directory)
|
|
519
|
+
allow(processor).to receive(:cleanup)
|
|
520
|
+
allow(processor).to receive(:gimp_path).and_return('/usr/bin/gimp')
|
|
521
|
+
|
|
522
|
+
result = nil
|
|
523
|
+
expect { result = processor.run }.to output(/SUCCESS/).to_stdout
|
|
524
|
+
|
|
525
|
+
# Should return the uniquely-named file from GIMP processing
|
|
526
|
+
expect(result[:output_file]).to match(/-nobg-fuzzy.*\.png$/)
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
it 'overwrites output file when --overwrite is specified' do
|
|
530
|
+
# Mock all dependencies
|
|
531
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
532
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
|
|
533
|
+
|
|
534
|
+
# Mock GimpProcessor - with overwrite:true, it should return same filename
|
|
535
|
+
gimp_double = instance_double(RubySpriter::GimpProcessor)
|
|
536
|
+
allow(RubySpriter::GimpProcessor).to receive(:new).and_return(gimp_double)
|
|
537
|
+
allow(gimp_double).to receive(:process).and_return('input-scaled-50pct.png')
|
|
538
|
+
|
|
539
|
+
processor = RubySpriter::Processor.new(
|
|
540
|
+
image: fixture_with_meta,
|
|
541
|
+
scale_percent: 50,
|
|
542
|
+
overwrite: true
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
allow(processor).to receive(:check_dependencies!)
|
|
546
|
+
allow(processor).to receive(:setup_temp_directory)
|
|
547
|
+
allow(processor).to receive(:cleanup)
|
|
548
|
+
allow(processor).to receive(:gimp_path).and_return('/usr/bin/gimp')
|
|
549
|
+
|
|
550
|
+
result = nil
|
|
551
|
+
expect { result = processor.run }.to output(/SUCCESS/).to_stdout
|
|
552
|
+
|
|
553
|
+
# Should return the base filename (no timestamp)
|
|
554
|
+
expect(result[:output_file]).to eq('input-scaled-50pct.png')
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
it 'generates unique output filename when --output is used without --overwrite' do
|
|
558
|
+
# Mock all dependencies
|
|
559
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
560
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
|
|
561
|
+
|
|
562
|
+
# Mock ensure_unique_output to verify it's called correctly
|
|
563
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:ensure_unique_output) do |path, overwrite:|
|
|
564
|
+
expect(path).to eq('custom_output.png')
|
|
565
|
+
expect(overwrite).to eq(false)
|
|
566
|
+
'custom_output_20251023_123456_789.png'
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
# Mock GimpProcessor
|
|
570
|
+
gimp_double = instance_double(RubySpriter::GimpProcessor)
|
|
571
|
+
allow(RubySpriter::GimpProcessor).to receive(:new).and_return(gimp_double)
|
|
572
|
+
allow(gimp_double).to receive(:process).and_return('temp-processed.png')
|
|
573
|
+
|
|
574
|
+
# Mock file operations
|
|
575
|
+
allow(FileUtils).to receive(:cp)
|
|
576
|
+
|
|
577
|
+
processor = RubySpriter::Processor.new(
|
|
578
|
+
image: fixture_with_meta,
|
|
579
|
+
remove_bg: true,
|
|
580
|
+
output: 'custom_output.png',
|
|
581
|
+
overwrite: false
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
allow(processor).to receive(:check_dependencies!)
|
|
585
|
+
allow(processor).to receive(:setup_temp_directory)
|
|
586
|
+
allow(processor).to receive(:cleanup)
|
|
587
|
+
allow(processor).to receive(:gimp_path).and_return('/usr/bin/gimp')
|
|
588
|
+
|
|
589
|
+
result = nil
|
|
590
|
+
expect { result = processor.run }.to output(/SUCCESS/).to_stdout
|
|
591
|
+
|
|
592
|
+
# Should return unique filename
|
|
593
|
+
expect(result[:output_file]).to match(/custom_output_\d{8}_\d{6}_\d{3}\.png$/)
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
it 'uses exact output filename when --output and --overwrite are both specified' do
|
|
597
|
+
# Mock all dependencies
|
|
598
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
599
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
|
|
600
|
+
|
|
601
|
+
# Mock ensure_unique_output to verify it's called with overwrite:true
|
|
602
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:ensure_unique_output) do |path, overwrite:|
|
|
603
|
+
expect(path).to eq('exact_output.png')
|
|
604
|
+
expect(overwrite).to eq(true)
|
|
605
|
+
'exact_output.png'
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
# Mock GimpProcessor
|
|
609
|
+
gimp_double = instance_double(RubySpriter::GimpProcessor)
|
|
610
|
+
allow(RubySpriter::GimpProcessor).to receive(:new).and_return(gimp_double)
|
|
611
|
+
allow(gimp_double).to receive(:process).and_return('temp-processed.png')
|
|
612
|
+
|
|
613
|
+
# Mock file operations
|
|
614
|
+
allow(FileUtils).to receive(:cp)
|
|
615
|
+
|
|
616
|
+
processor = RubySpriter::Processor.new(
|
|
617
|
+
image: fixture_with_meta,
|
|
618
|
+
scale_percent: 50,
|
|
619
|
+
output: 'exact_output.png',
|
|
620
|
+
overwrite: true
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
allow(processor).to receive(:check_dependencies!)
|
|
624
|
+
allow(processor).to receive(:setup_temp_directory)
|
|
625
|
+
allow(processor).to receive(:cleanup)
|
|
626
|
+
allow(processor).to receive(:gimp_path).and_return('/usr/bin/gimp')
|
|
627
|
+
|
|
628
|
+
result = nil
|
|
629
|
+
expect { result = processor.run }.to output(/SUCCESS/).to_stdout
|
|
630
|
+
|
|
631
|
+
# Should return exact filename (no timestamp)
|
|
632
|
+
expect(result[:output_file]).to eq('exact_output.png')
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
it 'generates unique filename when using --sharpen alone without --output' do
|
|
636
|
+
# Mock all dependencies
|
|
637
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
638
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
|
|
639
|
+
|
|
640
|
+
# Mock GimpProcessor to return a sharpened file
|
|
641
|
+
gimp_double = instance_double(RubySpriter::GimpProcessor)
|
|
642
|
+
allow(RubySpriter::GimpProcessor).to receive(:new).and_return(gimp_double)
|
|
643
|
+
allow(gimp_double).to receive(:process).and_return('input-sharpened_20251023_123456_789.png')
|
|
644
|
+
|
|
645
|
+
processor = RubySpriter::Processor.new(
|
|
646
|
+
image: fixture_with_meta,
|
|
647
|
+
sharpen: true,
|
|
648
|
+
overwrite: false
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
allow(processor).to receive(:check_dependencies!)
|
|
652
|
+
allow(processor).to receive(:setup_temp_directory)
|
|
653
|
+
allow(processor).to receive(:cleanup)
|
|
654
|
+
|
|
655
|
+
result = nil
|
|
656
|
+
expect { result = processor.run }.to output(/SUCCESS/).to_stdout
|
|
657
|
+
|
|
658
|
+
# Should return the uniquely-named sharpened file
|
|
659
|
+
expect(result[:output_file]).to match(/-sharpened.*\.png$/)
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
it 'overwrites sharpened file when --sharpen with --overwrite' do
|
|
663
|
+
# Mock all dependencies
|
|
664
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
665
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
|
|
666
|
+
|
|
667
|
+
# Mock GimpProcessor - with overwrite:true, should return base filename
|
|
668
|
+
gimp_double = instance_double(RubySpriter::GimpProcessor)
|
|
669
|
+
allow(RubySpriter::GimpProcessor).to receive(:new).and_return(gimp_double)
|
|
670
|
+
allow(gimp_double).to receive(:process).and_return('input-sharpened.png')
|
|
671
|
+
|
|
672
|
+
processor = RubySpriter::Processor.new(
|
|
673
|
+
image: fixture_with_meta,
|
|
674
|
+
sharpen: true,
|
|
675
|
+
overwrite: true
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
allow(processor).to receive(:check_dependencies!)
|
|
679
|
+
allow(processor).to receive(:setup_temp_directory)
|
|
680
|
+
allow(processor).to receive(:cleanup)
|
|
681
|
+
|
|
682
|
+
result = nil
|
|
683
|
+
expect { result = processor.run }.to output(/SUCCESS/).to_stdout
|
|
684
|
+
|
|
685
|
+
# Should return the base filename (no timestamp)
|
|
686
|
+
expect(result[:output_file]).to eq('input-sharpened.png')
|
|
687
|
+
end
|
|
428
688
|
end
|
|
429
689
|
end
|
|
430
690
|
|
|
431
691
|
describe '--video flag' do
|
|
432
692
|
let(:fixture_video) { File.join(__dir__, '..', 'fixtures', 'test_video.mp4') }
|
|
433
693
|
|
|
694
|
+
describe 'context-sensitive help' do
|
|
695
|
+
it 'shows video mode help with --help' do
|
|
696
|
+
output = StringIO.new
|
|
697
|
+
$stdout = output
|
|
698
|
+
|
|
699
|
+
begin
|
|
700
|
+
described_class.start(['--video', '--help'])
|
|
701
|
+
rescue SystemExit
|
|
702
|
+
# Expected
|
|
703
|
+
ensure
|
|
704
|
+
$stdout = STDOUT
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
expect(output.string).to include('Video Mode')
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
it 'shows parent-child option hierarchy in video mode help' do
|
|
711
|
+
output = StringIO.new
|
|
712
|
+
$stdout = output
|
|
713
|
+
|
|
714
|
+
begin
|
|
715
|
+
described_class.start(['--video', '--help'])
|
|
716
|
+
rescue SystemExit
|
|
717
|
+
# Expected
|
|
718
|
+
ensure
|
|
719
|
+
$stdout = STDOUT
|
|
720
|
+
end
|
|
721
|
+
|
|
722
|
+
# Check for parent options
|
|
723
|
+
expect(output.string).to include('-s, --scale PERCENT')
|
|
724
|
+
expect(output.string).to include('-r, --remove-bg')
|
|
725
|
+
|
|
726
|
+
# Check for child options with hierarchy marker
|
|
727
|
+
expect(output.string).to include('└─ Interpolation:')
|
|
728
|
+
expect(output.string).to include('└─ Sharpen radius')
|
|
729
|
+
expect(output.string).to include('└─ Use fuzzy select')
|
|
730
|
+
expect(output.string).to include('└─ Feather radius')
|
|
731
|
+
expect(output.string).to include('└─ Grow selection')
|
|
732
|
+
|
|
733
|
+
# Check that --order mentions BOTH requirement
|
|
734
|
+
expect(output.string).to match(/order.*BOTH.*--scale.*AND.*--remove-bg/i)
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
it 'shows --sharpen as standalone option in video mode help' do
|
|
738
|
+
output = StringIO.new
|
|
739
|
+
$stdout = output
|
|
740
|
+
|
|
741
|
+
begin
|
|
742
|
+
described_class.start(['--video', '--help'])
|
|
743
|
+
rescue SystemExit
|
|
744
|
+
# Expected
|
|
745
|
+
ensure
|
|
746
|
+
$stdout = STDOUT
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
# --sharpen should be a standalone parent option (not indented under --scale)
|
|
750
|
+
expect(output.string).to match(/^ --sharpen\s+Apply unsharp mask/)
|
|
751
|
+
|
|
752
|
+
# --sharpen modifiers should be children under --sharpen
|
|
753
|
+
expect(output.string).to include('└─ Sharpen radius')
|
|
754
|
+
expect(output.string).to include('└─ Sharpen gain')
|
|
755
|
+
expect(output.string).to include('└─ Sharpen threshold')
|
|
756
|
+
|
|
757
|
+
# --interpolation should ONLY be under --scale (not under --sharpen)
|
|
758
|
+
lines = output.string.lines
|
|
759
|
+
sharpen_line_idx = lines.index { |l| l.include?('--sharpen') && l.include?('Apply unsharp mask') }
|
|
760
|
+
scale_line_idx = lines.index { |l| l.include?('--scale PERCENT') }
|
|
761
|
+
interpolation_line_idx = lines.index { |l| l.include?('└─ Interpolation') }
|
|
762
|
+
|
|
763
|
+
# Interpolation should come after scale, not after sharpen
|
|
764
|
+
expect(interpolation_line_idx).to be > scale_line_idx
|
|
765
|
+
expect(interpolation_line_idx).to be < sharpen_line_idx
|
|
766
|
+
end
|
|
767
|
+
|
|
768
|
+
it 'shows image mode help with --help' do
|
|
769
|
+
output = StringIO.new
|
|
770
|
+
$stdout = output
|
|
771
|
+
|
|
772
|
+
begin
|
|
773
|
+
described_class.start(['--image', '--help'])
|
|
774
|
+
rescue SystemExit
|
|
775
|
+
# Expected
|
|
776
|
+
ensure
|
|
777
|
+
$stdout = STDOUT
|
|
778
|
+
end
|
|
779
|
+
|
|
780
|
+
expect(output.string).to include('Image Mode')
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
it 'shows consolidate mode help with --help' do
|
|
784
|
+
output = StringIO.new
|
|
785
|
+
$stdout = output
|
|
786
|
+
|
|
787
|
+
begin
|
|
788
|
+
described_class.start(['--consolidate', '--help'])
|
|
789
|
+
rescue SystemExit
|
|
790
|
+
# Expected
|
|
791
|
+
ensure
|
|
792
|
+
$stdout = STDOUT
|
|
793
|
+
end
|
|
794
|
+
|
|
795
|
+
expect(output.string).to include('Consolidate Mode')
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
it 'shows batch mode help with --help' do
|
|
799
|
+
output = StringIO.new
|
|
800
|
+
$stdout = output
|
|
801
|
+
|
|
802
|
+
begin
|
|
803
|
+
described_class.start(['--batch', '--help'])
|
|
804
|
+
rescue SystemExit
|
|
805
|
+
# Expected
|
|
806
|
+
ensure
|
|
807
|
+
$stdout = STDOUT
|
|
808
|
+
end
|
|
809
|
+
|
|
810
|
+
expect(output.string).to include('Batch Mode')
|
|
811
|
+
end
|
|
812
|
+
|
|
813
|
+
it 'shows split mode help with --help' do
|
|
814
|
+
output = StringIO.new
|
|
815
|
+
$stdout = output
|
|
816
|
+
|
|
817
|
+
begin
|
|
818
|
+
described_class.start(['--split', '--help'])
|
|
819
|
+
rescue SystemExit
|
|
820
|
+
# Expected
|
|
821
|
+
ensure
|
|
822
|
+
$stdout = STDOUT
|
|
823
|
+
end
|
|
824
|
+
|
|
825
|
+
expect(output.string).to include('Split Mode')
|
|
826
|
+
end
|
|
827
|
+
end
|
|
828
|
+
|
|
434
829
|
describe 'argument parsing' do
|
|
435
830
|
it 'sets video option with --video flag' do
|
|
436
831
|
processor_double = instance_double(RubySpriter::Processor)
|
|
@@ -763,6 +1158,19 @@ RSpec.describe RubySpriter::CLI do
|
|
|
763
1158
|
|
|
764
1159
|
described_class.start(['--video', fixture_video, '--output', 'custom_spritesheet.png'])
|
|
765
1160
|
end
|
|
1161
|
+
|
|
1162
|
+
it 'works with --save-frames option' do
|
|
1163
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1164
|
+
allow(processor_double).to receive(:run)
|
|
1165
|
+
|
|
1166
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1167
|
+
expect(options[:video]).to eq(fixture_video)
|
|
1168
|
+
expect(options[:save_frames]).to eq(true)
|
|
1169
|
+
processor_double
|
|
1170
|
+
end
|
|
1171
|
+
|
|
1172
|
+
described_class.start(['--video', fixture_video, '--save-frames'])
|
|
1173
|
+
end
|
|
766
1174
|
end
|
|
767
1175
|
|
|
768
1176
|
describe 'preset configurations' do
|
|
@@ -1117,6 +1525,176 @@ RSpec.describe RubySpriter::CLI do
|
|
|
1117
1525
|
'--debug'
|
|
1118
1526
|
])
|
|
1119
1527
|
end
|
|
1528
|
+
|
|
1529
|
+
it 'works with --overwrite option' do
|
|
1530
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1531
|
+
allow(processor_double).to receive(:run)
|
|
1532
|
+
|
|
1533
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1534
|
+
expect(options[:consolidate]).to eq([spritesheet_4x2, spritesheet_6x2])
|
|
1535
|
+
expect(options[:overwrite]).to eq(true)
|
|
1536
|
+
processor_double
|
|
1537
|
+
end
|
|
1538
|
+
|
|
1539
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}", '--overwrite'])
|
|
1540
|
+
end
|
|
1541
|
+
end
|
|
1542
|
+
|
|
1543
|
+
describe 'default output filename behavior' do
|
|
1544
|
+
it 'generates consolidated_spritesheet.png when no --output specified' do
|
|
1545
|
+
# Mock all the dependencies
|
|
1546
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
1547
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:ensure_unique_output) do |path, overwrite:|
|
|
1548
|
+
expect(path).to eq('consolidated_spritesheet.png')
|
|
1549
|
+
expect(overwrite).to eq(false)
|
|
1550
|
+
'consolidated_spritesheet.png'
|
|
1551
|
+
end
|
|
1552
|
+
|
|
1553
|
+
consolidator_double = instance_double(RubySpriter::Consolidator)
|
|
1554
|
+
allow(RubySpriter::Consolidator).to receive(:new).and_return(consolidator_double)
|
|
1555
|
+
allow(consolidator_double).to receive(:consolidate).and_return({
|
|
1556
|
+
output_file: 'consolidated_spritesheet.png',
|
|
1557
|
+
columns: 2,
|
|
1558
|
+
rows: 4,
|
|
1559
|
+
frames: 8
|
|
1560
|
+
})
|
|
1561
|
+
|
|
1562
|
+
processor = RubySpriter::Processor.new(
|
|
1563
|
+
consolidate_mode: true,
|
|
1564
|
+
consolidate: [spritesheet_4x2, spritesheet_6x2],
|
|
1565
|
+
overwrite: false
|
|
1566
|
+
)
|
|
1567
|
+
|
|
1568
|
+
allow(processor).to receive(:check_dependencies!)
|
|
1569
|
+
allow(processor).to receive(:setup_temp_directory)
|
|
1570
|
+
allow(processor).to receive(:cleanup)
|
|
1571
|
+
|
|
1572
|
+
# Capture output to suppress console messages
|
|
1573
|
+
expect { processor.run }.to output(/SUCCESS/).to_stdout
|
|
1574
|
+
end
|
|
1575
|
+
|
|
1576
|
+
it 'respects --overwrite flag with default filename' do
|
|
1577
|
+
# Mock all the dependencies
|
|
1578
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
1579
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:ensure_unique_output) do |path, overwrite:|
|
|
1580
|
+
expect(path).to eq('consolidated_spritesheet.png')
|
|
1581
|
+
expect(overwrite).to eq(true)
|
|
1582
|
+
'consolidated_spritesheet.png'
|
|
1583
|
+
end
|
|
1584
|
+
|
|
1585
|
+
consolidator_double = instance_double(RubySpriter::Consolidator)
|
|
1586
|
+
allow(RubySpriter::Consolidator).to receive(:new).and_return(consolidator_double)
|
|
1587
|
+
allow(consolidator_double).to receive(:consolidate).and_return({
|
|
1588
|
+
output_file: 'consolidated_spritesheet.png',
|
|
1589
|
+
columns: 2,
|
|
1590
|
+
rows: 4,
|
|
1591
|
+
frames: 8
|
|
1592
|
+
})
|
|
1593
|
+
|
|
1594
|
+
processor = RubySpriter::Processor.new(
|
|
1595
|
+
consolidate_mode: true,
|
|
1596
|
+
consolidate: [spritesheet_4x2, spritesheet_6x2],
|
|
1597
|
+
overwrite: true
|
|
1598
|
+
)
|
|
1599
|
+
|
|
1600
|
+
allow(processor).to receive(:check_dependencies!)
|
|
1601
|
+
allow(processor).to receive(:setup_temp_directory)
|
|
1602
|
+
allow(processor).to receive(:cleanup)
|
|
1603
|
+
|
|
1604
|
+
# Capture output to suppress console messages
|
|
1605
|
+
expect { processor.run }.to output(/SUCCESS/).to_stdout
|
|
1606
|
+
end
|
|
1607
|
+
end
|
|
1608
|
+
|
|
1609
|
+
describe 'directory-based consolidation' do
|
|
1610
|
+
let(:test_dir) { File.join(@test_dir, 'consolidate_dir') }
|
|
1611
|
+
|
|
1612
|
+
before do
|
|
1613
|
+
FileUtils.mkdir_p(test_dir)
|
|
1614
|
+
# Copy fixture spritesheets to test directory
|
|
1615
|
+
FileUtils.cp(spritesheet_4x2, File.join(test_dir, 'sprite1.png'))
|
|
1616
|
+
FileUtils.cp(spritesheet_6x2, File.join(test_dir, 'sprite2.png'))
|
|
1617
|
+
end
|
|
1618
|
+
|
|
1619
|
+
it 'accepts --dir option with --consolidate' do
|
|
1620
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1621
|
+
allow(processor_double).to receive(:run)
|
|
1622
|
+
|
|
1623
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1624
|
+
expect(options[:consolidate]).to be_nil
|
|
1625
|
+
expect(options[:dir]).to eq(test_dir)
|
|
1626
|
+
processor_double
|
|
1627
|
+
end
|
|
1628
|
+
|
|
1629
|
+
described_class.start(['--consolidate', '--dir', test_dir])
|
|
1630
|
+
end
|
|
1631
|
+
|
|
1632
|
+
it 'validates directory exists' do
|
|
1633
|
+
expect do
|
|
1634
|
+
described_class.start(['--consolidate', '--dir', 'nonexistent_directory'])
|
|
1635
|
+
end.to raise_error(RubySpriter::ValidationError, /Directory not found/)
|
|
1636
|
+
end
|
|
1637
|
+
|
|
1638
|
+
it 'cannot use --dir with comma-separated file list' do
|
|
1639
|
+
expect do
|
|
1640
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}", '--dir', test_dir])
|
|
1641
|
+
end.to raise_error(RubySpriter::ValidationError, /Cannot use --dir with comma-separated file list/)
|
|
1642
|
+
end
|
|
1643
|
+
|
|
1644
|
+
it 'works with --output option' do
|
|
1645
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1646
|
+
allow(processor_double).to receive(:run)
|
|
1647
|
+
|
|
1648
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1649
|
+
expect(options[:dir]).to eq(test_dir)
|
|
1650
|
+
expect(options[:output]).to eq('custom_output.png')
|
|
1651
|
+
processor_double
|
|
1652
|
+
end
|
|
1653
|
+
|
|
1654
|
+
described_class.start(['--consolidate', '--dir', test_dir, '--output', 'custom_output.png'])
|
|
1655
|
+
end
|
|
1656
|
+
|
|
1657
|
+
it 'works with --outputdir option' do
|
|
1658
|
+
output_dir = File.join(@test_dir, 'output')
|
|
1659
|
+
FileUtils.mkdir_p(output_dir)
|
|
1660
|
+
|
|
1661
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1662
|
+
allow(processor_double).to receive(:run)
|
|
1663
|
+
|
|
1664
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1665
|
+
expect(options[:dir]).to eq(test_dir)
|
|
1666
|
+
expect(options[:outputdir]).to eq(output_dir)
|
|
1667
|
+
processor_double
|
|
1668
|
+
end
|
|
1669
|
+
|
|
1670
|
+
described_class.start(['--consolidate', '--dir', test_dir, '--outputdir', output_dir])
|
|
1671
|
+
end
|
|
1672
|
+
|
|
1673
|
+
it 'works with --overwrite option' do
|
|
1674
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1675
|
+
allow(processor_double).to receive(:run)
|
|
1676
|
+
|
|
1677
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1678
|
+
expect(options[:dir]).to eq(test_dir)
|
|
1679
|
+
expect(options[:overwrite]).to eq(true)
|
|
1680
|
+
processor_double
|
|
1681
|
+
end
|
|
1682
|
+
|
|
1683
|
+
described_class.start(['--consolidate', '--dir', test_dir, '--overwrite'])
|
|
1684
|
+
end
|
|
1685
|
+
|
|
1686
|
+
it 'works with --max-compress option' do
|
|
1687
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1688
|
+
allow(processor_double).to receive(:run)
|
|
1689
|
+
|
|
1690
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1691
|
+
expect(options[:dir]).to eq(test_dir)
|
|
1692
|
+
expect(options[:max_compress]).to eq(true)
|
|
1693
|
+
processor_double
|
|
1694
|
+
end
|
|
1695
|
+
|
|
1696
|
+
described_class.start(['--consolidate', '--dir', test_dir, '--max-compress'])
|
|
1697
|
+
end
|
|
1120
1698
|
end
|
|
1121
1699
|
end
|
|
1122
1700
|
|
|
@@ -1138,5 +1716,177 @@ RSpec.describe RubySpriter::CLI do
|
|
|
1138
1716
|
end.to raise_error(SystemExit)
|
|
1139
1717
|
end
|
|
1140
1718
|
end
|
|
1719
|
+
|
|
1720
|
+
describe '--split option' do
|
|
1721
|
+
it 'parses split option with R:C format' do
|
|
1722
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1723
|
+
allow(processor_double).to receive(:run)
|
|
1724
|
+
|
|
1725
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1726
|
+
expect(options[:split]).to eq('4:4')
|
|
1727
|
+
processor_double
|
|
1728
|
+
end
|
|
1729
|
+
|
|
1730
|
+
described_class.start(['--image', 'test.png', '--split', '4:4'])
|
|
1731
|
+
end
|
|
1732
|
+
end
|
|
1733
|
+
|
|
1734
|
+
describe '--override-md option' do
|
|
1735
|
+
it 'sets override_md option to true' do
|
|
1736
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1737
|
+
allow(processor_double).to receive(:run)
|
|
1738
|
+
|
|
1739
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1740
|
+
expect(options[:override_md]).to eq(true)
|
|
1741
|
+
processor_double
|
|
1742
|
+
end
|
|
1743
|
+
|
|
1744
|
+
described_class.start(['--image', 'test.png', '--split', '4:4', '--override-md'])
|
|
1745
|
+
end
|
|
1746
|
+
end
|
|
1747
|
+
|
|
1748
|
+
describe '--extract option' do
|
|
1749
|
+
it 'parses extract option with comma-separated frame numbers' do
|
|
1750
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1751
|
+
allow(processor_double).to receive(:run)
|
|
1752
|
+
|
|
1753
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1754
|
+
expect(options[:extract]).to eq('1,2,4,5,8')
|
|
1755
|
+
processor_double
|
|
1756
|
+
end
|
|
1757
|
+
|
|
1758
|
+
described_class.start(['--image', 'test.png', '--extract', '1,2,4,5,8'])
|
|
1759
|
+
end
|
|
1760
|
+
|
|
1761
|
+
it 'allows duplicate frame numbers' do
|
|
1762
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1763
|
+
allow(processor_double).to receive(:run)
|
|
1764
|
+
|
|
1765
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1766
|
+
expect(options[:extract]).to eq('1,1,2,2,3,3')
|
|
1767
|
+
processor_double
|
|
1768
|
+
end
|
|
1769
|
+
|
|
1770
|
+
described_class.start(['--image', 'test.png', '--extract', '1,1,2,2,3,3'])
|
|
1771
|
+
end
|
|
1772
|
+
|
|
1773
|
+
it 'cannot be used with --split' do
|
|
1774
|
+
expect do
|
|
1775
|
+
described_class.start(['--image', 'test.png', '--extract', '1,2,3', '--split', '4:4'])
|
|
1776
|
+
end.to raise_error(RubySpriter::ValidationError, /--extract and --split are mutually exclusive/)
|
|
1777
|
+
end
|
|
1778
|
+
end
|
|
1779
|
+
|
|
1780
|
+
describe '--columns option' do
|
|
1781
|
+
it 'parses columns option for extraction grid' do
|
|
1782
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1783
|
+
allow(processor_double).to receive(:run)
|
|
1784
|
+
|
|
1785
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1786
|
+
expect(options[:columns]).to eq(3)
|
|
1787
|
+
processor_double
|
|
1788
|
+
end
|
|
1789
|
+
|
|
1790
|
+
described_class.start(['--image', 'test.png', '--extract', '1,2,3', '--columns', '3'])
|
|
1791
|
+
end
|
|
1792
|
+
|
|
1793
|
+
it 'works without --extract (for future use)' do
|
|
1794
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1795
|
+
allow(processor_double).to receive(:run)
|
|
1796
|
+
|
|
1797
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1798
|
+
expect(options[:columns]).to eq(5)
|
|
1799
|
+
processor_double
|
|
1800
|
+
end
|
|
1801
|
+
|
|
1802
|
+
described_class.start(['--image', 'test.png', '--columns', '5'])
|
|
1803
|
+
end
|
|
1804
|
+
end
|
|
1805
|
+
|
|
1806
|
+
describe '--save-frames option' do
|
|
1807
|
+
it 'sets save_frames option to true' do
|
|
1808
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1809
|
+
allow(processor_double).to receive(:run)
|
|
1810
|
+
|
|
1811
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1812
|
+
expect(options[:save_frames]).to eq(true)
|
|
1813
|
+
processor_double
|
|
1814
|
+
end
|
|
1815
|
+
|
|
1816
|
+
described_class.start(['--image', 'test.png', '--extract', '1,2,3', '--save-frames'])
|
|
1817
|
+
end
|
|
1818
|
+
|
|
1819
|
+
it 'can be used without --extract' do
|
|
1820
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1821
|
+
allow(processor_double).to receive(:run)
|
|
1822
|
+
|
|
1823
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1824
|
+
expect(options[:save_frames]).to eq(true)
|
|
1825
|
+
processor_double
|
|
1826
|
+
end
|
|
1827
|
+
|
|
1828
|
+
described_class.start(['--image', 'test.png', '--split', '4:4', '--save-frames'])
|
|
1829
|
+
end
|
|
1830
|
+
end
|
|
1831
|
+
|
|
1832
|
+
describe '--add-meta option' do
|
|
1833
|
+
it 'parses add-meta option with R:C format' do
|
|
1834
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1835
|
+
allow(processor_double).to receive(:run)
|
|
1836
|
+
|
|
1837
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1838
|
+
expect(options[:add_meta]).to eq('4:4')
|
|
1839
|
+
processor_double
|
|
1840
|
+
end
|
|
1841
|
+
|
|
1842
|
+
described_class.start(['--image', 'test.png', '--add-meta', '4:4'])
|
|
1843
|
+
end
|
|
1844
|
+
|
|
1845
|
+
it 'cannot be combined with --scale' do
|
|
1846
|
+
expect do
|
|
1847
|
+
described_class.start(['--image', 'test.png', '--add-meta', '4:4', '--scale', '50'])
|
|
1848
|
+
end.to raise_error(RubySpriter::ValidationError, /--add-meta cannot be combined with processing options/)
|
|
1849
|
+
end
|
|
1850
|
+
|
|
1851
|
+
it 'cannot be combined with --remove-bg' do
|
|
1852
|
+
expect do
|
|
1853
|
+
described_class.start(['--image', 'test.png', '--add-meta', '4:4', '--remove-bg'])
|
|
1854
|
+
end.to raise_error(RubySpriter::ValidationError, /--add-meta cannot be combined with processing options/)
|
|
1855
|
+
end
|
|
1856
|
+
|
|
1857
|
+
it 'cannot be combined with --sharpen' do
|
|
1858
|
+
expect do
|
|
1859
|
+
described_class.start(['--image', 'test.png', '--add-meta', '4:4', '--sharpen'])
|
|
1860
|
+
end.to raise_error(RubySpriter::ValidationError, /--add-meta cannot be combined with processing options/)
|
|
1861
|
+
end
|
|
1862
|
+
end
|
|
1863
|
+
|
|
1864
|
+
describe '--overwrite-meta option' do
|
|
1865
|
+
it 'sets overwrite_meta option to true' do
|
|
1866
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1867
|
+
allow(processor_double).to receive(:run)
|
|
1868
|
+
|
|
1869
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1870
|
+
expect(options[:overwrite_meta]).to eq(true)
|
|
1871
|
+
processor_double
|
|
1872
|
+
end
|
|
1873
|
+
|
|
1874
|
+
described_class.start(['--image', 'test.png', '--add-meta', '4:4', '--overwrite-meta'])
|
|
1875
|
+
end
|
|
1876
|
+
end
|
|
1877
|
+
|
|
1878
|
+
describe '--frames option for partial grids' do
|
|
1879
|
+
it 'parses frames option with integer value' do
|
|
1880
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1881
|
+
allow(processor_double).to receive(:run)
|
|
1882
|
+
|
|
1883
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1884
|
+
expect(options[:frame_count]).to eq(14)
|
|
1885
|
+
processor_double
|
|
1886
|
+
end
|
|
1887
|
+
|
|
1888
|
+
described_class.start(['--image', 'test.png', '--add-meta', '4:4', '--frames', '14'])
|
|
1889
|
+
end
|
|
1890
|
+
end
|
|
1141
1891
|
end
|
|
1142
1892
|
end
|