Metadata-Version: 2.4
Name: cs-app-playon
Version: 20260531
Summary: PlayOn facilities, primarily access to the download API. Includes a nice command line tool.
Keywords: python3
Author-email: Cameron Simpson <cs@cskk.id.au>
Description-Content-Type: text/markdown
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Utilities
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
Requires-Dist: cs.cmdutils>=20260531
Requires-Dist: cs.context>=20250528
Requires-Dist: cs.deco>=20260525
Requires-Dist: cs.fileutils>=20210731
Requires-Dist: cs.fstags>=20260531
Requires-Dist: cs.lex>=20260526
Requires-Dist: cs.logutils>=20250323
Requires-Dist: cs.mediainfo>=20260531
Requires-Dist: cs.pfx>=20210731
Requires-Dist: cs.progress>=20260531
Requires-Dist: cs.resources>=20250915
Requires-Dist: cs.result>=20250306
Requires-Dist: cs.service_api>=20260531
Requires-Dist: cs.sqltags>=20260531
Requires-Dist: cs.tagset>=20260531
Requires-Dist: cs.threads>=20260531
Requires-Dist: cs.units>=20260526
Requires-Dist: cs.upd>=20260526
Requires-Dist: icontract
Requires-Dist: requests
Requires-Dist: typeguard
Project-URL: MonoRepo Commits, https://bitbucket.org/cameron_simpson/css/commits/branch/main
Project-URL: Monorepo Git Mirror, https://github.com/cameron-simpson/css
Project-URL: Monorepo Hg/Mercurial Mirror, https://hg.sr.ht/~cameron-simpson/css
Project-URL: Source, https://github.com/cameron-simpson/css/blob/main/lib/python/cs/app/playon.py

PlayOn facilities, primarily access to the download API.
Includes a nice command line tool.

