New Upstream Release - guessit

Ready changes

Summary

Merged new upstream version: 3.7.1 (was: 3.5.0).

Diff

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9bf0b97..12b0586 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -11,33 +11,33 @@ jobs:
 
     strategy:
       matrix:
-        python-version: [ "3.6", "3.7", "3.8", "3.9", "3.10" ]  # pypy-3.6, pypy-3.7 are supported but a bit slow.
+        python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11", "pypy-3.8", "pypy-3.9" ]
         regex: [ "0", "1" ]
         exclude:
           # regex module doesn't play well with pypy and unicode.
-          - python-version: "pypy-3.6"
+          - python-version: "pypy-3.8"
             regex: "1"
-          - python-version: "pypy-3.7"
+          - python-version: "pypy-3.9"
             regex: "1"
           # test regex module only with Python 3.9.
-          - python-version: "3.6"
-            regex: "1"
           - python-version: "3.7"
             regex: "1"
           - python-version: "3.8"
             regex: "1"
           - python-version: "3.10"
             regex: "1"
+          - python-version: "3.11"
+            regex: "1"
 
     steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+
       - name: Setup python ${{ matrix.python-version }}
-        uses: actions/setup-python@v2
+        uses: actions/setup-python@v4
         with:
           python-version: ${{ matrix.python-version }}
 
-      - name: Checkout
-        uses: actions/checkout@v2
-
       - name: Install Dependencies
         run: |
           pip install -e .[dev,test]
@@ -57,7 +57,7 @@ jobs:
           REBULK_REGEX_ENABLED: ${{ matrix.regex }}
 
       - name: Codecov
-        uses: codecov/codecov-action@v1
+        uses: codecov/codecov-action@v3
 
   commitlint:
     if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
@@ -65,10 +65,11 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
         with:
           fetch-depth: 0
-      - uses: wagoid/commitlint-github-action@v2
+
+      - uses: wagoid/commitlint-github-action@v5
 
   build-setuptools:
     if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
@@ -77,16 +78,16 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-      - name: Setup python 3.9
-        uses: actions/setup-python@v2
-        with:
-          python-version: 3.9
-
       - name: Checkout
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
         with:
           fetch-depth: 0
 
+      - name: Setup python 3.9
+        uses: actions/setup-python@v4
+        with:
+          python-version: 3.9
+
       - name: Install Dependencies
         run: |
           pip install -e .[dev]
@@ -102,7 +103,7 @@ jobs:
       - name: Build
         run: python setup.py sdist bdist_wheel
 
-      - uses: actions/upload-artifact@v2
+      - uses: actions/upload-artifact@v3
         with:
           name: guessit-python
           path: ./dist
@@ -114,16 +115,16 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-      - name: Setup python 3.9
-        uses: actions/setup-python@v2
-        with:
-          python-version: 3.9
-
       - name: Checkout
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
         with:
           fetch-depth: 0
 
+      - name: Setup python 3.9
+        uses: actions/setup-python@v4
+        with:
+          python-version: 3.9
+
       - name: Install Dependencies
         run: |
           pip install -e .[dev]
@@ -142,7 +143,7 @@ jobs:
       - name: Check binary
         run: ./dist/guessit "Treme.1x03.Right.Place,.Wrong.Time.HDTV.XviD-NoTV.avi"
 
-      - uses: actions/upload-artifact@v2
+      - uses: actions/upload-artifact@v3
         if: matrix.regex == '0'
         with:
           name: guessit-bin-linux
@@ -155,16 +156,16 @@ jobs:
     runs-on: windows-latest
 
     steps:
-      - name: Setup python 3.9
-        uses: actions/setup-python@v2
-        with:
-          python-version: 3.9
-
       - name: Checkout
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
         with:
           fetch-depth: 0
 
+      - name: Setup python 3.9
+        uses: actions/setup-python@v4
+        with:
+          python-version: 3.9
+
       - name: Install Dependencies
         run: |
           pip install -e .[dev]
@@ -183,7 +184,7 @@ jobs:
       - name: Check binary
         run: ./dist/guessit "Treme.1x03.Right.Place,.Wrong.Time.HDTV.XviD-NoTV.avi"
 
-      - uses: actions/upload-artifact@v2
+      - uses: actions/upload-artifact@v3
         with:
           name: guessit-bin-windows
           path: ./dist
@@ -195,16 +196,16 @@ jobs:
     runs-on: macos-latest
 
     steps:
-      - name: Setup python 3.9
-        uses: actions/setup-python@v2
-        with:
-          python-version: 3.9
-
       - name: Checkout
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
         with:
           fetch-depth: 0
 
+      - name: Setup python 3.9
+        uses: actions/setup-python@v4
+        with:
+          python-version: 3.9
+
       - name: Install Dependencies
         run: |
           pip install -e .[dev]
@@ -223,7 +224,7 @@ jobs:
       - name: Check binary
         run: ./dist/guessit "Treme.1x03.Right.Place,.Wrong.Time.HDTV.XviD-NoTV.avi"
 
-      - uses: actions/upload-artifact@v2
+      - uses: actions/upload-artifact@v3
         with:
           name: guessit-bin-macos
           path: ./dist
@@ -235,17 +236,17 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-      - name: Setup python 3.9
-        uses: actions/setup-python@v2
-        with:
-          python-version: 3.9
-
       - name: Checkout
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
         with:
           fetch-depth: 0
 
-      - uses: actions/download-artifact@v2
+      - name: Setup python 3.9
+        uses: actions/setup-python@v4
+        with:
+          python-version: 3.9
+
+      - uses: actions/download-artifact@v3
         with:
           path: artifacts
 
@@ -272,11 +273,11 @@ jobs:
           PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
 
       - name: Merge master to develop
-        uses: robotology/gh-action-nightly-merge@v1.3.2
+        uses: robotology/gh-action-nightly-merge@v1.4.0
         with:
           stable_branch: 'master'
           development_branch: 'develop'
           allow_ff: true
           user_name: github-actions
         env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/mkdocs.yml b/.github/workflows/mkdocs.yml
index d4cdcec..95f6161 100644
--- a/.github/workflows/mkdocs.yml
+++ b/.github/workflows/mkdocs.yml
@@ -7,8 +7,8 @@ jobs:
   deploy:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
-      - uses: actions/setup-python@v2
+      - uses: actions/checkout@v3
+      - uses: actions/setup-python@v4
         with:
           python-version: 3.x
 
@@ -16,7 +16,7 @@ jobs:
       - run: mkdocs build
 
       - name: Deploy 🚀
-        uses: JamesIves/github-pages-deploy-action@4.1.5
+        uses: JamesIves/github-pages-deploy-action@v4
         with:
           token: ${{ secrets.GITHUB_TOKEN }}
           branch: gh-pages
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d9f6b1d..f564c0f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,37 @@ Changelog
 
 <!--next-version-placeholder-->
 
