ruby_spriter 0.6.5
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 +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +217 -0
- data/Gemfile +17 -0
- data/LICENSE +21 -0
- data/README.md +561 -0
- data/bin/ruby_spriter +20 -0
- data/lib/ruby_spriter/cli.rb +249 -0
- data/lib/ruby_spriter/consolidator.rb +146 -0
- data/lib/ruby_spriter/dependency_checker.rb +174 -0
- data/lib/ruby_spriter/gimp_processor.rb +664 -0
- data/lib/ruby_spriter/metadata_manager.rb +116 -0
- data/lib/ruby_spriter/platform.rb +82 -0
- data/lib/ruby_spriter/processor.rb +251 -0
- data/lib/ruby_spriter/utils/file_helper.rb +57 -0
- data/lib/ruby_spriter/utils/output_formatter.rb +65 -0
- data/lib/ruby_spriter/utils/path_helper.rb +59 -0
- data/lib/ruby_spriter/version.rb +7 -0
- data/lib/ruby_spriter/video_processor.rb +139 -0
- data/lib/ruby_spriter.rb +31 -0
- data/ruby_spriter.gemspec +42 -0
- data/spec/fixtures/image_without_metadata.png +0 -0
- data/spec/fixtures/spritesheet_4x2.png +0 -0
- data/spec/fixtures/spritesheet_4x4.png +0 -0
- data/spec/fixtures/spritesheet_6x2.png +0 -0
- data/spec/fixtures/spritesheet_with_metadata.png +0 -0
- data/spec/fixtures/test_video.mp4 +0 -0
- data/spec/ruby_spriter/cli_spec.rb +1142 -0
- data/spec/ruby_spriter/consolidator_spec.rb +375 -0
- data/spec/ruby_spriter/dependency_checker_spec.rb +0 -0
- data/spec/ruby_spriter/gimp_processor_spec.rb +425 -0
- data/spec/ruby_spriter/metadata_manager_spec.rb +0 -0
- data/spec/ruby_spriter/platform_spec.rb +82 -0
- data/spec/ruby_spriter/processor_spec.rb +0 -0
- data/spec/ruby_spriter/utils/file_helper_spec.rb +71 -0
- data/spec/ruby_spriter/utils/output_formatter_spec.rb +0 -0
- data/spec/ruby_spriter/utils/path_helper_spec.rb +78 -0
- data/spec/ruby_spriter/video_processor_spec.rb +0 -0
- data/spec/spec_helper.rb +41 -0
- metadata +88 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe RubySpriter::Consolidator do
|
|
6
|
+
let(:spritesheet1) { File.join(__dir__, '..', 'fixtures', 'spritesheet_4x2.png') }
|
|
7
|
+
let(:spritesheet2) { File.join(__dir__, '..', 'fixtures', 'spritesheet_6x2.png') }
|
|
8
|
+
let(:spritesheet3) { File.join(__dir__, '..', 'fixtures', 'spritesheet_4x4.png') }
|
|
9
|
+
let(:output_file) { File.join($test_temp_dir, 'consolidated.png') }
|
|
10
|
+
|
|
11
|
+
describe '#initialize' do
|
|
12
|
+
it 'initializes with empty options by default' do
|
|
13
|
+
consolidator = described_class.new
|
|
14
|
+
|
|
15
|
+
expect(consolidator.options).to eq({})
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'initializes with provided options' do
|
|
19
|
+
consolidator = described_class.new(debug: true, validate_columns: false)
|
|
20
|
+
|
|
21
|
+
expect(consolidator.options[:debug]).to be true
|
|
22
|
+
expect(consolidator.options[:validate_columns]).to be false
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe '#consolidate' do
|
|
27
|
+
let(:consolidator) { described_class.new }
|
|
28
|
+
|
|
29
|
+
# Helper to set up common mocks for successful consolidation
|
|
30
|
+
def stub_consolidation_success
|
|
31
|
+
allow(RubySpriter::MetadataManager).to receive(:embed)
|
|
32
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
33
|
+
allow(File).to receive(:rename)
|
|
34
|
+
allow(File).to receive(:exist?).and_call_original
|
|
35
|
+
allow(File).to receive(:exist?).with(/consolidated.*temp/).and_return(true)
|
|
36
|
+
allow(File).to receive(:delete)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
context 'with file validation' do
|
|
40
|
+
it 'raises error when given 0 files' do
|
|
41
|
+
expect {
|
|
42
|
+
consolidator.consolidate([], output_file)
|
|
43
|
+
}.to raise_error(RubySpriter::ValidationError, /Need at least 2 files/)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'raises error when given only 1 file' do
|
|
47
|
+
expect {
|
|
48
|
+
consolidator.consolidate([spritesheet1], output_file)
|
|
49
|
+
}.to raise_error(RubySpriter::ValidationError, /Need at least 2 files/)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'accepts 2 files minimum' do
|
|
53
|
+
stub_consolidation_success
|
|
54
|
+
allow(RubySpriter::MetadataManager).to receive(:read).and_return(
|
|
55
|
+
{ columns: 2, rows: 2, frames: 4 }
|
|
56
|
+
)
|
|
57
|
+
allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
|
|
58
|
+
allow(File).to receive(:size).and_return(1000)
|
|
59
|
+
|
|
60
|
+
expect {
|
|
61
|
+
consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
62
|
+
}.not_to raise_error
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'raises error when file does not exist' do
|
|
66
|
+
non_existent = 'non_existent.png'
|
|
67
|
+
|
|
68
|
+
expect {
|
|
69
|
+
consolidator.consolidate([spritesheet1, non_existent], output_file)
|
|
70
|
+
}.to raise_error(RubySpriter::ValidationError, /not found/)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
context 'with metadata reading' do
|
|
75
|
+
it 'reads metadata from all files' do
|
|
76
|
+
stub_consolidation_success
|
|
77
|
+
metadata1 = { columns: 2, rows: 2, frames: 4 }
|
|
78
|
+
metadata2 = { columns: 2, rows: 3, frames: 6 }
|
|
79
|
+
|
|
80
|
+
expect(RubySpriter::MetadataManager).to receive(:read).with(spritesheet1).and_return(metadata1)
|
|
81
|
+
expect(RubySpriter::MetadataManager).to receive(:read).with(spritesheet2).and_return(metadata2)
|
|
82
|
+
|
|
83
|
+
allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
|
|
84
|
+
allow(File).to receive(:size).and_return(1000)
|
|
85
|
+
|
|
86
|
+
consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'raises ValidationError when file missing metadata' do
|
|
90
|
+
allow(RubySpriter::MetadataManager).to receive(:read).with(spritesheet1).and_return(nil)
|
|
91
|
+
|
|
92
|
+
expect {
|
|
93
|
+
consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
94
|
+
}.to raise_error(RubySpriter::ValidationError, /missing metadata/)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it 'raises error with helpful message about Ruby Spriter spritesheets' do
|
|
98
|
+
allow(RubySpriter::MetadataManager).to receive(:read).with(spritesheet1).and_return(nil)
|
|
99
|
+
|
|
100
|
+
expect {
|
|
101
|
+
consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
102
|
+
}.to raise_error(RubySpriter::ValidationError, /All files must be Ruby Spriter spritesheets/)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
context 'with column validation enabled (default)' do
|
|
107
|
+
let(:consolidator) { described_class.new(validate_columns: true) }
|
|
108
|
+
|
|
109
|
+
it 'passes when all files have same column count' do
|
|
110
|
+
stub_consolidation_success
|
|
111
|
+
metadata1 = { columns: 2, rows: 2, frames: 4 }
|
|
112
|
+
metadata2 = { columns: 2, rows: 3, frames: 6 }
|
|
113
|
+
|
|
114
|
+
allow(RubySpriter::MetadataManager).to receive(:read).and_return(metadata1, metadata2)
|
|
115
|
+
allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
|
|
116
|
+
allow(File).to receive(:size).and_return(1000)
|
|
117
|
+
|
|
118
|
+
expect {
|
|
119
|
+
consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
120
|
+
}.not_to raise_error
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it 'raises error when column counts do not match' do
|
|
124
|
+
metadata1 = { columns: 2, rows: 2, frames: 4 }
|
|
125
|
+
metadata2 = { columns: 4, rows: 1, frames: 4 }
|
|
126
|
+
|
|
127
|
+
allow(RubySpriter::MetadataManager).to receive(:read).and_return(metadata1, metadata2)
|
|
128
|
+
|
|
129
|
+
expect {
|
|
130
|
+
consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
131
|
+
}.to raise_error(RubySpriter::ValidationError, /Column count mismatch/)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it 'error message shows expected and actual column counts' do
|
|
135
|
+
metadata1 = { columns: 2, rows: 2, frames: 4 }
|
|
136
|
+
metadata2 = { columns: 4, rows: 1, frames: 4 }
|
|
137
|
+
|
|
138
|
+
allow(RubySpriter::MetadataManager).to receive(:read).and_return(metadata1, metadata2)
|
|
139
|
+
|
|
140
|
+
expect {
|
|
141
|
+
consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
142
|
+
}.to raise_error(RubySpriter::ValidationError, /Expected 2, found 4/)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it 'error message suggests --no-validate-columns flag' do
|
|
146
|
+
metadata1 = { columns: 2, rows: 2, frames: 4 }
|
|
147
|
+
metadata2 = { columns: 4, rows: 1, frames: 4 }
|
|
148
|
+
|
|
149
|
+
allow(RubySpriter::MetadataManager).to receive(:read).and_return(metadata1, metadata2)
|
|
150
|
+
|
|
151
|
+
expect {
|
|
152
|
+
consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
153
|
+
}.to raise_error(RubySpriter::ValidationError, /--no-validate-columns/)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
context 'with column validation disabled' do
|
|
158
|
+
let(:consolidator) { described_class.new(validate_columns: false) }
|
|
159
|
+
before { stub_consolidation_success }
|
|
160
|
+
|
|
161
|
+
it 'allows mismatched column counts' do
|
|
162
|
+
metadata1 = { columns: 2, rows: 2, frames: 4 }
|
|
163
|
+
metadata2 = { columns: 4, rows: 1, frames: 4 }
|
|
164
|
+
|
|
165
|
+
allow(RubySpriter::MetadataManager).to receive(:read).and_return(metadata1, metadata2)
|
|
166
|
+
allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
|
|
167
|
+
allow(File).to receive(:size).and_return(1000)
|
|
168
|
+
|
|
169
|
+
expect {
|
|
170
|
+
consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
171
|
+
}.not_to raise_error
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
context 'with ImageMagick consolidation' do
|
|
176
|
+
before do
|
|
177
|
+
stub_consolidation_success
|
|
178
|
+
allow(RubySpriter::MetadataManager).to receive(:read).and_return(
|
|
179
|
+
{ columns: 2, rows: 2, frames: 4 }
|
|
180
|
+
)
|
|
181
|
+
allow(File).to receive(:size).and_return(1000)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it 'calls ImageMagick with correct command' do
|
|
185
|
+
expect(Open3).to receive(:capture3) do |cmd|
|
|
186
|
+
expect(cmd).to include('convert')
|
|
187
|
+
expect(cmd).to include('-append')
|
|
188
|
+
expect(cmd).to include(spritesheet1)
|
|
189
|
+
expect(cmd).to include(spritesheet2)
|
|
190
|
+
expect(cmd).to include(output_file)
|
|
191
|
+
['', '', instance_double(Process::Status, success?: true)]
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
it 'uses -append flag for vertical stacking' do
|
|
198
|
+
expect(Open3).to receive(:capture3) do |cmd|
|
|
199
|
+
expect(cmd).to include('-append')
|
|
200
|
+
['', '', instance_double(Process::Status, success?: true)]
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
it 'raises ProcessingError when ImageMagick fails' do
|
|
207
|
+
allow(Open3).to receive(:capture3).and_return(
|
|
208
|
+
['', 'ImageMagick error', instance_double(Process::Status, success?: false)]
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
expect {
|
|
212
|
+
consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
213
|
+
}.to raise_error(RubySpriter::ProcessingError, /ImageMagick consolidation failed/)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
it 'shows debug output when debug option enabled' do
|
|
217
|
+
consolidator = described_class.new(debug: true)
|
|
218
|
+
|
|
219
|
+
allow(RubySpriter::MetadataManager).to receive(:read).and_return(
|
|
220
|
+
{ columns: 2, rows: 2, frames: 4 }
|
|
221
|
+
)
|
|
222
|
+
allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
|
|
223
|
+
allow(File).to receive(:size).and_return(1000)
|
|
224
|
+
|
|
225
|
+
expect {
|
|
226
|
+
consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
227
|
+
}.to output(/DEBUG: ImageMagick command/).to_stdout
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
context 'with successful consolidation' do
|
|
232
|
+
before do
|
|
233
|
+
stub_consolidation_success
|
|
234
|
+
allow(RubySpriter::MetadataManager).to receive(:read).and_return(
|
|
235
|
+
{ columns: 2, rows: 2, frames: 4 },
|
|
236
|
+
{ columns: 2, rows: 3, frames: 6 }
|
|
237
|
+
)
|
|
238
|
+
allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
|
|
239
|
+
allow(File).to receive(:size).and_return(5000)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
it 'calculates correct total frames' do
|
|
243
|
+
result = consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
244
|
+
|
|
245
|
+
expect(result[:frames]).to eq(10) # 4 + 6
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
it 'calculates correct row count' do
|
|
249
|
+
result = consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
250
|
+
|
|
251
|
+
# 10 frames / 2 columns = 5 rows
|
|
252
|
+
expect(result[:rows]).to eq(5)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
it 'uses columns from first spritesheet' do
|
|
256
|
+
result = consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
257
|
+
|
|
258
|
+
expect(result[:columns]).to eq(2)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
it 'embeds metadata in output file' do
|
|
262
|
+
expect(RubySpriter::MetadataManager).to receive(:embed).with(
|
|
263
|
+
anything,
|
|
264
|
+
output_file,
|
|
265
|
+
hash_including(columns: 2, rows: 5, frames: 10)
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
it 'returns hash with correct keys' do
|
|
272
|
+
result = consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
273
|
+
|
|
274
|
+
expect(result).to include(
|
|
275
|
+
output_file: output_file,
|
|
276
|
+
columns: 2,
|
|
277
|
+
rows: 5,
|
|
278
|
+
frames: 10,
|
|
279
|
+
size: 5000
|
|
280
|
+
)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
it 'returns correct file size' do
|
|
284
|
+
result = consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
285
|
+
|
|
286
|
+
expect(result[:size]).to eq(5000)
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
context 'with 3 spritesheets' do
|
|
291
|
+
before do
|
|
292
|
+
stub_consolidation_success
|
|
293
|
+
allow(RubySpriter::MetadataManager).to receive(:read).and_return(
|
|
294
|
+
{ columns: 4, rows: 1, frames: 4 },
|
|
295
|
+
{ columns: 4, rows: 1, frames: 4 },
|
|
296
|
+
{ columns: 4, rows: 1, frames: 4 }
|
|
297
|
+
)
|
|
298
|
+
allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
|
|
299
|
+
allow(File).to receive(:size).and_return(8000)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
it 'successfully consolidates all 3 files' do
|
|
303
|
+
expect(Open3).to receive(:capture3) do |cmd|
|
|
304
|
+
expect(cmd).to include(spritesheet1)
|
|
305
|
+
expect(cmd).to include(spritesheet2)
|
|
306
|
+
expect(cmd).to include(spritesheet3)
|
|
307
|
+
['', '', instance_double(Process::Status, success?: true)]
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
consolidator.consolidate([spritesheet1, spritesheet2, spritesheet3], output_file)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
it 'calculates correct total frames for 3 files' do
|
|
314
|
+
result = consolidator.consolidate([spritesheet1, spritesheet2, spritesheet3], output_file)
|
|
315
|
+
|
|
316
|
+
expect(result[:frames]).to eq(12) # 4 + 4 + 4
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
it 'calculates correct row count for 3 files' do
|
|
320
|
+
result = consolidator.consolidate([spritesheet1, spritesheet2, spritesheet3], output_file)
|
|
321
|
+
|
|
322
|
+
# 12 frames / 4 columns = 3 rows
|
|
323
|
+
expect(result[:rows]).to eq(3)
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
context 'with output display' do
|
|
328
|
+
before do
|
|
329
|
+
stub_consolidation_success
|
|
330
|
+
allow(RubySpriter::MetadataManager).to receive(:read).and_return(
|
|
331
|
+
{ columns: 2, rows: 2, frames: 4 },
|
|
332
|
+
{ columns: 2, rows: 3, frames: 6 }
|
|
333
|
+
)
|
|
334
|
+
allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
|
|
335
|
+
allow(File).to receive(:size).and_return(5000)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
it 'displays success message' do
|
|
339
|
+
expect {
|
|
340
|
+
consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
341
|
+
}.to output(/Consolidated spritesheet created/).to_stdout
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
it 'displays output file path' do
|
|
345
|
+
expect {
|
|
346
|
+
consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
347
|
+
}.to output(/Output:.*consolidated\.png/).to_stdout
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
it 'displays grid layout information' do
|
|
351
|
+
expect {
|
|
352
|
+
consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
353
|
+
}.to output(/Grid Layout:.*Columns: 2.*Rows: 5.*Total Frames: 10/m).to_stdout
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
it 'displays Godot AnimatedSprite2D settings' do
|
|
357
|
+
expect {
|
|
358
|
+
consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
359
|
+
}.to output(/Godot AnimatedSprite2D Settings:.*HFrames = 2.*VFrames = 5/m).to_stdout
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
it 'displays source breakdown with frame counts' do
|
|
363
|
+
expect {
|
|
364
|
+
consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
365
|
+
}.to output(/Source Breakdown:.*spritesheet_4x2\.png.*grid \(4 frames\).*spritesheet_6x2\.png.*grid \(6 frames\)/m).to_stdout
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
it 'displays number of combined spritesheets' do
|
|
369
|
+
expect {
|
|
370
|
+
consolidator.consolidate([spritesheet1, spritesheet2], output_file)
|
|
371
|
+
}.to output(/Combined 2 spritesheets/).to_stdout
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|
|
File without changes
|