ruby_spriter 0.6.5 → 0.6.6

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.
@@ -0,0 +1,385 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe RubySpriter::Processor do
6
+ describe 'default options' do
7
+ it 'sets default max_width to 320' do
8
+ processor = described_class.new
9
+ expect(processor.options[:max_width]).to eq(320)
10
+ end
11
+ end
12
+
13
+ describe 'numeric option validation' do
14
+ describe '--frames validation' do
15
+ it 'raises error when frame_count is below minimum (0)' do
16
+ expect {
17
+ described_class.new(frame_count: 0)
18
+ }.to raise_error(RubySpriter::ValidationError, /frame_count must be between 1 and 10000/)
19
+ end
20
+
21
+ it 'raises error when frame_count is above maximum (10001)' do
22
+ expect {
23
+ described_class.new(frame_count: 10001)
24
+ }.to raise_error(RubySpriter::ValidationError, /frame_count must be between 1 and 10000/)
25
+ end
26
+ end
27
+
28
+ describe '--columns validation' do
29
+ it 'raises error when columns is below minimum (0)' do
30
+ expect {
31
+ described_class.new(columns: 0)
32
+ }.to raise_error(RubySpriter::ValidationError, /columns must be between 1 and 100/)
33
+ end
34
+
35
+ it 'raises error when columns is above maximum (101)' do
36
+ expect {
37
+ described_class.new(columns: 101)
38
+ }.to raise_error(RubySpriter::ValidationError, /columns must be between 1 and 100/)
39
+ end
40
+ end
41
+
42
+ describe '--width validation' do
43
+ it 'raises error when max_width is below minimum (0)' do
44
+ expect {
45
+ described_class.new(max_width: 0)
46
+ }.to raise_error(RubySpriter::ValidationError, /max_width must be between 1 and 1920/)
47
+ end
48
+
49
+ it 'raises error when max_width is above maximum (1921)' do
50
+ expect {
51
+ described_class.new(max_width: 1921)
52
+ }.to raise_error(RubySpriter::ValidationError, /max_width must be between 1 and 1920/)
53
+ end
54
+ end
55
+
56
+ describe '--scale validation' do
57
+ it 'raises error when scale_percent is below minimum (0)' do
58
+ expect {
59
+ described_class.new(scale_percent: 0)
60
+ }.to raise_error(RubySpriter::ValidationError, /scale_percent must be between 1 and 500/)
61
+ end
62
+
63
+ it 'raises error when scale_percent is above maximum (501)' do
64
+ expect {
65
+ described_class.new(scale_percent: 501)
66
+ }.to raise_error(RubySpriter::ValidationError, /scale_percent must be between 1 and 500/)
67
+ end
68
+ end
69
+
70
+ describe '--grow validation' do
71
+ it 'raises error when grow_selection is below minimum (-1)' do
72
+ expect {
73
+ described_class.new(grow_selection: -1)
74
+ }.to raise_error(RubySpriter::ValidationError, /grow_selection must be between 0 and 100/)
75
+ end
76
+
77
+ it 'raises error when grow_selection is above maximum (101)' do
78
+ expect {
79
+ described_class.new(grow_selection: 101)
80
+ }.to raise_error(RubySpriter::ValidationError, /grow_selection must be between 0 and 100/)
81
+ end
82
+ end
83
+
84
+ describe '--sharpen-radius validation' do
85
+ it 'raises error when sharpen_radius is below minimum (0.0)' do
86
+ expect {
87
+ described_class.new(sharpen_radius: 0.0)
88
+ }.to raise_error(RubySpriter::ValidationError, /sharpen_radius must be between 0.1 and 100.0/)
89
+ end
90
+
91
+ it 'raises error when sharpen_radius is above maximum (100.1)' do
92
+ expect {
93
+ described_class.new(sharpen_radius: 100.1)
94
+ }.to raise_error(RubySpriter::ValidationError, /sharpen_radius must be between 0.1 and 100.0/)
95
+ end
96
+ end
97
+
98
+ describe '--sharpen-gain validation' do
99
+ it 'raises error when sharpen_gain is below minimum (-0.1)' do
100
+ expect {
101
+ described_class.new(sharpen_gain: -0.1)
102
+ }.to raise_error(RubySpriter::ValidationError, /sharpen_gain must be between 0.0 and 10.0/)
103
+ end
104
+
105
+ it 'raises error when sharpen_gain is above maximum (10.1)' do
106
+ expect {
107
+ described_class.new(sharpen_gain: 10.1)
108
+ }.to raise_error(RubySpriter::ValidationError, /sharpen_gain must be between 0.0 and 10.0/)
109
+ end
110
+ end
111
+
112
+ describe '--sharpen-threshold validation' do
113
+ it 'raises error when sharpen_threshold is below minimum (-0.1)' do
114
+ expect {
115
+ described_class.new(sharpen_threshold: -0.1)
116
+ }.to raise_error(RubySpriter::ValidationError, /sharpen_threshold must be between 0.0 and 1.0/)
117
+ end
118
+
119
+ it 'raises error when sharpen_threshold is above maximum (1.1)' do
120
+ expect {
121
+ described_class.new(sharpen_threshold: 1.1)
122
+ }.to raise_error(RubySpriter::ValidationError, /sharpen_threshold must be between 0.0 and 1.0/)
123
+ end
124
+ end
125
+
126
+ describe '--threshold validation' do
127
+ it 'raises error when bg_threshold is below minimum (-0.1)' do
128
+ expect {
129
+ described_class.new(bg_threshold: -0.1)
130
+ }.to raise_error(RubySpriter::ValidationError, /bg_threshold must be between 0.0 and 100.0/)
131
+ end
132
+
133
+ it 'raises error when bg_threshold is above maximum (100.1)' do
134
+ expect {
135
+ described_class.new(bg_threshold: 100.1)
136
+ }.to raise_error(RubySpriter::ValidationError, /bg_threshold must be between 0.0 and 100.0/)
137
+ end
138
+ end
139
+ end
140
+
141
+ describe '--split validation' do
142
+ describe 'format validation' do
143
+ it 'raises error for invalid split format (missing colon)' do
144
+ expect {
145
+ described_class.new(image: 'test.png', split: '44')
146
+ }.to raise_error(RubySpriter::ValidationError, /Invalid --split format/)
147
+ end
148
+
149
+ it 'raises error for invalid split format (non-numeric rows)' do
150
+ expect {
151
+ described_class.new(image: 'test.png', split: 'a:4')
152
+ }.to raise_error(RubySpriter::ValidationError, /Invalid --split format/)
153
+ end
154
+
155
+ it 'raises error for invalid split format (non-numeric columns)' do
156
+ expect {
157
+ described_class.new(image: 'test.png', split: '4:b')
158
+ }.to raise_error(RubySpriter::ValidationError, /Invalid --split format/)
159
+ end
160
+ end
161
+
162
+ describe 'range validation' do
163
+ it 'raises error when rows is below minimum (0)' do
164
+ expect {
165
+ described_class.new(image: 'test.png', split: '0:4')
166
+ }.to raise_error(RubySpriter::ValidationError, /rows must be between 1 and 99/)
167
+ end
168
+
169
+ it 'raises error when rows is above maximum (100)' do
170
+ expect {
171
+ described_class.new(image: 'test.png', split: '100:4')
172
+ }.to raise_error(RubySpriter::ValidationError, /rows must be between 1 and 99/)
173
+ end
174
+
175
+ it 'raises error when columns is below minimum (0)' do
176
+ expect {
177
+ described_class.new(image: 'test.png', split: '4:0')
178
+ }.to raise_error(RubySpriter::ValidationError, /columns must be between 1 and 99/)
179
+ end
180
+
181
+ it 'raises error when columns is above maximum (100)' do
182
+ expect {
183
+ described_class.new(image: 'test.png', split: '4:100')
184
+ }.to raise_error(RubySpriter::ValidationError, /columns must be between 1 and 99/)
185
+ end
186
+
187
+ it 'raises error when total frames >= 1000' do
188
+ expect {
189
+ described_class.new(image: 'test.png', split: '32:32')
190
+ }.to raise_error(RubySpriter::ValidationError, /Total frames \(1024\) must be less than 1000/)
191
+ end
192
+
193
+ it 'raises error when total frames equals 1000' do
194
+ expect {
195
+ described_class.new(image: 'test.png', split: '20:50')
196
+ }.to raise_error(RubySpriter::ValidationError, /Total frames \(1000\) must be less than 1000/)
197
+ end
198
+
199
+ it 'allows maximum valid frames (999)' do
200
+ expect {
201
+ described_class.new(image: 'test.png', split: '27:37')
202
+ }.not_to raise_error
203
+ end
204
+ end
205
+ end
206
+
207
+ describe '--split metadata priority logic' do
208
+ let(:temp_dir) { Dir.mktmpdir('test_split_') }
209
+ let(:image_file) { File.join(temp_dir, 'spritesheet.png') }
210
+
211
+ before do
212
+ FileUtils.touch(image_file)
213
+
214
+ allow_any_instance_of(RubySpriter::DependencyChecker).to receive(:check_all).and_return({
215
+ ffmpeg: { available: true },
216
+ ffprobe: { available: true },
217
+ imagemagick: { available: true },
218
+ gimp: { available: true }
219
+ })
220
+ allow_any_instance_of(RubySpriter::DependencyChecker).to receive(:gimp_path).and_return('/usr/bin/gimp')
221
+ end
222
+
223
+ after do
224
+ FileUtils.rm_rf(temp_dir)
225
+ end
226
+
227
+ context 'when image has metadata' do
228
+ before do
229
+ allow(RubySpriter::MetadataManager).to receive(:read).with(image_file).and_return({
230
+ columns: 4,
231
+ rows: 4,
232
+ frames: 16
233
+ })
234
+ end
235
+
236
+ it 'uses metadata when --split not provided' do
237
+ processor = described_class.new(image: image_file, save_frames: true)
238
+
239
+ splitter = instance_double(RubySpriter::Utils::SpritesheetSplitter)
240
+ allow(RubySpriter::Utils::SpritesheetSplitter).to receive(:new).and_return(splitter)
241
+ expect(splitter).to receive(:split_into_frames).with(image_file, anything, 4, 4, 16)
242
+
243
+ processor.run
244
+ end
245
+
246
+ it 'warns and uses metadata when --split provided without --override-md' do
247
+ processor = described_class.new(image: image_file, split: '5:5')
248
+
249
+ splitter = instance_double(RubySpriter::Utils::SpritesheetSplitter)
250
+ allow(RubySpriter::Utils::SpritesheetSplitter).to receive(:new).and_return(splitter)
251
+ expect(splitter).to receive(:split_into_frames).with(image_file, anything, 4, 4, 16)
252
+
253
+ expect(RubySpriter::Utils::OutputFormatter).to receive(:note).with(/Image has metadata.*Your --split values will be ignored/)
254
+
255
+ processor.run
256
+ end
257
+
258
+ it 'uses --split when --override-md provided' do
259
+ processor = described_class.new(image: image_file, split: '5:5', override_md: true)
260
+
261
+ # Mock ImageMagick identify for dimension validation
262
+ allow(Open3).to receive(:capture3).with(/identify/).and_return(
263
+ ["500x500\n", '', instance_double(Process::Status, success?: true)]
264
+ )
265
+
266
+ splitter = instance_double(RubySpriter::Utils::SpritesheetSplitter)
267
+ allow(RubySpriter::Utils::SpritesheetSplitter).to receive(:new).and_return(splitter)
268
+ expect(splitter).to receive(:split_into_frames).with(image_file, anything, 5, 5, 25)
269
+
270
+ processor.run
271
+ end
272
+ end
273
+
274
+ context 'when image has no metadata' do
275
+ before do
276
+ allow(RubySpriter::MetadataManager).to receive(:read).with(image_file).and_return(nil)
277
+ end
278
+
279
+ it 'uses --split values when provided' do
280
+ processor = described_class.new(image: image_file, split: '6:6')
281
+
282
+ # Mock ImageMagick identify for dimension validation
283
+ allow(Open3).to receive(:capture3).with(/identify/).and_return(
284
+ ["600x600\n", '', instance_double(Process::Status, success?: true)]
285
+ )
286
+
287
+ splitter = instance_double(RubySpriter::Utils::SpritesheetSplitter)
288
+ allow(RubySpriter::Utils::SpritesheetSplitter).to receive(:new).and_return(splitter)
289
+ expect(splitter).to receive(:split_into_frames).with(image_file, anything, 6, 6, 36)
290
+
291
+ processor.run
292
+ end
293
+
294
+ it 'raises error when --split not provided' do
295
+ processor = described_class.new(image: image_file, save_frames: true)
296
+
297
+ expect {
298
+ processor.run
299
+ }.to raise_error(RubySpriter::ValidationError, /Image has no metadata.*Please provide --split/)
300
+ end
301
+ end
302
+ end
303
+
304
+ describe 'frame extraction with --save-frames' do
305
+ let(:temp_dir) { Dir.mktmpdir('test_') }
306
+ let(:video_file) { File.join(temp_dir, 'test.mp4') }
307
+ let(:spritesheet_file) { File.join(temp_dir, 'spritesheet.png') }
308
+
309
+ before do
310
+ FileUtils.touch(video_file)
311
+ FileUtils.touch(spritesheet_file)
312
+
313
+ allow_any_instance_of(RubySpriter::DependencyChecker).to receive(:check_all).and_return({
314
+ ffmpeg: { available: true },
315
+ ffprobe: { available: true },
316
+ imagemagick: { available: true },
317
+ gimp: { available: true }
318
+ })
319
+ allow_any_instance_of(RubySpriter::DependencyChecker).to receive(:gimp_path).and_return('/usr/bin/gimp')
320
+ end
321
+
322
+ after do
323
+ FileUtils.rm_rf(temp_dir)
324
+ end
325
+
326
+ it 'splits spritesheet into frames after video processing when save_frames is true' do
327
+ processor = described_class.new(video: video_file, save_frames: true)
328
+
329
+ # The output file will be generated from video filename
330
+ expected_output = File.join(temp_dir, 'test_spritesheet.png')
331
+
332
+ video_processor = instance_double(RubySpriter::VideoProcessor)
333
+ allow(RubySpriter::VideoProcessor).to receive(:new).and_return(video_processor)
334
+ allow(video_processor).to receive(:create_spritesheet).and_return({
335
+ output_file: expected_output,
336
+ columns: 4,
337
+ rows: 4,
338
+ frames: 16
339
+ })
340
+
341
+ splitter = instance_double(RubySpriter::Utils::SpritesheetSplitter)
342
+ allow(RubySpriter::Utils::SpritesheetSplitter).to receive(:new).and_return(splitter)
343
+ expect(splitter).to receive(:split_into_frames).with(expected_output, anything, 4, 4, 16)
344
+
345
+ processor.run
346
+ end
347
+
348
+ it 'splits spritesheet into frames after image processing when save_frames is true' do
349
+ processor = described_class.new(image: spritesheet_file, save_frames: true)
350
+
351
+ # Mock metadata reading
352
+ allow(RubySpriter::MetadataManager).to receive(:read).with(spritesheet_file).and_return({
353
+ columns: 4,
354
+ rows: 4,
355
+ frames: 16
356
+ })
357
+
358
+ splitter = instance_double(RubySpriter::Utils::SpritesheetSplitter)
359
+ allow(RubySpriter::Utils::SpritesheetSplitter).to receive(:new).and_return(splitter)
360
+ expect(splitter).to receive(:split_into_frames).with(spritesheet_file, anything, 4, 4, 16)
361
+
362
+ processor.run
363
+ end
364
+
365
+ it 'does not split frames when save_frames is false' do
366
+ processor = described_class.new(video: video_file, save_frames: false)
367
+
368
+ # The output file will be generated from video filename
369
+ expected_output = File.join(temp_dir, 'test_spritesheet.png')
370
+
371
+ video_processor = instance_double(RubySpriter::VideoProcessor)
372
+ allow(RubySpriter::VideoProcessor).to receive(:new).and_return(video_processor)
373
+ allow(video_processor).to receive(:create_spritesheet).and_return({
374
+ output_file: expected_output,
375
+ columns: 4,
376
+ rows: 4,
377
+ frames: 16
378
+ })
379
+
380
+ expect(RubySpriter::Utils::SpritesheetSplitter).not_to receive(:new)
381
+
382
+ processor.run
383
+ end
384
+ end
385
+ end
@@ -62,10 +62,89 @@ RSpec.describe RubySpriter::Utils::FileHelper do
62
62
  it 'validates file exists and is readable' do
