Metadata-Version: 2.4
Name: cs-app-playon
Version: 20260914
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>=20260912
Requires-Dist: cs.context>=20250528
Requires-Dist: cs.deco>=20260912
Requires-Dist: cs.fileutils>=20210731
Requires-Dist: cs.fstags>=20260912
Requires-Dist: cs.lex>=20260912
Requires-Dist: cs.logutils>=20260912
Requires-Dist: cs.mediainfo>=20260914
Requires-Dist: cs.obj>=20260912
Requires-Dist: cs.pfx>=20210731
Requires-Dist: cs.progress>=20260531
Requires-Dist: cs.resources>=20250915
Requires-Dist: cs.rfc2616>=20260531
Requires-Dist: cs.seq>=20260912
Requires-Dist: cs.service_api>=20260914
Requires-Dist: cs.sqltags>=20260914
Requires-Dist: cs.tagged>=20260912
Requires-Dist: cs.threads>=20260912
Requires-Dist: cs.units>=20260526
Requires-Dist: cs.upd>=20260912
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.



Short summary:


* `Feature`: A PlayOn featured show record.


* `main`: Playon command line mode; see the `PlayOnCommand` class below.


* `PlayOnAPI`: Access to the PlayOn API.


* `PlayOnCommand`: Playon command line implementation.


* `PlayOnSQLTags`: PlayOn flavoured `SQLTags`; it just has custom values for the default db location.


* `Recording`: A PlayOn recording.


* `Service`: A PlayOn service description.

# Functions

## main(argv=None)

Playon command line mode;
see the `PlayOnCommand` class below.

# Classes

## class Feature(_PlayOnEntity)

A PlayOn featured show record. I think.

### `Feature.TYPE_SUBNAME`

    'feature'

### `Feature.format_attributes`

    {'json': <function Entity.json at 0x10dd48400>}

## class PlayOn(cs.tagged.Entities, cs.obj.Refreshable)



### `PlayOn.TYPE_CONVERSIONS`

    {   <class 'cs.app.playon.Recording'>: {   'Episode': <class 'int'>,
                                               'ReleaseYear': <class 'int'>,
                                               'Season': <class 'int'>}}

### `PlayOn.TYPE_ZONE`

    'playon'

### `PlayOn.__getitem__(self, index: tuple | int) -> cs.tagged.Entity`

If `index` is an `int` return the associated `Recording`.
Otherwise `index` should be a `tuple`, returns the associated `Entity`.

### `PlayOn.__iter__(self) -> Iterable[cs.app.playon.Recording]`

Iteration iterates over the recordins.

### `PlayOn.all_recordings(self) -> Iterable[cs.app.playon.Recording]`

A generator yielding all the `Recording`s from the database.
Note that this includes both recorded, queued, and expired items.

### `PlayOn.features(self) -> set[cs.app.playon.Feature]`

Return a set of `Feature`s known to the API.

### `PlayOn.ls(self, recording_specs, *, format: str, long_mode=False)`

List the specified recordings.

### `PlayOn.queue(self) -> set[cs.app.playon.Recording]`

Return a set of `Recording`s in the queue.

### `PlayOn.recording_ids_from_str(self, arg)`

Convert a string to a list of recording ids.

### `PlayOn.recordings(self) -> set[cs.app.playon.Recording]`

Return a set of `Recording`s known to the API.

### `PlayOn.services(self) -> set[cs.app.playon.Service]`

Return a set of `Service`s known to the API.

## class PlayOnAPI(cs.obj.SingletonMixin, cs.service_api.HTTPServiceAPI)

Access to the PlayOn API.

### `PlayOnAPI.API_AUTH_GRACETIME`

    30

### `PlayOnAPI.API_BASE`

    'https://api.playonrecorder.com/v3/'

### `PlayOnAPI.API_HOSTNAME`

    'api.playonrecorder.com'

### `PlayOnAPI.CDS_BASE`

    'https://cds.playonrecorder.com/api/v6/'

### `PlayOnAPI.CDS_BASE_LOCAL`

    'https://cds-au.playonrecorder.com/api/v6/'

### `PlayOnAPI.CDS_HOSTNAME`

    'cds.playonrecorder.com'

