emby-cli 0.3.0__tar.gz → 0.4.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.3.0 → emby_cli-0.4.0}/CHANGELOG.md +7 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/PKG-INFO +15 -11
- {emby_cli-0.3.0 → emby_cli-0.4.0}/README.md +14 -10
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/_version.py +2 -2
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/cli.py +63 -10
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/client.py +9 -5
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/commands/download.py +20 -15
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/commands/help.py +1 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/commands/search.py +17 -11
- emby_cli-0.4.0/src/emby_cli/commands/show.py +273 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/constants.py +11 -0
- emby_cli-0.4.0/src/emby_cli/mode_args.py +31 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_client.py +13 -7
- {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_parser.py +79 -17
- {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_search.py +7 -7
- emby_cli-0.4.0/tests/test_show.py +142 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_validate_args.py +58 -19
- {emby_cli-0.3.0 → emby_cli-0.4.0}/.env.example +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/.github/workflows/ci.yml +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/.github/workflows/publish.yml +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/.gitignore +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/LICENSE +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/pyproject.toml +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/__init__.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/__main__.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/auth_cache.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/commands/__init__.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/commands/config.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/commands/info.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/commands/login.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/commands/logout.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/commands/play.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/commands/version.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/credentials.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/download_ops.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/output.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/resolve.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/util.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/version.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_auth_cache.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_config.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_credentials.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_download_ops.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_info.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_logout.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_output.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_resolve.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_resolve_table.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_session_auth.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_util.py +0 -0
- {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_version.py +0 -0
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.4.0
|
|
6
|
+
|
|
7
|
+
Added:
|
|
8
|
+
|
|
9
|
+
- `show`: detail view for `--item` / `--library` (QUERY or `--id`); multiple matches print a disambiguation table with IDs. Libraries include total item count and the last 10 added (`DateCreated`), limited to Movie/Episode/Audio (no Studio, etc.).
|
|
10
|
+
- `search` / `download`: `--item QUERY` / `--library QUERY` (query on the mode flag); `--media-item` is an alias of `--item`. `--search` still works as an alternative selector.
|
|
11
|
+
|
|
5
12
|
## 0.3.0
|
|
6
13
|
|
|
7
14
|
Breaking:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: emby-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: CLI to list, search, play, and download/backup original media from an Emby server via its REST API.
|
|
5
5
|
Project-URL: Homepage, https://github.com/soukron/emby-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/soukron/emby-cli
|
|
@@ -84,18 +84,22 @@ emby-cli config current-server
|
|
|
84
84
|
emby-cli config view
|
|
85
85
|
emby-cli version
|
|
86
86
|
emby-cli info
|
|
87
|
-
emby-cli search --
|
|
88
|
-
emby-cli search --
|
|
89
|
-
emby-cli search --
|
|
90
|
-
emby-cli search --
|
|
87
|
+
emby-cli search --item "Title"
|
|
88
|
+
emby-cli search --item "Title" --count 50
|
|
89
|
+
emby-cli search --item --id 123456
|
|
90
|
+
emby-cli search --item --all
|
|
91
91
|
emby-cli search --library --all
|
|
92
92
|
emby-cli search --library --id 614156
|
|
93
|
-
emby-cli search --library
|
|
94
|
-
|
|
95
|
-
emby-cli
|
|
96
|
-
emby-cli
|
|
93
|
+
emby-cli search --library "PELICULAS"
|
|
94
|
+
emby-cli show --item "Title"
|
|
95
|
+
emby-cli show --item --id 123456
|
|
96
|
+
emby-cli show --library "PELICULAS"
|
|
97
|
+
emby-cli show --library --id 614156
|
|
98
|
+
|
|
99
|
+
emby-cli download --item --id 123456
|
|
100
|
+
emby-cli download --item "californication S01E01" --pick-best-item
|
|
97
101
|
emby-cli download --library --id 12345
|
|
98
|
-
emby-cli download --library
|
|
102
|
+
emby-cli download --library "PELICULAS 4K"
|
|
99
103
|
emby-cli download --from-file titles.txt --dry-run
|
|
100
104
|
|
|
101
105
|
emby-cli play --id 123456
|
|
@@ -104,7 +108,7 @@ emby-cli play --search "Pelicula (1980)" --pick-best-item
|
|
|
104
108
|
|
|
105
109
|
`info` reports libraries and totals from `GET /Items/Counts` (totals may include multiple versions of the same title).
|
|
106
110
|
|
|
107
|
-
Title resolution (`play --search`, `download --
|
|
111
|
+
Title resolution (`play --search`, `download --item` with a query, `download --from-file`) is **strict** by default (year mismatch or multiple matches fail). Pass `--pick-best-item` to auto-select the best ≤1080p version. With `--from-file` or `--item` + query, a season line like `Show S01` downloads the whole season (same pick-best rule per episode version). Library query requires an exact name match (case-insensitive); no pick-best.
|
|
108
112
|
|
|
109
113
|
`--dry-run` / `-n` works for all `download` modes (no files written).
|
|
110
114
|
|
|
@@ -50,18 +50,22 @@ emby-cli config current-server
|
|
|
50
50
|
emby-cli config view
|
|
51
51
|
emby-cli version
|
|
52
52
|
emby-cli info
|
|
53
|
-
emby-cli search --
|
|
54
|
-
emby-cli search --
|
|
55
|
-
emby-cli search --
|
|
56
|
-
emby-cli search --
|
|
53
|
+
emby-cli search --item "Title"
|
|
54
|
+
emby-cli search --item "Title" --count 50
|
|
55
|
+
emby-cli search --item --id 123456
|
|
56
|
+
emby-cli search --item --all
|
|
57
57
|
emby-cli search --library --all
|
|
58
58
|
emby-cli search --library --id 614156
|
|
59
|
-
emby-cli search --library
|
|
60
|
-
|
|
61
|
-
emby-cli
|
|
62
|
-
emby-cli
|
|
59
|
+
emby-cli search --library "PELICULAS"
|
|
60
|
+
emby-cli show --item "Title"
|
|
61
|
+
emby-cli show --item --id 123456
|
|
62
|
+
emby-cli show --library "PELICULAS"
|
|
63
|
+
emby-cli show --library --id 614156
|
|
64
|
+
|
|
65
|
+
emby-cli download --item --id 123456
|
|
66
|
+
emby-cli download --item "californication S01E01" --pick-best-item
|
|
63
67
|
emby-cli download --library --id 12345
|
|
64
|
-
emby-cli download --library
|
|
68
|
+
emby-cli download --library "PELICULAS 4K"
|
|
65
69
|
emby-cli download --from-file titles.txt --dry-run
|
|
66
70
|
|
|
67
71
|
emby-cli play --id 123456
|
|
@@ -70,7 +74,7 @@ emby-cli play --search "Pelicula (1980)" --pick-best-item
|
|
|
70
74
|
|
|
71
75
|
`info` reports libraries and totals from `GET /Items/Counts` (totals may include multiple versions of the same title).
|
|
72
76
|
|
|
73
|
-
Title resolution (`play --search`, `download --
|
|
77
|
+
Title resolution (`play --search`, `download --item` with a query, `download --from-file`) is **strict** by default (year mismatch or multiple matches fail). Pass `--pick-best-item` to auto-select the best ≤1080p version. With `--from-file` or `--item` + query, a season line like `Show S01` downloads the whole season (same pick-best rule per episode version). Library query requires an exact name match (case-insensitive); no pick-best.
|
|
74
78
|
|
|
75
79
|
`--dry-run` / `-n` works for all `download` modes (no files written).
|
|
76
80
|
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.4.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 4, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -21,6 +21,7 @@ from emby_cli.commands.login import cmd_login
|
|
|
21
21
|
from emby_cli.commands.logout import cmd_logout
|
|
22
22
|
from emby_cli.commands.play import cmd_play, validate_play_args
|
|
23
23
|
from emby_cli.commands.search import cmd_search, validate_search_args
|
|
24
|
+
from emby_cli.commands.show import cmd_show, validate_show_args
|
|
24
25
|
from emby_cli.commands.version import cmd_version
|
|
25
26
|
from emby_cli.constants import DEFAULT_OUTPUT, SEARCH_COUNT_DEFAULT
|
|
26
27
|
from emby_cli.credentials import (
|
|
@@ -82,14 +83,23 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
82
83
|
)
|
|
83
84
|
mode = dl.add_mutually_exclusive_group(required=True)
|
|
84
85
|
mode.add_argument(
|
|
86
|
+
"--item",
|
|
85
87
|
"--media-item",
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
nargs="?",
|
|
89
|
+
const="",
|
|
90
|
+
default=None,
|
|
91
|
+
metavar="QUERY",
|
|
92
|
+
dest="item",
|
|
93
|
+
help="Download media items; optional QUERY, or use with --id "
|
|
94
|
+
"(--media-item is an alias)",
|
|
88
95
|
)
|
|
89
96
|
mode.add_argument(
|
|
90
97
|
"--library",
|
|
91
|
-
|
|
92
|
-
|
|
98
|
+
nargs="?",
|
|
99
|
+
const="",
|
|
100
|
+
default=None,
|
|
101
|
+
metavar="QUERY",
|
|
102
|
+
help="Download a library; optional QUERY, or use with --id",
|
|
93
103
|
)
|
|
94
104
|
mode.add_argument(
|
|
95
105
|
"--from-file",
|
|
@@ -105,7 +115,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
105
115
|
)
|
|
106
116
|
dl.add_argument(
|
|
107
117
|
"--search",
|
|
108
|
-
help="Search query
|
|
118
|
+
help="Search query (alternative to QUERY on --item / --library)",
|
|
109
119
|
)
|
|
110
120
|
dl.add_argument(
|
|
111
121
|
"--dry-run",
|
|
@@ -146,14 +156,23 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
146
156
|
sr = sub.add_parser("search", help=_help_by_name["search"])
|
|
147
157
|
sr_mode = sr.add_mutually_exclusive_group(required=True)
|
|
148
158
|
sr_mode.add_argument(
|
|
159
|
+
"--item",
|
|
149
160
|
"--media-item",
|
|
150
|
-
|
|
151
|
-
|
|
161
|
+
nargs="?",
|
|
162
|
+
const="",
|
|
163
|
+
default=None,
|
|
164
|
+
metavar="QUERY",
|
|
165
|
+
dest="item",
|
|
166
|
+
help="Search media items; optional QUERY, or use with --id / --all "
|
|
167
|
+
"(--media-item is an alias)",
|
|
152
168
|
)
|
|
153
169
|
sr_mode.add_argument(
|
|
154
170
|
"--library",
|
|
155
|
-
|
|
156
|
-
|
|
171
|
+
nargs="?",
|
|
172
|
+
const="",
|
|
173
|
+
default=None,
|
|
174
|
+
metavar="QUERY",
|
|
175
|
+
help="Search libraries; optional QUERY, or use with --id / --all",
|
|
157
176
|
)
|
|
158
177
|
sr.add_argument(
|
|
159
178
|
"--id",
|
|
@@ -162,7 +181,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
162
181
|
)
|
|
163
182
|
sr.add_argument(
|
|
164
183
|
"--search",
|
|
165
|
-
help="Search query
|
|
184
|
+
help="Search query (alternative to QUERY on --item / --library)",
|
|
166
185
|
)
|
|
167
186
|
sr.add_argument(
|
|
168
187
|
"--all",
|
|
@@ -206,6 +225,37 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
206
225
|
"(default: list matches and fail)",
|
|
207
226
|
)
|
|
208
227
|
|
|
228
|
+
sh = sub.add_parser("show", help=_help_by_name["show"])
|
|
229
|
+
sh_mode = sh.add_mutually_exclusive_group(required=True)
|
|
230
|
+
sh_mode.add_argument(
|
|
231
|
+
"--item",
|
|
232
|
+
"--media-item",
|
|
233
|
+
nargs="?",
|
|
234
|
+
const="",
|
|
235
|
+
default=None,
|
|
236
|
+
metavar="QUERY",
|
|
237
|
+
dest="item",
|
|
238
|
+
help="Show a media item; optional QUERY, or use with --id "
|
|
239
|
+
"(--media-item is an alias)",
|
|
240
|
+
)
|
|
241
|
+
sh_mode.add_argument(
|
|
242
|
+
"--library",
|
|
243
|
+
nargs="?",
|
|
244
|
+
const="",
|
|
245
|
+
default=None,
|
|
246
|
+
metavar="QUERY",
|
|
247
|
+
help="Show a library; optional QUERY, or use with --id",
|
|
248
|
+
)
|
|
249
|
+
sh.add_argument(
|
|
250
|
+
"--id",
|
|
251
|
+
default=None,
|
|
252
|
+
help="Media item or library ID",
|
|
253
|
+
)
|
|
254
|
+
sh.add_argument(
|
|
255
|
+
"--search",
|
|
256
|
+
help="Search query (alternative to QUERY on --item / --library)",
|
|
257
|
+
)
|
|
258
|
+
|
|
209
259
|
sub.add_parser("version", help=_help_by_name["version"])
|
|
210
260
|
sub.add_parser("info", help=_help_by_name["info"])
|
|
211
261
|
|
|
@@ -249,6 +299,8 @@ def _validate_command_args(command: str, args: argparse.Namespace) -> str | None
|
|
|
249
299
|
return validate_download_args(args)
|
|
250
300
|
if command == "play":
|
|
251
301
|
return validate_play_args(args)
|
|
302
|
+
if command == "show":
|
|
303
|
+
return validate_show_args(args)
|
|
252
304
|
return None
|
|
253
305
|
|
|
254
306
|
|
|
@@ -293,6 +345,7 @@ def main() -> None:
|
|
|
293
345
|
"download": cmd_download,
|
|
294
346
|
"search": cmd_search,
|
|
295
347
|
"play": cmd_play,
|
|
348
|
+
"show": cmd_show,
|
|
296
349
|
}
|
|
297
350
|
commands[args.command](client, args)
|
|
298
351
|
|
|
@@ -458,15 +458,19 @@ class EmbyClient:
|
|
|
458
458
|
recursive: bool = True,
|
|
459
459
|
start: int = 0,
|
|
460
460
|
limit: int = 200,
|
|
461
|
+
*,
|
|
462
|
+
sort_by: str = "SortName",
|
|
463
|
+
sort_order: str = "Ascending",
|
|
464
|
+
fields: str | None = None,
|
|
461
465
|
) -> dict:
|
|
462
466
|
uid = self.resolve_user_id()
|
|
463
467
|
params: dict = {
|
|
464
468
|
"StartIndex": start,
|
|
465
469
|
"Limit": limit,
|
|
466
470
|
"Recursive": str(recursive).lower(),
|
|
467
|
-
"Fields": ITEM_FIELDS,
|
|
468
|
-
"SortBy":
|
|
469
|
-
"SortOrder":
|
|
471
|
+
"Fields": fields or ITEM_FIELDS,
|
|
472
|
+
"SortBy": sort_by,
|
|
473
|
+
"SortOrder": sort_order,
|
|
470
474
|
}
|
|
471
475
|
if parent_id:
|
|
472
476
|
params["ParentId"] = parent_id
|
|
@@ -493,11 +497,11 @@ class EmbyClient:
|
|
|
493
497
|
break
|
|
494
498
|
return items
|
|
495
499
|
|
|
496
|
-
def get_item_info(self, item_id: str) -> dict:
|
|
500
|
+
def get_item_info(self, item_id: str, *, fields: str | None = None) -> dict:
|
|
497
501
|
uid = self.resolve_user_id()
|
|
498
502
|
resp = self._get(
|
|
499
503
|
f"/Users/{uid}/Items/{item_id}",
|
|
500
|
-
params={"Fields": ITEM_FIELDS},
|
|
504
|
+
params={"Fields": fields or ITEM_FIELDS},
|
|
501
505
|
)
|
|
502
506
|
return resp.json()
|
|
503
507
|
|
|
@@ -15,6 +15,7 @@ from emby_cli.download_ops import (
|
|
|
15
15
|
download_one_item,
|
|
16
16
|
find_library,
|
|
17
17
|
)
|
|
18
|
+
from emby_cli.mode_args import mode_is_item, mode_is_library, resolve_query
|
|
18
19
|
from emby_cli.output import Stats, print_done, print_error
|
|
19
20
|
from emby_cli.resolve import resolve_title_items
|
|
20
21
|
from emby_cli.util import format_duration
|
|
@@ -60,22 +61,26 @@ def _download_items(
|
|
|
60
61
|
|
|
61
62
|
def validate_download_args(args: argparse.Namespace) -> str | None:
|
|
62
63
|
"""Return an error message if mode/selectors are invalid; else ``None``."""
|
|
63
|
-
if
|
|
64
|
-
|
|
64
|
+
if mode_is_item(args):
|
|
65
|
+
query, err = resolve_query(args)
|
|
66
|
+
if err:
|
|
67
|
+
return err
|
|
65
68
|
item_id = (getattr(args, "id", None) or "").strip() or None
|
|
66
|
-
if not item_id and not
|
|
69
|
+
if not item_id and not query:
|
|
67
70
|
item_id = (os.environ.get("EMBY_ITEM_ID") or "").strip() or None
|
|
68
|
-
if bool(item_id) == bool(
|
|
69
|
-
return "With --
|
|
71
|
+
if bool(item_id) == bool(query):
|
|
72
|
+
return "With --item, provide exactly one of --id or QUERY/--search"
|
|
70
73
|
return None
|
|
71
74
|
|
|
72
|
-
if
|
|
75
|
+
if mode_is_library(args):
|
|
73
76
|
if bool(getattr(args, "pick_best_item", False)):
|
|
74
77
|
return "--pick-best-item cannot be used with --library"
|
|
78
|
+
query, err = resolve_query(args)
|
|
79
|
+
if err:
|
|
80
|
+
return err
|
|
75
81
|
library_id = (getattr(args, "id", None) or "").strip() or None
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return "With --library, provide exactly one of --id or --search"
|
|
82
|
+
if bool(library_id) == bool(query):
|
|
83
|
+
return "With --library, provide exactly one of --id or QUERY/--search"
|
|
79
84
|
return None
|
|
80
85
|
|
|
81
86
|
if getattr(args, "from_file", None):
|
|
@@ -85,7 +90,7 @@ def validate_download_args(args: argparse.Namespace) -> str | None:
|
|
|
85
90
|
return "With --from-file, do not pass --id or --search"
|
|
86
91
|
return None
|
|
87
92
|
|
|
88
|
-
return "Specify --
|
|
93
|
+
return "Specify --item, --library, or --from-file"
|
|
89
94
|
|
|
90
95
|
|
|
91
96
|
def _cmd_download_item(client: EmbyClient, args: argparse.Namespace) -> None:
|
|
@@ -94,7 +99,7 @@ def _cmd_download_item(client: EmbyClient, args: argparse.Namespace) -> None:
|
|
|
94
99
|
method = getattr(args, "method", "download")
|
|
95
100
|
dry_run = bool(getattr(args, "dry_run", False))
|
|
96
101
|
pick_best = bool(getattr(args, "pick_best_item", False))
|
|
97
|
-
search = (
|
|
102
|
+
search, _ = resolve_query(args)
|
|
98
103
|
item_id = (getattr(args, "id", None) or "").strip() or None
|
|
99
104
|
if not item_id and not search:
|
|
100
105
|
item_id = (os.environ.get("EMBY_ITEM_ID") or "").strip() or None
|
|
@@ -157,7 +162,7 @@ def _cmd_download_library(client: EmbyClient, args: argparse.Namespace) -> None:
|
|
|
157
162
|
method = getattr(args, "method", "download")
|
|
158
163
|
dry_run = bool(getattr(args, "dry_run", False))
|
|
159
164
|
library_id = (getattr(args, "id", None) or "").strip() or None
|
|
160
|
-
search = (
|
|
165
|
+
search, _ = resolve_query(args)
|
|
161
166
|
|
|
162
167
|
if dry_run:
|
|
163
168
|
print("*** DRY RUN — no files will be downloaded ***\n")
|
|
@@ -335,14 +340,14 @@ def cmd_download(client: EmbyClient, args: argparse.Namespace) -> None:
|
|
|
335
340
|
print(err)
|
|
336
341
|
sys.exit(1)
|
|
337
342
|
|
|
338
|
-
if
|
|
343
|
+
if mode_is_item(args):
|
|
339
344
|
_cmd_download_item(client, args)
|
|
340
345
|
return
|
|
341
|
-
if
|
|
346
|
+
if mode_is_library(args):
|
|
342
347
|
_cmd_download_library(client, args)
|
|
343
348
|
return
|
|
344
349
|
if getattr(args, "from_file", None):
|
|
345
350
|
_cmd_download_from_file(client, args)
|
|
346
351
|
return
|
|
347
|
-
print("Specify --
|
|
352
|
+
print("Specify --item, --library, or --from-file")
|
|
348
353
|
sys.exit(1)
|
|
@@ -11,6 +11,7 @@ COMMAND_SUMMARIES: tuple[tuple[str, str], ...] = (
|
|
|
11
11
|
("version", "Show emby-cli version"),
|
|
12
12
|
("info", "Show session user, server details, libraries, and item counts"),
|
|
13
13
|
("search", "Search media items or libraries"),
|
|
14
|
+
("show", "Show details for a media item or library"),
|
|
14
15
|
("download", "Download media items or libraries"),
|
|
15
16
|
("play", "Play a media item using an external player"),
|
|
16
17
|
)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""search command — media items or libraries (--
|
|
1
|
+
"""search command — media items or libraries (--item/--library + QUERY/--id/--all)."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -8,6 +8,7 @@ import sys
|
|
|
8
8
|
from emby_cli.client import EmbyClient
|
|
9
9
|
from emby_cli.constants import SEARCH_COUNT_DEFAULT, SEARCH_ITEM_TYPES
|
|
10
10
|
from emby_cli.download_ops import find_library
|
|
11
|
+
from emby_cli.mode_args import mode_is_library, resolve_query
|
|
11
12
|
from emby_cli.resolve import print_item_choices, print_library_choices
|
|
12
13
|
|
|
13
14
|
|
|
@@ -39,9 +40,8 @@ def _print_libraries(
|
|
|
39
40
|
print()
|
|
40
41
|
|
|
41
42
|
|
|
42
|
-
def _selector_count(args: argparse.Namespace) -> int:
|
|
43
|
+
def _selector_count(args: argparse.Namespace, query: str | None) -> int:
|
|
43
44
|
item_id = (getattr(args, "id", None) or "").strip()
|
|
44
|
-
query = (getattr(args, "search", None) or "").strip()
|
|
45
45
|
use_all = bool(getattr(args, "all", False))
|
|
46
46
|
return sum(bool(x) for x in (item_id, query, use_all))
|
|
47
47
|
|
|
@@ -52,8 +52,14 @@ def validate_search_args(args: argparse.Namespace) -> str | None:
|
|
|
52
52
|
count = SEARCH_COUNT_DEFAULT if raw_count is None else int(raw_count)
|
|
53
53
|
if count < 1:
|
|
54
54
|
return "error: --count must be >= 1"
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
query, err = resolve_query(args)
|
|
56
|
+
if err:
|
|
57
|
+
return err
|
|
58
|
+
if _selector_count(args, query) != 1:
|
|
59
|
+
return (
|
|
60
|
+
"Provide exactly one of QUERY on --item/--library, "
|
|
61
|
+
"--search, --id, or --all"
|
|
62
|
+
)
|
|
57
63
|
return None
|
|
58
64
|
|
|
59
65
|
|
|
@@ -69,10 +75,10 @@ def cmd_search(client: EmbyClient, args: argparse.Namespace) -> None:
|
|
|
69
75
|
raw_count = getattr(args, "count", SEARCH_COUNT_DEFAULT)
|
|
70
76
|
count = SEARCH_COUNT_DEFAULT if raw_count is None else int(raw_count)
|
|
71
77
|
item_id = (getattr(args, "id", None) or "").strip() or None
|
|
72
|
-
query = (
|
|
78
|
+
query, _ = resolve_query(args)
|
|
73
79
|
use_all = bool(getattr(args, "all", False))
|
|
74
80
|
|
|
75
|
-
if
|
|
81
|
+
if mode_is_library(args):
|
|
76
82
|
libraries = client.get_libraries()
|
|
77
83
|
if use_all:
|
|
78
84
|
_print_libraries(client, libraries, count=count)
|
|
@@ -87,7 +93,7 @@ def cmd_search(client: EmbyClient, args: argparse.Namespace) -> None:
|
|
|
87
93
|
_print_libraries(client, [lib], count=count)
|
|
88
94
|
return
|
|
89
95
|
|
|
90
|
-
needle = query.lower()
|
|
96
|
+
needle = (query or "").lower()
|
|
91
97
|
matches = [
|
|
92
98
|
lib for lib in libraries
|
|
93
99
|
if needle in (lib.get("Name") or "").lower()
|
|
@@ -95,15 +101,15 @@ def cmd_search(client: EmbyClient, args: argparse.Namespace) -> None:
|
|
|
95
101
|
_print_libraries(client, matches, count=count)
|
|
96
102
|
return
|
|
97
103
|
|
|
98
|
-
# --media-item
|
|
104
|
+
# --item / --media-item
|
|
99
105
|
if use_all:
|
|
100
106
|
probe = client.get_items(item_type=SEARCH_ITEM_TYPES, limit=0)
|
|
101
107
|
total = int(probe.get("TotalRecordCount") or 0)
|
|
102
108
|
if total > count:
|
|
103
109
|
print(
|
|
104
110
|
f"There are {total} media items on this server. "
|
|
105
|
-
"Please narrow the results with
|
|
106
|
-
' emby-cli search --
|
|
111
|
+
"Please narrow the results with a query, for example:\n"
|
|
112
|
+
' emby-cli search --item "title"'
|
|
107
113
|
)
|
|
108
114
|
sys.exit(1)
|
|
109
115
|
if total == 0:
|