+## v3.7.1 (2023-02-20)
+### Fix
+* **episode:** Ignore absolute_episode guess when SxxExx match is available in filepart ([`4aa5012`](https://github.com/guessit-io/guessit/commit/4aa5012edbf9f2898a417baec2c301859f5bcd92))
+* **title:** Fix title guessing for `Show Name/Season XX/episode.mkv` directories pattern ([`e717928`](https://github.com/guessit-io/guessit/commit/e717928b8544489bf8aa4f6b2866c0c10f3a7a88))
+
+## v3.7.0 (2023-02-18)
+### Feature
+* **week:** Add week property ([`8309bf1`](https://github.com/guessit-io/guessit/commit/8309bf14e34e17a871e0f496c27cf431aaba18e1))
+
+### Fix
+* **episode:** Fix invalid episode range when a weak episode is present before the match ([`ff0a327`](https://github.com/guessit-io/guessit/commit/ff0a3271af736f67e7a6a5fd12b92152035ea57b))
+* **expected:** Build output from input string for expected_title/expected_group ([`90cc215`](https://github.com/guessit-io/guessit/commit/90cc2156aafee39ef20c3cb3aaf0e26ddcb83933))
+* **release_group:** Properly extract group name from format "Title (MediaInfo Individual) [Group]" ([`25bd367`](https://github.com/guessit-io/guessit/commit/25bd367032262dc2a0ee06852c21846202413823))
+* **edition:** Improve remastered/restored detection ([`c3611b9`](https://github.com/guessit-io/guessit/commit/c3611b9b26e7f6c1b6bb2d0e9a708843f3242084))
+* **container:** Add m2ts to container extensions ([`05cca80`](https://github.com/guessit-io/guessit/commit/05cca806530302733377a278c4bb8c200b6a502c))
+
+### Documentation
+* **contributing:** Update branch name ([`4af631d`](https://github.com/guessit-io/guessit/commit/4af631df7a7945a7d229919d56c7f4d874750a4a))
+
+## v3.6.0 (2023-02-18)
+### Feature
+* **audio_codec:** Detect "DTS:X" (closes #728) ([`2bdd8f5`](https://github.com/guessit-io/guessit/commit/2bdd8f568a0fa6c5eb97e1f29d5e4d488d86a2aa))
+
+## v3.5.0 (2022-11-01)
+### Feature
+* **dependencies:** Drop Python 3.6 support ([`47f5718`](https://github.com/guessit-io/guessit/commit/47f57184a9d0a25c1b415638d0b003dad88ce607))
+
+### Fix
+* **audio_codec:** Detect "E-AC-3" and "AC-3" ([`72dc12e`](https://github.com/guessit-io/guessit/commit/72dc12e2489d240839a216041ffe47e9dd128b0f))
+* **typo:** Fix common typo ([`42a80f0`](https://github.com/guessit-io/guessit/commit/42a80f0992387c96fc120480aaea35e4b3d9f5b8))
+
 ## v3.4.3 (2021-11-20)
 ### Fix
 * **setuptools:** Drop usage of test_requires and setup_requires ([#720](https://github.com/guessit-io/guessit/issues/720)) ([`324b38c`](https://github.com/guessit-io/guessit/commit/324b38ce62cd43efc51074dbd8c5e2ed64fc7573))
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 674716e..98dbd95 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,7 +8,7 @@ welcome!
     for issues that should be ideal for people who are not very familiar
     with the codebase yet.
 2.  Fork [the repository][] on Github to start making your changes to
-    the **master** branch (or branch off of it).
+    the **develop** branch (or branch off of it).
 3.  Write a test which shows that the bug was fixed or that the feature
     works as expected.
 4.  Send a pull request and bug the maintainer until it gets merged and
diff --git a/README.md b/README.md
index 2d313ee..002a834 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ For example, GuessIt can do the following:
         "type": "episode"
     }
 
-More information are available at [guessit.io](http://guessit.io/).
+More information is available at [guessit-io.github.io/guessit](https://guessit-io.github.io/guessit).
 
 Support
 -------
diff --git a/debian/changelog b/debian/changelog
index 2c9a176..432cd4f 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+guessit (3.7.1-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sun, 02 Apr 2023 13:00:43 -0000
+
 guessit (3.4.3-2) unstable; urgency=medium
 
   * Use secure URI in Homepage field.
diff --git a/docs/CNAME b/docs/CNAME
deleted file mode 100644
index 7af953d..0000000
--- a/docs/CNAME
+++ /dev/null
@@ -1 +0,0 @@
-doc.guessit.io
\ No newline at end of file
diff --git a/docs/properties.md b/docs/properties.md
index 1142010..dc3f128 100644
--- a/docs/properties.md
+++ b/docs/properties.md
@@ -42,6 +42,10 @@ Main properties
 
     Year of movie (or episode).
 
+-   **week**
+
+    Week number, from 1 to 52, of episode.
+
 -   **release\_group**
 
     Name of (non)scene group that released the file.
diff --git a/guessit/__version__.py b/guessit/__version__.py
index 2c2296b..24ac02e 100644
--- a/guessit/__version__.py
+++ b/guessit/__version__.py
@@ -4,4 +4,4 @@
 Version module
 """
 # pragma: no cover
-__version__ = '3.4.3'
+__version__ = '3.7.1'
diff --git a/guessit/api.py b/guessit/api.py
index 9b5e7f6..bc25b0d 100644
--- a/guessit/api.py
+++ b/guessit/api.py
@@ -23,7 +23,7 @@ class GuessitException(Exception):
     """
 
     def __init__(self, string, options):
-        super().__init__("An internal error has occured in guessit.\n"
+        super().__init__("An internal error has occurred in guessit.\n"
                          "===================== Guessit Exception Report =====================\n"
                          f"version={__version__}\n"
                          f"string={str(string)}\n"
@@ -98,7 +98,7 @@ def suggested_expected(titles, options=None):
     return default_api.suggested_expected(titles, options)
 
 
-class GuessItApi(object):
+class GuessItApi:
     """
     An api class that can be configured with custom Rebulk configuration.
     """
@@ -114,7 +114,7 @@ class GuessItApi(object):
         """
         Reset api internal state.
         """
-        self.__init__()
+        self.__init__()  # pylint:disable=unnecessary-dunder-call
 
     @classmethod
     def _fix_encoding(cls, value):
diff --git a/guessit/config/options.json b/guessit/config/options.json
index 3539cf9..37b0df2 100644
--- a/guessit/config/options.json
+++ b/guessit/config/options.json
@@ -55,13 +55,14 @@
       "audio_codec": {
         "MP3": {"string": ["MP3", "LAME"],"regex": ["LAME(?:\\d)+-?(?:\\d)+"]},
         "MP2": "MP2",
-        "Dolby Digital": {"string": ["Dolby", "DolbyDigital"], "regex": ["Dolby-Digital", "DD", "AC3D?"]},
+        "Dolby Digital": {"string": ["Dolby", "DolbyDigital"], "regex": ["Dolby-Digital", "DD", "AC-?3D?"]},
         "Dolby Atmos": {"string": ["Atmos"], "regex": ["Dolby-?Atmos"]},
         "AAC": "AAC",
-        "Dolby Digital Plus": ["EAC3", "DDP", "DD+"],
+        "Dolby Digital Plus": {"string": ["DDP", "DD+"], "regex": ["E-?AC-?3"]},
         "FLAC": "Flac",
         "DTS": "DTS",
         "DTS-HD": {"regex":  ["DTS-?HD", "DTS(?=-?MA)"], "conflict_solver": "lambda match, other: other if other.name == 'audio_codec' else '__default__'"},
+        "DTS:X": {"string": ["DTS:X", "DTS-X", "DTSX"] },
         "Dolby TrueHD": {"regex": ["True-?HD"] },
         "Opus": "Opus",
         "Vorbis": "Vorbis",
@@ -189,6 +190,7 @@
         "ram",
         "rm",
         "ts",
+        "m2ts",
         "vob",
         "wav",
         "webm",
@@ -241,12 +243,10 @@
         "Extended": {"string": ["extended"], "regex": ["extended-?cut", "extended-?version"], "tags": ["has-neighbor", "release-group-prefix"]},
         "Alternative Cut": {"regex": ["alternat(e|ive)(?:-?Cut)?"], "tags": ["has-neighbor", "release-group-prefix"]},
         "Remastered": [
-          {"string": "Remastered", "tags": ["has-neighbor", "release-group-prefix"]},
-          {"regex": "4k-remaster(?:ed)?", "tags": ["release-group-prefix"]}
+          {"regex": "(?:4k.)?remaster(?:ed)?", "tags": ["release-group-prefix"]}
         ],
         "Restored": [
-          {"string": "Restored", "tags": ["has-neighbor", "release-group-prefix"]},
-          {"regex": "4k-restore(?:d)?", "tags": ["release-group-prefix"]}
+          {"regex": "(?:4k.)?restore(?:d)?", "tags": ["release-group-prefix"]}
         ],
         "Uncensored": {"string": "Uncensored", "tags": ["has-neighbor", "release-group-prefix"]},
         "Uncut": {"string": "Uncut", "tags": ["has-neighbor", "release-group-prefix"]},
@@ -839,6 +839,9 @@
       "Yahoo": "YHOO",
       "YouTube Red": "RED",
       "ZDF": "ZDF"
+    },
+    "date": {
+      "week_words": ["week"]
     }
   }
 }
diff --git a/guessit/rules/common/date.py b/guessit/rules/common/date.py
index 1e11456..5850e9d 100644
--- a/guessit/rules/common/date.py
+++ b/guessit/rules/common/date.py
@@ -34,6 +34,11 @@ def valid_year(year):
     return 1920 <= year < 2030
 
 
+def valid_week(week):
+    """Check if number is a valid week"""
+    return 1 <= week < 53
+
+
 def _is_int(string):
     """
     Check if the input string is an integer
diff --git a/guessit/rules/common/expected.py b/guessit/rules/common/expected.py
index 19f2f88..000fcbf 100644
--- a/guessit/rules/common/expected.py
+++ b/guessit/rules/common/expected.py
@@ -3,9 +3,8 @@
 """
 Expected property factory
 """
-from rebulk.remodule import re
-
 from rebulk import Rebulk
+from rebulk.remodule import re
 from rebulk.utils import find_all
 
 from . import dash, seps
@@ -42,12 +41,13 @@ def build_expected_function(context_key):
                 for match in matches:
                     ret.append(match.span)
             else:
-                value = search
                 for sep in seps:
                     input_string = input_string.replace(sep, ' ')
                     search = search.replace(sep, ' ')
                 for start in find_all(input_string, search, ignore_case=True):
-                    ret.append({'start': start, 'end': start + len(search), 'value': value})
+                    end = start + len(search)
+                    value = input_string[start:end]
+                    ret.append({'start': start, 'end': end, 'value': value})
         return ret
 
     return expected
diff --git a/guessit/rules/common/quantity.py b/guessit/rules/common/quantity.py
index 2a4fcdc..aed15e7 100644
--- a/guessit/rules/common/quantity.py
+++ b/guessit/rules/common/quantity.py
@@ -10,7 +10,7 @@ from rebulk.remodule import re
 from ..common import seps
 
 
-class Quantity(object):
+class Quantity:
     """
     Represent a quantity object with magnitude and units.
     """
diff --git a/guessit/rules/properties/cd.py b/guessit/rules/properties/cd.py
index f9b3424..6ede364 100644
--- a/guessit/rules/properties/cd.py
+++ b/guessit/rules/properties/cd.py
@@ -11,7 +11,7 @@ from ..common.pattern import is_disabled
 from ...config import load_config_patterns
 
 
-def cd(config):  # pylint:disable=unused-argument
+def cd(config):  # pylint:disable=unused-argument,invalid-name
     """
     Builder for rebulk object.
 
diff --git a/guessit/rules/properties/country.py b/guessit/rules/properties/country.py
index 69f8890..f115614 100644
--- a/guessit/rules/properties/country.py
+++ b/guessit/rules/properties/country.py
@@ -87,7 +87,7 @@ class GuessitCountryConverter(babelfish.CountryReverseConverter):  # pylint: dis
         raise babelfish.CountryReverseError(name)
 
 
-class CountryFinder(object):
+class CountryFinder:
     """Helper class to search and return country matches."""
 
     def __init__(self, allowed_countries, common_words):
diff --git a/guessit/rules/properties/crc.py b/guessit/rules/properties/crc.py
index eedee93..d842157 100644
--- a/guessit/rules/properties/crc.py
+++ b/guessit/rules/properties/crc.py
@@ -35,9 +35,9 @@ def crc(config):  # pylint:disable=unused-argument
     return rebulk
 
 
-_DIGIT = 0
-_LETTER = 1
-_OTHER = 2
+_digit = 0
+_letter = 1
+_other = 2
 
 _idnum = re.compile(r'(?P<uuid>[a-zA-Z0-9-]{20,})')  # 1.0, (0, 0))
 
@@ -61,18 +61,18 @@ def guess_idnumber(string):
         letter_count = 0
         last_letter = None
 
-        last = _LETTER
+        last = _letter
         for c in result['uuid']:
             if c in '0123456789':
-                ci = _DIGIT
+                ci = _digit
             elif c in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ':
-                ci = _LETTER
+                ci = _letter
                 if c != last_letter:
                     switch_letter_count += 1
                 last_letter = c
                 letter_count += 1
             else:
-                ci = _OTHER
+                ci = _other
 
             if ci != last:
                 switch_count += 1
diff --git a/guessit/rules/properties/date.py b/guessit/rules/properties/date.py
index e50cdfa..4f322c3 100644
--- a/guessit/rules/properties/date.py
+++ b/guessit/rules/properties/date.py
@@ -1,13 +1,17 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 """
-date and year properties
+date, week and year properties
 """
+import re
+
 from rebulk import Rebulk, RemoveMatch, Rule
 
-from ..common.date import search_date, valid_year
+from ..common import dash
+from ..common.date import search_date, valid_year, valid_week
 from ..common.pattern import is_disabled
 from ..common.validators import seps_surround
+from ...reutils import build_or_pattern
 
 
 def date(config):  # pylint:disable=unused-argument
@@ -28,6 +32,15 @@ def date(config):  # pylint:disable=unused-argument
                  else '__default__',
                  validator=lambda match: seps_surround(match) and valid_year(match.value))
 
+    rebulk.regex(build_or_pattern(config.get('week_words')) + r"-?(\d{1,2})",
+                 name="week", formatter=int,
+                 children=True,
+                 flags=re.IGNORECASE, abbreviations=[dash],
+                 conflict_solver=lambda match, other: other
+                 if other.name in ('episode', 'season') and len(other.raw) < len(match.raw)
+                 else '__default__',
+                 validator=lambda match: seps_surround(match) and valid_week(match.value))
+
     def date_functional(string, context):  # pylint:disable=inconsistent-return-statements
         """
         Search for date in the string and retrieves match
diff --git a/guessit/rules/properties/episodes.py b/guessit/rules/properties/episodes.py
index 7aa2245..cbb05b8 100644
--- a/guessit/rules/properties/episodes.py
+++ b/guessit/rules/properties/episodes.py
@@ -68,8 +68,8 @@ def episodes(config):
                 if other.name in ('video_codec', 'audio_codec', 'container', 'date'):
                     return match
                 if (other.name == 'audio_channels' and 'weak-audio_channels' not in other.tags
-                        and not match.initiator.children.named(match.name + 'Marker')) or (
-                            other.name == 'screen_size' and not int_coercable(other.raw)):
+                    and not match.initiator.children.named(match.name + 'Marker')) or (
+                        other.name == 'screen_size' and not int_coercable(other.raw)):
                     return match
                 if other.name in ('season', 'episode') and match.initiator != other.initiator:
                     if (match.initiator.name in ('weak_episode', 'weak_duplicate')
@@ -172,7 +172,7 @@ def episodes(config):
         disabled=is_season_episode_disabled) \
         .defaults(tags=['SxxExx']) \
         .regex(build_or_pattern(season_markers, name='seasonMarker') + r'(?P<season>\d+)@?' +
-               build_or_pattern(episode_markers + disc_markers, name='episodeMarker') + r'@?(?P<episode>\d+)')\
+               build_or_pattern(episode_markers + disc_markers, name='episodeMarker') + r'@?(?P<episode>\d+)') \
         .repeater('+') \
         .regex(build_or_pattern(episode_markers + disc_markers + discrete_separators + range_separators,
                                 name='episodeSeparator',
@@ -186,7 +186,7 @@ def episodes(config):
         .defaults(tags=['SxxExx']) \
         .regex(r'(?P<season>\d+)@?' +
                build_or_pattern(season_ep_markers, name='episodeMarker') +
-               r'@?(?P<episode>\d+)').repeater('+') \
+               r'@?(?P<episode>\d+)').repeater('+')
 
     rebulk.chain(tags=['SxxExx'],
                  validate_all=True,
@@ -338,7 +338,6 @@ def episodes(config):
 
     rebulk.defaults(private_names=['episodeSeparator', 'seasonSeparator'])
 
-    # TODO: List of words
     # detached of X count (season/episode)
     rebulk.regex(r'(?P<episode>\d+)-?' + build_or_pattern(of_words) +
                  r'-?(?P<count>\d+)-?' + build_or_pattern(episode_words) + '?',
@@ -512,7 +511,6 @@ class AbstractSeparatorRange(Rule):
     """
     Remove separator matches and create matches for season range.
     """
-    priority = 128
     consequence = [RemoveMatch, AppendMatch]
 
     def __init__(self, range_separators, property_name):
@@ -520,6 +518,9 @@ class AbstractSeparatorRange(Rule):
         self.range_separators = range_separators
         self.property_name = property_name
 
+    def _can_start_range(self, match):  # pylint: disable=unused-argument
+        return True
+
     def when(self, matches, context):
         to_remove = []
         to_append = []
@@ -540,7 +541,10 @@ class AbstractSeparatorRange(Rule):
             to_remove.append(separator)
 
         previous_match = None
-        for next_match in matches.named(self.property_name):
+        sorted_matches = sorted(matches.named(self.property_name), key=lambda x: x.span[0])
+        for next_match in sorted_matches:
+            if not previous_match and not self._can_start_range(next_match):
+                continue
             if previous_match:
                 separator = matches.input_string[previous_match.initiator.end:next_match.initiator.start]
                 if separator not in self.range_separators:
@@ -578,17 +582,25 @@ class RenameToAbsoluteEpisode(Rule):
     The matches in the group with higher episode values are renamed to absolute_episode.
     """
 
-    consequence = RenameMatch('absolute_episode')
+    consequence = [RenameMatch('absolute_episode'), RemoveMatch]
 
     def when(self, matches, context):  # pylint:disable=inconsistent-return-statements
         initiators = {match.initiator for match in matches.named('episode')
                       if len(match.initiator.children.named('episode')) > 1}
         if len(initiators) != 2:
-            ret = []
+            ret = ([], [])
             for filepart in matches.markers.named('path'):
+                sxxexx_episode_matches = matches.range(filepart.start + 1, filepart.end,
+                                                       predicate=lambda m: m.name == 'episode' and
+                                                                           'SxxExx' in m.tags)
                 if matches.range(filepart.start + 1, filepart.end, predicate=lambda m: m.name == 'episode'):
-                    ret.extend(
-                        matches.starting(filepart.start, predicate=lambda m: m.initiator.name == 'weak_episode'))
+                    absolute_episode_candidate = matches.starting(filepart.start,
+                                                                  predicate=lambda
+                                                                      m: m.initiator.name == 'weak_episode')
+                    if sxxexx_episode_matches:
+                        ret[1].extend(absolute_episode_candidate)
+                    else:
+                        ret[0].extend(absolute_episode_candidate)
             return ret
 
         initiators = sorted(initiators, key=lambda item: item.end)
@@ -597,24 +609,29 @@ class RenameToAbsoluteEpisode(Rule):
             second_range = matches.named('episode', predicate=lambda m: m.initiator == initiators[1])
             if len(first_range) == len(second_range):
                 if second_range[0].value > first_range[0].value:
-                    return second_range
+                    return second_range, []
                 if first_range[0].value > second_range[0].value:
-                    return first_range
+                    return first_range, []
 
 
 class EpisodeNumberSeparatorRange(AbstractSeparatorRange):
     """
     Remove separator matches and create matches for episoderNumber range.
     """
+    priority = 128
 
     def __init__(self, range_separators):
         super().__init__(range_separators, "episode")
 
+    def _can_start_range(self, match):
+        return 'weak-episode' not in match.tags
+
 
 class SeasonSeparatorRange(AbstractSeparatorRange):
     """
     Remove separator matches and create matches for season range.
     """
+    priority = 128
 
     def __init__(self, range_separators):
         super().__init__(range_separators, "season")
@@ -728,7 +745,7 @@ class RemoveInvalidSeason(Rule):
         for filepart in matches.markers.named('path'):
             strong_season = matches.range(filepart.start, filepart.end, index=0,
                                           predicate=lambda m: m.name == 'season'
-                                          and not m.private and 'SxxExx' in m.tags)
+                                                              and not m.private and 'SxxExx' in m.tags)
             if strong_season:
                 if strong_season.initiator.children.named('episode'):
                     for season in matches.range(strong_season.end, filepart.end,
@@ -756,7 +773,7 @@ class RemoveInvalidEpisode(Rule):
         for filepart in matches.markers.named('path'):
             strong_episode = matches.range(filepart.start, filepart.end, index=0,
                                            predicate=lambda m: m.name == 'episode'
-                                           and not m.private and 'SxxExx' in m.tags)
+                                                               and not m.private and 'SxxExx' in m.tags)
             if strong_episode:
                 strong_ep_marker = RemoveInvalidEpisode.get_episode_prefix(matches, strong_episode)
                 for episode in matches.range(strong_episode.end, filepart.end,
@@ -843,7 +860,7 @@ class RemoveDetachedEpisodeNumber(Rule):
                 episode_numbers[0].value < 10 and \
                 episode_numbers[1].value - episode_numbers[0].value != 1:
             parent = episode_numbers[0]
-            while parent:  # TODO: Add a feature in rebulk to avoid this ...
+            while parent:
                 ret.append(parent)
                 parent = parent.parent
         return ret
diff --git a/guessit/rules/properties/language.py b/guessit/rules/properties/language.py
index 8a83d88..3c41e10 100644
--- a/guessit/rules/properties/language.py
+++ b/guessit/rules/properties/language.py
@@ -137,7 +137,7 @@ def length_comparator(value):
 _LanguageMatch = namedtuple('_LanguageMatch', ['property_name', 'word', 'lang'])
 
 
-class LanguageWord(object):
+class LanguageWord:
     """
     Extension to the Word namedtuple in order to create compound words.
 
@@ -191,7 +191,7 @@ def to_rebulk_match(language_match):
     }
 
 
-class LanguageFinder(object):
+class LanguageFinder:
     """
     Helper class to search and return language matches: 'language' and 'subtitle_language' properties
     """
diff --git a/guessit/rules/properties/release_group.py b/guessit/rules/properties/release_group.py
index 09e845f..4f5286c 100644
--- a/guessit/rules/properties/release_group.py
+++ b/guessit/rules/properties/release_group.py
@@ -4,6 +4,7 @@
 release_group property
 """
 import copy
+import re
 
 from rebulk import Rebulk, Rule, AppendMatch, RemoveMatch
 from rebulk.match import Match
@@ -50,7 +51,10 @@ def release_group(config):
             if string.lower().endswith(forbidden) and string[-len(forbidden) - 1:-len(forbidden)] in seps:
                 string = string[:len(forbidden)]
                 string = string.strip(groupname_seps)
-        return string.strip()
+
+        # Release groups that credit individual members often use a format like "Title (MediaInfo Individual) [Group]".
+        # This results in a group name of "Individual) [Group]", which should be transformed to "Individual Group".
+        return re.sub(r'(.+)\)\s?\[(.+)\]', r'\1 \2', string.strip())
 
     rebulk = Rebulk(disabled=lambda context: is_disabled(context, 'release_group'))
 
diff --git a/guessit/rules/properties/title.py b/guessit/rules/properties/title.py
index 1d57de1..fad8484 100644
--- a/guessit/rules/properties/title.py
+++ b/guessit/rules/properties/title.py
@@ -49,7 +49,7 @@ class TitleBaseRule(Rule):
     """
     Add title match in existing matches
     """
-    # pylint:disable=no-self-use,unused-argument
+    # pylint:disable=unused-argument
     consequence = [AppendMatch, RemoveMatch]
 
     def __init__(self, match_name, match_tags=None, alternative_match_name=None):
@@ -142,8 +142,8 @@ class TitleBaseRule(Rule):
             for outside in outside_matches:
                 other_languages.extend(matches.range(outside.start, outside.end,
                                                      lambda c_match: c_match.name == match.name and
-                                                     c_match not in to_keep and
-                                                     c_match.value not in NON_SPECIFIC_LANGUAGES))
+                                                                     c_match not in to_keep and
+                                                                     c_match.value not in NON_SPECIFIC_LANGUAGES))
 
             if not other_languages and (not starting or len(match.raw) <= 3):
                 return True
@@ -163,7 +163,8 @@ class TitleBaseRule(Rule):
             return match.start >= hole.start and match.end <= hole.end
         return True
 
-    def check_titles_in_filepart(self, filepart, matches, context):  # pylint:disable=inconsistent-return-statements
+    def check_titles_in_filepart(self, filepart, matches, context,  # pylint:disable=inconsistent-return-statements
+                                 additional_ignored=None):
         """
         Find title in filepart (ignoring language)
         """
@@ -171,7 +172,8 @@ class TitleBaseRule(Rule):
         start, end = filepart.span
 
         holes = matches.holes(start, end + 1, formatter=formatters(cleanup, reorder_title),
-                              ignore=self.is_ignored,
+                              ignore=self.is_ignored if additional_ignored is None else lambda m: self.is_ignored(
+                                  m) or additional_ignored(m),
                               predicate=lambda m: m.value)
 
         holes = self.holes_process(holes, matches)
@@ -247,8 +249,46 @@ class TitleBaseRule(Rule):
                     titles = [hole]
                 return titles, to_remove
 
+    def _serie_name_filepart(self, matches, fileparts):
+        # Try to get show title from subdirectory of a season only directory (Show Name/Season 1/episode_title.avi)
+        for index in range(len(fileparts) - 1):
+            if index == 0:
+                continue
+            filepart = fileparts[index]
+            filepart_matches = [m for m in matches.range(filepart.start, filepart.end) if not m.private]
+            if len(filepart_matches) == 1 and filepart_matches[0].name == 'season' and \
+                    (filepart_matches[0].span == filepart.span or
+                     filepart_matches[0].parent and filepart_matches[0].parent.span == filepart.span):
+                # Filepath match season match exactly
+                return fileparts[index + 1]
+        return None
+
+    def _serie_name_filepart_match(self, matches, context, serie_name_filepart, to_append, to_remove):
+        def serie_name_filepart_ignored(match):
+            for tag in match.tags:
+                if tag == 'weak' or tag.startswith('weak-'):
+                    return True
+            return False
+
+        titles = self.check_titles_in_filepart(serie_name_filepart, matches, context, serie_name_filepart_ignored)
+        if titles:
+            titles, to_remove_c = titles
+            if len(titles) == 1:
+                to_append.extend(titles)
+                to_remove.extend(to_remove_c)
+                return titles[0]
+        return None
+
+    def _year_fileparts(self, matches, fileparts):
+        year_fileparts = []
+        for filepart in fileparts:
+            year_match = matches.range(filepart.start, filepart.end, lambda match: match.name == 'year', 0)
+            if year_match:
+                year_fileparts.append(filepart)
+        return year_fileparts
+
     def when(self, matches, context):
-        ret = []
+        to_append = []
         to_remove = []
 
         if matches.named(self.match_name, lambda match: 'expected' in match.tags):
@@ -257,36 +297,43 @@ class TitleBaseRule(Rule):
         fileparts = [filepart for filepart in list(marker_sorted(matches.markers.named('path'), matches))
                      if not self.filepart_filter or self.filepart_filter(filepart, matches)]
 
-        # Priorize fileparts containing the year
-        years_fileparts = []
-        for filepart in fileparts:
-            year_match = matches.range(filepart.start, filepart.end, lambda match: match.name == 'year', 0)
-            if year_match:
-                years_fileparts.append(filepart)
+        serie_name_filepart = self._serie_name_filepart(matches, fileparts)
+
+        serie_name_filepath_match = None
+        if serie_name_filepart:
+            serie_name_filepath_match = self._serie_name_filepart_match(matches, context, serie_name_filepart,
+                                                                        to_append, to_remove)
+
+        # Force inclusion of fileparts containing the year
+        year_fileparts = self._year_fileparts(matches, fileparts)
 
         for filepart in fileparts:
             try:
-                years_fileparts.remove(filepart)
+                year_fileparts.remove(filepart)
             except ValueError:
                 pass
             titles = self.check_titles_in_filepart(filepart, matches, context)
             if titles:
                 titles, to_remove_c = titles
-                ret.extend(titles)
+                if serie_name_filepath_match:
+                    for title_match in titles:
+                        if title_match.value != serie_name_filepath_match.value:
+                            title_match.name = 'episode_title'
+                to_append.extend(titles)
                 to_remove.extend(to_remove_c)
                 break
 
         # Add title match in all fileparts containing the year.
-        for filepart in years_fileparts:
+        for filepart in year_fileparts:
             titles = self.check_titles_in_filepart(filepart, matches, context)
             if titles:
                 # pylint:disable=unbalanced-tuple-unpacking
                 titles, to_remove_c = titles
-                ret.extend(titles)
+                to_append.extend(titles)
                 to_remove.extend(to_remove_c)
 
-        if ret or to_remove:
-            return ret, to_remove
+        if to_append or to_remove:
+            return to_append, to_remove
         return False
 
 
diff --git a/guessit/test/__init__.py b/guessit/test/__init__.py
index e5be370..c5db3ab 100644
--- a/guessit/test/__init__.py
+++ b/guessit/test/__init__.py
@@ -1,3 +1,3 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-# pylint: disable=no-self-use, pointless-statement, missing-docstring, invalid-name
+# pylint: disable=pointless-statement, missing-docstring, invalid-name
diff --git a/guessit/test/episodes.yml b/guessit/test/episodes.yml
index 52e29ec..7de7da1 100644
--- a/guessit/test/episodes.yml
+++ b/guessit/test/episodes.yml
@@ -2340,7 +2340,7 @@
 
 ? /11.22.63/Season 1/11.22.63.106.hdtv-abc
 : options: -T 11.22.63
-  title: 11.22.63
+  title: 11 22 63
   season: 1
   episode: 6
   source: HDTV
@@ -4062,8 +4062,7 @@
   type: episode
 
 ? 165.Show Name.s08e014
-: absolute_episode: 165
-  title: Show Name
+: title: 165 Show Name
   season: 8
   episode: 14
   type: episode
@@ -4672,7 +4671,7 @@
   episode: 1
   screen_size: 1080p
   streaming_service: Viki
-  source: Web 
+  source: Web
   release_group: BLUEBERRY
   container: mp4
   mimetype: video/mp4
@@ -4702,7 +4701,6 @@
   video_codec: H.264
   audio_codec: FLAC
   container: mkv
-  mimetype: video/x-matroska
   type: episode
 
 ? "[EveTaku] Kyouso Giga ONA v2 [540p][128BAC43].mkv"
@@ -4713,7 +4711,6 @@
   screen_size: 540p
   crc32: 128BAC43
   container: mkv
-  mimetype: video/x-matroska
   type: episode
 
 ? '[Erai-raws] Fumetsu no Anata e - 03 [720p][Multiple Subtitle].mkv'
@@ -4723,7 +4720,6 @@
   screen_size: 720p
   subtitle_language: mul
   container: mkv
-  mimetype: video/x-matroska
   type: episode
 
 ? Mom.S06E08.Jell-O.Shots.and.the.Truth.About.Santa.1080p.AMZN.WEB-DL.DDP5.1.H.264-NTb.mkv
@@ -4739,7 +4735,6 @@
   video_codec: H.264
   release_group: NTb
   container: mkv
-  mimetype: video/x-matroska
   type: episode
 
 ? Archer.2009.S12E05.Shots.720p.HULU.WEB-DL.DDP5.1.H.264-NOGRP
@@ -4756,3 +4751,22 @@
   video_codec: H.264
   release_group: NOGRP
   type: episode
+
+? /mydatapool/mydata/Videos/Shows/C/Caprica (2008)/Season 1/Apotheosis_1920x1080.mp4
+: title: Caprica
+  year: 2008
+  season: 1
+  episode_title: Apotheosis
+  screen_size: 1080p
+  container: mp4
+  type: episode
+
+? 4400.S01E01.1080p.WEB.H264-NOGRP
+: title: '4400'
+  season: 1
+  episode: 1
+  screen_size: 1080p
+  source: Web
+  video_codec: H.264
+  release_group: NOGRP
+  type: episode
diff --git a/guessit/test/movies.yml b/guessit/test/movies.yml
index ff4232a..9fce923 100644
--- a/guessit/test/movies.yml
+++ b/guessit/test/movies.yml
@@ -1711,6 +1711,20 @@
   release_group: LAZY
   type: movie
 
+? The.Movie.2016.2160p.UHD.BluRay.REMUX.HDR.HEVC.DTS-X-NOGROUP
+: title: The Movie
+  year: 2016
+  screen_size: 2160p
+  source: Ultra HD Blu-ray
+  other:
+  - HDR10
+  - Remux
+  video_codec: H.265
+  video_profile: High Efficiency Video Coding
+  audio_codec: DTS:X
+  release_group: NOGROUP
+  type: movie
+
 ? Test (2013) [WEBDL-1080p] [x264 AC3] [ENG+RU+PT] [NTb].mkv
 : title: Test
   year: 2013
@@ -1857,5 +1871,4 @@
   video_codec: H.264
   release_group: DON
   container: mkv
-  mimetype: video/x-matroska
   type: movie
diff --git a/guessit/test/rules/__init__.py b/guessit/test/rules/__init__.py
index e5be370..c5db3ab 100644
--- a/guessit/test/rules/__init__.py
+++ b/guessit/test/rules/__init__.py
@@ -1,3 +1,3 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-# pylint: disable=no-self-use, pointless-statement, missing-docstring, invalid-name
+# pylint: disable=pointless-statement, missing-docstring, invalid-name
diff --git a/guessit/test/rules/audio_codec.yml b/guessit/test/rules/audio_codec.yml
index 1c78efc..b215751 100644
--- a/guessit/test/rules/audio_codec.yml
+++ b/guessit/test/rules/audio_codec.yml
@@ -15,11 +15,15 @@
 ? +DD
 ? +Dolby Digital
 ? +AC3
+? +AC-3
 : audio_codec: Dolby Digital
 
 ? +DDP
 ? +DD+
 ? +EAC3
+? +EAC-3
+? +E-AC-3
+? +E-AC3
 : audio_codec: Dolby Digital Plus
 
 ? +DolbyAtmos
@@ -116,6 +120,11 @@
 : audio_codec: DTS
   audio_profile: Extended Surround
 
+? DTS:X
+? DTS-X
+? DTSX
+: audio_codec: DTS:X
+
 ? DD-EX
 ? DDEX
 ? -EX
diff --git a/guessit/test/rules/edition.yml b/guessit/test/rules/edition.yml
index d1d5277..c34d606 100644
--- a/guessit/test/rules/edition.yml
+++ b/guessit/test/rules/edition.yml
@@ -31,6 +31,22 @@
 ? Super Movie Alternative Cut XViD
 : edition: Alternative Cut
 
+? Remaster
+? Remastered
+? 4k-Remaster
+? 4k-Remastered
+? 4k Remaster
+? 4k Remastered
+: edition: Remastered
+
+? Restore
+? Restored
+? 4k-Restore
+? 4k-Restored
+? 4k Restore
+? 4k Restored
+: edition: Restored
+
 ? ddc
 : edition: Director's Definitive Cut
 
diff --git a/guessit/test/rules/episodes.yml b/guessit/test/rules/episodes.yml
index 44e06a3..7eff1e7 100644
--- a/guessit/test/rules/episodes.yml
+++ b/guessit/test/rules/episodes.yml
@@ -293,7 +293,7 @@
 ? Something 4x05-06
 ? Something-4x05-06
 : options: -T something
-  title: something
+  title: Something
   season: 4
   episode:
   - 5
@@ -329,3 +329,15 @@
   year: 2010
   season: 2010
   episode: 2
+
+? Show Name - S32-Dummy 45-Ep 6478
+: title: Show Name
+  episode_title: Dummy 45
+  season: 32
+  episode: 6478
+
+? Show Name - S32-Week 45-Ep 6478
+: title: Show Name
+  season: 32
+  week: 45
+  episode: 6478
diff --git a/guessit/test/rules/processors_test.py b/guessit/test/rules/processors_test.py
index c22e968..57f04be 100644
--- a/guessit/test/rules/processors_test.py
+++ b/guessit/test/rules/processors_test.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-# pylint: disable=no-self-use, pointless-statement, missing-docstring, invalid-name, pointless-string-statement
+# pylint: disable=pointless-statement, missing-docstring, invalid-name, pointless-string-statement
 
 from rebulk.match import Matches, Match
 
diff --git a/guessit/test/rules/release_group.yml b/guessit/test/rules/release_group.yml
index c96383e..53e5f73 100644
--- a/guessit/test/rules/release_group.yml
+++ b/guessit/test/rules/release_group.yml
@@ -69,3 +69,12 @@
 : title: Show Name
   video_codec: H.264
   release_group: RiPRG
+
+? Archer (2009) S13E01 The Big Con (1080p AMZN Webrip x265 10bit EAC3 5.1 - JBENT)[TAoE]
+: release_group: JBENT TAoE
+
+? Dark Phoenix (2019) (1080p BluRay x265 HEVC 10bit AAC 7.1 Tigole) [QxR]
+: release_group: Tigole QxR
+
+? The Peripheral (2022) Season 1 S01 (1080p AMZN WEB-DL x265 HEVC 10bit DDP5.1 D0ct0rLew) [SEV]
+: release_group: D0ct0rLew SEV
\ No newline at end of file
diff --git a/guessit/test/rules/title.yml b/guessit/test/rules/title.yml
index 05c7f20..82bd91b 100644
--- a/guessit/test/rules/title.yml
+++ b/guessit/test/rules/title.yml
@@ -41,3 +41,8 @@
   episode_title: This E.P.T.I.T.L.E has dots
   type: episode
 
+? /mydatapool/mydata/Videos/Shows/C/Caprica/Season 1/Apotheosis_1920x1080.mp4
+: title: Caprica
+  episode_title: Apotheosis
+  season: 1
+  type: episode
diff --git a/guessit/test/test_api.py b/guessit/test/test_api.py
index 790de20..e559834 100644
--- a/guessit/test/test_api.py
+++ b/guessit/test/test_api.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-# pylint: disable=no-self-use, pointless-statement, missing-docstring, invalid-name, pointless-string-statement
+# pylint: disable=pointless-statement, missing-docstring, invalid-name, pointless-string-statement
 import json
 import os
 from pathlib import Path
@@ -58,7 +58,7 @@ def test_properties():
 def test_exception():
     with pytest.raises(GuessitException) as excinfo:
         guessit(object())
-    assert "An internal error has occured in guessit" in str(excinfo.value)
+    assert "An internal error has occurred in guessit" in str(excinfo.value)
     assert "Guessit Exception Report" in str(excinfo.value)
     assert "Please report at https://github.com/guessit-io/guessit/issues" in str(excinfo.value)
 
diff --git a/guessit/test/test_api_unicode_literals.py b/guessit/test/test_api_unicode_literals.py
index 79bbbca..fa6227d 100644
--- a/guessit/test/test_api_unicode_literals.py
+++ b/guessit/test/test_api_unicode_literals.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-# pylint: disable=no-self-use, pointless-statement, missing-docstring, invalid-name, pointless-string-statement
+# pylint: disable=pointless-statement, missing-docstring, invalid-name, pointless-string-statement
 
 
 import os
@@ -60,6 +60,6 @@ def test_properties():
 def test_exception():
     with pytest.raises(GuessitException) as excinfo:
         guessit(object())
-    assert "An internal error has occured in guessit" in str(excinfo.value)
+    assert "An internal error has occurred in guessit" in str(excinfo.value)
     assert "Guessit Exception Report" in str(excinfo.value)
     assert "Please report at https://github.com/guessit-io/guessit/issues" in str(excinfo.value)
diff --git a/guessit/test/test_benchmark.py b/guessit/test/test_benchmark.py
index 34386e3..0c561ee 100644
--- a/guessit/test/test_benchmark.py
+++ b/guessit/test/test_benchmark.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-# pylint: disable=no-self-use,pointless-statement,missing-docstring,invalid-name,line-too-long
+# pylint: disable=pointless-statement,missing-docstring,invalid-name,line-too-long
 import time
 
 import pytest
@@ -34,7 +34,7 @@ def case4():
     warmup=False
 )
 @pytest.mark.skipif(True, reason="Disabled")
-class TestBenchmark(object):
+class TestBenchmark:
     def test_case1(self, benchmark):
         ret = benchmark(case1)
         assert ret
diff --git a/guessit/test/test_main.py b/guessit/test/test_main.py
index a34cd4a..90b8169 100644
--- a/guessit/test/test_main.py
+++ b/guessit/test/test_main.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-# pylint: disable=no-self-use, pointless-statement, missing-docstring, invalid-name
+# pylint: disable=pointless-statement, missing-docstring, invalid-name
 import json
 import os
 import sys
diff --git a/guessit/test/test_options.py b/guessit/test/test_options.py
index 4f019b3..de13180 100644
--- a/guessit/test/test_options.py
+++ b/guessit/test/test_options.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-# pylint: disable=no-self-use, pointless-statement, missing-docstring, invalid-name, pointless-string-statement
+# pylint: disable=pointless-statement, missing-docstring, invalid-name, pointless-string-statement
 import os
 
 import pytest
diff --git a/guessit/test/test_yml.py b/guessit/test/test_yml.py
index 53d7f67..6adced1 100644
--- a/guessit/test/test_yml.py
+++ b/guessit/test/test_yml.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-# pylint: disable=no-self-use, pointless-statement, missing-docstring, invalid-name
+# pylint: disable=pointless-statement, missing-docstring, invalid-name
 import logging
 import os
 
@@ -18,7 +18,7 @@ logger = logging.getLogger(__name__)
 __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
 
 
-class EntryResult(object):
+class EntryResult:
     def __init__(self, string, negates=False):
         self.string = string
         self.negates = negates
@@ -114,7 +114,7 @@ def files_and_ids(predicate=None):
     return files, ids
 
 
-class TestYml(object):
+class TestYml:
     """
     Run tests from yaml files.
     Multiple input strings having same expected results can be chained.
diff --git a/mkdocs.yml b/mkdocs.yml
index 46698b6..a81600c 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -1,6 +1,6 @@
 site_name: GuessIt
 
-site_url: https://guessit.io
+site_url: https://guessit-io.github.io/guessit
 site_description: GuessIt is a python library that extracts as much information as possible from a video filename.
 site_author: Rémi Alvergnat <toilal.dev@gmail.com>
 
diff --git a/pylintrc b/pylintrc
index cd5d0f9..3bf03c8 100644
--- a/pylintrc
+++ b/pylintrc
@@ -1,388 +1,619 @@
-[MASTER]
+[MAIN]
 
-# Specify a configuration file.
-#rcfile=
+# Analyse import fallback blocks. This can be used to support both Python 2 and
+# 3 compatible code, which means that the block might have code that exists
+# only in one or another interpreter, leading to false positives when analysed.
+analyse-fallback-blocks=no
+
+# Load and enable all available extensions. Use --list-extensions to see a list
+# all available extensions.
+#enable-all-extensions=
+
+# In error mode, messages with a category besides ERROR or FATAL are
+# suppressed, and no reports are done by default. Error mode is compatible with
+# disabling specific errors.
+#errors-only=
+
+# Always return a 0 (non-error) status code, even if lint errors are found.
+# This is primarily useful in continuous integration scripts.
+#exit-zero=
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code.
+extension-pkg-allow-list=
+
+# A comma-separated list of package or module names from where C extensions may
+# be loaded. Extensions are loading into the active Python interpreter and may
+# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
+# for backward compatibility.)
+extension-pkg-whitelist=
+
+# Return non-zero exit code if any of these messages/categories are detected,
+# even if score is above --fail-under value. Syntax same as enable. Messages
+# specified are enabled, while categories only check already-enabled messages.
+fail-on=
+
+# Specify a score threshold under which the program will exit with error.
+fail-under=10
+
+# Interpret the stdin as a python script, whose filename needs to be passed as
+# the module_or_package argument.
+#from-stdin=
+
+# Files or directories to be skipped. They should be base names, not paths.
+ignore=CVS
+
+# Add files or directories matching the regular expressions patterns to the
+# ignore-list. The regex matches against paths and can be in Posix or Windows
+# format. Because '\' represents the directory delimiter on Windows systems, it
+# can't be used as an escape character.
+ignore-paths=
+
+# Files or directories matching the regular expression patterns are skipped.
+# The regex matches against base names, not paths. The default value ignores
+# Emacs file locks
+ignore-patterns=^\.#
+
+# List of module names for which member attributes should not be checked
+# (useful for modules/projects where namespaces are manipulated during runtime
+# and thus existing member attributes cannot be deduced by static analysis). It
+# supports qualified module names, as well as Unix pattern matching.
+ignored-modules=
 
 # Python code to execute, usually for sys.path manipulation such as
 # pygtk.require().
 #init-hook=
 
-# Add files or directories to the blacklist. They should be base names, not
-# paths.
-ignore=CVS
+# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
+# number of processors available to use, and will cap the count on Windows to
+# avoid hangs.
+jobs=1
 
-# Pickle collected data for later comparisons.
-persistent=yes
+# Control the amount of potential inferred values when inferring a single
+# object. This can help the performance when dealing with large functions or
+# complex, nested conditions.
+limit-inference-results=100
 
-# List of plugins (as comma separated values of python modules names) to load,
+# List of plugins (as comma separated values of python module names) to load,
 # usually to register additional checkers.
 load-plugins=
 
-# Use multiple processes to speed up Pylint.
-jobs=1
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# Minimum Python version to use for version dependent checks. Will default to
+# the version used to run pylint.
+py-version=3.9
+
+# Discover python modules and packages in the file system subtree.
+recursive=no
+
+# When enabled, pylint would attempt to guess common misconfiguration and emit
+# user-friendly hints instead of false-positive error messages.
+suggestion-mode=yes
 
 # Allow loading of arbitrary C extensions. Extensions are imported into the
 # active Python interpreter and may run arbitrary code.
 unsafe-load-any-extension=no
 
-# A comma-separated list of package or module names from where C extensions may
-# be loaded. Extensions are loading into the active Python interpreter and may
-# run arbitrary code
-extension-pkg-whitelist=
+# In verbose mode, extra non-checker-related info will be displayed.
+#verbose=
 
-# Allow optimization of some AST trees. This will activate a peephole AST
-# optimizer, which will apply various small optimizations. For instance, it can
-# be used to obtain the result of joining multiple strings with the addition
-# operator. Joining a lot of strings can lead to a maximum recursion error in
-# Pylint and this flag can prevent that. It has one side effect, the resulting
-# AST will be different than the one from reality.
-optimize-ast=no
 
+[BASIC]
 
-[MESSAGES CONTROL]
+# Naming style matching correct argument names.
+argument-naming-style=snake_case
 
-# Only show warnings with the listed confidence levels. Leave empty to show
-# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
-confidence=
+# Regular expression matching correct argument names. Overrides argument-
+# naming-style. If left empty, argument names will be checked with the set
+# naming style.
+#argument-rgx=
 
-# Enable the message, report, category or checker with the given id(s). You can
-# either give multiple identifier separated by comma (,) or put this option
-# multiple time. See also the "--disable" option for examples.
-#enable=
+# Naming style matching correct attribute names.
+attr-naming-style=snake_case
 
-# Disable the message, report, category or checker with the given id(s). You
-# can either give multiple identifiers separated by comma (,) or put this
-# option multiple times (only on the command line, not in the configuration
-# file where it should appear only once).You can also use "--disable=all" to
-# disable everything first and then reenable specific checks. For example, if
-# you want to run only the similarities checker, you can use "--disable=all
-# --enable=similarities". If you want to run only the classes checker, but have
-# no Warning level messages displayed, use"--disable=all --enable=classes
-# --disable=W"
-disable=unichr-builtin,backtick,delslice-method,indexing-exception,execfile-builtin,map-builtin-not-iterating,
-    intern-builtin,coerce-method,long-builtin,cmp-method,useless-suppression,range-builtin-not-iterating,
-    metaclass-assignment,filter-builtin-not-iterating,next-method-called,parameter-unpacking,xrange-builtin,long-suffix,
-    setslice-method,zip-builtin-not-iterating,suppressed-message,no-absolute-import,dict-iter-method,raw_input-builtin,
-    standarderror-builtin,using-cmp-argument,input-builtin,hex-method,unicode-builtin,reduce-builtin,old-octal-literal,
-    dict-view-method,old-ne-operator,coerce-builtin,cmp-builtin,old-raise-syntax,getslice-method,print-statement,
-    unpacking-in-except,import-star-module-level,buffer-builtin,round-builtin,file-builtin,reload-builtin,old-division,
-    apply-builtin,oct-method,nonzero-method,basestring-builtin,raising-string,too-few-public-methods,too-many-arguments,
-    too-many-instance-attributes,bad-builtin,too-many-ancestors,too-few-format-args,fixme,duplicate-code,
-    deprecated-lambda,too-many-nested-blocks,useless-object-inheritance,import-outside-toplevel,
-    I
+# Regular expression matching correct attribute names. Overrides attr-naming-
+# style. If left empty, attribute names will be checked with the set naming
+# style.
+#attr-rgx=
 
+# Bad variable names which should always be refused, separated by a comma.
+bad-names=foo,
+          bar,
+          baz,
+          toto,
+          tutu,
+          tata
 
-[REPORTS]
+# Bad variable names regexes, separated by a comma. If names match any regex,
+# they will always be refused
+bad-names-rgxs=
 
-# Set the output format. Available formats are text, parseable, colorized, msvs
-# (visual studio) and html. You can also give a reporter class, eg
-# mypackage.mymodule.MyReporterClass.
-output-format=colorized
+# Naming style matching correct class attribute names.
+class-attribute-naming-style=any
 
-# Put messages in a separate file for each module / package specified on the
-# command line instead of printing them on stdout. Reports (if any) will be
-# written in a file name "pylint_global.[txt|html]".
-files-output=no
+# Regular expression matching correct class attribute names. Overrides class-
+# attribute-naming-style. If left empty, class attribute names will be checked
+# with the set naming style.
+#class-attribute-rgx=
 
-# Tells whether to display a full report or only the messages
-reports=no
+# Naming style matching correct class constant names.
+class-const-naming-style=snake_case
 
-# Python expression which should return a note less than 10 (10 is the highest
-# note). You have access to the variables errors warning, statement which
-# respectively contain the number of errors / warnings messages and the total
-# number of statements analyzed. This is used by the global evaluation report
-# (RP0004).
-evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+# Regular expression matching correct class constant names. Overrides class-
+# const-naming-style. If left empty, class constant names will be checked with
+# the set naming style.
+#class-const-rgx=
 
-# Template used to display messages. This is a python new-style format string
-# used to format the message information. See doc for all details
-#msg-template=
+# Naming style matching correct class names.
+class-naming-style=PascalCase
 
+# Regular expression matching correct class names. Overrides class-naming-
+# style. If left empty, class names will be checked with the set naming style.
+#class-rgx=
 
-[FORMAT]
+# Naming style matching correct constant names.
+const-naming-style=snake_case
 
-# Maximum number of characters on a single line.
-max-line-length=120
+# Regular expression matching correct constant names. Overrides const-naming-
+# style. If left empty, constant names will be checked with the set naming
+# style.
+#const-rgx=
 
-# Regexp for a line that is allowed to be longer than the limit.
-ignore-long-lines=^\s*(# )?<?https?://\S+>?$
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=-1
 
-# Allow the body of an if to be on the same line as the test if there is no
-# else.
-single-line-if-stmt=no
+# Naming style matching correct function names.
+function-naming-style=snake_case
 
-# List of optional constructs for which whitespace checking is disabled. `dict-
-# separator` is used to allow tabulation in dicts, etc.: {1  : 1,\n222: 2}.
-# `trailing-comma` allows a space between comma and closing bracket: (a, ).
-# `empty-line` allows space-only lines.
-no-space-check=trailing-comma,dict-separator
+# Regular expression matching correct function names. Overrides function-
+# naming-style. If left empty, function names will be checked with the set
+# naming style.
+#function-rgx=
 
-# Maximum number of lines in a module
-max-module-lines=1000
+# Good variable names which should always be accepted, separated by a comma.
+good-names=i,
+           j,
+           k,
+           ex,
+           Run,
+           _
 
-# String used as indentation unit. This is usually "    " (4 spaces) or "\t" (1
-# tab).
-indent-string='    '
+# Good variable names regexes, separated by a comma. If names match any regex,
+# they will always be accepted
+good-names-rgxs=
 
-# Number of spaces of indent required inside a hanging  or continued line.
-indent-after-paren=4
+# Include a hint for the correct naming format with invalid-name.
+include-naming-hint=no
 
-# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
-expected-line-ending-format=
+# Naming style matching correct inline iteration names.
+inlinevar-naming-style=any
 
+# Regular expression matching correct inline iteration names. Overrides
+# inlinevar-naming-style. If left empty, inline iteration names will be checked
+# with the set naming style.
+#inlinevar-rgx=
 
-[TYPECHECK]
+# Naming style matching correct method names.
+method-naming-style=snake_case
 
-# Tells whether missing members accessed in mixin class should be ignored. A
-# mixin class is detected if its name ends with "mixin" (case insensitive).
-ignore-mixin-members=yes
+# Regular expression matching correct method names. Overrides method-naming-
+# style. If left empty, method names will be checked with the set naming style.
+#method-rgx=
 
-# List of module names for which member attributes should not be checked
-# (useful for modules/projects where namespaces are manipulated during runtime
-# and thus existing member attributes cannot be deduced by static analysis. It
-# supports qualified module names, as well as Unix pattern matching.
-ignored-modules=
+# Naming style matching correct module names.
+module-naming-style=snake_case
 
-# List of classes names for which member attributes should not be checked
-# (useful for classes with attributes dynamically set). This supports can work
-# with qualified names.
-ignored-classes=
+# Regular expression matching correct module names. Overrides module-naming-
+# style. If left empty, module names will be checked with the set naming style.
+#module-rgx=
 
-# List of members which are set dynamically and missed by pylint inference
-# system, and so shouldn't trigger E1101 when accessed. Python regular
-# expressions are accepted.
-generated-members=
+# Colon-delimited sets of names that determine each other's naming style when
+# the name regexes allow several styles.
+name-group=
 
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=^_
 
-[BASIC]
+# List of decorators that produce properties, such as abc.abstractproperty. Add
+# to this list to register other decorators that produce valid properties.
+# These decorators are taken in consideration only for invalid-name.
+property-classes=abc.abstractproperty
 
-# List of builtins function names that should not be used, separated by a comma
-bad-functions=map,filter
+# Regular expression matching correct type variable names. If left empty, type
+# variable names will be checked with the set naming style.
+#typevar-rgx=
 
-# Good variable names which should always be accepted, separated by a comma
-good-names=i,j,k,ex,Run,_,cd
+# Naming style matching correct variable names.
+variable-naming-style=snake_case
 
-# Bad variable names which should always be refused, separated by a comma
-bad-names=foo,bar,baz,toto,tutu,tata
+# Regular expression matching correct variable names. Overrides variable-
+# naming-style. If left empty, variable names will be checked with the set
+# naming style.
+#variable-rgx=
 
-# Colon-delimited sets of names that determine each other's naming style when
-# the name regexes allow several styles.
-name-group=
 
-# Include a hint for the correct naming format with invalid-name
-include-naming-hint=no
+[CLASSES]
 
-# Regular expression matching correct class attribute names
-class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+# Warn about protected attribute access inside special methods
+check-protected-access-in-special-methods=no
 
-# Naming hint for class attribute names
-class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,
+                      __new__,
+                      setUp,
+                      __post_init__
 
-# Regular expression matching correct function names
-function-rgx=[a-z_][a-z0-9_]{2,30}$
+# List of member names, which should be excluded from the protected access
+# warning.
+exclude-protected=_asdict,
+                  _fields,
+                  _replace,
+                  _source,
+                  _make
 
-# Naming hint for function names
-function-name-hint=[a-z_][a-z0-9_]{2,30}$
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
 
-# Regular expression matching correct constant names
-const-rgx=(([a-zA-Z_][a-zA-Z0-9_]*)|(__.*__))$
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=cls
 
-# Naming hint for constant names
-const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
 
-# Regular expression matching correct module names
-module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+[DESIGN]
 
-# Naming hint for module names
-module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+# List of regular expressions of class ancestor names to ignore when counting
+# public methods (see R0903)
+exclude-too-few-public-methods=
 
-# Regular expression matching correct variable names
-variable-rgx=[a-z_][a-z0-9_]{2,30}$
+# List of qualified class names to ignore when counting class parents (see
+# R0901)
+ignored-parents=
 
-# Naming hint for variable names
-variable-name-hint=[a-z_][a-z0-9_]{2,30}$
+# Maximum number of arguments for function / method.
+max-args=8
 
-# Regular expression matching correct method names
-method-rgx=[a-z_][a-z0-9_]{2,30}$
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
 
-# Naming hint for method names
-method-name-hint=[a-z_][a-z0-9_]{2,30}$
+# Maximum number of boolean expressions in an if statement (see R0916).
+max-bool-expr=5
 
-# Regular expression matching correct class names
-class-rgx=[A-Z_][a-zA-Z0-9]+$
+# Maximum number of branch for function / method body.
+max-branches=12
 
-# Naming hint for class names
-class-name-hint=[A-Z_][a-zA-Z0-9]+$
+# Maximum number of locals for function / method body.
+max-locals=15
 
-# Regular expression matching correct inline iteration names
-inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+# Maximum number of parents for a class (see R0901).
+max-parents=10
 
-# Naming hint for inline iteration names
-inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
 
-# Regular expression matching correct argument names
-argument-rgx=[a-z_][a-z0-9_]{2,30}$
+# Maximum number of return / yield for function / method body.
+max-returns=6
 
-# Naming hint for argument names
-argument-name-hint=[a-z_][a-z0-9_]{2,30}$
+# Maximum number of statements in function / method body.
+max-statements=50
 
-# Regular expression matching correct attribute names
-attr-rgx=[a-z_][a-z0-9_]{2,30}$
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=1
 
-# Naming hint for attribute names
-attr-name-hint=[a-z_][a-z0-9_]{2,30}$
 
-# Regular expression which should only match function or class names that do
-# not require a docstring.
-no-docstring-rgx=^_
+[EXCEPTIONS]
 
-# Minimum line length for functions/classes that require docstrings, shorter
-# ones are exempt.
-docstring-min-length=-1
+# Exceptions that will emit a warning when caught.
+overgeneral-exceptions=builtins.BaseException,
+                       builtins.Exception
 
 
-[ELIF]
+[FORMAT]
 
-# Maximum number of nested blocks for function / method body
-max-nested-blocks=5
+# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
+expected-line-ending-format=
 
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )?<?https?://\S+>?$
 
-[SPELLING]
+# Number of spaces of indent required inside a hanging or continued line.
+indent-after-paren=4
 
-# Spelling dictionary name. Available dictionaries: none. To make it working
-# install python-enchant package.
-spelling-dict=
+# String used as indentation unit. This is usually "    " (4 spaces) or "\t" (1
+# tab).
+indent-string='    '
 
-# List of comma separated words that should not be checked.
-spelling-ignore-words=
+# Maximum number of characters on a single line.
+max-line-length=120
 
-# A path to a file that contains private dictionary; one word per line.
-spelling-private-dict-file=
+# Maximum number of lines in a module.
+max-module-lines=1000
 
-# Tells whether to store unknown words to indicated private dictionary in
-# --spelling-private-dict-file option instead of raising a message.
-spelling-store-unknown-words=no
+# Allow the body of a class to be on the same line as the declaration if body
+# contains single statement.
+single-line-class-stmt=no
 
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=no
 
-[VARIABLES]
 
-# Tells whether we should check for unused import in __init__ files.
-init-import=no
+[IMPORTS]
 
-# A regular expression matching the name of dummy variables (i.e. expectedly
-# not used).
-dummy-variables-rgx=_$|dummy
+# List of modules that can be imported at any level, not just the top level
+# one.
+allow-any-import-level=
 
-# List of additional names supposed to be defined in builtins. Remember that
-# you should avoid to define new builtins when possible.
-additional-builtins=
+# Allow wildcard imports from modules that define __all__.
+allow-wildcard-with-all=no
 
-# List of strings which can identify a callback function by name. A callback
-# name must start or end with one of those strings.
-callbacks=cb_,_cb
+# Deprecated modules which should not be used, separated by a comma.
+deprecated-modules=
+
+# Output a graph (.gv or any supported image format) of external dependencies
+# to the given file (report RP0402 must not be disabled).
+ext-import-graph=
+
+# Output a graph (.gv or any supported image format) of all (i.e. internal and
+# external) dependencies to the given file (report RP0402 must not be
+# disabled).
+import-graph=
+
+# Output a graph (.gv or any supported image format) of internal dependencies
+# to the given file (report RP0402 must not be disabled).
+int-import-graph=
+
+# Force import order to recognize a module as part of the standard
+# compatibility libraries.
+known-standard-library=
+
+# Force import order to recognize a module as part of a third party library.
+known-third-party=enchant
+
+# Couples of modules and preferred modules, separated by a comma.
+preferred-modules=
 
 
 [LOGGING]
 
+# The type of string formatting that logging methods do. `old` means using %
+# formatting, `new` is for `{}` formatting.
+logging-format-style=old
+
 # Logging modules to check that the string format arguments are in logging
-# function parameter format
+# function parameter format.
 logging-modules=logging
 
 
+[MESSAGES CONTROL]
+
+# Only show warnings with the listed confidence levels. Leave empty to show
+# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE,
+# UNDEFINED.
+confidence=HIGH,
+           CONTROL_FLOW,
+           INFERENCE,
+           INFERENCE_FAILURE,
+           UNDEFINED
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once). You can also use "--disable=all" to
+# disable everything first and then re-enable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use "--disable=all --enable=classes
+# --disable=W".
+disable=raw-checker-failed,
+        bad-inline-option,
+        locally-disabled,
+        file-ignored,
+        suppressed-message,
+        useless-suppression,
+        deprecated-pragma,
+        use-symbolic-message-instead,
+        duplicate-code
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once). See also the "--disable" option for examples.
+enable=c-extension-no-member
+
+
+[METHOD_ARGS]
+
+# List of qualified names (i.e., library.method) which require a timeout
+# parameter e.g. 'requests.api.get,requests.api.post'
+timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,
+      XXX,
+      TODO
+
+# Regular expression of note tags to take in consideration.
+notes-rgx=
+
+
+[REFACTORING]
+
+# Maximum number of nested blocks for function / method body
+max-nested-blocks=8
+
+# Complete name of functions that never returns. When checking for
+# inconsistent-return-statements if a never returning function is called then
+# it will be considered as an explicit return statement and no message will be
+# printed.
+never-returning-functions=sys.exit,argparse.parse_error
+
+
+[REPORTS]
+
+# Python expression which should return a score less than or equal to 10. You
+# have access to the variables 'fatal', 'error', 'warning', 'refactor',
+# 'convention', and 'info' which contain the number of messages in each
+# category, as well as 'statement' which is the total number of statements
+# analyzed. This score is used by the global evaluation report (RP0004).
+evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details.
+msg-template=
+
+# Set the output format. Available formats are text, parseable, colorized, json
+# and msvs (visual studio). You can also give a reporter class, e.g.
+# mypackage.mymodule.MyReporterClass.
+#output-format=
+
+# Tells whether to display a full report or only the messages.
+reports=no
+
+# Activate the evaluation score.
+score=yes
+
+
 [SIMILARITIES]
 
+# Comments are removed from the similarity computation
+ignore-comments=yes
+
+# Docstrings are removed from the similarity computation
+ignore-docstrings=yes
+
+# Imports are removed from the similarity computation
+ignore-imports=yes
+
+# Signatures are removed from the similarity computation
+ignore-signatures=yes
+
 # Minimum lines number of a similarity.
 min-similarity-lines=4
 
-# Ignore comments when computing similarities.
-ignore-comments=yes
 
-# Ignore docstrings when computing similarities.
-ignore-docstrings=yes
+[SPELLING]
 
-# Ignore imports when computing similarities.
-ignore-imports=no
+# Limits count of emitted suggestions for spelling mistakes.
+max-spelling-suggestions=4
 
+# Spelling dictionary name. Available dictionaries: none. To make it work,
+# install the 'python-enchant' package.
+spelling-dict=
 
-[MISCELLANEOUS]
+# List of comma separated words that should be considered directives if they
+# appear at the beginning of a comment and should not be checked.
+spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
 
-# List of note tags to take in consideration, separated by a comma.
-notes=FIXME,XXX,TODO
+# List of comma separated words that should not be checked.
+spelling-ignore-words=
 
+# A path to a file that contains the private dictionary; one word per line.
+spelling-private-dict-file=
 
-[DESIGN]
+# Tells whether to store unknown words to the private dictionary (see the
+# --spelling-private-dict-file option) instead of raising a message.
+spelling-store-unknown-words=no
 
-# Maximum number of arguments for function / method
-max-args=5
 
-# Argument names that match this expression will be ignored. Default to name
-# with leading underscore
-ignored-argument-names=_.*
+[STRING]
 
-# Maximum number of locals for function / method body
-max-locals=15
+# This flag controls whether inconsistent-quotes generates a warning when the
+# character used as a quote delimiter is used inconsistently within a module.
+check-quote-consistency=no
 
-# Maximum number of return / yield for function / method body
-max-returns=6
+# This flag controls whether the implicit-str-concat should generate a warning
+# on implicit string concatenation in sequences defined over several lines.
+check-str-concat-over-line-jumps=no
 
-# Maximum number of branch for function / method body
-max-branches=12
 
-# Maximum number of statements in function / method body
-max-statements=50
+[TYPECHECK]
 
-# Maximum number of parents for a class (see R0901).
-max-parents=7
+# List of decorators that produce context managers, such as
+# contextlib.contextmanager. Add to this list to register other decorators that
+# produce valid context managers.
+contextmanager-decorators=contextlib.contextmanager
 
-# Maximum number of attributes for a class (see R0902).
-max-attributes=7
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E1101 when accessed. Python regular
+# expressions are accepted.
+generated-members=
 
-# Minimum number of public methods for a class (see R0903).
-min-public-methods=2
+# Tells whether to warn about missing members when the owner of the attribute
+# is inferred to be None.
+ignore-none=yes
 
-# Maximum number of public methods for a class (see R0904).
-max-public-methods=20
+# This flag controls whether pylint should warn about no-member and similar
+# checks whenever an opaque object is returned when inferring. The inference
+# can return multiple potential results while evaluating a Python object, but
+# some branches might not be evaluated, which results in partial inference. In
+# that case, it might be useful to still emit no-member and other checks for
+# the rest of the inferred objects.
+ignore-on-opaque-inference=yes
 
-# Maximum number of boolean expressions in a if statement
-max-bool-expr=5
+# List of symbolic message names to ignore for Mixin members.
+ignored-checks-for-mixins=no-member,
+                          not-async-context-manager,
+                          not-context-manager,
+                          attribute-defined-outside-init
 
+# List of class names for which member attributes should not be checked (useful
+# for classes with dynamically set attributes). This supports the use of
+# qualified names.
+ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace
 
-[IMPORTS]
+# Show a hint with possible names when a member name was not found. The aspect
+# of finding the hint is based on edit distance.
+missing-member-hint=yes
 
-# Deprecated modules which should not be used, separated by a comma
-deprecated-modules=optparse
+# The minimum edit distance a name should have in order to be considered a
+# similar match for a missing member name.
+missing-member-hint-distance=1
 
-# Create a graph of every (i.e. internal and external) dependencies in the
-# given file (report RP0402 must not be disabled)
-import-graph=
+# The total number of similar names that should be taken in consideration when
+# showing a hint for a missing member.
+missing-member-max-choices=1
 
-# Create a graph of external dependencies in the given file (report RP0402 must
-# not be disabled)
-ext-import-graph=
+# Regex pattern to define which classes are considered mixins.
+mixin-class-rgx=.*[Mm]ixin
 
-# Create a graph of internal dependencies in the given file (report RP0402 must
-# not be disabled)
-int-import-graph=
+# List of decorators that change the signature of a decorated function.
+signature-mutators=
 
 
-[CLASSES]
+[VARIABLES]
 
-# List of method names used to declare (i.e. assign) instance attributes.
-defining-attr-methods=__init__,__new__,setUp
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid defining new builtins when possible.
+additional-builtins=
 
-# List of valid names for the first argument in a class method.
-valid-classmethod-first-arg=cls
+# Tells whether unused global variables should be treated as a violation.
+allow-global-unused-variables=yes
 
-# List of valid names for the first argument in a metaclass class method.
-valid-metaclass-classmethod-first-arg=mcs
+# List of names allowed to shadow builtins
+allowed-redefined-builtins=
 
-# List of member names, which should be excluded from the protected access
-# warning.
-exclude-protected=_asdict,_fields,_replace,_source,_make
+# List of strings which can identify a callback function by name. A callback
+# name must start or end with one of those strings.
+callbacks=cb_,
+          _cb
 
+# A regular expression matching the name of dummy variables (i.e. expected to
+# not be used).
+dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
 
-[EXCEPTIONS]
+# Argument names that match this expression will be ignored.
+ignored-argument-names=_.*|^ignored_|^unused_
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
 
-# Exceptions that will emit a warning when being caught. Defaults to
-# "Exception"
-overgeneral-exceptions=Exception
+# List of qualified module names which can have objects that can redefine
+# builtins.
+redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
diff --git a/setup.py b/setup.py
index 222d738..53a9e6d 100644
--- a/setup.py
+++ b/setup.py
@@ -15,7 +15,7 @@ with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f:
 with io.open(os.path.join(here, 'CHANGELOG.md'), encoding='utf-8') as f:
     changelog = f.read()
 
-install_requires = ['rebulk>=3.1.0', 'babelfish>=0.6.0', 'python-dateutil', 'importlib-resources;python_version<"3.9"']
+install_requires = ['rebulk>=3.2.0', 'babelfish>=0.6.0', 'python-dateutil', 'importlib-resources;python_version<"3.9"']
 
 dev_require = ['tox', 'mkdocs', 'mkdocs-material', 'pyinstaller', 'python-semantic-release']
 
@@ -43,18 +43,18 @@ args = dict(name='guessit',
                          'Operating System :: OS Independent',
                          'Intended Audience :: Developers',
                          'Programming Language :: Python :: 3',
-                         'Programming Language :: Python :: 3.6',
                          'Programming Language :: Python :: 3.7',
                          'Programming Language :: Python :: 3.8',
                          'Programming Language :: Python :: 3.9',
                          'Programming Language :: Python :: 3.10',
+                         'Programming Language :: Python :: 3.11',
                          'Topic :: Multimedia',
                          'Topic :: Software Development :: Libraries :: Python Modules'
                          ],
             keywords='python library release parser name filename movies series episodes animes',
             author='Rémi Alvergnat',
             author_email='toilal.dev@gmail.com',
-            url='https://guessit.io',
+            url='https://guessit-io.github.io/guessit',
             download_url='https://pypi.python.org/packages/source/g/guessit/guessit-%s.tar.gz' % version,
             license='LGPLv3',
             packages=find_packages(),
diff --git a/tox.ini b/tox.ini
index 9998292..85a0575 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py36,py37,py38,py39,py310,pypy3
+envlist = py37,py38,py39,py310,py311,pypy3.8,pypy3.9
 
 [testenv]
 commands =

More details

Full run details

Historical runs