media_trim 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rubocop.yml +64 -0
- data/CHANGELOG.md +11 -0
- data/README.md +175 -0
- data/Rakefile +40 -0
- data/exe/trim +8 -0
- data/lib/media_trim/version.rb +3 -0
- data/lib/media_trim.rb +16 -0
- data/lib/trim_class.rb +63 -0
- data/lib/trim_help.rb +55 -0
- data/lib/trim_main.rb +87 -0
- data/lib/trim_run.rb +48 -0
- data/media_trim.gemspec +73 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/trim_api_spec.rb +43 -0
- data/spec/trim_command_line_spec.rb +31 -0
- metadata +121 -0
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
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
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
|
data/media_trim.gemspec
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|
+
...
|