media_trim 0.2.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: 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
+ ...