iml 0.1.5 → 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.
@@ -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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module IML
4
4
  # Version
5
- VERSION = '0.1.5'
5
+ VERSION = "0.2.0"
6
6
  end
data/lib/iml.rb CHANGED
@@ -1,26 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'nokogiri'
4
- require 'open-uri'
5
- require 'yaml'
6
- require 'tqdm'
7
- require 'logger'
8
- require 'fileutils'
9
- require 'pathname'
10
- require 'active_support/inflector'
11
- require 'active_support/core_ext/object/blank'
12
- require 'active_support/core_ext/hash'
13
- require 'ostruct'
14
- require 'pry' unless ENV['BUNDLER_VERSION'].nil?
15
- require 'iml/version'
16
- require 'iml/base'
17
- require 'iml/patterns'
18
- require 'iml/text'
19
- require 'iml/movie'
20
- require 'iml/tvseries'
21
- require 'iml/hash'
22
- require 'iml/imdb'
3
+ require "yaml"
4
+ require "fileutils"
5
+ require "iml/version"
23
6
 
24
- # IML Namespace
25
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
26
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
- group: "[\\[\\]\\w]*?"
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,141 +1,85 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
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: 2018-07-07 00:00:00.000000000 Z
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: nokogiri
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '1.8'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '1.8'
55
- - !ruby/object:Gem::Dependency
56
- name: codeclimate-test-reporter
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '1.0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '1.0'
69
13
  - !ruby/object:Gem::Dependency
70
14
  name: bundler
71
15
  requirement: !ruby/object:Gem::Requirement
72
16
  requirements:
73
- - - "~>"
17
+ - - ">="
74
18
  - !ruby/object:Gem::Version
75
- version: '1.16'
19
+ version: '2.4'
76
20
  type: :development
77
21
  prerelease: false
78
22
  version_requirements: !ruby/object:Gem::Requirement
79
23
  requirements:
80
- - - "~>"
24
+ - - ">="
81
25
  - !ruby/object:Gem::Version
82
- version: '1.16'
26
+ version: '2.4'
83
27
  - !ruby/object:Gem::Dependency
84
28
  name: rake
85
29
  requirement: !ruby/object:Gem::Requirement
86
30
  requirements:
87
31
  - - "~>"
88
32
  - !ruby/object:Gem::Version
89
- version: '12.3'
33
+ version: '13.0'
90
34
  type: :development
91
35
  prerelease: false
92
36
  version_requirements: !ruby/object:Gem::Requirement
93
37
  requirements:
94
38
  - - "~>"
95
39
  - !ruby/object:Gem::Version
96
- version: '12.3'
40
+ version: '13.0'
97
41
  - !ruby/object:Gem::Dependency
98
42
  name: rspec
99
43
  requirement: !ruby/object:Gem::Requirement
100
44
  requirements:
101
45
  - - "~>"
102
46
  - !ruby/object:Gem::Version
103
- version: '3.7'
47
+ version: '3.13'
104
48
  type: :development
105
49
  prerelease: false
106
50
  version_requirements: !ruby/object:Gem::Requirement
107
51
  requirements:
108
52
  - - "~>"
109
53
  - !ruby/object:Gem::Version
110
- version: '3.7'
54
+ version: '3.13'
111
55
  - !ruby/object:Gem::Dependency
112
- name: pry
56
+ name: simplecov
113
57
  requirement: !ruby/object:Gem::Requirement
114
58
  requirements:
115
59
  - - "~>"
116
60
  - !ruby/object:Gem::Version
117
- version: '0.11'
61
+ version: '0.22'
118
62
  type: :development
119
63
  prerelease: false
120
64
  version_requirements: !ruby/object:Gem::Requirement
121
65
  requirements:
122
66
  - - "~>"
123
67
  - !ruby/object:Gem::Version
124
- version: '0.11'
68
+ version: '0.22'
125
69
  - !ruby/object:Gem::Dependency
126
- name: simplecov
70
+ name: standard
127
71
  requirement: !ruby/object:Gem::Requirement
128
72
  requirements:
129
73
  - - "~>"
130
74
  - !ruby/object:Gem::Version
131
- version: '0.16'
75
+ version: '1.40'
132
76
  type: :development
133
77
  prerelease: false
134
78
  version_requirements: !ruby/object:Gem::Requirement
