ffmpeg_core 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4efe5152c9dd01b8ebb2d34c9f6d6877371d017845242297f57f65044c968648
4
- data.tar.gz: a1b09735ebc0acc9c21b252e3fa32b4168e0c5694eab8d72fa299e7c8c88dc9f
3
+ metadata.gz: 026e0c17db47148e554c7b08f77a3e21a7b78b6f7d65915d3bf5159d057aae08
4
+ data.tar.gz: d0e49b402f4dff9aa73944f3a6e1543260d970c1114b4faccf9d3c51bb3da670
5
5
  SHA512:
6
- metadata.gz: f3c4ace64795f63226082eb3eeb6e395c0f35976b86de25a3a8ed7c46973ce7d2b234de2485a6e3cd083060653da3f2d6a8f8c3a254260a15d5e472462097a21
7
- data.tar.gz: 7b390fe28da3e17efe7a4523001aa41a0aa70685fc8e5d5951faf55e5d2feabe96e3aa5e48f94c0f3a0c70016a23b49c7b063739ae953bde41bba92b524e36b7
6
+ metadata.gz: 1d6eee4ad41d752b13ce1aab69be77ca24a5368b60db93b468feb12c2340d7f26dec285d09924503237a176b6bed299d4d9b1801e375fb6c5953f5c030e8f957
7
+ data.tar.gz: 987108002867b7d622aeb57b2606e59edae50a4762e76bb7287f52f97de836b740d2a65258df1e7f6baf8f1596e288d3bdad1b666e3b1b01974046212cfea3bf
data/CHANGELOG.md CHANGED
@@ -5,6 +5,19 @@ 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.3.0] - 2026-01-16
9
+
10
+ ### Added
11
+
12
+ - **Remote Input Support:** `Movie.new` now accepts HTTP, HTTPS, RTMP, and RTSP URLs.
13
+ - **Crop Support:** Added `crop` option to `transcode` (e.g., `crop: { width: 100, height: 100, x: 10, y: 10 }`).
14
+ - **Advanced Metadata:** Added `video_profile` and `video_level` to `Probe`.
15
+ - Rotation detection from `side_data_list` for better compatibility with newer video formats.
16
+
17
+ ### Fixed
18
+
19
+ - **Rotation Geometry:** `Probe#width` and `Probe#height` now correctly swap values if the video is rotated 90 or 270 degrees.
20
+
8
21
  ## [0.2.0] - 2026-01-15
9
22
 
10
23
  ### Added
data/README.md CHANGED
@@ -11,6 +11,7 @@ Modern Ruby wrapper for FFmpeg with clean API and proper error handling.
11
11
  - Zero runtime dependencies
12
12
  - **Real-time progress reporting**
13
13
  - **Support for video/audio filters and quality presets**
14
+ - **Remote input support (HTTP/HTTPS/RTMP/RTSP)**
14
15
  - Proper error handling with detailed context
15
16
  - Thread-safe configuration
16
17
  - Simple, intuitive API
@@ -41,19 +42,26 @@ bundle install
41
42
  ```ruby
42
43
  require "ffmpeg_core"
43
44
 
44
- # Load a video file
45
+ # Load a video file or remote URL
45
46
  movie = FFmpegCore::Movie.new("input.mp4")
47
+ # movie = FFmpegCore::Movie.new("http://example.com/video.mp4")
46
48
 
47
49
  # Get metadata
48
50
  movie.duration # => 120.5 (seconds)
49
- movie.resolution # => "1920x1080"
51
+ movie.resolution # => "1920x1080" (automatically swapped if rotated)
52
+ movie.width # => 1920
53
+ movie.height # => 1080
50
54
  movie.video_codec # => "h264"
51
55
  movie.audio_codec # => "aac"
52
56
  movie.frame_rate # => 29.97
53
57
  movie.bitrate # => 5000 (kb/s)
54
- movie.rotation # => 90 (degrees)
55
- movie.aspect_ratio # => "16:9"
56
58
  movie.valid? # => true
59
+
60
+ # Access detailed metadata via probe
61
+ movie.probe.rotation # => 90 (degrees)
62
+ movie.probe.aspect_ratio # => "16:9"
63
+ movie.probe.video_profile # => "High"
64
+ movie.probe.video_level # => 41
57
65
  ```
58
66
 
59
67
  ### Transcoding
