rays-video 0.2.0 → 0.3.0

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.
data/test/test_video.rb CHANGED
@@ -10,8 +10,32 @@ class TestVideo < Test::Unit::TestCase
10
10
  Rays::Video.new(w, h, fps: fps, pixel_density: pd)
11
11
  end
12
12
 
13
- def image(w = 10, h = 10)
14
- Rays::Image.new(w, h)
13
+ def image(gray = 0)
14
+ Rays::Image.new(10, 10).paint {fill gray / 255.0; rect 0, 0, 10, 10}
15
+ end
16
+
17
+ def gray(image)
18
+ (image[5, 5].red * 255).round
19
+ end
20
+
21
+ def grays(video)
22
+ video.map {|image| gray image}
23
+ end
24
+
25
+ def color(*args)
26
+ Rays::Color.new(*args)
27
+ end
28
+
29
+ def load_video(colors, ext = 'gif', &block)
30
+ tmpdir do |dir|
31
+ v = video
32
+ v.append(*colors.map {|c|
33
+ Rays::Image.new(10, 10).paint {fill(*c); rect 0, 0, 10, 10}
34
+ })
35
+ path = File.join dir, "test.#{ext}"
36
+ v.save path
37
+ block.call Rays::Video.load(path)
38
+ end
15
39
  end
16
40
 
17
41
  def test_initialize()
@@ -42,28 +66,60 @@ class TestVideo < Test::Unit::TestCase
42
66
  end
43
67
 
44
68
  def test_insert()
45
- v = video; assert_equal [], v.map(&:width)
46
- v.append image(1), image(2); assert_equal [1, 2], v.map(&:width)
47
- v.insert 1, image(3); assert_equal [1, 3, 2], v.map(&:width)
48
- v.insert 2, image(4), image(5); assert_equal [1, 3, 4, 5, 2], v.map(&:width)
49
-
69
+ v = video; assert_equal [], grays(v)
70
+ v.append image(10), image(20); assert_equal [10, 20], grays(v)
71
+ v.insert 1, image(30); assert_equal [10, 30, 20], grays(v)
72
+ v.insert 2, image(40), image(50); assert_equal [10, 30, 40, 50, 20], grays(v)
73
+ v.insert 5, image(60); assert_equal [10, 30, 40, 50, 20, 60], grays(v)
74
+
75
+ assert_raise(IndexError) {v.insert 7, image}
76
+ assert_raise(ArgumentError) {v.insert 0, Rays::Image.new(10, 5)}
77
+ assert_raise(ArgumentError) {v.insert 0, Rays::Image.new(5, 10)}
78
+ assert_raise(ArgumentError) {v.insert 0, Rays::Image.new(10, 10, pixel_density: 2)}
79
+ end
80
+
81
+ def test_insert_with_pixel_density()
82
+ v = video 10, 10, 30, 2
83
+ v.append Rays::Image.new(10, 10, pixel_density: 2)
84
+ assert_equal 1, v.size
85
+
86
+ assert_raise(ArgumentError) {v.append Rays::Image.new(10, 10, pixel_density: 1)}
87
+ assert_raise(ArgumentError) {v.append Rays::Image.new(20, 20, pixel_density: 2)}
50
88
  end
51
89
 
52
90
  def test_append()
53
- v = video; assert_equal [], v.map(&:width)
54
- v.append image(1); assert_equal [1], v.map(&:width)
55
- v.append image(2), image(3); assert_equal [1, 2, 3], v.map(&:width)
56
-
91
+ v = video; assert_equal [], grays(v)
92
+ v.append image(10); assert_equal [10], grays(v)
93
+ v.append image(20), image(30); assert_equal [10, 20, 30], grays(v)
94
+ v.push image(40); assert_equal [10, 20, 30, 40], grays(v)
95
+ v << image(50) << image(60); assert_equal [10, 20, 30, 40, 50, 60], grays(v)
96
+
97
+ assert_same v, v.append(image)
98
+ assert_same v, v.push(image)
99
+ assert_same v, v << image
57
100
  end
