emby-cli 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- emby_cli-0.1.0/.env.example +14 -0
- emby_cli-0.1.0/.github/workflows/ci.yml +33 -0
- emby_cli-0.1.0/.github/workflows/publish.yml +44 -0
- emby_cli-0.1.0/.gitignore +11 -0
- emby_cli-0.1.0/CHANGELOG.md +5 -0
- emby_cli-0.1.0/LICENSE +20 -0
- emby_cli-0.1.0/PKG-INFO +113 -0
- emby_cli-0.1.0/README.md +79 -0
- emby_cli-0.1.0/pyproject.toml +68 -0
- emby_cli-0.1.0/src/emby_cli/__init__.py +6 -0
- emby_cli-0.1.0/src/emby_cli/__main__.py +4 -0
- emby_cli-0.1.0/src/emby_cli/_version.py +24 -0
- emby_cli-0.1.0/src/emby_cli/cli.py +134 -0
- emby_cli-0.1.0/src/emby_cli/client.py +499 -0
- emby_cli-0.1.0/src/emby_cli/commands/__init__.py +1 -0
- emby_cli-0.1.0/src/emby_cli/commands/batch.py +244 -0
- emby_cli-0.1.0/src/emby_cli/commands/download.py +77 -0
- emby_cli-0.1.0/src/emby_cli/commands/list.py +35 -0
- emby_cli-0.1.0/src/emby_cli/commands/play.py +129 -0
- emby_cli-0.1.0/src/emby_cli/commands/search.py +31 -0
- emby_cli-0.1.0/src/emby_cli/commands/sync.py +54 -0
- emby_cli-0.1.0/src/emby_cli/constants.py +9 -0
- emby_cli-0.1.0/src/emby_cli/download_ops.py +47 -0
- emby_cli-0.1.0/src/emby_cli/resolve.py +255 -0
- emby_cli-0.1.0/src/emby_cli/util.py +117 -0
- emby_cli-0.1.0/src/emby_cli/version.py +17 -0
- emby_cli-0.1.0/tests/test_parser.py +32 -0
- emby_cli-0.1.0/tests/test_resolve.py +52 -0
- emby_cli-0.1.0/tests/test_util.py +44 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Emby server (required)
|
|
2
|
+
EMBY_SERVER=http://emby.example:8096
|
|
3
|
+
|
|
4
|
+
# Auth: API key OR username/password
|
|
5
|
+
EMBY_API_KEY=
|
|
6
|
+
EMBY_USERNAME=
|
|
7
|
+
EMBY_PASSWORD=
|
|
8
|
+
|
|
9
|
+
# Optional defaults
|
|
10
|
+
# EMBY_OUTPUT=./downloads
|
|
11
|
+
# EMBY_ITEM_ID=
|
|
12
|
+
# EMBY_LIBRARY=
|
|
13
|
+
# EMBY_METHOD=download
|
|
14
|
+
# EMBY_PLAYER=vlc
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.9", "3.11", "3.12"]
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
with:
|
|
20
|
+
fetch-depth: 0
|
|
21
|
+
|
|
22
|
+
- name: Set up Python
|
|
23
|
+
uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: ${{ matrix.python-version }}
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: |
|
|
29
|
+
python -m pip install --upgrade pip
|
|
30
|
+
pip install -e ".[dev]"
|
|
31
|
+
|
|
32
|
+
- name: Run tests
|
|
33
|
+
run: pytest -q
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
publish:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
environment: pypi
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 0
|
|
20
|
+
|
|
21
|
+
- name: Set up Python
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.11"
|
|
25
|
+
|
|
26
|
+
- name: Install build tools
|
|
27
|
+
run: python -m pip install --upgrade pip build
|
|
28
|
+
|
|
29
|
+
- name: Build package
|
|
30
|
+
run: python -m build
|
|
31
|
+
|
|
32
|
+
- name: Verify version matches tag
|
|
33
|
+
run: |
|
|
34
|
+
TAG="${GITHUB_REF#refs/tags/}"
|
|
35
|
+
TAG="${TAG#v}"
|
|
36
|
+
WHEEL_VERSION=$(unzip -p dist/*.whl '*.dist-info/METADATA' | grep '^Version:' | cut -d' ' -f2)
|
|
37
|
+
echo "Tag: $TAG — Wheel: $WHEEL_VERSION"
|
|
38
|
+
test "$WHEEL_VERSION" = "$TAG"
|
|
39
|
+
|
|
40
|
+
- name: Check metadata
|
|
41
|
+
run: pip install twine && twine check dist/*
|
|
42
|
+
|
|
43
|
+
- name: Publish to PyPI
|
|
44
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
emby_cli-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Creative Commons Attribution-NonCommercial 4.0 International
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sergio Garcia <soukron@gmbros.net>
|
|
4
|
+
|
|
5
|
+
This work is licensed under the Creative Commons Attribution-NonCommercial
|
|
6
|
+
4.0 International License.
|
|
7
|
+
|
|
8
|
+
You are free to:
|
|
9
|
+
- Share — copy and redistribute the material in any medium or format
|
|
10
|
+
- Adapt — remix, transform, and build upon the material
|
|
11
|
+
|
|
12
|
+
Under the following terms:
|
|
13
|
+
- Attribution — You must give appropriate credit, provide a link to the
|
|
14
|
+
license, and indicate if changes were made.
|
|
15
|
+
- NonCommercial — You may not use the material for commercial purposes.
|
|
16
|
+
|
|
17
|
+
No additional restrictions — You may not apply legal terms or technological
|
|
18
|
+
measures that legally restrict others from doing anything the license permits.
|
|
19
|
+
|
|
20
|
+
Full license text: https://creativecommons.org/licenses/by-nc/4.0/legalcode
|
emby_cli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: emby-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI to list, search, play, and download/backup original media from an Emby server via its REST API.
|
|
5
|
+
Project-URL: Homepage, https://github.com/soukron/emby-cli
|
|
6
|
+
Project-URL: Repository, https://github.com/soukron/emby-cli
|
|
7
|
+
Project-URL: Issues, https://github.com/soukron/emby-cli/issues
|
|
8
|
+
Author-email: Sergio Garcia <soukron@gmbros.net>
|
|
9
|
+
Maintainer-email: Sergio Garcia <soukron@gmbros.net>
|
|
10
|
+
License-Expression: CC-BY-NC-4.0
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: backup,cli,download,emby,media
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
16
|
+
Classifier: License :: Free for non-commercial use
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Multimedia :: Video
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Requires-Dist: m3u8>=4.0
|
|
26
|
+
Requires-Dist: requests>=2.31
|
|
27
|
+
Requires-Dist: static-ffmpeg>=3.0
|
|
28
|
+
Requires-Dist: tqdm>=4.66
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: build>=1.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: twine>=5.0; extra == 'dev'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# emby-cli
|
|
36
|
+
|
|
37
|
+
[PyPI](https://pypi.org/project/emby-cli/)
|
|
38
|
+
[License: CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/)
|
|
39
|
+
|
|
40
|
+
CLI to **list, search, play, and download/backup** original media files from an [Emby](https://emby.media/) server via its REST API. Only HTTP access is required (default port 8096) — no SSH or rsync.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install emby-cli
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Development (from a clone of this repo):
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
52
|
+
pip install -e ".[dev]"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
In the gmbros.net ops tree (`emby-downloader/`), use the root Makefile instead:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
cd ~/.local/gmbros.net/emby-downloader
|
|
59
|
+
make install
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Configuration
|
|
63
|
+
|
|
64
|
+
Set environment variables (or pass CLI flags):
|
|
65
|
+
|
|
66
|
+
| Variable | Description |
|
|
67
|
+
|----------|-------------|
|
|
68
|
+
| `EMBY_SERVER` | Server URL (required) |
|
|
69
|
+
| `EMBY_API_KEY` | API key (or use username/password) |
|
|
70
|
+
| `EMBY_USERNAME` / `EMBY_PASSWORD` | Name/password auth |
|
|
71
|
+
| `EMBY_OUTPUT` | Download directory (default `./downloads`) |
|
|
72
|
+
| `EMBY_METHOD` | `download`, `stream`, or `hls` |
|
|
73
|
+
| `EMBY_PLAYER` | External player for `play` |
|
|
74
|
+
|
|
75
|
+
See `.env.example`. The app does not load `.env` itself — `source` it or export vars.
|
|
76
|
+
|
|
77
|
+
## Usage
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
emby-cli list
|
|
81
|
+
emby-cli search "Title"
|
|
82
|
+
emby-cli download -i <itemId> -o ./downloads
|
|
83
|
+
emby-cli sync -l "Movies"
|
|
84
|
+
emby-cli batch -F titles.txt -n
|
|
85
|
+
emby-cli play "Movie (2010)"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Download methods (`-m` / `EMBY_METHOD`):
|
|
89
|
+
|
|
90
|
+
- `download` — `GET /Items/{id}/Download`
|
|
91
|
+
- `stream` — DirectStreamUrl / `original.*` (Emby Web style)
|
|
92
|
+
- `hls` — HLS segments remuxed to `.mkv` (needs `static-ffmpeg`)
|
|
93
|
+
|
|
94
|
+
## Development
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
pip install -e ".[dev]"
|
|
98
|
+
pytest -q
|
|
99
|
+
python -m build && twine check dist/*
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Ops Makefile (gmbros.net host only, not in this repo):
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
cd ~/.local/gmbros.net/emby-downloader
|
|
106
|
+
make install / make test / make build / make check
|
|
107
|
+
make push
|
|
108
|
+
make release VERSION=0.1.0 # tag + gh release → CI publishes to PyPI
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
[CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/)
|
emby_cli-0.1.0/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# emby-cli
|
|
2
|
+
|
|
3
|
+
[PyPI](https://pypi.org/project/emby-cli/)
|
|
4
|
+
[License: CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/)
|
|
5
|
+
|
|
6
|
+
CLI to **list, search, play, and download/backup** original media files from an [Emby](https://emby.media/) server via its REST API. Only HTTP access is required (default port 8096) — no SSH or rsync.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install emby-cli
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Development (from a clone of this repo):
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
18
|
+
pip install -e ".[dev]"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
In the gmbros.net ops tree (`emby-downloader/`), use the root Makefile instead:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
cd ~/.local/gmbros.net/emby-downloader
|
|
25
|
+
make install
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Configuration
|
|
29
|
+
|
|
30
|
+
Set environment variables (or pass CLI flags):
|
|
31
|
+
|
|
32
|
+
| Variable | Description |
|
|
33
|
+
|----------|-------------|
|
|
34
|
+
| `EMBY_SERVER` | Server URL (required) |
|
|
35
|
+
| `EMBY_API_KEY` | API key (or use username/password) |
|
|
36
|
+
| `EMBY_USERNAME` / `EMBY_PASSWORD` | Name/password auth |
|
|
37
|
+
| `EMBY_OUTPUT` | Download directory (default `./downloads`) |
|
|
38
|
+
| `EMBY_METHOD` | `download`, `stream`, or `hls` |
|
|
39
|
+
| `EMBY_PLAYER` | External player for `play` |
|
|
40
|
+
|
|
41
|
+
See `.env.example`. The app does not load `.env` itself — `source` it or export vars.
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
emby-cli list
|
|
47
|
+
emby-cli search "Title"
|
|
48
|
+
emby-cli download -i <itemId> -o ./downloads
|
|
49
|
+
emby-cli sync -l "Movies"
|
|
50
|
+
emby-cli batch -F titles.txt -n
|
|
51
|
+
emby-cli play "Movie (2010)"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Download methods (`-m` / `EMBY_METHOD`):
|
|
55
|
+
|
|
56
|
+
- `download` — `GET /Items/{id}/Download`
|
|
57
|
+
- `stream` — DirectStreamUrl / `original.*` (Emby Web style)
|
|
58
|
+
- `hls` — HLS segments remuxed to `.mkv` (needs `static-ffmpeg`)
|
|
59
|
+
|
|
60
|
+
## Development
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install -e ".[dev]"
|
|
64
|
+
pytest -q
|
|
65
|
+
python -m build && twine check dist/*
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Ops Makefile (gmbros.net host only, not in this repo):
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
cd ~/.local/gmbros.net/emby-downloader
|
|
72
|
+
make install / make test / make build / make check
|
|
73
|
+
make push
|
|
74
|
+
make release VERSION=0.1.0 # tag + gh release → CI publishes to PyPI
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
[CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "emby-cli"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "CLI to list, search, play, and download/backup original media from an Emby server via its REST API."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "CC-BY-NC-4.0"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Sergio Garcia", email = "soukron@gmbros.net" },
|
|
15
|
+
]
|
|
16
|
+
maintainers = [
|
|
17
|
+
{ name = "Sergio Garcia", email = "soukron@gmbros.net" },
|
|
18
|
+
]
|
|
19
|
+
keywords = ["emby", "media", "download", "backup", "cli"]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 4 - Beta",
|
|
22
|
+
"Environment :: Console",
|
|
23
|
+
"Intended Audience :: End Users/Desktop",
|
|
24
|
+
"License :: Free for non-commercial use",
|
|
25
|
+
"Operating System :: OS Independent",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Programming Language :: Python :: 3.9",
|
|
28
|
+
"Programming Language :: Python :: 3.10",
|
|
29
|
+
"Programming Language :: Python :: 3.11",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Topic :: Multimedia :: Video",
|
|
32
|
+
]
|
|
33
|
+
dependencies = [
|
|
34
|
+
"requests>=2.31",
|
|
35
|
+
"tqdm>=4.66",
|
|
36
|
+
"m3u8>=4.0",
|
|
37
|
+
"static-ffmpeg>=3.0",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://github.com/soukron/emby-cli"
|
|
42
|
+
Repository = "https://github.com/soukron/emby-cli"
|
|
43
|
+
Issues = "https://github.com/soukron/emby-cli/issues"
|
|
44
|
+
|
|
45
|
+
[project.optional-dependencies]
|
|
46
|
+
dev = [
|
|
47
|
+
"pytest>=8.0",
|
|
48
|
+
"build>=1.0",
|
|
49
|
+
"twine>=5.0",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[project.scripts]
|
|
53
|
+
emby-cli = "emby_cli.cli:main"
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build.targets.wheel]
|
|
56
|
+
only-include = ["src/emby_cli"]
|
|
57
|
+
|
|
58
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
59
|
+
"src/emby_cli" = "emby_cli"
|
|
60
|
+
|
|
61
|
+
[tool.hatch.version]
|
|
62
|
+
source = "vcs"
|
|
63
|
+
|
|
64
|
+
[tool.hatch.build.hooks.vcs]
|
|
65
|
+
version-file = "src/emby_cli/_version.py"
|
|
66
|
+
|
|
67
|
+
[tool.pytest.ini_options]
|
|
68
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Argument parser and entrypoint for emby-cli."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import warnings
|
|
9
|
+
|
|
10
|
+
# macOS system Python ships LibreSSL; urllib3 v2 only warns, TLS still works.
|
|
11
|
+
warnings.filterwarnings("ignore", message="urllib3 v2 only supports OpenSSL")
|
|
12
|
+
|
|
13
|
+
import requests
|
|
14
|
+
|
|
15
|
+
from emby_cli.client import EmbyClient
|
|
16
|
+
from emby_cli.commands.batch import cmd_batch
|
|
17
|
+
from emby_cli.commands.download import cmd_download
|
|
18
|
+
from emby_cli.commands.list import cmd_list
|
|
19
|
+
from emby_cli.commands.play import cmd_play
|
|
20
|
+
from emby_cli.commands.search import cmd_search
|
|
21
|
+
from emby_cli.commands.sync import cmd_sync
|
|
22
|
+
from emby_cli.constants import DEFAULT_OUTPUT
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
26
|
+
env = os.environ.get
|
|
27
|
+
|
|
28
|
+
p = argparse.ArgumentParser(
|
|
29
|
+
prog="emby-cli",
|
|
30
|
+
description="Download / backup original media files from an Emby server via its REST API.",
|
|
31
|
+
)
|
|
32
|
+
p.add_argument("--server", "-s", default=env("EMBY_SERVER"), help="Emby server URL (env: EMBY_SERVER)")
|
|
33
|
+
p.add_argument("--api-key", "-k", default=env("EMBY_API_KEY"), help="API key (env: EMBY_API_KEY)")
|
|
34
|
+
p.add_argument("--username", "-u", default=env("EMBY_USERNAME"), help="Username (env: EMBY_USERNAME)")
|
|
35
|
+
p.add_argument("--password", "-p", default=env("EMBY_PASSWORD"), help="Password (env: EMBY_PASSWORD)")
|
|
36
|
+
|
|
37
|
+
sub = p.add_subparsers(dest="command", required=True)
|
|
38
|
+
|
|
39
|
+
ls = sub.add_parser("list", help="List libraries or items in a library")
|
|
40
|
+
ls.add_argument("--library", "-l", help="Library name to list items for")
|
|
41
|
+
|
|
42
|
+
dl = sub.add_parser("download", help="Download items")
|
|
43
|
+
dl.add_argument("--library", "-l", help="Library name to download")
|
|
44
|
+
dl.add_argument("--item-id", "-i", default=env("EMBY_ITEM_ID"), help="Specific item ID to download (env: EMBY_ITEM_ID)")
|
|
45
|
+
dl.add_argument("--output", "-o", default=env("EMBY_OUTPUT", DEFAULT_OUTPUT),
|
|
46
|
+
help=f"Output directory (env: EMBY_OUTPUT, default: {DEFAULT_OUTPUT})")
|
|
47
|
+
dl.add_argument("--force", "-f", action="store_true", help="Re-download even if file exists with matching size")
|
|
48
|
+
dl.add_argument("--throttle", "-t", type=float, nargs="?", const=1.0, default=0,
|
|
49
|
+
help="Limit speed to playback rate. Optional multiplier: 1=realtime, 1.5=50%% faster (default: off)")
|
|
50
|
+
dl.add_argument("--method", "-m", default=env("EMBY_METHOD", "download"),
|
|
51
|
+
choices=["download", "stream", "hls"],
|
|
52
|
+
help="Download method: 'download' (API Download), 'stream' (browser-like original.*), "
|
|
53
|
+
"or 'hls' (stream chunks + remux) (env: EMBY_METHOD)")
|
|
54
|
+
|
|
55
|
+
sr = sub.add_parser("search", help="Search for items by name")
|
|
56
|
+
sr.add_argument("query", help="Search query")
|
|
57
|
+
|
|
58
|
+
pl = sub.add_parser("play", help="Play an item via DirectStreamUrl in an external player")
|
|
59
|
+
pl.add_argument("query", nargs="?",
|
|
60
|
+
help="Title line like batch: 'Movie (2010)' or 'Show (2000) S01E01'")
|
|
61
|
+
pl.add_argument("--item-id", "-i", default=env("EMBY_ITEM_ID"),
|
|
62
|
+
help="Item ID to play (env: EMBY_ITEM_ID)")
|
|
63
|
+
pl.add_argument("--player", default=env("EMBY_PLAYER"),
|
|
64
|
+
help="External player command or path (env: EMBY_PLAYER), e.g. vlc or "
|
|
65
|
+
"/Applications/VLC.app/Contents/MacOS/VLC")
|
|
66
|
+
pl.add_argument("--wait", action="store_true",
|
|
67
|
+
help="Block until the player process exits (default: detach and return)")
|
|
68
|
+
pl.add_argument("--pick-best-item", type=int, choices=[0, 1], default=0,
|
|
69
|
+
help="On ambiguous search results: 0=list and require --item-id (default), "
|
|
70
|
+
"1=auto-select best ≤1080p like batch")
|
|
71
|
+
|
|
72
|
+
sy = sub.add_parser("sync", help="Sync all libraries (or one with --library)")
|
|
73
|
+
sy.add_argument("--library", "-l", default=env("EMBY_LIBRARY"), help="Specific library to sync (env: EMBY_LIBRARY)")
|
|
74
|
+
sy.add_argument("--output", "-o", default=env("EMBY_OUTPUT", DEFAULT_OUTPUT),
|
|
75
|
+
help=f"Output directory (env: EMBY_OUTPUT, default: {DEFAULT_OUTPUT})")
|
|
76
|
+
sy.add_argument("--force", "-f", action="store_true", help="Re-download even if file exists with matching size")
|
|
77
|
+
sy.add_argument("--throttle", "-t", type=float, nargs="?", const=1.0, default=0,
|
|
78
|
+
help="Limit speed to playback rate. Optional multiplier: 1=realtime, 1.5=50%% faster (default: off)")
|
|
79
|
+
sy.add_argument("--method", "-m", default=env("EMBY_METHOD", "download"),
|
|
80
|
+
choices=["download", "stream", "hls"],
|
|
81
|
+
help="Download method: 'download' (API Download), 'stream' (browser-like original.*), "
|
|
82
|
+
"or 'hls' (stream chunks + remux) (env: EMBY_METHOD)")
|
|
83
|
+
|
|
84
|
+
ba = sub.add_parser("batch", help="Download titles from a text file (movies, seasons, episodes)")
|
|
85
|
+
ba.add_argument("--file", "-F", required=True, help="Text file with one title per line")
|
|
86
|
+
ba.add_argument("--dry-run", "-n", action="store_true", help="Search and select only, do not download")
|
|
87
|
+
ba.add_argument("--output", "-o", default=env("EMBY_OUTPUT", DEFAULT_OUTPUT),
|
|
88
|
+
help=f"Output directory (env: EMBY_OUTPUT, default: {DEFAULT_OUTPUT})")
|
|
89
|
+
ba.add_argument("--force", "-f", action="store_true", help="Re-download even if file exists with matching size")
|
|
90
|
+
ba.add_argument("--throttle", "-t", type=float, nargs="?", const=1.0, default=0,
|
|
91
|
+
help="Limit speed to playback rate. Optional multiplier: 1=realtime, 1.5=50%% faster (default: off)")
|
|
92
|
+
ba.add_argument("--method", "-m", default=env("EMBY_METHOD", "download"),
|
|
93
|
+
choices=["download", "stream", "hls"],
|
|
94
|
+
help="Download method: 'download' (API Download), 'stream' (browser-like original.*), "
|
|
95
|
+
"or 'hls' (stream chunks + remux) (env: EMBY_METHOD)")
|
|
96
|
+
|
|
97
|
+
return p
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def main() -> None:
|
|
101
|
+
parser = build_parser()
|
|
102
|
+
args = parser.parse_args()
|
|
103
|
+
|
|
104
|
+
if not args.server:
|
|
105
|
+
parser.error("Provide --server or set EMBY_SERVER")
|
|
106
|
+
|
|
107
|
+
if not args.api_key and not args.username:
|
|
108
|
+
parser.error("Provide --api-key / EMBY_API_KEY or --username / EMBY_USERNAME")
|
|
109
|
+
|
|
110
|
+
client = EmbyClient(args.server, api_key=args.api_key)
|
|
111
|
+
|
|
112
|
+
if args.username is not None:
|
|
113
|
+
pw = args.password if args.password is not None else ""
|
|
114
|
+
print(f"Authenticating as '{args.username}'...")
|
|
115
|
+
try:
|
|
116
|
+
client.authenticate(args.username, pw)
|
|
117
|
+
except requests.HTTPError as exc:
|
|
118
|
+
print(f"Authentication failed: {exc}")
|
|
119
|
+
sys.exit(1)
|
|
120
|
+
print("OK\n")
|
|
121
|
+
|
|
122
|
+
commands = {
|
|
123
|
+
"list": cmd_list,
|
|
124
|
+
"download": cmd_download,
|
|
125
|
+
"search": cmd_search,
|
|
126
|
+
"play": cmd_play,
|
|
127
|
+
"sync": cmd_sync,
|
|
128
|
+
"batch": cmd_batch,
|
|
129
|
+
}
|
|
130
|
+
commands[args.command](client, args)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
main()
|