puremotion 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/.yardopts +1 -0
  2. data/README.md +69 -0
  3. data/Rakefile +23 -11
  4. data/examples/progress_reporting.rb +26 -0
  5. data/examples/simple.rb +22 -0
  6. data/ext/puremotion/audio.c +38 -0
  7. data/ext/puremotion/extconf.rb +34 -0
  8. data/ext/puremotion/frame.c +176 -0
  9. data/ext/puremotion/media.c +175 -0
  10. data/ext/puremotion/puremotion.c +26 -0
  11. data/ext/puremotion/puremotion.h +38 -0
  12. data/ext/puremotion/stream.c +128 -0
  13. data/ext/puremotion/stream_collection.c +44 -0
  14. data/ext/puremotion/utils.c +81 -0
  15. data/ext/puremotion/utils.h +6 -0
  16. data/ext/puremotion/video.c +141 -0
  17. data/lib/{puremotion/events → events}/event.rb +0 -0
  18. data/lib/{puremotion/events → events}/generator.rb +0 -0
  19. data/lib/media.rb +89 -0
  20. data/lib/preset/audio/audio.rb +42 -0
  21. data/lib/preset/file.rb +19 -0
  22. data/lib/preset/general.rb +28 -0
  23. data/lib/preset/metadata.rb +41 -0
  24. data/lib/preset/preset.rb +120 -0
  25. data/lib/preset/video/crop.rb +29 -0
  26. data/lib/preset/video/pad.rb +31 -0
  27. data/lib/preset/video/video.rb +130 -0
  28. data/lib/puremotion.rb +20 -12
  29. data/lib/puremotion_native.so +0 -0
  30. data/lib/threading.rb +54 -0
  31. data/lib/{puremotion/tools → tools}/ffmpeg.rb +12 -50
  32. data/lib/transcode/transcode.rb +142 -0
  33. data/spec/spec_helper.rb +7 -0
  34. data/spec/units/media_spec.rb +13 -0
  35. data/spec/units/preset_spec.rb +91 -0
  36. metadata +52 -44
  37. data/.document +0 -5
  38. data/.gitignore +0 -21
  39. data/README.rdoc +0 -18
  40. data/VERSION +0 -1
  41. data/lib/puremotion/codecs.rb +0 -59
  42. data/lib/puremotion/media.rb +0 -490
  43. data/lib/puremotion/media/stream.rb +0 -4
  44. data/lib/puremotion/media/stream/audio.rb +0 -0
  45. data/lib/puremotion/media/stream/base.rb +0 -7
  46. data/lib/puremotion/media/stream/collection.rb +0 -5
  47. data/lib/puremotion/media/stream/video.rb +0 -61
  48. data/lib/puremotion/recipes/ipod.yml +0 -12
  49. data/lib/puremotion/thread.rb +0 -153
  50. data/lib/puremotion/transcode/recipe.rb +0 -250
  51. data/lib/puremotion/transcode/transcode.rb +0 -153
  52. data/puremotion.gemspec +0 -68
  53. data/test/helper.rb +0 -10
  54. data/test/test_puremotion.rb +0 -7
@@ -0,0 +1 @@
1
+ --no-private
@@ -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 analysis and conversion of media using FFmpeg}
10
- gem.email = "iain.iw.wilson@googlemail.com"
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.add_development_dependency "thoughtbot-shoulda", ">= 0"
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
- require 'rake/rdoctask'
46
- Rake::RDocTask.new do |rdoc|
47
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
-
49
- rdoc.rdoc_dir = 'rdoc'
50
- rdoc.title = "puremotion #{version}"
51
- rdoc.rdoc_files.include('README*')
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
@@ -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
+ }