puremotion 0.0.1 → 0.1.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/.yardopts +1 -0
- data/README.md +69 -0
- data/Rakefile +23 -11
- data/examples/progress_reporting.rb +26 -0
- data/examples/simple.rb +22 -0
- data/ext/puremotion/audio.c +38 -0
- data/ext/puremotion/extconf.rb +34 -0
- data/ext/puremotion/frame.c +176 -0
- data/ext/puremotion/media.c +175 -0
- data/ext/puremotion/puremotion.c +26 -0
- data/ext/puremotion/puremotion.h +38 -0
- data/ext/puremotion/stream.c +128 -0
- data/ext/puremotion/stream_collection.c +44 -0
- data/ext/puremotion/utils.c +81 -0
- data/ext/puremotion/utils.h +6 -0
- data/ext/puremotion/video.c +141 -0
- data/lib/{puremotion/events → events}/event.rb +0 -0
- data/lib/{puremotion/events → events}/generator.rb +0 -0
- data/lib/media.rb +89 -0
- data/lib/preset/audio/audio.rb +42 -0
- data/lib/preset/file.rb +19 -0
- data/lib/preset/general.rb +28 -0
- data/lib/preset/metadata.rb +41 -0
- data/lib/preset/preset.rb +120 -0
- data/lib/preset/video/crop.rb +29 -0
- data/lib/preset/video/pad.rb +31 -0
- data/lib/preset/video/video.rb +130 -0
- data/lib/puremotion.rb +20 -12
- data/lib/puremotion_native.so +0 -0
- data/lib/threading.rb +54 -0
- data/lib/{puremotion/tools → tools}/ffmpeg.rb +12 -50
- data/lib/transcode/transcode.rb +142 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/units/media_spec.rb +13 -0
- data/spec/units/preset_spec.rb +91 -0
- metadata +52 -44
- data/.document +0 -5
- data/.gitignore +0 -21
- data/README.rdoc +0 -18
- data/VERSION +0 -1
- data/lib/puremotion/codecs.rb +0 -59
- data/lib/puremotion/media.rb +0 -490
- data/lib/puremotion/media/stream.rb +0 -4
- data/lib/puremotion/media/stream/audio.rb +0 -0
- data/lib/puremotion/media/stream/base.rb +0 -7
- data/lib/puremotion/media/stream/collection.rb +0 -5
- data/lib/puremotion/media/stream/video.rb +0 -61
- data/lib/puremotion/recipes/ipod.yml +0 -12
- data/lib/puremotion/thread.rb +0 -153
- data/lib/puremotion/transcode/recipe.rb +0 -250
- data/lib/puremotion/transcode/transcode.rb +0 -153
- data/puremotion.gemspec +0 -68
- data/test/helper.rb +0 -10
- data/test/test_puremotion.rb +0 -7
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--no-private
|
data/README.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
PureMotion (0.1.0)
|
2
|
+
==================
|
3
|
+
|
4
|
+
**Homepage**: [http://github.com/ominiom/puremotion](http://github.com/ominiom/puremotion)
|
5
|
+
|
6
|
+
OVERVIEW
|
7
|
+
--------
|
8
|
+
|
9
|
+
PureMotion is a Ruby gem for handling media files through ffmpeg
|
10
|
+
|
11
|
+
|
12
|
+
FEATURES
|
13
|
+
--------
|
14
|
+
|
15
|
+
**1. Media Information**:
|
16
|
+
|
17
|
+
PureMotion uses the libav* libraries from the FFmpeg project to read information
|
18
|
+
from all FFmpeg supported media files.
|
19
|
+
|
20
|
+
|
21
|
+
**2. Transcoding**:
|
22
|
+
|
23
|
+
PureMotion provides a DSL for building and running ffmpeg transcodes with progress
|
24
|
+
reporting and logging. Consider the following code for transcoding a video to flv :
|
25
|
+
|
26
|
+
Transcode do
|
27
|
+
|
28
|
+
input 'sample.ogv'
|
29
|
+
|
30
|
+
video do
|
31
|
+
codec :flv
|
32
|
+
resize 320, 240
|
33
|
+
bitrate '320k'
|
34
|
+
end
|
35
|
+
|
36
|
+
audio do
|
37
|
+
codec :libmp3lame
|
38
|
+
channels 2
|
39
|
+
bitrate '64k'
|
40
|
+
end
|
41
|
+
|
42
|
+
output 'converted.flv'
|
43
|
+
|
44
|
+
log 'transcode.log'
|
45
|
+
|
46
|
+
event :progress do |transcode, progress|
|
47
|
+
puts "#{progress[:percent]}%"
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
**3. Thumbnails**:
|
53
|
+
|
54
|
+
The GD image library is used to resize and save in PNG format captured images from
|
55
|
+
a video stream.
|
56
|
+
|
57
|
+
Media 'sample.mp4' do
|
58
|
+
|
59
|
+
if video? then
|
60
|
+
seek(5).grab.resize(320, 240).save('thumb.png')
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
BUILDING
|
66
|
+
--------
|
67
|
+
|
68
|
+
To build the PureMotion gem from source you will need to have libav* and libgd
|
69
|
+
installed.
|
data/Rakefile
CHANGED
@@ -6,11 +6,24 @@ begin
|
|
6
6
|
Jeweler::Tasks.new do |gem|
|
7
7
|
gem.name = "puremotion"
|
8
8
|
gem.summary = %Q{PureMotion}
|
9
|
-
gem.description = %Q{A Ruby wrapper for
|
10
|
-
gem.email = "iain
|
9
|
+
gem.description = %Q{A Ruby wrapper for FFmpeg}
|
10
|
+
gem.email = "iain@ominiom.com"
|
11
11
|
gem.homepage = "http://github.com/ominiom/puremotion"
|
12
12
|
gem.authors = ["Ominiom"]
|
13
|
-
gem.
|
13
|
+
gem.extensions = ["ext/puremotion/extconf.rb"]
|
14
|
+
gem.files = [
|
15
|
+
"README.md",
|
16
|
+
"LICENSE",
|
17
|
+
"Rakefile",
|
18
|
+
".yardopts",
|
19
|
+
"ext/puremotion/*.c",
|
20
|
+
"ext/puremotion/*.h",
|
21
|
+
"ext/puremotion/*.rb",
|
22
|
+
"lib/*",
|
23
|
+
"lib/*",
|
24
|
+
"lib/*/*",
|
25
|
+
"lib/*/*/*",
|
26
|
+
]
|
14
27
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
28
|
end
|
16
29
|
Jeweler::GemcutterTasks.new
|
@@ -42,12 +55,11 @@ task :test => :check_dependencies
|
|
42
55
|
|
43
56
|
task :default => :test
|
44
57
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
58
|
+
begin
|
59
|
+
require 'yard'
|
60
|
+
YARD::Rake::YardocTask.new
|
61
|
+
rescue LoadError
|
62
|
+
task :yardoc do
|
63
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
64
|
+
end
|
53
65
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
Transcode do
|
2
|
+
|
3
|
+
input '../spec/samples/sample.ogv'
|
4
|
+
|
5
|
+
overwrite!
|
6
|
+
|
7
|
+
video do
|
8
|
+
codec :flv
|
9
|
+
resolution 320, 240
|
10
|
+
bitrate '240k'
|
11
|
+
end
|
12
|
+
|
13
|
+
audio do
|
14
|
+
codec :libmp3lame
|
15
|
+
bitrate '64k'
|
16
|
+
end
|
17
|
+
|
18
|
+
output 'test.flv'
|
19
|
+
|
20
|
+
log 'simple.log'
|
21
|
+
|
22
|
+
event :progress do |transcode, progress|
|
23
|
+
puts "#{progress[:percent]}%"
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
data/examples/simple.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Transcode do
|
2
|
+
|
3
|
+
input '../spec/samples/sample.ogv'
|
4
|
+
|
5
|
+
overwrite!
|
6
|
+
|
7
|
+
video do
|
8
|
+
codec :flv
|
9
|
+
resolution 320, 240
|
10
|
+
bitrate '240k'
|
11
|
+
end
|
12
|
+
|
13
|
+
audio do
|
14
|
+
codec :libmp3lame
|
15
|
+
bitrate '64k'
|
16
|
+
end
|
17
|
+
|
18
|
+
output 'test.flv'
|
19
|
+
|
20
|
+
log 'simple.log'
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#include "puremotion.h"
|
2
|
+
#include "utils.h"
|
3
|
+
|
4
|
+
VALUE rb_cStream;
|
5
|
+
VALUE rb_cAudioStream;
|
6
|
+
VALUE rb_mStreams;
|
7
|
+
|
8
|
+
static VALUE stream_sample_rate(VALUE self) {
|
9
|
+
AVStream * stream = get_stream(self);
|
10
|
+
|
11
|
+
rb_float_new(stream->codec->sample_rate);
|
12
|
+
}
|
13
|
+
|
14
|
+
static VALUE audio_stream_init(VALUE self, VALUE media) {
|
15
|
+
//printf("Stream initialized\n");
|
16
|
+
rb_iv_set(self, "@media", media);
|
17
|
+
return self;
|
18
|
+
}
|
19
|
+
|
20
|
+
static VALUE alloc_audio_stream(VALUE self) {
|
21
|
+
//printf("Stream allocating...\n");
|
22
|
+
AVStream * stream = av_new_stream(NULL, 0);
|
23
|
+
//printf("Stream wrapping...\n");
|
24
|
+
return Data_Wrap_Struct(rb_cAudioStream, 0, 0, stream);
|
25
|
+
}
|
26
|
+
|
27
|
+
VALUE build_audio_stream(AVStream *stream, VALUE rb_media) {
|
28
|
+
//printf("Stream building...\n");
|
29
|
+
VALUE rb_stream = Data_Wrap_Struct(rb_cAudioStream, 0, 0, stream);
|
30
|
+
//printf("Stream wrapped\n");
|
31
|
+
return audio_stream_init(rb_stream, rb_media);
|
32
|
+
}
|
33
|
+
|
34
|
+
void Init_audio_stream() {
|
35
|
+
rb_cAudioStream = rb_define_class_under(rb_mStreams, "Audio", rb_cStream);
|
36
|
+
|
37
|
+
rb_define_method(rb_cAudioStream, "sample_rate", stream_sample_rate, 0);
|
38
|
+
}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
if find_executable('pkg-config')
|
4
|
+
$CFLAGS << ' ' + `pkg-config libavfilter --cflags`.strip
|
5
|
+
$CFLAGS << ' ' + `pkg-config libavcodec --cflags`.strip
|
6
|
+
$CFLAGS << ' ' + `pkg-config libavutil --cflags`.strip
|
7
|
+
$CFLAGS << ' ' + `pkg-config libswscale --cflags`.strip
|
8
|
+
$LDFLAGS << ' ' + `pkg-config libavfilter --libs`.strip
|
9
|
+
$LDFLAGS << ' ' + `pkg-config libavcodec --libs`.strip
|
10
|
+
$LDFLAGS << ' ' + `pkg-config libavutil --libs`.strip
|
11
|
+
$LDFLAGS << ' ' + `pkg-config libswscale --libs`.strip
|
12
|
+
$LDFLAGS << ' ' + `pkg-config libgd --libs`.strip
|
13
|
+
end
|
14
|
+
|
15
|
+
ffmpeg_include, ffmpeg_lib = dir_config("ffmpeg")
|
16
|
+
dir_config("libswscale")
|
17
|
+
|
18
|
+
$CFLAGS << " -W -Wall -static"
|
19
|
+
#$LDFLAGS << " -rpath #{ffmpeg_lib}"
|
20
|
+
|
21
|
+
if have_library("avformat") and find_header('libavformat/avformat.h') and
|
22
|
+
have_library("avcodec") and find_header('libavutil/avutil.h') and
|
23
|
+
have_library("avutil") and find_header('libavcodec/avcodec.h') and
|
24
|
+
have_library("swscale") and find_header('libswscale/swscale.h') and
|
25
|
+
have_library("gd") and find_header('gd.h') then
|
26
|
+
|
27
|
+
$objs = %w(puremotion.o media.o stream_collection.o stream.o video.o audio.o utils.o frame.o)
|
28
|
+
|
29
|
+
create_makefile("puremotion_native")
|
30
|
+
|
31
|
+
else
|
32
|
+
STDERR.puts "missing library"
|
33
|
+
exit 1
|
34
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
#include "puremotion.h"
|
2
|
+
#include "utils.h"
|
3
|
+
|
4
|
+
VALUE rb_cFrame;
|
5
|
+
|
6
|
+
static AVFrame * alloc_picture(int pix_fmt, int width, int height) {
|
7
|
+
AVFrame *picture;
|
8
|
+
uint8_t *picture_buf;
|
9
|
+
int size;
|
10
|
+
|
11
|
+
picture = avcodec_alloc_frame();
|
12
|
+
if (!picture)
|
13
|
+
return NULL;
|
14
|
+
size = avpicture_get_size(pix_fmt, width, height);
|
15
|
+
picture_buf = av_malloc(size);
|
16
|
+
if (!picture_buf) {
|
17
|
+
av_free(picture);
|
18
|
+
return NULL;
|
19
|
+
}
|
20
|
+
avpicture_fill((AVPicture *)picture, picture_buf,
|
21
|
+
pix_fmt, width, height);
|
22
|
+
return picture;
|
23
|
+
}
|
24
|
+
|
25
|
+
static VALUE frame_to_rgb24(VALUE self) {
|
26
|
+
int width = NUM2INT(rb_iv_get(self, "@width"));
|
27
|
+
int height = NUM2INT(rb_iv_get(self, "@height"));
|
28
|
+
int pixel_format = NUM2INT(rb_iv_get(self, "@pixel_format"));
|
29
|
+
|
30
|
+
struct SwsContext *img_convert_ctx = NULL;
|
31
|
+
img_convert_ctx = sws_getContext(width, height, pixel_format,
|
32
|
+
width, height, PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL);
|
33
|
+
|
34
|
+
AVFrame * from = get_frame(self);
|
35
|
+
AVFrame * to = alloc_picture(PIX_FMT_RGB24, width, height);
|
36
|
+
|
37
|
+
sws_scale(img_convert_ctx, from->data, from->linesize,
|
38
|
+
0, height, to->data, to->linesize);
|
39
|
+
|
40
|
+
av_free(img_convert_ctx);
|
41
|
+
|
42
|
+
return build_frame_object(to, width, height, PIX_FMT_RGB24);
|
43
|
+
}
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
static VALUE frame_to_ppm(VALUE self) {
|
48
|
+
VALUE rb_frame = frame_to_rgb24(self);
|
49
|
+
AVFrame * frame = get_frame(rb_frame);
|
50
|
+
|
51
|
+
int width = NUM2INT(rb_iv_get(self, "@width"));
|
52
|
+
int height = NUM2INT(rb_iv_get(self, "@height"));
|
53
|
+
|
54
|
+
char header[255];
|
55
|
+
sprintf(header, "P6\n%d %d\n255\n", width, height);
|
56
|
+
|
57
|
+
int size = strlen(header) + frame->linesize[0] * height;
|
58
|
+
char * data_string = malloc(size);
|
59
|
+
strcpy(data_string, header);
|
60
|
+
|
61
|
+
memcpy(data_string + strlen(header), frame->data[0], frame->linesize[0] * height);
|
62
|
+
|
63
|
+
return rb_str_new(data_string, size);
|
64
|
+
}
|
65
|
+
|
66
|
+
static VALUE frame_resize(VALUE self, VALUE w, VALUE h) {
|
67
|
+
|
68
|
+
int orig_width = NUM2INT(rb_iv_get(self, "@width"));
|
69
|
+
int orig_height = NUM2INT(rb_iv_get(self, "@height"));
|
70
|
+
|
71
|
+
int new_width = NUM2INT(w);
|
72
|
+
int new_height = NUM2INT(h);
|
73
|
+
|
74
|
+
int pixel_format = NUM2INT(rb_iv_get(self, "@pixel_format"));
|
75
|
+
|
76
|
+
struct SwsContext *img_convert_ctx = NULL;
|
77
|
+
img_convert_ctx = sws_getContext(orig_width, orig_height, pixel_format,
|
78
|
+
new_width, new_height, PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL);
|
79
|
+
|
80
|
+
AVFrame * from = get_frame(self);
|
81
|
+
AVFrame * to = alloc_picture(PIX_FMT_RGB24, new_width, new_height);
|
82
|
+
|
83
|
+
sws_scale(img_convert_ctx, from->data, from->linesize,
|
84
|
+
0, orig_height, to->data, to->linesize);
|
85
|
+
|
86
|
+
av_free(img_convert_ctx);
|
87
|
+
|
88
|
+
return build_frame_object(to, new_width, new_height, PIX_FMT_RGB24);
|
89
|
+
|
90
|
+
}
|
91
|
+
|
92
|
+
static VALUE frame_save(VALUE self, VALUE filename) {
|
93
|
+
|
94
|
+
int w = NUM2INT(rb_iv_get(self, "@width"));
|
95
|
+
int h = NUM2INT(rb_iv_get(self, "@height"));
|
96
|
+
|
97
|
+
gdImagePtr img = gdImageCreateTrueColor(w, h);
|
98
|
+
|
99
|
+
AVFrame *frame = get_frame(self);
|
100
|
+
|
101
|
+
int x, y;
|
102
|
+
|
103
|
+
// Copy pixel by pixel...
|
104
|
+
for( x = 0; x <= w; x++ ) {
|
105
|
+
for( y = 0; y <= h; y++ ) {
|
106
|
+
|
107
|
+
int off = (y * frame->linesize[0])+(3*x);
|
108
|
+
int red = frame->data[0][off];
|
109
|
+
int green = frame->data[0][off + 1];
|
110
|
+
int blue = frame->data[0][off + 2];
|
111
|
+
|
112
|
+
int c = gdImageColorAllocate(img,
|
113
|
+
red,
|
114
|
+
green,
|
115
|
+
blue
|
116
|
+
);
|
117
|
+
|
118
|
+
gdImageSetPixel(img, x, y, c);
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
FILE *f = fopen(StringValuePtr(filename), "wb");
|
123
|
+
gdImagePng(img, f);
|
124
|
+
fclose(f);
|
125
|
+
|
126
|
+
// Gone forever....
|
127
|
+
gdImageDestroy(img);
|
128
|
+
|
129
|
+
return self;
|
130
|
+
|
131
|
+
}
|
132
|
+
|
133
|
+
static void free_frame(AVFrame * frame) {
|
134
|
+
//fprintf(stderr, "will free frame\n");
|
135
|
+
av_free(frame);
|
136
|
+
}
|
137
|
+
|
138
|
+
static VALUE alloc_frame(VALUE klass) {
|
139
|
+
AVFrame * frame = avcodec_alloc_frame();
|
140
|
+
VALUE obj;
|
141
|
+
obj = Data_Wrap_Struct(klass, 0, free_frame, frame);
|
142
|
+
return obj;
|
143
|
+
}
|
144
|
+
|
145
|
+
static VALUE frame_init(VALUE self, VALUE width, VALUE height, VALUE pixel_format) {
|
146
|
+
//fprintf(stderr, "new frame : %dx%d, pix:%d\n", NUM2INT(width), NUM2INT(height), NUM2INT(pixel_format));
|
147
|
+
rb_iv_set(self, "@width", width);
|
148
|
+
rb_iv_set(self, "@height", height);
|
149
|
+
rb_iv_set(self, "@pixel_format", pixel_format);
|
150
|
+
return self;
|
151
|
+
}
|
152
|
+
|
153
|
+
VALUE build_frame_object(AVFrame * frame, int width, int height, int pixel_format) {
|
154
|
+
VALUE obj = Data_Wrap_Struct(rb_cFrame, 0, free_frame, frame);
|
155
|
+
|
156
|
+
return frame_init(obj,
|
157
|
+
INT2FIX(width),
|
158
|
+
INT2FIX(height),
|
159
|
+
INT2FIX(pixel_format));
|
160
|
+
}
|
161
|
+
|
162
|
+
void Init_frame() {
|
163
|
+
rb_cFrame = rb_define_class_under(rb_mPureMotion, "Frame", rb_cObject);
|
164
|
+
|
165
|
+
rb_define_alloc_func(rb_cFrame, alloc_frame);
|
166
|
+
rb_define_method(rb_cFrame, "initialize", frame_init, 3);
|
167
|
+
|
168
|
+
rb_funcall(rb_cFrame, rb_intern("attr_reader"), 1, rb_sym("width"));
|
169
|
+
rb_funcall(rb_cFrame, rb_intern("attr_reader"), 1, rb_sym("height"));
|
170
|
+
rb_funcall(rb_cFrame, rb_intern("attr_reader"), 1, rb_sym("pixel_format"));
|
171
|
+
|
172
|
+
rb_define_method(rb_cFrame, "to_rgb24", frame_to_rgb24, 0);
|
173
|
+
rb_define_method(rb_cFrame, "to_ppm", frame_to_ppm, 0);
|
174
|
+
rb_define_method(rb_cFrame, "resize", frame_resize, 2);
|
175
|
+
rb_define_method(rb_cFrame, "save", frame_save, 1);
|
176
|
+
}
|
@@ -0,0 +1,175 @@
|
|
1
|
+
#include "puremotion.h"
|
2
|
+
#include "utils.h"
|
3
|
+
|
4
|
+
VALUE rb_cMedia;
|
5
|
+
VALUE rb_cStreamCollection;
|
6
|
+
VALUE rb_eUnsupportedFormat;
|
7
|
+
|
8
|
+
/* call-seq: filename => String
|
9
|
+
*
|
10
|
+
* Returns the path to the file encapsulated by this instance of the media class
|
11
|
+
*
|
12
|
+
* @return [String]
|
13
|
+
*
|
14
|
+
*/
|
15
|
+
static VALUE media_filename(VALUE self) {
|
16
|
+
AVFormatContext * format_context = get_format_context(self);
|
17
|
+
if( format_context->filename == NULL ) return Qnil;
|
18
|
+
return rb_funcall(rb_cFile, rb_intern("expand_path"), 1, rb_str_new2(format_context->filename));
|
19
|
+
}
|
20
|
+
|
21
|
+
/* call-seq: streams => Array<PureMotion::Streams::Stream>
|
22
|
+
*
|
23
|
+
* Collection of streams in the media
|
24
|
+
*
|
25
|
+
* @return [Array<PureMotion::Streams::Stream>]
|
26
|
+
*/
|
27
|
+
static VALUE media_streams(VALUE self) {
|
28
|
+
|
29
|
+
return build_stream_collection(self);
|
30
|
+
|
31
|
+
}
|
32
|
+
|
33
|
+
/* call-seq: bitrate => Number
|
34
|
+
*
|
35
|
+
* Media bitrate in bytes/sec
|
36
|
+
*
|
37
|
+
* @return [Number]
|
38
|
+
*/
|
39
|
+
static VALUE media_bitrate(VALUE self) {
|
40
|
+
AVFormatContext * format_context = get_format_context(self);
|
41
|
+
return INT2NUM( format_context->bit_rate );
|
42
|
+
}
|
43
|
+
|
44
|
+
/* call-seq: duration => Float
|
45
|
+
*
|
46
|
+
* Media duration in seconds
|
47
|
+
*
|
48
|
+
* @return [Float]
|
49
|
+
*/
|
50
|
+
static VALUE media_duration(VALUE self) {
|
51
|
+
AVFormatContext * format_context = get_format_context(self);
|
52
|
+
|
53
|
+
if (format_context->duration == AV_NOPTS_VALUE) return Qnil;
|
54
|
+
|
55
|
+
return rb_float_new(format_context->duration / (double)AV_TIME_BASE);
|
56
|
+
}
|
57
|
+
|
58
|
+
/* call-seq: duration_human => String
|
59
|
+
*
|
60
|
+
* Formatted human-readable duration string
|
61
|
+
*
|
62
|
+
* @return [String]
|
63
|
+
*/
|
64
|
+
static VALUE media_duration_human(VALUE self) {
|
65
|
+
AVFormatContext * format_context = get_format_context(self);
|
66
|
+
|
67
|
+
if (format_context->duration == AV_NOPTS_VALUE) return Qnil;
|
68
|
+
|
69
|
+
int hours, mins, secs, us;
|
70
|
+
char cstr[64] = "";
|
71
|
+
|
72
|
+
secs = format_context->duration / AV_TIME_BASE;
|
73
|
+
us = format_context->duration % AV_TIME_BASE;
|
74
|
+
mins = secs / 60;
|
75
|
+
secs %= 60;
|
76
|
+
hours = mins / 60;
|
77
|
+
mins %= 60;
|
78
|
+
sprintf(cstr, "%02d:%02d:%02d.%01d", hours, mins, secs,
|
79
|
+
(10 * us) / AV_TIME_BASE);
|
80
|
+
|
81
|
+
return rb_str_new2(cstr);
|
82
|
+
}
|
83
|
+
|
84
|
+
// Determines if the file can be read and processed
|
85
|
+
// @return [Boolean]
|
86
|
+
static VALUE media_valid(VALUE self) {
|
87
|
+
return rb_iv_get(self, "@valid");
|
88
|
+
}
|
89
|
+
|
90
|
+
/* call-seq: initialize(String) => Media
|
91
|
+
*
|
92
|
+
* Loads a media file
|
93
|
+
*
|
94
|
+
* @param [String] file Path to a media file to load
|
95
|
+
*/
|
96
|
+
static VALUE media_init(VALUE self, VALUE file) {
|
97
|
+
|
98
|
+
|
99
|
+
AVFormatContext *fmt_ctx = get_format_context(self);
|
100
|
+
|
101
|
+
AVFormatParameters fp, *ap = &fp;
|
102
|
+
|
103
|
+
memset(ap, 0, sizeof(fp));
|
104
|
+
|
105
|
+
ap->prealloced_context = 1;
|
106
|
+
ap->width = 0;
|
107
|
+
ap->height = 0;
|
108
|
+
ap->pix_fmt = PIX_FMT_NONE;
|
109
|
+
|
110
|
+
if( rb_funcall(rb_cFile, rb_intern("file?"), 1, file) == Qfalse )
|
111
|
+
rb_raise(rb_eArgError, "File not found '%s'", StringValuePtr(file));
|
112
|
+
|
113
|
+
int error = av_open_input_file(&fmt_ctx, StringValuePtr(file), NULL, 0, ap);
|
114
|
+
|
115
|
+
if( error < 0 ) {
|
116
|
+
rb_raise(rb_eUnsupportedFormat, "File '%s' unable to be opened. Unsupported format.", StringValuePtr(file));
|
117
|
+
rb_iv_set(self, "@valid", Qfalse);
|
118
|
+
return Qnil;
|
119
|
+
}
|
120
|
+
|
121
|
+
error = av_find_stream_info(fmt_ctx);
|
122
|
+
|
123
|
+
if( error < 0 ) {
|
124
|
+
rb_raise(rb_eUnsupportedFormat, "File '%s': Streams are unreadable.", StringValuePtr(file));
|
125
|
+
rb_iv_set(self, "@valid", Qfalse);
|
126
|
+
}
|
127
|
+
|
128
|
+
rb_iv_set(self, "@valid", Qtrue);
|
129
|
+
|
130
|
+
return self;
|
131
|
+
|
132
|
+
}
|
133
|
+
|
134
|
+
static void free_media(AVFormatContext *fmt_ctx) {
|
135
|
+
if ( fmt_ctx == NULL) return;
|
136
|
+
int i;
|
137
|
+
|
138
|
+
rb_funcall(rb_mKernel, "puts", 1, INT2NUM(fmt_ctx->nb_streams));
|
139
|
+
|
140
|
+
for(i = 0; i < fmt_ctx->nb_streams; i++) {
|
141
|
+
if( fmt_ctx->streams[i]->codec->codec != NULL )
|
142
|
+
avcodec_close(fmt_ctx->streams[i]->codec);
|
143
|
+
}
|
144
|
+
|
145
|
+
if( fmt_ctx->iformat ) av_close_input_file(fmt_ctx);
|
146
|
+
else av_free(fmt_ctx);
|
147
|
+
}
|
148
|
+
|
149
|
+
static VALUE alloc_media(VALUE self) {
|
150
|
+
AVFormatContext * fmt_ctx = av_alloc_format_context();
|
151
|
+
VALUE obj;
|
152
|
+
fmt_ctx->oformat = NULL;
|
153
|
+
fmt_ctx->iformat = NULL;
|
154
|
+
|
155
|
+
obj = Data_Wrap_Struct(self, 0, free_media, fmt_ctx);
|
156
|
+
return obj;
|
157
|
+
}
|
158
|
+
|
159
|
+
void Init_media() {
|
160
|
+
|
161
|
+
rb_cMedia = rb_define_class_under(rb_mPureMotion, "Media", rb_cObject);
|
162
|
+
|
163
|
+
rb_define_alloc_func(rb_cMedia, alloc_media);
|
164
|
+
rb_define_method(rb_cMedia, "initialize", media_init, 1);
|
165
|
+
|
166
|
+
rb_define_method(rb_cMedia, "duration", media_duration, 0);
|
167
|
+
rb_define_method(rb_cMedia, "duration_human", media_duration_human, 0);
|
168
|
+
rb_define_method(rb_cMedia, "bitrate", media_bitrate, 0);
|
169
|
+
rb_define_method(rb_cMedia, "filename", media_filename, 0);
|
170
|
+
rb_define_method(rb_cMedia, "streams", media_streams, 0);
|
171
|
+
rb_define_method(rb_cMedia, "valid?", media_valid, 0);
|
172
|
+
|
173
|
+
rb_eUnsupportedFormat = rb_define_class_under(rb_mPureMotion, "UnsupportedFormat", rb_eStandardError);
|
174
|
+
|
175
|
+
}
|