*Latest release 20260531*:
* DOwnloads: use cs.rfc2616.content_length to get the download content-length, open the download file with 'xb'.
* PlayOnAPI is now the source of the sqltags, do not expect to access it outside.
* New _PlayOnEntity(HasSQLTags) base class for Recording and LoginState.
* PlayOnAPI: new .login_userid property for external use.
* PlayOnAPI.__getitem__: an int index is promoted to ('recording,str(index)) for lookup.
* PlayOnAPI: new .recordings() finding playon.recording.*; use it for __iter__.
* PlayOnAPI: new .recording_ids_from_str() method for use by the CLI.
* PlayOnAPI: use the new UsesTagSets stuff.
* The PlayOn API has started reciting times with a microsecond field.
* PlayOnAPI.from_playon_date: accomodate time formats with and without microseconds.
* Recording.STALE_AGE -> Recording.refresh_lifespan for Refreshable.
* PlayOnCommand.cmd_dl: use pmap() to run the downloads in parallel.
* Assorted other internal changes.

Short summary:
* `main`: Playon command line mode; see the `PlayOnCommand` class below.
* `PlayOnAPI`: Access to the PlayOn API.
* `PlayOnCommand`: Playon command line implementation.
* `PlayonSeriesEpisodeInfo`: A `SeriesEpisodeInfo` with a `from_Recording()` factory method to build one from a PlayOn `Recording` instead or other mapping with `playon.*` keys.
* `PlayOnSQLTags`: PlayOn flavoured `SQLTags`; it just has custom values for the default db location.
* `Recording`: A PlayOn recording.

Module contents:
- <a name="main"></a>`main(argv=None)`: Playon command line mode;
  see the `PlayOnCommand` class below.
- <a name="PlayOnAPI"></a>`class PlayOnAPI(cs.service_api.HTTPServiceAPI)`: Access to the PlayOn API.

*`PlayOnAPI.HasTagsClass`*

*`PlayOnAPI.TagSetsClass`*

*`PlayOnAPI.__getitem__(self, index: tuple | int) -> cs.tagset.HasTags`*:
If `index` is an `int` return the associated `Recording`.
Otherwise `index` should be a `tuple`, returns the associated `HasTags`.

*`PlayOnAPI.__iter__(self)`*:
Iteration iterates over the recordins.

*`PlayOnAPI.account(self)`*:
Return account information.

*`PlayOnAPI.as_contextmanager(self)`*:
Run the generator from the `cls` class specific `__enter_exit__`
method via `self` as a context manager.

Example from `RunState` which subclasses `HasThreadState`,
both of which are `ContextManagerMixin` subclasses:

    class RunState(HasThreadState):
        .....
        def __enter_exit__(self):
            with HasThreadState.as_contextmanager(self):
                ... RunState context manager stuff ...

This runs the `HasThreadState` context manager
around the main `RunState` context manager.

*`PlayOnAPI.auth_token`*:
An auth token obtained from the login state.

*`PlayOnAPI.available(self)`*:
Yield the `Recording`s.

Note that this includes both recorded and queued items.

*`PlayOnAPI.by_entity_id(entity_id: str) -> cs.tagset.HasTags`*:
Return the `HasTags` instance corresponding to `entity_id`
from the full tb 
Raise `ValueError` is `entity_id` cannot be parsed by
`TagSetTyping.type_parts_of`.
Raise `KeyError` if there is no `UsesTagSets` instance for the zone.

*`PlayOnAPI.cdsurl_data(self, suburl, method='GET', headers=None, **kw)`*:
Wrapper for `suburl_data` using `CDS_BASE` as the base URL.

*`PlayOnAPI.download(self, download_id: int, filename=None, *, quiet: bool, runstate: Optional[cs.resources.RunState] = <function uses_runstate.<locals>.<lambda> at 0x10e6c3ce0>, verbose: bool)`*:
Download the file with `download_id` to `filename_basis`.
Return the `TagSet` for the recording.

The default `filename` is the basename of the filename
from the download.
If the filename is supplied with a trailing dot (`'.'`)
then the file extension will be taken from the filename
of the download URL.

*`PlayOnAPI.feature(self, feature_id)`*:
Return the feature `SQLTags` instance for `feature_id`.

*`PlayOnAPI.featured_image_url(self, feature_name: str)`*:
URL of the image for a featured show.

*`PlayOnAPI.features(self)`*:
Fetch the list of featured shows.

*`PlayOnAPI.fetch_recordings(self, *, verbose=False) -> set[cs.app.playon.Recording]`*:
Return a set of the `Recording` instances for the available recordings.
This makes an API request.

*`PlayOnAPI.from_playon_date(date_s) -> datetime.datetime`*:
Return a timezone aware datetime from a PlayOn date/time value;
The PlayOn API seems to use UTC date strings.

*`PlayOnAPI.jwt`*:
The JWT token.

*`PlayOnAPI.login(self)`*:
Perform a login, return the resulting `dict`.
*Does not* update the state of `self`.

*`PlayOnAPI.login_expiry`*:
Expiry UNIX time for the login state.

*`PlayOnAPI.ls(self, recording_specs, *, format: str, long_mode=False)`*:
List the specified recordings.

*`PlayOnAPI.notifications(self)`*:
Return the notifications.

*`PlayOnAPI.queue(self)`*:
Return the `TagSet` instances for the queued recordings.

*`PlayOnAPI.recording_ids_from_str(self, arg)`*:
Convert a string to a list of recording ids.

*`PlayOnAPI.recordings(self)`*:
Yield the `Recording`s.

Note that this includes both recorded and queued items.

*`PlayOnAPI.renew_jwt(self)`*:
UNUSED

*`PlayOnAPI.service(self, service_id: str)`*:
Return the service `SQLTags` instance for `service_id`.

*`PlayOnAPI.services(self)`*:
Fetch the list of services.

*`PlayOnAPI.suburl(self, suburl, *, api_version=None, headers=None, base_url=None, **kw)`*:
Override `HTTPServiceAPI.suburl` with default
`headers={'Authorization':self.jwt}`.
- <a name="PlayOnCommand"></a>`class PlayOnCommand(cs.cmdutils.BaseCommand)`: Playon command line implementation.

  Usage summary:

      Usage: playon [common-options...] subcommand [args...]

        Environment:
          PLAYON_USER               PlayOn login name, default from $EMAIL.
          PLAYON_PASSWORD           PlayOn password.
                                    This is obtained from .netrc if omitted.
          PLAYON_FILENAME_FORMAT  Format string for downloaded filenames.
                                    Default: {series_prefix}{series_episode_name}--{resolution}--{playon.ProviderID}--playon--{playon.ID}
          PLAYON_TAGS_DBURL         Location of state tags database.
                                    Default: ~/var/playon.sqlite

        Recording specification:
          an int        The specific recording id.
          all           All known recordings.
          downloaded    Recordings already downloaded.
          expired       Recording which are no longer available.
          pending       Recordings not already downloaded.
          /regexp       Recordings whose Series or Name match the regexp,
                        case insensitive.
        Subcommands:
          account [common-options...]
            Report account state.
          api [common-options...] suburl
            GET suburl via the API, print result.
          cds [common-options...] suburl
            GET suburl via the content delivery API, print result.
            Example subpaths:
              content
              content/provider-name
          dl [common-options...] [recordings...]
            Download the specified recordings, default "pending".
            Options:
              -j dl-jobs  Concurrent download jobs.
          downloaded [common-options...] recordings...
            Mark the specified recordings as downloaded and no longer pending.
          feature [common-options...] [feature_id]
            List features.
            Options:
              -l  Long mode.
          help [common-options...] [-l] [-s] [subcommand-names...]
            Print help for subcommands.
            This outputs the full help for the named subcommands,
            or the short help for all subcommands if no names are specified.
            Options:
              -l  Long listing.
              -r  Recurse into subcommands.
              -s  Short listing.
          info [common-options...] [field-names...]
            Recite general information.
            Explicit field names may be provided to override the default listing.
          ls [common-options...] [recordings...]
            List available downloads.
            Options:
              -l            Long listing: list tags below each entry.
              -o ls-format  Format string for each entry. Default format:
                            {playon.ID} {playon.HumanSize} {resolution} {nice_name} {playon.ProviderID} {status:upper}
          poll [common-options...] [options...]
          q [common-options...] [recordings...]
            List queued recordings.
            Options:
              -l               Long listing: list tags below each entry.
              -o queue-format  Format string for each entry. Default format:
                               {playon.ID} {playon.Series} {playon.Name} {playon.ProviderID}
          queue [common-options...] [recordings...]
            List queued recordings.
            Options:
              -l               Long listing: list tags below each entry.
              -o queue-format  Format string for each entry. Default format:
                               {playon.ID} {playon.Series} {playon.Name} {playon.ProviderID}
          refresh [common-options...] [queue] [recordings]
            Update the db state from the PlayOn service.
          rename [common-options...] [-o filename_format] filenames...
            Rename the filenames according to their fstags.
            -n    No action, dry run.
            -o filename_format
                  Format for the new filename, default '{series_prefix}{series_episode_name}--{resolution}--{playon.ProviderID}--playon--{playon.ID}'.
            Options:
              -o filename-format  Filename format.
          repl [common-options...]
            Run a REPL (Read Evaluate Print Loop), an interactive Python prompt.
            Options:
              --banner banner  Banner.
          service [common-options...] [service_id]
            List services.
          shell [common-options...]
            Run a command prompt via cmd.Cmd using this command's subcommands.

*`PlayOnCommand.Options`*

*`PlayOnCommand.cmd_account(self, argv)`*:
Usage: {cmd}
Report account state.

*`PlayOnCommand.cmd_api(self, argv)`*:
Usage: {cmd} suburl
GET suburl via the API, print result.

*`PlayOnCommand.cmd_cds(self, argv)`*:
Usage: {cmd} suburl
GET suburl via the content delivery API, print result.
Example subpaths:
  content
  content/provider-name

*`PlayOnCommand.cmd_dl(self, argv)`*:
Usage: {cmd} [recordings...]
Download the specified recordings, default "pending".
Options:
  -j dl-jobs  Concurrent download jobs.

*`PlayOnCommand.cmd_downloaded(self, argv, locale='en_US')`*:
Usage: {cmd} recordings...
Mark the specified recordings as downloaded and no longer pending.

*`PlayOnCommand.cmd_feature(self, argv, locale='en_US')`*:
Usage: {cmd} [feature_id]
List features.
Options:
  -l  Long mode.

*`PlayOnCommand.cmd_ls(self, argv)`*:
Usage: {cmd} [recordings...]
List available downloads.
Options:
  -l            Long listing: list tags below each entry.
  -o ls-format  Format string for each entry. Default format:
                {LS_FORMAT}

*`PlayOnCommand.cmd_q(self, argv)`*:
Usage: {cmd} [recordings...]
List queued recordings.
Options:
  -l               Long listing: list tags below each entry.
  -o queue-format  Format string for each entry. Default format:
                   {QUEUE_FORMAT}

*`PlayOnCommand.cmd_queue(self, argv)`*:
Usage: {cmd} [recordings...]
List queued recordings.
Options:
  -l               Long listing: list tags below each entry.
  -o queue-format  Format string for each entry. Default format:
                   {QUEUE_FORMAT}

*`PlayOnCommand.cmd_refresh(self, argv)`*:
Usage: {cmd} [queue] [recordings]
Update the db state from the PlayOn service.

*`PlayOnCommand.cmd_rename(self, argv, *, fstags: Optional[cs.fstags.FSTags] = <function <lambda> at 0x10cfa7a60>)`*:
Usage: {cmd} [-o filename_format] filenames...
Rename the filenames according to their fstags.
-n    No action, dry run.
-o filename_format
      Format for the new filename, default {DEFAULT_FILENAME_FORMAT!r}.
Options:
  -o filename-format  Filename format.

*`PlayOnCommand.cmd_service(self, argv, locale='en_US')`*:
Usage: {cmd} [service_id]
List services.

*`PlayOnCommand.run_context(self)`*:
Prepare the `PlayOnAPI` around each command invocation.
- <a name="PlayonSeriesEpisodeInfo"></a>`class PlayonSeriesEpisodeInfo(cs.mediainfo.SeriesEpisodeInfo)`: A `SeriesEpisodeInfo` with a `from_Recording()` factory method to build
  one from a PlayOn `Recording` instead or other mapping with `playon.*` keys.

*`PlayonSeriesEpisodeInfo.from_Recording(R: Mapping[str, Any])`*:
Infer series episode information from a `Recording`
or any mapping with ".playon.*" keys.
- <a name="PlayOnSQLTags"></a>`class PlayOnSQLTags(cs.sqltags.SQLTags)`: PlayOn flavoured `SQLTags`; it just has custom values for the default db location.
- <a name="Recording"></a>`class Recording(_PlayOnEntity)`: A PlayOn recording.

*`Recording.filename(self, filename_format=None) -> str`*:
Return the computed filename per `filename_format`,
default from `DEFAULT_FILENAME_FORMAT`: `'{series_prefix}{series_episode_name}--{resolution}--{playon.ProviderID}--playon--{playon.ID}'`.

*`Recording.is_available(self)`*:
Is a recording available for download?

*`Recording.is_downloaded(self)`*:
Test whether this recording has been downloaded
based on the presence of a `download_path` `Tag`
or a true `downloaded` `Tag`.

*`Recording.is_expired(self)`*:
Test whether this recording is expired,
which implies that it is no longer available for download.

*`Recording.is_pending(self)`*:
A pending download: available and not already downloaded.

*`Recording.is_queued(self)`*:
Is a recording still in the queue?

*`Recording.ls(self, *, format=None, long_mode=False, print_func=None)`*:
List a recording.

*`Recording.nice_name(self)`*:
A nice name for the recording: the PlayOn series and name,
omitting the series if that is `None`.

*`Recording.recording_id(self)`*:
The recording id or `None`.

*`Recording.refresh_needed(self, **kw)`*:
Override for `Refreshable.refresh_needed` which always
returns `False` for expired recordings.

*`Recording.resolution(self)`*:
The recording resolution derived from the quality
via the `Recording.RECORDING_QUALITY` mapping.

*`Recording.series_prefix(self)`*:
Return a series prefix for recording containing the series name
and season and episode, or `''`.

*`Recording.status(self)`*:
Return a short status string.

# Release Log



*Release 20260531*:
* DOwnloads: use cs.rfc2616.content_length to get the download content-length, open the download file with 'xb'.
* PlayOnAPI is now the source of the sqltags, do not expect to access it outside.
* New _PlayOnEntity(HasSQLTags) base class for Recording and LoginState.
* PlayOnAPI: new .login_userid property for external use.
* PlayOnAPI.__getitem__: an int index is promoted to ('recording,str(index)) for lookup.
* PlayOnAPI: new .recordings() finding playon.recording.*; use it for __iter__.
* PlayOnAPI: new .recording_ids_from_str() method for use by the CLI.
* PlayOnAPI: use the new UsesTagSets stuff.
* The PlayOn API has started reciting times with a microsecond field.
* PlayOnAPI.from_playon_date: accomodate time formats with and without microseconds.
* Recording.STALE_AGE -> Recording.refresh_lifespan for Refreshable.
* PlayOnCommand.cmd_dl: use pmap() to run the downloads in parallel.
* Assorted other internal changes.

*Release 20241007*:
Make things more cancellable.

*Release 20240723*:
* Replace many raises of RuntimeError with NotImplementedError, suggestion by @dimaqq on discuss.python.org.
* This update tracks some bugfixes in some required modules.

*Release 20240522*:
New superior tv-series/season/episode inference.

*Release 20240316*:
Fixed release upload artifacts.

*Release 20240201.1*:
Release with "playon" script.

*Release 20240201*:
* PlayOnCommand.cmd_dl: collapse dashes more reliably, restore the space squashing, make the downloads interruptable.
* PlayOnCommand._list: sort each listing argument by recording id.

*Release 20230705*:
DEFAULT_FILENAME_FORMAT: replace naive playon.Name with series_episode_name which is the name with leading series/episode info removed, honour in "playon dl".

*Release 20230703*:
* PlayOnAPI: features, feature, featured_image_url, service_image_url.
* PlayOnCommand: new cmd_feature like cmd_service but for featured shows.
* PlayOnAPI.suburl: infer _base_url from api_version if _base_url is None and api_version is provided.
* Recording.is_downloaded: also check for a 'downloaded' tag, fallback for when the downloaded_path is empty.
* PlayOnCommand.cmd_downloaded: add 'downloaded" tag to specified recordings.

*Release 20230217*:
* Move some core stuff off into cs.service_api.HTTPServiceAPI.
* Move core Recording.is_stale() method to TagSet.is_stale(), leave override method behind.
* Persist login tokens in a db for reuse while still fresh.
* "playon dl": allow interrupting downloads.
* Cleaner handling of playon.Name having a leading SNNeNN prefix.

*Release 20221228*:
* PlayOnAPI.suburl_data: progress reporting, raise on bad response, upgrade JSON error warning.
* PlayOnAPI: use a common cookie jar across API calls.
* PlayOnCommand: new "api" and "cds" API access subcommands.
* PlayOnCommand._refresh_sqltags_data: bugfix "expired cache" logic.
* PlayOnCommand: new "poll" subcommand reporting the API notifications response.

*Release 20220311*:
Bugfix criteria for refreshing the PlayOn state.

*Release 20211212*:
Initial release.
