ruby_spriter 0.6.6 → 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 +138 -0
- data/README.md +336 -33
- data/lib/ruby_spriter/batch_processor.rb +212 -0
- data/lib/ruby_spriter/cli.rb +354 -7
- data/lib/ruby_spriter/compression_manager.rb +101 -0
- data/lib/ruby_spriter/consolidator.rb +33 -0
- data/lib/ruby_spriter/processor.rb +412 -7
- data/lib/ruby_spriter/version.rb +2 -2
- data/lib/ruby_spriter.rb +2 -0
- data/spec/ruby_spriter/batch_processor_spec.rb +200 -0
- data/spec/ruby_spriter/cli_spec.rb +387 -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 +350 -0
- metadata +5 -1
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe RubySpriter::BatchProcessor do
|
|
6
|
+
let(:test_dir) { 'E:/test/videos' }
|
|
7
|
+
let(:output_dir) { 'E:/test/output' }
|
|
8
|
+
let(:video1) { File.join(test_dir, 'video1.mp4') }
|
|
9
|
+
let(:video2) { File.join(test_dir, 'video2.mp4') }
|
|
10
|
+
let(:video3) { File.join(test_dir, 'video3.mp4') }
|
|
11
|
+
|
|
12
|
+
let(:options) do
|
|
13
|
+
{
|
|
14
|
+
batch: true,
|
|
15
|
+
dir: test_dir,
|
|
16
|
+
columns: 4,
|
|
17
|
+
frame_count: 16,
|
|
18
|
+
max_width: 320,
|
|
19
|
+
overwrite: false,
|
|
20
|
+
debug: false
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
before do
|
|
25
|
+
# Mock file system
|
|
26
|
+
allow(Dir).to receive(:exist?).and_return(true)
|
|
27
|
+
allow(File).to receive(:exist?).and_return(true)
|
|
28
|
+
allow(File).to receive(:directory?).with(test_dir).and_return(true)
|
|
29
|
+
allow(Dir).to receive(:glob).with(File.join(test_dir, '*.mp4')).and_return([video1, video2, video3])
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe '#initialize' do
|
|
33
|
+
it 'raises error if directory does not exist' do
|
|
34
|
+
allow(File).to receive(:directory?).with(test_dir).and_return(false)
|
|
35
|
+
|
|
36
|
+
expect {
|
|
37
|
+
described_class.new(options)
|
|
38
|
+
}.to raise_error(RubySpriter::ValidationError, /Directory not found/)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'initializes with valid options' do
|
|
42
|
+
processor = described_class.new(options)
|
|
43
|
+
expect(processor.options).to eq(options)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe '#find_videos' do
|
|
48
|
+
it 'finds all MP4 files in directory' do
|
|
49
|
+
processor = described_class.new(options)
|
|
50
|
+
videos = processor.find_videos
|
|
51
|
+
|
|
52
|
+
expect(videos).to eq([video1, video2, video3])
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'raises error if no videos found' do
|
|
56
|
+
allow(Dir).to receive(:glob).with(File.join(test_dir, '*.mp4')).and_return([])
|
|
57
|
+
|
|
58
|
+
processor = described_class.new(options)
|
|
59
|
+
|
|
60
|
+
expect {
|
|
61
|
+
processor.find_videos
|
|
62
|
+
}.to raise_error(RubySpriter::ValidationError, /No MP4 files found/)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
describe '#process' do
|
|
67
|
+
let(:video_processor_mock) { instance_double(RubySpriter::VideoProcessor) }
|
|
68
|
+
|
|
69
|
+
before do
|
|
70
|
+
allow(RubySpriter::VideoProcessor).to receive(:new).and_return(video_processor_mock)
|
|
71
|
+
allow(video_processor_mock).to receive(:create_spritesheet).and_return(
|
|
72
|
+
{ output_file: 'output.png', columns: 4, rows: 4, frames: 16 }
|
|
73
|
+
)
|
|
74
|
+
allow(File).to receive(:exist?).and_return(false) # No existing files
|
|
75
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:spritesheet_filename) do |video|
|
|
76
|
+
video.gsub('.mp4', '_spritesheet.png')
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it 'processes all videos in directory' do
|
|
81
|
+
processor = described_class.new(options)
|
|
82
|
+
|
|
83
|
+
expect(video_processor_mock).to receive(:create_spritesheet).exactly(3).times
|
|
84
|
+
|
|
85
|
+
results = processor.process
|
|
86
|
+
|
|
87
|
+
expect(results[:processed_count]).to eq(3)
|
|
88
|
+
expect(results[:outputs].length).to eq(3)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'outputs to same directory by default' do
|
|
92
|
+
processor = described_class.new(options)
|
|
93
|
+
|
|
94
|
+
expected_output1 = File.join(test_dir, 'video1_spritesheet.png')
|
|
95
|
+
expect(video_processor_mock).to receive(:create_spritesheet).with(video1, expected_output1)
|
|
96
|
+
|
|
97
|
+
processor.process
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'outputs to specified outputdir when provided' do
|
|
101
|
+
options_with_output = options.merge(outputdir: output_dir)
|
|
102
|
+
processor = described_class.new(options_with_output)
|
|
103
|
+
|
|
104
|
+
allow(File).to receive(:directory?).with(output_dir).and_return(true)
|
|
105
|
+
|
|
106
|
+
expected_output1 = File.join(output_dir, 'video1_spritesheet.png')
|
|
107
|
+
expect(video_processor_mock).to receive(:create_spritesheet).with(video1, expected_output1)
|
|
108
|
+
|
|
109
|
+
processor.process
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it 'creates output directory if it does not exist' do
|
|
113
|
+
options_with_output = options.merge(outputdir: output_dir)
|
|
114
|
+
processor = described_class.new(options_with_output)
|
|
115
|
+
|
|
116
|
+
allow(File).to receive(:directory?).with(output_dir).and_return(false)
|
|
117
|
+
expect(FileUtils).to receive(:mkdir_p).with(output_dir)
|
|
118
|
+
|
|
119
|
+
processor.process
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'enforces unique filenames unless overwrite is true' do
|
|
123
|
+
allow(File).to receive(:exist?).with(/video1_spritesheet\.png/).and_return(true)
|
|
124
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:ensure_unique_output).and_call_original
|
|
125
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:unique_filename).and_return('video1_spritesheet_20251024_123456_789.png')
|
|
126
|
+
|
|
127
|
+
processor = described_class.new(options)
|
|
128
|
+
|
|
129
|
+
expect(RubySpriter::Utils::FileHelper).to receive(:ensure_unique_output).at_least(:once)
|
|
130
|
+
|
|
131
|
+
processor.process
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it 'continues processing after error in one video' do
|
|
135
|
+
processor = described_class.new(options)
|
|
136
|
+
|
|
137
|
+
allow(video_processor_mock).to receive(:create_spritesheet).and_raise(RubySpriter::ProcessingError, 'Test error')
|
|
138
|
+
|
|
139
|
+
results = processor.process
|
|
140
|
+
|
|
141
|
+
expect(results[:processed_count]).to eq(0)
|
|
142
|
+
expect(results[:errors].length).to eq(3)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
describe '#consolidate_results' do
|
|
147
|
+
let(:spritesheet1) { File.join(test_dir, 'video1_spritesheet.png') }
|
|
148
|
+
let(:spritesheet2) { File.join(test_dir, 'video2_spritesheet.png') }
|
|
149
|
+
let(:spritesheet3) { File.join(test_dir, 'video3_spritesheet.png') }
|
|
150
|
+
let(:consolidator_mock) { instance_double(RubySpriter::Consolidator) }
|
|
151
|
+
|
|
152
|
+
before do
|
|
153
|
+
allow(RubySpriter::Consolidator).to receive(:new).and_return(consolidator_mock)
|
|
154
|
+
allow(consolidator_mock).to receive(:consolidate).and_return(
|
|
155
|
+
{ output_file: 'consolidated.png', columns: 4, rows: 12, frames: 48 }
|
|
156
|
+
)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it 'consolidates all spritesheets when batch_consolidate is true' do
|
|
160
|
+
options_with_consolidate = options.merge(batch_consolidate: true)
|
|
161
|
+
processor = described_class.new(options_with_consolidate)
|
|
162
|
+
|
|
163
|
+
outputs = [spritesheet1, spritesheet2, spritesheet3]
|
|
164
|
+
|
|
165
|
+
expect(consolidator_mock).to receive(:consolidate) do |files, output|
|
|
166
|
+
expect(files).to eq(outputs)
|
|
167
|
+
expect(output).to include('batch_consolidated_spritesheet')
|
|
168
|
+
expect(output).to end_with('.png')
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
processor.consolidate_results(outputs)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it 'uses outputdir for consolidated file if specified' do
|
|
175
|
+
options_with_consolidate = options.merge(batch_consolidate: true, outputdir: output_dir)
|
|
176
|
+
processor = described_class.new(options_with_consolidate)
|
|
177
|
+
|
|
178
|
+
allow(File).to receive(:directory?).with(output_dir).and_return(true)
|
|
179
|
+
|
|
180
|
+
outputs = [spritesheet1, spritesheet2, spritesheet3]
|
|
181
|
+
|
|
182
|
+
expect(consolidator_mock).to receive(:consolidate) do |files, output|
|
|
183
|
+
expect(output).to start_with(output_dir)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
processor.consolidate_results(outputs)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it 'does not consolidate when batch_consolidate is false' do
|
|
190
|
+
processor = described_class.new(options)
|
|
191
|
+
|
|
192
|
+
outputs = [spritesheet1, spritesheet2, spritesheet3]
|
|
193
|
+
|
|
194
|
+
expect(consolidator_mock).not_to receive(:consolidate)
|
|
195
|
+
|
|
196
|
+
result = processor.consolidate_results(outputs)
|
|
197
|
+
expect(result).to be_nil
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
@@ -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
|
|
@@ -676,6 +691,141 @@ RSpec.describe RubySpriter::CLI do
|
|
|
676
691
|
describe '--video flag' do
|
|
677
692
|
let(:fixture_video) { File.join(__dir__, '..', 'fixtures', 'test_video.mp4') }
|
|
678
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
|
+
|
|
679
829
|
describe 'argument parsing' do
|
|
680
830
|
it 'sets video option with --video flag' do
|
|
681
831
|
processor_double = instance_double(RubySpriter::Processor)
|
|
@@ -1410,6 +1560,7 @@ RSpec.describe RubySpriter::CLI do
|
|
|
1410
1560
|
})
|
|
1411
1561
|
|
|
1412
1562
|
processor = RubySpriter::Processor.new(
|
|
1563
|
+
consolidate_mode: true,
|
|
1413
1564
|
consolidate: [spritesheet_4x2, spritesheet_6x2],
|
|
1414
1565
|
overwrite: false
|
|
1415
1566
|
)
|
|
@@ -1441,6 +1592,7 @@ RSpec.describe RubySpriter::CLI do
|
|
|
1441
1592
|
})
|
|
1442
1593
|
|
|
1443
1594
|
processor = RubySpriter::Processor.new(
|
|
1595
|
+
consolidate_mode: true,
|
|
1444
1596
|
consolidate: [spritesheet_4x2, spritesheet_6x2],
|
|
1445
1597
|
overwrite: true
|
|
1446
1598
|
)
|
|
@@ -1453,6 +1605,97 @@ RSpec.describe RubySpriter::CLI do
|
|
|
1453
1605
|
expect { processor.run }.to output(/SUCCESS/).to_stdout
|
|
1454
1606
|
end
|
|
1455
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
|
|
1698
|
+
end
|
|
1456
1699
|
end
|
|
1457
1700
|
|
|
1458
1701
|
describe 'error handling' do
|
|
@@ -1501,5 +1744,149 @@ RSpec.describe RubySpriter::CLI do
|
|
|
1501
1744
|
described_class.start(['--image', 'test.png', '--split', '4:4', '--override-md'])
|
|
1502
1745
|
end
|
|
1503
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
|
|
1504
1891
|
end
|
|
1505
1892
|
end
|