media_trim 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 36ab2c24be1441d81a352ca71f72e7476e5bc748cde55da317fac140063c5a6d
4
+ data.tar.gz: bb39ea9a3dd5b8e91da60dd6b6e64d1e03b31330647cfb5ec7666041d470ee89
5
+ SHA512:
6
+ metadata.gz: b5fa5f3e4634c0f04197744edf1c809a9ff139931af5ba6911b8098c5de37abe26f14b3353de3ae7468868f1435256549b025634e723d4973d8333abcd4ce0c9
7
+ data.tar.gz: 21fa1ee01fa7b71392e2acd7f40a4b1e63ee69f6fcbc8300939b542974b39b4a76ec232ce02d4b8aca6d0e63000f6edc47a31e673af908721e3ab2b22163175f
data/.rubocop.yml ADDED
@@ -0,0 +1,64 @@
1
+ require:
2
+ - rubocop-md
3
+ - rubocop-performance
4
+ - rubocop-rake
5
+
6
+ AllCops:
7
+ Exclude:
8
+ - binstub/**/*
9
+ - exe/**/*
10
+ - vendor/**/*
11
+ - Gemfile*
12
+ NewCops: enable
13
+
14
+ Gemspec/DeprecatedAttributeAssignment:
15
+ Enabled: false
16
+
17
+ Gemspec/RequireMFA:
18
+ Enabled: false
19
+
20
+ Gemspec/RequiredRubyVersion:
21
+ Enabled: false
22
+
23
+ Layout/HashAlignment:
24
+ EnforcedColonStyle: table
25
+ EnforcedHashRocketStyle: table
26
+
27
+ Layout/LineLength:
28
+ Max: 150
29
+
30
+ Metrics/AbcSize:
31
+ Max: 40
32
+
33
+ Metrics/BlockLength:
34
+ Exclude:
35
+ - media_trim.gemspec
36
+ Max: 40
37
+
38
+ Metrics/CyclomaticComplexity:
39
+ Max: 15
40
+
41
+ Metrics/MethodLength:
42
+ Max: 40
43
+
44
+ Metrics/ModuleLength:
45
+ Enabled: false
46
+
47
+ Metrics/ParameterLists:
48
+ Enabled: false
49
+
50
+ Metrics/PerceivedComplexity:
51
+ Max: 15
52
+
53
+ Naming/FileName:
54
+ Exclude:
55
+ - Rakefile
56
+
57
+ Style/Documentation:
58
+ Enabled: false
59
+
60
+ Style/FrozenStringLiteralComment:
61
+ Enabled: false
62
+
63
+ Style/TrailingCommaInHashLiteral:
64
+ EnforcedStyleForMultiline: comma
data/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Change Log
2
+
3
+
4
+ ## 0.2.0
5
+
6
+ * Packaged as a gem.
7
+
8
+
9
+ ## 0.1.0
10
+
11
+ * Initial release as a script.
data/README.md ADDED
@@ -0,0 +1,175 @@
1
+ # `media_trim` [![Gem Version](https://badge.fury.io/rb/media_trim.svg)](https://badge.fury.io/rb/media_trim)
2
+
3
+ Trims an audio or video file using `ffmpeg`.
4
+
5
+ * Works with all formats supported by `ffmpeg`, including `mp3`, `mp4`, `mkv`, and many more.
6
+ * Seeks to the nearest frame positions by re-encoding the media.
7
+ * Reduces file size produced by OBS Studio by over 80 percent.
8
+ * Can be used as a Ruby gem.
9
+ * Installs the `trim` command.
10
+
11
+
12
+ ## Installation
13
+
14
+ You need a working Ruby environment to install this program.
15
+ I describe how to set that up [here](http://localhost:4001/ruby/1000-ruby-setup.html).
16
+
17
+
18
+ ### Standalone
19
+
20
+ The `trim` command is provided by the `media_trim` Ruby gem.
21
+ Install it like this:
22
+
23
+ ```shell
24
+ $ gem install media_trim
25
+ ```
26
+
27
+ ### As a Dependency of a Ruby Program
28
+
29
+ Add this line to your application’s `Gemfile`:
30
+
31
+ ```ruby
32
+ gem 'media_trim'
33
+ ```
34
+
35
+ Then execute:
36
+
37
+ ```shell
38
+ $ bundle
39
+ ```
40
+
41
+ ### As a Dependency of a Ruby Gem
42
+
43
+ Add the following to your application’s `.gemspec`:
44
+
45
+ ```ruby
46
+ spec.add_dependency 'media_trim'
47
+ ```
48
+
49
+ Then execute:
50
+
51
+ ```shell
52
+ $ bundle
53
+ ```
54
+
55
+ ## Usage
56
+
57
+ ### Command-line Usage
58
+
59
+ ```shell
60
+ trim [OPTIONS] dir/file.ext start [[to|for] end]
61
+ ```
62
+
63
+ * The `start` and `end` timecodes have the format `[HH:[MM:]]SS[.XXX]`.
64
+ Note that decimal seconds may be specified, but frames may not;
65
+ this is consistent with how `ffmpeg` parses timecodes.
66
+ * `end` defaults to the end of the audio/video file
67
+
68
+ When run as a command, output files are named by adding a `trim.` prefix to the media file name,
69
+ e.g. `dir/trim.file.ext`.
70
+ By default, the `trim` command does not overwrite pre-existing output files.
71
+ When trimming is complete, the `trim` command displays the trimmed file,
72
+ unless the `-q` option is specified.
73
+
74
+ `OPTIONS` are:
75
+
76
+ * `-d` Enable debug output.
77
+ * `-h` Display help information.
78
+ * `-f` Overwrite output file if present.
79
+ * `-v` Verbose output.
80
+ * `-V` Do not view the trimmed file when complete.
81
+
82
+
83
+ #### Examples
84
+
85
+ Crop `dir/file.mp4` from 15.0 seconds to the end of the video, save to `demo/trim.demo.mp4`:
86
+
87
+ ```shell
88
+ $ trim demo/demo.mp4 15
89
+ ```
90
+
91
+ Crop dir/file.mkv from 3 minutes, 25 seconds to 9 minutes, 35 seconds, save to `demo/trim.demo.mp4`:
92
+
93
+ ```shell
94
+ $ trim demo/demo.mp4 3:25 9:35
95
+ ```
96
+
97
+ Same as the previous example, using optional `to` syntax:
98
+
99
+ ```shell
100
+ $ trim demo/demo.mp4 3:25 to 9:35
101
+ ```
102
+
103
+ Save as the previous example, but specify the duration instead of the end time by using the `for` keyword:
104
+
105
+ ```shell
106
+ $ trim demo/demo.mp4 3:25 for 6:10
107
+ ```
108
+
109
+
110
+ ## Figuring Out Start and Stop Times
111
+
112
+ Need a way to figure out the start and stop times to trim a video?
113
+ [DJV](https://darbyjohnston.github.io/DJV/) is an excellent video viewer.
114
+
115
+ * Allows frame-by-frame stepping
116
+ * Displays the current time reliabily
117
+ * F/OSS
118
+ * Mac, Windows, Linux
119
+ * High quality
120
+
121
+
122
+ ## Development
123
+
124
+ After checking out this git repository, install dependencies by typing:
125
+
126
+ ```shell
127
+ $ bin/setup
128
+ ```
129
+
130
+ You should do the above before running Visual Studio Code.
131
+
132
+
133
+ ### Run the Tests
134
+
135
+ ```shell
136
+ $ bundle exec rake test
137
+ ```
138
+
139
+
140
+ ### Interactive Session
141
+
142
+ The following will allow you to experiment:
143
+
144
+ ```shell
145
+ $ bin/console
146
+ ```
147
+
148
+
149
+ ### Local Installation
150
+
151
+ To install this gem onto your local machine, type:
152
+
153
+ ```shell
154
+ $ bundle exec rake install
155
+ ```
156
+
157
+
158
+ ### To Release A New Version
159
+
160
+ To create a git tag for the new version, push git commits and tags,
161
+ and push the new version of the gem to https://rubygems.org, type:
162
+
163
+ ```shell
164
+ $ bundle exec rake release
165
+ ```
166
+
167
+
168
+ ## Contributing
169
+
170
+ Bug reports and pull requests are welcome at https://github.com/mslinn/trim.
171
+
172
+
173
+ ## License
174
+
175
+ 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,40 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.libs << 'lib'
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ desc 'Bump patch version'
11
+ task :patch do
12
+ system 'gem bump --tag'
13
+ end
14
+
15
+ desc 'Bump minor version'
16
+ task :minor do
17
+ system 'gem bump --version minor --tag'
18
+ end
19
+
20
+ desc 'Bump major version'
21
+ task :major do
22
+ system 'gem bump --version major --tag'
23
+ end
24
+
25
+ task publish: [:build] do
26
+ $VERBOSE = nil
27
+ load 'trim/lib/version.rb'
28
+ system "gem push pkg/trim-#{Trim::VERSION}.gem"
29
+ end
30
+
31
+ desc 'Bump patch version, create git tag, build the gem and release to geminabox (default)'
32
+ task release_patch: %i[test patch publish]
33
+
34
+ desc 'Bump minor version, create git tag, build the gem and release to geminabox'
35
+ task release_minor: %i[test minor publish]
36
+
37
+ desc 'Bump major version, create git tag, build the gem and release to geminabox'
38
+ task release_major: %i[test major publish]
39
+
40
+ task default: :test
data/exe/trim ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'media_trim'
4
+
5
+ media_trim = MediaTrim.new
6
+ media_trim.options
7
+ media_trim.setup ARGV
8
+ media_trim.run
@@ -0,0 +1,3 @@
1
+ module MediaTrimVersion
2
+ VERSION = '0.2.0'.freeze
3
+ end
data/lib/media_trim.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'colorator'
2
+ require 'fileutils'
3
+ require 'optparse'
4
+ require 'time'
5
+ require_relative 'media_trim/version' unless defined? MediaTrimVersion::VERSION
6
+ require_relative 'trim_class'
7
+ require_relative 'trim_help'
8
+ require_relative 'trim_main'
9
+ require_relative 'trim_run'
10
+
11
+ if __FILE__ == $PROGRAM_NAME
12
+ media_trim = MediaTrim.new
13
+ media_trim.options
14
+ media_trim.setup ARGV
15
+ media_trim.run
16
+ end
data/lib/trim_class.rb ADDED
@@ -0,0 +1,63 @@
1
+ class MediaTrim
2
+ def self.add_times(str1, str2)
3
+ time1 = Time.parse mk_time str1
4
+ time2 = Time.parse mk_time str2
5
+ h = time2.strftime('%H').to_i
6
+ m = time2.strftime('%M').to_i
7
+ s = time2.strftime('%S').to_i
8
+ millis = time2.strftime('%L').to_f / 1000.0
9
+ sum = (time1 + (h * 60 * 60) + (m * 60) + s + millis)
10
+ return sum.strftime('%H:%M:%S') if h.positive?
11
+
12
+ sum.strftime('%M:%S')
13
+ end
14
+
15
+ def self.time_format(elapsed_seconds)
16
+ elapsed_time = elapsed_seconds.to_i
17
+ hours = (elapsed_time / (60 * 60)).to_i
18
+ minutes = ((elapsed_time - (hours * 60)) / 60).to_i
19
+ seconds = elapsed_time - (hours * 60 * 60) - (minutes * 60)
20
+
21
+ result = "#{minutes.to_s.rjust 2, '0'}:#{seconds.to_s.delete_suffix('.0').rjust 2, '0'}"
22
+ result = "#{hours}:#{result}}" unless hours.zero?
23
+ result
24
+ end
25
+
26
+ # @return time difference HH:MM:SS, ignoring millis
27
+ def self.duration(str1, str2)
28
+ time1 = Time.parse mk_time str1
29
+ time2 = Time.parse mk_time str2
30
+
31
+ MediaTrim.time_format(time2 - time1)
32
+ end
33
+
34
+ # Expand an environment variable reference
35
+ def self.expand_env(str, die_if_undefined: false)
36
+ str&.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) do
37
+ envar = Regexp.last_match(1)
38
+ raise TrimError, "MediaTrim error: #{envar} is undefined".red, [] \
39
+ if !ENV.key?(envar) && die_if_undefined # Suppress stack trace
40
+
41
+ ENV.fetch(envar, nil)
42
+ end
43
+ end
44
+
45
+ def self.mk_time(str)
46
+ case str.count ':'
47
+ when 0 then "0:0:#{str}"
48
+ when 1 then "0:#{str}"
49
+ when 2 then str
50
+ else raise TrimError, "Error: #{str} is not a valid time"
51
+ end
52
+ end
53
+
54
+ def self.to_seconds(str)
55
+ array = str.split(':').map(&:to_i).reverse
56
+ case array.length
57
+ when 1 then str.to_i
58
+ when 2 then array[0] + (array[1] * 60)
59
+ when 3 then array[0] + (array[1] * 60) + (array[2] * 60 * 60)
60
+ else raise TrimError, "Error: #{str} is not a valid time"
61
+ end
62
+ end
63
+ end
data/lib/trim_help.rb ADDED
@@ -0,0 +1,55 @@
1
+ class MediaTrim
2
+ def self.help(msg = nil)
3
+ puts "Error: #{msg}.\n".red if msg
4
+ puts <<~END_HELP
5
+ media_trim - Trim an audio or video file using ffmpeg
6
+
7
+ - Works with all formats supported by ffmpeg, including mp3, mp4, mkv, and many more.
8
+ - Seeks to the nearest frame positions by re-encoding the media.
9
+ - Reduces file size produced by OBS Studio by over 80 percent.
10
+ - Can be used as a Ruby gem.
11
+ - Installs the 'trim' command.
12
+
13
+ When run as a command, output files are named by adding a 'trim.' prefix to the media file name, e.g. 'dir/trim.file.ext'.
14
+ By default, the trim command does not overwrite pre-existing output files.
15
+ When trimming is complete, the trim command displays the trimmed file, unless the -q option is specified
16
+
17
+ Command-line Usage:
18
+ trim [OPTIONS] dir/file.ext start [[to|for] end]
19
+
20
+ - The start and end timecodes have the format [HH:[MM:]]SS[.XXX]
21
+ Note that decimal seconds may be specified, but frames may not;
22
+ this is consistent with how ffmpeg parses timecodes.
23
+ - end defaults to the end of the audio/video file
24
+
25
+ OPTIONS are:
26
+ -d Enable debug output
27
+ -f Overwrite output file if present
28
+ -h Display help information.
29
+ -v Verbose output
30
+ -V Do not view the trimmed file when complete.
31
+
32
+ Examples:
33
+ # Crop dir/file.mp4 from 15.0 seconds to the end of the video, save to demo/trim.demo.mp4:
34
+ trim demo/demo.mp4 15
35
+
36
+ # Crop dir/file.mkv from 3 minutes, 25 seconds to 9 minutes, 35 seconds, save to demo/trim.demo.mp4:
37
+ trim demo/demo.mp4 3:25 9:35
38
+
39
+ # Same as the previous example, using optional 'to' syntax:
40
+ trim demo/demo.mp4 3:25 to 9:35
41
+
42
+ # Save as the previous example, but specify the duration instead of the end time by using the for keyword:
43
+ trim demo/demo.mp4 3:25 for 6:10
44
+
45
+ Need a way to figure out the start and stop times to trim a video?
46
+ DJV is an excellent video viewer https://darbyjohnston.github.io/DJV/
47
+ - allows frame-by-frame stepping
48
+ - displays the current time reliabily
49
+ - F/OSS
50
+ - Mac, Windows, Linux
51
+ - High quality
52
+ END_HELP
53
+ exit 1
54
+ end
55
+ end
data/lib/trim_main.rb ADDED
@@ -0,0 +1,87 @@
1
+ TrimError = Class.new StandardError # Define a new StandardError subclass
2
+
3
+ class MediaTrim
4
+ attr_accessor :copy_filename, :fname, :interval, :msg_end, :overwrite, :quiet, :start, :view
5
+
6
+ # @param to [String] end timecode; duration not supported for API
7
+ def initialize(filename = nil, trimmed_filename = nil, start = '0', to = nil, **options)
8
+ @fname = MediaTrim.expand_env(filename) if filename
9
+ @copy_filename = MediaTrim.expand_env(trimmed_filename) if trimmed_filename
10
+ @start = MediaTrim.time_format start
11
+ @interval = ['-ss', MediaTrim.time_format(@start)]
12
+
13
+ @overwrite = options[:overwrite] ? '-y' : '-n'
14
+ @quiet = options[:quiet].nil? || options[:quiet] ? ['-hide_banner', '-loglevel', 'error', '-nostats'] : []
15
+ @view = options[:view].nil? ? true : options[:view]
16
+
17
+ prepare(@start, to, mode: :timecode) if to
18
+ end
19
+
20
+ def options
21
+ OptionParser.new do |opts|
22
+ opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
23
+
24
+ opts.on('-f', '--[no-]@overwrite', 'Overwrite any previous output') do |f|
25
+ @overwrite = f ? '-y' : '-n'
26
+ end
27
+ opts.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
28
+ @quiet = [] if v
29
+ end
30
+ opts.on('-h', '', 'Display help') do |_|
31
+ help
32
+ end
33
+ opts.on('-V', '--[no-]@view', 'View ffmpeg output') do |v|
34
+ @view = false if v
35
+ end
36
+ end.parse!
37
+ end
38
+
39
+ # @param argv [array] Copy of ARGV
40
+ def setup(argv)
41
+ MediaTrim.help 'Please specify the name of the video file to trim' unless argv[0]
42
+ @fname = MediaTrim.expand_env argv[0]
43
+ unless File.exist? @fname
44
+ puts "Error: '#{File.realpath @fname}' does not exist.".red
45
+ exit 1
46
+ end
47
+ original_filename = File.basename @fname, '.*'
48
+ ext = File.extname @fname
49
+ @copy_filename = "#{File.dirname @fname}/trim.#{original_filename}#{ext}"
50
+
51
+ MediaTrim.help 'Please specify the time to @start trimming the video file from' unless argv[1]
52
+ @start = MediaTrim.time_format argv[1]
53
+
54
+ @interval = ['-ss', @start]
55
+ @msg_end = ''
56
+ index = 2
57
+ return unless argv.length > index
58
+
59
+ if argv[index] == 'for' # duration
60
+ index += 1
61
+ MediaTrim.help 'No duration was specified' unless argv.length > index
62
+ to = prepare @start, argv[index], mode: :duration
63
+ else # end timecode
64
+ index += 1 if argv[index] == 'to'
65
+ MediaTrim.help 'No end time was specified' unless argv.length > index
66
+ to = prepare @start, argv[index], mode: :timecode
67
+ end
68
+ return unless @start >= to
69
+
70
+ raise TrimError, "Error: @start time (#{@start}) must be before end time (#{to})" if @start >= to
71
+ end
72
+
73
+ def prepare(from, duration_or_timecode, mode: :duration)
74
+ if mode == :duration
75
+ timecode = MediaTrim.time_format duration_or_timecode
76
+ time_end = MediaTrim.add_times from, timecode
77
+ @interval += ['-t', time_end]
78
+ @msg_end = " for a duration of #{timecode} (until #{time_end})"
79
+ else # end timecode was specified
80
+ time_end = MediaTrim.time_format(MediaTrim.to_seconds(duration_or_timecode))
81
+ elapsed_time = MediaTrim.duration from, time_end
82
+ @interval += ['-to', time_end]
83
+ @msg_end = " to #{time_end} (duration #{elapsed_time})"
84
+ end
85
+ time_end
86
+ end
87
+ end
data/lib/trim_run.rb ADDED
@@ -0,0 +1,48 @@
1
+ class MediaTrim
2
+ def run
3
+ raise TrimError, 'Error: No filename was specified'.red unless @fname
4
+ raise TrimError, 'Error: No trimmed filename was specified'.red unless @copy_filename
5
+ raise TrimError, 'Error: No starting timestamp was specified'.red unless @start
6
+ raise TrimError, 'Error: Starting timestamp must be a string'.red unless @start.instance_of? String
7
+
8
+ puts "Trimming '#{@fname}' from #{@start}#{@msg_end}".cyan
9
+ command = ['ffmpeg',
10
+ *@quiet,
11
+ '-hwaccel', 'auto',
12
+ @overwrite,
13
+ '-i', @fname,
14
+ '-acodec', 'aac',
15
+ *@interval,
16
+ @copy_filename]
17
+ # puts command.join(' ').yellow
18
+ start_clock = Process.clock_gettime(Process::CLOCK_MONOTONIC)
19
+ status = system(*command)
20
+ end_clock = Process.clock_gettime(Process::CLOCK_MONOTONIC)
21
+ elapsed = end_clock - start_clock
22
+ puts "Trim took #{MediaTrim.time_format elapsed.to_i}".cyan
23
+ $stdout.flush
24
+ exit 1 unless status
25
+
26
+ # View trimmed file unless -q option was specified
27
+ return unless @view
28
+
29
+ # Open in Windows if running in WSL
30
+ if File.exist? '/mnt/c/Program Files/DJV2/bin/djv.com'
31
+ realpath = File.realpath @copy_filename
32
+ windows_path = `wslpath -m '#{realpath}'`.chomp
33
+ spawn 'cmd.exe', '/c',
34
+ 'C:\\Program Files\\DJV2\\bin\\djv.com',
35
+ '-full_screen',
36
+ '-full_screen_monitor', '2',
37
+ windows_path
38
+ elsif `which cmd.exe`
39
+ exec 'cmd.exe', '/C', '@start', @copy_filename, "--extraintf='luaintf{intf=\"looper_custom_time\"}'"
40
+ elsif `which xdg-open`
41
+ # Open any file with its default Linux application with xdg-open.
42
+ # Define default apps in ~/.local/share/applications/defaults.list,
43
+ # which is read on every invocation.
44
+ # See https://askubuntu.com/questions/809981/set-the-default-video-player-from-the-command-line
45
+ exec 'xdg-open', @copy_filename
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,73 @@
1
+ require_relative 'lib/media_trim/version' unless defined? MediaTrimVersion::VERSION
2
+
3
+ Gem::Specification.new do |spec|
4
+ host = 'https://github.com/mslinn/media_trim'
5
+
6
+ spec.authors = ['Mike Slinn']
7
+ spec.bindir = 'exe'
8
+ spec.description = <<~END_DESC
9
+ Trim an audio or video file using ffmpeg
10
+
11
+ - Works with all formats supported by ffmpeg, including mp3, mp4, mkv, and many more.
12
+ - Seeks to the nearest frame positions by re-encoding the media.
13
+ - Reduces file size procduced by OBS Studio by over 80 percent.
14
+ - Can be used as a Ruby gem.
15
+ - Installs the 'trim' command.
16
+
17
+ When run as a command, output files are named by adding a 'trim.' prefix to the media file name, e.g. 'dir/trim.file.ext'.
18
+ By default, the trim command does not overwrite pre-existing output files.
19
+ When trimming is complete, the trim command displays the trimmed file, unless the -q option is specified
20
+
21
+ Command-line Usage:
22
+ trim [OPTIONS] dir/file.ext start [[to|for] end]
23
+
24
+ - The start and end timecodes have the format [HH:[MM:]]SS[.XXX]
25
+ Note that decimal seconds may be specified, bug frames may not;
26
+ this is consistent with how ffmpeg parses timecodes.
27
+ - end defaults to end of the audio/video file
28
+
29
+ OPTIONS are:
30
+ -d Enable debug output.
31
+ -f Overwrite output file if present.
32
+ -h Display help information.
33
+ -v Verbose output.
34
+ -V Do not @view the trimmed file when complete.
35
+
36
+ Examples:
37
+ # Crop dir/file.mp4 from 15.0 seconds to the end of the video, save to demo/trim.demo.mp4:
38
+ trim demo/demo.mp4 15
39
+
40
+ # Crop dir/file.mkv from 3 minutes, 25 seconds to 9 minutes, 35 seconds, save to demo/trim.demo.mp4:
41
+ trim demo/demo.mp4 3:25 9:35
42
+
43
+ # Same as the previous example, using optional 'to' syntax:
44
+ trim demo/demo.mp4 3:25 to 9:35
45
+
46
+ # Save as the previous example, but specify the duration instead of the end time by using the for keyword:
47
+ trim demo/demo.mp4 3:25 for 6:10
48
+ END_DESC
49
+ spec.email = ['mslinn@mslinn.com']
50
+ spec.files = Dir['.rubocop.yml', 'LICENSE.*', 'Rakefile', '{exe,lib,spec}/**/*', '*.gemspec', '*.md']
51
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
52
+ spec.homepage = 'https://www.mslinn.com/av_studio/425-trimming-media.html'
53
+ spec.license = 'MIT'
54
+ spec.metadata = {
55
+ 'allowed_push_host' => 'https://rubygems.org',
56
+ 'bug_tracker_uri' => "#{host}/issues",
57
+ 'changelog_uri' => "#{host}/CHANGELOG.md",
58
+ 'homepage_uri' => spec.homepage,
59
+ 'source_code_uri' => host,
60
+ }
61
+ spec.name = 'media_trim'
62
+ spec.post_install_message = <<~END_MESSAGE
63
+
64
+ Thanks for installing #{spec.name} v#{MediaTrimVersion::VERSION}!
65
+
66
+ END_MESSAGE
67
+ spec.require_paths = ['lib']
68
+ spec.required_ruby_version = '>= 2.5.0'
69
+ spec.summary = 'Trim an audio or video file using ffmpeg'
70
+ spec.version = MediaTrimVersion::VERSION
71
+
72
+ spec.add_dependency 'colorator'
73
+ end
@@ -0,0 +1,9 @@
1
+ require_relative '../lib/media_trim'
2
+
3
+ RSpec.configure do |config|
4
+ config.filter_run_when_matching focus: true
5
+ # config.order = 'random'
6
+
7
+ # See https://relishapp.com/rspec/rspec-core/docs/command-line/only-failures
8
+ config.example_status_persistence_file_path = '../spec/status_persistence.txt'
9
+ end
@@ -0,0 +1,43 @@
1
+ require_relative '../lib/media_trim'
2
+
3
+ # Tests API argument handling
4
+ RSpec.describe(MediaTrim) do
5
+ it 'initializes with only start timecode' do
6
+ mt = described_class.new 'demo/demo.mp4', 'demo/trim.demo.mp4', '0'
7
+ expect(mt.fname).to eq('demo/demo.mp4')
8
+ expect(mt.copy_filename).to eq('demo/trim.demo.mp4')
9
+ expect(mt.start).to eq('00:00')
10
+ expect(mt.interval).to eq(['-ss', '00:00'])
11
+ expect(mt.msg_end).to be_nil
12
+ end
13
+
14
+ it 'initializes with end timecode' do
15
+ mt = described_class.new 'demo/demo.mp4', 'demo/trim.demo.mp4', '0', '1'
16
+ expect(mt.fname).to eq('demo/demo.mp4')
17
+ expect(mt.copy_filename).to eq('demo/trim.demo.mp4')
18
+ expect(mt.start).to eq('00:00')
19
+ expect(mt.interval).to eq(['-ss', '00:00', '-to', '00:01'])
20
+ expect(mt.msg_end).to eq(' to 00:01 (duration 00:01)')
21
+ end
22
+
23
+ it 'initializes when overwrite is specified' do
24
+ mt = described_class.new 'demo/demo.mp4', 'demo/trim.demo.mp4', '0', overwrite: true
25
+ expect(mt.overwrite).to be_truthy
26
+ expect(mt.quiet).not_to be_empty
27
+ expect(mt.view).to be_truthy
28
+ end
29
+
30
+ it 'initializes when quiet is specified' do
31
+ mt = described_class.new 'demo/demo.mp4', 'demo/trim.demo.mp4', '0', quiet: false
32
+ expect(mt.overwrite).to eq('-n')
33
+ expect(mt.quiet).to be_empty
34
+ expect(mt.view).to be_truthy
35
+ end
36
+
37
+ it 'initializes when view is specified' do
38
+ mt = described_class.new 'demo/demo.mp4', 'demo/trim.demo.mp4', '0', view: false
39
+ expect(mt.overwrite).to eq('-n')
40
+ expect(mt.quiet).not_to be_empty
41
+ expect(mt.view).to be_falsey
42
+ end
43
+ end
@@ -0,0 +1,31 @@
1
+ require_relative '../lib/media_trim'
2
+
3
+ # Tests command line argument handling
4
+ RSpec.describe(MediaTrim) do
5
+ mt = described_class.new
6
+
7
+ it 'prepares duration' do
8
+ mt.prepare '1', '1', mode: :duration
9
+ expect(mt.msg_end).to eq(' for a duration of 00:01 (until 00:02)')
10
+ end
11
+
12
+ it 'prepares end timecode' do
13
+ mt.prepare '1', '2', mode: :timecode
14
+ expect(mt.msg_end).to eq(' to 00:02 (duration 00:01)')
15
+ end
16
+
17
+ it 'sets up duration' do
18
+ mt.setup ['demo/demo.mp4', '1', 'for', '1']
19
+ expect(mt.msg_end).to eq(' for a duration of 00:01 (until 00:02)')
20
+ end
21
+
22
+ it 'sets up end timecode with to' do
23
+ mt.setup ['demo/demo.mp4', '1', 'to', '2']
24
+ expect(mt.msg_end).to eq(' to 00:02 (duration 00:01)')
25
+ end
26
+
27
+ it 'sets up end timecode' do
28
+ mt.setup ['demo/demo.mp4', '1', '2']
29
+ expect(mt.msg_end).to eq(' to 00:02 (duration 00:01)')
30
+ end
31
+ end
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: media_trim
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Mike Slinn
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-11-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colorator
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: |
28
+ Trim an audio or video file using ffmpeg
29
+
30
+ - Works with all formats supported by ffmpeg, including mp3, mp4, mkv, and many more.
31
+ - Seeks to the nearest frame positions by re-encoding the media.
32
+ - Reduces file size procduced by OBS Studio by over 80 percent.
33
+ - Can be used as a Ruby gem.
34
+ - Installs the 'trim' command.
35
+
36
+ When run as a command, output files are named by adding a 'trim.' prefix to the media file name, e.g. 'dir/trim.file.ext'.
37
+ By default, the trim command does not overwrite pre-existing output files.
38
+ When trimming is complete, the trim command displays the trimmed file, unless the -q option is specified
39
+
40
+ Command-line Usage:
41
+ trim [OPTIONS] dir/file.ext start [[to|for] end]
42
+
43
+ - The start and end timecodes have the format [HH:[MM:]]SS[.XXX]
44
+ Note that decimal seconds may be specified, bug frames may not;
45
+ this is consistent with how ffmpeg parses timecodes.
46
+ - end defaults to end of the audio/video file
47
+
48
+ OPTIONS are:
49
+ -d Enable debug output.
50
+ -f Overwrite output file if present.
51
+ -h Display help information.
52
+ -v Verbose output.
53
+ -V Do not @view the trimmed file when complete.
54
+
55
+ Examples:
56
+ # Crop dir/file.mp4 from 15.0 seconds to the end of the video, save to demo/trim.demo.mp4:
57
+ trim demo/demo.mp4 15
58
+
59
+ # Crop dir/file.mkv from 3 minutes, 25 seconds to 9 minutes, 35 seconds, save to demo/trim.demo.mp4:
60
+ trim demo/demo.mp4 3:25 9:35
61
+
62
+ # Same as the previous example, using optional 'to' syntax:
63
+ trim demo/demo.mp4 3:25 to 9:35
64
+
65
+ # Save as the previous example, but specify the duration instead of the end time by using the for keyword:
66
+ trim demo/demo.mp4 3:25 for 6:10
67
+ email:
68
+ - mslinn@mslinn.com
69
+ executables:
70
+ - trim
71
+ extensions: []
72
+ extra_rdoc_files: []
73
+ files:
74
+ - ".rubocop.yml"
75
+ - CHANGELOG.md
76
+ - README.md
77
+ - Rakefile
78
+ - exe/trim
79
+ - lib/media_trim.rb
80
+ - lib/media_trim/version.rb
81
+ - lib/trim_class.rb
82
+ - lib/trim_help.rb
83
+ - lib/trim_main.rb
84
+ - lib/trim_run.rb
85
+ - media_trim.gemspec
86
+ - spec/spec_helper.rb
87
+ - spec/trim_api_spec.rb
88
+ - spec/trim_command_line_spec.rb
89
+ homepage: https://www.mslinn.com/av_studio/425-trimming-media.html
90
+ licenses:
91
+ - MIT
92
+ metadata:
93
+ allowed_push_host: https://rubygems.org
94
+ bug_tracker_uri: https://github.com/mslinn/media_trim/issues
95
+ changelog_uri: https://github.com/mslinn/media_trim/CHANGELOG.md
96
+ homepage_uri: https://www.mslinn.com/av_studio/425-trimming-media.html
97
+ source_code_uri: https://github.com/mslinn/media_trim
98
+ post_install_message: |2+
99
+
100
+ Thanks for installing media_trim v0.2.0!
101
+
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: 2.5.0
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubygems_version: 3.3.7
117
+ signing_key:
118
+ specification_version: 4
119
+ summary: Trim an audio or video file using ffmpeg
120
+ test_files: []
121
+ ...