rays-video 0.1.2
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/.doc/ext/rays-video/native.cpp +17 -0
- data/.doc/ext/rays-video/video.cpp +257 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +12 -0
- data/.github/workflows/release-gem.yml +51 -0
- data/.github/workflows/tag.yml +35 -0
- data/.github/workflows/test.yml +37 -0
- data/.github/workflows/utils.rb +127 -0
- data/CONTRIBUTING.md +7 -0
- data/ChangeLog.md +19 -0
- data/Gemfile +5 -0
- data/LICENSE +21 -0
- data/README.md +147 -0
- data/Rakefile +25 -0
- data/VERSION +1 -0
- data/ext/rays-video/defs.h +17 -0
- data/ext/rays-video/extconf.rb +23 -0
- data/ext/rays-video/native.cpp +17 -0
- data/ext/rays-video/video.cpp +282 -0
- data/include/rays/video.h +102 -0
- data/include/rays-video/ruby/video.h +47 -0
- data/include/rays-video/ruby.h +10 -0
- data/include/rays-video.h +10 -0
- data/lib/rays/video.rb +45 -0
- data/lib/rays-video/ext.rb +1 -0
- data/lib/rays-video/extension.rb +41 -0
- data/lib/rays-video.rb +3 -0
- data/rays-video.gemspec +39 -0
- data/src/ios/video.mm +633 -0
- data/src/ios/video_audio_in.h +22 -0
- data/src/ios/video_audio_in.mm +252 -0
- data/src/osx/video.mm +633 -0
- data/src/osx/video_audio_in.h +22 -0
- data/src/osx/video_audio_in.mm +252 -0
- data/src/sdl/video.cpp +86 -0
- data/src/sdl/video_audio_in.cpp +63 -0
- data/src/video.cpp +278 -0
- data/src/video.h +50 -0
- data/src/video_audio_in.h +57 -0
- data/src/win32/video.cpp +86 -0
- data/src/win32/video_audio_in.cpp +63 -0
- data/test/helper.rb +15 -0
- data/test/test_video.rb +165 -0
- metadata +145 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// -*- mode: objc -*-
|
|
2
|
+
#import "video_audio_in.h"
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
#import <AVFoundation/AVFoundation.h>
|
|
6
|
+
#include "rays/exception.h"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
namespace Rays
|
|
10
|
+
{
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
struct VideoAudioIn::Data
|
|
14
|
+
{
|
|
15
|
+
|
|
16
|
+
AVAsset* asset = nil;
|
|
17
|
+
|
|
18
|
+
AVAssetTrack* audio_track = nil;
|
|
19
|
+
|
|
20
|
+
uint nsamples = 0;
|
|
21
|
+
|
|
22
|
+
Beeps::Signals buffer;
|
|
23
|
+
|
|
24
|
+
uint buffer_offset = 0;
|
|
25
|
+
|
|
26
|
+
AVAssetReader* reader = nil;
|
|
27
|
+
|
|
28
|
+
AVAssetReaderAudioMixOutput* output = nil;
|
|
29
|
+
|
|
30
|
+
Data (AVAsset* asset, AVAssetTrack* audio_track)
|
|
31
|
+
{
|
|
32
|
+
assert(asset && audio_track);
|
|
33
|
+
|
|
34
|
+
CMFormatDescriptionRef format =
|
|
35
|
+
(__bridge CMFormatDescriptionRef) audio_track.formatDescriptions.firstObject;
|
|
36
|
+
if (!format)
|
|
37
|
+
rays_error(__FILE__, __LINE__, "failed to get CMFormatDescription");
|
|
38
|
+
|
|
39
|
+
const AudioStreamBasicDescription* desc =
|
|
40
|
+
CMAudioFormatDescriptionGetStreamBasicDescription(format);
|
|
41
|
+
if (!desc)
|
|
42
|
+
rays_error(__FILE__, __LINE__, "failed to get AudioStreamBasicDescription");
|
|
43
|
+
|
|
44
|
+
this->asset = [asset retain];
|
|
45
|
+
this->audio_track = [audio_track retain];
|
|
46
|
+
uint nchannels = std::min<uint>(desc->mChannelsPerFrame, 2);
|
|
47
|
+
this->buffer = Beeps::Signals(2048, nchannels, desc->mSampleRate);
|
|
48
|
+
double duration = CMTimeGetSeconds(audio_track.timeRange.duration);
|
|
49
|
+
this->nsamples = (uint) (duration * buffer.sample_rate());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
~Data ()
|
|
53
|
+
{
|
|
54
|
+
clear_reader();
|
|
55
|
+
[audio_track release];
|
|
56
|
+
[asset release];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
void create_reader (uint offset)
|
|
60
|
+
{
|
|
61
|
+
clear_reader();
|
|
62
|
+
|
|
63
|
+
NSError* error = nil;
|
|
64
|
+
AVAssetReader* reader =
|
|
65
|
+
[[[AVAssetReader alloc] initWithAsset: asset error: &error] autorelease];
|
|
66
|
+
if (!reader || error)
|
|
67
|
+
rays_error(__FILE__, __LINE__, "failed to create AVAssetReader");
|
|
68
|
+
|
|
69
|
+
AVAssetReaderAudioMixOutput* output = [AVAssetReaderAudioMixOutput
|
|
70
|
+
assetReaderAudioMixOutputWithAudioTracks: @[audio_track]
|
|
71
|
+
audioSettings: @{
|
|
72
|
+
AVFormatIDKey: @(kAudioFormatLinearPCM),
|
|
73
|
+
AVLinearPCMBitDepthKey: @32,
|
|
74
|
+
AVLinearPCMIsFloatKey: @YES,
|
|
75
|
+
AVLinearPCMIsNonInterleaved: @YES,
|
|
76
|
+
AVNumberOfChannelsKey: @(buffer.nchannels()),
|
|
77
|
+
}];
|
|
78
|
+
if (![reader canAddOutput: output])
|
|
79
|
+
rays_error(__FILE__, __LINE__, "cannot add audio output");
|
|
80
|
+
|
|
81
|
+
[reader addOutput: output];
|
|
82
|
+
reader.timeRange = CMTimeRangeMake(
|
|
83
|
+
CMTimeMakeWithSeconds(
|
|
84
|
+
(double) offset / buffer.sample_rate(),
|
|
85
|
+
(int32_t) buffer.sample_rate()),
|
|
86
|
+
kCMTimePositiveInfinity);
|
|
87
|
+
if (![reader startReading])
|
|
88
|
+
rays_error(__FILE__, __LINE__, "failed to start reading audio");
|
|
89
|
+
|
|
90
|
+
this->reader = [reader retain];
|
|
91
|
+
this->output = [output retain];
|
|
92
|
+
this->buffer_offset = offset;
|
|
93
|
+
this->buffer .clear();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
void clear_reader ()
|
|
97
|
+
{
|
|
98
|
+
if (reader && reader.status == AVAssetReaderStatusReading)
|
|
99
|
+
[reader cancelReading];
|
|
100
|
+
|
|
101
|
+
[output release];
|
|
102
|
+
output = nil;
|
|
103
|
+
[reader release];
|
|
104
|
+
reader = nil;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
bool read_next (Beeps::Signals* signals, uint* offset)
|
|
108
|
+
{
|
|
109
|
+
assert(signals && offset);
|
|
110
|
+
|
|
111
|
+
if (
|
|
112
|
+
!reader ||
|
|
113
|
+
*offset < buffer_offset ||
|
|
114
|
+
*offset > buffer_offset + buffer.nsamples())
|
|
115
|
+
{
|
|
116
|
+
create_reader(*offset);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (*offset == buffer_offset + buffer.nsamples())
|
|
120
|
+
{
|
|
121
|
+
if (!read_next_buffer()) return false;
|
|
122
|
+
buffer_offset = *offset;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
uint size = signals->append(buffer, *offset - buffer_offset);
|
|
126
|
+
*offset += size;
|
|
127
|
+
return size > 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
bool read_next_buffer ()
|
|
131
|
+
{
|
|
132
|
+
if (!reader || !output || reader.status != AVAssetReaderStatusReading)
|
|
133
|
+
return false;
|
|
134
|
+
|
|
135
|
+
std::shared_ptr<opaqueCMSampleBuffer> samples(
|
|
136
|
+
[output copyNextSampleBuffer],
|
|
137
|
+
CFRelease);
|
|
138
|
+
if (!samples)
|
|
139
|
+
return false;
|
|
140
|
+
|
|
141
|
+
uint nsamples = (uint) CMSampleBufferGetNumSamples(samples.get());
|
|
142
|
+
if (nsamples <= 0)
|
|
143
|
+
return false;
|
|
144
|
+
|
|
145
|
+
CMBlockBufferRef block = CMSampleBufferGetDataBuffer(samples.get());
|
|
146
|
+
if (!block)
|
|
147
|
+
return false;
|
|
148
|
+
|
|
149
|
+
size_t size = CMBlockBufferGetDataLength(block);
|
|
150
|
+
char* data = NULL;
|
|
151
|
+
CMBlockBufferGetDataPointer(block, 0, NULL, &size, &data);
|
|
152
|
+
if (!data || size <= 0)
|
|
153
|
+
rays_error(__FILE__, __LINE__);
|
|
154
|
+
|
|
155
|
+
CMFormatDescriptionRef format =
|
|
156
|
+
CMSampleBufferGetFormatDescription(samples.get());
|
|
157
|
+
if (!format)
|
|
158
|
+
rays_error(__FILE__, __LINE__);
|
|
159
|
+
|
|
160
|
+
const AudioStreamBasicDescription* desc =
|
|
161
|
+
CMAudioFormatDescriptionGetStreamBasicDescription(format);
|
|
162
|
+
if (!desc)
|
|
163
|
+
rays_error(__FILE__, __LINE__);
|
|
164
|
+
|
|
165
|
+
uint nchannels = desc->mChannelsPerFrame;
|
|
166
|
+
if (nchannels != buffer.nchannels())
|
|
167
|
+
rays_error(__FILE__, __LINE__);
|
|
168
|
+
|
|
169
|
+
std::vector<const float*> channels(nchannels);
|
|
170
|
+
for (uint ch = 0; ch < nchannels; ++ch)
|
|
171
|
+
channels[ch] = (const float*) data + ch * nsamples;
|
|
172
|
+
|
|
173
|
+
buffer.clear(nsamples);
|
|
174
|
+
buffer.append(channels.data(), nsamples, nchannels, buffer.sample_rate());
|
|
175
|
+
return buffer.nsamples() > 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
};// VideoAudioIn::Data
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
VideoAudioIn::Data*
|
|
182
|
+
VideoAudioIn_Data_create (AVAsset* asset, AVAssetTrack* audio_track)
|
|
183
|
+
{
|
|
184
|
+
return new VideoAudioIn::Data(asset, audio_track);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
VideoAudioIn::VideoAudioIn (Data* data)
|
|
189
|
+
: self(data)
|
|
190
|
+
{
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
VideoAudioIn::~VideoAudioIn ()
|
|
194
|
+
{
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
double
|
|
198
|
+
VideoAudioIn::sample_rate () const
|
|
199
|
+
{
|
|
200
|
+
return self->buffer.sample_rate();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
uint
|
|
204
|
+
VideoAudioIn::nchannels () const
|
|
205
|
+
{
|
|
206
|
+
return self->buffer.nchannels();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
uint
|
|
210
|
+
VideoAudioIn::nsamples () const
|
|
211
|
+
{
|
|
212
|
+
return self->nsamples;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
float
|
|
216
|
+
VideoAudioIn::seconds () const
|
|
217
|
+
{
|
|
218
|
+
if (this->sample_rate() <= 0)
|
|
219
|
+
return 0;
|
|
220
|
+
|
|
221
|
+
return (float) (self->nsamples / this->sample_rate());
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
VideoAudioIn::operator bool () const
|
|
225
|
+
{
|
|
226
|
+
return
|
|
227
|
+
Super::operator bool() &&
|
|
228
|
+
self->asset &&
|
|
229
|
+
self->audio_track &&
|
|
230
|
+
self->buffer;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
bool
|
|
234
|
+
VideoAudioIn::seekable () const
|
|
235
|
+
{
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
void
|
|
240
|
+
VideoAudioIn::generate (Context* context, Beeps::Signals* signals, uint* offset)
|
|
241
|
+
{
|
|
242
|
+
Super::generate(context, signals, offset);
|
|
243
|
+
|
|
244
|
+
while (!signals->full())
|
|
245
|
+
{
|
|
246
|
+
if (!self->read_next(signals, offset))
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
}// Rays
|
data/src/sdl/video.cpp
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#include "../video.h"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
#include "rays/exception.h"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
namespace Rays
|
|
8
|
+
{
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
struct VideoReader::Data
|
|
12
|
+
{
|
|
13
|
+
};// VideoReader::Data
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
VideoReader::VideoReader ()
|
|
17
|
+
{
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
VideoReader::VideoReader (const char*)
|
|
21
|
+
{
|
|
22
|
+
not_implemented_error(__FILE__, __LINE__);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
Image
|
|
26
|
+
VideoReader::decode_image (size_t, float) const
|
|
27
|
+
{
|
|
28
|
+
not_implemented_error(__FILE__, __LINE__);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
VideoAudioInList
|
|
32
|
+
VideoReader::get_audio_tracks () const
|
|
33
|
+
{
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
coord
|
|
38
|
+
VideoReader::width () const
|
|
39
|
+
{
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
coord
|
|
44
|
+
VideoReader::height () const
|
|
45
|
+
{
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
float
|
|
50
|
+
VideoReader::fps () const
|
|
51
|
+
{
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
size_t
|
|
56
|
+
VideoReader::size () const
|
|
57
|
+
{
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
VideoReader::operator bool () const
|
|
62
|
+
{
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
bool
|
|
67
|
+
VideoReader::operator ! () const
|
|
68
|
+
{
|
|
69
|
+
return !operator bool();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
void
|
|
74
|
+
Video::save (const char*)
|
|
75
|
+
{
|
|
76
|
+
not_implemented_error(__FILE__, __LINE__);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const StringList&
|
|
80
|
+
get_video_exts ()
|
|
81
|
+
{
|
|
82
|
+
not_implemented_error(__FILE__, __LINE__);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
}// Rays
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#include "../video_audio_in.h"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
namespace Rays
|
|
5
|
+
{
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
struct VideoAudioIn::Data
|
|
9
|
+
{
|
|
10
|
+
};// VideoAudioIn::Data
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
VideoAudioIn::VideoAudioIn (Data* data)
|
|
14
|
+
: self(data)
|
|
15
|
+
{
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
VideoAudioIn::~VideoAudioIn ()
|
|
19
|
+
{
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
double
|
|
23
|
+
VideoAudioIn::sample_rate () const
|
|
24
|
+
{
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
uint
|
|
29
|
+
VideoAudioIn::nchannels () const
|
|
30
|
+
{
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
uint
|
|
35
|
+
VideoAudioIn::nsamples () const
|
|
36
|
+
{
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
float
|
|
41
|
+
VideoAudioIn::seconds () const
|
|
42
|
+
{
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
VideoAudioIn::operator bool () const
|
|
47
|
+
{
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
bool
|
|
52
|
+
VideoAudioIn::seekable () const
|
|
53
|
+
{
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
void
|
|
58
|
+
VideoAudioIn::generate (Context* context, Beeps::Signals* signals, uint* offset)
|
|
59
|
+
{
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
}// Rays
|
data/src/video.cpp
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
#include "video.h"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
#include <xot/util.h>
|
|
5
|
+
#include <beeps/sound.h>
|
|
6
|
+
#include "rays/bitmap.h"
|
|
7
|
+
#include "rays/exception.h"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
namespace Rays
|
|
11
|
+
{
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
struct Video::Data
|
|
15
|
+
{
|
|
16
|
+
|
|
17
|
+
int width = 0, height = 0;
|
|
18
|
+
|
|
19
|
+
float fps = 0, pixel_density = 1;
|
|
20
|
+
|
|
21
|
+
size_t position = 0;
|
|
22
|
+
|
|
23
|
+
std::vector<Image> images;
|
|
24
|
+
|
|
25
|
+
VideoAudioInList audio_tracks;
|
|
26
|
+
|
|
27
|
+
Beeps::SoundPlayer player;
|
|
28
|
+
|
|
29
|
+
};// Video::Data
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
struct VideoImageData : public Image::Data
|
|
33
|
+
{
|
|
34
|
+
|
|
35
|
+
VideoReader reader;
|
|
36
|
+
|
|
37
|
+
size_t index;
|
|
38
|
+
|
|
39
|
+
VideoImageData (const VideoReader& reader, size_t index)
|
|
40
|
+
: reader(reader), index(index)
|
|
41
|
+
{
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
void preprocess (const Image* image) const override
|
|
45
|
+
{
|
|
46
|
+
Image decoded = reader.decode_image(index, 1);
|
|
47
|
+
Bitmap bitmap = decoded.bitmap();
|
|
48
|
+
if (bitmap) Xot::hint_memory_usage(bitmap.size());
|
|
49
|
+
const_cast<Image*>(image)->self = decoded.self;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
};// VideoImageData
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
Video
|
|
56
|
+
load_video (const char* path)
|
|
57
|
+
{
|
|
58
|
+
VideoReader reader(path);
|
|
59
|
+
if (!reader)
|
|
60
|
+
invalid_state_error(__FILE__, __LINE__);
|
|
61
|
+
|
|
62
|
+
Video video;
|
|
63
|
+
Video::Data* self = video.self.get();
|
|
64
|
+
self->width = reader.width();
|
|
65
|
+
self->height = reader.height();
|
|
66
|
+
self->fps = reader.fps();
|
|
67
|
+
self->pixel_density = 1;
|
|
68
|
+
self->position = 0;
|
|
69
|
+
|
|
70
|
+
size_t size = reader.size();
|
|
71
|
+
self->images.reserve(size);
|
|
72
|
+
for (size_t i = 0; i < size; ++i)
|
|
73
|
+
self->images.push_back(Image(new VideoImageData(reader, i)));
|
|
74
|
+
|
|
75
|
+
self->audio_tracks = reader.get_audio_tracks();
|
|
76
|
+
|
|
77
|
+
return video;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
Video::Video ()
|
|
82
|
+
{
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
Video::Video (int width, int height, float fps, float pixel_density)
|
|
86
|
+
{
|
|
87
|
+
if (width <= 0)
|
|
88
|
+
argument_error(__FILE__, __LINE__, "width must be > 0");
|
|
89
|
+
if (height <= 0)
|
|
90
|
+
argument_error(__FILE__, __LINE__, "height must be > 0");
|
|
91
|
+
if (fps <= 0)
|
|
92
|
+
argument_error(__FILE__, __LINE__, "fps must be > 0");
|
|
93
|
+
if (pixel_density <= 0)
|
|
94
|
+
argument_error(__FILE__, __LINE__, "pixel_density must be > 0");
|
|
95
|
+
|
|
96
|
+
self->width = width;
|
|
97
|
+
self->height = height;
|
|
98
|
+
self->fps = fps;
|
|
99
|
+
self->pixel_density = pixel_density;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
Video::~Video ()
|
|
103
|
+
{
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
Video
|
|
107
|
+
Video::dup () const
|
|
108
|
+
{
|
|
109
|
+
Video v;
|
|
110
|
+
*v.self = *self;
|
|
111
|
+
return v;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
void
|
|
115
|
+
Video::insert (size_t index, const Image& image)
|
|
116
|
+
{
|
|
117
|
+
if (!*this)
|
|
118
|
+
invalid_state_error(__FILE__, __LINE__, "video is not initialized");
|
|
119
|
+
|
|
120
|
+
self->images.insert(self->images.begin() + index, image);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
void
|
|
124
|
+
Video::append (const Image& image)
|
|
125
|
+
{
|
|
126
|
+
insert(size(), image);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
void
|
|
130
|
+
Video::remove (size_t index)
|
|
131
|
+
{
|
|
132
|
+
if (index >= size()) return;
|
|
133
|
+
self->images.erase(self->images.begin() + index);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
void
|
|
137
|
+
Video::play ()
|
|
138
|
+
{
|
|
139
|
+
if (empty())
|
|
140
|
+
invalid_state_error(__FILE__, __LINE__, "video is empty");
|
|
141
|
+
|
|
142
|
+
if (self->audio_tracks.empty())
|
|
143
|
+
invalid_state_error(__FILE__, __LINE__, "playing video without audio is not yet supported");
|
|
144
|
+
|
|
145
|
+
VideoAudioIn* in = self->audio_tracks[0].get();
|
|
146
|
+
self->player = Beeps::Sound(in, 0, in->nchannels(), in->sample_rate()).play();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
void
|
|
150
|
+
Video::pause ()
|
|
151
|
+
{
|
|
152
|
+
if (self->player) self->player.pause();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
void
|
|
156
|
+
Video::stop ()
|
|
157
|
+
{
|
|
158
|
+
if (self->player) self->player.stop();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
void
|
|
162
|
+
Video::set_time_scale (float scale)
|
|
163
|
+
{
|
|
164
|
+
if (self->player) self->player.set_time_scale(scale);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
float
|
|
168
|
+
Video::time_scale () const
|
|
169
|
+
{
|
|
170
|
+
return self->player ? self->player.time_scale() : 1;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
coord
|
|
174
|
+
Video::width () const
|
|
175
|
+
{
|
|
176
|
+
return self->width;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
coord
|
|
180
|
+
Video::height () const
|
|
181
|
+
{
|
|
182
|
+
return self->height;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
float
|
|
186
|
+
Video::fps () const
|
|
187
|
+
{
|
|
188
|
+
return self->fps;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
float
|
|
192
|
+
Video::pixel_density () const
|
|
193
|
+
{
|
|
194
|
+
return self->pixel_density;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
size_t
|
|
198
|
+
Video::size () const
|
|
199
|
+
{
|
|
200
|
+
return self->images.size();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
bool
|
|
204
|
+
Video::empty () const
|
|
205
|
+
{
|
|
206
|
+
return self->images.empty();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
void
|
|
210
|
+
Video::set_position (size_t index)
|
|
211
|
+
{
|
|
212
|
+
if (empty()) index = 0;
|
|
213
|
+
else if (index >= size()) index = size() - 1;
|
|
214
|
+
self->position = index;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
size_t
|
|
218
|
+
Video::position () const
|
|
219
|
+
{
|
|
220
|
+
if (empty()) self->position = 0;
|
|
221
|
+
else if (self->position >= size()) self->position = size() - 1;
|
|
222
|
+
return self->position;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
Video::const_iterator
|
|
226
|
+
Video::begin () const
|
|
227
|
+
{
|
|
228
|
+
return self->images.begin();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
Video::const_iterator
|
|
232
|
+
Video::end () const
|
|
233
|
+
{
|
|
234
|
+
return self->images.end();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
Image
|
|
238
|
+
Video::operator [] (size_t index) const
|
|
239
|
+
{
|
|
240
|
+
if (empty())
|
|
241
|
+
{
|
|
242
|
+
index_error(
|
|
243
|
+
__FILE__, __LINE__, "index %zu is out of range (empty)",
|
|
244
|
+
index);
|
|
245
|
+
}
|
|
246
|
+
if (index >= size())
|
|
247
|
+
{
|
|
248
|
+
index_error(
|
|
249
|
+
__FILE__, __LINE__, "index %zu is out of range (0..%zu)",
|
|
250
|
+
index, size() - 1);
|
|
251
|
+
}
|
|
252
|
+
return self->images[index];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
Video::operator Image () const
|
|
256
|
+
{
|
|
257
|
+
if (self->player)
|
|
258
|
+
{
|
|
259
|
+
size_t index = (size_t) (self->player.time() * self->fps);
|
|
260
|
+
if (index >= size()) index = size() - 1;
|
|
261
|
+
self->position = index;
|
|
262
|
+
}
|
|
263
|
+
return operator[](self->position);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
Video::operator bool () const
|
|
267
|
+
{
|
|
268
|
+
return self->width > 0 && self->height > 0;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
bool
|
|
272
|
+
Video::operator ! () const
|
|
273
|
+
{
|
|
274
|
+
return !operator bool();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
}// Rays
|
data/src/video.h
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// -*- c++ -*-
|
|
2
|
+
#pragma once
|
|
3
|
+
#ifndef __RAYS_VIDEO_SRC_VIDEO_H__
|
|
4
|
+
#define __RAYS_VIDEO_SRC_VIDEO_H__
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
#include "rays/video.h"
|
|
8
|
+
#include "video_audio_in.h"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
namespace Rays
|
|
12
|
+
{
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class VideoReader
|
|
16
|
+
{
|
|
17
|
+
|
|
18
|
+
public:
|
|
19
|
+
|
|
20
|
+
VideoReader ();
|
|
21
|
+
|
|
22
|
+
VideoReader (const char* path);
|
|
23
|
+
|
|
24
|
+
Image decode_image (size_t index, float pixel_density) const;
|
|
25
|
+
|
|
26
|
+
VideoAudioInList get_audio_tracks () const;
|
|
27
|
+
|
|
28
|
+
coord width () const;
|
|
29
|
+
|
|
30
|
+
coord height () const;
|
|
31
|
+
|
|
32
|
+
float fps () const;
|
|
33
|
+
|
|
34
|
+
size_t size () const;
|
|
35
|
+
|
|
36
|
+
operator bool () const;
|
|
37
|
+
|
|
38
|
+
bool operator ! () const;
|
|
39
|
+
|
|
40
|
+
struct Data;
|
|
41
|
+
|
|
42
|
+
Xot::PSharedImpl<Data> self;
|
|
43
|
+
|
|
44
|
+
};// VideoReader
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
}// Rays
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
#endif//EOH
|