shownamer 2.2.0__tar.gz → 2.3.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.
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shownamer
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: A powerful yet lightweight command-line tool that automatically renames your TV show and movie files.
5
- Author-email: Gemini <gemini@google.com>
5
+ Author-email: Amal Lalgi <theamallalgi+shownamer@google.com>
6
6
  Classifier: Programming Language :: Python :: 3
7
7
  Classifier: License :: OSI Approved :: MIT License
8
8
  Classifier: Operating System :: OS Independent
@@ -4,9 +4,9 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "shownamer"
7
- version = "2.2.0"
7
+ version = "2.3.0"
8
8
  authors = [
9
- { name="Gemini", email="gemini@google.com" },
9
+ { name="Amal Lalgi", email="theamallalgi+shownamer@google.com" },
10
10
  ]
11
11
  description = "A powerful yet lightweight command-line tool that automatically renames your TV show and movie files."
12
12
  readme = "README.md"
@@ -1,3 +1,3 @@
1
1
  """Shownamer - The Ultimate Media Renamer."""
2
2
 
3
- __version__ = "2.2.0"
3
+ __version__ = "2.3.0"
@@ -5,6 +5,7 @@ BASE_URL = "http://api.tvmaze.com"
5
5
  OMDB_KEY_FILE = Path.home() / ".shownamer_omdb_key"
6
6
  OMDB_URL = "http://www.omdbapi.com/"
7
7
 
8
+
8
9
  def get_omdb_key():
9
10
  if OMDB_KEY_FILE.exists():
10
11
  return OMDB_KEY_FILE.read_text().strip()
@@ -14,6 +15,7 @@ def get_omdb_key():
14
15
  OMDB_KEY_FILE.write_text(key)
15
16
  return key
16
17
 
18
+
17
19
  def fetch_omdb_metadata(title, year=None, api_key=None):
18
20
  params = {"t": title, "apikey": api_key, "type": "movie"}
19
21
  if year:
@@ -27,6 +29,7 @@ def fetch_omdb_metadata(title, year=None, api_key=None):
27
29
  pass
28
30
  return None
29
31
 
32
+
30
33
  def search_media(name, media_type="shows"):
31
34
  endpoint = f"search/{media_type}"
32
35
  try:
@@ -39,14 +42,35 @@ def search_media(name, media_type="shows"):
39
42
  print(f"[!] Error searching for {name}: {e}")
40
43
  return None
41
44
 
45
+
42
46
  def get_episode_by_number(show_id, season, episode):
43
47
  try:
44
48
  response = requests.get(
45
49
  f"{BASE_URL}/shows/{show_id}/episodebynumber",
46
- params={"season": season, "number": episode}
50
+ params={"season": season, "number": episode},
47
51
  )
48
52
  response.raise_for_status()
49
53
  return response.json()
50
54
  except requests.exceptions.RequestException as e:
51
55
  print(f"[!] Error getting episode S{season:02}E{episode:02}: {e}")
52
56
  return None
