ffi-libav 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/.gitignore +27 -0
- data/Gemfile +10 -0
- data/LICENSE +27 -0
- data/README.md +47 -0
- data/Rakefile +9 -0
- data/examples/extract_keyframes.rb +47 -0
- data/examples/read_file.rb +46 -0
- data/examples/seek.rb +50 -0
- data/ffi-libav.gemspec +23 -0
- data/lib/ffi-libav.rb +47 -0
- data/lib/ffi/libav.i +55 -0
- data/lib/ffi/libav.rb +3034 -0
- data/lib/libav/frame.rb +113 -0
- data/lib/libav/reader.rb +114 -0
- data/lib/libav/stream.rb +211 -0
- data/lib/libav/version.rb +3 -0
- metadata +116 -0
data/lib/libav/frame.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'ffi/libav'
|
2
|
+
|
3
|
+
module Libav::Frame; end
|
4
|
+
|
5
|
+
class Libav::Frame::Video
|
6
|
+
include FFI::Libav
|
7
|
+
|
8
|
+
attr_reader :av_frame, :stream
|
9
|
+
attr_accessor :number
|
10
|
+
|
11
|
+
# Initialize a new frame, and optionally allocate memory for the frame data.
|
12
|
+
#
|
13
|
+
# If only a +:stream+ is provided, the remaining attributes will be copied
|
14
|
+
# from that stream.
|
15
|
+
#
|
16
|
+
# ==== Options
|
17
|
+
# * +:stream+ - Libav::Stream this frame belongs to
|
18
|
+
# * +:width+ - width of the frame
|
19
|
+
# * +:height+ - height of the frame
|
20
|
+
# * +:pixel_format+ - format of the frame
|
21
|
+
# * +:alloc+ - Allocate space for the frame data [default: true]
|
22
|
+
#
|
23
|
+
def initialize(p={})
|
24
|
+
# Create our frame and alloc space for the frame data
|
25
|
+
@av_frame = AVFrame.new
|
26
|
+
|
27
|
+
@stream = p[:stream]
|
28
|
+
@av_frame[:width] = p[:width] || @stream && @stream.width
|
29
|
+
@av_frame[:height] = p[:height] || @stream && @stream.height
|
30
|
+
@av_frame[:format] = p[:pixel_format] || @stream && @stream.pixel_format
|
31
|
+
|
32
|
+
# Allocate the frame's data unless the caller doesn't want us to.
|
33
|
+
unless p[:alloc] == false
|
34
|
+
av_picture = AVPicture.new @av_frame.pointer
|
35
|
+
avpicture_alloc(av_picture, @av_frame[:format], @av_frame[:width],
|
36
|
+
@av_frame[:height])
|
37
|
+
ObjectSpace.define_finalizer(self, cleanup_proc(av_picture))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Throw together a bunch of helper methods for accessing the AVFrame
|
42
|
+
# attributes.
|
43
|
+
AVFrame.members.each do |member|
|
44
|
+
define_method(member) { @av_frame[member] }
|
45
|
+
define_method(member.to_s + "=") { |v| @av_frame[member] = v }
|
46
|
+
end
|
47
|
+
|
48
|
+
def key_frame?
|
49
|
+
key_frame == 1
|
50
|
+
end
|
51
|
+
|
52
|
+
def pixel_format
|
53
|
+
format.is_a?(Fixnum) ? PixelFormat[format] : format
|
54
|
+
end
|
55
|
+
|
56
|
+
def pixel_format=(v)
|
57
|
+
send("format=", (v.is_a?(Fixnum) ? v : PixelFormat[v]))
|
58
|
+
end
|
59
|
+
|
60
|
+
# Scale the frame
|
61
|
+
#
|
62
|
+
# If any of the +:width+, +:height+, or +:pixel_format+ options are not
|
63
|
+
# supplied, they will be copied from the +:output_frame+, if provided,
|
64
|
+
# otherwise they will be copied from this frame.
|
65
|
+
#
|
66
|
+
# If no +:scale_ctx+ is provided, one will be allocated and freed within this
|
67
|
+
# method.
|
68
|
+
#
|
69
|
+
# If no +:output_frame+ is provided, this method will allocate a new one.
|
70
|
+
#
|
71
|
+
# ==== Options
|
72
|
+
# * +:width+ - width of the scaled frame
|
73
|
+
# * +:height+ - height of the scaled frame
|
74
|
+
# * +:pixel_format+ - pixel format of the scaled frame
|
75
|
+
# * +:scale_ctx+ - optional software scaling context
|
76
|
+
# + +:output_frame+ - optional output frame
|
77
|
+
#
|
78
|
+
def scale(p={})
|
79
|
+
out = p[:output_frame] ||
|
80
|
+
self.class.new(:width => p[:width] || width,
|
81
|
+
:height => p[:height] || height,
|
82
|
+
:pixel_format => p[:pixel_format] || pixel_format,
|
83
|
+
:stream => stream)
|
84
|
+
ctx = p[:scale_ctx] ||
|
85
|
+
sws_getContext(width, height, pixel_format,
|
86
|
+
out.width, out.height, out.pixel_format,
|
87
|
+
SWS_BICUBIC, nil, nil, nil)
|
88
|
+
raise NoMemoryError, "sws_getContext() failed" if ctx.nil?
|
89
|
+
|
90
|
+
# Scale the image
|
91
|
+
rc = sws_scale(ctx, data, linesize, 0, height, out.data, out.linesize)
|
92
|
+
|
93
|
+
# Free the scale context if one wasn't provided by the caller
|
94
|
+
sws_freeContext(ctx) unless p[:scale_ctx]
|
95
|
+
|
96
|
+
# Let's copy a handful of attributes to the scaled frame
|
97
|
+
%w{ pts number key_frame }.each do |k|
|
98
|
+
out.send("#{k}=", send(k))
|
99
|
+
end
|
100
|
+
|
101
|
+
# Return our scaled frame
|
102
|
+
out
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
# Returns Proc responsible for cleaning up the picture memory when it gets
|
108
|
+
# garbage collected.
|
109
|
+
def cleanup_proc(picture)
|
110
|
+
proc { avpicture_free(picture) }
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
data/lib/libav/reader.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'ffi/libav'
|
2
|
+
|
3
|
+
# Libav file reader
|
4
|
+
class Libav::Reader
|
5
|
+
include FFI::Libav
|
6
|
+
|
7
|
+
attr_reader :filename, :streams, :av_format_ctx
|
8
|
+
|
9
|
+
# Initialize a Reader for a specific file
|
10
|
+
#
|
11
|
+
# ==== Attributes
|
12
|
+
# * +filename+ - file to read
|
13
|
+
#
|
14
|
+
def initialize(filename, p={})
|
15
|
+
@filename = filename or raise ArgumentError, "No filename"
|
16
|
+
|
17
|
+
Libav.register_all
|
18
|
+
@av_format_ctx = FFI::MemoryPointer.new(:pointer)
|
19
|
+
rc = avformat_open_input(@av_format_ctx, @filename, nil, nil)
|
20
|
+
raise RuntimeError, "avformat_open_input() failed, filename='%s', rc=%d" %
|
21
|
+
[filename, rc] if rc != 0
|
22
|
+
@av_format_ctx = AVFormatContext.new @av_format_ctx.get_pointer(0)
|
23
|
+
|
24
|
+
rc = avformat_find_stream_info(@av_format_ctx, nil)
|
25
|
+
raise RuntimeError, "av_find_stream_info() failed, rc=#{rc}" if rc < 0
|
26
|
+
|
27
|
+
# Open all of our streams
|
28
|
+
initialize_streams(p)
|
29
|
+
|
30
|
+
# Set up a finalizer to close all the things we've opened
|
31
|
+
ObjectSpace.define_finalizer(self,
|
32
|
+
cleanup_proc(@av_format_ctx, streams.map { |s| s.av_codec_ctx }))
|
33
|
+
|
34
|
+
# Our packet for reading
|
35
|
+
@packet = AVPacket.new
|
36
|
+
av_init_packet(@packet)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Call +av_dump_format+ to print out the format info for the video
|
40
|
+
def dump_format
|
41
|
+
FFI::Libav.av_dump_format(@av_format_ctx, 0, @filename, 0)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Video duration in (fractional) seconds
|
45
|
+
def duration
|
46
|
+
@duration ||= @av_format_ctx[:duration].to_f / AV_TIME_BASE
|
47
|
+
end
|
48
|
+
|
49
|
+
# Loop through each frame
|
50
|
+
#
|
51
|
+
# ==== Argument
|
52
|
+
# * +block+ - block of code to call with the frame
|
53
|
+
#
|
54
|
+
# ==== Usage
|
55
|
+
# # Read each frame
|
56
|
+
# reader.each_frame do |frame|
|
57
|
+
#
|
58
|
+
# # call some method for showing the frame
|
59
|
+
# my_show_frame(frame)
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
def each_frame(&block)
|
63
|
+
raise ArgumentError, "No block provided" unless block_given?
|
64
|
+
|
65
|
+
while av_read_frame(@av_format_ctx, @packet) >= 0
|
66
|
+
frame = @streams[@packet[:stream_index]].decode_frame(@packet)
|
67
|
+
av_free_packet(@packet)
|
68
|
+
rc = frame ? yield(frame) : true
|
69
|
+
break if rc == false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Get the default stream
|
74
|
+
def default_stream
|
75
|
+
@streams[av_find_default_stream_index(@av_format_ctx)]
|
76
|
+
end
|
77
|
+
|
78
|
+
# See Libav::Stream.seek
|
79
|
+
def seek(p = {})
|
80
|
+
default_stream.seek(p)
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# Generate the Proc that is responsible for releasing our avcodec, avformat
|
86
|
+
# structures.
|
87
|
+
def cleanup_proc(codecs, format)
|
88
|
+
proc do
|
89
|
+
codecs.each { |codec| avcodec_close codec }
|
90
|
+
avformat_close_input(format)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Lookup and initialize the streams
|
95
|
+
def initialize_streams(p={})
|
96
|
+
@streams = @av_format_ctx[:nb_streams].times.map do |i|
|
97
|
+
av_stream = AVStream.new \
|
98
|
+
@av_format_ctx[:streams].get_pointer(i * FFI::Pointer::SIZE)
|
99
|
+
av_codec_ctx = av_stream[:codec]
|
100
|
+
|
101
|
+
case av_codec_ctx[:codec_type]
|
102
|
+
when :video
|
103
|
+
Libav::Stream::Video.new(:reader => self,
|
104
|
+
:av_stream => av_stream,
|
105
|
+
:pixel_format => p[:pixel_format],
|
106
|
+
:width => p[:width],
|
107
|
+
:height => p[:height])
|
108
|
+
else
|
109
|
+
Libav::Stream::Unsupported.new(:reader => self,
|
110
|
+
:av_stream => av_stream)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/lib/libav/stream.rb
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
require 'ffi/libav'
|
2
|
+
|
3
|
+
# Generic Stream class. Most of the logic resides in Libav::Stream::Video.
|
4
|
+
module Libav::Stream
|
5
|
+
include FFI::Libav
|
6
|
+
|
7
|
+
attr_reader :reader, :av_stream, :av_codec_ctx
|
8
|
+
|
9
|
+
def initialize(p={})
|
10
|
+
@reader = p[:reader] or raise ArgumentError, "no :reader"
|
11
|
+
@av_stream = p[:av_stream] or raise ArgumentError, "no :av_stream"
|
12
|
+
@av_codec_ctx = @av_stream[:codec]
|
13
|
+
|
14
|
+
# open the codec
|
15
|
+
codec = avcodec_find_decoder(@av_codec_ctx[:codec_id]) or
|
16
|
+
raise RuntimeError, "No decoder found for #{@av_codec_ctx[:codec_id]}"
|
17
|
+
avcodec_open2(@av_codec_ctx, codec, nil) == 0 or
|
18
|
+
raise RuntimeError, "avcodec_open() failed"
|
19
|
+
end
|
20
|
+
|
21
|
+
def discard=(value)
|
22
|
+
@av_stream[:discard] = value
|
23
|
+
end
|
24
|
+
|
25
|
+
def discard
|
26
|
+
@av_stream[:discard]
|
27
|
+
end
|
28
|
+
|
29
|
+
def type
|
30
|
+
@av_codec_ctx[:codec_type]
|
31
|
+
end
|
32
|
+
|
33
|
+
def index
|
34
|
+
@av_stream[:index]
|
35
|
+
end
|
36
|
+
|
37
|
+
def decode_frame(packet)
|
38
|
+
return false
|
39
|
+
raise NotImplementedError, "decode_frame() not defined for #{self.class}"
|
40
|
+
end
|
41
|
+
|
42
|
+
# Loop through each frame of this stream
|
43
|
+
def each_frame(&block)
|
44
|
+
@reader.each_frame { |frame| yield frame if frame.stream == self }
|
45
|
+
end
|
46
|
+
|
47
|
+
# Get the next frame in the stream
|
48
|
+
def next_frame
|
49
|
+
frame = nil
|
50
|
+
each_frame { |f| frame = f; break }
|
51
|
+
frame
|
52
|
+
end
|
53
|
+
|
54
|
+
# Skip some +n+ frames in the stream
|
55
|
+
def skip_frames(n)
|
56
|
+
raise RuntimeError, "Cannot skip frames when discarding all frames" if
|
57
|
+
discard == :all
|
58
|
+
each_frame { |f| n -= 1 != 0 }
|
59
|
+
end
|
60
|
+
|
61
|
+
# Seek to a specific location within the stream; the location can be either
|
62
|
+
# a PTS value or an absolute byte position.
|
63
|
+
#
|
64
|
+
# Arguments:
|
65
|
+
# [:pts] PTS location
|
66
|
+
# [:pos] Byte location
|
67
|
+
# [:backward] Seek backward
|
68
|
+
# [:any] Seek to non-key frames
|
69
|
+
#
|
70
|
+
def seek(p={})
|
71
|
+
p = { :pts => p } unless p.is_a? Hash
|
72
|
+
|
73
|
+
raise ArgumentError, ":pts and :pos are mutually exclusive" \
|
74
|
+
if p[:pts] and p[:pos]
|
75
|
+
|
76
|
+
pos = p[:pts] || p[:pos]
|
77
|
+
flags = 0
|
78
|
+
flags |= AVSEEK_FLAG_BYTE if p[:pos]
|
79
|
+
flags |= AVSEEK_FLAG_BACKWARD if p[:backward]
|
80
|
+
flags |= AVSEEK_FLAG_ANY if p[:any]
|
81
|
+
|
82
|
+
rc = av_seek_frame(@reader.av_format_ctx, @av_stream[:index], pos, flags)
|
83
|
+
raise RuntimeError, "av_seek_frame() failed, %d" % rc if rc < 0
|
84
|
+
true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class Libav::Stream::Video
|
89
|
+
include Libav::Stream
|
90
|
+
|
91
|
+
attr_reader :raw_frame, :width, :height, :pixel_format, :reader
|
92
|
+
|
93
|
+
def initialize(p={})
|
94
|
+
super(p)
|
95
|
+
|
96
|
+
# Handle frame width and height and setup any scaling necessary
|
97
|
+
@width = p[:widht] || @av_codec_ctx[:width]
|
98
|
+
@height = p[:height] || @av_codec_ctx[:height]
|
99
|
+
@pixel_format = p[:pixel_format] || @av_codec_ctx[:pix_fmt]
|
100
|
+
init_scaling
|
101
|
+
|
102
|
+
# Our frame structure for decoding the frame
|
103
|
+
@raw_frame = Libav::Frame::Video.new :stream => self, :alloc => false,
|
104
|
+
:width => @av_codec_ctx[:width],
|
105
|
+
:height => @av_codec_ctx[:height],
|
106
|
+
:pixel_format =>
|
107
|
+
@av_codec_ctx[:pix_fmt]
|
108
|
+
|
109
|
+
# Pointer used to denote that decode frame was successful
|
110
|
+
@frame_finished = FFI::MemoryPointer.new :int
|
111
|
+
|
112
|
+
# Elaborate schemes to capture an accurate pts value. When the buffer for
|
113
|
+
# the frame is allocated, we pull the dts from the last packet processed
|
114
|
+
# (set in decode_frame()), and set it in the opaque field of the frame.
|
115
|
+
#
|
116
|
+
# Since we may be running on a 32-bit platform, we can't just shove the
|
117
|
+
# 64-bit dts in the :opaque pointer, so we have to alloc some space for the
|
118
|
+
# address. Instead of allocating and freeing repeatedly, we're going to
|
119
|
+
# alloc it once now and reuse it for each decoded frame.
|
120
|
+
@last_dts = nil
|
121
|
+
@opaque = FFI::MemoryPointer.new :uint64
|
122
|
+
|
123
|
+
@av_codec_ctx[:get_buffer] = \
|
124
|
+
FFI::Function.new(:int, [AVCodecContext.ptr, AVFrame.ptr]) do |ctx,frame|
|
125
|
+
|
126
|
+
# Use the default method to get the buffer
|
127
|
+
ret = avcodec_default_get_buffer(ctx, frame)
|
128
|
+
|
129
|
+
# Update the :opaque field point at a copy of the last pts we've seen.
|
130
|
+
@opaque.write_int64 @last_dts
|
131
|
+
frame[:opaque] = @opaque
|
132
|
+
|
133
|
+
ret
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def fps
|
138
|
+
@av_stream[:r_frame_rate]
|
139
|
+
end
|
140
|
+
|
141
|
+
# Set the +width+ of the frames returned by +decode_frame+
|
142
|
+
def width=(width)
|
143
|
+
@width = width
|
144
|
+
init_scaling
|
145
|
+
end
|
146
|
+
|
147
|
+
# Set the +height+ of the frames returned by +decode_frame+
|
148
|
+
def height=(height)
|
149
|
+
@height = height
|
150
|
+
init_scaling
|
151
|
+
end
|
152
|
+
|
153
|
+
# Set the +pixel format+ of the frames returned by +decode_frame+
|
154
|
+
def pixel_format=(pixel_format)
|
155
|
+
@pixel_format = pixel_format
|
156
|
+
init_scaling
|
157
|
+
end
|
158
|
+
|
159
|
+
# Called by Libav::Reader.each_frame to decode each frame
|
160
|
+
def decode_frame(packet)
|
161
|
+
# initialize_scaling unless @scaling_initialized
|
162
|
+
|
163
|
+
@last_dts = packet[:dts]
|
164
|
+
avcodec_get_frame_defaults(@raw_frame.av_frame)
|
165
|
+
rc = avcodec_decode_video2(@av_codec_ctx, @raw_frame.av_frame,
|
166
|
+
@frame_finished, packet)
|
167
|
+
raise RuntimeError, "avcodec_decode_video2() failed, rc=#{rc}" if rc < 0
|
168
|
+
return if @frame_finished.read_int == 0
|
169
|
+
|
170
|
+
@raw_frame.pts = @raw_frame.av_frame[:opaque].read_int64
|
171
|
+
@raw_frame.number = @av_codec_ctx[:frame_number].to_i
|
172
|
+
|
173
|
+
return @raw_frame unless @swscale_ctx
|
174
|
+
|
175
|
+
@raw_frame.scale(:scale_ctx => @swscale_ctx,
|
176
|
+
:output_frame => @scaled_frame)
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
def init_scaling
|
182
|
+
sws_freeContext(@swscale_ctx) unless @swscale_ctx.nil?
|
183
|
+
@swscale_ctx = nil
|
184
|
+
@scaled_frame = nil
|
185
|
+
|
186
|
+
return if @width == @av_codec_ctx[:width] &&
|
187
|
+
@height == @av_codec_ctx[:height] &&
|
188
|
+
@pixel_format == @av_codec_ctx[:pix_fmt]
|
189
|
+
|
190
|
+
@swscale_ctx = sws_getContext(@av_codec_ctx[:width],
|
191
|
+
@av_codec_ctx[:height],
|
192
|
+
@av_codec_ctx[:pix_fmt],
|
193
|
+
@width, @height, @pixel_format,
|
194
|
+
SWS_BICUBIC, nil, nil, nil) or
|
195
|
+
raise NoMemoryError, "sws_getContext() failed"
|
196
|
+
|
197
|
+
@scaled_frame = Libav::Frame::Video.new(:width => @width,
|
198
|
+
:height => @height,
|
199
|
+
:pixel_format => @pixel_format,
|
200
|
+
:stream => self)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
class Libav::Stream::Unsupported
|
205
|
+
include Libav::Stream
|
206
|
+
|
207
|
+
def initialize(p={})
|
208
|
+
super(p)
|
209
|
+
self.discard = :all
|
210
|
+
end
|
211
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ffi-libav
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- David M. Lary
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-01-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.5'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.5'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: ffi
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description:
|
63
|
+
email:
|
64
|
+
- dmlary@gmail.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- Gemfile
|
71
|
+
- LICENSE
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- examples/extract_keyframes.rb
|
75
|
+
- examples/read_file.rb
|
76
|
+
- examples/seek.rb
|
77
|
+
- ffi-libav.gemspec
|
78
|
+
- lib/ffi-libav.rb
|
79
|
+
- lib/ffi/libav.i
|
80
|
+
- lib/ffi/libav.rb
|
81
|
+
- lib/libav/frame.rb
|
82
|
+
- lib/libav/reader.rb
|
83
|
+
- lib/libav/stream.rb
|
84
|
+
- lib/libav/version.rb
|
85
|
+
homepage: https://github.com/dmlary/ffi-libav
|
86
|
+
licenses:
|
87
|
+
- BSD 3-Clause
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ! '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
segments:
|
99
|
+
- 0
|
100
|
+
hash: 2735303191728479060
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ! '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
segments:
|
108
|
+
- 0
|
109
|
+
hash: 2735303191728479060
|
110
|
+
requirements: []
|
111
|
+
rubyforge_project:
|
112
|
+
rubygems_version: 1.8.24
|
113
|
+
signing_key:
|
114
|
+
specification_version: 3
|
115
|
+
summary: Ruby FFI bindings and wrapper for Libav
|
116
|
+
test_files: []
|