135
79
  requirements:
136
80
  - - "~>"
137
81
  - !ruby/object:Gem::Version
138
- version: '0.16'
82
+ version: '1.40'
139
83
  - !ruby/object:Gem::Dependency
140
84
  name: yard
141
85
  requirement: !ruby/object:Gem::Requirement
@@ -150,34 +94,6 @@ dependencies:
150
94
  - - "~>"
151
95
  - !ruby/object:Gem::Version
152
96
  version: '0.9'
153
- - !ruby/object:Gem::Dependency
154
- name: vcr
155
- requirement: !ruby/object:Gem::Requirement
156
- requirements:
157
- - - "~>"
158
- - !ruby/object:Gem::Version
159
- version: '4.0'
160
- type: :development
161
- prerelease: false
162
- version_requirements: !ruby/object:Gem::Requirement
163
- requirements:
164
- - - "~>"
165
- - !ruby/object:Gem::Version
166
- version: '4.0'
167
- - !ruby/object:Gem::Dependency
168
- name: webmock
169
- requirement: !ruby/object:Gem::Requirement
170
- requirements:
171
- - - "~>"
172
- - !ruby/object:Gem::Version
173
- version: '3.4'
174
- type: :development
175
- prerelease: false
176
- version_requirements: !ruby/object:Gem::Requirement
177
- requirements:
178
- - - "~>"
179
- - !ruby/object:Gem::Version
180
- version: '3.4'
181
97
  description: Library which parses strings into media objects
182
98
  email:
183
99
  - adam.ladachowski@gmail.com
@@ -186,29 +102,31 @@ executables:
186
102
  extensions: []
187
103
  extra_rdoc_files: []
188
104
  files:
105
+ - ".claude/settings.local.json"
106
+ - ".github/workflows/ci.yml"
107
+ - ".github/workflows/publish.yml"
189
108
  - ".gitignore"
190
109
  - ".rspec"
191
- - ".rubocop.yml"
192
- - ".travis.yml"
193
110
  - Gemfile
194
111
  - Gemfile.lock
195
112
  - LICENSE.txt
113
+ - PLAN.md
196
114
  - README.md
197
115
  - Rakefile
116
+ - TODO.md
198
117
  - bin/iml
199
118
  - doc/iml-logo.png
200
- - fixtures/vcr_cassettes/IML_Base_imdb.yml
201
- - fixtures/vcr_cassettes/IML_IMDB_new.yml
202
119
  - gemspec.yml
203
120
  - iml.gemspec
204
121
  - lib/iml.rb
205
- - lib/iml/base.rb
206
- - lib/iml/hash.rb
207
- - lib/iml/imdb.rb
208
- - lib/iml/movie.rb
209
- - lib/iml/patterns.rb
210
- - lib/iml/text.rb
211
- - lib/iml/tvseries.rb
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
212
130
  - lib/iml/version.rb
213
131
  - patterns.yml
214
132
  homepage: https://github.com/aladac/iml
@@ -217,7 +135,7 @@ licenses:
217
135
  metadata:
218
136
  allowed_push_host: https://rubygems.org
219
137
  yard.run: yri
220
- post_install_message:
138
+ post_install_message:
221
139
  rdoc_options: []
222
140
  require_paths:
223
141
  - lib
@@ -225,16 +143,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
225
143
  requirements:
226
144
  - - ">="
227
145
  - !ruby/object:Gem::Version
228
- version: '2.4'
146
+ version: '3.1'
229
147
  required_rubygems_version: !ruby/object:Gem::Requirement
230
148
  requirements:
231
149
  - - ">="
232
150
  - !ruby/object:Gem::Version
233
151
  version: '0'
234
152
  requirements: []
235
- rubyforge_project:
236
- rubygems_version: 2.6.14.1
237
- signing_key:
153
+ rubygems_version: 3.5.22
154
+ signing_key:
238
155
  specification_version: 4
239
156
  summary: Media string and object manipulation library
240
157
  test_files: []
data/.rubocop.yml DELETED
@@ -1,17 +0,0 @@
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 DELETED
@@ -1,7 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- rvm:
4
- - 2.4
5
- - 2.5
6
- before_install: gem install bundler -v 1.16.2
7
- script: bundle exec rspec && bundle exec codeclimate-test-reporter