ffmpeg_core 0.4.0 → 0.5.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +38 -0
- data/lib/ffmpeg_core/audio_extractor.rb +61 -0
- data/lib/ffmpeg_core/clipper.rb +69 -0
- data/lib/ffmpeg_core/configuration.rb +46 -15
- data/lib/ffmpeg_core/movie.rb +42 -0
- data/lib/ffmpeg_core/probe.rb +48 -0
- data/lib/ffmpeg_core/transcoder.rb +12 -2
- data/lib/ffmpeg_core/version.rb +1 -1
- data/lib/ffmpeg_core.rb +2 -0
- metadata +19 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bb9323f5aae7b7f162d4b43f219f14506af5fa012e033451a2021e92bec14128
|
|
4
|
+
data.tar.gz: 7164b154e6b542e2179f430418cd3b42fb68cc48a5dff62a679b13eb135b3de7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f97a26864b497d77ae8b862daa0b45154f038cbc94dfbf56703a4879ccbac680b294acdacf35d20538fb736d046dbd85c55514bc6d7e7a8c0d6c41030878f40d
|
|
7
|
+
data.tar.gz: 5235e82f757aa787cb76dbed3af50a5bd4eff074c9476918f90d9d8c8187f658979a6ca0b773d762569f631394326c27c45d45d265f1a0e13f9eb2890a1bba26
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,44 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.5.0] - 2026-04-13
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Probe — extended metadata:**
|
|
13
|
+
- `subtitle_streams` — array of subtitle streams
|
|
14
|
+
- `chapters` — file chapters (requires ffprobe `-show_chapters`)
|
|
15
|
+
- `format_name` — container format name (mp4, mkv, avi…)
|
|
16
|
+
- `tags` — file-level tags (title, artist, etc.)
|
|
17
|
+
- `audio_sample_rate` — sample rate of the first audio stream
|
|
18
|
+
- `audio_channels` — channel count
|
|
19
|
+
- `audio_channel_layout` — channel layout string (stereo, 5.1…)
|
|
20
|
+
- `pixel_format` — video pixel format (yuv420p, etc.)
|
|
21
|
+
- `has_video?` / `has_audio?` — stream presence predicates
|
|
22
|
+
- `exif` — EXIF tags merged from format and video stream tags (FFmpeg 8.1)
|
|
23
|
+
- **Movie — new operations:**
|
|
24
|
+
- `cut(output, start_time:, duration:)` / `cut(output, start_time:, end_time:)` — lossless trim via `-c copy`
|
|
25
|
+
- `extract_audio(output, codec:)` — extract audio track to file
|
|
26
|
+
- `screenshots(output_dir, count:)` — extract multiple screenshots at equal intervals
|
|
27
|
+
- **Hardware acceleration — AV1 and D3D12 support (FFmpeg 8.0/8.1):**
|
|
28
|
+
- AV1 via `:nvenc` (`av1_nvenc`), `:vaapi` (`av1_vaapi`), `:vulkan` (`av1_vulkan`)
|
|
29
|
+
- D3D12 via `:d3d12` (`h264_d3d12va`) for Windows
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- `Probe#probe!` now passes `-show_chapters` to ffprobe
|
|
34
|
+
|
|
35
|
+
## [0.4.1] - 2026-04-09
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- Binary detection on Windows: `where` fallback now correctly resolves ffmpeg/ffprobe when not on PATH
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- Simplified binary detection: removed redundant Ruby PATH scan, extracted lookup steps into focused private methods
|
|
44
|
+
- Expanded `Configuration` spec: ENV override, `BinaryNotFoundError`, known-path fallback, `reset_configuration!`
|
|
45
|
+
|
|
8
46
|
## [0.4.0] - 2026-01-26
|
|
9
47
|
|
|
10
48
|
### Added
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
5
|
+
module FFmpegCore
|
|
6
|
+
# Extract audio track from video files
|
|
7
|
+
class AudioExtractor
|
|
8
|
+
attr_reader :input_path, :output_path, :options
|
|
9
|
+
|
|
10
|
+
def initialize(input_path, output_path, options = {})
|
|
11
|
+
@input_path = input_path.to_s
|
|
12
|
+
@output_path = output_path.to_s
|
|
13
|
+
@options = options
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def run
|
|
17
|
+
validate_input!
|
|
18
|
+
ensure_output_directory!
|
|
19
|
+
|
|
20
|
+
command = build_command
|
|
21
|
+
execute_command(command)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def validate_input!
|
|
27
|
+
return if %r{^(https?|rtmp|rtsp)://}.match?(input_path)
|
|
28
|
+
|
|
29
|
+
raise InvalidInputError, "Input file does not exist: #{input_path}" unless File.exist?(input_path)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def ensure_output_directory!
|
|
33
|
+
output_dir = File.dirname(output_path)
|
|
34
|
+
FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def build_command
|
|
38
|
+
cmd = [FFmpegCore.configuration.ffmpeg_binary, "-i", input_path]
|
|
39
|
+
cmd += ["-vn"]
|
|
40
|
+
cmd += ["-c:a", options[:codec]] if options[:codec]
|
|
41
|
+
cmd += ["-y", output_path]
|
|
42
|
+
cmd
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def execute_command(command)
|
|
46
|
+
_stdout, stderr, status = Open3.capture3(*command)
|
|
47
|
+
|
|
48
|
+
unless status.success?
|
|
49
|
+
raise TranscodingError.new(
|
|
50
|
+
"FFmpeg audio extraction failed",
|
|
51
|
+
command: command.join(" "),
|
|
52
|
+
exit_status: status.exitstatus,
|
|
53
|
+
stdout: "",
|
|
54
|
+
stderr: stderr
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
output_path
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
5
|
+
module FFmpegCore
|
|
6
|
+
# Cut/trim video segments
|
|
7
|
+
class Clipper
|
|
8
|
+
attr_reader :input_path, :output_path, :options
|
|
9
|
+
|
|
10
|
+
def initialize(input_path, output_path, options = {})
|
|
11
|
+
@input_path = input_path.to_s
|
|
12
|
+
@output_path = output_path.to_s
|
|
13
|
+
@options = options
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def run
|
|
17
|
+
validate_input!
|
|
18
|
+
ensure_output_directory!
|
|
19
|
+
|
|
20
|
+
command = build_command
|
|
21
|
+
execute_command(command)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def validate_input!
|
|
27
|
+
return if %r{^(https?|rtmp|rtsp)://}.match?(input_path)
|
|
28
|
+
|
|
29
|
+
raise InvalidInputError, "Input file does not exist: #{input_path}" unless File.exist?(input_path)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def ensure_output_directory!
|
|
33
|
+
output_dir = File.dirname(output_path)
|
|
34
|
+
FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def build_command
|
|
38
|
+
cmd = [FFmpegCore.configuration.ffmpeg_binary]
|
|
39
|
+
|
|
40
|
+
cmd += ["-ss", options[:start_time].to_s] if options[:start_time]
|
|
41
|
+
cmd += ["-i", input_path]
|
|
42
|
+
|
|
43
|
+
if options[:duration]
|
|
44
|
+
cmd += ["-t", options[:duration].to_s]
|
|
45
|
+
elsif options[:end_time]
|
|
46
|
+
cmd += ["-to", options[:end_time].to_s]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
cmd += ["-c", "copy", "-y", output_path]
|
|
50
|
+
cmd
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def execute_command(command)
|
|
54
|
+
_stdout, stderr, status = Open3.capture3(*command)
|
|
55
|
+
|
|
56
|
+
unless status.success?
|
|
57
|
+
raise TranscodingError.new(
|
|
58
|
+
"FFmpeg cut failed",
|
|
59
|
+
command: command.join(" "),
|
|
60
|
+
exit_status: status.exitstatus,
|
|
61
|
+
stdout: "",
|
|
62
|
+
stderr: stderr
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
output_path
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -40,23 +40,54 @@ module FFmpegCore
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def detect_binary(name)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
binary_from_env(name) ||
|
|
44
|
+
binary_from_system_lookup(name) ||
|
|
45
|
+
binary_from_known_paths(name) ||
|
|
46
|
+
raise(BinaryNotFoundError, <<~MSG)
|
|
47
|
+
#{name} not found.
|
|
48
|
+
Install FFmpeg and ensure it's in PATH.
|
|
49
|
+
macOS: brew install ffmpeg
|
|
50
|
+
Linux: apt install ffmpeg / yum install ffmpeg
|
|
51
|
+
Windows: choco install ffmpeg or scoop install ffmpeg
|
|
52
|
+
MSG
|
|
53
|
+
end
|
|
49
54
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
# Checks FFMPEGCORE_<NAME> env variable for an explicit binary override.
|
|
56
|
+
def binary_from_env(name)
|
|
57
|
+
path = ENV["FFMPEGCORE_#{name.upcase}"]
|
|
58
|
+
path if path && File.executable?(path)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Uses the OS-native `which` (Unix) or `where` (Windows) command.
|
|
62
|
+
def binary_from_system_lookup(name)
|
|
63
|
+
cmd = Gem.win_platform? ? "where #{name}" : "which #{name}"
|
|
64
|
+
stdout, status = Open3.capture2(cmd)
|
|
65
|
+
return unless status.success?
|
|
58
66
|
|
|
59
|
-
|
|
67
|
+
path = stdout.lines.first&.strip
|
|
68
|
+
path if path && File.executable?(path)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Falls back to a list of well-known installation paths.
|
|
72
|
+
def binary_from_known_paths(name)
|
|
73
|
+
known_paths(name).find { |p| File.executable?(p) }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def known_paths(name)
|
|
77
|
+
if Gem.win_platform?
|
|
78
|
+
[
|
|
79
|
+
"C:/ffmpeg/bin/#{name}.exe",
|
|
80
|
+
"C:/ProgramData/chocolatey/bin/#{name}.exe",
|
|
81
|
+
"#{ENV["USERPROFILE"]}/scoop/apps/ffmpeg/current/bin/#{name}.exe"
|
|
82
|
+
]
|
|
83
|
+
else
|
|
84
|
+
[
|
|
85
|
+
"/opt/homebrew/bin/#{name}", # macOS ARM
|
|
86
|
+
"/usr/local/bin/#{name}", # macOS Intel / Linux
|
|
87
|
+
"/usr/bin/#{name}",
|
|
88
|
+
"/snap/bin/#{name}"
|
|
89
|
+
]
|
|
90
|
+
end
|
|
60
91
|
end
|
|
61
92
|
end
|
|
62
93
|
|
data/lib/ffmpeg_core/movie.rb
CHANGED
|
@@ -52,5 +52,47 @@ module FFmpegCore
|
|
|
52
52
|
screenshotter = Screenshot.new(path, output_path, options)
|
|
53
53
|
screenshotter.extract
|
|
54
54
|
end
|
|
55
|
+
|
|
56
|
+
# Cut/trim a segment from video
|
|
57
|
+
#
|
|
58
|
+
# @param output_path [String] Path to output file
|
|
59
|
+
# @param options [Hash] Cut options
|
|
60
|
+
# @option options [Integer, Float] :start_time Start time in seconds
|
|
61
|
+
# @option options [Integer, Float] :duration Duration in seconds
|
|
62
|
+
# @option options [Integer, Float] :end_time End time in seconds (alternative to :duration)
|
|
63
|
+
# @return [String] Path to output file
|
|
64
|
+
def cut(output_path, options = {})
|
|
65
|
+
clipper = Clipper.new(path, output_path, options)
|
|
66
|
+
clipper.run
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Extract audio track from video
|
|
70
|
+
#
|
|
71
|
+
# @param output_path [String] Path to output audio file
|
|
72
|
+
# @param options [Hash] Extraction options
|
|
73
|
+
# @option options [String] :codec Audio codec (e.g., "libmp3lame", "aac")
|
|
74
|
+
# @return [String] Path to output file
|
|
75
|
+
def extract_audio(output_path, options = {})
|
|
76
|
+
extractor = AudioExtractor.new(path, output_path, options)
|
|
77
|
+
extractor.run
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Extract multiple screenshots at equal intervals
|
|
81
|
+
#
|
|
82
|
+
# @param output_dir [String] Directory to save screenshots
|
|
83
|
+
# @param count [Integer] Number of screenshots to extract (default: 5)
|
|
84
|
+
# @return [Array<String>] Paths to screenshot files
|
|
85
|
+
def screenshots(output_dir, count: 5)
|
|
86
|
+
FileUtils.mkdir_p(output_dir)
|
|
87
|
+
total = duration || 0
|
|
88
|
+
interval = total / (count + 1).to_f
|
|
89
|
+
|
|
90
|
+
(1..count).map do |i|
|
|
91
|
+
seek = (interval * i).round(2)
|
|
92
|
+
output_path = File.join(output_dir, format("screenshot_%03d.jpg", i))
|
|
93
|
+
Screenshot.new(path, output_path, seek_time: seek).extract
|
|
94
|
+
output_path
|
|
95
|
+
end
|
|
96
|
+
end
|
|
55
97
|
end
|
|
56
98
|
end
|
data/lib/ffmpeg_core/probe.rb
CHANGED
|
@@ -111,6 +111,53 @@ module FFmpegCore
|
|
|
111
111
|
streams.select { |s| s["codec_type"] == "audio" }
|
|
112
112
|
end
|
|
113
113
|
|
|
114
|
+
def subtitle_streams
|
|
115
|
+
streams.select { |s| s["codec_type"] == "subtitle" }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def chapters
|
|
119
|
+
@metadata.fetch("chapters", [])
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def format_name
|
|
123
|
+
@metadata.dig("format", "format_name")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def tags
|
|
127
|
+
@metadata.dig("format", "tags") || {}
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def audio_sample_rate
|
|
131
|
+
audio_stream&.dig("sample_rate")&.to_i
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def audio_channels
|
|
135
|
+
audio_stream&.dig("channels")
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def audio_channel_layout
|
|
139
|
+
audio_stream&.dig("channel_layout")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def pixel_format
|
|
143
|
+
video_stream&.dig("pix_fmt")
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def has_video?
|
|
147
|
+
!video_stream.nil?
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def has_audio?
|
|
151
|
+
!audio_stream.nil?
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# EXIF metadata: merges format-level and video stream tags (FFmpeg 8.1+)
|
|
155
|
+
def exif
|
|
156
|
+
format_tags = @metadata.dig("format", "tags") || {}
|
|
157
|
+
stream_tags = video_stream&.dig("tags") || {}
|
|
158
|
+
format_tags.merge(stream_tags)
|
|
159
|
+
end
|
|
160
|
+
|
|
114
161
|
def valid?
|
|
115
162
|
!video_stream.nil?
|
|
116
163
|
end
|
|
@@ -135,6 +182,7 @@ module FFmpegCore
|
|
|
135
182
|
"-print_format", "json",
|
|
136
183
|
"-show_format",
|
|
137
184
|
"-show_streams",
|
|
185
|
+
"-show_chapters",
|
|
138
186
|
path
|
|
139
187
|
]
|
|
140
188
|
|
|
@@ -189,25 +189,35 @@ module FFmpegCore
|
|
|
189
189
|
:h264
|
|
190
190
|
when /x265|hevc/i
|
|
191
191
|
:hevc
|
|
192
|
+
when /av1|libaom/i
|
|
193
|
+
:av1
|
|
192
194
|
end
|
|
193
195
|
end
|
|
194
196
|
|
|
195
197
|
HW_FLAGS = {
|
|
196
198
|
vaapi: ["-hwaccel", "vaapi", "-hwaccel_output_format", "vaapi"],
|
|
197
199
|
qsv: ["-hwaccel", "qsv"],
|
|
198
|
-
nvenc: ["-hwaccel", "cuda", "-hwaccel_output_format", "cuda"]
|
|
200
|
+
nvenc: ["-hwaccel", "cuda", "-hwaccel_output_format", "cuda"],
|
|
201
|
+
vulkan: ["-hwaccel", "vulkan"],
|
|
202
|
+
d3d12: ["-hwaccel", "d3d12va"]
|
|
199
203
|
}.freeze
|
|
200
204
|
|
|
201
205
|
HW_ENCODERS = {
|
|
202
206
|
h264: {
|
|
203
207
|
nvenc: "h264_nvenc",
|
|
204
208
|
vaapi: "h264_vaapi",
|
|
205
|
-
qsv: "h264_qsv"
|
|
209
|
+
qsv: "h264_qsv",
|
|
210
|
+
d3d12: "h264_d3d12va"
|
|
206
211
|
},
|
|
207
212
|
hevc: {
|
|
208
213
|
nvenc: "hevc_nvenc",
|
|
209
214
|
vaapi: "hevc_vaapi",
|
|
210
215
|
qsv: "hevc_qsv"
|
|
216
|
+
},
|
|
217
|
+
av1: {
|
|
218
|
+
nvenc: "av1_nvenc",
|
|
219
|
+
vaapi: "av1_vaapi",
|
|
220
|
+
vulkan: "av1_vulkan"
|
|
211
221
|
}
|
|
212
222
|
}.freeze
|
|
213
223
|
end
|
data/lib/ffmpeg_core/version.rb
CHANGED
data/lib/ffmpeg_core.rb
CHANGED
|
@@ -24,4 +24,6 @@ require_relative "ffmpeg_core/configuration"
|
|
|
24
24
|
require_relative "ffmpeg_core/probe"
|
|
25
25
|
require_relative "ffmpeg_core/transcoder"
|
|
26
26
|
require_relative "ffmpeg_core/screenshot"
|
|
27
|
+
require_relative "ffmpeg_core/clipper"
|
|
28
|
+
require_relative "ffmpeg_core/audio_extractor"
|
|
27
29
|
require_relative "ffmpeg_core/movie"
|
metadata
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ffmpeg_core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alexey Poimtsev
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: simplecov
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
12
26
|
description: A clean, well-tested FFmpeg wrapper with modern Ruby conventions, proper
|
|
13
27
|
error handling, and zero dependencies.
|
|
14
28
|
email:
|
|
@@ -23,6 +37,8 @@ files:
|
|
|
23
37
|
- Rakefile
|
|
24
38
|
- lefthook.yml
|
|
25
39
|
- lib/ffmpeg_core.rb
|
|
40
|
+
- lib/ffmpeg_core/audio_extractor.rb
|
|
41
|
+
- lib/ffmpeg_core/clipper.rb
|
|
26
42
|
- lib/ffmpeg_core/configuration.rb
|
|
27
43
|
- lib/ffmpeg_core/errors.rb
|
|
28
44
|
- lib/ffmpeg_core/movie.rb
|
|
@@ -53,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
53
69
|
- !ruby/object:Gem::Version
|
|
54
70
|
version: '0'
|
|
55
71
|
requirements: []
|
|
56
|
-
rubygems_version: 4.0.
|
|
72
|
+
rubygems_version: 4.0.10
|
|
57
73
|
specification_version: 4
|
|
58
74
|
summary: Modern Ruby wrapper for FFmpeg
|
|
59
75
|
test_files: []
|