vcs_ruby 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,59 +1,76 @@
1
1
  #
2
- # Thumbnails from video
2
+ # Frame from video
3
3
  #
4
4
 
5
5
  require 'mini_magick'
6
6
 
7
7
  module VCSRuby
8
- class Thumbnail
8
+ class Frame
9
9
  attr_accessor :width, :height, :aspect
10
- attr_accessor :image_path
11
- attr_accessor :time
12
- attr_reader :filters
10
+ attr_reader :filters, :time
13
11
 
14
- def initialize capper, video, configuration
15
- @capper = capper
12
+ def initialize video, capturer, time
16
13
  @video = video
17
- @configuration = configuration
14
+ @capturer = capturer
15
+ @time = time
18
16
  @filters = []
19
17
  end
20
18
 
19
+ def filename= file_path
20
+ @out_path = File.dirname(file_path)
21
+ @out_filename = File.basename(file_path,'.*')
22
+ end
23
+
24
+ def filename
25
+ File.join(@out_path, "#{@out_filename}.#{@capturer.format_extension}")
26
+ end
27
+
28
+ def format= fmt
29
+ @capturer.format = fmt
30
+ end
31
+
32
+ def format
33
+ @capturer.format
34
+ end
35
+
21
36
  def capture
22
- @capper.grab @time, @image_path
37
+ @capturer.grab @time, filename
23
38
  end
24
39
 
25
- def capture_and_evade interval
26
- times = [TimeIndex.new] + @configuration.blank_alternatives
27
- times.select! { |t| (t < interval / 2) and (t > interval / -2) }
40
+ def capture_and_evade interval = nil
41
+ times = [TimeIndex.new] + Configuration.instance.blank_alternatives
42
+ if interval
43
+ times.select! { |t| (t < interval / 2) and (t > interval / -2) }
44
+ end
28
45
  times.map! { |t| @time + t }
29
46
 
30
47
  times.each do |time|
31
48
  @time = time
32
49
  capture
33
50
  break unless blank?
34
- puts "Blank frame detected. => #{@time}" unless Tools::quiet?
35
- puts "Giving up!" if time == times.last && !Tools::quiet?
51
+ puts "Blank frame detected. => #{@time}" unless Configuration.instance.quiet?
52
+ puts "Giving up!" if time == times.last && !Configuration.instance.quiet?
36
53
  end
37
54
  end
38
55
 
39
56
  def blank?
40
- image = MiniMagick::Image.open @image_path
57
+ image = MiniMagick::Image.open filename
41
58
  image.colorspace 'Gray'
42
59
  mean = image['%[fx:image.mean]'].to_f
43
- return mean < @configuration.blank_threshold
60
+ return mean < Configuration.instance.blank_threshold
44
61
  end
45
62
 
46
63
  def apply_filters
47
64
  MiniMagick::Tool::Convert.new do |convert|
48
65
  convert.background 'Transparent'
49
66
  convert.fill 'Transparent'
50
- convert << @image_path
67
+ convert << filename
51
68
 
52
69
  sorted_filters.each do |filter|
53
70
  call_filter filter, convert
54
71
  end
55
72
 
56
- convert << @image_path
73
+ convert << filename
57
74
  end
58
75
  end
59
76
 
@@ -76,12 +93,12 @@ private
76
93
 
77
94
  def timestamp_filter convert
78
95
  convert.stack do |box|
79
- box.box @configuration.timestamp_background
80
- box.fill @configuration.timestamp_color
81
- box.pointsize @configuration.timestamp_font.size
96
+ box.box Configuration.instance.timestamp_background
97
+ box.fill Configuration.instance.timestamp_color
98
+ box.pointsize Configuration.instance.timestamp_font.size
82
99
  box.gravity 'SouthEast'
83
- if @configuration.timestamp_font.exists?
84
- box.font @configuration.timestamp_font.path
100
+ if Configuration.instance.timestamp_font.exists?
101
+ box.font Configuration.instance.timestamp_font.path
85
102
  end
86
103
  box.annotate('+10+10', " #{@time.to_timestamp} ")
87
104
  end
@@ -115,7 +132,11 @@ private
115
132
  a.fill 'White'
116
133
  a.background 'White'
117
134
  a.bordercolor 'White'
118
- a.mattecolor 'White'
135
+ if Tools.magick_version.major > 6
136
+ a.alpha_color 'White'
137
+ else
138
+ a.mattecolor 'White'
139
+ end
119
140
  a.frame "#{border}x#{border}"
120
141
  a.stack do |b|
121
142
  b.flip
