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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/video2gif/cli.rb +30 -1
- data/lib/video2gif/ffmpeg/subtitles.rb +40 -0
- data/lib/video2gif/ffmpeg.rb +33 -5
- data/lib/video2gif/options.rb +21 -8
- data/lib/video2gif/utils.rb +9 -0
- data/lib/video2gif/version.rb +1 -1
- data/lib/video2gif.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f906c9980bbe4a0dacb015c1a6b0a1af786d1f82b252ea8963d71ff46a53acc9
|
4
|
+
data.tar.gz: 6d948e892703ff81ecce6395074efc6486a97f7348c3378369c2712476d8fb35
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f83e62d9f5ee2f592d1bf9e5ea6b5ea93bf1e5fea7ad004399f611312985ba540c63fd1ee9e29d7b3e74017140cb55630f4f66aa2c4833de091d231b40c2ebce
|
7
|
+
data.tar.gz: c36ffa22c9fa87c45f318210eff0603d02d4be084d29d0f141c8e49fcdcf9e2e20db73edfd76984da3434dafe923beb586b38ac8653ae47f2d04ca28814b7dae
|
data/Gemfile.lock
CHANGED
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(
|
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
|
+
|
data/lib/video2gif/ffmpeg.rb
CHANGED
@@ -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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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'
|
data/lib/video2gif/options.rb
CHANGED
@@ -148,14 +148,27 @@ module Video2gif
|
|
148
148
|
options[:tonemap] = t || 'hable'
|
149
149
|
end
|
150
150
|
|
151
|
-
parser.on('--subtitles [
|
152
|
-
'Attempt to use the
|
153
|
-
'
|
154
|
-
'
|
155
|
-
'
|
156
|
-
'
|
157
|
-
|
158
|
-
|
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):'
|
data/lib/video2gif/utils.rb
CHANGED
@@ -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
|
data/lib/video2gif/version.rb
CHANGED
data/lib/video2gif.rb
CHANGED
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.
|
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
|