57
+
58
+
59
+ def get_show_episodes(show_id):
60
+ try:
61
+ response = requests.get(f"{BASE_URL}/shows/{show_id}/episodes")
62
+ response.raise_for_status()
63
+ return response.json()
64
+ except requests.exceptions.RequestException as e:
65
+ print(f"[!] Error getting episode list for show {show_id}: {e}")
66
+ return []
67
+
68
+
69
+ def get_show_cast(show_id):
70
+ try:
71
+ response = requests.get(f"{BASE_URL}/shows/{show_id}/cast")
72
+ response.raise_for_status()
73
+ return response.json()
74
+ except requests.exceptions.RequestException as e:
75
+ print(f"[!] Error getting cast for show {show_id}: {e}")
76
+ return []
@@ -0,0 +1,433 @@
1
+ import os
2
+ import shutil
3
+ from pathlib import Path
4
+ from . import utils, api
5
+ from . import titleEmbed
6
+ import textwrap
7
+ import re
8
+
9
+
10
+ def process_directory(args):
11
+ if args.name:
12
+ list_detected_media(args.dir, args.ext, args.movie, args)
13
+ return
14
+
15
+ for filename in os.listdir(args.dir):
16
+ file_ext = os.path.splitext(filename)[1][1:]
17
+ if file_ext.lower() in [e.lower() for e in args.ext]:
18
+ process_file(filename, args)
19
+
20
+
21
+ def get_rating(media_info, source):
22
+ for rating in media_info.get("Ratings", []):
23
+ if rating.get("Source") == source:
24
+ return rating.get("Value", "N/A")
25
+ return "N/A"
26
+
27
+
28
+ def process_file(filename, args):
29
+ if args.format:
30
+ try:
31
+ utils.validate_format(args.format)
32
+ except ValueError as e:
33
+ print(f"[!] Error: {e}")
34
+ return
35
+
36
+ if args.verbose:
37
+ print(f"Processing: {filename}")
38
+
39
+ file_path = os.path.join(args.dir, filename)
40
+ file_info = utils.parse_filename(os.path.splitext(filename)[0], args.movie)
41
+
42
+ if not file_info:
43
+ if args.verbose:
44
+ print(f"[skip] Could not parse file information from '{filename}'")
45
+ return
46
+
47
+ rename_succeeded = False
48
+ new_name = ""
49
+ if args.movie:
50
+ new_name = rename_movie(file_info, args)
51
+ else:
52
+ if not file_info.get("is_movie"):
53
+ new_name = rename_show(file_info, args)
54
+
55
+ if new_name:
56
+ file_ext = os.path.splitext(filename)[1]
57
+ new_filename = new_name + file_ext
58
+ new_filepath = os.path.join(args.dir, new_filename)
59
+
60
+ if filename == new_filename:
61
+ print(f"[skip] '{filename}' already matches the target name.")
62
+ elif not args.dry_run:
63
+ if os.path.exists(new_filepath):
64
+ print(f"[skip] '{new_filename}' already exists.")
65
+ else:
66
+ try:
67
+ print(f"[rename] '{filename}' → '{new_filename}'")
68
+ shutil.move(file_path, new_filepath)
69
+ rename_succeeded = True
70
+ except OSError as e:
71
+ print(f" → [!] Error renaming file: {e}")
72
+ else:
73
+ print(f"[rename] '{filename}' → '{new_filename}'")
74
+
75
+ if args.title:
76
+ resolved_name = new_name if new_name else os.path.splitext(filename)[0]
77
+
78
+ titleStr = _buildTitleStr(resolved_name, args, file_info)
79
+
80
+ if args.dry_run:
81
+ print(f" → [title] Would embed: {titleStr}")
82
+ else:
83
+ target_path = Path(new_filepath) if rename_succeeded else Path(file_path)
84
+
85
+ success = titleEmbed.embedTitle(target_path, titleStr)
86
+
87
+ if args.verbose:
88
+ if success:
89
+ print(f" → [title] Embedded: {titleStr}")
90
+ else:
91
+ print(
92
+ f" → [title] Failed to embed metadata (ffmpeg/mutagen required)"
93
+ )
94
+ elif args.verbose:
95
+ print(f"[skip] No new name generated for '{filename}'")
96
+
97
+
98
+ def _buildTitleStr(resolvedName: str, args, fileInfo: dict) -> str:
99
+ if args.format:
100
+ return resolvedName
101
+ if args.movie:
102
+ name, _, year = resolvedName.rpartition(" (")
103
+ return titleEmbed.buildMovieTitle(name, year.rstrip(")"))
104
+ season = fileInfo.get("season", 1)
105
+ episode = fileInfo.get("episode", 1)
106
+ title = resolvedName.split(" - ", 1)[-1] if " - " in resolvedName else resolvedName
107
+ showName = fileInfo.get("name", resolvedName)
108
+ return titleEmbed.buildShowTitle(showName, season, episode, title)
109
+
110
+
111
+ def format_episode_ranges(episodes):
112
+ episodes = sorted(episodes)
113
+
114
+ if not episodes:
115
+ return ""
116
+
117
+ ranges = []
118
+ start = end = episodes[0]
119
+
120
+ for ep in episodes[1:]:
121
+ if ep == end + 1:
122
+ end = ep
123
+ else:
124
+ ranges.append((start, end))
125
+ start = end = ep
126
+
127
+ ranges.append((start, end))
128
+
129
+ parts = []
130
+
131
+ for start, end in ranges:
132
+ if start == end:
133
+ parts.append(str(start))
134
+ else:
135
+ parts.append(f"{start}-{end}")
136
+
137
+ return ", ".join(parts)
138
+
139
+
140
+ def format_ranges(numbers):
141
+ numbers = sorted(numbers)
142
+
143
+ if not numbers:
144
+ return "None"
145
+
146
+ ranges = []
147
+ start = end = numbers[0]
148
+
149
+ for n in numbers[1:]:
150
+ if n == end + 1:
151
+ end = n
152
+ else:
153
+ ranges.append((start, end))
154
+ start = end = n
155
+
156
+ ranges.append((start, end))
157
+
158
+ parts = []
159
+
160
+ for start, end in ranges:
161
+ if start == end:
162
+ parts.append(f"{start:02}")
163
+ else:
164
+ parts.append(f"{start:02}-{end:02}")
165
+
166
+ return ", ".join(parts)
167
+
168
+
169
+ def rename_show(file_info, args):
170
+ media_info = api.search_media(file_info["name"], "shows")
171
+ if not media_info:
172
+ if args.verbose:
173
+ print(f" → [API Error] Could not find show '{file_info['name']}'")
174
+ return None
175
+
176
+ show_id = media_info["id"]
177
+ episode_info = api.get_episode_by_number(
178
+ show_id, file_info["season"], file_info["episode"]
179
+ )
180
+ if not episode_info:
181
+ if args.verbose:
182
+ print(
183
+ f" → [API Error] Could not find episode S{file_info['season']:02}E{file_info['episode']:02} for '{media_info['name']}'"
184
+ )
185
+ return None
186
+
187
+ format_str = args.format or "{name} S{season:02}E{episode:02} - {title}"
188
+
189
+ try:
190
+ return format_str.format(
191
+ name=utils.clean_show_name(media_info["name"], args.char),
192
+ season=file_info["season"],
193
+ episode=file_info["episode"],
194
+ title=utils.clean_show_name(episode_info["name"], args.char),
195
+ year=media_info.get("premiered", "N/A").split("-")[0]
196
+ if media_info.get("premiered")
197
+ else "N/A",
198
+ )
199
+ except KeyError as e:
200
+ print(f"[!] Unknown placeholder in --format: {e}")
201
+ return None
202
+
203
+
204
+ def rename_movie(file_info, args):
205
+ api_key = args.api_key or api.get_omdb_key()
206
+ media_info = api.fetch_omdb_metadata(file_info["name"], file_info["year"], api_key)
207
+
208
+ if not media_info:
209
+ if args.verbose:
210
+ print(f" → [API Error] Could not find movie '{file_info['name']}'")
211
+ return None
212
+
213
+ format_str = args.format or "{name} ({year})"
214
+
215
+ try:
216
+ return format_str.format(
217
+ name=utils.clean_show_name(media_info["Title"], args.char),
218
+ year=media_info.get("Year", "N/A"),
219
+ director=media_info.get("Director", "N/A").split(",")[0],
220
+ genre=media_info.get("Genre", "N/A").split(",")[0],
221
+ )
222
+ except KeyError as e:
223
+ print(f"[!] Unknown placeholder in --format: {e}")
224
+ return None
225
+
226
+
227
+ def list_detected_media(directory, extensions, is_movie=False, args=None):
228
+ if is_movie and args is None:
229
+ raise ValueError("args is required when is_movie=True")
230
+
231
+ media = {}
232
+ if is_movie:
233
+ api_key = args.api_key or api.get_omdb_key()
234
+
235
+ for filename in os.listdir(directory):
236
+ file_ext = os.path.splitext(filename)[1][1:]
237
+ if file_ext.lower() in [e.lower() for e in extensions]:
238
+ info = utils.parse_filename(os.path.splitext(filename)[0], is_movie)
239
+ if info:
240
+ name = info["name"]
241
+ if is_movie:
242
+ if name not in media:
243
+ media_info = api.fetch_omdb_metadata(
244
+ info["name"], info["year"], api_key
245
+ )
246
+ if media_info:
247
+ media[name] = {
248
+ "filename": filename,
249
+ "title": media_info.get("Title", "N/A"),
250
+ "year": media_info.get("Year", "N/A"),
251
+ "director": media_info.get("Director", "N/A"),
252
+ "genre": media_info.get("Genre", "N/A"),
253
+ "runtime": media_info.get("Runtime", "N/A"),
254
+ "rated": media_info.get("Rated", "N/A"),
255
+ "released": media_info.get("Released", "N/A"),
256
+ "writer": media_info.get("Writer", "N/A"),
257
+ "actors": media_info.get("Actors", "N/A"),
258
+ "plot": media_info.get("Plot", "N/A"),
259
+ "language": media_info.get("Language", "N/A"),
260
+ "country": media_info.get("Country", "N/A"),
261
+ "awards": media_info.get("Awards", "N/A"),
262
+ "imdb_rating": media_info.get("imdbRating", "N/A"),
263
+ "rotten_tomatoes": get_rating(
264
+ media_info, "Rotten Tomatoes"
265
+ ),
266
+ "metacritic": get_rating(media_info, "Metacritic"),
267
+ "box_office": media_info.get("BoxOffice", "N/A"),
268
+ "production": media_info.get("Production", "N/A"),
269
+ }
270
+ else:
271
+ if name not in media:
272
+ show_info = api.search_media(name, "shows")
273
+
274
+ all_episodes = []
275
+ cast = []
276
+
277
+ if show_info:
278
+ all_episodes = api.get_show_episodes(show_info["id"])
279
+ cast = api.get_show_cast(show_info["id"])
280
+
281
+ media[name] = {
282
+ "show_info": show_info,
283
+ "all_episodes": all_episodes,
284
+ "cast": cast,
285
+ "seasons": {},
286
+ }
287
+
288
+ season = info["season"]
289
+ episode = info["episode"]
290
+
291
+ if season not in media[name]["seasons"]:
292
+ media[name]["seasons"][season] = set()
293
+
294
+ media[name]["seasons"][season].add(episode)
295
+
296
+ for name, data in media.items():
297
+ if is_movie:
298
+ print(f"Movie Name: {data['title']}")
299
+ print(f"Filename: {data['filename']}")
300
+ print(f"Year: {data['year']}")
301
+ print(f"Director(s): {data['director']}")
302
+ print(f"Genre(s): {data['genre']}")
303
+ print(f"Runtime: {data['runtime']}")
304
+ print(f"Rated: {data['rated']}")
305
+ print(f"Released: {data['released']}")
306
+ print(f"Writer(s): {data['writer']}")
307
+ print(f"Main Cast: {data['actors']}")
308
+ wrapped_plot = textwrap.fill(
309
+ data["plot"], width=80, subsequent_indent=" "
310
+ )
311
+ print(f"Plot: {wrapped_plot}")
312
+ # print(f"Plot: {data['plot']}")
313
+ print(f"Language(s): {data['language']}")
314
+ print(f"Country: {data['country']}")
315
+ print(f"Awards: {data['awards']}")
316
+ print(f"IMDb Rating: {data['imdb_rating']}")
317
+ print(f"Rotten Tomatoes: {data['rotten_tomatoes']}")
318
+ print(f"Metacritic: {data['metacritic']}")
319
+ print(f"Box Office: {data['box_office']}")
320
+ print(f"Production/Studio: {data['production']}")
321
+ print("---\n")
322
+ else:
323
+ show = data.get("show_info")
324
+
325
+ if not show:
326
+ print(f"[i] {name}")
327
+ continue
328
+
329
+ print(f"Show Name: {show.get('name', 'N/A')}")
330
+
331
+ premiered = show.get("premiered")
332
+ if premiered:
333
+ print(f"Premiered: {premiered[:4]}")
334
+
335
+ ended = show.get("ended")
336
+ if ended:
337
+ print(f"Ended: {ended[:4]}")
338
+
339
+ print(f"Status: {show.get('status', 'N/A')}")
340
+ print(f"Genres: {', '.join(show.get('genres', [])) or 'N/A'}")
341
+ print(f"Language: {show.get('language', 'N/A')}")
342
+
343
+ network = show.get("network")
344
+ country = None
345
+ if network:
346
+ country = network.get("country", {}).get("name")
347
+ if country:
348
+ print(f"Country: {country}")
349
+
350
+ runtime = show.get("runtime")
351
+ if runtime:
352
+ print(f"Runtime: {runtime} min")
353
+
354
+ cast_names = [actor["person"]["name"] for actor in data.get("cast", [])[:5]]
355
+ if cast_names:
356
+ print(f"Main Cast: {', '.join(cast_names)}")
357
+
358
+ rating = show.get("rating", {})
359
+ if rating.get("average"):
360
+ print(f"Rating: {rating['average']}")
361
+
362
+ summary = show.get("summary")
363
+ if summary:
364
+ summary = re.sub(r"<[^>]*>", "", summary)
365
+ wrapped_summary = textwrap.fill(
366
+ summary, width=80, subsequent_indent=" "
367
+ )
368
+ print(f"Summary: {wrapped_summary}")
369
+
370
+ official = {}
371
+ for ep in data["all_episodes"]:
372
+ season = ep["season"]
373
+ if season not in official:
374
+ official[season] = set()
375
+ official[season].add(ep["number"])
376
+
377
+ print(f"Total Seasons: {len(official)}")
378
+ total_episode_count = sum(len(eps) for eps in official.values())
379
+ print(f"Total Episodes: {total_episode_count}")
380
+
381
+ print("\t")
382
+ print("Local Collection Status:")
383
+ present_seasons = set(data["seasons"].keys())
384
+ official_seasons = set(official.keys())
385
+ for season_num in sorted(present_seasons):
386
+ owned = data["seasons"][season_num]
387
+ total = official.get(season_num, set())
388
+ owned_count = len(owned)
389
+ total_count = len(total)
390
+ if total_count and owned_count == total_count:
391
+ print(
392
+ f"[✓] Season {season_num:02}: "
393
+ f"{owned_count} / {total_count} episodes (Complete)"
394
+ )
395
+ else:
396
+ print(
397
+ f"[!] Season {season_num:02}: "
398
+ f"{owned_count} / {total_count} episodes"
399
+ )
400
+ if total:
401
+ missing = sorted(total - owned)
402
+ if missing:
403
+ available = sorted(owned)
404
+ print(
405
+ f" Available: Episodes {format_episode_ranges(available)}"
406
+ )
407
+ print(
408
+ f" Missing: Episodes {format_episode_ranges(missing)}"
409
+ )
410
+
411
+ # Missing Seasons
412
+ missing_seasons = sorted(official_seasons - present_seasons)
413
+ print()
414
+ if missing_seasons:
415
+ print(f"Missing Seasons: {format_ranges(missing_seasons)}")
416
+ else:
417
+ print("Missing Seasons: None")
418
+ present_episode_count = sum(
419
+ len(season) for season in data["seasons"].values()
420
+ )
421
+ print()
422
+
423
+ # Collection Summary
424
+ print("Collection Summary:")
425
+ print(f"Seasons Present: {len(present_seasons)} / {len(official_seasons)}")
426
+ print(f"Episodes Present: {present_episode_count} / {total_episode_count}")
427
+
428
+ if present_episode_count == total_episode_count and len(
429
+ present_seasons
430
+ ) == len(official_seasons):
431
+ print("Collection Complete")
432
+
433
+ print("---\n")
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shownamer
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: A powerful yet lightweight command-line tool that automatically renames your TV show and movie files.
5
- Author-email: Gemini <gemini@google.com>
5
+ Author-email: Amal Lalgi <theamallalgi+shownamer@google.com>
6
6
  Classifier: Programming Language :: Python :: 3
