iml 0.1.6 → 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 +5 -5
- data/.claude/settings.local.json +7 -0
- data/.github/workflows/ci.yml +30 -0
- data/.github/workflows/publish.yml +32 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +108 -50
- data/PLAN.md +350 -0
- data/README.md +79 -23
- data/Rakefile +9 -8
- data/TODO.md +43 -0
- data/bin/iml +80 -70
- data/gemspec.yml +7 -10
- data/iml.gemspec +22 -31
- data/lib/iml/configuration.rb +49 -0
- data/lib/iml/file_mover.rb +20 -0
- data/lib/iml/formatter.rb +16 -0
- data/lib/iml/media/movie.rb +30 -0
- data/lib/iml/media/tv_series.rb +45 -0
- data/lib/iml/normalizer.rb +79 -0
- data/lib/iml/parser.rb +39 -0
- data/lib/iml/pattern_builder.rb +42 -0
- data/lib/iml/version.rb +1 -1
- data/lib/iml.rb +34 -25
- data/patterns.yml +36 -1
- metadata +34 -72
- data/.rubocop.yml +0 -15
- data/.travis.yml +0 -7
- data/lib/iml/base.rb +0 -94
- data/lib/iml/hash.rb +0 -14
- data/lib/iml/movie.rb +0 -24
- data/lib/iml/patterns.rb +0 -64
- data/lib/iml/text.rb +0 -51
- data/lib/iml/tvseries.rb +0 -38
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class IML::Normalizer
|
|
4
|
+
def initialize(config)
|
|
5
|
+
@config = config
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call(attrs)
|
|
9
|
+
attrs = attrs.transform_keys(&:to_sym)
|
|
10
|
+
attrs = normalize_video_codec(attrs)
|
|
11
|
+
attrs = normalize_audio_codec(attrs)
|
|
12
|
+
attrs = normalize_source(attrs)
|
|
13
|
+
attrs = normalize_titles(attrs)
|
|
14
|
+
remove_blank_values(attrs)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def normalize_video_codec(attrs)
|
|
20
|
+
codec = attrs[:codec]
|
|
21
|
+
return attrs unless codec
|
|
22
|
+
|
|
23
|
+
codec_down = codec.downcase
|
|
24
|
+
normalized = @config.codec_map[codec_down]
|
|
25
|
+
attrs.merge(codec: normalized || codec)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def normalize_audio_codec(attrs)
|
|
29
|
+
audio = attrs[:audio]
|
|
30
|
+
return attrs unless audio
|
|
31
|
+
|
|
32
|
+
audio_down = audio.downcase
|
|
33
|
+
audio_entry = @config.audio_map[audio_down]
|
|
34
|
+
return attrs unless audio_entry.is_a?(Hash)
|
|
35
|
+
|
|
36
|
+
final_names = @config.audio_map.values
|
|
37
|
+
.select { |v| v.is_a?(Hash) }
|
|
38
|
+
.map { |v| v["name"] }
|
|
39
|
+
|
|
40
|
+
return attrs if final_names.include?(audio)
|
|
41
|
+
|
|
42
|
+
attrs.merge(
|
|
43
|
+
audio: audio_entry["name"],
|
|
44
|
+
channels: audio_entry["channels"]
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def normalize_source(attrs)
|
|
49
|
+
source = attrs[:source]
|
|
50
|
+
return attrs unless source
|
|
51
|
+
|
|
52
|
+
normalized = @config.source_map[source.downcase]
|
|
53
|
+
attrs.merge(source: normalized || source)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def normalize_titles(attrs)
|
|
57
|
+
attrs = titleize_field(attrs, :title)
|
|
58
|
+
attrs = titleize_field(attrs, :episode_title)
|
|
59
|
+
if attrs[:episode_title].to_s.strip.empty?
|
|
60
|
+
attrs = attrs.merge(episode_title: nil)
|
|
61
|
+
end
|
|
62
|
+
attrs
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def titleize_field(attrs, field)
|
|
66
|
+
value = attrs[field]
|
|
67
|
+
return attrs unless value.is_a?(String) && !value.empty?
|
|
68
|
+
|
|
69
|
+
attrs.merge(field => to_title(value))
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def to_title(str)
|
|
73
|
+
str.tr(".", " ").tr("_", " ").gsub(/\b\w/, &:upcase)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def remove_blank_values(attrs)
|
|
77
|
+
attrs.reject { |_, v| v.nil? || (v.is_a?(String) && v.empty?) }
|
|
78
|
+
end
|
|
79
|
+
end
|
data/lib/iml/parser.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class IML::Parser
|
|
4
|
+
def initialize(config: IML.configuration, pattern_builder: nil, normalizer: nil)
|
|
5
|
+
@config = config
|
|
6
|
+
@pattern_builder = pattern_builder || IML::PatternBuilder.new(config)
|
|
7
|
+
@normalizer = normalizer || IML::Normalizer.new(config)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def parse(filename)
|
|
11
|
+
try_tv(filename) || try_movie(filename)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def try_tv(filename)
|
|
17
|
+
captures = match_first(filename, @pattern_builder.tv_patterns)
|
|
18
|
+
return unless captures
|
|
19
|
+
|
|
20
|
+
attrs = @normalizer.call(captures)
|
|
21
|
+
IML::Media::TvSeries.new(**attrs)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def try_movie(filename)
|
|
25
|
+
captures = match_first(filename, @pattern_builder.movie_patterns)
|
|
26
|
+
return unless captures
|
|
27
|
+
|
|
28
|
+
attrs = @normalizer.call(captures)
|
|
29
|
+
IML::Media::Movie.new(**attrs)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def match_first(filename, patterns)
|
|
33
|
+
patterns.each do |pattern|
|
|
34
|
+
match = filename.match(pattern)
|
|
35
|
+
return match.named_captures if match
|
|
36
|
+
end
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class IML::PatternBuilder
|
|
4
|
+
FIELDS = %i[
|
|
5
|
+
title year tags quality source codec audio group
|
|
6
|
+
extension season episode episode_title extras
|
|
7
|
+
video_tags bit_depth
|
|
8
|
+
].freeze
|
|
9
|
+
|
|
10
|
+
def initialize(config)
|
|
11
|
+
@config = config
|
|
12
|
+
@field_patterns = {}
|
|
13
|
+
FIELDS.each { |f| @field_patterns[f] = config.pattern_for(f) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def movie_patterns
|
|
17
|
+
@movie_patterns ||= [
|
|
18
|
+
# Standard: title.year.tags.quality.source.codec.audio-group.ext
|
|
19
|
+
build('^%<title>s\.%<year>s\.?%<tags>s\.?%<quality>s?\.?%<source>s\.%<codec>s\.?%<audio>s?-?%<group>s\.%<extension>s$'),
|
|
20
|
+
# Audio before codec: title.year.tags.quality.source.audio.codec-group.ext
|
|
21
|
+
build('^%<title>s\.%<year>s\.?%<tags>s\.?%<quality>s?\.?%<source>s\.%<audio>s\.?%<codec>s-?%<group>s\.%<extension>s$'),
|
|
22
|
+
# Bracket format: title_(year)_[quality,source,audio,codec]_-_group.ext
|
|
23
|
+
build('^%<title>s_\(%<year>s\)_\[%<quality>s,%<source>s,%<audio>s,%<codec>s\]_-_%<group>s.%<extension>s$'),
|
|
24
|
+
# P2P with bracket group: title.year.tags.quality.source.extras[group].ext
|
|
25
|
+
build('^%<title>s\.%<year>s\.?%<tags>s\.?%<quality>s?\.?%<source>s\.%<extras>s%<group>s\.%<extension>s$'),
|
|
26
|
+
# Extended: title.year.quality.video_tags.source.codec.bit_depth.audio-group.ext
|
|
27
|
+
build('^%<title>s\.%<year>s\.?%<tags>s\.?%<quality>s?\.?%<video_tags>s%<source>s\.%<codec>s\.?%<bit_depth>s\.?%<audio>s?-?%<group>s\.%<extension>s$')
|
|
28
|
+
].freeze
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def tv_patterns
|
|
32
|
+
@tv_patterns ||= [
|
|
33
|
+
build('^%<title>s.S%<season>sE%<episode>s.?%<episode_title>s?.?%<quality>s?.%<source>s.%<audio>s?\.?%<codec>s-%<group>s.%<extension>s$')
|
|
34
|
+
].freeze
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def build(template)
|
|
40
|
+
Regexp.new(format(template, @field_patterns), Regexp::IGNORECASE)
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/iml/version.rb
CHANGED
data/lib/iml.rb
CHANGED
|
@@ -1,30 +1,39 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require 'logger'
|
|
7
|
-
require 'fileutils'
|
|
8
|
-
require 'pathname'
|
|
9
|
-
require 'active_support/inflector'
|
|
10
|
-
require 'active_support/core_ext/object/blank'
|
|
11
|
-
require 'active_support/core_ext/hash'
|
|
12
|
-
require 'ostruct'
|
|
13
|
-
require 'pry' unless ENV['BUNDLER_VERSION'].nil?
|
|
14
|
-
require 'iml/version'
|
|
15
|
-
require 'iml/base'
|
|
16
|
-
require 'iml/patterns'
|
|
17
|
-
require 'iml/text'
|
|
18
|
-
require 'iml/movie'
|
|
19
|
-
require 'iml/tvseries'
|
|
20
|
-
require 'iml/hash'
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "iml/version"
|
|
21
6
|
|
|
22
|
-
begin
|
|
23
|
-
require 'iml-imdb'
|
|
24
|
-
rescue LoadError
|
|
25
|
-
puts "IMDB support disabled, to enable add 'iml-imdb' to Gemfile or install manually"
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# IML Namespace
|
|
29
7
|
module IML
|
|
8
|
+
class Error < StandardError; end
|
|
9
|
+
class FileNotFoundError < Error; end
|
|
10
|
+
|
|
11
|
+
module Media; end
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def configuration(path = nil)
|
|
15
|
+
if path
|
|
16
|
+
@configuration = Configuration.new(path)
|
|
17
|
+
else
|
|
18
|
+
@configuration ||= Configuration.new
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def reset_configuration!
|
|
23
|
+
@configuration = nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def parse(filename)
|
|
27
|
+
Parser.new.parse(filename)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
30
|
end
|
|
31
|
+
|
|
32
|
+
require "iml/configuration"
|
|
33
|
+
require "iml/pattern_builder"
|
|
34
|
+
require "iml/normalizer"
|
|
35
|
+
require "iml/formatter"
|
|
36
|
+
require "iml/file_mover"
|
|
37
|
+
require "iml/parser"
|
|
38
|
+
require "iml/media/movie"
|
|
39
|
+
require "iml/media/tv_series"
|
data/patterns.yml
CHANGED
|
@@ -6,8 +6,10 @@ quality:
|
|
|
6
6
|
codec:
|
|
7
7
|
x264: h.264
|
|
8
8
|
h264: h.264
|
|
9
|
+
h.264: h.264
|
|
9
10
|
x265: h.265
|
|
10
11
|
h265: h.265
|
|
12
|
+
h.265: h.265
|
|
11
13
|
avc: h.264
|
|
12
14
|
xvid: Xvid
|
|
13
15
|
source:
|
|
@@ -15,11 +17,20 @@ source:
|
|
|
15
17
|
bdrip: BD
|
|
16
18
|
dvdrip: DVD
|
|
17
19
|
webrip: WEB
|
|
20
|
+
web-dl: WEB
|
|
18
21
|
bluray: BD
|
|
19
22
|
nf.web-dl: Netflix
|
|
23
|
+
nf.webrip: Netflix
|
|
20
24
|
brrip: BD
|
|
21
25
|
web: WEB
|
|
22
26
|
amzn.webrip: Amazon
|
|
27
|
+
amzn.web-dl: Amazon
|
|
28
|
+
atvp.web-dl: Apple TV+
|
|
29
|
+
dsnp.web-dl: Disney+
|
|
30
|
+
hmax.web-dl: HBO Max
|
|
31
|
+
pcok.web-dl: Peacock
|
|
32
|
+
it.web-dl: iTunes
|
|
33
|
+
it.webrip: iTunes
|
|
23
34
|
audio:
|
|
24
35
|
aac:
|
|
25
36
|
name: AAC
|
|
@@ -32,18 +43,39 @@ audio:
|
|
|
32
43
|
dts-hd.ma.5.1:
|
|
33
44
|
name: DTS-HD Master
|
|
34
45
|
channels: '5.1'
|
|
46
|
+
dts-hd.ma.7.1:
|
|
47
|
+
name: DTS-HD Master
|
|
48
|
+
channels: '7.1'
|
|
35
49
|
truehd.7.1.atmos:
|
|
36
50
|
name: TrueHD Atmos
|
|
37
51
|
channels: '7.1'
|
|
52
|
+
truehd.5.1:
|
|
53
|
+
name: TrueHD
|
|
54
|
+
channels: '5.1'
|
|
38
55
|
dd5.1:
|
|
39
56
|
name: AC3
|
|
40
57
|
channels: '5.1'
|
|
58
|
+
dd2.0:
|
|
59
|
+
name: AC3
|
|
60
|
+
channels: '2.0'
|
|
41
61
|
ddp5.1:
|
|
42
62
|
name: E-AC3
|
|
43
63
|
channels: '5.1'
|
|
64
|
+
ddp2.0:
|
|
65
|
+
name: E-AC3
|
|
66
|
+
channels: '2.0'
|
|
67
|
+
ddp5.1.atmos:
|
|
68
|
+
name: E-AC3 Atmos
|
|
69
|
+
channels: '5.1'
|
|
44
70
|
aac2.0:
|
|
45
71
|
name: AAC
|
|
46
72
|
channels: '2.0'
|
|
73
|
+
aac5.1:
|
|
74
|
+
name: AAC
|
|
75
|
+
channels: '5.1'
|
|
76
|
+
eac3:
|
|
77
|
+
name: E-AC3
|
|
78
|
+
tags: "(?:REPACK|PROPER|RERIP|INTERNAL|REAL|iNTERNAL)?"
|
|
47
79
|
season: "\\d{2}"
|
|
48
80
|
episode: "\\d{2}"
|
|
49
81
|
episode_title: ".*?"
|
|
@@ -51,6 +83,9 @@ extension:
|
|
|
51
83
|
- avi
|
|
52
84
|
- mp4
|
|
53
85
|
- mkv
|
|
54
|
-
|
|
86
|
+
extras: "(?:[\\w+]+\\.)*[\\w+]*"
|
|
87
|
+
video_tags: "(?:(?:4K|UHD|HDR10\\+?|HDR|DV|SDR|HYBRID|MULTi)\\.?)*"
|
|
88
|
+
bit_depth: "(?:(?:10bit|8bit|12bit)\\.?)?"
|
|
89
|
+
group: "[\\[\\]\\w ]*?"
|
|
55
90
|
title: ".*"
|
|
56
91
|
year: "\\d{4}"
|
metadata
CHANGED
|
@@ -1,127 +1,85 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: iml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Adam Ladachowski
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-02-04 00:00:00.000000000 Z
|
|
12
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
13
|
- !ruby/object:Gem::Dependency
|
|
56
14
|
name: bundler
|
|
57
15
|
requirement: !ruby/object:Gem::Requirement
|
|
58
16
|
requirements:
|
|
59
|
-
- - "
|
|
17
|
+
- - ">="
|
|
60
18
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '
|
|
19
|
+
version: '2.4'
|
|
62
20
|
type: :development
|
|
63
21
|
prerelease: false
|
|
64
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
23
|
requirements:
|
|
66
|
-
- - "
|
|
24
|
+
- - ">="
|
|
67
25
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '
|
|
26
|
+
version: '2.4'
|
|
69
27
|
- !ruby/object:Gem::Dependency
|
|
70
28
|
name: rake
|
|
71
29
|
requirement: !ruby/object:Gem::Requirement
|
|
72
30
|
requirements:
|
|
73
31
|
- - "~>"
|
|
74
32
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '
|
|
33
|
+
version: '13.0'
|
|
76
34
|
type: :development
|
|
77
35
|
prerelease: false
|
|
78
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
37
|
requirements:
|
|
80
38
|
- - "~>"
|
|
81
39
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '
|
|
40
|
+
version: '13.0'
|
|
83
41
|
- !ruby/object:Gem::Dependency
|
|
84
42
|
name: rspec
|
|
85
43
|
requirement: !ruby/object:Gem::Requirement
|
|
86
44
|
requirements:
|
|
87
45
|
- - "~>"
|
|
88
46
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '3.
|
|
47
|
+
version: '3.13'
|
|
90
48
|
type: :development
|
|
91
49
|
prerelease: false
|
|
92
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
51
|
requirements:
|
|
94
52
|
- - "~>"
|
|
95
53
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '3.
|
|
54
|
+
version: '3.13'
|
|
97
55
|
- !ruby/object:Gem::Dependency
|
|
98
|
-
name:
|
|
56
|
+
name: simplecov
|
|
99
57
|
requirement: !ruby/object:Gem::Requirement
|
|
100
58
|
requirements:
|
|
101
59
|
- - "~>"
|
|
102
60
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: '0.
|
|
61
|
+
version: '0.22'
|
|
104
62
|
type: :development
|
|
105
63
|
prerelease: false
|
|
106
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
65
|
requirements:
|
|
108
66
|
- - "~>"
|
|
109
67
|
- !ruby/object:Gem::Version
|
|
110
|
-
version: '0.
|
|
68
|
+
version: '0.22'
|
|
111
69
|
- !ruby/object:Gem::Dependency
|
|
112
|
-
name:
|
|
70
|
+
name: standard
|
|
113
71
|
requirement: !ruby/object:Gem::Requirement
|
|
114
72
|
requirements:
|
|
115
73
|
- - "~>"
|
|
116
74
|
- !ruby/object:Gem::Version
|
|
117
|
-
version: '
|
|
75
|
+
version: '1.40'
|
|
118
76
|
type: :development
|
|
119
77
|
prerelease: false
|
|
120
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
121
79
|
requirements:
|
|
122
80
|
- - "~>"
|
|
123
81
|
- !ruby/object:Gem::Version
|
|
124
|
-
version: '
|
|
82
|
+
version: '1.40'
|
|
125
83
|
- !ruby/object:Gem::Dependency
|
|
126
84
|
name: yard
|
|
127
85
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -144,26 +102,31 @@ executables:
|
|
|
144
102
|
extensions: []
|
|
145
103
|
extra_rdoc_files: []
|
|
146
104
|
files:
|
|
105
|
+
- ".claude/settings.local.json"
|
|
106
|
+
- ".github/workflows/ci.yml"
|
|
107
|
+
- ".github/workflows/publish.yml"
|
|
147
108
|
- ".gitignore"
|
|
148
109
|
- ".rspec"
|
|
149
|
-
- ".rubocop.yml"
|
|
150
|
-
- ".travis.yml"
|
|
151
110
|
- Gemfile
|
|
152
111
|
- Gemfile.lock
|
|
153
112
|
- LICENSE.txt
|
|
113
|
+
- PLAN.md
|
|
154
114
|
- README.md
|
|
155
115
|
- Rakefile
|
|
116
|
+
- TODO.md
|
|
156
117
|
- bin/iml
|
|
157
118
|
- doc/iml-logo.png
|
|
158
119
|
- gemspec.yml
|
|
159
120
|
- iml.gemspec
|
|
160
121
|
- lib/iml.rb
|
|
161
|
-
- lib/iml/
|
|
162
|
-
- lib/iml/
|
|
163
|
-
- lib/iml/
|
|
164
|
-
- lib/iml/
|
|
165
|
-
- lib/iml/
|
|
166
|
-
- lib/iml/
|
|
122
|
+
- lib/iml/configuration.rb
|
|
123
|
+
- lib/iml/file_mover.rb
|
|
124
|
+
- lib/iml/formatter.rb
|
|
125
|
+
- lib/iml/media/movie.rb
|
|
126
|
+
- lib/iml/media/tv_series.rb
|
|
127
|
+
- lib/iml/normalizer.rb
|
|
128
|
+
- lib/iml/parser.rb
|
|
129
|
+
- lib/iml/pattern_builder.rb
|
|
167
130
|
- lib/iml/version.rb
|
|
168
131
|
- patterns.yml
|
|
169
132
|
homepage: https://github.com/aladac/iml
|
|
@@ -172,7 +135,7 @@ licenses:
|
|
|
172
135
|
metadata:
|
|
173
136
|
allowed_push_host: https://rubygems.org
|
|
174
137
|
yard.run: yri
|
|
175
|
-
post_install_message:
|
|
138
|
+
post_install_message:
|
|
176
139
|
rdoc_options: []
|
|
177
140
|
require_paths:
|
|
178
141
|
- lib
|
|
@@ -180,16 +143,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
180
143
|
requirements:
|
|
181
144
|
- - ">="
|
|
182
145
|
- !ruby/object:Gem::Version
|
|
183
|
-
version: '
|
|
146
|
+
version: '3.1'
|
|
184
147
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
185
148
|
requirements:
|
|
186
149
|
- - ">="
|
|
187
150
|
- !ruby/object:Gem::Version
|
|
188
151
|
version: '0'
|
|
189
152
|
requirements: []
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
signing_key:
|
|
153
|
+
rubygems_version: 3.5.22
|
|
154
|
+
signing_key:
|
|
193
155
|
specification_version: 4
|
|
194
156
|
summary: Media string and object manipulation library
|
|
195
157
|
test_files: []
|
data/.rubocop.yml
DELETED
data/.travis.yml
DELETED
data/lib/iml/base.rb
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
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 if codec
|
|
64
|
-
normalize_audio_codec_name if audio
|
|
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
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
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
|