@@ -71,10 +79,48 @@ movie.transcode("output.mp4", {
71
79
  video_codec: "libx264",
72
80
  audio_codec: "aac",
73
81
  video_bitrate: "1000k",
82
+ audio_bitrate: "128k",
74
83
  resolution: "1280x720",
84
+ crop: { width: 500, height: 500, x: 10, y: 10 }, # Crop video
75
85
  video_filter: "scale=1280:-1,transpose=1", # Resize and rotate
86
+ audio_filter: "volume=0.5", # Reduce volume
76
87
  preset: "slow", # ffmpeg preset (ultrafast, fast, medium, slow, etc.)
77
- crf: 23 # Constant Rate Factor (0-51)
88
+ crf: 23, # Constant Rate Factor (0-51)
89
+ custom: %w[-map 0:v -map 0:a] # Custom FFmpeg flags
90
+ })
91
+ ```
92
+
93
+ ### Using Filters
94
+
95
+ FFmpegCore supports raw FFmpeg filter strings for both video (`video_filter` or `-vf`) and audio (`audio_filter` or `-af`).
96
+
97
+ **Common Video Filters:**
98
+
99
+ ```ruby
100
+ movie.transcode("output.mp4", {
101
+ # Scale to width 1280, keep aspect ratio
102
+ video_filter: "scale=1280:-1",
103
+
104
+ # Crop 100x100 starting at position (10,10)
105
+ video_filter: "crop=100:100:10:10",
106
+
107
+ # Rotate 90 degrees clockwise
108
+ video_filter: "transpose=1",
109
+
110
+ # Chain multiple filters (Scale then Rotate)
111
+ video_filter: "scale=1280:-1,transpose=1"
112
+ })
113
+ ```
114
+
115
+ **Common Audio Filters:**
116
+
117
+ ```ruby
118
+ movie.transcode("output.mp4", {
119
+ # Increase volume by 50%
120
+ audio_filter: "volume=1.5",
121
+
122
+ # Fade in first 5 seconds
123
+ audio_filter: "afade=t=in:ss=0:d=5"
78
124
  })
79
125
  ```
80
126
 
@@ -86,7 +132,7 @@ movie = FFmpegCore::Movie.new("input.mp4")
86
132
  # Extract screenshot at specific time
87
133
  movie.screenshot("thumbnail.jpg", seek_time: 5)
88
134
 
89
- # With resolution
135
+ # With resolution and quality
90
136
  movie.screenshot("thumbnail.jpg", {
91
137
  seek_time: 10,
92
138
  resolution: "640x360",
@@ -106,7 +152,7 @@ end
106
152
 
107
153
  ## Error Handling
108
154
 
109
- FFmpegCore provides specific error classes for different failure scenarios:
155
+ FFmpegCore provides specific error classes for different failure scenarios. All execution errors (transcoding, probing, screenshots) inherit from `FFmpegCore::ExecutionError`, which provides access to the command, exit status, and stderr output.
110
156
 
111
157
  ```ruby
112
158
  begin
@@ -115,9 +161,9 @@ begin
115
161
  rescue FFmpegCore::InvalidInputError => e
116
162
  # File doesn't exist or is not readable
117
163
  puts "Input error: #{e.message}"
118
- rescue FFmpegCore::TranscodingError => e
119
- # FFmpeg transcoding failed
120
- puts "Transcoding failed: #{e.message}"
164
+ rescue FFmpegCore::ExecutionError => e
165
+ # Covers TranscodingError, ProbeError, and ScreenshotError
166
+ puts "Execution failed: #{e.message}"
121
167
  puts "Command: #{e.command}"
122
168
  puts "Exit status: #{e.exit_status}"
123
169
  puts "Stderr: #{e.stderr}"
@@ -129,14 +175,16 @@ end
129
175
 
130
176
  ### Error Classes
131
177
 
