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.
@@ -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
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ venv/
5
+ .env
6
+ *.egg-info/
7
+ dist/
8
+ build/
9
+ .pytest_cache/
10
+ .mypy_cache/
11
+ src/emby_cli/_version.py
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ - Initial packaging as `emby-cli` (modular CLI from emby-downloader).
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
@@ -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/)
@@ -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,6 @@
1
+ """emby-cli — backup and stream media from an Emby server."""
2
+
3
+ from emby_cli.client import EmbyClient
4
+ from emby_cli.version import get_version
5
+
6
+ __all__ = ["EmbyClient", "get_version"]
@@ -0,0 +1,4 @@
1
+ from emby_cli.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -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()