### `PlayOnAPI.CDS_HOSTNAME_LOCAL`

    'cds-au.playonrecorder.com'

### `PlayOnAPI.PLAYON_ACCOUNT_ENVVAR`

    'PLAYON_ACCOUNT'

### `PlayOnAPI.__getitem__(self, index: tuple | int) -> cs.tagged.Entity`

If `index` is an `int` return the associated `Recording`.
Otherwise `index` should be a `tuple`, returns the associated `Entity`.

### `PlayOnAPI.account(self)`

Return account information.

### `PlayOnAPI.auth_token`

    <property object at 0x11079aca0>

### `PlayOnAPI.cdsurl_data(self, suburl, method='GET', headers=None, **kw)`

Wrapper for `suburl_data` using `CDS_BASE` as the base URL.

### `PlayOnAPI.default_user_id()`

The default `login_userid` comes from the netrc entry for `cls.API_HOSTNAME`.

### `PlayOnAPI.download(self, download_id: int, filename=None, *, fstags: cs.fstags.FSTags, quiet: bool, runstate: Optional[cs.resources.RunState] = <function uses_runstate.<locals>.<lambda> at 0x1107ae2a0>, verbose: bool) -> tuple[str, dict]`

Download the file with `download_id` to `filename_basis`.
Return a 2-tuple of `(saved,api_data)` being the saved
filesystem path and the data component of the API response.

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.featured_image_url(self, feature_name: str)`

URL of the image for a featured show.

### `PlayOnAPI.features(self) -> list[dict]`

Fetch the list of featured shows.

### `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.login(self, login_subpath='login')`

Perform a login, return the resulting `dict`.
*This does not* update the state of `self`.*

The `.login_state` tracks the current auth state.

### `PlayOnAPI.notifications(self)`

Return the notifications.

### `PlayOnAPI.queue(self) -> list[dict]`

Return a list of the queued recording entries.

### `PlayOnAPI.recordings(self, *, verbose=False) -> list[dict]`

Return a list of the available recording entries.

### `PlayOnAPI.renew_jwt(self)`

UNUSED

### `PlayOnAPI.services(self) -> list[dict]`

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}`.

## 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.
            -o filename-format  Filename format.
        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...]
          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(cmd: Optional[str] = None, dry_run: bool = False, force: bool = False, verbosity: int = 0, runstate: Optional[cs.resources.RunState] = None, runstate_signals: Tuple[int] = (<Signals.SIGHUP: 1>, <Signals.SIGINT: 2>, <Signals.SIGQUIT: 3>, <Signals.SIGTERM: 15>), ssh_exe: str = <factory>, user: Optional[str] = <factory>, password: Optional[str] = <factory>, dl_jobs: int = 2, filename_format: str = <factory>, ls_format: str = '{playon.ID} {playon.HumanSize} {resolution} {nice_name} {playon.ProviderID} {status:upper}', queue_format: str = '{playon.ID} {playon.Series} {playon.Name} {playon.ProviderID}') -> None`

Options(cmd: Optional[str] = None, dry_run: bool = False, force: bool = False, verbosity: int = 0, runstate: Optional[cs.resources.RunState] = None, runstate_signals: Tuple[int] = (<Signals.SIGHUP: 1>, <Signals.SIGINT: 2>, <Signals.SIGQUIT: 3>, <Signals.SIGTERM: 15>), ssh_exe: str = <factory>, user: Optional[str] = <factory>, password: Optional[str] = <factory>, dl_jobs: int = 2, filename_format: str = <factory>, ls_format: str = '{playon.ID} {playon.HumanSize} {resolution} {nice_name} {playon.ProviderID} {status:upper}', queue_format: str = '{playon.ID} {playon.Series} {playon.Name} {playon.ProviderID}')

### `PlayOnCommand.USAGE_FORMAT`

    ('Usage: {cmd} subcommand [args...]\n'
     '\n'
     '    Environment:\n'
     '      PLAYON_USER               PlayOn login name, default from $EMAIL.\n'
     '      PLAYON_PASSWORD           PlayOn password.\n'
     '                                This is obtained from .netrc if omitted.\n'
     '      {FILENAME_FORMAT_ENVVAR}  Format string for downloaded filenames.\n'
     '                                Default: {DEFAULT_FILENAME_FORMAT}\n'
     '      {PLAYON_DBURL_ENVVAR:17}         Location of state tags database.\n'
     '                                Default: {PLAYON_DBURL_DEFAULT}\n'
     '\n'
     '    Recording specification:\n'
     '      an int        The specific recording id.\n'
     '      all           All known recordings.\n'
     '      downloaded    Recordings already downloaded.\n'
     '      expired       Recording which are no longer available.\n'
     '      pending       Recordings not already downloaded.\n'
     '      /regexp       Recordings whose Series or Name match the regexp,\n'
     '                    case insensitive.\n'
     '  ')

### `PlayOnCommand.USAGE_KEYWORDS`

    {   'DEFAULT_DL_PARALLELISM': 2,
        'DEFAULT_FILENAME_FORMAT': '{series_prefix}{series_episode_name}--{resolution}--{playon.ProviderID}--playon--{playon.ID}',
        'FILENAME_FORMAT_ENVVAR': 'PLAYON_FILENAME_FORMAT',
        'LS_FORMAT': '{playon.ID} {playon.HumanSize} {resolution} {nice_name} '
                     '{playon.ProviderID} {status:upper}',
        'PLAYON_DBURL_DEFAULT': '~/var/playon.sqlite',
        'PLAYON_DBURL_ENVVAR': 'PLAYON_TAGS_DBURL',
        'QUEUE_FORMAT': '{playon.ID} {playon.Series} {playon.Name} '
                        '{playon.ProviderID}'}

### `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.
  -o filename-format  Filename format.