@@ -0,0 +1,143 @@
1
+ #
2
+ # libAV Abstraction
3
+ #
4
+
5
+ require 'capturer'
6
+ require 'command'
7
+
8
+ module VCSRuby
9
+ class LibAV < Capturer
10
+
11
+ HEADER = 10
12
+
13
+ ENCODING_SUPPORT = 2
14
+ VIDEO_CODEC = 3
15
+ NAME = 8
16
+
17
+ attr_reader :info, :video_streams, :audio_streams
18
+
19
+ def initialize video
20
+ @video = video
21
+ @avconv = Command.new :libav, 'avconv'
22
+ @avprobe = Command.new :libav, 'avprobe'
23
+
24
+ detect_version if available?
25
+ end
26
+
27
+ def file_valid?
28
+ return probe_meta_information
29
+ end
30
+
31
+ def name
32
+ :libav
33
+ end
34
+
35
+ def available?
36
+ @avconv.available? && @avprobe.available?
37
+ end
38
+
39
+
40
+ def detect_version
41
+ info = @avconv.execute('-version')
42
+ match = /avconv ([\d|.|-|:]*)/.match(info)
43
+ if match
44
+ @version = match[1]
45
+ end
46
+ end
47
+
48
+
49
+
50
+ def grab time, image_path
51
+ @avconv.execute "-y -ss #{time.total_seconds} -i \"#{@video.full_path}\" -an -dframes 1 -vframes 1 -vcodec #{format} -f rawvideo \"#{image_path}\""
52
+ end
53
+
54
+ def available_formats
55
+ # Ordered by priority
56
+ image_formats = ['png', 'tiff', 'bmp', 'mjpeg']
57
+ formats = []
58
+
59
+ list = @avprobe.execute "-codecs"
60
+ list.lines.drop(HEADER).each do |codec|
61
+ name, e, v = format_split(codec)
62
+ formats << name if image_formats.include?(name) && e && v
63
+ end
64
+
65
+ image_formats.select{ |format| formats.include?(format) }.map(&:to_sym)
66
+ end
67
+
68
+ def to_s
69
+ "LibAV #{@version}"
70
+ end
71
+
72
+ private
73
+ def format_split line
74
+ correction = 0
75
+ unless line[0] == ' '
76
+ correction = 1
77
+ end
78
+ e = line[ENCODING_SUPPORT - correction] == 'E'
79
+ v = line[VIDEO_CODEC - correction] == 'V'
80
+
81
+ name = line[NAME-correction..-1].split(' ', 2).first
82
+ return name, e, v
83
+ rescue
84
+ return nil, false, false
85
+ end
86
+
87
+ def probe_meta_information
88
+ check_cache
89
+ parse_meta_info
90
+ return true
91
+ rescue Exception => e
92
+ puts e
93
+ return false
94
+ end
95
+
96
+ def check_cache
97
+ unless @cache
98
+ @cache = @avprobe.execute("\"#{@video.full_path}\" -show_format -show_streams", "2>&1")
99
+ end
100
+ end
101
+
102
+ def get_hash defines
103
+ result = {}
104
+ defines.lines.each do |line|
105
+ kv = line.split("=")
106
+ result[kv[0].strip] = kv[1].strip if kv.count == 2
107
+ end
108
+ result
109
+ end
110
+
111
+ def parse_meta_info
112
+ parse_format
113
+ parse_audio_streams
114
+ parse_video_streams
115
+ end
116
+
117
+ def parse_format
118
+ @cache.scan(/\[FORMAT\](.*?)\[\/FORMAT\]/m) do |format|
119
+ @info = LibAVMetaInfo.new(get_hash(format[0]))
120
+ end
121
+ end
122
+
123
+ def parse_audio_streams
124
+ @audio_streams = []
125
+ @cache.scan(/\[STREAM\](.*?)\[\/STREAM\]/m) do |stream|
126
+ info = get_hash(stream[0])
127
+ if info['codec_type'] == 'audio'
128
+ @audio_streams << LibAVAudioStream.new(info)
129
+ end
130
+ end
131
+ end
132
+
133
+ def parse_video_streams
134
+ @video_streams = []
135
+ @cache.scan(/\[STREAM\](.*?)\[\/STREAM\]/m) do |stream|
136
+ info = get_hash(stream[0])
137
+ if info['codec_type'] == 'video'
138
+ @video_streams << LibAVVideoStream.new(info)
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,38 @@
1
+ #
2
+ # Implementes AudioStream Interface for libAV
3
+ #
4
+
5
+ # AudioStream = Struct.new(:codec, :channels, :channel_layout, :sample_rate, :bit_rate, :raw)
6
+ module VCSRuby
7
+ class LibAVAudioStream
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
+ end
@@ -0,0 +1,37 @@
1
+ #
2
+ # Implementes MetaInfo Interface for libAV
3
+ #
4
+
5
+ # MetaInformation = Struct.new(:duration, :bit_rate, :size, :format, :extension, :raw)
6
+
7
+ require_relative '../time_index'
8
+
9
+ module VCSRuby
10
+ class LibAVMetaInfo
11
+ attr_reader :raw
12
+
13
+ def initialize meta_info
14
+ @raw = meta_info
15
+ end
16
+
17
+ def duration
18
+ TimeIndex.new(@raw['duration'].to_f)
19
+ end
20
+
21
+ def bit_rate
22
+ @raw['bit_rate'].to_i
23
+ end
24
+
25
+ def size
26
+ @raw['size'].to_i
27
+ end
28
+
29
+ def format
30
+ @raw['format_long_name']
31
+ end
32
+
33
+ def extension
34
+ @raw['format_name']
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,52 @@
1
+ #
2
+ # Implementes VideoStream Interface for libAV
3
+ #
4
+
5
+ # VideoStream = Struct.new(:width, :height, :codec, :color_space, :bit_rate, :frame_rate, :aspect_ratio, :raw)
6
+
7
+ module VCSRuby
8
+ class LibAVVideoStream
9
+ attr_reader :raw
10
+
11
+ def initialize video_stream
12
+ @raw = video_stream
13
+ end
14
+
15
+ def width
16
+ @raw['width'].to_i
17
+ end
18
+
19
+ def height
20
+ @raw['height'].to_i
21
+ end
22
+
23
+ def codec short = false
24
+ if short
25
+ @raw['codec_name']
26
+ else
27
+ @raw['codec_long_name']
28
+ end
29
+ end
30
+
31
+ def color_space
32
+ if ["unknown", "", nil].include? @raw['color_space']
33
+ @raw['pix_fmt']
34
+ else
35
+ @raw['color_space']
36
+ end
37
+ end
38
+
39
+ def bit_rate
40
+ @raw['bit_rate'].to_i
41
+ end
42
+
43
+
44
+ def frame_rate
45
+ Rational(@raw['r_frame_rate'])
46
+ end
47
+
48
+ def aspect_ratio
49
+ @raw['display_aspect_ratio']
50
+ end
51
+ end
52
+ end
data/lib/stream.rb ADDED
@@ -0,0 +1,24 @@
1
+ #
2
+ # Represents a stream in a video
3
+ #
4
+
5
+ require 'vcs'
6
+
7
+ module VCSRuby
8
+ class Stream
9
+ def initialize stream
10
+ @stream = stream
11
+ create_accessors
12
+ end
13
+
14
+ def create_accessors
15
+ @stream.each do |key, value|
16
+ self.class.send :define_method, key.to_sym do
17
+ return @stream[key]
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+ end
24
+ end
data/lib/tools.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  #
4
4
 