63
63
  file = File.join(@test_dir, 'test.txt')
64
64
  File.write(file, 'test')
65
-
65
+
66
66
  expect {
67
67
  described_class.validate_readable!(file)
68
68
  }.not_to raise_error
69
69
  end
70
70
  end
71
+
72
+ describe '.unique_filename' do
73
+ it 'returns original filename when file does not exist' do
74
+ result = described_class.unique_filename('/path/to/output.png')
75
+ expect(result).to eq('/path/to/output.png')
76
+ end
77
+
78
+ it 'adds timestamp when file exists' do
79
+ file = File.join(@test_dir, 'existing.png')
80
+ File.write(file, 'test')
81
+
82
+ result = described_class.unique_filename(file)
83
+ expect(result).to match(/existing_\d{8}_\d{6}_\d{3}\.png$/)
84
+ expect(result).not_to eq(file)
85
+ end
86
+
87
+ it 'handles files with multiple dots in name' do
88
+ file = File.join(@test_dir, 'my.sprite.sheet.png')
89
+ File.write(file, 'test')
90
+
91
+ result = described_class.unique_filename(file)
92
+ expect(result).to match(/my\.sprite\.sheet_\d{8}_\d{6}_\d{3}\.png$/)
93
+ end
94
+
95
+ it 'preserves directory path' do
96
+ file = File.join(@test_dir, 'subdir', 'output.png')
97
+ FileUtils.mkdir_p(File.dirname(file))
98
+ File.write(file, 'test')
99
+
100
+ result = described_class.unique_filename(file)
101
+ expect(result).to start_with(File.join(@test_dir, 'subdir'))
102
+ end
103
+
104
+ it 'generates different filenames for consecutive calls' do
105
+ file = File.join(@test_dir, 'test.png')
106
+ File.write(file, 'test')
107
+
108
+ result1 = described_class.unique_filename(file)
109
+ sleep(0.01) # Small delay to ensure different timestamps
110
+ result2 = described_class.unique_filename(file)
111
+
112
+ expect(result1).not_to eq(result2)
113
+ end
114
+ end
115
+
116
+ describe '.ensure_unique_output' do
117
+ it 'returns original path when overwrite is true' do
118
+ file = File.join(@test_dir, 'output.png')
119
+ File.write(file, 'test')
120
+
121
+ result = described_class.ensure_unique_output(file, overwrite: true)
122
+ expect(result).to eq(file)
123
+ end
124
+
125
+ it 'returns original path when file does not exist and overwrite is false' do
126
+ file = File.join(@test_dir, 'new_output.png')
127
+
128
+ result = described_class.ensure_unique_output(file, overwrite: false)
129
+ expect(result).to eq(file)
130
+ end
131
+
132
+ it 'returns unique filename when file exists and overwrite is false' do
133
+ file = File.join(@test_dir, 'existing_output.png')
134
+ File.write(file, 'test')
135
+
136
+ result = described_class.ensure_unique_output(file, overwrite: false)
137
+ expect(result).to match(/existing_output_\d{8}_\d{6}_\d{3}\.png$/)
138
+ expect(result).not_to eq(file)
139
+ end
140
+
141
+ it 'defaults to overwrite false when not specified' do
142
+ file = File.join(@test_dir, 'default_test.png')
143
+ File.write(file, 'test')
144
+
145
+ result = described_class.ensure_unique_output(file)
146
+ expect(result).not_to eq(file)
147
+ expect(result).to match(/default_test_\d{8}_\d{6}_\d{3}\.png$/)
148
+ end
149
+ end
71
150
  end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe RubySpriter::Utils::SpritesheetSplitter do