58
101
 
59
102
  def test_remove()
60
- v = video; assert_equal [], v.map(&:width)
61
- v.append image(1), image(2), image(3); assert_equal [1, 2, 3], v.map(&:width)
62
- v.remove 1; assert_equal [1, 3], v.map(&:width)
103
+ v = video; assert_equal [], grays(v)
104
+ v.append image(10), image(20), image(30); assert_equal [10, 20, 30], grays(v)
105
+ v.remove 1; assert_equal [10, 30], grays(v)
63
106
 
64
107
  assert_raise(NotImplementedError) {v.remove 1..}
65
108
  end
66
109
 
110
+ def test_set_at()
111
+ v = video
112
+ v.append image(10), image(20), image(30)
113
+ v[1] = image(40); assert_equal [10, 40, 30], grays(v)
114
+ v[0] = image(50); assert_equal [50, 40, 30], grays(v)
115
+ v[2] = image(60); assert_equal [50, 40, 60], grays(v)
116
+
117
+ assert_raise(IndexError) {v[3] = image}
118
+ assert_raise(RangeError) {v[-1] = image}
119
+ assert_raise(IndexError) {video[0] = image}
120
+ assert_raise(ArgumentError) {v[0] = Rays::Image.new(5, 5)}
121
+ end
122
+
67
123
  def test_size()
68
124
  v = video; assert_equal 0, v.size
69
125
  v.append image, image; assert_equal 2, v.size
@@ -85,29 +141,29 @@ class TestVideo < Test::Unit::TestCase
85
141
 
86
142
  def test_each()
87
143
  v = video
88
- v.append image(1), image(2), image(3)
144
+ v.append image(10), image(20), image(30)
89
145
 
90
146
  assert_equal(
91
- [[0, 1], [1, 2], [2, 3]],
92
- v.map.with_index {|image, index| [index, image.width]})
147
+ [[0, 10], [1, 20], [2, 30]],
148
+ v.map.with_index {|image, index| [index, gray(image)]})
93
149
  end
94
150
 
95
151
  def test_to_image()
96
- v = video.tap {_1.append image(1), image(2), image(3)}
97
- assert_equal 1, v.to_image.width
98
- v.pos = 1; assert_equal 2, v.to_image.width
99
- v.pos = 2; assert_equal 3, v.to_image.width
100
- v.pos = 3; assert_equal 3, v.to_image.width
101
- v.pos = 9; assert_equal 3, v.to_image.width
152
+ v = video.tap {_1.append image(10), image(20), image(30)}
153
+ assert_equal 10, gray(v.to_image)
154
+ v.pos = 1; assert_equal 20, gray(v.to_image)
155
+ v.pos = 2; assert_equal 30, gray(v.to_image)
156
+ v.pos = 3; assert_equal 30, gray(v.to_image)
157
+ v.pos = 9; assert_equal 30, gray(v.to_image)
102
158
  end
103
159
 
104
160
  def test_at()
105
161
  v = video
106
- v.append image(1), image(2), image(3)
107
- assert_equal 1, v[0].width
108
- assert_equal 3, v[2].width
162
+ v.append image(10), image(20), image(30)
163
+ assert_equal 10, gray(v[0])
164
+ assert_equal 30, gray(v[2])
109
165
 
110
- assert_raise(IndexError) {v[-1]}
166
+ assert_raise(RangeError) {v[-1]}
111
167
  assert_raise(IndexError) {v[3]}
112
168
  end
113
169
 
@@ -157,6 +213,87 @@ class TestVideo < Test::Unit::TestCase
157
213
  end
158
214
  end
159
215
 