132
- | Error | Description |
133
- | --------------------------------- | -------------------------------------- |
134
- | `FFmpegCore::Error` | Base error class |
135
- | `FFmpegCore::BinaryNotFoundError` | FFmpeg/FFprobe not found |
136
- | `FFmpegCore::InvalidInputError` | Input file doesn't exist or unreadable |
137
- | `FFmpegCore::ProbeError` | Failed to extract metadata |
138
- | `FFmpegCore::TranscodingError` | FFmpeg transcoding failed |
139
- | `FFmpegCore::ScreenshotError` | Screenshot extraction failed |
178
+ | Error | Description | Parent |
179
+ | --------------------------------- | -------------------------------------- | ------ |
180
+ | `FFmpegCore::Error` | Base error class | StandardError |
181
+ | `FFmpegCore::BinaryNotFoundError` | FFmpeg/FFprobe not found | Error |
182
+ | `FFmpegCore::InvalidInputError` | Input file doesn't exist or unreadable | Error |
183
+ | `FFmpegCore::OutputError` | Output file cannot be written | Error |
184
+ | `FFmpegCore::ExecutionError` | Base for command execution errors | Error |
185
+ | `FFmpegCore::ProbeError` | Failed to extract metadata | ExecutionError |
186
+ | `FFmpegCore::TranscodingError` | FFmpeg transcoding failed | ExecutionError |
187
+ | `FFmpegCore::ScreenshotError` | Screenshot extraction failed | ExecutionError |
140
188
 
141
189
  ## Development
142
190
 
@@ -153,4 +201,4 @@ bundle exec rubocop
153
201
 
154
202
  ## License
155
203
 
156
- MIT License. See [LICENSE.txt](LICENSE.txt) for details.
204
+ MIT License. See [LICENSE.txt](LICENSE.txt) for details.
@@ -39,16 +39,32 @@ module FFmpegCore
39
39
  video_stream&.dig("codec_name")
40
40
  end
41
41
 
42
+ def video_profile
43
+ video_stream&.dig("profile")
44
+ end
45
+
46
+ def video_level
47
+ video_stream&.dig("level")
48
+ end
49
+
42
50
  def audio_codec
43
51
  audio_stream&.dig("codec_name")
44
52
  end
45
53
 
46
54
  def width
47
- video_stream&.dig("width")
55
+ val = video_stream&.dig("width")
56
+ return val unless val
57
+ return val if (rotation || 0) % 180 == 0
58
+
59
+ video_stream&.dig("height")
48
60
  end
49
61
 
50
62
  def height
51
- video_stream&.dig("height")
63
+ val = video_stream&.dig("height")
64
+ return val unless val
65
+ return val if (rotation || 0) % 180 == 0
66
+
67
+ video_stream&.dig("width")
52
68
  end
53
69
 
54
70
  def frame_rate
@@ -79,6 +95,10 @@ module FFmpegCore
79
95
  tags = video_stream.fetch("tags", {})
80
96
  return tags["rotate"].to_i if tags["rotate"]
81
97
 
98
+ # Try side_data_list (common in some newer formats)
99
+ side_data = video_stream.fetch("side_data_list", []).find { |sd| sd.key?("rotation") }
100
+ return side_data["rotation"].to_i if side_data
101
+
82
102
  # Default to 0 if not found
83
103
  0
84
104
  end
@@ -102,6 +122,8 @@ module FFmpegCore
102
122
  end
103
123
 
104
124
  def validate_file!
125
+ return if path =~ %r{^(https?|rtmp|rtsp)://}
126
+
105
127
  raise InvalidInputError, "File does not exist: #{path}" unless File.exist?(path)
106
128
  raise InvalidInputError, "File is not readable: #{path}" unless File.readable?(path)
107
129
  end
@@ -25,6 +25,8 @@ module FFmpegCore
25
25
  private
26
26
 
27
27
  def validate_input!
28
+ return if input_path =~ %r{^(https?|rtmp|rtsp)://}
29
+
28
30
  raise InvalidInputError, "Input file does not exist: #{input_path}" unless File.exist?(input_path)
29
31
  end
30
32
 
@@ -57,8 +59,16 @@ module FFmpegCore
57
59
  # Frame rate
58
60
  cmd += ["-r", options[:frame_rate].to_s] if options[:frame_rate]
59
61
 
60
- # Video filter
61
- cmd += ["-vf", options[:video_filter]] if options[:video_filter]
62
+ # Video filters
63
+ video_filters = []
64
+ video_filters << options[:video_filter] if options[:video_filter]
65
+
66
+ if options[:crop]
67
+ crop = options[:crop]
68
+ video_filters << "crop=#{crop[:width]}:#{crop[:height]}:#{crop[:x]}:#{crop[:y]}"
69
+ end
70
+
71
+ cmd += ["-vf", video_filters.join(",")] unless video_filters.empty?
62
72
 
63
73
  # Audio filter
64
74
  cmd += ["-af", options[:audio_filter]] if options[:audio_filter]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FFmpegCore
4
- VERSION = "0.2.0"
5
- end
4
+ VERSION = "0.3.0"
5
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ffmpeg_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexey Poimtsev