7
7
  Classifier: License :: OSI Approved :: MIT License
8
8
  Classifier: Operating System :: OS Independent
@@ -1,180 +0,0 @@
1
- import os
2
- import shutil
3
- from pathlib import Path
4
- from . import utils, api
5
- from . import titleEmbed
6
-
7
-
8
- def process_directory(args):
9
- if args.name:
10
- list_detected_media(args.dir, args.ext, args.movie, args)
11
- return
12
-
13
- for filename in os.listdir(args.dir):
14
- file_ext = os.path.splitext(filename)[1][1:]
15
- if file_ext.lower() in [e.lower() for e in args.ext]:
16
- process_file(filename, args)
17
-
18
-
19
- def process_file(filename, args):
20
- if args.format:
21
- try:
22
- utils.validate_format(args.format)
23
- except ValueError as e:
24
- print(f"[!] Error: {e}")
25
- return
26
-
27
- if args.verbose:
28
- print(f"Processing: {filename}")
29
-
30
- file_path = os.path.join(args.dir, filename)
31
- file_info = utils.parse_filename(os.path.splitext(filename)[0], args.movie)
32
-
33
- if not file_info:
34
- if args.verbose:
35
- print(f"[skip] Could not parse file information from '{filename}'")
36
- return
37
-
38
- new_name = ""
39
- if args.movie:
40
- new_name = rename_movie(file_info, args)
41
- else:
42
- if not file_info.get("is_movie"):
43
- new_name = rename_show(file_info, args)
44
-
45
- if new_name:
46
- file_ext = os.path.splitext(filename)[1]
47
- new_filename = new_name + file_ext
48
- new_filepath = os.path.join(args.dir, new_filename)
49
-
50
- print(f"[rename] '{filename}' → '{new_filename}'")
51
-
52
- if not args.dry_run:
53
- if os.path.exists(new_filepath):
54
- print(f"[skip] '{new_filename}' already exists.")
55
- else:
56
- try:
57
- shutil.move(file_path, new_filepath)
58
- if args.title:
59
- titleStr = _buildTitleStr(new_name, args, file_info)
60
- success = titleEmbed.embedTitle(Path(new_filepath), titleStr)
61
- if args.verbose:
62
- if success:
63
- print(f" → [title] Embedded: {titleStr}")
64
- else:
65
- print(f" → [title] Failed to embed metadata (ffmpeg/mutagen required)")
66
- except OSError as e:
67
- print(f" → [!] Error renaming file: {e}")
68
- else:
69
- if args.title:
70
- titleStr = _buildTitleStr(new_name, args, file_info)
71
- print(f" → [title] Would embed: {titleStr}")
72
- elif args.verbose:
73
- print(f"[skip] No new name generated for '{filename}'")
74
-
75
-
76
- def _buildTitleStr(resolvedName: str, args, fileInfo: dict) -> str:
77
- if args.format:
78
- return resolvedName
79
- if args.movie:
80
- name, _, year = resolvedName.rpartition(" (")
81
- return titleEmbed.buildMovieTitle(name, year.rstrip(")"))
82
- season = fileInfo.get("season", 1)
83
- episode = fileInfo.get("episode", 1)
84
- title = resolvedName.split(" - ", 1)[-1] if " - " in resolvedName else resolvedName
85
- showName = fileInfo.get("name", resolvedName)
86
- return titleEmbed.buildShowTitle(showName, season, episode, title)
87
-
88
-
89
- def rename_show(file_info, args):
90
- media_info = api.search_media(file_info["name"], "shows")
91
- if not media_info:
92
- if args.verbose:
93
- print(f" → [API Error] Could not find show '{file_info['name']}'")
94
- return None
95
-
96
- show_id = media_info["id"]
97
- episode_info = api.get_episode_by_number(show_id, file_info["season"], file_info["episode"])
98
- if not episode_info:
99
- if args.verbose:
100
- print(f" → [API Error] Could not find episode S{file_info['season']:02}E{file_info['episode']:02} for '{media_info['name']}'")
101
- return None
102
-
103
- format_str = args.format or "{name} S{season:02}E{episode:02} - {title}"
104
-
105
- try:
106
- return format_str.format(
107
- name=utils.clean_show_name(media_info["name"], args.char),
108
- season=file_info["season"],
109
- episode=file_info["episode"],
110
- title=utils.clean_show_name(episode_info["name"], args.char),
111
- year=media_info.get("premiered", "N/A").split("-")[0] if media_info.get("premiered") else "N/A",
112
- )
113
- except KeyError as e:
114
- print(f"[!] Unknown placeholder in --format: {e}")
115
- return None
116
-
117
-
118
- def rename_movie(file_info, args):
119
- api_key = args.api_key or api.get_omdb_key()
120
- media_info = api.fetch_omdb_metadata(file_info["name"], file_info["year"], api_key)
121
-
122
- if not media_info:
123
- if args.verbose:
124
- print(f" → [API Error] Could not find movie '{file_info['name']}'")
125
- return None
126
-
127
- format_str = args.format or "{name} ({year})"
128
-
129
- try:
130
- return format_str.format(
131
- name=utils.clean_show_name(media_info["Title"], args.char),
132
- year=media_info.get("Year", "N/A"),
133
- director=media_info.get("Director", "N/A").split(",")[0],
134
- genre=media_info.get("Genre", "N/A").split(",")[0],
135
- )
136
- except KeyError as e:
137
- print(f"[!] Unknown placeholder in --format: {e}")
138
- return None
139
-
140
-
141
- def list_detected_media(directory, extensions, is_movie=False, args=None):
142
- if is_movie and args is None:
143
- raise ValueError("args is required when is_movie=True")
144
-
145
- media = {}
146
- if is_movie:
147
- api_key = args.api_key or api.get_omdb_key()
148
-
149
- for filename in os.listdir(directory):
150
- file_ext = os.path.splitext(filename)[1][1:]
151
- if file_ext.lower() in [e.lower() for e in extensions]:
152
- info = utils.parse_filename(os.path.splitext(filename)[0], is_movie)
153
- if info:
154
- name = info["name"]
155
- if is_movie:
156
- if name not in media:
157
- media_info = api.fetch_omdb_metadata(info["name"], info["year"], api_key)
158
- if media_info:
159
- media[name] = {
160
- "filename": filename,
161
- "year": media_info.get("Year", "N/A"),
162
- "director": media_info.get("Director", "N/A"),
163
- "genre": media_info.get("Genre", "N/A"),
164
- }
165
- else:
166
- if name not in media:
167
- media[name] = {"seasons": set(), "episodes": 0}
168
- media[name]["seasons"].add(info["season"])
169
- media[name]["episodes"] += 1
170
-
171
- for name, data in media.items():
172
- if is_movie:
173
- print(f"Movie Name: {name}")
174
- print(f"Filename: {data['filename']}")
175
- print(f"Year of Release: {data['year']}")
176
- print(f"Director: {data['director']}")
177
- print(f"Genre: {data['genre']}")
178
- print("---\n")
179
- else:
180
- print(f"[i] {name}: {len(data['seasons'])} season(s), {data['episodes']} episode(s)")
File without changes
File without changes
File without changes
File without changes
File without changes