216
+ def test_load_mp4_frames_in_any_order()
217
+ colors = 40.times.map {|i| [0, 0, 0].tap {_1[i % 3] = 1}}
218
+ load_video colors, 'mp4' do |v|
219
+ # sequential, skip forward, seek back, jump to the end, back to the head
220
+ [0, 1, 2, 10, 5, 39, 0, 20].each do |i|
221
+ rgb = v[i][5, 5].to_a[0, 3]
222
+ assert_equal i % 3, rgb.index(rgb.max), "frame #{i}: #{rgb}"
223
+ end
224
+ end
225
+ end
226
+
227
+ def test_load_frame_is_readonly()
228
+ load_video [[1, 0, 0], [0, 1, 0]] do |v|
229
+ f = v[0]
230
+ assert_true f.frozen?
231
+ assert_equal color(1, 0, 0, 1), f[5, 5]
232
+ assert_raise(FrozenError) {f.paint {}}
233
+ assert_raise(FrozenError) {f[5, 5] = color(0, 0, 1, 1)}
234
+
235
+ d = f.dup
236
+ assert_false d.frozen?
237
+ d[5, 5] = color(0, 0, 1, 1)
238
+ assert_equal color(0, 0, 1, 1), d[5, 5]
239
+ assert_equal color(1, 0, 0, 1), f[5, 5]
240
+ end
241
+ end
242
+
243
+ def test_load_frame_keeps_its_own_index()
244
+ load_video [[1, 0, 0], [0, 1, 0]] do |v|
245
+ a, b = v[0], v[1]
246
+ assert_equal color(1, 0, 0, 1), a[5, 5]
247
+ assert_equal color(0, 1, 0, 1), b[5, 5]
248
+ assert_equal color(1, 0, 0, 1), a[5, 5]
249
+
250
+ v.pos = 1
251
+ assert_equal color(0, 1, 0, 1), v.to_image[5, 5]
252
+ assert_equal color(1, 0, 0, 1), a[5, 5]
253
+ end
254
+ end
255
+
256
+ def test_load_transparent_frame_replaces_previous_pixels()
257
+ load_video [[1, 0, 0], [0, 0, 0, 0]] do |v|
258
+ assert_equal color(1, 0, 0, 1), v[0][5, 5]
259
+ assert_equal color(0, 0, 0, 0), v[1][5, 5]
260
+ assert_equal color(1, 0, 0, 1), v[0][5, 5]
261
+ end
262
+ end
263
+
264
+ def test_load_frame_replaced_by_dup_is_writable()
265
+ load_video [[1, 0, 0], [0, 1, 0]] do |v|
266
+ v[1] = v[1].dup
267
+ assert_false v[1].frozen?
268
+
269
+ v[1].paint {fill 0, 0, 1; rect 0, 0, 10, 10}
270
+ v.pos = 1
271
+ assert_equal color(0, 0, 1, 1), v.to_image[5, 5]
272
+ assert_equal color(1, 0, 0, 1), v[0][5, 5]
273
+ end
274
+ end
275
+
276
+ def test_load_frames_appended_to_another_video()
277
+ load_video [[1, 0, 0], [0, 1, 0]] do |v|
278
+ w = video
279
+ w.append v[1], v[0]
280
+ assert_true w[0].frozen?
281
+ assert_equal color(0, 1, 0, 1), w[0][5, 5]
282
+ assert_equal color(1, 0, 0, 1), w[1][5, 5]
283
+ assert_equal color(1, 0, 0, 1), v[0][5, 5]
284
+ end
285
+ end
286
+
287
+ def test_load_dup_plays_independently()
288
+ load_video [[1, 0, 0], [0, 1, 0]] do |v|
289
+ w = v.dup
290
+ v.pos, w.pos = 0, 1
291
+ assert_equal color(1, 0, 0, 1), v.to_image[5, 5]
292
+ assert_equal color(0, 1, 0, 1), w.to_image[5, 5]
293
+ assert_equal color(1, 0, 0, 1), v.to_image[5, 5]
294
+ end
295
+ end
296
+
160
297
  def test_exts()
161
298
  assert_include Rays::Video.exts, 'mp4'
