mkv-episode-matcher 0.5.0__py3-none-any.whl → 0.7.0__py3-none-any.whl

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.

Potentially problematic release.


This version of mkv-episode-matcher might be problematic. Click here for more details.

Files changed (24) hide show
  1. mkv_episode_matcher/__init__.py +2 -2
  2. mkv_episode_matcher/__main__.py +222 -76
  3. mkv_episode_matcher/config.py +0 -3
  4. mkv_episode_matcher/episode_identification.py +164 -124
  5. mkv_episode_matcher/episode_matcher.py +102 -55
  6. mkv_episode_matcher/subtitle_utils.py +26 -25
  7. mkv_episode_matcher/utils.py +74 -57
  8. {mkv_episode_matcher-0.5.0.dist-info → mkv_episode_matcher-0.7.0.dist-info}/METADATA +10 -13
  9. mkv_episode_matcher-0.7.0.dist-info/RECORD +14 -0
  10. {mkv_episode_matcher-0.5.0.dist-info → mkv_episode_matcher-0.7.0.dist-info}/WHEEL +1 -1
  11. mkv_episode_matcher/libraries/pgs2srt/.gitignore +0 -2
  12. mkv_episode_matcher/libraries/pgs2srt/Libraries/SubZero/SubZero.py +0 -321
  13. mkv_episode_matcher/libraries/pgs2srt/Libraries/SubZero/dictionaries/data.py +0 -16700
  14. mkv_episode_matcher/libraries/pgs2srt/Libraries/SubZero/post_processing.py +0 -260
  15. mkv_episode_matcher/libraries/pgs2srt/README.md +0 -26
  16. mkv_episode_matcher/libraries/pgs2srt/__init__.py +0 -0
  17. mkv_episode_matcher/libraries/pgs2srt/imagemaker.py +0 -89
  18. mkv_episode_matcher/libraries/pgs2srt/pgs2srt.py +0 -150
  19. mkv_episode_matcher/libraries/pgs2srt/pgsreader.py +0 -225
  20. mkv_episode_matcher/libraries/pgs2srt/requirements.txt +0 -4
  21. mkv_episode_matcher/mkv_to_srt.py +0 -302
  22. mkv_episode_matcher-0.5.0.dist-info/RECORD +0 -25
  23. {mkv_episode_matcher-0.5.0.dist-info → mkv_episode_matcher-0.7.0.dist-info}/entry_points.txt +0 -0
  24. {mkv_episode_matcher-0.5.0.dist-info → mkv_episode_matcher-0.7.0.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,9 @@
1
1
  """MKV Episode Matcher package."""
2
- from importlib.metadata import version, PackageNotFoundError
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
3
4
 
4
5
  try:
5
6
  __version__ = version("mkv-episode-matcher")
6
7
  except PackageNotFoundError:
7
8
  # package is not installed
8
9
  __version__ = "unknown"
9
-
@@ -1,40 +1,47 @@
1
- # __main__.py
1
+ # __main__.py (enhanced version)
2
2
  import argparse
3
3
  import os
4
4
  import sys
5
+ from typing import Optional
5
6
 
6
7
  from loguru import logger
8
+ from rich.console import Console
9
+ from rich.panel import Panel
10
+ from rich.progress import Progress, SpinnerColumn, TextColumn
11
+ from rich.prompt import Confirm, Prompt
12
+
7
13
  from mkv_episode_matcher import __version__
8
14
  from mkv_episode_matcher.config import get_config, set_config
9
15
 
16
+ # Initialize rich console for better output
17
+ console = Console()
18
+
10
19
  # Log the start of the application
11
20
  logger.info("Starting the application")
12
21
 
13
-
14
22
  # Check if the configuration directory exists, if not create it
15
- if not os.path.exists(os.path.join(os.path.expanduser("~"), ".mkv-episode-matcher")):
16
- os.makedirs(os.path.join(os.path.expanduser("~"), ".mkv-episode-matcher"))
23
+ CONFIG_DIR = os.path.join(os.path.expanduser("~"), ".mkv-episode-matcher")
24
+ if not os.path.exists(CONFIG_DIR):
25
+ os.makedirs(CONFIG_DIR)
17
26
 
18
27
  # Define the paths for the configuration file and cache directory
19
- CONFIG_FILE = os.path.join(
20
- os.path.expanduser("~"), ".mkv-episode-matcher", "config.ini"
21
- )
22
- CACHE_DIR = os.path.join(os.path.expanduser("~"), ".mkv-episode-matcher", "cache")
28
+ CONFIG_FILE = os.path.join(CONFIG_DIR, "config.ini")
29
+ CACHE_DIR = os.path.join(CONFIG_DIR, "cache")
23
30
 
24
31
  # Check if the cache directory exists, if not create it
25
32
  if not os.path.exists(CACHE_DIR):
26
33
  os.makedirs(CACHE_DIR)
27
34
 
28
35
  # Check if logs directory exists, if not create it
29
- log_dir = os.path.join(os.path.expanduser("~"), ".mkv-episode-matcher", "logs")
36
+ log_dir = os.path.join(CONFIG_DIR, "logs")
30
37
  if not os.path.exists(log_dir):
31
38
  os.mkdir(log_dir)
32
-
39
+ logger.remove()
33
40
  # Add a new handler for stdout logs
34
41
  logger.add(
35
42
  os.path.join(log_dir, "stdout.log"),
36
43
  format="{time} {level} {message}",
37
- level="DEBUG",
44
+ level="INFO",
38
45
  rotation="10 MB",
39
46
  )
40
47
 
@@ -42,32 +49,96 @@ logger.add(
42
49
  logger.add(os.path.join(log_dir, "stderr.log"), level="ERROR", rotation="10 MB")
43
50
 
44
51
 
45
- @logger.catch
46
- def main():
52
+ def print_welcome_message():
53
+ """Print a stylized welcome message."""
54
+ console.print(
55
+ Panel.fit(
56
+ f"[bold blue]MKV Episode Matcher v{__version__}[/bold blue]\n"
57
+ "[cyan]Automatically match and rename your MKV TV episodes[/cyan]",
58
+ border_style="blue",
59
+ padding=(1, 4),
60
+ )
61
+ )
62
+ console.print()
63
+
64
+
65
+ def confirm_api_key(config_value: Optional[str], key_name: str, description: str) -> str:
66
+ """
67
+ Confirm if the user wants to use an existing API key or enter a new one.
68
+
69
+ Args:
70
+ config_value: The current value from the config
71
+ key_name: The name of the key
72
+ description: Description of the key for user information
73
+
74
+ Returns:
75
+ The API key to use
47
76
  """
48
- Entry point of the application.
77
+ if config_value:
78
+ console.print(f"[cyan]{key_name}:[/cyan] {description}")
79
+ console.print(f"Current value: [green]{mask_api_key(config_value)}[/green]")
80
+ if Confirm.ask("Use existing key?", default=True):
81
+ return config_value
82
+
83
+ return Prompt.ask(f"Enter your {key_name}")
84
+
85
+
86
+ def mask_api_key(key: str) -> str:
87
+ """Mask the API key for display purposes."""
88
+ if not key:
89
+ return ""
90
+ if len(key) <= 8:
91
+ return "*" * len(key)
92
+ return key[:4] + "*" * (len(key) - 8) + key[-4:]
93
+
49
94
 
50
- This function is responsible for starting the application, parsing command-line arguments,
51
- setting the configuration, and processing the show.
95
+ def select_season(seasons):
96
+ """
97
+ Allow user to select a season from a list.
98
+
99
+ Args:
100
+ seasons: List of available seasons
101
+
102
+ Returns:
103
+ Selected season number or None for all seasons
104
+ """
105
+ console.print("[bold cyan]Available Seasons:[/bold cyan]")
106
+ for i, season in enumerate(seasons, 1):
107
+ season_num = os.path.basename(season).replace("Season ", "")
108
+ console.print(f" {i}. Season {season_num}")
109
+
110
+ console.print(f" 0. All Seasons")
111
+
112
+ choice = Prompt.ask(
113
+ "Select a season number (0 for all)",
114
+ choices=[str(i) for i in range(len(seasons) + 1)],
115
+ default="0"
116
+ )
117
+
118
+ if int(choice) == 0:
119
+ return None
120
+
121
+ selected_season = seasons[int(choice) - 1]
122
+ return int(os.path.basename(selected_season).replace("Season ", ""))
52
123
 
53
- Command-line arguments:
54
- --tmdb-api-key: The API key for the TMDb API. If not provided, the function will try to get it from the cache or prompt the user to input it.
55
- --show-dir: The main directory of the show. If not provided, the function will prompt the user to input it.
56
- --season: The season number to be processed. If not provided, all seasons will be processed.
57
- --dry-run: A boolean flag indicating whether to perform a dry run (i.e., not rename any files). If not provided, the function will rename files.
58
- --get-subs: A boolean flag indicating whether to download subtitles for the show. If not provided, the function will not download subtitles.
59
- --tesseract-path: The path to the tesseract executable. If not provided, the function will try to get it from the cache or prompt the user to input it.
60
124
 
61
- The function logs its progress to two separate log files: one for standard output and one for errors.
125
+ @logger.catch
126
+ def main():
62
127
  """
128
+ Entry point of the application with enhanced user interface.
129
+ """
130
+ print_welcome_message()
63
131
 
64
132
  # Parse command-line arguments
65
- parser = argparse.ArgumentParser(description="Process shows with TMDb API")
133
+ parser = argparse.ArgumentParser(
134
+ description="Automatically match and rename your MKV TV episodes",
135
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
136
+ )
66
137
  parser.add_argument(
67
138
  "--version",
68
139
  action="version",
69
140
  version=f"%(prog)s {__version__}",
70
- help="Show the version number and exit"
141
+ help="Show the version number and exit",
71
142
  )
72
143
  parser.add_argument("--tmdb-api-key", help="TMDb API key")
73
144
  parser.add_argument("--show-dir", help="Main directory of the show")
@@ -76,41 +147,46 @@ def main():
76
147
  type=int,
77
148
  default=None,
78
149
  nargs="?",
79
- help="Specify the season number to be processed (default: None)",
150
+ help="Specify the season number to be processed (default: all seasons)",
80
151
  )
81
152
  parser.add_argument(
82
153
  "--dry-run",
83
- type=bool,
84
- default=None,
85
- nargs="?",
86
- help="Don't rename any files (default: None)",
154
+ action="store_true",
155
+ help="Don't rename any files, just show what would happen",
87
156
  )
88
157
  parser.add_argument(
89
158
  "--get-subs",
90
- type=bool,
91
- default=None,
92
- nargs="?",
93
- help="Download subtitles for the show (default: None)",
159
+ action="store_true",
160
+ help="Download subtitles for the show",
94
161
  )
95
162
  parser.add_argument(
96
- "--tesseract-path",
97
- type=str,
98
- default=None,
99
- nargs="?",
100
- help="Path to the tesseract executable (default: None)",
163
+ "--check-gpu",
164
+ action="store_true",
165
+ help="Check if GPU is available for faster processing",
101
166
  )
102
167
  parser.add_argument(
103
- "--check-gpu",
104
- type=bool,
105
- default=False,
106
- nargs="?",
107
- help="Check if GPU is available (default: False)",
168
+ "--verbose", "-v",
169
+ action="store_true",
170
+ help="Enable verbose output",
171
+ )
172
+ parser.add_argument(
173
+ "--confidence",
174
+ type=float,
175
+ default=0.7,
176
+ help="Set confidence threshold for episode matching (0.0-1.0)",
108
177
  )
178
+
109
179
  args = parser.parse_args()
180
+ if args.verbose:
181
+ console.print("[bold cyan]Command-line Arguments[/bold cyan]")
182
+ console.print(args)
110
183
  if args.check_gpu:
111
184
  from mkv_episode_matcher.utils import check_gpu_support
112
- check_gpu_support()
185
+ with console.status("[bold green]Checking GPU support..."):
186
+ check_gpu_support()
113
187
  return
188
+
189
+
114
190
  logger.debug(f"Command-line arguments: {args}")
115
191
 
116
192
  # Load configuration once
@@ -118,43 +194,61 @@ def main():
118
194
 
119
195
  # Get TMDb API key
120
196
  tmdb_api_key = args.tmdb_api_key or config.get("tmdb_api_key")
121
- if not tmdb_api_key:
122
- tmdb_api_key = input("Enter your TMDb API key: ")
123
- logger.debug(f"TMDb API Key: {tmdb_api_key}")
124
-
125
- logger.debug("Getting OpenSubtitles API key")
197
+
126
198
  open_subtitles_api_key = config.get("open_subtitles_api_key")
127
199
  open_subtitles_user_agent = config.get("open_subtitles_user_agent")
128
200
  open_subtitles_username = config.get("open_subtitles_username")
129
201
  open_subtitles_password = config.get("open_subtitles_password")
130
-
202
+
131
203
  if args.get_subs:
132
- if not open_subtitles_api_key:
133
- open_subtitles_api_key = input("Enter your OpenSubtitles API key: ")
134
- if not open_subtitles_user_agent:
135
- open_subtitles_user_agent = input("Enter your OpenSubtitles User Agent: ")
136
- if not open_subtitles_username:
137
- open_subtitles_username = input("Enter your OpenSubtitles Username: ")
138
- if not open_subtitles_password:
139
- open_subtitles_password = input("Enter your OpenSubtitles Password: ")
140
-
141
- # Use config for show directory and tesseract path
204
+ console.print("[bold cyan]Subtitle Download Configuration[/bold cyan]")
205
+
206
+ tmdb_api_key = confirm_api_key(
207
+ tmdb_api_key,
208
+ "TMDb API key",
209
+ "Used to lookup show and episode information"
210
+ )
211
+
212
+ open_subtitles_api_key = confirm_api_key(
213
+ open_subtitles_api_key,
214
+ "OpenSubtitles API key",
215
+ "Required for subtitle downloads"
216
+ )
217
+
218
+ open_subtitles_user_agent = confirm_api_key(
219
+ open_subtitles_user_agent,
220
+ "OpenSubtitles User Agent",
221
+ "Required for subtitle downloads"
222
+ )
223
+
224
+ open_subtitles_username = confirm_api_key(
225
+ open_subtitles_username,
226
+ "OpenSubtitles Username",
227
+ "Account username for OpenSubtitles"
228
+ )
229
+
230
+ open_subtitles_password = confirm_api_key(
231
+ open_subtitles_password,
232
+ "OpenSubtitles Password",
233
+ "Account password for OpenSubtitles"
234
+ )
235
+
236
+ # Use config for show directory
142
237
  show_dir = args.show_dir or config.get("show_dir")
143
238
  if not show_dir:
144
- show_dir = input("Enter the main directory of the show:")
239
+ show_dir = Prompt.ask("Enter the main directory of the show")
240
+
145
241
  logger.info(f"Show Directory: {show_dir}")
242
+ if not os.path.exists(show_dir):
243
+ console.print(f"[bold red]Error:[/bold red] Show directory '{show_dir}' does not exist.")
244
+ return
245
+
146
246
  if not show_dir:
147
247
  show_dir = os.getcwd()
148
-
149
- if not args.tesseract_path:
150
- tesseract_path = config.get("tesseract_path")
151
- if not tesseract_path:
152
- tesseract_path = input(r"Enter the path to the tesseract executable: ['C:\Program Files\Tesseract-OCR\tesseract.exe']")
153
- else:
154
- tesseract_path = args.tesseract_path
155
- logger.debug(f"Teesseract Path: {tesseract_path}")
248
+ console.print(f"Using current directory: [cyan]{show_dir}[/cyan]")
249
+
156
250
  logger.debug(f"Show Directory: {show_dir}")
157
-
251
+
158
252
  # Set the configuration
159
253
  set_config(
160
254
  tmdb_api_key,
@@ -164,17 +258,69 @@ def main():
164
258
  open_subtitles_password,
165
259
  show_dir,
166
260
  CONFIG_FILE,
167
- tesseract_path=tesseract_path,
168
261
  )
169
262
  logger.info("Configuration set")
170
263
 
171
264
  # Process the show
172
265
  from mkv_episode_matcher.episode_matcher import process_show
266
+ from mkv_episode_matcher.utils import get_valid_seasons
173
267
 
174
- process_show(args.season, dry_run=args.dry_run, get_subs=args.get_subs)
175
- logger.info("Show processing completed")
268
+ console.print()
269
+ if args.dry_run:
270
+ console.print(
271
+ Panel.fit(
272
+ "[bold yellow]DRY RUN MODE[/bold yellow]\n"
273
+ "Files will not be renamed, only showing what would happen.",
274
+ border_style="yellow",
275
+ )
276
+ )
277
+
278
+ seasons = get_valid_seasons(show_dir)
279
+ if not seasons:
280
+ console.print("[bold red]Error:[/bold red] No seasons with .mkv files found in the show directory.")
281
+ return
282
+
283
+ # If season wasn't specified and there are multiple seasons, let user choose
284
+ selected_season = args.season
285
+ if selected_season is None and len(seasons) > 1:
286
+ selected_season = select_season(seasons)
287
+
288
+ # Show what's going to happen
289
+ show_name = os.path.basename(show_dir)
290
+ season_text = f"Season {selected_season}" if selected_season else "all seasons"
291
+
292
+ console.print(
293
+ f"[bold green]Processing[/bold green] [cyan]{show_name}[/cyan], {season_text}"
294
+ )
295
+
296
+ # # Setup progress spinner
297
+ # with Progress(
298
+ # TextColumn("[bold green]Processing...[/bold green]"),
299
+ # console=console,
300
+ # ) as progress:
301
+ # task = progress.add_task("", total=None)
302
+ process_show(
303
+ selected_season,
304
+ dry_run=args.dry_run,
305
+ get_subs=args.get_subs,
306
+ verbose=args.verbose,
307
+ confidence=args.confidence
308
+ )
309
+
310
+ console.print("[bold green]✓[/bold green] Processing completed successfully!")
311
+
312
+ # Show where logs are stored
313
+ console.print(f"\n[dim]Logs available at: {log_dir}[/dim]")
176
314
 
177
315
 
178
316
  # Run the main function if the script is run directly
179
317
  if __name__ == "__main__":
180
- main()
318
+ try:
319
+ main()
320
+ except KeyboardInterrupt:
321
+ console.print("\n[yellow]Process interrupted by user.[/yellow]")
322
+ sys.exit(1)
323
+ except Exception as e:
324
+ console.print(f"\n[bold red]Error:[/bold red] {str(e)}")
325
+ logger.exception("Unhandled exception")
326
+ sys.exit(1)
@@ -27,7 +27,6 @@ def set_config(
27
27
  open_subtitles_password,
28
28
  show_dir,
29
29
  file,
30
- tesseract_path=None,
31
30
  ):
32
31
  """
33
32
  Sets the configuration values and writes them to a file.
@@ -40,7 +39,6 @@ def set_config(
40
39
  open_subtitles_password (str): The password for OpenSubtitles.
41
40
  show_dir (str): The directory where the TV show episodes are located.
42
41
  file (str): The path to the configuration file.
43
- tesseract_path (str, optional): The path to the Tesseract OCR executable.
44
42
 
45
43
  Returns:
46
44
  None
@@ -54,7 +52,6 @@ def set_config(
54
52
  "open_subtitles_user_agent": str(open_subtitles_user_agent),
55
53
  "open_subtitles_username": str(open_subtitles_username),
56
54
  "open_subtitles_password": str(open_subtitles_password),
57
- "tesseract_path": str(tesseract_path),
58
55
  }
59
56
  logger.info(
60
57
  f"Setting config with API:{tmdb_api_key}, show_dir: {show_dir}, and max_threads: {MAX_THREADS}"