Repository: Diaoul/subliminal (python)

Issue:
Video is guessed by transforming to absolute path
This was a change introduced by #1084. 

When calling:

```shell
subliminal -l en 1x1.avi
```

"1x1.avi" is transformed to an absolute path, to "/<current folder>/1x1.avi". As `guessit` uses the path and not only the file name to guess the video information, it will use "<current folder>" to guess, which is not always what is wanted.


Pull request description:
CLI: add --use-absolute-path to choose if path is converted to absolute
closes #1216 

Patch:
diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml
index 5dc7578..1d21319 100644
--- a/.github/workflows/CI.yaml
+++ b/.github/workflows/CI.yaml
@@ -126,31 +126,10 @@ jobs:
         with:
           persist-credentials: false
           fetch-depth: 0
-      - name: Install other test software (on Linux only)
-        uses: awalsh128/cache-apt-pkgs-action@v1
-        with:
-          packages: rar
-          version: 1.0
-      - uses: actions/setup-python@v5
-        with:
-          python-version-file: .python-version-default
-          cache: pip
-      - name: Install dependencies
-        run: |
-          python -Im pip install --upgrade pip
-          python -Im pip install tox requests
-      - name: Download tests data
-        run: |
-          tox -e prepare-tests
-      - name: Upload files for test
-        uses: actions/upload-artifact@v4
-        with:
-          name: tests-data
-          path: tests/data/
-          include-hidden-files: false
-          if-no-files-found: error
-          # heavy artifact should be deleted asap
-          retention-days: 1
+      - name: Prepare tests
+        uses: ./.github/actions/prepare-tests
+      - name: Display downloaded files
+        run: ls -R tests/data
 
   test:
     name: Test
@@ -170,11 +149,8 @@ jobs:
         with:
           python-version: ${{ matrix.py }}
           allow-prereleases: true
-      - uses: actions/download-artifact@v4
-        with:
-          name: tests-data
-          path: tests/data/
-          merge-multiple: true
+      - name: Prepare tests
+        uses: ./.github/actions/prepare-tests
       - name: Display downloaded files
         run: ls -R tests/data
 
@@ -278,11 +254,10 @@ jobs:
         id: setup
         with:
           python-version-file: .python-version-default
-      - uses: actions/download-artifact@v4
-        with:
-          name: tests-data
-          path: tests/data/
-          merge-multiple: true
+      - name: Prepare tests
+        uses: ./.github/actions/prepare-tests
+      - name: Display downloaded files
+        run: ls -R tests/data
       - name: Install hatch
         run: python -Im pip install hatch
 
@@ -327,7 +302,7 @@ jobs:
 
     needs:
       - coverage
-      # - core-coverage
+      - core-coverage
       - docs
       - install-dev
       - typing
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index de41142..2c34c7c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -8,19 +8,19 @@ repos:
       - id: end-of-file-fixer
 
   - repo: https://github.com/crate-ci/typos
-    rev: typos-dict-v0.11.35
+    rev: v1.30.3
     hooks:
       - id: typos
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.8.0
+    rev: v0.11.2
     hooks:
       - id: ruff
         args: [--fix, --unsafe-fixes]
       - id: ruff-format
 
   - repo: https://github.com/abravalheri/validate-pyproject
-    rev: v0.23
+    rev: v0.24.1
     hooks:
       - id: validate-pyproject
         name: validate-pyproject
diff --git a/changelog.d/1216.change.rst b/changelog.d/1216.change.rst
deleted file mode 100644
index 14e96a3..0000000
--- a/changelog.d/1216.change.rst
+++ /dev/null
@@ -1,1 +0,0 @@
-do not convert path to absolute path
diff --git a/changelog.d/1216.cli.rst b/changelog.d/1216.cli.rst
new file mode 100644
index 0000000..94c1e48
--- /dev/null
+++ b/changelog.d/1216.cli.rst
@@ -0,0 +1,2 @@
+Add a CLI option `--use-absolute-path` that can take the values 'fallback' (default), 'never' or 'always'
+to choose if the given path is transformed to an absolute path before guessing information from the path.
diff --git a/changelog.d/1243.misc.rst b/changelog.d/1243.misc.rst
new file mode 100644
index 0000000..debf268
--- /dev/null
+++ b/changelog.d/1243.misc.rst
@@ -0,0 +1,1 @@
+Add Subtitle.set_content method and use it instead of Subtitle.content property setter
diff --git a/docs/user/usage.rst b/docs/user/usage.rst
index ffc5198..12ab665 100644
--- a/docs/user/usage.rst
+++ b/docs/user/usage.rst
@@ -93,7 +93,8 @@ Here video information was guessed based on the name of the video, you can acces
     >>> video.release_group
     'LOL'
 
-Video information is guessed using the path, not only the file name. This helps guessing episodes such as:
+Video information is guessed using the path with parent directories, not only the file name.
+This helps guessing episodes such as:
 
     >>> episode = Video.fromname('Mrs.America/01x01.mp4')
     >>> episode
diff --git a/src/subliminal/cli.py b/src/subliminal/cli.py
index cb75ae5..526f52f 100644
--- a/src/subliminal/cli.py
+++ b/src/subliminal/cli.py
@@ -211,6 +211,50 @@ def plural(quantity: int, name: str, *, bold: bool = True, **kwargs: Any) -> str
     )
 
 
+def scan_video_path(
+    filepath: str | os.PathLike[str],
+    *,
+    name: str | None = None,
+    absolute_path: bool = False,
+    verbose: int = 0,
+    debug: bool = False,
+) -> Video | None:
+    """Try to scan a video at path, with a option to convert to absolute path before."""
+    exists = os.path.exists(filepath)
+    # Take the absolute path, and only if the path exists
+    if absolute_path and exists:
+        filepath = os.path.abspath(filepath)
+    # Used for print
+    filepath_or_name = f'{filepath} ({name})' if name else filepath
+
+    try:
+        video = scan_path(filepath, name=name)
+
+    except GuessingError as e:
+        logger.exception(
+            'Cannot guess information about %s %s',
+            'path' if exists else 'non-existing path',
+            filepath_or_name,
+        )
+        # Show a simple error message
+        if verbose > 0:  # pragma: no cover
+            # new line was already added with debug
+            if not debug:
+                click.echo()
+            click.secho(e, fg='yellow')
+        return None
+
+    except ValueError:  # pragma: no cover
+        logger.exception(
+            'Unexpected error while collecting %s %s',
+            'path' if exists else 'non-existing path',
+            filepath_or_name,
+        )
+        return None
+
+    return video
+
+
 AGE = AgeParamType()
 
 PROVIDER = click.Choice(['ALL', *sorted(provider_manager.names())])
