video2gif 0.0.21 → 0.0.22

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f4e0acee1fd59d611f97a41816906dbbb332ced3fbaaf90b0801e0736c2d00f9
4
- data.tar.gz: dc29a597398d407ab48bacdbde2bbd281602583a68e0a04c9423029a0b1ab01c
3
+ metadata.gz: f906c9980bbe4a0dacb015c1a6b0a1af786d1f82b252ea8963d71ff46a53acc9
4
+ data.tar.gz: 6d948e892703ff81ecce6395074efc6486a97f7348c3378369c2712476d8fb35
5
5
  SHA512:
6
- metadata.gz: b8e804320c36b811ca32f06a454484019d3e84d2b084aa90e7229d35e177a8d226126d02c2ea1d2b581c91a5a623fae6683865e543fe41f2c488d9bc159b0c44
7
- data.tar.gz: 7f87f0e27e7f8931b7579d773c8f0e516506df02d0b992ac62e025a17fa482b066f4716404e230002fcabc9c7e41a7bd7b846777795cbe23462d6b1eaa055c1b
6
+ metadata.gz: f83e62d9f5ee2f592d1bf9e5ea6b5ea93bf1e5fea7ad004399f611312985ba540c63fd1ee9e29d7b3e74017140cb55630f4f66aa2c4833de091d231b40c2ebce
7
+ data.tar.gz: c36ffa22c9fa87c45f318210eff0603d02d4be084d29d0f141c8e49fcdcf9e2e20db73edfd76984da3434dafe923beb586b38ac8653ae47f2d04ca28814b7dae
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- video2gif (0.0.21)
4
+ video2gif (0.0.22)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/lib/video2gif/cli.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
3
4
  require 'logger'
4
5
  require 'open3'
5
6
  require 'video2gif'
@@ -11,18 +12,45 @@ module Video2gif
11
12
  logger = Logger.new(STDOUT)
12
13
  options = Video2gif::Options.parse(ARGV)
13
14
 
15
+ if options[:subtitles]
16
+ lines = []
17
+
18
+ Open3.popen2e(*Video2gif::FFmpeg.ffprobe_command(options, logger)) do |stdin, stdout_stderr, thread|
19
+ stdin.close
20
+ stdout_stderr.each do |line|
21
+ logger.info(line.chomp) if options[:verbose] unless options[:quiet]
22
+ lines << line
23
+ end
24
+ stdout_stderr.close
25
+
26
+ unless thread.value.success?
27
+ # TODO: more info, output lines with errors?
28
+ raise "Process #{thread.pid} failed! Try again with --verbose to see error."
29
+ end
30
+ end
31
+
32
+ options[:probe_infos] = JSON.parse(lines.join, symbolize_names: true)
33
+
34
+ if options[:subtitles] && !options[:probe_infos][:streams].any? do |s|
35
+ s[:codec_type] == 'subtitle'
36
+ end
37
+ logger.warn('Could not find subtitles in the file, they will be omitted') unless options[:quiet]
38
+ end
39
+ end
40
+
14
41
  if options[:autocrop]
15
42
  Open3.popen2e(*Video2gif::FFmpeg.cropdetect_command(options, logger)) do |stdin, stdout_stderr, thread|
16
43
  stdin.close
17
44
  stdout_stderr.each do |line|
18
45
  logger.info(line.chomp) if options[:verbose] unless options[:quiet]
19
46
  if line.include?('Parsed_cropdetect')
20
- options[:autocrop] = line.match('crop=([0-9]+\:[0-9]+\:[0-9]+\:[0-9]+)')
47
+ options[:autocrop] = line.match(Video2Gif::FFmpeg::CROP_REGEX)
21
48
  end
22
49
  end
23
50
  stdout_stderr.close
24
51
 
25
52
  unless thread.value.success?
53
+ # TODO: more info, output lines with errors?
26
54
  raise "Process #{thread.pid} failed! Try again with --verbose to see error."
27
55
  end
28
56
  end