6
+ describe '#split_into_frames' do
7
+ let(:spritesheet_file) { 'spritesheet.png' }
8
+ let(:output_dir) { '/tmp/frames' }
9
+ let(:columns) { 4 }
10
+ let(:rows) { 4 }
11
+ let(:frames) { 16 }
12
+ let(:tile_width) { 100 }
13
+ let(:tile_height) { 100 }
14
+
15
+ let(:splitter) { described_class.new }
16
+
17
+ before do
18
+ allow(FileUtils).to receive(:mkdir_p)
19
+ allow(File).to receive(:exist?).and_return(true)
20
+ allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
21
+
22
+ # Mock ImageMagick identify to return dimensions
23
+ allow(Open3).to receive(:capture3).with(/identify/).and_return(
24
+ ["400x400\n", '', instance_double(Process::Status, success?: true)]
25
+ )
26
+ end
27
+
28
+ it 'creates output directory for frames' do
29
+ expect(FileUtils).to receive(:mkdir_p).with(output_dir)
30
+
31
+ splitter.split_into_frames(spritesheet_file, output_dir, columns, rows, frames)
32
+ end
33
+
34
+ it 'extracts each frame with FR prefix and zero-padded numbers' do
35
+ # Expect ImageMagick convert commands for each frame
36
+ expect(Open3).to receive(:capture3).with(/identify/).once.and_return(
37
+ ["400x400\n", '', instance_double(Process::Status, success?: true)]
38
+ )
39
+
40
+ (1..frames).each do |i|
41
+ expect(Open3).to receive(:capture3).with(/convert.*FR#{format('%03d', i)}_/).and_return(
42
+ ['', '', instance_double(Process::Status, success?: true)]
43
+ )
44
+ end
45
+
46
+ splitter.split_into_frames(spritesheet_file, output_dir, columns, rows, frames)
47
+ end
48
+
49
+ it 'calculates tile dimensions from spritesheet size and grid' do
50
+ # For 400x400 image with 4x4 grid, tiles should be 100x100
51
+ expect(Open3).to receive(:capture3).with(/identify/).and_return(
52
+ ["400x400\n", '', instance_double(Process::Status, success?: true)]
53
+ )
54
+
55
+ # Check that crop parameters use 100x100
56
+ expect(Open3).to receive(:capture3).with(/100x100\+0\+0/).and_return(
57
+ ['', '', instance_double(Process::Status, success?: true)]
58
+ )
59
+
60
+ allow(Open3).to receive(:capture3).and_return(['', '', instance_double(Process::Status, success?: true)])
61
+
62
+ splitter.split_into_frames(spritesheet_file, output_dir, columns, rows, 1)
63
+ end
64
+
65
+ it 'includes spritesheet basename in frame output names' do
66
+ basename = File.basename(spritesheet_file, '.*')
67
+
68
+ # Mock identify first
69
+ allow(Open3).to receive(:capture3).with(/identify/).and_return(
70
+ ["400x400\n", '', instance_double(Process::Status, success?: true)]
71
+ )
72
+
73
+ # Expect convert with frame filename
74
+ expect(Open3).to receive(:capture3).with(/FR001_#{basename}\.png/).and_return(
75
+ ['', '', instance_double(Process::Status, success?: true)]
76
+ )
77
+
78
+ splitter.split_into_frames(spritesheet_file, output_dir, columns, rows, 1)
79
+ end
80
+
81
+ it 'raises ProcessingError when ImageMagick fails' do
82
+ allow(Open3).to receive(:capture3).with(/identify/).and_return(
83
+ ["400x400\n", '', instance_double(Process::Status, success?: true)]
84
+ )
85
+
86
+ allow(Open3).to receive(:capture3).with(/convert/).and_return(
87
+ ['', 'Error message', instance_double(Process::Status, success?: false)]
88
+ )
89
+
90
+ expect {
91
+ splitter.split_into_frames(spritesheet_file, output_dir, columns, rows, 1)
92
+ }.to raise_error(RubySpriter::ProcessingError, /Could not extract frame/)
93
+ end
94
+
95
+ it 'displays progress information' do
96
+ expect(RubySpriter::Utils::OutputFormatter).to receive(:header).with(/Extracting Frames/)
97
+ expect(RubySpriter::Utils::OutputFormatter).to receive(:indent).with(/Splitting spritesheet into #{frames} frames/)
98
+ expect(RubySpriter::Utils::OutputFormatter).to receive(:indent).with(/Output directory:/)
99
+ expect(RubySpriter::Utils::OutputFormatter).to receive(:indent).with(/Frames extracted successfully/)
100
+
101
+ splitter.split_into_frames(spritesheet_file, output_dir, columns, rows, frames)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe RubySpriter::VideoProcessor do
6
+ describe '#create_spritesheet' do
7
+ let(:video_file) { 'test_video.mp4' }
8
+ let(:output_file) { 'spritesheet.png' }
9
+ let(:processor) { described_class.new(frame_count: 16, columns: 4) }
10
+
11
+ before do
12
+ allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
13
+ allow(File).to receive(:size).and_return(1000)
14
+ allow(File).to receive(:exist?).and_return(true)
15
+ allow(File).to receive(:delete)
16
+ allow(RubySpriter::MetadataManager).to receive(:embed)
17
+ allow(Open3).to receive(:capture3).and_return(['2.0', '', instance_double(Process::Status, success?: true)])
18
+ end
19
+
20
+ it 'creates spritesheet from video file' do
21
+ result = processor.create_spritesheet(video_file, output_file)
22
+
23
+ expect(result[:output_file]).to eq(output_file)
24
+ expect(result[:columns]).to eq(4)
25
+ expect(result[:rows]).to eq(4)
26
+ expect(result[:frames]).to eq(16)
27
+ end
28
+ end
29
+ end
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.5
4
+ version: 0.6.6
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-23 00:00:00.000000000 Z
11
+ date: 2025-10-24 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
@@ -38,6 +38,7 @@ files:
38
38
  - lib/ruby_spriter/utils/file_helper.rb
39
39
  - lib/ruby_spriter/utils/output_formatter.rb
40
40
  - lib/ruby_spriter/utils/path_helper.rb
41
+ - lib/ruby_spriter/utils/spritesheet_splitter.rb
41
42
  - lib/ruby_spriter/version.rb
42
43
  - lib/ruby_spriter/video_processor.rb
43
44
  - ruby_spriter.gemspec
@@ -57,6 +58,7 @@ files:
57
58
  - spec/ruby_spriter/utils/file_helper_spec.rb
58
59
  - spec/ruby_spriter/utils/output_formatter_spec.rb
59
60
  - spec/ruby_spriter/utils/path_helper_spec.rb
61
+ - spec/ruby_spriter/utils/spritesheet_splitter_spec.rb
60
62
  - spec/ruby_spriter/video_processor_spec.rb
61
63
  - spec/spec_helper.rb
62
64
  homepage: https://github.com/scooter-indie/ruby-spriter