frames 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 +17 -0
- data/.travis.yml +8 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +48 -0
- data/Rakefile +13 -0
- data/frames.gemspec +22 -0
- data/lib/frames/analyzer.rb +41 -0
- data/lib/frames/frame.rb +19 -0
- data/lib/frames/frame_constructor.rb +63 -0
- data/lib/frames/version.rb +3 -0
- data/lib/frames.rb +10 -0
- data/spec/frames/analyzer_spec.rb +19 -0
- data/spec/frames/frame_constuctor_spec.rb +68 -0
- data/spec/spec_helper.rb +4 -0
- metadata +80 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 brookemckim
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Frames
|
2
|
+
|
3
|
+
Uses ffprobe to analyze video files frame by frame.
|
4
|
+
|
5
|
+
Use Cases:
|
6
|
+
|
7
|
+
* List all P/B/I frames of a video file.
|
8
|
+
* List all key frames of a video file.
|
9
|
+
* Analyze DTS/PTS of each frame.
|
10
|
+
|
11
|
+
## Prerequisites
|
12
|
+
|
13
|
+
[ffprobe](http://ffmpeg.org/ffprobe.html)
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
gem 'frames'
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
$ bundle
|
24
|
+
|
25
|
+
Or install it yourself as:
|
26
|
+
|
27
|
+
$ gem install frames
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
```
|
32
|
+
# Point the analyzer to your video file.
|
33
|
+
frame_analyzer = Frames::Anaylzer.new("/Users/bmckim/Desktop/sample.avi")
|
34
|
+
|
35
|
+
# Get an Array of each frame.
|
36
|
+
frame_analyzer.frames
|
37
|
+
|
38
|
+
# Want the raw ffprobe output?
|
39
|
+
frame.probe
|
40
|
+
```
|
41
|
+
|
42
|
+
## Contributing
|
43
|
+
|
44
|
+
1. Fork it
|
45
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
46
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
47
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
48
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
Rake::TestTask.new do |t|
|
6
|
+
t.libs.push 'lib'
|
7
|
+
t.libs.push 'spec'
|
8
|
+
t.test_files = FileList['spec/**/*_spec.rb']
|
9
|
+
t.verbose = true
|
10
|
+
end
|
11
|
+
|
12
|
+
task :default => [:test]
|
13
|
+
task :spec => [:test]
|
data/frames.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'frames/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "frames"
|
8
|
+
gem.version = Frames::VERSION
|
9
|
+
gem.authors = ["brookemckim"]
|
10
|
+
gem.email = ["brooke.mckim@gmail.com"]
|
11
|
+
gem.description = %q{Uses ffprobe to retrieve metadata about each frame of a video.}
|
12
|
+
gem.summary = %q{Analyze video metadata frame by frame.}
|
13
|
+
gem.homepage = "http://github.com/brookemckim/frames"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_development_dependency 'mocha'
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Frames
|
2
|
+
class FileError < StandardError; end
|
3
|
+
|
4
|
+
# Public: Run ffprobe on a file and analyze the frames from the output.
|
5
|
+
#
|
6
|
+
# file - The file to be anaylzed
|
7
|
+
#
|
8
|
+
# Examples
|
9
|
+
#
|
10
|
+
# analyzer = Anaylzer.new("/Users/bmckim/Desktop/sample.avi")
|
11
|
+
# analyzer.frames
|
12
|
+
# # => [...]
|
13
|
+
class Analyzer
|
14
|
+
def initialize(file)
|
15
|
+
@file = String(file)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Public: Returns the Array of Frames.
|
19
|
+
def frames
|
20
|
+
@frames ||= constructor.frames
|
21
|
+
end
|
22
|
+
|
23
|
+
# Public: Probe the video file.
|
24
|
+
#
|
25
|
+
# Returns String of raw output from ffprobe.
|
26
|
+
def probe
|
27
|
+
output = `ffprobe -show_frames "#{@file}" 2>/dev/null`
|
28
|
+
|
29
|
+
if $? == 0
|
30
|
+
output
|
31
|
+
else
|
32
|
+
raise FileError, "File does not exist or ffprobe was unable to read it"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Internal: Create FrameConstructor from ffprobe raw output.
|
37
|
+
def constructor
|
38
|
+
FrameConstructor.new(probe)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/frames/frame.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Internal: Takes a hash of video/audio frame attributes and makes them
|
2
|
+
# getters on an object.
|
3
|
+
module Frames
|
4
|
+
class Frame
|
5
|
+
def initialize(attrs = {})
|
6
|
+
attrs.each do |k,v|
|
7
|
+
self.instance_variable_set(:"@#{k}", v)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :media_type, :key_frame, :pkt_pts, :pkt_pts_time, :pkt_dts,
|
12
|
+
:pkt_dts_time, :pkt_duration, :pkt_duration_time, :pkt_pos,
|
13
|
+
:sample_fmt, :nb_samples, :channels, :channel_layout, :width, :height,
|
14
|
+
:sample_aspect_ratio, :pict_type, :coded_picture_number,
|
15
|
+
:display_picture_number, :interlaced_frame, :top_field_first,
|
16
|
+
:repeat_pict, :reference
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Internal: Converts raw output from ffprobe into an Array of Frames.
|
2
|
+
#
|
3
|
+
# Examples
|
4
|
+
#
|
5
|
+
# fc = FrameConstructor.new(`ffprobe -show_frames /path/to/video.mp4`)
|
6
|
+
# fc.frames # => [<Frame>,<Frame>]
|
7
|
+
module Frames
|
8
|
+
class FrameConstructor
|
9
|
+
def initialize(ffprobe_output)
|
10
|
+
@raw_output = ffprobe_output
|
11
|
+
end
|
12
|
+
|
13
|
+
# Internal: Maps Array of hashes from output into Array Frame objects.
|
14
|
+
def frames
|
15
|
+
frame_hashes.map { |h| Frame.new(h) }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Internal: Converts each raw frame into Hash.
|
19
|
+
#
|
20
|
+
# Returns Array of Hashes.
|
21
|
+
def frame_hashes
|
22
|
+
hashes = []
|
23
|
+
|
24
|
+
split_raw_frames.each do |frame|
|
25
|
+
hashes << raw_frame_to_hash(frame)
|
26
|
+
end
|
27
|
+
|
28
|
+
hashes
|
29
|
+
end
|
30
|
+
|
31
|
+
# Internal: Splits ffprobe output into seperate frames.
|
32
|
+
#
|
33
|
+
# Returns Array of raw ffprobe frames.
|
34
|
+
def split_raw_frames
|
35
|
+
@raw_output.split('[/FRAME]').take_while { |f|
|
36
|
+
f['[FRAME']
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Internal: Parses ffprobe frame metadata into hash.
|
43
|
+
#
|
44
|
+
# Returns Hash of frame attributes.
|
45
|
+
def raw_frame_to_hash(raw_frame)
|
46
|
+
hash_of_raw_frames = {}
|
47
|
+
|
48
|
+
raw_frame
|
49
|
+
.split("\n")
|
50
|
+
.delete_if { |value| !value['='] }
|
51
|
+
.each do |attribute|
|
52
|
+
attribute_array = attribute.split('=')
|
53
|
+
key = attribute_array[0]
|
54
|
+
value = attribute_array[1]
|
55
|
+
|
56
|
+
hash_of_raw_frames.merge!(key.to_sym => value)
|
57
|
+
end
|
58
|
+
|
59
|
+
hash_of_raw_frames
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
data/lib/frames.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Frames::Analyzer do
|
4
|
+
describe 'when video file is valid' do
|
5
|
+
before do
|
6
|
+
@analyzer = Frames::Analyzer.new('/path/to_video.mp4')
|
7
|
+
|
8
|
+
constructor = mock('constructor', :frames => 'framesgohere')
|
9
|
+
@analyzer.stubs(:probe)
|
10
|
+
@analyzer.stubs(:constructor).returns(constructor)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#frames' do
|
14
|
+
it 'sets frames to value from the frame constructor' do
|
15
|
+
@analyzer.frames.must_equal 'framesgohere'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Frames::FrameConstructor do
|
4
|
+
before do
|
5
|
+
@single_raw_output = <<EOF
|
6
|
+
[FRAME]\nmedia_type=video\nkey_frame=0\npkt_pts=N/A\npkt_pts_time=N/A\npkt_dts=N/A\npkt_dts_time=N/A\npkt_pos=6218536\nwidth=522\nheight=282\npix_fmt=yuv420p\nsample_aspect_ratio=1:1\npict_type=P\ncoded_picture_number=860\ndisplay_picture_number=0\ninterlaced_frame=0\ntop_field_first=0\nrepeat_pict=0\nreference=3\n[/FRAME]\n
|
7
|
+
EOF
|
8
|
+
|
9
|
+
@multiple_raw_output = <<EOF
|
10
|
+
[FRAME]\nmedia_type=video\nkey_frame=0\npkt_pts=N/A\npkt_pts_time=N/A\npkt_dts=N/A\npkt_dts_time=N/A\npkt_pos=6218536\nwidth=522\nheight=282\npix_fmt=yuv420p\nsample_aspect_ratio=1:1\npict_type=P\ncoded_picture_number=860\ndisplay_picture_number=0\ninterlaced_frame=0\ntop_field_first=0\nrepeat_pict=0\nreference=3\n[/FRAME]\n
|
11
|
+
[FRAME]\nmedia_type=video\nkey_frame=0\npkt_pts=855\npkt_pts_time=35.660625\npkt_dts=855\npkt_dts_time=35.660625\npkt_pos=6196580\nwidth=522\nheight=282\npix_fmt=yuv420p\nsample_aspect_ratio=1:1\npict_type=B\ncoded_picture_number=855\ndisplay_picture_number=0\ninterlaced_frame=0\ntop_field_first=0\nrepeat_pict=0\nreference=0\n[/FRAME]
|
12
|
+
EOF
|
13
|
+
@frame_constructor = Frames::FrameConstructor.new(@multiple_raw_output)
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#frames' do
|
17
|
+
it 'converts raw output to Array of frames' do
|
18
|
+
frames = @frame_constructor.frames
|
19
|
+
|
20
|
+
frames.must_be_instance_of(Array)
|
21
|
+
|
22
|
+
frames.size.must_equal 2
|
23
|
+
frames.each { |f| f.must_be_instance_of(Frames::Frame) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#from_hashes' do
|
28
|
+
it 'returns Array of formatted frames' do
|
29
|
+
@frame_constructor.stubs(:raw_frame_to_hash).returns({})
|
30
|
+
|
31
|
+
@frame_constructor.frame_hashes.must_equal [{},{}]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#split_raw_frames' do
|
36
|
+
it 'splits the raw frames' do
|
37
|
+
@frame_constructor.split_raw_frames.size.must_equal 2
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#raw_frame_to_hash' do
|
42
|
+
it 'converts raw output frame to hash' do
|
43
|
+
frame_hash = @frame_constructor.send('raw_frame_to_hash', @single_raw_output)
|
44
|
+
expected_frame_hash = {
|
45
|
+
media_type: 'video',
|
46
|
+
key_frame: '0',
|
47
|
+
pkt_pts: 'N/A',
|
48
|
+
pkt_pts_time: 'N/A',
|
49
|
+
pkt_dts: 'N/A',
|
50
|
+
pkt_dts_time: 'N/A',
|
51
|
+
pkt_pos: '6218536',
|
52
|
+
width: '522',
|
53
|
+
height: '282',
|
54
|
+
pix_fmt: 'yuv420p',
|
55
|
+
sample_aspect_ratio: '1:1',
|
56
|
+
pict_type: 'P',
|
57
|
+
coded_picture_number: '860',
|
58
|
+
display_picture_number: '0',
|
59
|
+
interlaced_frame: '0',
|
60
|
+
top_field_first: '0',
|
61
|
+
repeat_pict: '0',
|
62
|
+
reference: '3'
|
63
|
+
}
|
64
|
+
|
65
|
+
frame_hash.must_equal expected_frame_hash
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: frames
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- brookemckim
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: mocha
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
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: '0'
|
30
|
+
description: Uses ffprobe to retrieve metadata about each frame of a video.
|
31
|
+
email:
|
32
|
+
- brooke.mckim@gmail.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- .travis.yml
|
39
|
+
- Gemfile
|
40
|
+
- LICENSE.txt
|
41
|
+
- README.md
|
42
|
+
- Rakefile
|
43
|
+
- frames.gemspec
|
44
|
+
- lib/frames.rb
|
45
|
+
- lib/frames/analyzer.rb
|
46
|
+
- lib/frames/frame.rb
|
47
|
+
- lib/frames/frame_constructor.rb
|
48
|
+
- lib/frames/version.rb
|
49
|
+
- spec/frames/analyzer_spec.rb
|
50
|
+
- spec/frames/frame_constuctor_spec.rb
|
51
|
+
- spec/spec_helper.rb
|
52
|
+
homepage: http://github.com/brookemckim/frames
|
53
|
+
licenses: []
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
requirements: []
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.8.24
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: Analyze video metadata frame by frame.
|
76
|
+
test_files:
|
77
|
+
- spec/frames/analyzer_spec.rb
|
78
|
+
- spec/frames/frame_constuctor_spec.rb
|
79
|
+
- spec/spec_helper.rb
|
80
|
+
has_rdoc:
|