puremotion 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|