@@ -684,6 +728,18 @@ def cache(ctx: click.Context, clear_subliminal: bool) -> None:
     show_default=True,
     help=f'Scan archives for videos (supported extensions: {", ".join(ARCHIVE_EXTENSIONS)}).',
 )
+@click.option(
+    '--use-absolute-path',
+    type=click.Choice(['fallback', 'always', 'never']),
+    default='fallback',
+    show_default=True,
+    show_choices=True,
+    help=(
+        'Convert the given path to an absolute path if "always" or leave the path as-is if "never". '
+        'If "fallback", first try without converting to absolute path, then if the guess failed, '
+        'retry after converting to absolute path.'
+    ),
+)
 @click.option(
     '-n',
     '--name',
@@ -724,6 +780,7 @@ def download(
     language_format: str,
     max_workers: int,
     archives: bool,
+    use_absolute_path: str,
     name: str | None,
     verbose: int,
     path: list[str],
@@ -782,6 +839,9 @@ def download(
     )
     logger.info('Use refiners: %s', use_refiners)
 
+    # Convert to absolute path only with 'always'
+    absolute_path = use_absolute_path == 'always'
+
     # scan videos
     videos = []
     ignored_videos = []
@@ -809,34 +869,22 @@ def download(
             # scan videos
             video_candidates: list[Video] = []
             for filepath in collected_filepaths:
-                exists = os.path.exists(p)
-                filepath_or_name = f'{filepath} ({name})' if name else filepath
-                try:
-                    video = scan_path(filepath, name=name)
-
-                except GuessingError as e:
-                    logger.exception(
-                        'Cannot guess information about %s %s',
-                        'path' if exists else 'non-existing path',
-                        filepath_or_name,
-                    )
-                    # Show a simple error message
-                    if verbose > 0:  # pragma: no cover
-                        # new line was already added with debug
-                        if not debug:
-                            click.echo()
-                        click.secho(e, fg='yellow')
-                    errored_paths.append(filepath)
-                    continue
-
-                except ValueError:  # pragma: no cover
-                    logger.exception(
-                        'Unexpected error while collecting %s %s',
-                        'path' if exists else 'non-existing path',
-                        filepath_or_name,
-                    )
-                    errored_paths.append(filepath)
-                    continue
+                # Try scanning the video at path
+                video = scan_video_path(filepath, absolute_path=absolute_path, name=name, verbose=verbose, debug=debug)
+                if video is None:
+                    # Fallback to scanning with absolute path
+                    if use_absolute_path == 'fallback':
+                        video = scan_video_path(
+                            filepath,
+                            absolute_path=True,
+                            name=name,
+                            verbose=verbose,
+                            debug=debug,
+                        )
+                    # Cannot scan the video
+                    if video is None:
+                        errored_paths.append(filepath)
+                        continue
 
                 # Set the use_time attribute before refining
                 video.use_ctime = use_ctime
@@ -871,7 +919,7 @@ def download(
             msg = f'{video_name!r} ignored'
             if video.exists:
                 langs = ', '.join(str(s) for s in video.subtitle_languages) or 'none'
-                days = f"{video.age.days:d} day{'s' if video.age.days > 1 else ''}"
+                days = f'{video.age.days:d} day{"s" if video.age.days > 1 else ""}'
                 msg += f' - subtitles: {langs} / age: {days}'
             else:
                 msg += ' - not a video file'
@@ -933,7 +981,7 @@ def download(
 
         if pp.discarded_providers:  # pragma: no cover
             click.secho(
-                f"Some providers have been discarded due to unexpected errors: {', '.join(pp.discarded_providers)}",
+                f'Some providers have been discarded due to unexpected errors: {", ".join(pp.discarded_providers)}',
                 fg='yellow',
             )
 
@@ -953,7 +1001,7 @@ def download(
         total_subtitles += len(saved_subtitles)
 
         if verbose > 0:
-            click.echo(f"{plural(len(saved_subtitles), 'subtitle')} downloaded for {os.path.split(v.name)[1]}")
+            click.echo(f'{plural(len(saved_subtitles), "subtitle")} downloaded for {os.path.split(v.name)[1]}')
 
         if verbose > 1:
             for s in saved_subtitles:
@@ -995,7 +1043,7 @@ def download(
                 )
 
     if verbose == 0:
-        click.echo(f"Downloaded {plural(total_subtitles, 'subtitle')}")
+        click.echo(f'Downloaded {plural(total_subtitles, "subtitle")}')
 
 
 def cli() -> None:  # pragma: no cover
diff --git a/src/subliminal/converters/opensubtitles.py b/src/subliminal/converters/opensubtitles.py
index 6c519a2..ffa1ff2 100644
--- a/src/subliminal/converters/opensubtitles.py
+++ b/src/subliminal/converters/opensubtitles.py
@@ -69,7 +69,7 @@ class OpenSubtitlesConverter(LanguageReverseConverter):
         if code in self.from_opensubtitles:
             return self.from_opensubtitles[code]  # type: ignore[no-any-return]
         for conv in [self.alpha3b_converter, self.alpha2_converter]:
-            conv = cast(LanguageReverseConverter, conv)
+            conv = cast('LanguageReverseConverter', conv)
             try:
                 return conv.reverse(code)  # type: ignore[no-any-return]
             except LanguageReverseError:
diff --git a/src/subliminal/providers/addic7ed.py b/src/subliminal/providers/addic7ed.py
index 1cb4079..bb11a2b 100644
--- a/src/subliminal/providers/addic7ed.py
+++ b/src/subliminal/providers/addic7ed.py
@@ -21,7 +21,7 @@ from requests.cookies import RequestsCookieJar
 from subliminal.cache import SHOW_EXPIRATION_TIME, region
 from subliminal.exceptions import ConfigurationError, DownloadLimitExceeded, NotInitializedProviderError
 from subliminal.matches import guess_matches
-from subliminal.subtitle import Subtitle, fix_line_ending
+from subliminal.subtitle import Subtitle
 from subliminal.utils import sanitize
 from subliminal.video import Episode, Video
 
@@ -638,4 +638,4 @@ class Addic7edProvider(Provider):
         if r.headers['Content-Type'] == 'text/html':  # pragma: no cover
             raise DownloadLimitExceeded
 
-        subtitle.content = fix_line_ending(r.content)
+        subtitle.set_content(r.content)
diff --git a/src/subliminal/providers/bsplayer.py b/src/subliminal/providers/bsplayer.py
index 77b2fef..4389bf4 100644
--- a/src/subliminal/providers/bsplayer.py
+++ b/src/subliminal/providers/bsplayer.py
@@ -16,7 +16,7 @@ from defusedxml import ElementTree  # type: ignore[import-untyped]
 from requests import Session
 
 from subliminal.exceptions import AuthenticationError, NotInitializedProviderError
-from subliminal.subtitle import Subtitle, fix_line_ending
+from subliminal.subtitle import Subtitle
 
 from . import Provider
 
@@ -361,4 +361,4 @@ class BSPlayerProvider(Provider):
             'Content-Length': '0',
         }
         r = self.session.get(subtitle.page_link or '', headers=headers, timeout=self.timeout)
-        subtitle.content = fix_line_ending(zlib.decompress(r.content, 47))
+        subtitle.set_content(zlib.decompress(r.content, 47))
diff --git a/src/subliminal/providers/gestdown.py b/src/subliminal/providers/gestdown.py
index ceac8cc..1cbe275 100644
--- a/src/subliminal/providers/gestdown.py
+++ b/src/subliminal/providers/gestdown.py
@@ -12,7 +12,7 @@ from requests import HTTPError, Session
 
 from subliminal.exceptions import DownloadLimitExceeded, NotInitializedProviderError
 from subliminal.matches import guess_matches
-from subliminal.subtitle import Subtitle, fix_line_ending
+from subliminal.subtitle import Subtitle
 from subliminal.utils import sanitize
 from subliminal.video import Episode, Video
 
@@ -351,7 +351,7 @@ class GestdownProvider(Provider):
             for subtitle in found['subtitles']:
                 # read the item
                 hearing_impaired = subtitle['hearingImpaired']
-                page_link = f"{self.server_url}{subtitle['downloadUri']}"
+                page_link = f'{self.server_url}{subtitle["downloadUri"]}'
                 release_group = subtitle['version']
 
                 m = id_pattern.match(page_link)
@@ -420,4 +420,4 @@ class GestdownProvider(Provider):
         if r.headers['Content-Type'] == 'text/html':
             raise DownloadLimitExceeded
 
-        subtitle.content = fix_line_ending(r.content)
+        subtitle.set_content(r.content)
diff --git a/src/subliminal/providers/mock.py b/src/subliminal/providers/mock.py
index 4991a7e..b8c451f 100644
--- a/src/subliminal/providers/mock.py
+++ b/src/subliminal/providers/mock.py
@@ -176,7 +176,7 @@ class MockProvider(Provider):
             self.__class__.__name__,
             subtitle,
         )
-        subtitle.content = subtitle.fake_content
+        subtitle.set_content(subtitle.fake_content)
 
 
 def mock_subtitle_provider(
diff --git a/src/subliminal/providers/napiprojekt.py b/src/subliminal/providers/napiprojekt.py
index 6c282bb..92642f0 100644
--- a/src/subliminal/providers/napiprojekt.py
+++ b/src/subliminal/providers/napiprojekt.py
@@ -64,7 +64,6 @@ class NapiProjektSubtitle(Subtitle):
         fps: float = 24,
     ) -> None:
         super().__init__(language, subtitle_id, fps=fps)
-        self.content = None
 
     @property
     def info(self) -> str:
@@ -177,7 +176,7 @@ class NapiProjektProvider(Provider):
 
         # Create subtitle object
         subtitle = self.subtitle_class(language=language, subtitle_id=video_hash)
-        subtitle.content = content
+        subtitle.set_content(content)
         logger.debug('Found subtitle %r', subtitle)
 
         return [subtitle]
diff --git a/src/subliminal/providers/opensubtitles.py b/src/subliminal/providers/opensubtitles.py
index 4e31cf3..52c6174 100644
--- a/src/subliminal/providers/opensubtitles.py
+++ b/src/subliminal/providers/opensubtitles.py
@@ -22,7 +22,7 @@ from subliminal.exceptions import (
     ServiceUnavailable,
 )
 from subliminal.matches import guess_matches
-from subliminal.subtitle import Subtitle, fix_line_ending
+from subliminal.subtitle import Subtitle
 from subliminal.utils import decorate_imdb_id, sanitize_id
 from subliminal.video import Episode, Movie, Video
 
@@ -341,7 +341,7 @@ class OpenSubtitlesProvider(Provider):
         """Download the content of the subtitle."""
         logger.info('Downloading subtitle %r', subtitle)
         response = checked(self.server.DownloadSubtitles(self.token, [str(subtitle.subtitle_id)]))  # type: ignore[arg-type]
-        subtitle.content = fix_line_ending(zlib.decompress(base64.b64decode(response['data'][0]['data']), 47))
+        subtitle.set_content(zlib.decompress(base64.b64decode(response['data'][0]['data']), 47))
 
 
 class OpenSubtitlesVipSubtitle(OpenSubtitlesSubtitle):
diff --git a/src/subliminal/providers/opensubtitlescom.py b/src/subliminal/providers/opensubtitlescom.py
index 9c50c0b..aee4659 100644
--- a/src/subliminal/providers/opensubtitlescom.py
+++ b/src/subliminal/providers/opensubtitlescom.py
@@ -25,7 +25,7 @@ from subliminal.exceptions import (
     ServiceUnavailable,
 )
 from subliminal.matches import guess_matches
-from subliminal.subtitle import Subtitle, fix_line_ending
+from subliminal.subtitle import Subtitle
 from subliminal.video import Episode, Movie, Video
 
 from . import Provider
@@ -364,7 +364,7 @@ def requires_auth(func: C) -> C:
 
         return func(self, *args, **kwargs)
 
-    return cast(C, wrapper)
+    return cast('C', wrapper)
 
 
 class OpenSubtitlesComProvider(Provider):
@@ -782,7 +782,7 @@ class OpenSubtitlesComProvider(Provider):
             logger.debug('Unable to download subtitle. No data returned from provider')
             return
 
-        subtitle.content = fix_line_ending(download_response.content)
+        subtitle.set_content(download_response.content)
 
 
 class OpenSubtitlesComVipSubtitle(OpenSubtitlesComSubtitle):
diff --git a/src/subliminal/providers/podnapisi.py b/src/subliminal/providers/podnapisi.py
index 37b4bbe..5ee7551 100644
--- a/src/subliminal/providers/podnapisi.py
+++ b/src/subliminal/providers/podnapisi.py
@@ -14,7 +14,7 @@ from requests import Session
 
 from subliminal.exceptions import NotInitializedProviderError, ProviderError
 from subliminal.matches import guess_matches
-from subliminal.subtitle import Subtitle, fix_line_ending
+from subliminal.subtitle import Subtitle
 from subliminal.video import Episode, Movie, Video
 
 from . import Provider, SecLevelOneTLSAdapter
@@ -249,4 +249,4 @@ class PodnapisiProvider(Provider):
                 msg = 'More than one file to unzip'
                 raise ProviderError(msg)
 
-            subtitle.content = fix_line_ending(zf.read(zf.namelist()[0]))
+            subtitle.set_content(zf.read(zf.namelist()[0]))
diff --git a/src/subliminal/providers/subtitulamos.py b/src/subliminal/providers/subtitulamos.py
index 3d80020..d50ce63 100644
--- a/src/subliminal/providers/subtitulamos.py
+++ b/src/subliminal/providers/subtitulamos.py
@@ -16,7 +16,7 @@ from subliminal import __short_version__
 from subliminal.cache import SHOW_EXPIRATION_TIME, region
 from subliminal.exceptions import NotInitializedProviderError, ProviderError
 from subliminal.matches import guess_matches
-from subliminal.subtitle import Subtitle, fix_line_ending
+from subliminal.subtitle import Subtitle
 from subliminal.video import Episode
 
 from . import ParserBeautifulSoup, Provider
@@ -168,7 +168,7 @@ class SubtitulamosProvider(Provider):
             timeout=self.timeout,
         )
         data = json.loads(r.text)
-        return cast(list[dict[str, str]], data)
+        return cast('list[dict[str, str]]', data)
 
     def _read_series(self, series_url: str) -> ParserBeautifulSoup:
         """Read series information from provider."""
@@ -216,7 +216,7 @@ class SubtitulamosProvider(Provider):
             msg = 'Season not found'
             raise NotExists(msg)
 
-        if 'selected' not in cast(list[str], season_element.get('class', [])):
+        if 'selected' not in cast('list[str]', season_element.get('class', [])):
             page_content = self._read_series(str(season_element.get('href', '')))
 
         # Select episode
@@ -229,7 +229,7 @@ class SubtitulamosProvider(Provider):
             raise NotExists(msg)
 
         episode_url = str(episode_element.get('href', ''))
-        if 'selected' not in cast(list[str], episode_element.get('class', [])):
+        if 'selected' not in cast('list[str]', episode_element.get('class', [])):
             page_content = self._read_series(episode_url)
 
         return page_content, episode_url
@@ -330,7 +330,7 @@ class SubtitulamosProvider(Provider):
         r = self.session.get(subtitle.download_link, headers={'Referer': subtitle.page_link}, timeout=10)
         r.raise_for_status()
 
-        subtitle.content = fix_line_ending(r.content)
+        subtitle.set_content(r.content)
 
 
 class SubtitulamosError(ProviderError):
diff --git a/src/subliminal/providers/tvsubtitles.py b/src/subliminal/providers/tvsubtitles.py
index 4f0443c..ce06014 100644
--- a/src/subliminal/providers/tvsubtitles.py
+++ b/src/subliminal/providers/tvsubtitles.py
@@ -16,7 +16,7 @@ from requests import Session
 from subliminal.cache import EPISODE_EXPIRATION_TIME, SHOW_EXPIRATION_TIME, region
 from subliminal.exceptions import NotInitializedProviderError, ProviderError
 from subliminal.matches import guess_matches
-from subliminal.subtitle import Subtitle, fix_line_ending
+from subliminal.subtitle import Subtitle
 from subliminal.utils import sanitize
 from subliminal.video import Episode, Video
 
@@ -329,4 +329,4 @@ class TVsubtitlesProvider(Provider):
                 msg = 'More than one file to unzip'
                 raise ProviderError(msg)
 
-            subtitle.content = fix_line_ending(zf.read(zf.namelist()[0]))
+            subtitle.set_content(zf.read(zf.namelist()[0]))
diff --git a/src/subliminal/refiners/hash.py b/src/subliminal/refiners/hash.py
index 4aedd16..ce280fd 100644
--- a/src/subliminal/refiners/hash.py
+++ b/src/subliminal/refiners/hash.py
@@ -8,7 +8,6 @@ import struct
 from typing import TYPE_CHECKING, Any, cast
 
 from subliminal.extensions import get_default_providers, provider_manager
-from subliminal.providers import Provider
 
 if TYPE_CHECKING:
     from collections.abc import Sequence, Set
@@ -16,6 +15,7 @@ if TYPE_CHECKING:
 
     from babelfish import Language  # type: ignore[import-untyped]
 
+    from subliminal.providers import Provider
     from subliminal.video import Video
 
     HashFunc: TypeAlias = Callable[[str | os.PathLike], str | None]
@@ -86,7 +86,7 @@ def refine(
 
     logger.debug('Computing hashes for %r', video.name)
     for name in providers:
-        provider = cast(Provider, provider_manager[name].plugin)
+        provider = cast('Provider', provider_manager[name].plugin)
         if not provider.check_types(video):
             continue
 
diff --git a/src/subliminal/refiners/omdb.py b/src/subliminal/refiners/omdb.py
index c33c678..584f8c8 100644
--- a/src/subliminal/refiners/omdb.py
+++ b/src/subliminal/refiners/omdb.py
@@ -73,11 +73,11 @@ class OMDBClient:
         is_movie: bool | None = None if type is None else (type == 'movie')
         if id is not None:
             res = self.search_by_id(id, is_movie=is_movie, plot=plot)
-            return cast(dict, res)
+            return cast('dict', res)
 
         if title is not None:
             res = self.search_by_title(title, is_movie=is_movie, year=year, plot=plot)
-            return cast(dict, res)
+            return cast('dict', res)
 
         # missing one required argument
         msg = 'At least id or title is required'
@@ -108,7 +108,7 @@ class OMDBClient:
         if j['Response'] == 'False':
             return {}
 
-        return cast(dict, j)
+        return cast('dict', j)
 
     @region.cache_on_arguments(expiration_time=REFINER_EXPIRATION_TIME)
     def search_by_title(
@@ -138,7 +138,7 @@ class OMDBClient:
         if j['Response'] == 'False':
             return {}
 
-        return cast(dict, j)
+        return cast('dict', j)
 
     @region.cache_on_arguments(expiration_time=REFINER_EXPIRATION_TIME)
     def search(
@@ -168,7 +168,7 @@ class OMDBClient:
         if j['Response'] == 'False':
             return {}
 
-        return cast(dict, j)
+        return cast('dict', j)
 
     @region.cache_on_arguments(expiration_time=REFINER_EXPIRATION_TIME)
     def search_all(self, title: str, is_movie: bool | None = None, year: int | None = None) -> list:
@@ -178,14 +178,14 @@ class OMDBClient:
             return []
 
         # fetch all paginated results
-        all_results = cast(list, results['Search'])
+        all_results = cast('list', results['Search'])
         total_results = int(results['totalResults'])
         page = 1
         while total_results > page * 10:
             page += 1
             results = self.search(title=title, is_movie=is_movie, year=year, page=page)
             if results:  # pragma: no branch
-                all_results.extend(cast(list, results['Search']))
+                all_results.extend(cast('list', results['Search']))
 
         return all_results
 
diff --git a/src/subliminal/refiners/tmdb.py b/src/subliminal/refiners/tmdb.py
index a7a1d74..8ef3002 100644
--- a/src/subliminal/refiners/tmdb.py
+++ b/src/subliminal/refiners/tmdb.py
@@ -96,7 +96,7 @@ class TMDBClient:
         r = self.session.get(self.base_url + f'/search/{category}', params=params)
 
         r.raise_for_status()
-        return cast(list, r.json().get('results'))
+        return cast('list', r.json().get('results'))
 
     @region.cache_on_arguments(expiration_time=REFINER_EXPIRATION_TIME)
     def get_id(
@@ -166,7 +166,7 @@ class TMDBClient:
         r = self.session.get(self.base_url + path, params=params)
         r.raise_for_status()
 
-        return cast(dict, r.json())
+        return cast('dict', r.json())
 
     @region.cache_on_arguments(expiration_time=REFINER_EXPIRATION_TIME)
     def search_movie(
@@ -180,7 +180,7 @@ class TMDBClient:
         if tmdb_id is None:
             return {}
         res = self.query(tmdb_id, is_movie=True)
-        return cast(dict, res)
+        return cast('dict', res)
 
     @region.cache_on_arguments(expiration_time=REFINER_EXPIRATION_TIME)
     def search_series(
@@ -194,7 +194,7 @@ class TMDBClient:
         if tmdb_id is None:
             return {}
         res = self.query(tmdb_id, is_movie=False)
-        return cast(dict, res)
+        return cast('dict', res)
 
     @region.cache_on_arguments(expiration_time=REFINER_EXPIRATION_TIME)
     def search_episode(
@@ -210,7 +210,7 @@ class TMDBClient:
         if tmdb_id is None:
             return {}
         res = self.query(tmdb_id, is_movie=False, season=season, episode=episode)
-        return cast(dict, res)
+        return cast('dict', res)
 
     @property
     def apikey(self) -> str | None:
diff --git a/src/subliminal/refiners/tvdb.py b/src/subliminal/refiners/tvdb.py
index 5df1f06..c4cf97f 100644
--- a/src/subliminal/refiners/tvdb.py
+++ b/src/subliminal/refiners/tvdb.py
@@ -39,7 +39,7 @@ def requires_auth(func: C) -> C:
             self.refresh_token()
         return func(self, *args, **kwargs)
 
-    return cast(C, wrapper)
+    return cast('C', wrapper)
 
 
 class TVDBClient:
@@ -187,7 +187,7 @@ class TVDBClient:
             return {}
         r.raise_for_status()
 
-        return cast(dict, r.json()['data'])
+        return cast('dict', r.json()['data'])
 
     @requires_auth
     def query_series_episodes(
@@ -221,7 +221,7 @@ class TVDBClient:
             return {}
         r.raise_for_status()
 
-        return cast(dict, r.json())
+        return cast('dict', r.json())
 
     @region.cache_on_arguments(expiration_time=REFINER_EXPIRATION_TIME)
     @requires_auth
@@ -239,7 +239,7 @@ class TVDBClient:
             return {}
         r.raise_for_status()
 
-        return cast(dict, r.json()['data'])
+        return cast('dict', r.json()['data'])
 
     @region.cache_on_arguments(expiration_time=REFINER_EXPIRATION_TIME)
     @requires_auth
@@ -256,7 +256,7 @@ class TVDBClient:
             return {}
         r.raise_for_status()
 
-        return cast(dict, r.json()['data'])
+        return cast('dict', r.json()['data'])
 
     @region.cache_on_arguments(expiration_time=REFINER_EXPIRATION_TIME)
     @requires_auth
@@ -276,7 +276,7 @@ class TVDBClient:
             return {}
         r.raise_for_status()
 
-        return cast(dict, r.json())
+        return cast('dict', r.json())
 
     @region.cache_on_arguments(expiration_time=REFINER_EXPIRATION_TIME)
     @requires_auth
@@ -311,7 +311,7 @@ class TVDBClient:
             return []
         r.raise_for_status()
 
-        return cast(list, r.json()['data'])
+        return cast('list', r.json()['data'])
 
     @property
     def apikey(self) -> str:
diff --git a/src/subliminal/subtitle.py b/src/subliminal/subtitle.py
index d9a4812..9971fe6 100644
--- a/src/subliminal/subtitle.py
+++ b/src/subliminal/subtitle.py
@@ -263,11 +263,7 @@ class Subtitle:
 
     @content.setter
     def content(self, value: bytes | None) -> None:
-        self.clear_content()
-        self._content = value
-
-        if self.force_guess_encoding and self.encoding is None:
-            self.encoding = self.guess_encoding()
+        self.set_content(value)
 
     @property
     def text(self) -> str:
@@ -276,6 +272,18 @@ class Subtitle:
             self._text = self._decode_content()
         return self._text
 
+    def set_content(self, value: bytes | None, *, fix: bool = True) -> None:
+        """Set subtitle bytes content."""
+        if fix and value:
+            value = fix_line_ending(value)
+
+        # Clear the previous content before adding new content
+        self.clear_content()
+        self._content = value
+
+        if self.force_guess_encoding and self.encoding is None:
+            self.encoding = self.guess_encoding()
+
     def clear_content(self) -> None:
         """Clear the content of the subtitle."""
         self._text = ''
diff --git a/src/subliminal/utils.py b/src/subliminal/utils.py
index e9ca3d3..c4e62a3 100644
--- a/src/subliminal/utils.py
+++ b/src/subliminal/utils.py
@@ -201,7 +201,7 @@ def ensure_list(value: T | Sequence[T] | None) -> list[T]:
     if value is None:
         return []
     if not is_iterable(value):
-        return [cast(T, value)]
+        return [cast('T', value)]
     return list(value)
 
 


Test patch:
diff --git a/.github/actions/prepare-tests/action.yaml b/.github/actions/prepare-tests/action.yaml
new file mode 100644
index 0000000..00fbeb3
--- /dev/null
+++ b/.github/actions/prepare-tests/action.yaml
@@ -0,0 +1,62 @@
+name: 'Download data for tests'
+description: 'Download .mkv and compress them with rar to be used in tests.'
+
+runs:
+  using: "composite"
+  steps:
+    - name: Check Runner OS
+      if: ${{ runner.os != 'Linux' && runner.os != 'Windows' && runner.os != 'macOS'}}
+      shell: bash
+      run: |
+        echo "::error title=⛔ error hint::Support Linux, Windows, and macOS Only"
+        exit 1
+    - uses: actions/checkout@v4
+      with:
+        persist-credentials: false
+        fetch-depth: 0
+    - name: Cache test data
+      id: cache-test-data
+      uses: actions/cache@v4
+      with:
+        path: tests/data/
+        enableCrossOsArchive: true
+        key: prepare-tests
+
+    - name: Download only from Linux Runner OS
+      if: ${{ steps.cache-test-data.outputs.cache-hit != 'true' && runner.os != 'Linux' }}
+      shell: bash
+      run: |
+        echo "::error title=⛔ error hint::Download cache only from Linux: ${{ runner.os }}."
+        exit 1
+    - name: Install other test software (on Linux only)
+      if: steps.cache-test-data.outputs.cache-hit != 'true'
+      uses: awalsh128/cache-apt-pkgs-action@v1
+      with:
+        packages: rar
+        version: 1.0
+    - uses: actions/setup-python@v5
+      if: steps.cache-test-data.outputs.cache-hit != 'true'
+      with:
+        python-version-file: .python-version-default
+        cache: pip
+    - name: Install dependencies
+      if: steps.cache-test-data.outputs.cache-hit != 'true'
+      shell: bash
+      run: |
+        python -Im pip install --upgrade pip
+        python -Im pip install tox requests
+    - name: Download tests data
+      if: steps.cache-test-data.outputs.cache-hit != 'true'
+      shell: bash
+      run: |
+        tox -e prepare-tests
+
+    # - name: Upload files for test
+    #   uses: actions/upload-artifact@v4
+    #   with:
+    #     name: tests-data
+    #     path: tests/data/
+    #     include-hidden-files: false
+    #     if-no-files-found: error
+    #     # heavy artifact should be deleted asap
+    #     retention-days: 1
diff --git a/.github/workflows/scheduled-tests.yaml b/.github/workflows/scheduled-tests.yaml
index 90d9e0e..2819bdd 100644
--- a/.github/workflows/scheduled-tests.yaml
+++ b/.github/workflows/scheduled-tests.yaml
@@ -30,6 +30,10 @@ jobs:
       - uses: actions/setup-python@v5
         with:
           python-version: "3.x"
+      - name: Prepare tests
+        uses: ./.github/actions/prepare-tests
+      - name: Display downloaded files
+        run: ls -R tests/data
       - name: install
         run: |
           python -Im pip install -U pip
diff --git a/tests/conftest.py b/tests/conftest.py
index 5f469bd..77f5fd7 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -659,10 +659,7 @@ def provider_manager(monkeypatch: pytest.MonkeyPatch) -> Generator[RegistrableEx
             'language': Language.fromietf('en'),
             'subtitle_id': 'Dadi',
             'fake_content': (
-                b'1\n00:00:02,090 --> 00:00:03,970\n'
-                b'Greetings.\n\n'
-                b'2\n00:00:04,080 --> 00:00:05,550\n'
-                b'Sgniteerg.\n\n'
+                b'1\n00:00:02,090 --> 00:00:03,970\nGreetings.\n\n2\n00:00:04,080 --> 00:00:05,550\nSgniteerg.\n\n'
             ),
             'video_name': episode2_name,
             'matches': {'episode', 'season', 'series', 'year'},
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 6050d57..1984b05 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -227,6 +227,41 @@ def test_cli_download_directory(tmp_path: os.PathLike[str]) -> None:
         assert subtitle_episode_filename in files
 
 
+@pytest.mark.parametrize('use_absolute_path', ['never', 'always', 'fallback'])
+def test_cli_download_use_absolute_path(
+    use_absolute_path: str,
+    monkeypatch: pytest.MonkeyPatch,
+    tmp_path: os.PathLike[str],
+) -> None:
+    runner = CliRunner()
+    folder_name = Path(tmp_path) / 'Doctor Who S01'
+    video_name = '1x1.mkv'
+
+    with runner.isolated_filesystem(temp_dir=tmp_path):
+        full_video_path = folder_name / video_name
+        ensure(full_video_path)
+        monkeypatch.chdir(folder_name)
+
+        # Need --verbose for the message to appear
+        result = runner.invoke(
+            subliminal_cli,
+            ['download', '-l', 'en', '--use-absolute-path', use_absolute_path, '-v', '-p', 'podnapisi', video_name],
+        )
+
+        assert result.exit_code == 0
+        if use_absolute_path == 'never':
+            assert 'Insufficient data to process the guess' in result.output
+            assert '0 video collected / 0 video ignored / 1 error' in result.output
+
+        elif use_absolute_path == 'fallback':
+            assert 'Insufficient data to process the guess' in result.output
+            assert '1 video collected / 0 video ignored / 0 error' in result.output
+
+        elif use_absolute_path == 'always':
+            assert 'Insufficient data to process the guess' not in result.output
+            assert '1 video collected / 0 video ignored / 0 error' in result.output
+
+
 @pytest.mark.parametrize(
     ('encoding', 'real_encoding'),
     [
diff --git a/tests/test_core.py b/tests/test_core.py
index cde469e..1894349 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -394,11 +394,11 @@ def test_save_subtitles(movies: dict[str, Movie], tmp_path: Path, monkeypatch: p
     ensure(tmp_path / movies['man_of_steel'].name)
     subtitle_no_content = Subtitle(Language('eng'), '')
     subtitle = Subtitle(Language('fra'), '')
-    subtitle.content = b'Some content'
+    subtitle.set_content(b'Some content')
     subtitle_other = Subtitle(Language('fra'), '')
-    subtitle_other.content = b'Some other content'
+    subtitle_other.set_content(b'Some other content')
     subtitle_pt_br = Subtitle(Language('por', 'BR'), '')
-    subtitle_pt_br.content = b'Some brazilian content'
+    subtitle_pt_br.set_content(b'Some brazilian content')
     subtitles = [subtitle_no_content, subtitle, subtitle_other, subtitle_pt_br]
 
     save_subtitles(movies['man_of_steel'], subtitles)
@@ -420,9 +420,9 @@ def test_save_subtitles(movies: dict[str, Movie], tmp_path: Path, monkeypatch: p
 
 def test_save_subtitles_single_directory_encoding(movies: dict[str, Movie], tmp_path: Path) -> None:
     subtitle = Subtitle(Language('jpn'), '')
-    subtitle.content = 'ハローワールド'.encode('shift-jis')
+    subtitle.set_content('ハローワールド'.encode('shift-jis'))
     subtitle_pt_br = Subtitle(Language('por', 'BR'), '')
-    subtitle_pt_br.content = b'Some brazilian content'
+    subtitle_pt_br.set_content(b'Some brazilian content')
     subtitles = [subtitle, subtitle_pt_br]
 
     save_subtitles(movies['man_of_steel'], subtitles, single=True, directory=os.fspath(tmp_path), encoding='utf-8')
@@ -458,7 +458,7 @@ def test_save_subtitles_convert(movies: dict[str, Movie], tmp_path: Path, monkey
         """
     )
     subtitle = Subtitle(Language('pol'), '')
-    subtitle.content = text.encode('utf-8')
+    subtitle.set_content(text.encode('utf-8'))
     subtitles = [subtitle]
 
     save_subtitles(video, subtitles, single=True, subtitle_format='srt')
diff --git a/tests/test_matches.py b/tests/test_matches.py
index f999d7b..60e4156 100644
--- a/tests/test_matches.py
+++ b/tests/test_matches.py
@@ -14,7 +14,7 @@ def test_guess_matches_movie(movies: dict[str, Movie]) -> None:
     guess = {
         'title': video.title.upper(),
         'year': video.year,
-        'release_group': cast(str, video.release_group).upper(),
+        'release_group': cast('str', video.release_group).upper(),
         'screen_size': video.resolution,
         'source': video.source,
         'video_codec': video.video_codec,
@@ -31,8 +31,8 @@ def test_guess_matches_episode(episodes: dict[str, Episode]) -> None:
         'season': video.season,
         'episode': video.episode,
         'year': video.year,
-        'episode_title': cast(str, video.title).upper(),
-        'release_group': cast(str, video.release_group).upper(),
+        'episode_title': cast('str', video.title).upper(),
+        'release_group': cast('str', video.release_group).upper(),
         'screen_size': video.resolution,
         'source': video.source,
         'video_codec': video.video_codec,
@@ -68,7 +68,7 @@ def test_guess_matches_episode_equivalent_release_group(episodes: dict[str, Epis
         'season': video.season,
         'episode': video.episode,
         'year': video.year,
-        'episode_title': cast(str, video.title).upper(),
+        'episode_title': cast('str', video.title).upper(),
         'release_group': 'LOL',
         'screen_size': video.resolution,
         'source': video.source,
@@ -98,7 +98,7 @@ def test_guess_matches_multiple_sources_no_match(episodes: dict[str, Episode]) -
         'season': video.season,
         'episode': video.episode,
         'year': video.year,
-        'episode_title': cast(str, video.title).upper(),
+        'episode_title': cast('str', video.title).upper(),
         'release_group': 'LOL',
         'screen_size': video.resolution,
         'source': [video.source, 'Blu-ray'],
diff --git a/tests/test_provider_pool.py b/tests/test_provider_pool.py
index 46543ab..a2a65ba 100644
--- a/tests/test_provider_pool.py
+++ b/tests/test_provider_pool.py
@@ -15,7 +15,6 @@ from subliminal.core import (
     refine,
     refiner_manager,
 )
-from subliminal.providers.mock import MockProvider
 from subliminal.score import episode_scores
 from subliminal.subtitle import Subtitle
 
@@ -23,6 +22,7 @@ if TYPE_CHECKING:
     from typing import Callable
 
     from subliminal.extensions import RegistrableExtensionManager
+    from subliminal.providers.mock import MockProvider
     from subliminal.video import Episode, Movie, Video
 
 # Core test
@@ -305,7 +305,7 @@ def test_list_subtitles_discarded_provider(
     # keep listed subtitle
     subtitle = subtitles[0]
 
-    provider = cast(MockProvider, pool['opensubtitlescom'])
+    provider = cast('MockProvider', pool['opensubtitlescom'])
 
     # Mock a broken provider
     provider.is_broken = True
@@ -332,7 +332,7 @@ def test_async_provider_pool_list_subtitles_discarded_providers(
 
     pool = AsyncProviderPool(max_workers=1)
     # One provider is broken
-    cast(MockProvider, pool['opensubtitlescom']).is_broken = True
+    cast('MockProvider', pool['opensubtitlescom']).is_broken = True
 
     subtitles = pool.list_subtitles(video, languages)
     assert {s.provider_name for s in subtitles} == {
@@ -360,7 +360,7 @@ def test_download_subtitles_discarded_provider(
     assert 'opensubtitlescom' not in pool.discarded_providers
 
     # Mock a broken provider
-    cast(MockProvider, pool['opensubtitlescom']).is_broken = True
+    cast('MockProvider', pool['opensubtitlescom']).is_broken = True
     # Try failing downloading subtitle
     assert not pool.download_subtitle(subtitle)
 
diff --git a/tests/test_subtitle.py b/tests/test_subtitle.py
index 3bab352..217506d 100644
--- a/tests/test_subtitle.py
+++ b/tests/test_subtitle.py
@@ -50,7 +50,7 @@ def test_languague_type(hearing_impaired: bool | None, foreign_only: bool | None
 
 def test_subtitle_text() -> None:
     subtitle = Subtitle(Language('eng'))
-    subtitle.content = b'Some ascii text'
+    subtitle.set_content(b'Some ascii text')
     assert subtitle.text == 'Some ascii text'
 
 
@@ -61,7 +61,7 @@ def test_subtitle_text_no_content() -> None:
 
 def test_subtitle_none_content() -> None:
     subtitle = Subtitle(Language('jpn'))
-    subtitle.content = None
+    subtitle.set_content(None)
     assert subtitle.is_valid() is False
 
 
@@ -86,12 +86,7 @@ def test_subtitle_is_valid_no_content() -> None:
 
 def test_subtitle_is_valid_valid(monkeypatch: pytest.MonkeyPatch) -> None:
     subtitle = Subtitle(Language('fra'))
-    text = (
-        '1\n'
-        '00:00:20,000 --> 00:00:24,400\n'
-        "En réponse à l'augmentation de la criminalité\n"
-        'dans certains quartiers,\n'
-    )
+    text = "1\n00:00:20,000 --> 00:00:24,400\nEn réponse à l'augmentation de la criminalité\ndans certains quartiers,\n"
     monkeypatch.setattr(Subtitle, 'text', text)
     assert subtitle.is_valid() is True
     assert subtitle.subtitle_format == 'srt'
@@ -100,10 +95,7 @@ def test_subtitle_is_valid_valid(monkeypatch: pytest.MonkeyPatch) -> None:
 def test_subtitle_is_valid_auto_fix(monkeypatch: pytest.MonkeyPatch) -> None:
     subtitle = Subtitle(Language('fra'), auto_fix_srt=True)
     text = (
-        '1\n'
-        '00:00:20,000 --> 00:00:24,400\n'
-        "En réponse à l'augmentation de la criminalité\n"
-        'dans certains quartiers,\n\n'
+        "1\n00:00:20,000 --> 00:00:24,400\nEn réponse à l'augmentation de la criminalité\ndans certains quartiers,\n\n"
     )
     text += "This line shouldn't be here"
     monkeypatch.setattr(Subtitle, 'text', text)
@@ -113,10 +105,7 @@ def test_subtitle_is_valid_auto_fix(monkeypatch: pytest.MonkeyPatch) -> None:
 def test_subtitle_is_valid_valid_begin(monkeypatch: pytest.MonkeyPatch) -> None:
     subtitle = Subtitle(Language('fra'))
     text = (
-        '1\n'
-        '00:00:20,000 --> 00:00:24,400\n'
-        "En réponse à l'augmentation de la criminalité\n"
-        'dans certains quartiers,\n\n'
+        "1\n00:00:20,000 --> 00:00:24,400\nEn réponse à l'augmentation de la criminalité\ndans certains quartiers,\n\n"
     ) * 20
     text += "This line shouldn't be here"
     monkeypatch.setattr(Subtitle, 'text', text)
@@ -126,11 +115,7 @@ def test_subtitle_is_valid_valid_begin(monkeypatch: pytest.MonkeyPatch) -> None:
 def test_subtitle_is_valid_sub_format(monkeypatch: pytest.MonkeyPatch, movies: dict[str, Movie]) -> None:
     video = movies['man_of_steel']
     subtitle = Subtitle(Language('pol'))
-    text = (
-        '{3146}{3189}/Nie rozumiecie?\n'
-        '{3189}{3244}/Jšdro Kryptona się rozpada.\n'
-        '{3244}{3299}To kwestia tygodni.\n'
-    )
+    text = '{3146}{3189}/Nie rozumiecie?\n{3189}{3244}/Jšdro Kryptona się rozpada.\n{3244}{3299}To kwestia tygodni.\n'
     monkeypatch.setattr(Subtitle, 'text', text)
     assert subtitle.is_valid() is True
     assert subtitle.subtitle_format == 'microdvd'
@@ -142,11 +127,7 @@ def test_subtitle_is_valid_sub_format(monkeypatch: pytest.MonkeyPatch, movies: d
 def test_subtitle_get_path_extension(monkeypatch: pytest.MonkeyPatch, movies: dict[str, Movie]) -> None:
     video = movies['man_of_steel']
     subtitle = Subtitle(Language('pol'))
-    text = (
-        '{3146}{3189}/Nie rozumiecie?\n'
-        '{3189}{3244}/Jšdro Kryptona się rozpada.\n'
-        '{3244}{3299}To kwestia tygodni.\n'
-    )
+    text = '{3146}{3189}/Nie rozumiecie?\n{3189}{3244}/Jšdro Kryptona się rozpada.\n{3244}{3299}To kwestia tygodni.\n'
     monkeypatch.setattr(Subtitle, 'text', text)
     assert subtitle.is_valid() is True
     assert subtitle.subtitle_format == 'microdvd'
@@ -259,6 +240,14 @@ def test_subtitle_invalid_encoding() -> None:
     assert subtitle.hearing_impaired is False
 
 
+def test_subtitle_set_content() -> None:
+    subtitle = Subtitle(language=Language('kur'), encoding=None)
+    content = b'Ti\xc5\x9ftek li vir'
+    subtitle.content = content
+    assert subtitle.content == content
+    assert subtitle.text == content.decode()
+
+
 def test_subtitle_guess_encoding_utf8() -> None:
     subtitle = Subtitle(
         language=Language('zho'),
@@ -266,7 +255,7 @@ def test_subtitle_guess_encoding_utf8() -> None:
         page_link=None,
         encoding=None,
     )
-    subtitle.content = b'Something here'
+    subtitle.set_content(b'Something here')
     assert subtitle.foreign_only is False
     assert subtitle.guess_encoding() == 'utf-8'
     assert subtitle.text == 'Something here'
@@ -281,7 +270,7 @@ def test_subtitle_text_guess_encoding_none() -> None:
         page_link=None,
         encoding=None,
     )
-    subtitle.content = content
+    subtitle.set_content(content)
 
     assert subtitle.guess_encoding() is None
     assert not subtitle.is_valid()
@@ -294,7 +283,7 @@ def test_subtitle_reencode() -> None:
         language=Language('por'),
         encoding='latin1',
     )
-    subtitle.content = content
+    subtitle.set_content(content)
     success = subtitle.reencode()
     assert success
     assert subtitle.content == b'Uma palavra longa \xc3\xa9 melhor do que um p\xc3\xa3o curto'
@@ -329,7 +318,7 @@ def test_subtitle_convert_same_format_same_encoding() -> None:
         encoding='utf-8',
     )
     text = "1\n00:00:05,000 --> 00:00:10,000\n«Attention, ce flim n'est pas un flim sur le cyclimse»\n\n"
-    subtitle.content = text.encode('utf-8')
+    subtitle.set_content(text.encode('utf-8'))
     assert subtitle.encoding == 'utf-8'
     assert subtitle.text == text
 
@@ -342,7 +331,7 @@ def test_subtitle_convert_same_format_different_encoding() -> None:
         encoding='latin1',
     )
     text = "1\n00:00:05,000 --> 00:00:10,000\n«Attention, ce flim n'est pas un flim sur le cyclimse»\n\n"
-    subtitle.content = text.encode('latin1')
+    subtitle.set_content(text.encode('latin1'))
     assert subtitle.encoding == 'iso8859-1'
     assert subtitle.text == text
 
@@ -362,7 +351,7 @@ def test_subtitle_convert_from_microdvd_no_fps() -> None:
     {3244}{3299}To kwestia tygodni.
     {3299}{3390}Ostrzegałem, że eksploatacja jądra|to samobójstwo.
     """
-    subtitle.content = text.encode('utf-8')
+    subtitle.set_content(text.encode('utf-8'))
 
     assert not subtitle.convert(output_format='srt', output_encoding='utf-8')
 
@@ -379,7 +368,7 @@ def test_subtitle_convert_from_microdvd_subtitle_fps() -> None:
     {3244}{3299}To kwestia tygodni.
     {3299}{3390}Ostrzegałem, że eksploatacja jądra|to samobójstwo.
     """
-    subtitle.content = text.encode('utf-8')
+    subtitle.set_content(text.encode('utf-8'))
 
     assert subtitle.convert(output_format='srt', output_encoding='utf-8')
     assert subtitle.subtitle_format == 'srt'
@@ -426,7 +415,7 @@ def test_subtitle_convert_from_microdvd_argument_fps() -> None:
     {3244}{3299}To kwestia tygodni.
     {3299}{3390}Ostrzegałem, że eksploatacja jądra|to samobójstwo.
     """
-    subtitle.content = text.encode('utf-8')
+    subtitle.set_content(text.encode('utf-8'))
 
     assert subtitle.convert(output_format='srt', output_encoding='utf-8', fps=24)
     assert subtitle.subtitle_format == 'srt'
@@ -492,7 +481,7 @@ def test_subtitle_convert_to_ssa() -> None:
 
         """
     )
-    subtitle.content = text.encode('utf-8')
+    subtitle.set_content(text.encode('utf-8'))
 
     assert subtitle.convert(output_format='ass')
     assert subtitle.subtitle_format == 'ass'