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.
@@ -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.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-24 00:00:00.000000000 Z
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