iml 0.1.4
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/.gitignore +14 -0
- data/.rspec +3 -0
- data/.rubocop.yml +17 -0
- data/.travis.yml +7 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +69 -0
- data/LICENSE.txt +21 -0
- data/README.md +63 -0
- data/Rakefile +16 -0
- data/bin/iml +96 -0
- data/gemspec.yml +12 -0
- data/iml.gemspec +48 -0
- data/lib/iml.rb +23 -0
- data/lib/iml/base.rb +94 -0
- data/lib/iml/hash.rb +14 -0
- data/lib/iml/movie.rb +24 -0
- data/lib/iml/patterns.rb +64 -0
- data/lib/iml/text.rb +51 -0
- data/lib/iml/tvseries.rb +38 -0
- data/lib/iml/version.rb +6 -0
- data/patterns.yml +56 -0
- metadata +195 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: eae63aa7eb68ca077d98468b14ac404b8ae95e55
|
4
|
+
data.tar.gz: a24253b80613ef0fda7d7bdde7b0b8ee28334e9d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 89b69eee16a50ba2155cf03f7e4a7e5337dd97d3308046bc162cae60e78205b26237eb684fc82c6224d5ef819b710a5b7fa058d3dcccd2fc75fb99da36e5bdef
|
7
|
+
data.tar.gz: '096033b1a19b068f6931ce08575aba84b8e87b18626936c5556a114215c062d9980d5bedf8e08834222e4cd08d86f302e612b5a6ef45d3f4042232796e9bfcf6'
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Metrics/LineLength:
|
2
|
+
Max: 200
|
3
|
+
Exclude:
|
4
|
+
- 'lib/iml.rb'
|
5
|
+
Metrics/BlockLength:
|
6
|
+
Exclude:
|
7
|
+
- '*.gemspec'
|
8
|
+
- '*.rake'
|
9
|
+
Style/ClassAndModuleChildren:
|
10
|
+
Enabled: false
|
11
|
+
Documentation:
|
12
|
+
Enabled: false
|
13
|
+
Rails:
|
14
|
+
Enabled: true
|
15
|
+
AllCops:
|
16
|
+
Exclude:
|
17
|
+
- 'spec/**/*'
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
iml (0.1.4)
|
5
|
+
activesupport (~> 5.2)
|
6
|
+
tqdm (~> 0.3)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
activesupport (5.2.0)
|
12
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
13
|
+
i18n (>= 0.7, < 2)
|
14
|
+
minitest (~> 5.1)
|
15
|
+
tzinfo (~> 1.1)
|
16
|
+
codeclimate-test-reporter (1.0.7)
|
17
|
+
simplecov
|
18
|
+
coderay (1.1.2)
|
19
|
+
concurrent-ruby (1.0.5)
|
20
|
+
diff-lcs (1.3)
|
21
|
+
docile (1.3.1)
|
22
|
+
i18n (1.0.1)
|
23
|
+
concurrent-ruby (~> 1.0)
|
24
|
+
json (2.1.0)
|
25
|
+
method_source (0.9.0)
|
26
|
+
minitest (5.11.3)
|
27
|
+
pry (0.11.3)
|
28
|
+
coderay (~> 1.1.0)
|
29
|
+
method_source (~> 0.9.0)
|
30
|
+
rake (12.3.1)
|
31
|
+
rspec (3.7.0)
|
32
|
+
rspec-core (~> 3.7.0)
|
33
|
+
rspec-expectations (~> 3.7.0)
|
34
|
+
rspec-mocks (~> 3.7.0)
|
35
|
+
rspec-core (3.7.1)
|
36
|
+
rspec-support (~> 3.7.0)
|
37
|
+
rspec-expectations (3.7.0)
|
38
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
39
|
+
rspec-support (~> 3.7.0)
|
40
|
+
rspec-mocks (3.7.0)
|
41
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
42
|
+
rspec-support (~> 3.7.0)
|
43
|
+
rspec-support (3.7.1)
|
44
|
+
simplecov (0.16.1)
|
45
|
+
docile (~> 1.1)
|
46
|
+
json (>= 1.8, < 3)
|
47
|
+
simplecov-html (~> 0.10.0)
|
48
|
+
simplecov-html (0.10.2)
|
49
|
+
thread_safe (0.3.6)
|
50
|
+
tqdm (0.3.0)
|
51
|
+
tzinfo (1.2.5)
|
52
|
+
thread_safe (~> 0.1)
|
53
|
+
yard (0.9.14)
|
54
|
+
|
55
|
+
PLATFORMS
|
56
|
+
ruby
|
57
|
+
|
58
|
+
DEPENDENCIES
|
59
|
+
bundler (~> 1.16)
|
60
|
+
codeclimate-test-reporter (~> 1.0)
|
61
|
+
iml!
|
62
|
+
pry (~> 0.11)
|
63
|
+
rake (~> 12.3)
|
64
|
+
rspec (~> 3.7)
|
65
|
+
simplecov (~> 0.16)
|
66
|
+
yard (~> 0.9)
|
67
|
+
|
68
|
+
BUNDLED WITH
|
69
|
+
1.16.2
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Adam Ladachowski
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
[](https://rubygems.org/gems/iml)
|
2
|
+
[](https://travis-ci.org/aladac/iml)
|
3
|
+
[](https://codeclimate.com/github/aladac/iml/maintainability)
|
4
|
+
[](https://codeclimate.com/github/aladac/iml/test_coverage)
|
5
|
+
[](https://www.rubydoc.info/github/aladac/iml/master)
|
6
|
+
|
7
|
+
[](https://rubygems.org/gems/iml)
|
8
|
+
|
9
|
+
*Intricate (Media) Matching Logic*
|
10
|
+
|
11
|
+
This is a media file handling library which is supposed to "guess" the intended type of media file based on specific naming patterns.
|
12
|
+
Its main purpose is to serve as runtime for renaming media files according to specified patterns.
|
13
|
+
The gem includes an executable `iml` through which rename operations are possible.
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
This gem requires ruby >= 2.4
|
18
|
+
|
19
|
+
Add this line to your application's Gemfile:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem 'iml'
|
23
|
+
```
|
24
|
+
|
25
|
+
And then execute:
|
26
|
+
|
27
|
+
$ bundle
|
28
|
+
|
29
|
+
Or install it yourself as:
|
30
|
+
|
31
|
+
$ gem install iml
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
### Command Line
|
36
|
+
|
37
|
+
```
|
38
|
+
Usage: iml [options] MEDIA_FILE [MEDIA_FILE] ...
|
39
|
+
-v, --[no-]verbose Run verbosely
|
40
|
+
-p, --[no-]pretend Dry run, do not move any files
|
41
|
+
-t, --target PATH Path to move media files to, default: current directory
|
42
|
+
-o, --movie-format FORMAT Format of the output path of movies, default: '%T (%Y).%f'
|
43
|
+
-O, --tv-format FORMAT Format of the output path of TV series, default: '%T/Season %s/%T - S%SE%E.%f'
|
44
|
+
-l, --list-formats Format description
|
45
|
+
-f, --force Use the force, override output files
|
46
|
+
|
47
|
+
$ iml -v Some.Cool.Movie.2018.1080p.BRRip.x264.aac-GROUP.mp4
|
48
|
+
I, [2018-07-06T13:38:29.836887 #70771] INFO -- : Some.Cool.Movie.2018.1080p.BRRip.x264.aac-GROUP.mp4 looks like a movie
|
49
|
+
I, [2018-07-06T13:38:29.837047 #70771] INFO -- : Moving Some.Cool.Movie.2018.1080p.BRRip.x264.aac-GROUP.mp4 to Some Cool Movie (2018).mp4
|
50
|
+
```
|
51
|
+
|
52
|
+
### Code
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
title = "An.Interesting.TV.Show.S01E01.1080p.WEBRIP.h265-GROUP.mkv"
|
56
|
+
=> "An.Interesting.TV.Show.S01E01.1080p.WEBRIP.h265-GROUP.mkv"
|
57
|
+
IML::Text.new(title).detect
|
58
|
+
=> #<IML::TVSeries title="An Interesting Tv Show", season="01", episode="01", quality="1080p", source="WEBRIP", codec="h265", group="GROUP", extension="mkv">
|
59
|
+
```
|
60
|
+
|
61
|
+
## License
|
62
|
+
|
63
|
+
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,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
|
8
|
+
desc 'Console'
|
9
|
+
task :console do
|
10
|
+
require 'bundler/setup'
|
11
|
+
require 'iml'
|
12
|
+
require 'pry'
|
13
|
+
Pry.start
|
14
|
+
end
|
15
|
+
|
16
|
+
task default: :spec
|
data/bin/iml
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'iml'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
options = {}
|
8
|
+
@logger = Logger.new(STDOUT)
|
9
|
+
|
10
|
+
begin
|
11
|
+
OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: #{File.basename $PROGRAM_NAME} [options] MEDIA_FILE [MEDIA_FILE] ..."
|
13
|
+
|
14
|
+
opts.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
|
15
|
+
options[:verbose] = v
|
16
|
+
end
|
17
|
+
opts.on('-p', '--[no-]pretend', 'Dry run, do not move any files') do |p|
|
18
|
+
options[:pretend] = p
|
19
|
+
end
|
20
|
+
opts.on('-t', '--target PATH', 'Path to move media files to, default: current directory') do |t|
|
21
|
+
options[:target] = t
|
22
|
+
end
|
23
|
+
opts.on('-o', '--movie-format FORMAT', "Format of the output path of movies, default: '#{IML::Movie::DEFAULT_FORMAT}'") do |f|
|
24
|
+
options[:movie_format] = f
|
25
|
+
end
|
26
|
+
opts.on('-O', '--tv-format FORMAT', "Format of the output path of TV series, default: '#{IML::TVSeries::DEFAULT_FORMAT}'") do |f|
|
27
|
+
options[:tv_format] = f
|
28
|
+
end
|
29
|
+
opts.on('-l', '--list-formats', 'Format description') do |f|
|
30
|
+
options[:format] = f
|
31
|
+
end
|
32
|
+
opts.on('-f', '--force', 'Use the force, override output files') do |f|
|
33
|
+
options[:force] = f
|
34
|
+
end
|
35
|
+
puts opts if opts.default_argv.empty? && STDIN.tty?
|
36
|
+
end.parse!
|
37
|
+
rescue OptionParser::InvalidOption => e
|
38
|
+
@logger.error e.message
|
39
|
+
end
|
40
|
+
|
41
|
+
if options[:format]
|
42
|
+
puts <<~FORMAT
|
43
|
+
Formatting description:
|
44
|
+
%T - title
|
45
|
+
%t - episode title
|
46
|
+
%Y - year
|
47
|
+
%S - season number string as found in the input path
|
48
|
+
%s - season number integer
|
49
|
+
%E - episode number string as found in the input path
|
50
|
+
%e - episode number integer
|
51
|
+
%g - group name
|
52
|
+
%a - audio codec
|
53
|
+
%v - video codec
|
54
|
+
%f - file format / extension
|
55
|
+
%z - source
|
56
|
+
%q - quality
|
57
|
+
|
58
|
+
FORMAT
|
59
|
+
exit
|
60
|
+
end
|
61
|
+
|
62
|
+
input = STDIN.tty? ? ARGV : $stdin.readlines
|
63
|
+
|
64
|
+
input ||= ARGV
|
65
|
+
|
66
|
+
def file_operations(path, media, options)
|
67
|
+
pathname = media.pathname
|
68
|
+
dirname = pathname.dirname
|
69
|
+
unless dirname.to_s == '.'
|
70
|
+
@logger.info "Creating #{dirname}" if options[:verbose] || options[:pretend]
|
71
|
+
media.create_dir
|
72
|
+
end
|
73
|
+
@logger.info "Moving #{path} to #{pathname}" if options[:verbose] || options[:pretend]
|
74
|
+
media.move(path)
|
75
|
+
end
|
76
|
+
|
77
|
+
input.map { |p| Pathname(p.chomp) }.tqdm(desc: 'Processing input', leave: true).each do |path|
|
78
|
+
filename = path.basename.to_s
|
79
|
+
unless path.exist?
|
80
|
+
@logger.warn("File #{path} does not exist, skipping")
|
81
|
+
next
|
82
|
+
end
|
83
|
+
media = IML::Text.new(filename, options).detect
|
84
|
+
unless media
|
85
|
+
@logger.warn "#{path} doesn't look like a media file" if options[:verbose] || options[:pretend]
|
86
|
+
next
|
87
|
+
end
|
88
|
+
if media.movie?
|
89
|
+
media.format_string = options[:movie_format] if options[:movie_format]
|
90
|
+
@logger.info "#{path} looks like a movie"
|
91
|
+
elsif media.tv?
|
92
|
+
media.format_string = options[:tv_format] if options[:tv_format]
|
93
|
+
@logger.info "#{path} looks like a TV series"
|
94
|
+
end
|
95
|
+
file_operations(path, media, options)
|
96
|
+
end
|
data/gemspec.yml
ADDED
data/iml.gemspec
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'iml/version'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
Gem::Specification.new do |spec|
|
9
|
+
gemspec = YAML.load_file('gemspec.yml')
|
10
|
+
spec.name = 'iml'
|
11
|
+
spec.version = IML::VERSION
|
12
|
+
spec.authors = ['Adam Ladachowski']
|
13
|
+
spec.email = ['adam.ladachowski@gmail.com']
|
14
|
+
|
15
|
+
spec.summary = 'Media string and object manipulation library'
|
16
|
+
spec.description = 'Library which parses strings into media objects'
|
17
|
+
spec.homepage = 'https://github.com/aladac/iml'
|
18
|
+
spec.license = 'MIT'
|
19
|
+
|
20
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
21
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
22
|
+
if spec.respond_to?(:metadata)
|
23
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
24
|
+
else
|
25
|
+
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
26
|
+
'public gem pushes.'
|
27
|
+
end
|
28
|
+
|
29
|
+
# Specify which files should be added to the gem when it is released.
|
30
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
31
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
32
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
33
|
+
end
|
34
|
+
spec.bindir = 'bin'
|
35
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
36
|
+
spec.require_paths = ['lib']
|
37
|
+
|
38
|
+
spec.required_ruby_version = '>= 2.4'
|
39
|
+
spec.metadata['yard.run'] = 'yri'
|
40
|
+
|
41
|
+
gemspec['dependencies'].each do |name, version|
|
42
|
+
spec.add_dependency(name, version)
|
43
|
+
end
|
44
|
+
|
45
|
+
gemspec['development_dependencies'].each do |name, version|
|
46
|
+
spec.add_development_dependency(name, version)
|
47
|
+
end
|
48
|
+
end
|
data/lib/iml.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'tqdm'
|
5
|
+
require 'logger'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'pathname'
|
8
|
+
require 'active_support/inflector'
|
9
|
+
require 'active_support/core_ext/object/blank'
|
10
|
+
require 'active_support/core_ext/hash'
|
11
|
+
require 'ostruct'
|
12
|
+
require 'pry' unless ENV['BUNDLER_VERSION'].nil?
|
13
|
+
require 'iml/version'
|
14
|
+
require 'iml/base'
|
15
|
+
require 'iml/patterns'
|
16
|
+
require 'iml/text'
|
17
|
+
require 'iml/movie'
|
18
|
+
require 'iml/tvseries'
|
19
|
+
require 'iml/hash'
|
20
|
+
|
21
|
+
# IML Namespace
|
22
|
+
module IML
|
23
|
+
end
|
data/lib/iml/base.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Base media file class
|
4
|
+
class IML::Base < OpenStruct
|
5
|
+
# @return <String> Allows retrieving and setting of the format string for the output name
|
6
|
+
attr_accessor :format_string
|
7
|
+
# @return <String> Allows for setting and getting the output path sans the output base filename
|
8
|
+
attr_accessor :prefix
|
9
|
+
# @return <Boolean> Allows for setting and getting dry run setting
|
10
|
+
attr_accessor :pretend
|
11
|
+
|
12
|
+
delegate :dirname, to: :pathname # @return <Pathname> output path sans the output base filename
|
13
|
+
delegate :basename, to: :pathname # @return <Pathname> output base filename
|
14
|
+
|
15
|
+
def initialize(hash = nil, options = {})
|
16
|
+
@prefix = options[:target]
|
17
|
+
@pretend = options[:pretend]
|
18
|
+
super(hash)
|
19
|
+
process if hash
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [String] formated output filename
|
23
|
+
def present
|
24
|
+
format_string = output_format
|
25
|
+
self.class::PLACEHOLDERS.each do |placeholder, attribute|
|
26
|
+
format_string = format_string.gsub(placeholder, send(attribute).to_s)
|
27
|
+
end
|
28
|
+
format_string
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Pathname] full output path of the media file
|
32
|
+
def pathname
|
33
|
+
@prefix ? Pathname(@prefix) + Pathname(present) : Pathname(present)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Creates the output directory if needed
|
37
|
+
# @return [Array<String>] array containing the path of the created output directory
|
38
|
+
# @example
|
39
|
+
# movie = IML::Text.new('Cool.Movie.2018.720p.BluRay.H264.AAC2.0-GROUP.mp4').detect
|
40
|
+
# # => <IML::Movie title="Cool Movie", year="2018", quality="720p", source="BluRay" ..>
|
41
|
+
# movie.create_dir
|
42
|
+
# # => ["."]
|
43
|
+
def create_dir
|
44
|
+
FileUtils.mkdir_p dirname unless @pretend
|
45
|
+
end
|
46
|
+
|
47
|
+
# Moves the media file to the output directory
|
48
|
+
# @return <Integer> 0 on success, 1 on failure
|
49
|
+
def move(path)
|
50
|
+
FileUtils.mv path, pathname unless @pretend
|
51
|
+
rescue Errno::ENOENT
|
52
|
+
1
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def output_format
|
58
|
+
format_string || self.class::DEFAULT_FORMAT
|
59
|
+
end
|
60
|
+
|
61
|
+
# Process the IML::Base object and apply some normalizing and cleanup on the fields
|
62
|
+
def process
|
63
|
+
normalize_video_codec_name
|
64
|
+
normalize_audio_codec_name
|
65
|
+
titleize
|
66
|
+
delete_fields
|
67
|
+
end
|
68
|
+
|
69
|
+
def normalize_video_codec_name
|
70
|
+
self.codec = IML::Patterns.config.codec[codec.downcase] unless IML::Patterns.config.codec.value?(codec.downcase)
|
71
|
+
end
|
72
|
+
|
73
|
+
def normalize_audio_codec_name
|
74
|
+
return false if final_audio_format?
|
75
|
+
self.channels = IML::Patterns.config.audio[audio.downcase][:channels]
|
76
|
+
self.audio = IML::Patterns.config.audio[audio.downcase][:name]
|
77
|
+
end
|
78
|
+
|
79
|
+
def final_audio_format?
|
80
|
+
return true if IML::Patterns.config.audio.values.map { |a| a[:name] }.include?(audio) || !audio
|
81
|
+
end
|
82
|
+
|
83
|
+
def delete_fields
|
84
|
+
each_pair do |k, v|
|
85
|
+
delete_field(k) unless v
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def titleize
|
90
|
+
self.title = IML::Text.new(title).to_title if title.is_a?(String)
|
91
|
+
self.episode_title = IML::Text.new(episode_title).to_title if episode_title.is_a?(String)
|
92
|
+
self.episode_title = nil if episode_title.to_s.empty?
|
93
|
+
end
|
94
|
+
end
|
data/lib/iml/hash.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# IML's own re-implementation of hash with key/method funky patch and active_supports HashWithIndeifferetAccess
|
4
|
+
class IML::Hash < HashWithIndifferentAccess
|
5
|
+
# Way to access Hash keys by method_name
|
6
|
+
def method_missing(method_name)
|
7
|
+
key?(method_name) ? self[method_name] : super
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return <Boolean> Responds true for methods that have names of Hash keys
|
11
|
+
def respond_to_missing?(method_name, _include_private = false)
|
12
|
+
key?(method_name) || false
|
13
|
+
end
|
14
|
+
end
|
data/lib/iml/movie.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Movie media file type class
|
4
|
+
class IML::Movie < IML::Base
|
5
|
+
# Formatting placeholders for Movies
|
6
|
+
PLACEHOLDERS = {
|
7
|
+
'%T' => :title,
|
8
|
+
'%Y' => :year,
|
9
|
+
'%f' => :extension,
|
10
|
+
'%v' => :codec,
|
11
|
+
'%a' => :audio,
|
12
|
+
'%g' => :group,
|
13
|
+
'%z' => :source,
|
14
|
+
'%q' => :quality
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
# Default formatting string
|
18
|
+
DEFAULT_FORMAT = '%T (%Y).%f'
|
19
|
+
|
20
|
+
# @return <Boolean> always true for IML::Movie objects
|
21
|
+
def movie?
|
22
|
+
true
|
23
|
+
end
|
24
|
+
end
|
data/lib/iml/patterns.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Contains methods to build the regexp to match against the input filenames
|
4
|
+
class IML::Patterns
|
5
|
+
# @return [IML::Hash] pattern config
|
6
|
+
def config
|
7
|
+
IML::Hash.new ::YAML.load_file(pattern_file)
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [IML::Hash] pattern config singleton version
|
11
|
+
def self.config
|
12
|
+
new.config
|
13
|
+
end
|
14
|
+
|
15
|
+
# Mini format arrtibute DSL
|
16
|
+
def method_missing(method_name)
|
17
|
+
m = config[method_name]
|
18
|
+
super unless m
|
19
|
+
if m.is_a?(Hash)
|
20
|
+
"(?<#{method_name}>(#{m.keys.join('|')}))"
|
21
|
+
elsif m.is_a?(Array)
|
22
|
+
"(?<#{method_name}>(#{m.join('|')}))"
|
23
|
+
elsif m.is_a?(String)
|
24
|
+
"(?<#{method_name}>#{m})"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return <Boolean> true when method name is a config key
|
29
|
+
def respond_to_missing?(method_name, _include_private = false)
|
30
|
+
config.key?(method_name) || false
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Array<Regex>] Array of Regexp patterns to match filenames of Movies
|
34
|
+
def movie
|
35
|
+
[
|
36
|
+
/#{format_pattern('^%<title>s\.%<year>s\.?%<quality>s?\.%<source>s\.%<codec>s\.?%<audio>s?-?%<group>s\.%<extension>s$')}/i,
|
37
|
+
/#{format_pattern('^%<title>s_\(%<year>s\)_\[%<quality>s,%<source>s,%<audio>s,%<codec>s\]_-_%<group>s.%<extension>s$')}/i
|
38
|
+
]
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Array<Regex>] Array of Regexp patterns to match filenames of TV Shows
|
42
|
+
def tv
|
43
|
+
[
|
44
|
+
/#{format_pattern('^%<title>s.S%<season>sE%<episode>s.?%<episode_title>s?.?%<quality>s?.%<source>s.%<audio>s?\.?%<codec>s-%<group>s.%<extension>s$')}/i
|
45
|
+
]
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def pattern_file
|
51
|
+
Pathname(__dir__) + '..' + '..' + 'patterns.yml'
|
52
|
+
end
|
53
|
+
|
54
|
+
def media_info
|
55
|
+
{
|
56
|
+
title: title, year: year, quality: quality, source: source, codec: codec, audio: audio, group: group,
|
57
|
+
extension: extension, season: season, episode: episode, episode_title: episode_title
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def format_pattern(pattern)
|
62
|
+
format(pattern, media_info)
|
63
|
+
end
|
64
|
+
end
|
data/lib/iml/text.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Parsing and mangling of text metadata
|
4
|
+
class IML::Text < String
|
5
|
+
attr_accessor :options
|
6
|
+
|
7
|
+
def initialize(string = nil, options = {})
|
8
|
+
@options = options
|
9
|
+
super(string.to_s)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Convert IML::Text to desired title format
|
13
|
+
def to_title
|
14
|
+
tr('.', ' ').tr('_', ' ').titleize
|
15
|
+
end
|
16
|
+
|
17
|
+
# Determine if IML::Text matches rules for a media type
|
18
|
+
# @return [<IML::Movie>, <IML::TVSeries>] Media type object
|
19
|
+
def detect
|
20
|
+
tv? || movie? || false
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def tv?
|
26
|
+
match = match_patterns(IML::Patterns.new.tv)
|
27
|
+
match ? IML::TVSeries.new(match, options) : false
|
28
|
+
end
|
29
|
+
|
30
|
+
def movie?
|
31
|
+
match = match_patterns(IML::Patterns.new.movie)
|
32
|
+
match ? IML::Movie.new(match, options) : false
|
33
|
+
end
|
34
|
+
|
35
|
+
def match_patterns(patterns)
|
36
|
+
patterns.each do |pattern|
|
37
|
+
match = match_and_return(pattern)
|
38
|
+
return match if match
|
39
|
+
end
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
def match_and_return(pattern)
|
44
|
+
match = self.match(pattern)
|
45
|
+
if match
|
46
|
+
match.named_captures
|
47
|
+
else
|
48
|
+
false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/iml/tvseries.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# TV Series media file type class
|
4
|
+
class IML::TVSeries < IML::Base
|
5
|
+
# Formatting placeholders for TV Series
|
6
|
+
PLACEHOLDERS = {
|
7
|
+
'%T' => :title,
|
8
|
+
'%E' => :episode,
|
9
|
+
'%S' => :season,
|
10
|
+
'%f' => :extension,
|
11
|
+
'%e' => :episode_i,
|
12
|
+
'%s' => :season_i,
|
13
|
+
'%t' => :episode_title,
|
14
|
+
'%a' => :audio,
|
15
|
+
'%v' => :codec,
|
16
|
+
'%q' => :quality,
|
17
|
+
'%g' => :group,
|
18
|
+
'%z' => :source
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
# Default formatting sting
|
22
|
+
DEFAULT_FORMAT = '%T/Season %s/%T - S%SE%E.%f'
|
23
|
+
|
24
|
+
# @return <Boolean> always true for IML::TVSeries
|
25
|
+
def tv?
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return <Integer> Season number in Integer
|
30
|
+
def season_i
|
31
|
+
season.to_i
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return <Integer> episode number in Integer
|
35
|
+
def episode_i
|
36
|
+
episode.to_i
|
37
|
+
end
|
38
|
+
end
|
data/lib/iml/version.rb
ADDED
data/patterns.yml
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
---
|
2
|
+
quality:
|
3
|
+
- 720p
|
4
|
+
- 1080p
|
5
|
+
- 2160p
|
6
|
+
codec:
|
7
|
+
x264: h.264
|
8
|
+
h264: h.264
|
9
|
+
x265: h.265
|
10
|
+
h265: h.265
|
11
|
+
avc: h.264
|
12
|
+
xvid: Xvid
|
13
|
+
source:
|
14
|
+
hdtv: HDTV
|
15
|
+
bdrip: BD
|
16
|
+
dvdrip: DVD
|
17
|
+
webrip: WEB
|
18
|
+
bluray: BD
|
19
|
+
nf.web-dl: Netflix
|
20
|
+
brrip: BD
|
21
|
+
web: WEB
|
22
|
+
amzn.webrip: Amazon
|
23
|
+
audio:
|
24
|
+
aac:
|
25
|
+
name: AAC
|
26
|
+
flac:
|
27
|
+
name: FLAC
|
28
|
+
ac3:
|
29
|
+
name: AC3
|
30
|
+
dts:
|
31
|
+
name: DTS
|
32
|
+
dts-hd.ma.5.1:
|
33
|
+
name: DTS-HD Master
|
34
|
+
channels: '5.1'
|
35
|
+
truehd.7.1.atmos:
|
36
|
+
name: TrueHD Atmos
|
37
|
+
channels: '7.1'
|
38
|
+
dd5.1:
|
39
|
+
name: AC3
|
40
|
+
channels: '5.1'
|
41
|
+
ddp5.1:
|
42
|
+
name: E-AC3
|
43
|
+
channels: '5.1'
|
44
|
+
aac2.0:
|
45
|
+
name: AAC
|
46
|
+
channels: '2.0'
|
47
|
+
season: "\\d{2}"
|
48
|
+
episode: "\\d{2}"
|
49
|
+
episode_title: ".*?"
|
50
|
+
extension:
|
51
|
+
- avi
|
52
|
+
- mp4
|
53
|
+
- mkv
|
54
|
+
group: "[\\[\\]\\w]*?"
|
55
|
+
title: ".*"
|
56
|
+
year: "\\d{4}"
|
metadata
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: iml
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Ladachowski
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-07-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: tqdm
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.3'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: codeclimate-test-reporter
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.16'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.16'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '12.3'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '12.3'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.7'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.7'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.11'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.11'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: simplecov
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.16'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.16'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: yard
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0.9'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0.9'
|
139
|
+
description: Library which parses strings into media objects
|
140
|
+
email:
|
141
|
+
- adam.ladachowski@gmail.com
|
142
|
+
executables:
|
143
|
+
- iml
|
144
|
+
extensions: []
|
145
|
+
extra_rdoc_files: []
|
146
|
+
files:
|
147
|
+
- ".gitignore"
|
148
|
+
- ".rspec"
|
149
|
+
- ".rubocop.yml"
|
150
|
+
- ".travis.yml"
|
151
|
+
- Gemfile
|
152
|
+
- Gemfile.lock
|
153
|
+
- LICENSE.txt
|
154
|
+
- README.md
|
155
|
+
- Rakefile
|
156
|
+
- bin/iml
|
157
|
+
- doc/iml-logo.png
|
158
|
+
- gemspec.yml
|
159
|
+
- iml.gemspec
|
160
|
+
- lib/iml.rb
|
161
|
+
- lib/iml/base.rb
|
162
|
+
- lib/iml/hash.rb
|
163
|
+
- lib/iml/movie.rb
|
164
|
+
- lib/iml/patterns.rb
|
165
|
+
- lib/iml/text.rb
|
166
|
+
- lib/iml/tvseries.rb
|
167
|
+
- lib/iml/version.rb
|
168
|
+
- patterns.yml
|
169
|
+
homepage: https://github.com/aladac/iml
|
170
|
+
licenses:
|
171
|
+
- MIT
|
172
|
+
metadata:
|
173
|
+
allowed_push_host: https://rubygems.org
|
174
|
+
yard.run: yri
|
175
|
+
post_install_message:
|
176
|
+
rdoc_options: []
|
177
|
+
require_paths:
|
178
|
+
- lib
|
179
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
180
|
+
requirements:
|
181
|
+
- - ">="
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: '2.4'
|
184
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
185
|
+
requirements:
|
186
|
+
- - ">="
|
187
|
+
- !ruby/object:Gem::Version
|
188
|
+
version: '0'
|
189
|
+
requirements: []
|
190
|
+
rubyforge_project:
|
191
|
+
rubygems_version: 2.6.14.1
|
192
|
+
signing_key:
|
193
|
+
specification_version: 4
|
194
|
+
summary: Media string and object manipulation library
|
195
|
+
test_files: []
|