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 +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` [](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
|
+
...
|