162
299
  assert_include Rays::Video.exts, 'gif'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rays-video
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - xordog
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-04 00:00:00.000000000 Z
11
+ date: 2026-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: xot
@@ -16,56 +16,56 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.4.0
19
+ version: 0.5.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.4.0
26
+ version: 0.5.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rucy
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.4.0
33
+ version: 0.5.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.4.0
40
+ version: 0.5.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: beeps
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 0.4.0
47
+ version: 0.5.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 0.4.0
54
+ version: 0.5.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rays
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 0.4.0
61
+ version: 0.5.0
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 0.4.0
68
+ version: 0.5.0
69
69
  description: Video encoding/decoding with audio support using Rays and Beeps.
70
70
  email: xordog@gmail.com
71
71
  executables: []
@@ -81,10 +81,10 @@ files:
81
81
  - ".github/workflows/release-gem.yml"
82
82
  - ".github/workflows/tag.yml"
83
83
  - ".github/workflows/test.yml"
84
- - ".github/workflows/utils.rb"
85
84
  - CONTRIBUTING.md
86
85
  - ChangeLog.md
87
86
  - Gemfile
87
+ - Gemfile.lock
88
88
  - LICENSE
89
89
  - README.md
90
90
  - Rakefile
@@ -103,7 +103,6 @@ files:
103
103
  - lib/rays/video.rb
104
104
  - rays-video.gemspec
105
105
  - src/ios/video.mm
106
- - src/ios/video_audio_in.h
107
106
  - src/ios/video_audio_in.mm
108
107
  - src/osx/video.mm
109
108
  - src/osx/video_audio_in.h
@@ -136,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
136
135
  - !ruby/object:Gem::Version
137
136
  version: '0'
138
137
  requirements: []
139
- rubygems_version: 3.4.19
138
+ rubygems_version: 3.5.22
140
139
  signing_key:
141
140
  specification_version: 4
142
141
  summary: Video support for Rays.
