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.
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
+ }