ruby_spriter 0.6.6 → 0.6.7.1
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 +257 -0
- data/README.md +384 -33
- data/lib/ruby_spriter/batch_processor.rb +214 -0
- data/lib/ruby_spriter/cli.rb +355 -8
- data/lib/ruby_spriter/compression_manager.rb +101 -0
- data/lib/ruby_spriter/consolidator.rb +33 -0
- data/lib/ruby_spriter/dependency_checker.rb +65 -15
- data/lib/ruby_spriter/gimp_processor.rb +395 -4
- data/lib/ruby_spriter/platform.rb +56 -1
- data/lib/ruby_spriter/processor.rb +419 -9
- 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/platform_spec.rb +11 -1
- data/spec/ruby_spriter/processor_spec.rb +350 -0
- metadata +6 -2
|
@@ -204,6 +204,356 @@ RSpec.describe RubySpriter::Processor do
|
|
|
204
204
|
end
|
|
205
205
|
end
|
|
206
206
|
|
|
207
|
+
describe '--extract validation' do
|
|
208
|
+
let(:temp_image) { File.join(Dir.mktmpdir, 'test.png') }
|
|
209
|
+
|
|
210
|
+
before do
|
|
211
|
+
FileUtils.touch(temp_image)
|
|
212
|
+
|
|
213
|
+
allow_any_instance_of(RubySpriter::DependencyChecker).to receive(:check_all).and_return({
|
|
214
|
+
ffmpeg: { available: true },
|
|
215
|
+
ffprobe: { available: true },
|
|
216
|
+
imagemagick: { available: true },
|
|
217
|
+
gimp: { available: true }
|
|
218
|
+
})
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
after do
|
|
222
|
+
FileUtils.rm_f(temp_image)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
describe 'format validation' do
|
|
226
|
+
it 'raises error for invalid extract format (non-numeric)' do
|
|
227
|
+
expect {
|
|
228
|
+
described_class.new(image: temp_image, extract: '1,a,3')
|
|
229
|
+
}.to raise_error(RubySpriter::ValidationError, /Invalid --extract format/)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
it 'raises error for invalid extract format (empty)' do
|
|
233
|
+
expect {
|
|
234
|
+
described_class.new(image: temp_image, extract: '')
|
|
235
|
+
}.to raise_error(RubySpriter::ValidationError, /Invalid --extract format/)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
it 'raises error for invalid extract format (spaces)' do
|
|
239
|
+
expect {
|
|
240
|
+
described_class.new(image: temp_image, extract: '1, 2, 3')
|
|
241
|
+
}.to raise_error(RubySpriter::ValidationError, /Invalid --extract format/)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
it 'accepts valid comma-separated frame numbers' do
|
|
245
|
+
allow(RubySpriter::MetadataManager).to receive(:read).with(temp_image).and_return({
|
|
246
|
+
columns: 4,
|
|
247
|
+
rows: 4,
|
|
248
|
+
frames: 16
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
expect {
|
|
252
|
+
described_class.new(image: temp_image, extract: '1,2,4,5,8')
|
|
253
|
+
}.not_to raise_error
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
describe 'minimum frames validation' do
|
|
258
|
+
it 'raises error when less than 2 frames requested' do
|
|
259
|
+
allow(RubySpriter::MetadataManager).to receive(:read).with(temp_image).and_return({
|
|
260
|
+
columns: 4,
|
|
261
|
+
rows: 4,
|
|
262
|
+
frames: 16
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
expect {
|
|
266
|
+
described_class.new(image: temp_image, extract: '1')
|
|
267
|
+
}.to raise_error(RubySpriter::ValidationError, /--extract requires at least 2 frames/)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
it 'allows exactly 2 frames' do
|
|
271
|
+
allow(RubySpriter::MetadataManager).to receive(:read).with(temp_image).and_return({
|
|
272
|
+
columns: 4,
|
|
273
|
+
rows: 4,
|
|
274
|
+
frames: 16
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
expect {
|
|
278
|
+
described_class.new(image: temp_image, extract: '1,2')
|
|
279
|
+
}.not_to raise_error
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
describe 'frame number validation' do
|
|
284
|
+
it 'raises error when frame number is 0' do
|
|
285
|
+
allow(RubySpriter::MetadataManager).to receive(:read).with(temp_image).and_return({
|
|
286
|
+
columns: 4,
|
|
287
|
+
rows: 4,
|
|
288
|
+
frames: 16
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
expect {
|
|
292
|
+
described_class.new(image: temp_image, extract: '0,1,2')
|
|
293
|
+
}.to raise_error(RubySpriter::ValidationError, /Frame numbers must be 1-indexed/)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
it 'raises error when frame number is negative' do
|
|
297
|
+
allow(RubySpriter::MetadataManager).to receive(:read).with(temp_image).and_return({
|
|
298
|
+
columns: 4,
|
|
299
|
+
rows: 4,
|
|
300
|
+
frames: 16
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
expect {
|
|
304
|
+
described_class.new(image: temp_image, extract: '1,-2,3')
|
|
305
|
+
}.to raise_error(RubySpriter::ValidationError, /Frame numbers must be 1-indexed/)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
it 'allows duplicate frame numbers' do
|
|
309
|
+
allow(RubySpriter::MetadataManager).to receive(:read).with(temp_image).and_return({
|
|
310
|
+
columns: 4,
|
|
311
|
+
rows: 4,
|
|
312
|
+
frames: 16
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
expect {
|
|
316
|
+
described_class.new(image: temp_image, extract: '1,1,2,2,3,3')
|
|
317
|
+
}.not_to raise_error
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
describe 'metadata requirement' do
|
|
322
|
+
it 'raises error when image has no metadata' do
|
|
323
|
+
allow(RubySpriter::MetadataManager).to receive(:read).with(temp_image).and_return(nil)
|
|
324
|
+
|
|
325
|
+
expect {
|
|
326
|
+
described_class.new(image: temp_image, extract: '1,2,3')
|
|
327
|
+
}.to raise_error(RubySpriter::ValidationError, /Image has no metadata.*Cannot extract frames/)
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
describe 'out of bounds validation' do
|
|
332
|
+
before do
|
|
333
|
+
allow(RubySpriter::MetadataManager).to receive(:read).with(temp_image).and_return({
|
|
334
|
+
columns: 4,
|
|
335
|
+
rows: 4,
|
|
336
|
+
frames: 16
|
|
337
|
+
})
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
it 'raises error when frame number exceeds total frames' do
|
|
341
|
+
expect {
|
|
342
|
+
described_class.new(image: temp_image, extract: '1,2,17')
|
|
343
|
+
}.to raise_error(RubySpriter::ValidationError, /Frame 17 is out of bounds.*only has 16 frames/)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
it 'allows frame number equal to total frames' do
|
|
347
|
+
expect {
|
|
348
|
+
described_class.new(image: temp_image, extract: '1,2,16')
|
|
349
|
+
}.not_to raise_error
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
it 'raises error for multiple out of bounds frames' do
|
|
353
|
+
expect {
|
|
354
|
+
described_class.new(image: temp_image, extract: '1,20,25')
|
|
355
|
+
}.to raise_error(RubySpriter::ValidationError, /Frame 20 is out of bounds/)
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
describe '--columns default' do
|
|
360
|
+
before do
|
|
361
|
+
allow(RubySpriter::MetadataManager).to receive(:read).with(temp_image).and_return({
|
|
362
|
+
columns: 4,
|
|
363
|
+
rows: 4,
|
|
364
|
+
frames: 16
|
|
365
|
+
})
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
it 'defaults to 4 columns when not specified' do
|
|
369
|
+
processor = described_class.new(image: temp_image, extract: '1,2,3,4')
|
|
370
|
+
expect(processor.instance_variable_get(:@options)[:columns]).to eq(4)
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
it 'uses specified columns when provided' do
|
|
374
|
+
processor = described_class.new(image: temp_image, extract: '1,2,3', columns: 3)
|
|
375
|
+
expect(processor.instance_variable_get(:@options)[:columns]).to eq(3)
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
describe '--add-meta validation' do
|
|
381
|
+
let(:temp_image) { File.join(Dir.mktmpdir, 'test.png') }
|
|
382
|
+
|
|
383
|
+
before do
|
|
384
|
+
FileUtils.touch(temp_image)
|
|
385
|
+
|
|
386
|
+
allow_any_instance_of(RubySpriter::DependencyChecker).to receive(:check_all).and_return({
|
|
387
|
+
ffmpeg: { available: true },
|
|
388
|
+
ffprobe: { available: true },
|
|
389
|
+
imagemagick: { available: true },
|
|
390
|
+
gimp: { available: true }
|
|
391
|
+
})
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
after do
|
|
395
|
+
FileUtils.rm_f(temp_image)
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
describe 'format validation' do
|
|
399
|
+
it 'raises error for invalid format (missing colon)' do
|
|
400
|
+
expect {
|
|
401
|
+
described_class.new(image: temp_image, add_meta: '44')
|
|
402
|
+
}.to raise_error(RubySpriter::ValidationError, /Invalid --add-meta format/)
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
it 'raises error for invalid format (non-numeric rows)' do
|
|
406
|
+
expect {
|
|
407
|
+
described_class.new(image: temp_image, add_meta: 'a:4')
|
|
408
|
+
}.to raise_error(RubySpriter::ValidationError, /Invalid --add-meta format/)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
it 'raises error for invalid format (non-numeric columns)' do
|
|
412
|
+
expect {
|
|
413
|
+
described_class.new(image: temp_image, add_meta: '4:b')
|
|
414
|
+
}.to raise_error(RubySpriter::ValidationError, /Invalid --add-meta format/)
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
it 'accepts valid R:C format' do
|
|
418
|
+
# Mock ImageMagick identify for dimension validation
|
|
419
|
+
allow(Open3).to receive(:capture3).with(/identify/).and_return(
|
|
420
|
+
["800x800\n", '', instance_double(Process::Status, success?: true)]
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
allow(RubySpriter::MetadataManager).to receive(:read).with(temp_image).and_return(nil)
|
|
424
|
+
|
|
425
|
+
expect {
|
|
426
|
+
described_class.new(image: temp_image, add_meta: '4:4')
|
|
427
|
+
}.not_to raise_error
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
describe 'range validation' do
|
|
432
|
+
it 'raises error when rows is below minimum (0)' do
|
|
433
|
+
expect {
|
|
434
|
+
described_class.new(image: temp_image, add_meta: '0:4')
|
|
435
|
+
}.to raise_error(RubySpriter::ValidationError, /rows must be between 1 and 99/)
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
it 'raises error when columns is below minimum (0)' do
|
|
439
|
+
expect {
|
|
440
|
+
described_class.new(image: temp_image, add_meta: '4:0')
|
|
441
|
+
}.to raise_error(RubySpriter::ValidationError, /columns must be between 1 and 99/)
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
it 'raises error when rows exceeds maximum (100)' do
|
|
445
|
+
expect {
|
|
446
|
+
described_class.new(image: temp_image, add_meta: '100:4')
|
|
447
|
+
}.to raise_error(RubySpriter::ValidationError, /rows must be between 1 and 99/)
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
it 'raises error when total frames exceeds 999' do
|
|
451
|
+
expect {
|
|
452
|
+
described_class.new(image: temp_image, add_meta: '20:50')
|
|
453
|
+
}.to raise_error(RubySpriter::ValidationError, /Total frames \(1000\) must be less than 1000/)
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
it 'allows maximum valid frames (999)' do
|
|
457
|
+
# Mock ImageMagick identify - 3700x2700 divides evenly by 27 rows x 37 columns (100x100 per frame)
|
|
458
|
+
allow(Open3).to receive(:capture3).with(/identify/).and_return(
|
|
459
|
+
["3700x2700\n", '', instance_double(Process::Status, success?: true)]
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
allow(RubySpriter::MetadataManager).to receive(:read).with(temp_image).and_return(nil)
|
|
463
|
+
|
|
464
|
+
expect {
|
|
465
|
+
described_class.new(image: temp_image, add_meta: '27:37')
|
|
466
|
+
}.not_to raise_error
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
describe 'existing metadata handling' do
|
|
471
|
+
it 'raises error when image already has metadata' do
|
|
472
|
+
allow(RubySpriter::MetadataManager).to receive(:read).with(temp_image).and_return({
|
|
473
|
+
columns: 4,
|
|
474
|
+
rows: 4,
|
|
475
|
+
frames: 16
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
expect {
|
|
479
|
+
described_class.new(image: temp_image, add_meta: '4:4')
|
|
480
|
+
}.to raise_error(RubySpriter::ValidationError, /Image already has spritesheet metadata.*Use --overwrite-meta/)
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
it 'allows replacing metadata with --overwrite-meta' do
|
|
484
|
+
allow(RubySpriter::MetadataManager).to receive(:read).with(temp_image).and_return({
|
|
485
|
+
columns: 4,
|
|
486
|
+
rows: 4,
|
|
487
|
+
frames: 16
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
# Mock ImageMagick identify
|
|
491
|
+
allow(Open3).to receive(:capture3).with(/identify/).and_return(
|
|
492
|
+
["800x800\n", '', instance_double(Process::Status, success?: true)]
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
expect {
|
|
496
|
+
described_class.new(image: temp_image, add_meta: '8:8', overwrite_meta: true)
|
|
497
|
+
}.not_to raise_error
|
|
498
|
+
end
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
describe 'dimension validation' do
|
|
502
|
+
before do
|
|
503
|
+
allow(RubySpriter::MetadataManager).to receive(:read).with(temp_image).and_return(nil)
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
it 'raises error when image dimensions do not divide evenly' do
|
|
507
|
+
# Mock ImageMagick identify - 800x600 doesn't divide by 3x3
|
|
508
|
+
allow(Open3).to receive(:capture3).with(/identify/).and_return(
|
|
509
|
+
["800x600\n", '', instance_double(Process::Status, success?: true)]
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
expect {
|
|
513
|
+
described_class.new(image: temp_image, add_meta: '3:3')
|
|
514
|
+
}.to raise_error(RubySpriter::ValidationError, /Image dimensions \(800x600\) must divide evenly by grid \(3x3\)/)
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
it 'allows dimensions that divide evenly' do
|
|
518
|
+
# Mock ImageMagick identify - 800x800 divides by 4x4
|
|
519
|
+
allow(Open3).to receive(:capture3).with(/identify/).and_return(
|
|
520
|
+
["800x800\n", '', instance_double(Process::Status, success?: true)]
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
expect {
|
|
524
|
+
described_class.new(image: temp_image, add_meta: '4:4')
|
|
525
|
+
}.not_to raise_error
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
describe 'frame count handling' do
|
|
530
|
+
before do
|
|
531
|
+
allow(RubySpriter::MetadataManager).to receive(:read).with(temp_image).and_return(nil)
|
|
532
|
+
allow(Open3).to receive(:capture3).with(/identify/).and_return(
|
|
533
|
+
["800x800\n", '', instance_double(Process::Status, success?: true)]
|
|
534
|
+
)
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
it 'calculates frame count from rows * columns by default' do
|
|
538
|
+
processor = described_class.new(image: temp_image, add_meta: '4:4')
|
|
539
|
+
# Frame count should be 16 (4x4)
|
|
540
|
+
# We'll verify this in the workflow implementation
|
|
541
|
+
expect(processor.instance_variable_get(:@options)[:add_meta]).to eq('4:4')
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
it 'allows custom frame count with --frames for partial grids' do
|
|
545
|
+
processor = described_class.new(image: temp_image, add_meta: '4:4', frame_count: 14)
|
|
546
|
+
expect(processor.instance_variable_get(:@options)[:frame_count]).to eq(14)
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
it 'raises error when custom frame count exceeds grid size' do
|
|
550
|
+
expect {
|
|
551
|
+
described_class.new(image: temp_image, add_meta: '4:4', frame_count: 20)
|
|
552
|
+
}.to raise_error(RubySpriter::ValidationError, /Frame count \(20\) exceeds grid size \(16\)/)
|
|
553
|
+
end
|
|
554
|
+
end
|
|
555
|
+
end
|
|
556
|
+
|
|
207
557
|
describe '--split metadata priority logic' do
|
|
208
558
|
let(:temp_dir) { Dir.mktmpdir('test_split_') }
|
|
209
559
|
let(:image_file) { File.join(temp_dir, 'spritesheet.png') }
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_spriter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.7.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- scooter-indie
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-10-
|
|
11
|
+
date: 2025-10-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: |
|
|
14
14
|
Ruby Spriter is a cross-platform tool for creating spritesheets from video files
|
|
@@ -28,7 +28,9 @@ files:
|
|
|
28
28
|
- README.md
|
|
29
29
|
- bin/ruby_spriter
|
|
30
30
|
- lib/ruby_spriter.rb
|
|
31
|
+
- lib/ruby_spriter/batch_processor.rb
|
|
31
32
|
- lib/ruby_spriter/cli.rb
|
|
33
|
+
- lib/ruby_spriter/compression_manager.rb
|
|
32
34
|
- lib/ruby_spriter/consolidator.rb
|
|
33
35
|
- lib/ruby_spriter/dependency_checker.rb
|
|
34
36
|
- lib/ruby_spriter/gimp_processor.rb
|
|
@@ -48,7 +50,9 @@ files:
|
|
|
48
50
|
- spec/fixtures/spritesheet_6x2.png
|
|
49
51
|
- spec/fixtures/spritesheet_with_metadata.png
|
|
50
52
|
- spec/fixtures/test_video.mp4
|
|
53
|
+
- spec/ruby_spriter/batch_processor_spec.rb
|
|
51
54
|
- spec/ruby_spriter/cli_spec.rb
|
|
55
|
+
- spec/ruby_spriter/compression_manager_spec.rb
|
|
52
56
|
- spec/ruby_spriter/consolidator_spec.rb
|
|
53
57
|
- spec/ruby_spriter/dependency_checker_spec.rb
|
|
54
58
|
- spec/ruby_spriter/gimp_processor_spec.rb
|