5
5
  module VCSRuby
6
+
6
7
  class Tools
7
8
  def self.windows?
8
9
  return ((RUBY_PLATFORM =~ /win32/ or RUBY_PLATFORM =~ /mingw32/) or (RbConfig::CONFIG['host_os'] =~ /mswin|windows/i))
@@ -12,24 +13,6 @@ module VCSRuby
12
13
  return ((RUBY_PLATFORM =~ /linux/) or (RbConfig::CONFIG['host_os'] =~ /linux/i))
13
14
  end
14
15
 
15
- def self.verbose= verbose
16
- @verbose = verbose
17
- @quiet = false if @verbose
18
- end
19
-
20
- def self.verbose?
21
- @verbose
22
- end
23
-
24
- def self.quiet= quiet
25
- @quiet = quiet
26
- @verbose = false if @quiet
27
- end
28
-
29
- def self.quiet?
30
- @quiet
31
- end
32
-
33
16
  def self.list_arguments arguments
34
17
  arguments.map{ |argument| argument.to_s }.join(', ')
35
18
  end
@@ -39,10 +22,20 @@ module VCSRuby
39
22
  exit 0
40
23
  end
41
24
 
25
+ MagickVersion = Struct.new(:major, :minor, :revision)
26
+ def self.magick_version
27
+ output = %x[convert -version]
28
+ m = output.match /(\d+)\.(\d+)\.(\d+)(-(\d+))?/
29
+ MagickVersion.new(m[1].to_i, m[2].to_i, m[3].to_i)
30
+ end
31
+
42
32
  def self.contact_sheet_with_options video, options
43
- sheet = VCSRuby::ContactSheet.new video, options[:profile]
33
+ Configuration.instance.load_profile options[:profile] if options[:profile]
34
+ Configuration.instance.capturer = options[:capturer]
35
+
36
+ video = VCSRuby::Video.new video
37
+ sheet = video.contact_sheet
44
38
 
