vcs_ruby 1.0.1 → 1.1.0

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.
@@ -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