### `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}
Update the db state from the PlayOn service.

### `PlayOnCommand.cmd_rename(*a, fstags: Optional[cs.fstags.FSTags] = <function uses_fstags.<locals>.<lambda> at 0x1107360c0>, **kw)`

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.

## class PlayOnLoginState(cs.service_api.LoginState)



## class PlayOnSQLTags(cs.sqltags.SQLTags)

PlayOn flavoured `SQLTags`; it just has custom values for the default db location.

### `PlayOnSQLTags.DBURL_DEFAULT`

    '~/var/playon.sqlite'

### `PlayOnSQLTags.DBURL_ENVVAR`

    'PLAYON_TAGS_DBURL'

## class Recording(_PlayOnEntity)

A PlayOn recording.

### `Recording.RECORDING_QUALITY`

    {1: '720p', 2: '1080p'}

### `Recording.TYPE_SUBNAME`

    'recording'

### `Recording.as_SeriesEpisodeInfo(self) -> cs.mediainfo.SeriesEpisodeInfo`

Infer series episode information from a `Recording`
(or any mapping with "playon.*" keys).

### `Recording.download(self, filename: str, *, fstags: cs.fstags.FSTags, playon_api: cs.app.playon.PlayOnAPI, runstate: Optional[cs.resources.RunState] = <function uses_runstate.<locals>.<lambda> at 0x110734180>)`

Download this recording to `filename`.

### `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.format_attributes`

    {   'is_available': <function Recording.is_available at 0x1107afba0>,
        'is_downloaded': <function Recording.is_downloaded at 0x1107afce0>,
        'is_expired': <function Recording.is_expired at 0x1107afe20>,
        'is_pending': <function Recording.is_pending at 0x1107afd80>,
        'is_queued': <function Recording.is_queued at 0x1107afc40>,
        'json': <function Entity.json at 0x10dd48400>,
        'nice_name': <function Recording.nice_name at 0x1107af920>,
        'recording_id': <function Recording.recording_id at 0x1107af6a0>,
        'resolution': <function Recording.resolution at 0x1107af560>,
        'series_episode_name': <function Recording.series_episode_name at 0x1107afb00>,
        'series_prefix': <function Recording.series_prefix at 0x1107afa60>,
        'status': <function Recording.status at 0x1107af9c0>}

### `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_lifespan`

    600

### `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.sei`

    <functools.cached_property object at 0x11077f4d0>

### `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.

## class Service(_PlayOnEntity)

A PlayOn service description.

### `Service.TYPE_SUBNAME`

    'service'

### `Service.format_attributes`

    {'json': <function Entity.json at 0x10dd48400>}