45
- sheet.capturer = options[:capturer]
46
39
  sheet.format = options[:format] if options[:format]
47
40
  sheet.title = options[:title] if options[:title]
48
41
  sheet.signature = options[:signature] if options[:signature]
data/lib/vcs.rb CHANGED
@@ -2,13 +2,25 @@
2
2
  # Video Contact Sheet Ruby
3
3
  #
4
4
 
5
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))
6
+
5
7
  require 'command'
6
8
  require 'configuration'
7
9
  require 'contact_sheet'
8
- require 'ffmpeg'
9
- require 'libav'
10
- require 'mplayer'
11
- require 'thumbnail'
10
+ require 'video'
11
+ require 'frame'
12
12
  require 'time_index'
13
13
  require 'tools'
14
14
  require 'version'
15
+ require 'FFmpeg/ffmpeg'
16
+ require 'FFmpeg/ffmpeg_audio_stream'
17
+ require 'FFmpeg/ffmpeg_video_stream'
18
+ require 'FFmpeg/ffmpeg_meta_info'
19
+ require 'libAV/libav'
20
+ require 'libAV/libav_audio_stream'
21
+ require 'libAV/libav_video_stream'
22
+ require 'libAV/libav_meta_info'
23
+ require 'MPlayer/mplayer'
24
+ require 'MPlayer/mplayer_audio_stream'
25
+ require 'MPlayer/mplayer_video_stream'
26
+ require 'MPlayer/mplayer_meta_info'
data/lib/version.info CHANGED
@@ -1 +1 @@
1
- 1.0.1
1
+ 1.1.1
data/lib/version.rb CHANGED
@@ -12,11 +12,11 @@ module VCSRuby
12
12
  end
13
13
 
14
14
  def self.update_version
15
+ current_version = read_version
15
16
  parts = File.open(version_path, &:readline).split('.').map(&:strip)
16
17
  parts[2] = (parts[2].to_i + 1).to_s
17
18
  File.open(version_path, 'w') {|f| f.write(parts.join('.')) }
18
-
19
- read_version
19
+ current_version
20
20
  end
21
21
  end
22
22
 
data/lib/video.rb ADDED
@@ -0,0 +1,89 @@
1
+ #
2
+ # Represents the video file
3
+ #
4
+
5
+ require 'vcs'
6
+
7
+ module VCSRuby
8
+ class Video
9
+ attr_reader :config
10
+
11
+ def initialize video
12
+ initialize_filename video
13
+ initialize_capturers
14
+ end
15
+
16
+ def valid?
17
+ capturer.file_valid?
18
+ end
19
+
20
+ def info
21
+ capturer.info
22
+ end
23
+
24
+ def video
25
+ capturer.video_streams.first
26
+ end
27
+
28
+ def video_streams
29
+ capturer.video_streams
30
+ end
31
+
32
+ def audio
33
+ capturer.audio_streams.first
34
+ end
35
+
36
+ def audio_streams
37
+ capturer.audio_streams
38
+ end
39
+
40
+ def full_path
41
+ File.join(@path, @filename)
42
+ end
43
+
44
+ def contact_sheet
45
+ @contact_sheet ||= ContactSheet.new self, capturer
46
+ end
47
+
48
+ def frame time_index
49
+ return Frame.new self, capturer, time_index
50
+ end
51
+
52
+ private
53
+ def initialize_filename video
54
+ @path = File.dirname(File.absolute_path(video))
55
+ @filename = File.basename(video)
56
+ end
57
+
58
+ def initialize_capturers
59
+ @capturers = []
60
+
61
+ @capturers << LibAV.new(self)
62
+ @capturers << MPlayer.new(self)
63
+ @capturers << FFmpeg.new(self)
64
+
65
+ if Configuration.instance.verbose?
66
+ puts "Available capturers: #{available_capturers.map{ |c| c.to_s }.join(', ')}"
67
+ end
68
+ end
69
+
70
+ def available_capturers
71
+ @capturers.select{ |c| c.available? }
72
+ end
73
+
74
+ def capturer
75
+ result = nil
76
+ if Configuration.instance.capturer == :any
77
+ result = available_capturers.first
78
+ else
79
+ result = available_capturers.select{ |c| c.name == Configuration.instance.capturer }.first
80
+ end
81
+
82
+ unless result
83
+ raise "Selected Capturer (#{Configuration.instance.capturer}) not available. Install one of these: #{@capturers.map{ |c| c.name }.join(', ')}"
84
+ end
85
+
86
+ return result
87
+ end
88
+ end
89
+ end