Skip to content

Commit

Permalink
Refactor (#12)
Browse files Browse the repository at this point in the history
- Refactor to use typer instead of click
- Implement a command subcommand style, with two subcommands: search, get-server-info
- Change some syntaxes during the refactor
- Improve options to show various properties like summary, year, etc..
- Require python 3.8
- Bump to version 0.4.0
  • Loading branch information
danielhoherd authored Jan 24, 2022
1 parent a8c37d4 commit 2d1b8d1
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 81 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pre-commit-action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: 3.7
python-version: 3.8
- name: Install dependencies
run: |
python -m pip install pre-commit
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ repos:
rev: v2.31.0
hooks:
- id: pyupgrade
args: ["--py37-plus"]
args: ["--py38-plus"]
- repo: https://github.com/pycqa/isort
rev: 5.10.1
hooks:
Expand Down
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,15 @@ test: ## Run tests
requirements-dev: .requirements-dev ## Install dev requirements
.requirements-dev:
pip3 install --user --upgrade poetry
poetry run pip install --quiet --upgrade pip setuptools wheel
poetry install
touch .requirements-dev .requirements

.PHONY: requirements
requirements: .requirements ## Install requirements
.requirements:
pip3 install --user --upgrade poetry
poetry run pip install --quiet --upgrade pip setuptools wheel
poetry install --no-dev
touch .requirements

Expand Down
84 changes: 58 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ The purpose of this tool is to allow you to download non-transcoded media from P

## Run via Docker

```
export PLEXDL_USER=whoever
export PLEXDL_PASS=hunter2
alias plexdl='docker run --rm -e PLEXDL_USER -e PLEXDL_PASS quay.io/danielhoherd/plexdl plexdl'
plexdl $movie_title
```sh
export PLEXDL_USERNAME=whoever
export PLEXDL_PASSWORD=hunter2
alias plexdl="docker run --rm -e PLEXDL_USERNAME -e PLEXDL_PASSWORD quay.io/danielhoherd/plexdl plexdl --show-ratings"
plexdl "some movie title"
```

## Local Installation

```
```sh
git clone https://github.com/danielhoherd/plexdl.git
pip3 install ./plexdl
```
Expand All @@ -25,29 +25,58 @@ pip3 install ./plexdl

```
$ plexdl --help
Usage: plexdl [OPTIONS] TITLE
Usage: plexdl [OPTIONS] COMMAND [ARGS]...
plexdl CLI.
Options:
--install-completion [bash|zsh|fish|powershell|pwsh]
Install completion for the specified shell.
--show-completion [bash|zsh|fish|powershell|pwsh]
Show completion for the specified shell, to
copy it or customize the installation.
--help Show this message and exit.
Commands:
get-server-info Show info about servers available to your account.
search Search for media in servers that are available to your...
$ plexdl get-server-info --help
Usage: plexdl get-server-info [OPTIONS] [USERNAME] [PASSWORD] [DEBUG]
Show info about servers available to your account.
Arguments:
[USERNAME] [env var: PLEXDL_USERNAME]
[PASSWORD] [env var: PLEXDL_PASSWORD]
[DEBUG] [env var: PLEXDL_DEBUG;default: False]
Options:
--help Show this message and exit.
$ plexdl search --help
Usage: plexdl search [OPTIONS] TITLE
Search for media in servers that are available to your account.
Searches your plex account for media matching the given string, then
prints out download commands.
Arguments:
TITLE [env var: PLEXDL_TITLE;required]
Options:
-v Increase verbosity (max -vvvv)
-u, --username TEXT Your Plex username (env PLEXDL_USER)
-p, --password TEXT Your Plex password (env PLEXDL_PASS)
-r, --relay / --no-relay Output relay servers along with direct
servers
--item-prefix TEXT String to prefix to each item (eg: curl -o)
--server-info / --no-server-info
Output summary about each server
--summary / --no-summary Output summary about each result
--ratings / --no-ratings Output rating information for each result
--metadata / --no-metadata Output media metadata about each file for
each result
--help Show this message and exit.$ export PLEXDL_USER='foo'
-u, --username TEXT [env var: PLEXDL_USERNAME; required]
-p, --password TEXT [env var: PLEXDL_PASSWORD; required]
--item-prefix TEXT String to prefix to each item (eg: curl -o)
--show-summary Show media summary for each result
--show-ratings Show ratings for each result
--show-metadata Show file and codec metadata for each file in each result
--include-relays Output relay servers along with direct servers
-v, --verbose
--debug
--help Show this message and exit.
```

```
$ plexdl living
$ plexdl search living
===============================================================================
Server: "demo-server-1"
-------------------------------------------------------------------------------
Expand All @@ -56,15 +85,17 @@ Movie: Night of the Living Dead
```

```
$ plexdl --ratings --metadata --summary living
$ plexdl search --show-ratings --show-metadata --show-summary living
===============================================================================
Server: "demo-server-2"
-------------------------------------------------------------------------------
Movie: Night of the Living Dead
Year: 1968
Studio: Image Ten
Summary: A ragtag group of Pennsylvanians barricade themselves in an old farmhouse to remain safe from a bloodthirsty, flesh-eating breed of monsters who are ravaging the East Coast of the United States.
Content rating: NR
Audience rating: 7.7
Critic rating: 5.5
Rated: NR
(1280x672, h264, ac3, 4208kbps)
"Night of the Living Dead.mkv" "https://...some_url..."
```
Expand All @@ -73,5 +104,6 @@ Rated: NR

- cleanup
- tests
- add option to search for specific types of content
- add option to search only for specific types of content
- add option to search only specific serveres
- don't try to name files with characters that would be invalid
4 changes: 2 additions & 2 deletions plexdl/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Shim for package execution (python3 -m plexdl ...)."""
from .cli import main
from .cli import app

if __name__ == "__main__": # pragma: no cover
main()
app()
121 changes: 91 additions & 30 deletions plexdl/cli.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
"""plexdl CLI."""
import datetime
import logging
import os
import sys

import humanize
import plexapi
from click import argument, command, echo, option, version_option
import typer
from importlib_metadata import version

__version__ = version(__package__)

from plexapi.myplex import MyPlexAccount
from plexapi.server import PlexServer

from plexdl.plexdl import Client


Expand All @@ -17,40 +22,96 @@ def get_logger(ctx, param, value):
logging.basicConfig(format="%(message)s")
log = logging.getLogger("plexdl")
if value > 0:
log.setLevel("DEBUG") # https://docs.python.org/3.7/library/logging.html#logging-levels
log.setLevel("DEBUG") # https://docs.python.org/3.9/library/logging.html#logging-levels
return value


@command()
@option("-v", count=True, help="Be verbose", callback=get_logger)
@option("-u", "--username", help="Your Plex username (env PLEXDL_USER)", envvar="PLEXDL_USER")
@option("-p", "--password", help="Your Plex password (env PLEXDL_PASS)", envvar="PLEXDL_PASS")
@option("-r", "--relay/--no-relay", default=False, help="Output relay servers along with direct servers")
@option("--item-prefix", default="", help="String to prefix to each item (eg: curl -o)", envvar="PLEXDL_ITEM_PREFIX")
@option("--server-info/--no-server-info", default=False, help="Output summary about each server")
@option("--summary/--no-summary", default=False, help="Output summary about each result")
@option("--ratings/--no-ratings", default=False, help="Output rating information for each result")
@option("--metadata/--no-metadata", default=False, help="Output media metadata about each file for each result")
@version_option(version=__version__)
@argument("title", envvar="PLEXDL_TITLE")
def main(username, password, title, relay, server_info, item_prefix, summary, ratings, metadata, v):
"""Search your plex account for media matching the given string, then prints out download commands."""
app = typer.Typer(help=__doc__)


@app.command()
def get_server_info(
username: str = typer.Argument(None, envvar="PLEXDL_USERNAME"),
password: str = typer.Argument(None, envvar="PLEXDL_PASSWORD"),
debug: bool = typer.Argument(False, envvar="PLEXDL_DEBUG"),
):
"""Show info about servers available to your account."""
p = MyPlexAccount(
username=username,
password=password,
)
servers = p.resources()
for server in servers:
if server.product == "Plex Media Server":
print(f"{server.name=} ", "-" * 70)
print(f" clientIdentifier: {server.clientIdentifier}")
last_seen_diff = datetime.datetime.now() - server.lastSeenAt
last_seen_time_delta = humanize.naturaldelta(last_seen_diff)
print(f" lastSeenAt: {server.lastSeenAt.strftime('%FT%T%z')} ({last_seen_time_delta})")
created_diff = datetime.datetime.now() - server.createdAt
created_time_delta = humanize.naturaldelta(created_diff)
print(f" createdAt: {server.createdAt.strftime('%FT%T%z')} ({created_time_delta})")
for i, connection in enumerate(server.connections):
preferred = connection.uri in server.preferred_connections()
print(f" connections[{i}]:")
print(f" local: {connection.local}")
print(f" uri: {connection.uri}")
print(f" httpuri: {connection.httpuri}")
print(f" relay: {bool(connection.relay)}")
print(f" preferred: {preferred}")
print(f" device: {server.device}")
print(f" home: {server.home}")
print(f" httpsRequired: {server.httpsRequired}")
print(f" name: {server.name}")
print(f" owned: {server.owned}")
print(f" ownerid: {server.ownerid}")
print(f" platform: {server.platform}")
print(f" platformVersion: {server.platformVersion}")
print(f" presence: {server.presence}")
print(f" product: {server.product}")
print(f" productVersion: {server.productVersion}")
print(f" provides: {server.provides}")
print(f" publicAddressMatches: {server.publicAddressMatches}")
print(f" sourceTitle: {server.sourceTitle}")
print(f" synced: {server.synced}")
print("")


@app.command()
def search(
username: str = typer.Option(..., "-u", "--username", envvar="PLEXDL_USERNAME"),
password: str = typer.Option(..., "-p", "--password", envvar="PLEXDL_PASSWORD"),
title: str = typer.Argument(..., envvar="PLEXDL_TITLE"),
item_prefix: str = typer.Option("", "--item-prefix", help="String to prefix to each item (eg: curl -o)"),
show_summary: bool = typer.Option(False, "--show-summary", help="Show media summary for each result"),
show_ratings: bool = typer.Option(False, "--show-ratings", help="Show ratings for each result"),
show_metadata: bool = typer.Option(False, "--show-metadata", help="Show file and codec metadata for each file in each result"),
include_relays: bool = typer.Option(False, "--include-relays", help="Output relay servers along with direct servers"),
verbose: bool = typer.Option(False, "--verbose", "-v"),
debug: bool = typer.Option(False, "--debug"),
):
"""Search for media in servers that are available to your account."""
p = Client(
username=username,
password=password,
title=title,
relay=include_relays,
debug=debug,
item_prefix=item_prefix,
summary=show_summary,
ratings=show_ratings,
metadata=show_metadata,
)
p.main()


if __name__ == "__main__":
try:
p = Client(
username=username,
password=password,
title=title,
relay=relay,
debug=v,
item_prefix=item_prefix,
server_info=server_info,
summary=summary,
ratings=ratings,
metadata=metadata,
)

p.main()
app()
except KeyboardInterrupt:
sys.exit(1)
except plexapi.exceptions.BadRequest:
sys.exit(2)


# https://typer.tiangolo.com/tutorial/subcommands/add-typer/
14 changes: 5 additions & 9 deletions plexdl/plexdl.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ def __init__(self, **kwargs):
self.password = kwargs["password"]
self.ratings = kwargs["ratings"]
self.relay = kwargs["relay"]
self.server_info = kwargs["server_info"]
self.summary = kwargs["summary"]
self.title = kwargs["title"]
self.username = kwargs["username"]
Expand Down Expand Up @@ -58,7 +57,7 @@ def print_item_info(self, item, access_token):
media_info.append(f"{length}")
except ValueError:
pass
if len(media_info) > 0:
if media_info:
print(f'({", ".join(media_info)})')
print(f' {self.item_prefix} "{download_filename}" "{download_url}"')

Expand All @@ -69,14 +68,16 @@ def print_all_items_for_server(self, item, access_token):
print("-" * 79)
print(f"{item.TYPE.capitalize()}: {item.title}")
if self.summary is True and len(item.summary) > 1:
print(f"Year: {item.year}")
print(f"Studio: {item.studio}")
print(f"Summary: {item.summary}")
if self.ratings is True:
if item.contentRating:
print(f"Content rating: {item.contentRating}")
if item.audienceRating:
print(f"Audience rating: {item.audienceRating}")
if item.rating:
print(f"Critic rating: {item.rating}")
if item.contentRating:
print(f"Rated: {item.contentRating}")
self.print_item_info(self, item, access_token)
elif item.TYPE in ["show"]:
print("-" * 79)
Expand Down Expand Up @@ -112,11 +113,6 @@ def main(self):
print("\n")
print("=" * 79)
print(f'Server: "{this_server_connection.friendlyName}"{relay_status}')
if self.server_info is True:
print(
f'Plex version: {this_server_connection.version}\n"'
f"OS: {this_server_connection.platform} {this_server_connection.platformVersion}"
)

# TODO: add flags for each media type to help sort down what is displayed (since /hub/seach?mediatype="foo" doesn't work)
# TODO: write handlers for each type
Expand Down
Loading

0 comments on commit 2d1b8d1

Please sign in to comment.