mediainfo 0.7.2 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a35ae85e3e7fa526c49c97050a6121e744f51e4db9079457d2eb5e6da5f5ca01
4
+ data.tar.gz: 4789c91354bcd189b99951c2cc424e5f4bdf05f18e01563487306e54cdc90bd2
5
+ SHA512:
6
+ metadata.gz: 170f8c1656897087b1fceb1dc82cf63e390d466b8d721e19bd2065f62016eb5972bcff5c0018e820e68bb6b64705bd888c2c04773c13eb161abe72f318b4bb7b
7
+ data.tar.gz: fa98fbcadf950ac6bba432228370763fa2b069aec50ac39ff6a9c574fa6637eafdb217dded2a41045c0812c6943e0d207c91f2c2b7ff18e05aa58bbb504c0580
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ Rakefile.cloud
2
+ pkg
3
+ .DS_Store
4
+ .irbrc
5
+ /.bundle/
6
+ /.yardoc
7
+ /_yardoc/
8
+ /coverage/
9
+ /doc/
10
+ /pkg/
11
+ /spec/reports/
12
+ /tmp/
13
+ .idea
14
+
15
+ # rspec failure tracking
16
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/Changelog CHANGED
@@ -1,6 +1,22 @@
1
- v0.7.2 More Stream Support
2
- - Added support for Text streams [Robert Mrasek]
3
- - Added support for Menu streams [Ned Campion]
1
+ v1.0.0
2
+ - Rewrite of foundation
3
+ - Module name change to MediaInfo to match convention (everything else does MediaInfo with an uppercase I)
4
+ - Readme rewrite + examples
5
+ - Rspec for testing, all features tested + new xml to test with
6
+ - Removal of all double quotes for single quotes, unless interpolation is needed
7
+ - Removal of underscore method that isn't used
8
+ - Variable name changes for readability and DRY
9
+ - Hpricot is EOL, removing code to support it
10
+ - Removal of all old testing, Rakefile contents
11
+ - Every method missing exception will return nil (if the type exists)
12
+ - New .track_types for visibility in client application
13
+ - pull/18 (Eliminated attr_reader needing to be updated)
14
+ - pull/12 (Eliminated need to add new Track/Streams)
15
+ - pull/15 (URL parsing added)
16
+ - Supports the use of <ID> element value (if integer/existent) when generating "track#"
17
+ - Supports <extra> (Apple/iPhone video)
18
+ - Parameter/element sanitization + conversion of string with integer/float in it to integer/float
19
+ - Standardized attribute names under tracks & added yml file to keep track
4
20
 
5
21
  v0.7.1 Tweakage
6
22
  - Removed Mediainfo#parsed_response accessor which always returned nil after
data/GemFile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/greatseth/#{repo_name}" }
4
+
5
+ gemspec
data/LICENSE CHANGED
@@ -1,18 +1,9 @@
1
- This program is free software. It comes without any warranty, to
2
- the extent permitted by applicable law. You can redistribute it
3
- and/or modify it under the terms of the Do What The Fuck You Want
4
- To Public License, Version 2, as published by Sam Hocevar. See
5
- http://sam.zoy.org/wtfpl/COPYING for more details.
1
+ MIT License
6
2
 
7
- DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
8
- Version 2, December 2004
3
+ Copyright (c) <year> <copyright holders>
9
4
 
10
- Copyright (C) 2009 Seth Thomas Rasmussen
11
- Everyone is permitted to copy and distribute verbatim or modified
12
- copies of this license document, and changing it is allowed as long
13
- as the name is changed.
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
14
6
 
15
- DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
16
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
17
8
 