@@ -1,127 +0,0 @@
1
- require 'shellwords'
2
-
3
- ALL_REPO = 'xord/all'
4
- ALL_DIR = '../all'
5
- ALL_FETCH_DEPTH = 100
6
-
7
- RENAMES = {reflex: 'reflexion'}
8
-
9
- def sh(cmd)
10
- puts cmd
11
- system cmd
12
- end
13
-
14
- def setup_dependencies(only: nil)
15
- gemspec_path = `git ls-files`.lines(chomp: true).find {|l| l =~ /\.gemspec$/}
16
- return unless gemspec_path
17
-
18
- gemspec = File.read gemspec_path
19
- name = File.basename gemspec_path, '.gemspec'
20
-
21
- exts = File.readlines('Rakefile')
22
- .map {|l| l[%r|^\s*require\W+([\w\-\_]+)/extension\W+$|, 1]}
23
- .compact
24
- .reject {|ext| ext == name}
25
- exts = exts & [only].flatten.map(&:to_s) if only
26
- return if exts.empty?
27
-
28
- unless setup_dependencies_via_monorepo(exts)
29
- setup_dependencies_via_each_repo_by_version(gemspec, exts)
30
- end
31
-
32
- exts.each {|ext| sh %( cd ../#{ext} && rake ext )}
33
- end
34
-
35
- def setup_dependencies_via_monorepo(exts)
36
- return false unless checkout_monorepo
37
- exts.each {|ext| sh %( ln -snf all/#{ext} ../#{ext} )}
38
- true
39
- end
40
-
41
- def checkout_monorepo()
42
- uuid = `git log -1 --format=%B`[/^\[\[([0-9a-fA-F-]+)\]\]$/, 1]
43
- return false unless uuid
44
-
45
- commit = setup_monorepo uuid
46
- return false unless commit
47
-
48
- Dir.chdir(ALL_DIR) {sh %( git checkout -q #{commit} )}
49
- true
50
- end
51
-
52
- def setup_monorepo(uuid)
53
- unless File.directory? ALL_DIR
54
- url = "https://github.com/#{ALL_REPO}.git"
55
- sh %( git clone --no-tags --depth #{ALL_FETCH_DEPTH} #{url} #{ALL_DIR} )
56
- end
57
- loop do
58
- commit = find_monorepo_commit uuid
59
- return commit if commit
60
-
61
- deepened = Dir.chdir ALL_DIR do
62
- before = `git rev-list --count HEAD`.to_i
63
- sh %( git fetch --deepen #{ALL_FETCH_DEPTH} )
64
- `git rev-list --count HEAD`.to_i > before
65
- end
66
- return nil unless deepened
67
- end
68
- end
69
-
70
- def find_monorepo_commit(uuid)
71
- Dir.chdir ALL_DIR do
72
- out = `git log origin/HEAD -F --grep="[[#{uuid}]]" --format=%H -1`.strip
73
- out.empty? ? nil : out
74
- end
75
- end
76
-
77
- def setup_dependencies_via_each_repo_by_version(gemspec, exts)
78
- exts.each do |ext|
79
- gem = RENAMES[ext.to_sym].then {|s| s || ext}
80
- ver = gemspec[/add_dependency.*['"]#{gem}['"].*['"]\s*~>\s*([\d\.]+)\s*['"]/, 1]
81
- opts = '-c advice.detachedHead=false --depth 1'
82
- clone = "git clone #{opts} https://github.com/xord/#{ext}.git ../#{ext}"
83
-
84
- # 'rake subtree:push' pushes all subrepos, so cloning by new tag
85
- # often fails before tagging each new tag
86
- sh %( #{clone} --branch v#{ver} || #{clone} )
87
- end
88
- end
89
-
90
- def tag_versions()
91
- changes = changelogs
92
- tags = `git tag`.lines chomp: true
93
- vers = `git log --oneline ./VERSION`
94
- .lines(chomp: true)
95
- .map {|line| line.split.first[/^\w+$/]}
96
- .map {|sha| [`git cat-file -p #{sha}:./VERSION 2>/dev/null`[/[\d\.]+/], sha]}
97
- .select {|ver, sha| ver && sha}
98
- .reverse
99
- .to_h
100
-
101
- vers.to_a.reverse.each do |ver, sha|
102
- tag = "v#{ver}"
103
- break if tags.include?(tag)
104
- sh %( git tag -a -m \"#{changes[tag]&.gsub '"', '\\"'}\" #{tag} #{sha} )
105
- end
106
- end
107
-
108
- def release(*paths)
109
- tag = ENV['GITHUB_REF']&.sub(%r|^refs/tags/|, '') || raise('GITHUB_REF tag not set')
110
- notes = (changelogs[tag] || '').shellescape
111
- paths = paths.flatten.join ' '
112
-
113
- sh(%( gh release create #{tag} #{paths} --notes #{notes} )) ||
114
- sh(%( gh release upload #{tag} #{paths} --clobber )) ||
115
- raise('failed to upload to releases')
116
- end
117
-
118
- def changelogs()
119
- File.read('ChangeLog.md')
120
- .split(/^\s*##\s*\[\s*(v[\d\.]+)\s*\].*$/)
121
- .slice(1..)
122
- .each_slice(2)
123
- .to_h
124
- .transform_values(&:strip!)
125
- rescue Errno::ENOENT
126
- raise 'failed to get changelogs'
127
- end
@@ -1,22 +0,0 @@
1
- // -*- mode: objc -*-
2
- #pragma once
3
- #ifndef __RAYS_VIDEO_SRC_IOS_VIDEO_AUDIO_IN_H__
4
- #define __RAYS_VIDEO_SRC_IOS_VIDEO_AUDIO_IN_H__
5
-
6
-
7
- #import <AVFoundation/AVFoundation.h>
8
- #include "../video_audio_in.h"
9
-
10
-
11
- namespace Rays
12
- {
13
-
14
-
15
- VideoAudioIn::Data* VideoAudioIn_Data_create (
16
- AVAsset* asset, AVAssetTrack* audio_track);
17
-
18
-
19
- }// Rays
20
-
21
-
22
- #endif//EOH