speedrun 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c0622185252d91812dedda26e692722bca73497fb9ef979cab8d4af602391867
4
+ data.tar.gz: 8c8d1ffb726b10f5aa0bf6c948713af211f710617fe348a9174f0cf57bd2a689
5
+ SHA512:
6
+ metadata.gz: b46d265c87d2679cef58df69c092bf72b297b27ca1663dfb5b72dd8f41a4208d9ab982cb0e26d8bab53aded0f6cdaf6a84b4fdb6753bef419faf1cc7ceb0a35c
7
+ data.tar.gz: cd11c55079861ada32b8eca148fe3c022b628b6d36b682d6a38126330f10c687bde7fdd66f787c82ddf8592118d47f429e30681837113cf46f90e1d368330796
data/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2025-11-03
9
+
10
+ ### Added
11
+ - Initial release of speedrun gem
12
+ - Core video trimming functionality to detect and remove freeze/low-motion regions
13
+ - FFmpeg integration for video processing
14
+ - CLI interface with Thor framework
15
+ - Time, duration, and filesize formatters
16
+ - Freeze detection using motion vectors
17
+ - Video segment extraction and concatenation
18
+ - Dry-run mode for testing without processing
19
+ - Configurable noise threshold for motion detection
20
+ - Progress feedback during video processing
21
+ - Comprehensive test suite with mocked FFmpeg calls
data/Guardfile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ guard :minitest, autorun: false do
4
+ # Watch lib files and run all tests
5
+ watch(%r{^lib/(.+)\.rb$}) { 'test' }
6
+
7
+ # Watch test files and run corresponding test
8
+ watch(%r{^test/test_(.+)\.rb$})
9
+ watch(%r{^test/speedrun/test_(.+)\.rb$})
10
+
11
+ # Watch test_helper and run all tests
12
+ watch(%r{^test/test_helper\.rb$}) { 'test' }
13
+ watch(%r{^test/support/.+\.rb$}) { 'test' }
14
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Benjamin Jackson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # speedrun
2
+
3
+ Automatically detect and remove freeze/low-motion regions from videos using ffmpeg.
4
+
5
+ ## Description
6
+
7
+ `speedrun` analyzes videos for frozen or low-motion segments (using ffmpeg's `freezedetect` filter) and removes them, stitching together only the active parts. Perfect for cleaning up screen recordings, presentation videos, or any footage with long static periods.
8
+
9
+ ## Installation
10
+
11
+ Install the gem:
12
+
13
+ ```bash
14
+ gem install speedrun
15
+ ```
16
+
17
+ Or add to your Gemfile:
18
+
19
+ ```ruby
20
+ gem 'speedrun'
21
+ ```
22
+
23
+ ### Requirements
24
+
25
+ - Ruby >= 3.2.0
26
+ - ffmpeg (with freezedetect filter support)
27
+ - ffprobe
28
+
29
+ Install ffmpeg via your package manager:
30
+
31
+ ```bash
32
+ # macOS
33
+ brew install ffmpeg
34
+
35
+ # Ubuntu/Debian
36
+ apt-get install ffmpeg
37
+
38
+ # Arch Linux
39
+ pacman -S ffmpeg
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ Basic usage:
45
+
46
+ ```bash
47
+ speedrun trim input.mp4
48
+ ```
49
+
50
+ This creates `input-trimmed.mp4` with frozen segments removed.
51
+
52
+ ### Options
53
+
54
+ ```bash
55
+ speedrun trim INPUT [OUTPUT] [options]
56
+
57
+ Options:
58
+ -n, --noise THRESHOLD # Noise tolerance in dB (default: -70)
59
+ -d, --duration SECONDS # Minimum freeze duration in seconds (default: 1.0)
60
+ --dry-run # Preview without processing
61
+ -q, --quiet # Minimal output
62
+
63
+ Examples:
64
+ speedrun trim video.mp4 # Creates video-trimmed.mp4
65
+ speedrun trim video.mp4 output.mp4 # Custom output name
66
+ speedrun trim video.mp4 --noise -60 # More sensitive detection
67
+ speedrun trim video.mp4 --duration 2.0 # Only remove freezes >= 2s
68
+ speedrun trim video.mp4 --dry-run # Preview analysis only
69
+ ```
70
+
71
+ #### Understanding the Noise Threshold
72
+
73
+ The `--noise` parameter controls how sensitive freeze detection is to small changes in the video:
74
+
75
+ - **Less negative values** (like `-60 dB`) = **More sensitive**
76
+ Detects freezes even when there's subtle motion or slight changes
77
+ Use when you want to catch nearly-static sections
78
+
79
+ - **More negative values** (like `-80 dB`) = **Less sensitive**
80
+ Only detects freezes when frames are nearly identical
81
+ Use when you want to preserve sections with minimal motion
82
+
83
+ The default of `-70 dB` works well for most screen recordings. If you're getting false positives (motion incorrectly flagged as frozen), try `-80 dB`. If freezes are being missed, try `-60 dB`.
84
+
85
+ ### Other Commands
86
+
87
+ ```bash
88
+ speedrun version # Show version
89
+ speedrun help # Show help
90
+ ```
91
+
92
+ ## How It Works
93
+
94
+ 1. **Detect:** Uses ffmpeg's `freezedetect` filter to find frozen/low-motion segments
95
+ 2. **Analyze:** Calculates which portions to keep vs. remove
96
+ 3. **Extract:** Extracts active segments using ffmpeg
97
+ 4. **Stitch:** Concatenates segments into final output
98
+
99
+ ## Development
100
+
101
+ After checking out the repo:
102
+
103
+ ```bash
104
+ bin/setup # Install dependencies
105
+ bundle exec rake test # Run test suite
106
+ bundle exec guard # Auto-run tests on file changes
107
+ ```
108
+
109
+ ### Testing
110
+
111
+ The codebase follows strict TDD with 100% test coverage. All ffmpeg calls are mocked for fast, isolated testing.
112
+
113
+ ```bash
114
+ bundle exec rake test # Run full test suite
115
+ ```
116
+
117
+ ### Test Structure
118
+
119
+ - **Unit tests:** All components tested in isolation with mocks
120
+ - **Integration tests:** Full workflow with mocked FFmpeg
121
+ - **Fixtures:** Sample ffmpeg/ffprobe outputs for realistic testing
122
+
123
+ ## Architecture
124
+
125
+ ```
126
+ lib/speedrun/
127
+ ├── version.rb # Version constant
128
+ ├── formatter.rb # Time/duration/filesize formatters
129
+ ├── ffmpeg.rb # FFmpeg command wrappers & parsers
130
+ ├── trimmer.rb # Core video processing logic
131
+ └── cli.rb # Thor-based CLI interface
132
+ ```
133
+
134
+ ## Contributing
135
+
136
+ Bug reports and pull requests are welcome on GitHub at https://github.com/benjaminjackson/speedrun.
137
+
138
+ ## License
139
+
140
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ task default: :test
data/exe/speedrun ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "speedrun"
5
+
6
+ Speedrun::CLI.start(ARGV)
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+
5
+ module Speedrun
6
+ class CLI < Thor
7
+ desc "version", "Display version"
8
+ def version
9
+ puts "speedrun version #{Speedrun::VERSION}"
10
+ end
11
+
12
+ desc "trim INPUT [OUTPUT]", "Trim freeze/low-motion regions from video"
13
+ option :noise, type: :numeric, aliases: '-n', default: -70, desc: "Noise tolerance in dB (less negative = more cuts)"
14
+ option :duration, type: :numeric, aliases: '-d', default: 1.0, desc: "Minimum freeze duration to remove in seconds"
15
+ option :'dry-run', type: :boolean, default: false, desc: "Preview without processing"
16
+ option :quiet, type: :boolean, aliases: '-q', default: false, desc: "Minimal output"
17
+ def trim(input_file, output_file = nil)
18
+ unless File.exist?(input_file)
19
+ puts "Error: File not found: #{input_file}"
20
+ exit 1
21
+ end
22
+
23
+ trimmer = Trimmer.new(
24
+ input_file,
25
+ output_file,
26
+ noise_threshold: options[:noise],
27
+ min_duration: options[:duration]
28
+ )
29
+
30
+ trimmer.dry_run = options[:'dry-run']
31
+ trimmer.run
32
+ rescue => e
33
+ puts "Error: #{e.message}"
34
+ exit 1
35
+ end
36
+
37
+ default_task :trim
38
+ end
39
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+ require 'tempfile'
5
+
6
+ module Speedrun
7
+ module FFmpeg
8
+ FREEZE_START_PATTERN = /freeze_start:\s*([\d.]+)/
9
+ FREEZE_END_PATTERN = /freeze_end:\s*([\d.]+)/
10
+
11
+ def self.parse_duration(output)
12
+ output.strip.to_f
13
+ end
14
+
15
+ def self.parse_freezes(output)
16
+ regions = []
17
+ freeze_start = nil
18
+
19
+ output.each_line do |line|
20
+ if line =~ FREEZE_START_PATTERN
21
+ freeze_start = ::Regexp.last_match(1).to_f
22
+ elsif line =~ FREEZE_END_PATTERN && freeze_start
23
+ freeze_end = ::Regexp.last_match(1).to_f
24
+ regions << [freeze_start, freeze_end]
25
+ freeze_start = nil
26
+ end
27
+ end
28
+
29
+ regions
30
+ end
31
+
32
+ def self.get_duration(file_path)
33
+ raise ArgumentError, "File not found: #{file_path}" unless File.exist?(file_path)
34
+
35
+ cmd = [
36
+ 'ffprobe',
37
+ '-v', 'error',
38
+ '-show_entries', 'format=duration',
39
+ '-of', 'default=noprint_wrappers=1:nokey=1',
40
+ file_path
41
+ ].shelljoin
42
+
43
+ output = `#{cmd}`
44
+ parse_duration(output)
45
+ end
46
+
47
+ def self.detect_freezes(file_path, noise_threshold: -70, min_duration: 1.0)
48
+ raise ArgumentError, "File not found: #{file_path}" unless File.exist?(file_path)
49
+
50
+ cmd = [
51
+ 'ffmpeg',
52
+ '-i', file_path,
53
+ '-vf', "freezedetect=n=#{noise_threshold}dB:d=#{min_duration}",
54
+ '-f', 'null',
55
+ '-'
56
+ ].shelljoin
57
+
58
+ output = `#{cmd} 2>&1`
59
+ parse_freezes(output)
60
+ end
61
+
62
+ def self.extract_and_concat(input_file, output_file, keep_regions)
63
+ raise ArgumentError, "File not found: #{input_file}" unless File.exist?(input_file)
64
+ raise ArgumentError, "No regions to keep" if keep_regions.empty?
65
+
66
+ abs_input = File.absolute_path(input_file)
67
+
68
+ Tempfile.create(['concat', '.txt']) do |concat_file|
69
+ keep_regions.each do |start_time, end_time|
70
+ concat_file.puts "file '#{abs_input}'"
71
+ concat_file.puts "inpoint #{start_time}"
72
+ concat_file.puts "outpoint #{end_time}"
73
+ end
74
+ concat_file.flush
75
+
76
+ cmd = [
77
+ 'ffmpeg',
78
+ '-f', 'concat',
79
+ '-safe', '0',
80
+ '-i', concat_file.path,
81
+ '-c', 'copy',
82
+ '-y',
83
+ output_file
84
+ ].shelljoin
85
+
86
+ output = `#{cmd} 2>&1`
87
+ success = $?.success?
88
+
89
+ raise "FFmpeg failed: #{output}" unless success
90
+
91
+ success
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Speedrun
4
+ module Formatter
5
+ SECONDS_PER_MINUTE = 60
6
+ SECONDS_PER_HOUR = 3600
7
+ BYTES_PER_KB = 1024
8
+ BYTES_PER_MB = 1024 * 1024
9
+ BYTES_PER_GB = 1024 * 1024 * 1024
10
+
11
+ def self.format_time(seconds)
12
+ hours = (seconds / SECONDS_PER_HOUR).to_i
13
+ minutes = ((seconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE).to_i
14
+ secs = seconds % SECONDS_PER_MINUTE
15
+
16
+ format('%02d:%02d:%06.3f', hours, minutes, secs)
17
+ end
18
+
19
+ def self.format_duration(seconds)
20
+ if seconds < SECONDS_PER_MINUTE
21
+ format('%.2fs', seconds)
22
+ elsif seconds < SECONDS_PER_HOUR
23
+ minutes = (seconds / SECONDS_PER_MINUTE).to_i
24
+ secs = seconds % SECONDS_PER_MINUTE
25
+ format('%dm %.1fs', minutes, secs)
26
+ else
27
+ hours = (seconds / SECONDS_PER_HOUR).to_i
28
+ minutes = ((seconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE).to_i
29
+ secs = seconds % SECONDS_PER_MINUTE
30
+ format('%dh %dm %ds', hours, minutes, secs.to_i)
31
+ end
32
+ end
33
+
34
+ def self.format_filesize(bytes)
35
+ if bytes < BYTES_PER_KB
36
+ format('%d B', bytes)
37
+ elsif bytes < BYTES_PER_MB
38
+ format('%.1f KB', bytes / BYTES_PER_KB.to_f)
39
+ elsif bytes < BYTES_PER_GB
40
+ format('%.1f MB', bytes / BYTES_PER_MB.to_f)
41
+ else
42
+ format('%.1f GB', bytes / BYTES_PER_GB.to_f)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+
5
+ module Speedrun
6
+ class Trimmer
7
+ attr_reader :input_file, :output_file
8
+ attr_accessor :dry_run
9
+
10
+ def initialize(input_file, output_file = nil, noise_threshold: -70, min_duration: 1.0)
11
+ raise ArgumentError, "File not found: #{input_file}" unless File.exist?(input_file)
12
+
13
+ @input_file = input_file
14
+ @output_file = output_file || generate_output_filename(input_file)
15
+ @noise_threshold = noise_threshold
16
+ @min_duration = min_duration
17
+ @dry_run = false
18
+ end
19
+
20
+ def dry_run?
21
+ @dry_run
22
+ end
23
+
24
+ def run
25
+ puts "Input: #{@input_file}"
26
+ puts "Output: #{@output_file}"
27
+ puts
28
+
29
+ # Step 1: Detect freeze regions
30
+ freeze_regions = FFmpeg.detect_freezes(
31
+ @input_file,
32
+ noise_threshold: @noise_threshold,
33
+ min_duration: @min_duration
34
+ )
35
+
36
+ if freeze_regions.empty?
37
+ puts "No freeze regions detected. Copying original video..."
38
+ FileUtils.cp(@input_file, @output_file) unless dry_run?
39
+ return
40
+ end
41
+
42
+ # Step 2: Calculate keep regions
43
+ video_duration = FFmpeg.get_duration(@input_file)
44
+ keep_regions = calculate_keep_regions(freeze_regions, video_duration)
45
+
46
+ if keep_regions.empty?
47
+ raise "All video content would be removed!"
48
+ end
49
+
50
+ # Step 3: Print summary
51
+ print_summary(freeze_regions, keep_regions, video_duration)
52
+
53
+ # Step 4: Process or dry-run
54
+ if dry_run?
55
+ puts
56
+ puts "DRY RUN - No files were modified"
57
+ else
58
+ puts
59
+ puts "Processing video..."
60
+ FFmpeg.extract_and_concat(@input_file, @output_file, keep_regions)
61
+
62
+ output_size = File.size(@output_file)
63
+ puts "Complete! Output saved to #{@output_file}"
64
+ puts " File size: #{Formatter.format_filesize(output_size)}"
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def generate_output_filename(input_file)
71
+ ext = File.extname(input_file)
72
+ base = File.basename(input_file, ext)
73
+ dir = File.dirname(input_file)
74
+
75
+ output = "#{base}-trimmed#{ext}"
76
+ dir == "." ? output : File.join(dir, output)
77
+ end
78
+
79
+ def calculate_keep_regions(freeze_regions, video_duration)
80
+ return [[0.0, video_duration]] if freeze_regions.empty?
81
+
82
+ keep_regions = []
83
+ current_time = 0.0
84
+
85
+ freeze_regions.each do |start_time, end_time|
86
+ if start_time > current_time
87
+ keep_regions << [current_time, start_time]
88
+ end
89
+ current_time = end_time
90
+ end
91
+
92
+ keep_regions << [current_time, video_duration] if current_time < video_duration
93
+
94
+ keep_regions
95
+ end
96
+
97
+ def print_summary(freeze_regions, keep_regions, video_duration)
98
+ total_removed = freeze_regions.sum { |s, e| e - s }
99
+ total_kept = keep_regions.sum { |s, e| e - s }
100
+
101
+ puts "Analysis complete:"
102
+ puts " Found #{freeze_regions.length} freeze region#{freeze_regions.length == 1 ? '' : 's'}"
103
+ puts " Keeping #{keep_regions.length} segment#{keep_regions.length == 1 ? '' : 's'} with motion"
104
+ puts
105
+ puts "Summary:"
106
+ puts " Original duration: #{Formatter.format_duration(video_duration)}"
107
+ puts " Keeping: #{Formatter.format_duration(total_kept)} (#{'%.1f' % (total_kept / video_duration * 100)}%)"
108
+ puts " Removing: #{Formatter.format_duration(total_removed)} (#{'%.1f' % (total_removed / video_duration * 100)}%)"
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Speedrun
4
+ VERSION = "0.1.0"
5
+ end
data/lib/speedrun.rb ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "speedrun/version"
4
+ require_relative "speedrun/formatter"
5
+ require_relative "speedrun/ffmpeg"
6
+ require_relative "speedrun/trimmer"
7
+ require_relative "speedrun/cli"
8
+
9
+ module Speedrun
10
+ class Error < StandardError; end
11
+ end
data/sig/speedrun.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Speedrun
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: speedrun
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Benjamin Jackson
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: thor
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.3'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.3'
26
+ - !ruby/object:Gem::Dependency
27
+ name: ruby-progressbar
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.13'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.13'
40
+ - !ruby/object:Gem::Dependency
41
+ name: pastel
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.8'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.8'
54
+ - !ruby/object:Gem::Dependency
55
+ name: guard
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '2.18'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '2.18'
68
+ - !ruby/object:Gem::Dependency
69
+ name: guard-minitest
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '2.4'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '2.4'
82
+ - !ruby/object:Gem::Dependency
83
+ name: minitest-stub_any_instance
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '1.0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '1.0'
96
+ description: CLI tool that uses ffmpeg to automatically detect frozen or low-motion
97
+ segments in videos and remove them, stitching together the active parts.
98
+ email:
99
+ - ben@hearmeout.co
100
+ executables:
101
+ - speedrun
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - CHANGELOG.md
106
+ - Guardfile
107
+ - LICENSE
108
+ - README.md
109
+ - Rakefile
110
+ - exe/speedrun
111
+ - lib/speedrun.rb
112
+ - lib/speedrun/cli.rb
113
+ - lib/speedrun/ffmpeg.rb
114
+ - lib/speedrun/formatter.rb
115
+ - lib/speedrun/trimmer.rb
116
+ - lib/speedrun/version.rb
117
+ - sig/speedrun.rbs
118
+ homepage: https://github.com/benjaminjackson/speedrun
119
+ licenses:
120
+ - MIT
121
+ metadata:
122
+ source_code_uri: https://github.com/benjaminjackson/speedrun
123
+ changelog_uri: https://github.com/benjaminjackson/speedrun/blob/main/CHANGELOG.md
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: 3.2.0
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ requirements: []
138
+ rubygems_version: 3.7.2
139
+ specification_version: 4
140
+ summary: Detect and remove freeze/low-motion regions from videos
141
+ test_files: []