@@ -36,6 +64,7 @@ module Video2gif
36
64
  stdout_stderr.close
37
65
 
38
66
  unless thread.value.success?
67
+ # TODO: more info, output lines with errors?
39
68
  raise "Process #{thread.pid} failed! Try again with --verbose to see error."
40
69
  end
41
70
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ module Video2gif
5
+ module FFmpeg
6
+ module Subtitles
7
+ KNOWN_TEXT_FORMATS = %w[
8
+ ass
9
+ dvb_teletext
10
+ eia_608
11
+ hdmv_text_subtitle
12
+ jacosub
13
+ microdvd
14
+ mov_text
15
+ mpl2
16
+ pjs
17
+ realtext
18
+ sami
19
+ srt
20
+ ssa
21
+ stl
22
+ subrip
23
+ subviewer
24
+ subviewer1
25
+ text
26
+ ttml
27
+ vplayer
28
+ webvtt
29
+ ]
30
+
31
+ KNOWN_BITMAP_FORMATS = %w[
32
+ dvb_subtitle
33
+ dvd_subtitle
34
+ hdmv_pgs_subtitle
35
+ xsub
36
+ ]
37
+ end
38
+ end
39
+ end
40
+
@@ -3,14 +3,30 @@
3
3
 
4
4
  module Video2gif
5
5
  module FFmpeg
6
+ CROP_REGEX = /crop=([0-9]+\:[0-9]+\:[0-9]+\:[0-9]+)/
7
+
6
8
  def self.filtergraph(options)
7
9
  filtergraph = []
8
10
 
9
- # Subtitle overlay has to be first due to sync problems.
10
- if options[:subtitles] && options[:subtitles].is_a?(TrueClass)
11
- filtergraph << '[0:v][0:s:0]overlay=format=auto'
12
- elsif options[:subtitles] && options[:subtitles].match(/\A\d+\z/)
13
- filtergraph << "[0:v][0:s:#{options[:subtitles]}]overlay=format=auto"
11
+ if options[:subtitles] && options[:probe_infos][:streams].any? do |s|
12
+ s[:codec_type] == 'subtitle'
13
+ end
14
+ video_info = options[:probe_infos][:streams].find { |s| s[:codec_type] == 'video' }
15
+ subtitle_info = options[:probe_infos][:streams].find_all { |s| s[:codec_type] == 'subtitle' }[options[:subtitle_index]]
16
+
17
+ if Video2gif::FFmpeg::Subtitles::KNOWN_TEXT_FORMATS.include?(subtitle_info[:codec_name])
18
+ filtergraph << "setpts=PTS+#{Video2gif::Utils.duration_to_seconds(options[:seek])}/TB"
19
+ filtergraph << "subtitles='#{options[:input_filename]}':si=#{options[:subtitle_index]}"
20
+ filtergraph << 'setpts=PTS-STARTPTS'
21
+ elsif Video2gif::FFmpeg::Subtitles::KNOWN_BITMAP_FORMATS.include?(subtitle_info[:codec_name])
22
+ filtergraph << "[0:s:#{options[:subtitle_index]}]scale=" + %W[
23
+ flags=lanczos
24
+ sws_dither=none
25
+ width=#{video_info[:width]}
26
+ height=#{video_info[:height]}
27
+ ].join(':') + '[subs]' if options[:width]
28
+ filtergraph << "[0:v][subs]overlay=format=auto"
29
+ end
14
30
  end
15
31
 
16
32
  # Set 'fps' filter first, drop unneeded frames instead of
@@ -114,6 +130,18 @@ module Video2gif
114
130
  filtergraph << "[paletteuse][palette]paletteuse=dither=#{options[:dither] || 'floyd_steinberg'}:diff_mode=rectangle"
115
131
  end
116
132
 
133
+ def self.ffprobe_command(options, logger, executable: 'ffprobe')
134
+ command = [executable]
135
+ command << '-v' << 'error'
136
+ command << '-show_entries' << 'stream'
137
+ command << '-print_format' << 'json'
138
+ command << '-i' << options[:input_filename]
139
+
140
+ logger.info(command.join(' ')) if options[:verbose] unless options[:quiet]
141
+
142
+ command
143
+ end
144
+
117
145
  def self.ffmpeg_command(options, executable: 'ffmpeg')
