vcs_ruby 1.1.8 → 1.1.9
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.
- checksums.yaml +7 -0
- data/bin/vcs.rb +171 -169
- data/lib/FFmpeg/ffmpeg.rb +121 -121
- data/lib/FFmpeg/ffmpeg_audio_stream.rb +37 -37
- data/lib/FFmpeg/ffmpeg_meta_info.rb +36 -36
- data/lib/FFmpeg/ffmpeg_video_stream.rb +62 -56
- data/lib/MPlayer/mplayer.rb +101 -101
- data/lib/MPlayer/mplayer_audio_stream.rb +33 -33
- data/lib/MPlayer/mplayer_meta_info.rb +39 -39
- data/lib/MPlayer/mplayer_video_stream.rb +44 -44
- data/lib/black.yml +12 -12
- data/lib/capturer.rb +69 -69
- data/lib/command.rb +51 -51
- data/lib/configuration.rb +150 -150
- data/lib/contact_sheet.rb +380 -366
- data/lib/defaults.yml +39 -39
- data/lib/font.rb +81 -81
- data/lib/frame.rb +161 -161
- data/lib/libAV/libav.rb +151 -151
- data/lib/libAV/libav_audio_stream.rb +37 -37
- data/lib/libAV/libav_meta_info.rb +36 -36
- data/lib/libAV/libav_video_stream.rb +62 -56
- data/lib/oldstyle.yml +22 -22
- data/lib/stream.rb +24 -24
- data/lib/time_index.rb +121 -121
- data/lib/tools.rb +81 -70
- data/lib/vcs.rb +26 -26
- data/lib/version.info +1 -1
- data/lib/version.rb +25 -25
- data/lib/video.rb +92 -88
- data/lib/white.yml +12 -12
- metadata +39 -47
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 29ae31b33ef7769387236764c6152b1c3244e20d
|
4
|
+
data.tar.gz: 2b311e1ac050c997064a6e4df901dafae902f44c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1d4d7095e00cd684f6ba975fdd368e657aefc8909380af9e9f6c9ad2b1f4b88ccce20b3f9a9980f9f785746f28c68b42f96fe0ddff3ea3e6ca29ca6b02a205c9
|
7
|
+
data.tar.gz: 625850f3813caadcdad5c36ebc1123570ace5686811a130ce562e56d7370aec8b9ead7517db2d1de9902f4c7833758b88ef914206a4ed13e52d315170a979602
|
data/bin/vcs.rb
CHANGED
@@ -1,169 +1,171 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
# Video Contact Sheet Ruby:
|
4
|
-
# ----------------------
|
5
|
-
#
|
6
|
-
# Generates contact sheets of videos
|
7
|
-
#
|
8
|
-
# Prerequisites: Ruby, ImageMagick, ffmpeg/libav or mplayer
|
9
|
-
#
|
10
|
-
|
11
|
-
# Load library path for development
|
12
|
-
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), "../lib")
|
13
|
-
|
14
|
-
require 'optparse'
|
15
|
-
require 'vcs'
|
16
|
-
require 'yaml'
|
17
|
-
|
18
|
-
include VCSRuby
|
19
|
-
|
20
|
-
|
21
|
-
# Configuration can Override options
|
22
|
-
options =
|
23
|
-
{
|
24
|
-
quiet: false,
|
25
|
-
verbose: false,
|
26
|
-
capturer: :any,
|
27
|
-
format: nil,
|
28
|
-
output: []
|
29
|
-
}
|
30
|
-
|
31
|
-
# Command Line Parameter arguments
|
32
|
-
|
33
|
-
arguments =
|
34
|
-
{
|
35
|
-
'--capturer' => [:ffmpeg, :libav, :mplayer, :any],
|
36
|
-
'--format' => [:png, :jpg, :jpeg, :tiff],
|
37
|
-
'--funky' => [:polaroid, :photos, :overlap, :rotate, :photoframe, :polaroidframe, :film, :random]
|
38
|
-
}
|
39
|
-
|
40
|
-
# Command Line Parameters
|
41
|
-
optparse = OptionParser.new do|opts|
|
42
|
-
opts.separator $vcs_ruby_name + ' ' + $vcs_ruby_version.to_s
|
43
|
-
opts.separator ''
|
44
|
-
opts.on( '-i [INTERVAL]', '--interval [INTERVAL]', 'Set the interval [INTERVAL]') do |interval|
|
45
|
-
options[:interval] = TimeIndex.new interval
|
46
|
-
end
|
47
|
-
opts.on( '-c [COLMNS]', '--columns [COLUMNS]', 'Arrange the output in <COLUMNS> columns.') do |columns|
|
48
|
-
options[:columns] = columns.to_i
|
49
|
-
end
|
50
|
-
opts.on( '-r [ROWS]', '--rows [ROWS]', 'Arrange the output in <ROWS> rows.') do |rows|
|
51
|
-
options[:rows] = rows.to_i
|
52
|
-
end
|
53
|
-
opts.on( '-H [HEIGHT]', '--height [HEIGHT]', 'Set the output (individual thumbnail) height.') do |height|
|
54
|
-
options[:height] = height.to_i
|
55
|
-
end
|
56
|
-
opts.on( '-W [WIDTH]', '--width [WIDTH]', 'Set the output (individual thumbnail) width.') do |width|
|
57
|
-
options[:width] = width.to_i
|
58
|
-
end
|
59
|
-
opts.on( '-A [ASPECT]', '--aspect [ASPECT]', 'Aspect ratio. Accepts a floating point number or a fraction.') do |aspect|
|
60
|
-
options[:
|
61
|
-
end
|
62
|
-
opts.on( '-f [FROM]', '--from [FROM]', 'Set starting time. No caps before this.') do |from|
|
63
|
-
options[:from] = TimeIndex.new from
|
64
|
-
end
|
65
|
-
opts.on( '-t [TO]', '--to [TO]', 'Set ending time. No caps beyond this.') do |to|
|
66
|
-
options[:to] = TimeIndex.new to
|
67
|
-
end
|
68
|
-
opts.on( '-T [TITLE]', '--title [TITLE]', 'Set ending time. No caps beyond this.') do |title|
|
69
|
-
options[:title] = title
|
70
|
-
end
|
71
|
-
opts.on( '-f [format]', '--format [FORMAT]', arguments['--format'], 'Formats: ' + Tools::list_arguments(arguments["--format"])) do |format|
|
72
|
-
options[:format] = format
|
73
|
-
end
|
74
|
-
opts.on('-C [CAPTURER]', '--capture [CAPTURER]', arguments['--capturer'], 'Capturer: ' + Tools::list_arguments(arguments["--capturer"])) do |capturer|
|
75
|
-
options[:capturer] = capturer
|
76
|
-
end
|
77
|
-
opts.on( '-T [TITLE]', '--title [TITLE]', 'Set Title') do |title|
|
78
|
-
options[:title] = title
|
79
|
-
end
|
80
|
-
opts.on( '-o [FILE]', '--output [FILE]', 'File name of output. When ommited will be derived from the input filename. Can be repeated for multiple files.') do |file|
|
81
|
-
options[:output] << file
|
82
|
-
end
|
83
|
-
opts.on( '-s [SIGNATURE]', '--signature [SIGNATURE]', 'Change the image signature to your preference.') do |signature|
|
84
|
-
options[:signature] = signature
|
85
|
-
end
|
86
|
-
opts.on( '--no-signature', 'Remove footer with signature') do
|
87
|
-
options[:no_signature] = true
|
88
|
-
end
|
89
|
-
opts.on( '-l [HIGHLIGHT]', '--highlight [HIGHLIGHT]', 'Add the frame found at timestamp [HIGHLIGHT] as a highlight.') do |highlight|
|
90
|
-
options[:highlight] = TimeIndex.new highlight
|
91
|
-
end
|
92
|
-
opts.on("--[no-]timestamp", "Add timestamp to thumbnails. Default: true") do |timestamp|
|
93
|
-
options[:timestamp] = timestamp
|
94
|
-
end
|
95
|
-
opts.on("--[no-]shadow", "Add shadow to thumbnails. Default: true") do |shadow|
|
96
|
-
options[:shadow] = shadow
|
97
|
-
end
|
98
|
-
opts.on("--[no-]polaroid", "Add polaroid frame to thumbnail. Default: false") do |polaroid|
|
99
|
-
options[:polaroid] = polaroid
|
100
|
-
end
|
101
|
-
opts.on( '-p [PROFILE]', '--profile [PROFILE]', 'Loads additional setting from profile.yml.') do |profile|
|
102
|
-
options[:profile] = profile
|
103
|
-
end
|
104
|
-
opts.on( '-q', '--quiet', 'Don\'t print progress messages just errors.') do |file|
|
105
|
-
options[:quiet] = true
|
106
|
-
end
|
107
|
-
opts.on('--continue', 'Prints Error message and continues with next file (if any left)') do |file|
|
108
|
-
options[:continue] = true
|
109
|
-
end
|
110
|
-
opts.on("-V", "--verbose", "More verbose Output.") do
|
111
|
-
options[:verbose] = true
|
112
|
-
end
|
113
|
-
opts.on( '-v', '--version', 'Current Version' ) do
|
114
|
-
puts $vcs_ruby_name + ' ' + $vcs_ruby_version.to_s
|
115
|
-
exit 0
|
116
|
-
end
|
117
|
-
|
118
|
-
opts.on( '-h', '--help', 'Prints help' ) do
|
119
|
-
options[:help] = true
|
120
|
-
end
|
121
|
-
|
122
|
-
opts.separator ''
|
123
|
-
opts.separator 'Examples:'
|
124
|
-
opts.separator ' Create a contact sheet with default values (4 x 4 matrix):'
|
125
|
-
opts.separator ' $ vcs video.avi'
|
126
|
-
opts.separator ''
|
127
|
-
opts.separator ' Create a sheet with vidcaps at intervals of 3 and a half minutes, save to'
|
128
|
-
opts.separator ' "output.jpg":'
|
129
|
-
opts.separator ' $ vcs -i 3m30 input.wmv -o output.jpg'
|
130
|
-
opts.separator ''
|
131
|
-
opts.separator ' Create a sheet with vidcaps starting at 3 mins and ending at 18 mins in 2m intervals'
|
132
|
-
opts.separator ' $ vcs --from 3m --to 18m -i 2m input.avi'
|
133
|
-
opts.separator ''
|
134
|
-
opts.separator ' See more examples at vcs-ruby homepage <https://github.com/FreeApophis/vcs.rb>.'
|
135
|
-
opts.separator ''
|
136
|
-
end
|
137
|
-
|
138
|
-
Tools::print_help optparse if ARGV.empty?
|
139
|
-
|
140
|
-
optparse.parse!
|
141
|
-
|
142
|
-
Tools::print_help optparse if options[:help] || ARGV.empty?
|
143
|
-
|
144
|
-
Configuration.instance.verbose = options[:verbose]
|
145
|
-
Configuration.instance.quiet = options[:quiet]
|
146
|
-
|
147
|
-
# Invoke ContactSheet
|
148
|
-
|
149
|
-
errors = {}
|
150
|
-
ARGV.each_with_index do |video, index|
|
151
|
-
begin
|
152
|
-
sheet = Tools::contact_sheet_with_options video, options
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
end
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Video Contact Sheet Ruby:
|
4
|
+
# ----------------------
|
5
|
+
#
|
6
|
+
# Generates contact sheets of videos
|
7
|
+
#
|
8
|
+
# Prerequisites: Ruby, ImageMagick, ffmpeg/libav or mplayer
|
9
|
+
#
|
10
|
+
|
11
|
+
# Load library path for development
|
12
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), "../lib")
|
13
|
+
|
14
|
+
require 'optparse'
|
15
|
+
require 'vcs'
|
16
|
+
require 'yaml'
|
17
|
+
|
18
|
+
include VCSRuby
|
19
|
+
|
20
|
+
|
21
|
+
# Configuration can Override options
|
22
|
+
options =
|
23
|
+
{
|
24
|
+
quiet: false,
|
25
|
+
verbose: false,
|
26
|
+
capturer: :any,
|
27
|
+
format: nil,
|
28
|
+
output: []
|
29
|
+
}
|
30
|
+
|
31
|
+
# Command Line Parameter arguments
|
32
|
+
|
33
|
+
arguments =
|
34
|
+
{
|
35
|
+
'--capturer' => [:ffmpeg, :libav, :mplayer, :any],
|
36
|
+
'--format' => [:png, :jpg, :jpeg, :tiff],
|
37
|
+
'--funky' => [:polaroid, :photos, :overlap, :rotate, :photoframe, :polaroidframe, :film, :random]
|
38
|
+
}
|
39
|
+
|
40
|
+
# Command Line Parameters
|
41
|
+
optparse = OptionParser.new do|opts|
|
42
|
+
opts.separator $vcs_ruby_name + ' ' + $vcs_ruby_version.to_s
|
43
|
+
opts.separator ''
|
44
|
+
opts.on( '-i [INTERVAL]', '--interval [INTERVAL]', 'Set the interval [INTERVAL]') do |interval|
|
45
|
+
options[:interval] = TimeIndex.new interval
|
46
|
+
end
|
47
|
+
opts.on( '-c [COLMNS]', '--columns [COLUMNS]', 'Arrange the output in <COLUMNS> columns.') do |columns|
|
48
|
+
options[:columns] = columns.to_i
|
49
|
+
end
|
50
|
+
opts.on( '-r [ROWS]', '--rows [ROWS]', 'Arrange the output in <ROWS> rows.') do |rows|
|
51
|
+
options[:rows] = rows.to_i
|
52
|
+
end
|
53
|
+
opts.on( '-H [HEIGHT]', '--height [HEIGHT]', 'Set the output (individual thumbnail) height.') do |height|
|
54
|
+
options[:height] = height.to_i
|
55
|
+
end
|
56
|
+
opts.on( '-W [WIDTH]', '--width [WIDTH]', 'Set the output (individual thumbnail) width.') do |width|
|
57
|
+
options[:width] = width.to_i
|
58
|
+
end
|
59
|
+
opts.on( '-A [ASPECT]', '--aspect [ASPECT]', 'Aspect ratio. Accepts a floating point number or a fraction. (i.e. 16/19)') do |aspect|
|
60
|
+
options[:aspect_ratio] = aspect.to_r
|
61
|
+
end
|
62
|
+
opts.on( '-f [FROM]', '--from [FROM]', 'Set starting time. No caps before this.') do |from|
|
63
|
+
options[:from] = TimeIndex.new from
|
64
|
+
end
|
65
|
+
opts.on( '-t [TO]', '--to [TO]', 'Set ending time. No caps beyond this.') do |to|
|
66
|
+
options[:to] = TimeIndex.new to
|
67
|
+
end
|
68
|
+
opts.on( '-T [TITLE]', '--title [TITLE]', 'Set ending time. No caps beyond this.') do |title|
|
69
|
+
options[:title] = title
|
70
|
+
end
|
71
|
+
opts.on( '-f [format]', '--format [FORMAT]', arguments['--format'], 'Formats: ' + Tools::list_arguments(arguments["--format"])) do |format|
|
72
|
+
options[:format] = format
|
73
|
+
end
|
74
|
+
opts.on('-C [CAPTURER]', '--capture [CAPTURER]', arguments['--capturer'], 'Capturer: ' + Tools::list_arguments(arguments["--capturer"])) do |capturer|
|
75
|
+
options[:capturer] = capturer
|
76
|
+
end
|
77
|
+
opts.on( '-T [TITLE]', '--title [TITLE]', 'Set Title') do |title|
|
78
|
+
options[:title] = title
|
79
|
+
end
|
80
|
+
opts.on( '-o [FILE]', '--output [FILE]', 'File name of output. When ommited will be derived from the input filename. Can be repeated for multiple files.') do |file|
|
81
|
+
options[:output] << file
|
82
|
+
end
|
83
|
+
opts.on( '-s [SIGNATURE]', '--signature [SIGNATURE]', 'Change the image signature to your preference.') do |signature|
|
84
|
+
options[:signature] = signature
|
85
|
+
end
|
86
|
+
opts.on( '--no-signature', 'Remove footer with signature') do
|
87
|
+
options[:no_signature] = true
|
88
|
+
end
|
89
|
+
opts.on( '-l [HIGHLIGHT]', '--highlight [HIGHLIGHT]', 'Add the frame found at timestamp [HIGHLIGHT] as a highlight.') do |highlight|
|
90
|
+
options[:highlight] = TimeIndex.new highlight
|
91
|
+
end
|
92
|
+
opts.on("--[no-]timestamp", "Add timestamp to thumbnails. Default: true") do |timestamp|
|
93
|
+
options[:timestamp] = timestamp
|
94
|
+
end
|
95
|
+
opts.on("--[no-]shadow", "Add shadow to thumbnails. Default: true") do |shadow|
|
96
|
+
options[:shadow] = shadow
|
97
|
+
end
|
98
|
+
opts.on("--[no-]polaroid", "Add polaroid frame to thumbnail. Default: false") do |polaroid|
|
99
|
+
options[:polaroid] = polaroid
|
100
|
+
end
|
101
|
+
opts.on( '-p [PROFILE]', '--profile [PROFILE]', 'Loads additional setting from profile.yml.') do |profile|
|
102
|
+
options[:profile] = profile
|
103
|
+
end
|
104
|
+
opts.on( '-q', '--quiet', 'Don\'t print progress messages just errors.') do |file|
|
105
|
+
options[:quiet] = true
|
106
|
+
end
|
107
|
+
opts.on('--continue', 'Prints Error message and continues with next file (if any left)') do |file|
|
108
|
+
options[:continue] = true
|
109
|
+
end
|
110
|
+
opts.on("-V", "--verbose", "More verbose Output.") do
|
111
|
+
options[:verbose] = true
|
112
|
+
end
|
113
|
+
opts.on( '-v', '--version', 'Current Version' ) do
|
114
|
+
puts $vcs_ruby_name + ' ' + $vcs_ruby_version.to_s
|
115
|
+
exit 0
|
116
|
+
end
|
117
|
+
|
118
|
+
opts.on( '-h', '--help', 'Prints help' ) do
|
119
|
+
options[:help] = true
|
120
|
+
end
|
121
|
+
|
122
|
+
opts.separator ''
|
123
|
+
opts.separator 'Examples:'
|
124
|
+
opts.separator ' Create a contact sheet with default values (4 x 4 matrix):'
|
125
|
+
opts.separator ' $ vcs video.avi'
|
126
|
+
opts.separator ''
|
127
|
+
opts.separator ' Create a sheet with vidcaps at intervals of 3 and a half minutes, save to'
|
128
|
+
opts.separator ' "output.jpg":'
|
129
|
+
opts.separator ' $ vcs -i 3m30 input.wmv -o output.jpg'
|
130
|
+
opts.separator ''
|
131
|
+
opts.separator ' Create a sheet with vidcaps starting at 3 mins and ending at 18 mins in 2m intervals'
|
132
|
+
opts.separator ' $ vcs --from 3m --to 18m -i 2m input.avi'
|
133
|
+
opts.separator ''
|
134
|
+
opts.separator ' See more examples at vcs-ruby homepage <https://github.com/FreeApophis/vcs.rb>.'
|
135
|
+
opts.separator ''
|
136
|
+
end
|
137
|
+
|
138
|
+
Tools::print_help optparse if ARGV.empty?
|
139
|
+
|
140
|
+
optparse.parse!
|
141
|
+
|
142
|
+
Tools::print_help optparse if options[:help] || ARGV.empty?
|
143
|
+
|
144
|
+
Configuration.instance.verbose = options[:verbose]
|
145
|
+
Configuration.instance.quiet = options[:quiet]
|
146
|
+
|
147
|
+
# Invoke ContactSheet
|
148
|
+
|
149
|
+
errors = {}
|
150
|
+
ARGV.each_with_index do |video, index|
|
151
|
+
begin
|
152
|
+
sheet = Tools::contact_sheet_with_options video, options
|
153
|
+
if sheet
|
154
|
+
sheet.initialize_filename(options[:output][index]) if options[:output][index]
|
155
|
+
sheet.build
|
156
|
+
end
|
157
|
+
rescue Exception => e
|
158
|
+
errors[video] = e
|
159
|
+
STDERR.puts "ERROR: #{e.message}"
|
160
|
+
STDERR.puts "#{e.backtrace.join("\n")}" if options[:verbose]
|
161
|
+
break unless options[:continue]
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
if options[:continue] && errors.length > 0
|
166
|
+
errors.each do |video, e|
|
167
|
+
STDERR.puts "File: #{video}"
|
168
|
+
STDERR.puts "ERROR: #{e.message}"
|
169
|
+
end
|
170
|
+
STDERR.puts "Total: #{errors.length} Errors"
|
171
|
+
end
|
data/lib/FFmpeg/ffmpeg.rb
CHANGED
@@ -1,121 +1,121 @@
|
|
1
|
-
#
|
2
|
-
# FFmpeg Abstraction
|
3
|
-
#
|
4
|
-
|
5
|
-
require 'capturer'
|
6
|
-
require 'command'
|
7
|
-
|
8
|
-
module VCSRuby
|
9
|
-
class FFmpeg < Capturer
|
10
|
-
|
11
|
-
HEADER = 10
|
12
|
-
ENCODING_SUPPORT = 2
|
13
|
-
VIDEO_CODEC = 3
|
14
|
-
NAME = 8
|
15
|
-
|
16
|
-
attr_reader :info, :video_streams, :audio_streams
|
17
|
-
|
18
|
-
def initialize video
|
19
|
-
@video = video
|
20
|
-
@ffmpeg = Command.new :ffmpeg, 'ffmpeg'
|
21
|
-
@ffprobe = Command.new :ffmpeg, 'ffprobe'
|
22
|
-
|
23
|
-
detect_version if available?
|
24
|
-
end
|
25
|
-
|
26
|
-
def file_valid?
|
27
|
-
return probe_meta_information
|
28
|
-
end
|
29
|
-
|
30
|
-
def name
|
31
|
-
:ffmpeg
|
32
|
-
end
|
33
|
-
|
34
|
-
def available?
|
35
|
-
@ffmpeg.available? && !libav?
|
36
|
-
end
|
37
|
-
|
38
|
-
def libav?
|
39
|
-
@libav
|
40
|
-
end
|
41
|
-
|
42
|
-
def detect_version
|
43
|
-
info = @ffmpeg.execute('-version')
|
44
|
-
match = /avconv ([\d|.|-|:]*)/.match(info)
|
45
|
-
@libav = !!match
|
46
|
-
match = /ffmpeg version ([\d|.]*)/.match(info)
|
47
|
-
if match
|
48
|
-
@version = match[1]
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def grab time, image_path
|
53
|
-
@ffmpeg.execute "-y -ss #{time.total_seconds} -i \"#{@video.full_path}\" -an -dframes 1 -vframes 1 -vcodec #{format} -f rawvideo \"#{image_path}\""
|
54
|
-
end
|
55
|
-
|
56
|
-
def available_formats
|
57
|
-
# Ordered by priority
|
58
|
-
image_formats = ['png', 'tiff', 'bmp', 'mjpeg']
|
59
|
-
formats = []
|
60
|
-
|
61
|
-
list = @ffprobe.execute "-codecs"
|
62
|
-
list.lines.drop(HEADER).each do |codec|
|
63
|
-
name, e, v = format_split(codec)
|
64
|
-
formats << name if image_formats.include?(name) && e && v
|
65
|
-
end
|
66
|
-
|
67
|
-
image_formats.select{ |format| formats.include?(format) }.map(&:to_sym)
|
68
|
-
end
|
69
|
-
|
70
|
-
def to_s
|
71
|
-
"FFmpeg #{@version}"
|
72
|
-
end
|
73
|
-
|
74
|
-
private
|
75
|
-
def format_split line
|
76
|
-
e = line[ENCODING_SUPPORT] == 'E'
|
77
|
-
v = line[VIDEO_CODEC] == 'V'
|
78
|
-
|
79
|
-
name = line[NAME..-1].split(' ', 2).first
|
80
|
-
return name, e, v
|
81
|
-
rescue
|
82
|
-
return nil, false, false
|
83
|
-
end
|
84
|
-
|
85
|
-
def check_cache
|
86
|
-
unless @cache
|
87
|
-
@cache = @ffprobe.execute("\"#{@video.full_path}\" -show_format -show_streams", "2>&1")
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
def parse_format
|
92
|
-
@cache.scan(/\[FORMAT\](.*?)\[\/FORMAT\]/m) do |format|
|
93
|
-
@info = FFmpegMetaInfo.new(get_hash(format[0]))
|
94
|
-
return true
|
95
|
-
end
|
96
|
-
false
|
97
|
-
end
|
98
|
-
|
99
|
-
def parse_audio_streams
|
100
|
-
@audio_streams = []
|
101
|
-
@cache.scan(/\[STREAM\](.*?)\[\/STREAM\]/m) do |stream|
|
102
|
-
info = get_hash(stream[0])
|
103
|
-
if info['codec_type'] == 'audio'
|
104
|
-
@audio_streams << FFmpegAudioStream.new(info)
|
105
|
-
end
|
106
|
-
end
|
107
|
-
true
|
108
|
-
end
|
109
|
-
|
110
|
-
def parse_video_streams
|
111
|
-
@video_streams = []
|
112
|
-
@cache.scan(/\[STREAM\](.*?)\[\/STREAM\]/m) do |stream|
|
113
|
-
info = get_hash(stream[0])
|
114
|
-
if info['codec_type'] == 'video'
|
115
|
-
@video_streams << FFmpegVideoStream.new(info)
|
116
|
-
end
|
117
|
-
end
|
118
|
-
true
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
1
|
+
#
|
2
|
+
# FFmpeg Abstraction
|
3
|
+
#
|
4
|
+
|
5
|
+
require 'capturer'
|
6
|
+
require 'command'
|
7
|
+
|
8
|
+
module VCSRuby
|
9
|
+
class FFmpeg < Capturer
|
10
|
+
|
11
|
+
HEADER = 10
|
12
|
+
ENCODING_SUPPORT = 2
|
13
|
+
VIDEO_CODEC = 3
|
14
|
+
NAME = 8
|
15
|
+
|
16
|
+
attr_reader :info, :video_streams, :audio_streams
|
17
|
+
|
18
|
+
def initialize video
|
19
|
+
@video = video
|
20
|
+
@ffmpeg = Command.new :ffmpeg, 'ffmpeg'
|
21
|
+
@ffprobe = Command.new :ffmpeg, 'ffprobe'
|
22
|
+
|
23
|
+
detect_version if available?
|
24
|
+
end
|
25
|
+
|
26
|
+
def file_valid?
|
27
|
+
return probe_meta_information
|
28
|
+
end
|
29
|
+
|
30
|
+
def name
|
31
|
+
:ffmpeg
|
32
|
+
end
|
33
|
+
|
34
|
+
def available?
|
35
|
+
@ffmpeg.available? && @ffprobe.available? && !libav?
|
36
|
+
end
|
37
|
+
|
38
|
+
def libav?
|
39
|
+
@libav
|
40
|
+
end
|
41
|
+
|
42
|
+
def detect_version
|
43
|
+
info = @ffmpeg.execute('-version')
|
44
|
+
match = /avconv ([\d|.|-|:]*)/.match(info)
|
45
|
+
@libav = !!match
|
46
|
+
match = /ffmpeg version ([\d|.]*)/.match(info)
|
47
|
+
if match
|
48
|
+
@version = match[1]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def grab time, image_path
|
53
|
+
@ffmpeg.execute "-y -ss #{time.total_seconds} -i \"#{@video.full_path}\" -an -dframes 1 -vframes 1 -vcodec #{format} -f rawvideo \"#{image_path}\""
|
54
|
+
end
|
55
|
+
|
56
|
+
def available_formats
|
57
|
+
# Ordered by priority
|
58
|
+
image_formats = ['png', 'tiff', 'bmp', 'mjpeg']
|
59
|
+
formats = []
|
60
|
+
|
61
|
+
list = @ffprobe.execute "-codecs"
|
62
|
+
list.lines.drop(HEADER).each do |codec|
|
63
|
+
name, e, v = format_split(codec)
|
64
|
+
formats << name if image_formats.include?(name) && e && v
|
65
|
+
end
|
66
|
+
|
67
|
+
image_formats.select{ |format| formats.include?(format) }.map(&:to_sym)
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_s
|
71
|
+
"FFmpeg #{@version}"
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
def format_split line
|
76
|
+
e = line[ENCODING_SUPPORT] == 'E'
|
77
|
+
v = line[VIDEO_CODEC] == 'V'
|
78
|
+
|
79
|
+
name = line[NAME..-1].split(' ', 2).first
|
80
|
+
return name, e, v
|
81
|
+
rescue
|
82
|
+
return nil, false, false
|
83
|
+
end
|
84
|
+
|
85
|
+
def check_cache
|
86
|
+
unless @cache
|
87
|
+
@cache = @ffprobe.execute("\"#{@video.full_path}\" -show_format -show_streams", "2>&1")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def parse_format
|
92
|
+
@cache.scan(/\[FORMAT\](.*?)\[\/FORMAT\]/m) do |format|
|
93
|
+
@info = FFmpegMetaInfo.new(get_hash(format[0]))
|
94
|
+
return true
|
95
|
+
end
|
96
|
+
false
|
97
|
+
end
|
98
|
+
|
99
|
+
def parse_audio_streams
|
100
|
+
@audio_streams = []
|
101
|
+
@cache.scan(/\[STREAM\](.*?)\[\/STREAM\]/m) do |stream|
|
102
|
+
info = get_hash(stream[0])
|
103
|
+
if info['codec_type'] == 'audio'
|
104
|
+
@audio_streams << FFmpegAudioStream.new(info)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
true
|
108
|
+
end
|
109
|
+
|
110
|
+
def parse_video_streams
|
111
|
+
@video_streams = []
|
112
|
+
@cache.scan(/\[STREAM\](.*?)\[\/STREAM\]/m) do |stream|
|
113
|
+
info = get_hash(stream[0])
|
114
|
+
if info['codec_type'] == 'video'
|
115
|
+
@video_streams << FFmpegVideoStream.new(info)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
true
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -1,38 +1,38 @@
|
|
1
|
-
#
|
2
|
-
# Implementes AudioStream Interface for FFmpeg
|
3
|
-
#
|
4
|
-
|
5
|
-
# AudioStream = Struct.new(:codec, :channels, :channel_layout, :sample_rate, :bit_rate, :raw)
|
6
|
-
module VCSRuby
|
7
|
-
class FFmpegAudioStream
|
8
|
-
attr_reader :raw
|
9
|
-
|
10
|
-
def initialize audio_stream
|
11
|
-
@raw = audio_stream
|
12
|
-
end
|
13
|
-
|
14
|
-
def codec short = false
|
15
|
-
if short
|
16
|
-
@raw['codec_name']
|
17
|
-
else
|
18
|
-
@raw['codec_long_name']
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def channels
|
23
|
-
@raw['channels'].to_i
|
24
|
-
end
|
25
|
-
|
26
|
-
def channel_layout
|
27
|
-
@raw['channel_layout']
|
28
|
-
end
|
29
|
-
|
30
|
-
def sample_rate
|
31
|
-
@raw['sample_rate'].to_i
|
32
|
-
end
|
33
|
-
|
34
|
-
def bit_rate
|
35
|
-
@raw['bit_rate'].to_i
|
36
|
-
end
|
37
|
-
end
|
1
|
+
#
|
2
|
+
# Implementes AudioStream Interface for FFmpeg
|
3
|
+
#
|
4
|
+
|
5
|
+
# AudioStream = Struct.new(:codec, :channels, :channel_layout, :sample_rate, :bit_rate, :raw)
|
6
|
+
module VCSRuby
|
7
|
+
class FFmpegAudioStream
|
8
|
+
attr_reader :raw
|
9
|
+
|
10
|
+
def initialize audio_stream
|
11
|
+
@raw = audio_stream
|
12
|
+
end
|
13
|
+
|
14
|
+
def codec short = false
|
15
|
+
if short
|
16
|
+
@raw['codec_name']
|
17
|
+
else
|
18
|
+
@raw['codec_long_name']
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def channels
|
23
|
+
@raw['channels'].to_i
|
24
|
+
end
|
25
|
+
|
26
|
+
def channel_layout
|
27
|
+
@raw['channel_layout']
|
28
|
+
end
|
29
|
+
|
30
|
+
def sample_rate
|
31
|
+
@raw['sample_rate'].to_i
|
32
|
+
end
|
33
|
+
|
34
|
+
def bit_rate
|
35
|
+
@raw['bit_rate'].to_i
|
36
|
+
end
|
37
|
+
end
|
38
38
|
end
|