ruby-ffprobe 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ .config
2
+ InstalledFiles
3
+ *.gem
4
+ doc
5
+ *.swp
6
+ test/testcases/source.ogv
7
+ coverage
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Philip Garrett.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a
4
+ copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included
12
+ in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,38 @@
1
+ = FFProbe - ffprobe wrapper for Ruby
2
+
3
+ FFprobe is a simple multimedia streams analyzer with a command-line
4
+ interface based on the FFmpeg project libraries. This is a ruby
5
+ interface to that command-line program.
6
+
7
+ == You will need
8
+
9
+ * A working Ruby installation
10
+ * A working ffprobe installation (http://sourceforge.net/projects/ffprobe/)
11
+ * A sane build environment
12
+
13
+ == Author
14
+
15
+ * Philip Garrett <philgarr at gmail.com>
16
+
17
+ == Copyright and License
18
+
19
+ Copyright (c) 2010 Philip Garrett.
20
+
21
+ Permission is hereby granted, free of charge, to any person obtaining a
22
+ copy of this software and associated documentation files (the
23
+ "Software"), to deal in the Software without restriction, including
24
+ without limitation the rights to use, copy, modify, merge, publish,
25
+ distribute, sublicense, and/or sell copies of the Software, and to
26
+ permit persons to whom the Software is furnished to do so, subject to
27
+ the following conditions:
28
+
29
+ The above copyright notice and this permission notice shall be included
30
+ in all copies or substantial portions of the Software.
31
+
32
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
33
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
34
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
35
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
36
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
37
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
38
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,87 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rcov/rcovtask'
5
+
6
+ begin
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |gemspec|
9
+ gemspec.name = "ruby-ffprobe"
10
+ gemspec.summary = "Ruby wrapper for FFprobe multimedia analyzer"
11
+ gemspec.description = File.read(File.join(File.dirname(__FILE__),"README.rdoc"))
12
+ gemspec.email = "philgarr@gmail.com"
13
+ gemspec.homepage = "http://github.com/kingpong/ruby-ffprobe"
14
+ gemspec.authors = ["Philip Garrett"]
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ namespace "ffprobe" do
22
+ task "exec" do
23
+ next if ENV["FFPROBE"]
24
+ ENV['PATH'].split(":").each do |dir|
25
+ fqpath = File.join(dir,"ffprobe")
26
+ if File.executable?(fqpath)
27
+ ENV['FFPROBE'] = fqpath
28
+ break
29
+ end
30
+ end
31
+ next if ENV["FFPROBE"]
32
+ raise "Could not find ffprobe in your path. You may set the environment variable FFPROBE to point to your ffprobe executable"
33
+ end
34
+ end
35
+
36
+ task "set_testopts_verbose" do
37
+ ENV["TESTOPTS"] = '-v'
38
+ end
39
+
40
+ Rake::TestTask.new do |t|
41
+ t.libs << "test"
42
+ t.test_files = FileList['test/**/test*.rb']
43
+ t.verbose = true
44
+ end
45
+
46
+ desc "Run tests with verbosity enabled"
47
+ task "vtest" => [:set_testopts_verbose, :test]
48
+
49
+ namespace "test" do
50
+ desc "Generate ffprobe output test cases"
51
+ task "cases" => ["ffprobe:exec","test/testcases/source.ogv"] do |t|
52
+ require "lib/ffprobe/safe_pipe"
53
+ dest_dir = File.expand_path("test/testcases")
54
+ source = File.expand_path(t.prerequisites.last)
55
+ variations = { "no_args" => [] }
56
+ %w{files frames packets streams tags}.each do |opt|
57
+ variations[opt] = ["-show_#{opt}"]
58
+ variations["pretty_#{opt}"] = ["-show_#{opt}", "-pretty"]
59
+ end
60
+ variations["files_streams_tags"] = ["-show_files", "-show_streams", "-show_tags"]
61
+ variations["pretty_files_streams_tags"] = ["-show_files", "-show_streams", "-show_tags", "-pretty"]
62
+ variations.each_pair do |name,args|
63
+ FFProbe::SafePipe.new(ENV["FFPROBE"], *(args + [source])).run do |pipe|
64
+ File.open(File.join(dest_dir,"#{name}.testcase"),"w") do |output|
65
+ output.write pipe.read
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ desc "Cleanup test output and testcases"
72
+ task "clean" do
73
+ Dir["test/testcases/*.testcase"].each { |file| File.unlink(file) }
74
+ end
75
+ end
76
+
77
+ Rcov::RcovTask.new("coverage") do |t|
78
+ t.libs << "test"
79
+ t.test_files = FileList["test/**/test_*.rb"]
80
+ t.output_dir = "coverage"
81
+ t.verbose = true
82
+ t.rcov_opts << '--exclude /gems/'
83
+ end
84
+
85
+ task "rcov" => "coverage"
86
+
87
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/lib/ffprobe.rb ADDED
@@ -0,0 +1,106 @@
1
+ #
2
+ # ffprobe.rb
3
+ #
4
+ # Copyright (c) 2010 Philip Garrett.
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a
7
+ # copy of this software and associated documentation files (the
8
+ # "Software"), to deal in the Software without restriction, including
9
+ # without limitation the rights to use, copy, modify, merge, publish,
10
+ # distribute, sublicense, and/or sell copies of the Software, and to
11
+ # permit persons to whom the Software is furnished to do so, subject to
12
+ # the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be included
15
+ # in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+ #
25
+
26
+ require 'ffprobe/entity'
27
+ require 'ffprobe/file_info'
28
+ require 'ffprobe/frame_info'
29
+ require 'ffprobe/packet_info'
30
+ require 'ffprobe/parser'
31
+ require 'ffprobe/result'
32
+ require 'ffprobe/safe_pipe'
33
+ require 'ffprobe/stream_info'
34
+ require 'ffprobe/tags_info'
35
+
36
+ class FFProbe
37
+
38
+ class << self
39
+ attr_accessor :executable
40
+ end
41
+ self.executable ||= "ffprobe"
42
+
43
+ class InvalidArgument < StandardError; end
44
+
45
+ FEATURES = [ :read_packets, :read_frames, :show_files, :show_frames, :show_packets, :show_streams, :show_tags, :pretty ]
46
+ FEATURES.each {|feature| attr_accessor(feature) }
47
+
48
+ DEFAULT_FEATURES = [:show_streams, :show_tags]
49
+
50
+ attr_accessor :features
51
+ def features=(desired_features)
52
+ @features = desired_features.each {|feature|
53
+ raise(InvalidArgument, "Unrecognized feature #{feature}") unless FEATURES.include?(feature)
54
+ }
55
+ end
56
+
57
+ attr_accessor :units
58
+ alias :units? :units
59
+ def units=(bool)
60
+ require 'ffprobe/unit' if bool
61
+ @units = bool
62
+ end
63
+
64
+ def initialize(*desired_features)
65
+ desired_features = DEFAULT_FEATURES if desired_features.empty?
66
+ self.features = desired_features.reject {|f| f == :units }
67
+ self.units = desired_features.include?(:units)
68
+ end
69
+
70
+ def probe(filename)
71
+ params = features.map {|f| "-#{f.to_s}" }
72
+ params << filename
73
+ result = nil
74
+ pipe = SafePipe.new(executable,*params)
75
+ pipe.run do |stream|
76
+ result = Result.from_stream(stream, self.units?)
77
+ end
78
+ result.success = pipe.success?
79
+ result.pretty = self.pretty? if result
80
+ result
81
+ end
82
+
83
+ def pretty?
84
+ features.include?(:pretty)
85
+ end
86
+
87
+ private
88
+
89
+ def executable
90
+ self.class.executable
91
+ end
92
+
93
+ end
94
+
95
+
96
+
97
+
98
+
99
+
100
+
101
+
102
+
103
+
104
+
105
+
106
+
@@ -0,0 +1,33 @@
1
+ class FFProbe
2
+ class Entity
3
+
4
+ def self.from_hash(hash,units=false)
5
+ target = new
6
+ target.units = units
7
+ hash.each {|k,v| target.__send__("#{k}=", v) }
8
+ target
9
+ end
10
+
11
+ attr_accessor :units
12
+ alias :units? :units
13
+
14
+ def self.units(mapping)
15
+ mapping.each do |property,unit|
16
+ self.class_eval <<-EOF
17
+ def #{property}=(val)
18
+ if units?
19
+ if val.nil?
20
+ @#{property} = nil
21
+ else
22
+ @#{property} = Unit.new("\#{val} #{unit}")
23
+ end
24
+ else
25
+ @#{property} = val
26
+ end
27
+ end
28
+ EOF
29
+ end
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,14 @@
1
+ class FFProbe
2
+ class FileInfo < Entity
3
+
4
+ attr_accessor :filename, :nb_streams, :demuxer_name, :demuxer_long_name,
5
+ :start_time, :duration, :size, :bit_rate,
6
+ :probed_size, :probed_nb_pkts, :probed_nb_frames
7
+
8
+ units :start_time => "s",
9
+ :duration => "s",
10
+ :size => "byte",
11
+ :bit_rate => "bit/s"
12
+
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ class FFProbe
2
+ class FrameInfo < Entity
3
+
4
+ attr_accessor :codec_type, :pict_type, :quality, :coded_picture_number, :display_picture_number,
5
+ :interlaced_frame, :repeat_pict, :reference, :samples_size, :stream_index,
6
+ :size, :pkt_pts, :pkt_dts, :pkt_duration, :file_pkt_nb, :stream_pkt_nb, :pkt_flag_key
7
+
8
+ units :size => "byte",
9
+ :pkt_pts => "s",
10
+ :pkt_dts => "s",
11
+ :pkt_duration => "s",
12
+ :samples_size => "byte"
13
+
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ class FFProbe
2
+ class PacketInfo < Entity
3
+
4
+ attr_accessor :codec_type, :stream_index, :pts, :pts_time, :dts, :dts_time,
5
+ :size, :file_pkt_nb, :stream_pkt_nb, :duration_ts, :duration_time, :flag_key
6
+
7
+ units :pts_time => "s",
8
+ :dts_time => "s",
9
+ :size => "byte",
10
+ :duration_time => "s"
11
+
12
+ end
13
+ end
@@ -0,0 +1,27 @@
1
+ class FFProbe
2
+ class Parser
3
+
4
+ def parse_stream(io)
5
+ result = {}
6
+ dest = nil
7
+ io.each_line do |line|
8
+ # $stderr.puts line
9
+ line.chomp!
10
+ case line
11
+ when /\A\[(\w+)\]\Z/
12
+ (result[$1.to_sym] ||= []).push( dest = {} )
13
+ when /\A\[\/(\w+)\]\Z/
14
+ # ignored, stanzas can't nest.
15
+ when /\A(\w+)=(.*)/
16
+ if dest
17
+ dest[$1.to_sym] = $2 == "N/A" ? nil : $2.strip
18
+ end
19
+ else
20
+ (result[:EXTRA] ||= []).push(line)
21
+ end
22
+ end
23
+ result
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,107 @@
1
+
2
+ class FFProbe
3
+ class Result
4
+
5
+ def self.from_stream(io,units=false)
6
+ from_hash(Parser.new.parse_stream(io), units)
7
+ end
8
+
9
+ def self.from_hash(hash,units=false)
10
+ target = new
11
+ target.units = units
12
+ hash.each do |key,value|
13
+ case key
14
+ when :FILE
15
+ target.file = FileInfo.from_hash(value[0], units)
16
+ when :FRAME
17
+ target.frames += value.map {|f| FrameInfo.from_hash(f, units) }
18
+ when :PACKET
19
+ target.packets += value.map {|p| PacketInfo.from_hash(p, units) }
20
+ when :STREAM
21
+ target.streams += value.map {|s| StreamInfo.from_hash(s, units) }
22
+ when :TAGS
23
+ target.tags = TagsInfo.from_hash(value[0], units)
24
+ when :EXTRA
25
+ target.extra = Array.new(value)
26
+ end
27
+ end
28
+ target
29
+ end
30
+
31
+ attr_accessor :file
32
+ attr_accessor :frames
33
+ attr_accessor :packets
34
+ attr_accessor :streams
35
+ attr_accessor :tags
36
+ attr_accessor :extra
37
+
38
+ attr_accessor :pretty
39
+ alias :pretty? :pretty
40
+
41
+ attr_accessor :success
42
+ alias :success? :success
43
+
44
+ attr_accessor :units
45
+ alias :units? :units
46
+
47
+ def initialize
48
+ @file = FileInfo.new
49
+ @frames = []
50
+ @packets = []
51
+ @streams = []
52
+ @tags = TagsInfo.new
53
+ @extra = []
54
+ end
55
+
56
+ def has_video?
57
+ @streams.first {|s| s.codec_type == "video" }
58
+ end
59
+
60
+ def video?
61
+ @streams.length == 1 && has_video?
62
+ end
63
+
64
+ def has_audio?
65
+ @streams.first {|s| s.codec_type == "audio" }
66
+ end
67
+
68
+ def audio?
69
+ @streams.length == 1 && has_audio?
70
+ end
71
+
72
+ end
73
+ end
74
+
75
+
76
+
77
+
78
+
79
+
80
+
81
+
82
+
83
+
84
+
85
+
86
+
87
+
88
+
89
+
90
+
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+
101
+
102
+
103
+
104
+
105
+
106
+
107
+ #