video2gif 0.0.21 → 0.0.22

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