18
- 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # MediaInfo
2
+
3
+ MediaInfo is a class wrapping [the mediainfo CLI](http://mediainfo.sourceforge.net).
4
+
5
+ ## Installation
6
+
7
+ $ gem install mediainfo
8
+
9
+ ## Usage
10
+
11
+ #### Parsing raw XML
12
+ media_info = MediaInfo.from(File.open('iphone6+_video.mov.xml').read)
13
+ #### Handling a local file
14
+ media_info = MediaInfo.from('~/Desktop/test.mov')
15
+ #### Handling a URL
16
+ media_info = MediaInfo.from('http://techslides.com/demos/sample-videos/small.mp4')
17
+
18
+ You can specify an alternate path for the MediaInfo Binary:
19
+
20
+ ENV['MEDIAINFO_PATH'] = "/opt/local/bin/mediainfo"
21
+
22
+ Once you have an MediaInfo object, you can start inspecting tracks:
23
+
24
+ media_info.track_types => ['general','video','audio']
25
+ media_info.track_types.count => 3
26
+ media_info.video? => true
27
+ media_info.image? => nil
28
+ media_info.image.filesize => MethodNotFound exception
29
+
30
+ When inspecting specific types of tracks, you have a couple general API options. The
31
+ first approach assumes one track of a given type, a common scenario in many video files,
32
+ for example:
33
+
34
+ media_info.video.count => 1
35
+ media_info.video.duration => 120 (seconds)
36
+
37
+ Sometimes you'll have more than one track of a given type:
38
+ - The first track type name, or any track type with <ID>1</ID> will not contain '1'
39
+
40
+
41
+ media_info.track_types => ['general','video','video2','audio','other','other2']
42
+ media_info.track_types.count => 5
43
+ media_info.video? => true
44
+ media_info.image? => nil
45
+ media_info.video.count => 1
46
+ media_info.video.duration => 29855000
47
+ media_info.video.display_aspect_ratio => 1.222
48
+ media_info.other.count => 2
49
+ media_info.video2.duration => 29855000
50
+
51
+ - Note that the above automatically converts MediaInfo Strings into Time, Integer, and Float objects:
52
+
53
+
54
+ media_info.video.encoded_date.class => Time
55
+ media_info.video2.duration.class => Integer
56
+ media_info.video.display_aspect_ratio.class => Float
57
+
58
+ - Any track attribute name with "date" and matching /\d-/ will be converted using Time.parse:
59
+
60
+
61
+ media_info.video.encoded_date => 2018-03-30 12:12:08 -0400
62
+ media_info.video.customdate => 2016-02-10 01:00:00 -0600
63
+
64
+ - .duration and .overall_duration will be returned as milliseconds AS LONG AS the Duration and Overall_Duration match one of the expected units:
65
+ - h (\<Duration>15h\</Duration>) (hour)
66
+ - hour (\<Duration>15hour\</Duration>)
67
+ - mn (\<Duration>15hour 6mn\</Duration>) (minute)
68
+ - m (\<Duration>15hour 6m\</Duration>) (minute)
69
+ - min (\<Duration>15hour 6min\</Duration>) (minute)
70
+ - s (\<Duration>15hour 6min 59s\</Duration>) (second)
71
+ - sec (\<Duration>15hour 6min 59sec\</Duration>) (second)
72
+ - ms (\<Duration>15hour 6min 59sec 301ms\</Duration>) (milliseconds)
73
+ - [Submit an issue to add more!](https://github.com/greatseth/mediainfo/issues)
74
+
75
+
76
+ media_info.video.duration => 9855000 (\<Duration>15s 164ms\</Duration>)
77
+ media_info.video.duration => 17196000 (\<Duration>36s 286ms\</Duration>)
78
+
79
+ - We standardize the naming of several Attributes:
80
+ - You can review lib/attribute_standardization_rules.yml to see them all
81
+
82
+
83
+ media_info.video.bitrate => "41.2 Mbps" (\<Bit_rate>41.2 Mbps\</Bit_rate>)
84
+ media_info.video.bit_rate => nil (\<Bit_rate>41.2 Mbps\</Bit_rate>)
85
+ media_info.general.filesize => "11.5 MiB" (\<File_size>11.5 MiB\</File_size>
86
+
87
+
88
+ In order to support all possible MediaInfo variations, you may see the following situation:
89
+
90
+ media_info.track_types => ['general','video','video5','audio','other','other2']
91
+
92
+ The track type media_info.video5 is available, but no video2, 3, and 4. This is because the MediaInfo from the video has:
93
+
94
+ <track type="Video">
95
+ <ID>1</ID>
96
+ ...
97
+ <track type="Video">
98
+ <ID>5</ID>
99
+ ...
100
+
101
+ *The ID will take priority for labeling.* Else if no ID exists, you'll see consecutive numbering for duplicate tracks in the Media.
102
+
103
+ Any second level attributes are also available:
104
+
105
+ MediaInfo.from('~/Desktop/test.mov').general.extra
106
+ => #<MediaInfo::Tracks::Attributes::Extra:0x00007fa89f13aa98
107
+ @com_apple_quicktime_creationdate=2018-03-30 08:12:08 -0400,
108
+ @com_apple_quicktime_location_iso6709="+39.0286-077.3958+095.957/",
109
+ @com_apple_quicktime_make="Apple",
110
+ @com_apple_quicktime_model=0,
111
+ @com_apple_quicktime_software=11.2>
112
+
113
+ REXML is used as the XML parser by default. If you'd like, you can
114
+ configure Mediainfo to use Nokogiri instead:
115
+
116
+ * define the `MEDIAINFO_XML_PARSER` environment variable to be the
117
+ name of the parser as you'd pass to a :gem or :require call.
118
+
119
+ e.g. `export MEDIAINFO_XML_PARSER=nokogiri`
120
+
121
+ Once you've got an instance setup, you can call numerous methods to get
122
+ a variety of information about a file. Some attributes may be present
123
+ for some files where others are not, but any supported attribute
124
+ should at least return `nil`.
125
+
126
+ ## Requirements
127
+
128
+ Gem version 1.0.0 has been tested on v18.03.1
129
+ Gem versions < 1.0.0 require at least: MediaInfoLib v0.7.25
130
+ Gem versions <= 0.5.1 worked against MediaInfoLib v0.7.11, which did not
131
+ generate XML output, and is no longer supported.
132
+
133
+ ## Contributors
134
+
135
+ * Seth Thomas Rasmussen - [http://greatseth.com](http://greatseth.com)
136
+ * Peter Vandenberk - [http://github.com/pvdb](http://github.com/pvdb)
137
+ * Ned Campion - [http://github.com/nedcampion](http://github.com/nedcampion)
138
+ * Daniel Jagszent - [http://github.com/d--j](http://github.com/d--j)
139
+ * Robert Mrasek - [http://github.com/derobo](http://github.com/derobo)
140
+ * Nathan Pierce - [http://github.com/NorseGaud](http://github.com/NorseGaud)
data/Rakefile CHANGED
@@ -1,63 +1,6 @@
1
- require "rubygems"
2
- require "echoe"
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
3
 
4
- class Echoe
5
- def honor_gitignore!
6
- self.ignore_pattern += \
7
- Dir["**/.gitignore"].inject([]) do |pattern,gitignore|
8
- pattern.concat \
9
- File.readlines(gitignore).
10
- map { |line| line.strip }.
11
- reject { |line| "" == line }.
12
- map { |glob|
13
- d = File.dirname(gitignore)
14
- d == "." ? glob : File.join(d, glob)
15
- }
16
- end.flatten.uniq
17
- end
18
- end
4
+ RSpec::Core::RakeTask.new(:spec)
19
5
 
20
- Echoe.new "mediainfo" do |p|
21
- p.description = p.summary = "Mediainfo is a class wrapping the mediainfo CLI (http://mediainfo.sourceforge.net)"
22
- p.author = "Seth Thomas Rasmussen"
23
- p.email = "sethrasmussen@gmail.com"
24
- p.url = "http://greatseth.github.com/mediainfo"
25
- p.ignore_pattern = %w( test/**/* )
26
- p.retain_gemspec = true
27
- p.honor_gitignore!
28
- end
29
-
30
- task :rexml do
31
- ENV.delete "MEDIAINFO_XML_PARSER"
32
- Rake::Task[:test].invoke
33
- end
34
-
35
- task :hpricot do
36
- ENV["MEDIAINFO_XML_PARSER"] = "hpricot"
37
- Rake::Task[:test].invoke
38
- end
39
-
40
- task :nokogiri do
41
- ENV["MEDIAINFO_XML_PARSER"] = "nokogiri"
42
- Rake::Task[:test].invoke
43
- end
44
-
45
- # TODO This doesn't work.
46
- task :all => [:rexml, :nokogiri, :hpricot]
47
-
48
-
49
- task :fixture do
50
- unless file = ENV["FILE"]
51
- puts "Usage: rake mediainfo:fixture file=/path/to/file"
52
- exit
53
- end
54
- fixture = File.expand_path "./test/fixtures/#{File.basename file}.xml"
55
- system "mediainfo #{file} --Output=XML > #{fixture}"
56
- if File.exist? fixture
57
- puts "Generated fixture #{fixture}."
58
- else
59
- puts "Error generating fixture. #{fixture} not created."
60
- end
61
- end
62
-
63
- # require 'github/pages/tasks'
6
+ task :default => :spec
@@ -0,0 +1,18 @@
1
+ # Format:
2
+ # 'ELEMENT_NAME_TO_CHANGE': 'WHAT IT NEEDS TO BE'
3
+ # BitRate will become
4
+ ## Bit_rate -> Bitrate
5
+ Bit_rate: Bitrate
6
+ Bitrate_mode: Bitrate_mode
7
+ Bit_rate_mode: Bitrate_mode
8
+ Overallbitrate: Overall_bitrate
9
+ Overall_bit_rate: Overall_bitrate
10
+ ## *_size -> *size
11
+ File_size: Filesize
12
+ Stream_size: Streamsize
13
+ ## Frame_rate -> Framerate
14
+ Frame_rate: Framerate
15
+ Frame_rate_mode: Framerate_mode
16
+ Maximum_frame_rate: Maximum_framerate
17
+ Displayaspectratio: Display_aspect_ratio
18
+ Pixelaspectratio: Pixel_aspect_ratio
data/lib/mediainfo.rb CHANGED
@@ -1,516 +1,104 @@
1
- require "forwardable"
2
- require "mediainfo/string"
3
- require "mediainfo/attr_readers"
1
+ require 'forwardable'
2
+ require 'net/http'
3
+ require 'mediainfo/errors'
4
+ require 'mediainfo/tracks'
5
+ require 'mediainfo/string'
4
6
 
5
- =begin
6
- # Mediainfo
7
+ module MediaInfo
7
8
 
8
- Mediainfo is a class wrapping [the mediainfo CLI](http://mediainfo.sourceforge.net).
9
-
10
- ## Installation
11
-
12
- $ gem install mediainfo
13
-
14
- ## Usage
15
-
16
- info = Mediainfo.new "/path/to/file"
17
-
18
- That will issue the system call to `mediainfo` and parse the output.
19
- You can specify an alternate path if necessary:
20
-
21
- Mediainfo.path = "/opt/local/bin/mediainfo"
22
-
23
- Once you have an info object, you can start inspecting streams and general metadata.
24
-
25
- info.streams.count # 2
26
- info.audio? # true
27
- info.video? # true
28
- info.image? # false
29
-
30
- When inspecting specific types of streams, you have a couple general API options. The
31
- first approach assumes one stream of a given type, a common scenario in many video files,
32
- for example.
33
-
34
- info.video.count # 1
35
- info.audio.count # 1
36
- info.video.duration # 120 (seconds)
37
-
38
- Sometimes you'll have more than one stream of a given type. Quicktime files can often
39
- contain artifacts like this from somebody editing a more 'normal' file.
40
-
41
- info = Mediainfo.new "funky.mov"
42
-
43
- info.video? # true
44
- info.video.count # 2
45
- info.video.duration # raises SingleStreamAPIError !
46
- info.video[0].duration # 120
47
- info.video[1].duration # 10
48
-
49
- For some more usage examples, please see the very reasonable test suite accompanying the source code
50
- for this library. It contains a bunch of relevant usage examples. More docs in the future.. contributions
51
- *very* welcome!
52
-
53
- Moving on, REXML is used as the XML parser by default. If you'd like, you can
54
- configure Mediainfo to use Hpricot or Nokogiri instead using one of
55
- the following approaches:
56
-
57
- * define the `MEDIAINFO_XML_PARSER` environment variable to be the
58
- name of the parser as you'd pass to a :gem or :require call.
59
-
60
- e.g. `export MEDIAINFO_XML_PARSER=nokogiri`
61
-
62
- * assign to Mediainfo.xml_parser after you've loaded the gem,
63
- following the same naming conventions mentioned previously.
64
-
65
- e.g. `Mediainfo.xml_parser = "hpricot"`
66
-
67
-
68
- Once you've got an instance setup, you can call numerous methods to get
69
- a variety of information about a file. Some attributes may be present
70
- for some files where others are not, but any supported attribute
71
- should at least return `nil`.
72
-
73
- For a list of all possible attributes supported:
74
-
75
- Mediainfo.supported_attributes
76
-
77
- ## Requirements
9
+ # Allow user to set custom mediainfo_path with ENV['MEDIAINFO_PATH']
10
+ def self.location
11
+ ENV['MEDIAINFO_PATH'].nil? ? mediainfo_location = '/usr/local/bin/mediainfo' : mediainfo_location = ENV['MEDIAINFO_PATH']
12
+ raise EnvironmentError, "#{mediainfo_location} cannot be found. Are you sure mediainfo is installed?" unless ::File.exist? mediainfo_location
13
+ return mediainfo_location
14
+ end
78
15
 
79
- This requires at least the following version of the Mediainfo CLI:
80
-
81
- MediaInfo Command line,
82
- MediaInfoLib - v0.7.25
83
-
84
- Previous versions of this gem(<= 0.5.1) worked against v0.7.11, which did not
85
- generate XML output, and is no longer supported.
86
- =end
87
- class Mediainfo
88
- extend Forwardable
89
- extend AttrReaders
90
-
91
- class Error < StandardError; end
92
- class ExecutionError < Error; end
93
- class IncompatibleVersionError < Error; end
94
- class UnknownVersionError < Error; end
95
-
96
- def self.delegate(method_name, stream_type = nil)
97
- if stream_type == :general
98
- def_delegator :"@#{stream_type}_stream", method_name
16
+ # Allow collection of MediaInfo version details
17
+ def self.version
18
+ version ||= `#{location} --Version`[/v([\d.]+)/, 1]
19
+ # Ensure MediaInfo isn't buggy and returns something
20
+ raise UnknownVersionError, 'Unable to determine mediainfo version. ' + "We tried: #{location} --Version." +
21
+ 'Set MediaInfo.path = \'/full/path/of/mediainfo\' if it is not in your PATH.' unless version
22
+ # Ensure you're not using an old version of MediaInfo
23
+ if version < '0.7.25'
24
+ raise IncompatibleVersionError, "Your version of mediainfo, #{version}, " +
25
+ 'is not compatible with this gem. >= 0.7.25 required.'
99
26
  else
100
- def_delegator :"@#{stream_type}_stream", method_name, "#{stream_type}_#{method_name}"
27
+ @version = version
101
28
  end
29
+
102
30
  end
103
-
104
- def self.version
105
- @version ||= `#{version_command}`[/v([\d.]+)/, 1]
106
- end
107
-
108
- def self.version_command
109
- "#{path} --Version"
110
- end
111
-
112
- # AttrReaders depends on this.
113
- def self.supported_attributes; @supported_attributes ||= []; end
114
-
115
- SECTIONS = [:general, :video, :audio, :image, :menu, :text]
116
- NON_GENERAL_SECTIONS = SECTIONS - [:general]
117
-
118
- attr_reader :streams
119
-
120
- # Size of source file as reported by File.size.
121
- # Returns nil if you haven't yet fired off the system command.
122
- def size; File.size(@full_filename) if @full_filename; end
123
-
124
- class StreamProxy
125
- def initialize(mediainfo, stream_type)
126
- unless Mediainfo::SECTIONS.include? stream_type
127
- raise ArgumentError, "invalid stream_type: #{stream_type.inspect}"
128
- end
129
-
130
- @stream_type = stream_type
131
- @mediainfo = mediainfo
132
- @streams = @mediainfo.streams.select { |x| x.send("#{stream_type}?") }
133
- end
134
-
135
- def [](id); @streams[id]; end
136
- def count; @streams.size; end
137
- attr_reader :streams
138
- attr_reader :stream_type
139
-
140
- class SingleStreamAPIError < RuntimeError; end
141
- class NoStreamsForProxyError < NoMethodError; end
142
-
143
- def method_missing(m, *a, &b)
144
- if streams.size > 1
145
- raise SingleStreamAPIError, "You cannot use the single stream, convenience API on a multi-stream file."
146
- else
147
- if relevant_stream = streams.detect { |s| s.respond_to?(m) }
148
- relevant_stream.send(m, *a, &b)
149
- else
150
- raise NoStreamsForProxyError, "there are no :#{stream_type} streams to send :#{m} to"
151
- end
152
- end
31
+
32
+ def self.xml_parser
33
+ ENV['MEDIAINFO_XML_PARSER'].nil? || ENV['MEDIAINFO_XML_PARSER'].to_s.strip.empty? ? xml_parser = 'rexml/document' : xml_parser = ENV['MEDIAINFO_XML_PARSER']
34
+ begin
35
+ require xml_parser
36
+ rescue Gem::LoadError => ex
37
+ raise Gem::LoadError, "Your specified XML parser, #{xml_parser.inspect}, could not be loaded: #{ex.message}"
153
38
  end
39
+ return xml_parser
154
40
  end
155
-
156
- class Stream
157
- class InvalidStreamType < Mediainfo::Error; end
158
-
159
- def self.inherited(stream_type)
160
- stream_type.extend(AttrReaders)
161
-
162
- def stream_type.method_added(method_name)
163
- if stream_type = name[/[^:]+$/][/^(#{SECTIONS.map { |x| x.to_s.capitalize } * '|'})/]
164
- stream_type.downcase!
165
- stream_type = stream_type.to_sym
41
+
42
+
43
+ def self.run(input = nil)
44
+ raise ArgumentError, 'Your input cannot be blank.' if input.nil?
45
+ command = "#{location} #{input} --Output=XML 2>&1"
46
+ raw_response = `#{command}`
47
+ unless $? == 0
48
+ raise ExecutionError, "Execution of '#{command}' failed. #{raw_response.inspect}"
49
+ end
50
+ return raw_response
51
+ end
52
+
53
+ def self.from(input)
54
+ input_guideline_message = 'Bad Input' + "\n" + "Input must be: \n" +
55
+ "A video or xml file location. Example: '~/videos/test_video.mov' or '~/videos/test_video.xml' \n" +
56
+ "A valid URL. Example: 'http://www.site.com/videofile.mov' \n" +
57
+ "Or MediaInfo XML \n"
58
+ if input # User must specify file
59
+ if input.include?('<?xml') # Must be first, else we could parse input (raw xml) with a URL in it and think it's a URL
60
+ return MediaInfo::Tracks.new(input)
61
+ elsif input.downcase.include?('http') || input.downcase.include?('www') # Handle Url Parsing
62
+ @uri = URI(input)
63
+ # Check if URL is valid
64
+ http = ::Net::HTTP.new(@uri.host,@uri.port)
65
+ request = Net::HTTP::Head.new(@uri.request_uri) # Only grab the Headers to be sure we don't try and download the whole file
66
+ response = http.request(request)
67
+ case response
68
+ when Net::HTTPOK
69
+ @escaped_input = URI.escape(@uri.to_s)
166
70
  else
167
- raise "could not determine stream type, please report bug!"
71
+ raise RemoteUrlError, "HTTP call to #{input} is not working!"
168
72
  end
169
-
170
- Mediainfo.delegate(method_name, stream_type)
171
- end
172
- end
173
-
174
- def self.create(stream_type)
175
- raise ArgumentError, "need a stream_type, received #{stream_type.inspect}" if stream_type.nil?
176
-
177
- stream_class_name = "#{stream_type}Stream"
178
-
179
- if Mediainfo.const_defined?(stream_class_name)
180
- Mediainfo.const_get(stream_class_name).new(stream_type)
73
+ elsif input.include?('.xml')
74
+ return MediaInfo::Tracks.new(::File.open(input).read)
75
+ elsif input.include?('/')
76
+ @file = ::File.expand_path input # turns ~/path/to/file into /home/user/path/to/file
77
+ raise ArgumentError, 'You must include a file location.' if @file.nil?
78
+ raise ArgumentError, "need a path to a video file, #{@file} does not exist" unless ::File.exist? @file
79
+ @file_path = ::File.dirname @file
80
+ @filename = ::File.basename @file
81
+ @escaped_input = @file.shell_escape_double_quotes
82
+ # Set variable for returned XML
181
83
  else
182
- raise InvalidStreamType, "bad stream type: #{stream_type.inspect}"
84
+ raise ArgumentError, input_guideline_message
183
85
  end
86
+ return MediaInfo::Tracks.new(MediaInfo.run(@escaped_input))
87
+ else
88
+ raise ArgumentError, input_guideline_message
184
89
  end
185
-
186
- def initialize(stream_type)
187
- raise ArgumentError, "need a stream_type, received #{stream_type.inspect}" if stream_type.nil?
188
-
189
- @stream_type = stream_type.downcase.to_sym
190
-
191
- # TODO @parsed_response is not the best name anymore, but I'm leaving it
192
- # alone to focus on refactoring the interface to the streams
193
- # before I refactor the attribute reader implementations.
194
- @parsed_response = { @stream_type => {} }
195
- end
196
-
197
- attr_reader :parsed_response
198
-
199
- def [](k); @parsed_response[@stream_type][k]; end
200
- def []=(k,v); @parsed_response[@stream_type][k] = v; end
201
-
202
- Mediainfo::SECTIONS.each { |t| define_method("#{t}?") { t == @stream_type } }
203
90
  end
204
-
205
- class GeneralStream < Stream
206
- mediainfo_attr_reader :codec_id, "Codec ID"
207
-
208
- mediainfo_duration_reader :duration
209
-
210
- mediainfo_attr_reader :format
211
- mediainfo_attr_reader :format_profile
212
- mediainfo_attr_reader :format_info
213
- mediainfo_attr_reader :overall_bit_rate
214
- mediainfo_attr_reader :writing_application
215
- mediainfo_attr_reader :writing_library
216
-
217
- mediainfo_date_reader :mastered_date
218
- mediainfo_date_reader :tagged_date
219
- mediainfo_date_reader :encoded_date
220
- end
221
-
222
- class VideoStream < Stream
223
- mediainfo_attr_reader :stream_id, "ID"
224
-
225
- mediainfo_duration_reader :duration
226
-
227
- mediainfo_attr_reader :stream_size
228
- mediainfo_attr_reader :bit_rate
229
- mediainfo_attr_reader :nominal_bit_rate
230
-
231
- mediainfo_attr_reader :bit_rate_mode
232
- def cbr?; video? and "Constant" == bit_rate_mode; end
233
- def vbr?; video? and not cbr?; end
234
-
235
- mediainfo_attr_reader :scan_order
236
- mediainfo_attr_reader :scan_type
237
- def interlaced?; video? and "Interlaced" == scan_type; end
238
- def progressive?; video? and not interlaced? end
239
-
240
- mediainfo_int_reader :resolution
241
-
242
- mediainfo_attr_reader :colorimetry
243
- alias_method :colorspace, :colorimetry
244
-
245
- mediainfo_attr_reader :format
246
- mediainfo_attr_reader :format_info
247
- mediainfo_attr_reader :format_profile
248
- mediainfo_attr_reader :format_version
249
- mediainfo_attr_reader :format_settings_cabac, "Format settings, CABAC"
250
- mediainfo_attr_reader :format_settings_reframes, "Format settings, ReFrames"
251
- mediainfo_attr_reader :format_settings_matrix, "Format settings, Matrix"
252
- # Format settings, BVOP : Yes
253
- # Format settings, QPel : No
254
- # Format settings, GMC : No warppoints
255
- # mediainfo_attr_reader :format_settings_qpel, "Format settings, QPel"
256
- mediainfo_attr_reader :color_primaries
257
- mediainfo_attr_reader :transfer_characteristics
258
- mediainfo_attr_reader :matrix_coefficients
259
91
 
260
- mediainfo_attr_reader :codec_id, "Codec ID"
261
- mediainfo_attr_reader :codec_info, "Codec ID/Info"
262
- alias_method :codec_id_info, :codec_info
263
92
 
264
- mediainfo_attr_reader :frame_rate
265
- def fps; frame_rate[/[\d.]+/].to_f if frame_rate; end
266
- alias_method :framerate, :fps
267
-
268
- mediainfo_attr_reader :minimum_frame_rate
269
- def min_fps; minimum_frame_rate[/[\d.]+/].to_f if video?; end
270
- alias_method :min_framerate, :min_fps
271
-
272
- mediainfo_attr_reader :maximum_frame_rate
273
- def max_fps; maximum_frame_rate[/[\d.]+/].to_f if video?; end
274
- alias_method :max_framerate, :max_fps
275
-
276
- mediainfo_attr_reader :frame_rate_mode
277
-
278
- mediainfo_attr_reader :display_aspect_ratio
279
- # alias_method :display_aspect_ratio, :display_aspect_ratio
280
-
281
- mediainfo_attr_reader :bits_pixel_frame, "Bits/(Pixel*Frame)"
282
-
283
- mediainfo_int_reader :width
284
- mediainfo_int_reader :height
285
-
286
- def frame_size; "#{width}x#{height}" if width or height; end
287
-
288
- mediainfo_date_reader :encoded_date
289
- mediainfo_date_reader :tagged_date
290
-
291
- mediainfo_attr_reader :standard
292
- end
293
-
294
- class AudioStream < Stream
295
- mediainfo_attr_reader :stream_id, "ID"
296
-
297
- mediainfo_duration_reader :duration
298
-
299
- mediainfo_attr_reader :sampling_rate
300
- def sample_rate
301
- return unless rate = sampling_rate_before_type_cast
302
- number = rate.gsub(/[^\d.]+/, "").to_f
303
- number = case rate
304
- when /KHz/ then number * 1000
305
- when /Hz/ then number
306
- else
307
- raise "unhandled sample rate! please report bug!"
308
- end
309
- number.to_i
93
+ def self.set_singleton_method(object,name,parameters)
94
+ # Handle parameters with invalid characters (instance_variable_set throws error)
95
+ name.gsub!('.','_') if name.include?('.') ## period in name
96
+ name.downcase!
97
+ # Create singleton_method
98
+ object.instance_variable_set("@#{name}",parameters)
99
+ object.define_singleton_method name do
100
+ object.instance_variable_get "@#{name}"
310
101
  end
311
- alias_method :sampling_rate, :sample_rate
312
-
313
- mediainfo_attr_reader :stream_size
314
- mediainfo_attr_reader :bit_rate
315
- mediainfo_attr_reader :bit_rate_mode
316
- mediainfo_attr_reader :interleave_duration, "Interleave, duration"
317
-
318
- mediainfo_int_reader :resolution
319
- alias_method :sample_bit_depth, :resolution
320
-
321
- mediainfo_attr_reader :format
322
- mediainfo_attr_reader :format_profile
323
- mediainfo_attr_reader :format_version
324
- mediainfo_attr_reader :format_info, "Format/Info"
325
- mediainfo_attr_reader :format_settings_sbr, "Format settings, SBR"
326
- mediainfo_attr_reader :format_settings_endianness, "Format settings, Endianness"
327
- mediainfo_attr_reader :format_settings_sign, "Format settings, Sign"
328
- mediainfo_attr_reader :codec_id, "Codec ID"
329
- mediainfo_attr_reader :codec_info, "Codec ID/Info"
330
- mediainfo_attr_reader :codec_id_hint
331
- mediainfo_attr_reader :channel_positions
332
-
333
- mediainfo_int_reader :channels, "Channel(s)"
334
- def stereo?; 2 == channels; end
335
- def mono?; 1 == channels; end
336
-
337
- mediainfo_date_reader :encoded_date
338
- mediainfo_date_reader :tagged_date
339
- end
340
-
341
- class ImageStream < Stream
342
- mediainfo_attr_reader :resolution
343
- mediainfo_attr_reader :format
344
-
345
- mediainfo_int_reader :width
346
- mediainfo_int_reader :height
347
-
348
- def frame_size; "#{width}x#{height}" if width or height; end
349
102
  end
350
103
 
351
- class TextStream < Stream
352
- mediainfo_attr_reader :stream_id, "ID"
353
- mediainfo_attr_reader :format
354
- mediainfo_attr_reader :codec_id, "Codec ID"
355
- mediainfo_attr_reader :codec_info, "Codec ID/Info"
356
- end
357
-
358
- class MenuStream < Stream
359
- mediainfo_attr_reader :stream_id, "ID"
360
- mediainfo_date_reader :encoded_date
361
- mediainfo_date_reader :tagged_date
362
- mediainfo_int_reader :delay
363
- end
364
-
365
- Mediainfo::SECTIONS.each do |stream_type|
366
- class_eval %{
367
- def #{stream_type}; @#{stream_type}_proxy ||= StreamProxy.new(self, :#{stream_type}); end
368
- def #{stream_type}?; streams.any? { |x| x.#{stream_type}? }; end
369
- }, __FILE__, __LINE__
370
- end
371
-
372
- ###
373
-
374
- attr_reader :raw_response, :full_filename, :filename, :path, :escaped_full_filename
375
-
376
- ###
377
-
378
- def initialize(full_filename = nil)
379
- unless mediainfo_version
380
- raise UnknownVersionError,
381
- "Unable to determine mediainfo version. " +
382
- "We tried: #{self.class.version_command} " +
383
- "Are you sure mediainfo is installed at #{self.class.path.inspect}? " +
384
- "Set Mediainfo.path = /where/is/mediainfo if it is not in your PATH."
385
- end
386
-
387
- if mediainfo_version < "0.7.25"
388
- raise IncompatibleVersionError,
389
- "Your version of mediainfo, #{mediainfo_version}, " +
390
- "is not compatible with this gem. >= 0.7.25 required."
391
- end
392
-
393
- @streams = []
394
-
395
- if full_filename
396
- @full_filename = File.expand_path full_filename
397
- @path = File.dirname @full_filename
398
- @filename = File.basename @full_filename
399
-
400
- raise ArgumentError, "need a path to a video file, got nil" unless @full_filename
401
- raise ArgumentError, "need a path to a video file, #{@full_filename} does not exist" unless File.exist? @full_filename
402
-
403
- @escaped_full_filename = @full_filename.shell_escape_double_quotes
404
-
405
- self.raw_response = mediainfo!
406
- end
407
- end
408
-
409
- def raw_response=(response)
410
- raise ArgumentError, "raw response is nil" if response.nil?
411
- @raw_response = response
412
- parse!
413
- @raw_response
414
- end
415
-
416
- class << self
417
- attr_accessor :path
418
-
419
- def load_xml_parser!(parser = xml_parser)
420
- begin
421
- gem parser
422
- require parser
423
- rescue Gem::LoadError => e
424
- raise Gem::LoadError,
425
- "your specified XML parser, #{parser.inspect}, could not be loaded: #{e}"
426
- end
427
- end
428
-
429
- attr_reader :xml_parser
430
- def xml_parser=(parser)
431
- load_xml_parser! parser
432
- @xml_parser = parser
433
- end
434
- end
435
-
436
- unless ENV["MEDIAINFO_XML_PARSER"].to_s.strip.empty?
437
- self.xml_parser = ENV["MEDIAINFO_XML_PARSER"]
438
- end
439
-
440
- def path; self.class.path; end
441
- def xml_parser; self.class.xml_parser; end
442
-
443
- def self.default_mediainfo_path!; self.path = "mediainfo"; end
444
- default_mediainfo_path! unless path
445
-
446
- def mediainfo_version; self.class.version; end
447
-
448
- attr_reader :last_command
449
-
450
- def inspect
451
- super.sub(/@raw_response=".+?", @/, %{@raw_response="...", @})
452
- end
453
-
454
- private
455
- def mediainfo!
456
- @last_command = "#{path} #{@escaped_full_filename} --Output=XML"
457
- run_command!
458
- end
459
-
460
- def run_command!
461
- raw_response = `#{@last_command} 2>&1`
462
- unless $? == 0
463
- raise ExecutionError,
464
- "Execution of `#{@last_command}` failed: #{raw_response.inspect}"
465
- end
466
- raw_response
467
- end
468
-
469
- def parse!
470
- if xml_parser
471
- self.class.load_xml_parser!
472
- else
473
- require "rexml/document"
474
- end
475
-
476
- case xml_parser
477
- when "nokogiri"
478
- Nokogiri::XML(@raw_response).xpath("//track").each { |t|
479
- s = Stream.create(t['type'])
480
- t.xpath("*").each do |c|
481
- s[key_for(c)] = c.content.strip
482
- end
483
- @streams << s
484
- }
485
- when "hpricot"
486
- Hpricot::XML(@raw_response).search("track").each { |t|
487
- s = Stream.create(t['type'])
488
- t.children.select { |n| n.is_a? Hpricot::Elem }.each do |c|
489
- s[key_for(c)] = c.inner_html.strip
490
- end
491
- @streams << s
492
- }
493
- else
494
- REXML::Document.new(@raw_response).elements.each("/Mediainfo/File/track") { |t|
495
- s = Stream.create(t.attributes['type'])
496
- t.children.select { |n| n.is_a? REXML::Element }.each do |c|
497
- s[key_for(c)] = c.text.strip
498
- end
499
- @streams << s
500
- }
501
- end
502
-
503
- SECTIONS.each do |section|
504
- default_target_stream = if send("#{section}?")
505
- send(section).streams.first
506
- else
507
- Mediainfo.const_get("#{section.to_s.capitalize}Stream").new(section.to_s.capitalize)
508
- end
509
- instance_variable_set "@#{section}_stream", default_target_stream
510
- end
511
- end
512
-
513
- def key_for(attribute_node)
514
- attribute_node.name.downcase.gsub(/_+/, "_").gsub(/_s(\W|$)/, "s").strip
515
- end
516
- end
104
+ end # end Module