118
146
  command = [executable]
119
147
  command << '-y'
@@ -148,14 +148,27 @@ module Video2gif
148
148
  options[:tonemap] = t || 'hable'
149
149
  end
150
150
 
151
- parser.on('--subtitles [SELECTION INDEX]',
152
- 'Attempt to use the subtitles built into the video to',
153
- 'overlay text on the resulting GIF. Takes an optional',
154
- 'integer value to choose the subtitle stream. (Defaults',
155
- 'to the first subtitle stream, 0) (ONLY WORKS WITH BITMAP',
156
- 'SUBTITLES AT THIS TIME)') do |s|
157
- options[:subtitles] = s || true
158
- end
151
+ parser.on('--subtitles [INDEX]',
152
+ '(Experimental, requires ffprobe) Attempt to use the',
153
+ 'subtitles built into the video to overlay text on the',
154
+ 'resulting GIF. Takes an optional integer value to',
155
+ 'choose the subtitle stream (defaults to the first',
156
+ 'subtitle stream, index 0)') do |s|
157
+ unless Video2gif::Utils.is_executable?('ffprobe')
158
+ puts 'ERROR: Requires FFmpeg utils to be installed (for ffprobe)!'
159
+ exit 1
160
+ end
161
+
162
+ options[:subtitles] = s || true
163
+ options[:subtitle_index] = if options[:subtitles].is_a?(TrueClass) # default to first stream
164
+ 0
165
+ elsif options[:subtitles].match?(/\A\d+\z/) # select stream by index
166
+ options[:subtitles].to_i
167
+ elsif options[:subtitles].is_a?(String) # open subtitles file
168
+ puts 'ERROR: Selecting subtitles by filename is not yet supported!'
169
+ exit 1
170
+ end
171
+ end
159
172
 
160
173
  parser.separator ''
161
174
  parser.separator 'Text overlay options (only used if text is defined):'
@@ -10,5 +10,14 @@ module Video2gif
10
10
  end
11
11
  end.flatten.any?
12
12
  end
13
+
14
+ # Convert "[-][HH:]MM:SS[.m...]" to "[-]S+[.m...]".
15
+ # https://ffmpeg.org/ffmpeg-utils.html#time-duration-syntax
16
+ def self.duration_to_seconds(duration)
17
+ return duration unless duration.include?(?:)
18
+ m = duration.match(/(?<sign>-)?(?<hours>\d+:)?(?<minutes>\d+):(?<seconds>\d+)(?<millis>\.\d+)?/)
19
+ seconds = m[:hours].to_i * 60 * 60 + m[:minutes].to_i * 60 + m[:seconds].to_i
20
+ duration = "#{m[:sign]}#{seconds}#{m[:millis]}"
21
+ end
13
22
  end
14
23
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Video2gif
4
- VERSION = '0.0.21'
4
+ VERSION = '0.0.22'
5
5
  end
data/lib/video2gif.rb CHANGED
@@ -4,6 +4,7 @@
4
4
  # them in
5
5
  require 'video2gif/cli'
6
6
  require 'video2gif/ffmpeg'
7
+ require 'video2gif/ffmpeg/subtitles'
7
8
  require 'video2gif/options'
8
9
  require 'video2gif/utils'
9
10
  require 'video2gif/version'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: video2gif
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.21
4
+ version: 0.0.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emily St.
@@ -103,6 +103,7 @@ files:
103
103
  - lib/video2gif.rb
104
104
  - lib/video2gif/cli.rb
105
105
  - lib/video2gif/ffmpeg.rb
106
+ - lib/video2gif/ffmpeg/subtitles.rb
106
107
  - lib/video2gif/options.rb
107
108
  - lib/video2gif/utils.rb
108
109
  - lib/video2gif/version.rb