mkv-episode-matcher 0.3.3__py3-none-any.whl → 0.3.4__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.

@@ -0,0 +1,82 @@
1
+ from typing import List, Optional, Union
2
+ import os
3
+ import re
4
+
5
+ def generate_subtitle_patterns(series_name: str, season: int, episode: int) -> List[str]:
6
+ """
7
+ Generate various common subtitle filename patterns.
8
+
9
+ Args:
10
+ series_name (str): Name of the series
11
+ season (int): Season number
12
+ episode (int): Episode number
13
+
14
+ Returns:
15
+ List[str]: List of possible subtitle filenames
16
+ """
17
+ patterns = [
18
+ # Standard format: "Show Name - S01E02.srt"
19
+ f"{series_name} - S{season:02d}E{episode:02d}.srt",
20
+
21
+ # Season x Episode format: "Show Name - 1x02.srt"
22
+ f"{series_name} - {season}x{episode:02d}.srt",
23
+
24
+ # Separate season/episode: "Show Name - Season 1 Episode 02.srt"
25
+ f"{series_name} - Season {season} Episode {episode:02d}.srt",
26
+
27
+ # Compact format: "ShowName.S01E02.srt"
28
+ f"{series_name.replace(' ', '')}.S{season:02d}E{episode:02d}.srt",
29
+
30
+ # Numbered format: "Show Name 102.srt"
31
+ f"{series_name} {season:01d}{episode:02d}.srt",
32
+
33
+ # Dot format: "Show.Name.1x02.srt"
34
+ f"{series_name.replace(' ', '.')}.{season}x{episode:02d}.srt",
35
+
36
+ # Underscore format: "Show_Name_S01E02.srt"
37
+ f"{series_name.replace(' ', '_')}_S{season:02d}E{episode:02d}.srt",
38
+ ]
39
+
40
+ return patterns
41
+
42
+ def find_existing_subtitle(series_cache_dir: str, series_name: str, season: int, episode: int) -> Optional[str]:
43
+ """
44
+ Check for existing subtitle files in various naming formats.
45
+
46
+ Args:
47
+ series_cache_dir (str): Directory containing subtitle files
48
+ series_name (str): Name of the series
49
+ season (int): Season number
50
+ episode (int): Episode number
51
+
52
+ Returns:
53
+ Optional[str]: Path to existing subtitle file if found, None otherwise
54
+ """
55
+ patterns = generate_subtitle_patterns(series_name, season, episode)
56
+
57
+ for pattern in patterns:
58
+ filepath = os.path.join(series_cache_dir, pattern)
59
+ if os.path.exists(filepath):
60
+ return filepath
61
+
62
+ return None
63
+
64
+ def sanitize_filename(filename: str) -> str:
65
+ """
66
+ Sanitize filename by removing/replacing invalid characters.
67
+
68
+ Args:
69
+ filename (str): Original filename
70
+
71
+ Returns:
72
+ str: Sanitized filename
73
+ """
74
+ # Replace problematic characters
75
+ filename = filename.replace(':', ' -')
76
+ filename = filename.replace('/', '-')
77
+ filename = filename.replace('\\', '-')
78
+
79
+ # Remove any other invalid characters
80
+ filename = re.sub(r'[<>:"/\\|?*]', '', filename)
81
+
82
+ return filename.strip()
@@ -10,7 +10,7 @@ from opensubtitlescom import OpenSubtitles
10
10
  from mkv_episode_matcher.__main__ import CACHE_DIR, CONFIG_FILE
11
11
  from mkv_episode_matcher.config import get_config
12
12
  from mkv_episode_matcher.tmdb_client import fetch_season_details
13
-
13
+ from mkv_episode_matcher.subtitle_utils import find_existing_subtitle,sanitize_filename
14
14
  def get_valid_seasons(show_dir):
15
15
  """
16
16
  Get all season directories that contain MKV files.
@@ -128,20 +128,17 @@ def get_subtitles(show_id, seasons: set[int]):
128
128
  Args:
129
129
  show_id (int): The ID of the TV show.
130
130
  seasons (Set[int]): A set of season numbers for which subtitles should be retrieved.
131
-
132
- Returns:
133
- None
134
131
  """
135
-
136
132
  logger.info(f"Getting subtitles for show ID {show_id}")
137
133
  config = get_config(CONFIG_FILE)
138
134
  show_dir = config.get("show_dir")
139
- series_name = os.path.basename(show_dir)
135
+ series_name = sanitize_filename(os.path.basename(show_dir))
140
136
  tmdb_api_key = config.get("tmdb_api_key")
141
137
  open_subtitles_api_key = config.get("open_subtitles_api_key")
142
138
  open_subtitles_user_agent = config.get("open_subtitles_user_agent")
143
139
  open_subtitles_username = config.get("open_subtitles_username")
144
140
  open_subtitles_password = config.get("open_subtitles_password")
141
+
145
142
  if not all([
146
143
  show_dir,
147
144
  tmdb_api_key,
@@ -151,63 +148,66 @@ def get_subtitles(show_id, seasons: set[int]):
151
148
  open_subtitles_password,
152
149
  ]):
153
150
  logger.error("Missing configuration settings. Please run the setup script.")
151
+ return
152
+
154
153
  try:
155
- # Initialize the OpenSubtitles client
156
154
  subtitles = OpenSubtitles(open_subtitles_user_agent, open_subtitles_api_key)
157
-
158
- # Log in (retrieve auth token)
159
155
  subtitles.login(open_subtitles_username, open_subtitles_password)
160
156
  except Exception as e:
161
157
  logger.error(f"Failed to log in to OpenSubtitles: {e}")
162
158
  return
159
+
163
160
  for season in seasons:
164
161
  episodes = fetch_season_details(show_id, season)
165
162
  logger.info(f"Found {episodes} episodes in Season {season}")
166
163
 
167
164
  for episode in range(1, episodes + 1):
168
165
  logger.info(f"Processing Season {season}, Episode {episode}...")
166
+
169
167
  series_cache_dir = os.path.join(CACHE_DIR, "data", series_name)
170
168
  os.makedirs(series_cache_dir, exist_ok=True)
169
+
170
+ # Check for existing subtitle in any supported format
171
+ existing_subtitle = find_existing_subtitle(
172
+ series_cache_dir, series_name, season, episode
173
+ )
174
+
175
+ if existing_subtitle:
176
+ logger.info(f"Subtitle already exists: {os.path.basename(existing_subtitle)}")
177
+ continue
178
+
179
+ # Default to standard format for new downloads
171
180
  srt_filepath = os.path.join(
172
181
  series_cache_dir,
173
182
  f"{series_name} - S{season:02d}E{episode:02d}.srt",
174
183
  )
175
- if not os.path.exists(srt_filepath):
176
- # get the episode info from TMDB
177
- url = f"https://api.themoviedb.org/3/tv/{show_id}/season/{season}/episode/{episode}?api_key={tmdb_api_key}"
178
- response = requests.get(url)
179
- response.raise_for_status()
180
- episode_data = response.json()
181
- episode_data["name"]
182
- episode_id = episode_data["id"]
183
- # search for the subtitle
184
- response = subtitles.search(tmdb_id=episode_id, languages="en")
185
- if len(response.data) == 0:
186
- logger.warning(
187
- f"No subtitles found for {series_name} - S{season:02d}E{episode:02d}"
188
- )
189
-
190
- for subtitle in response.data:
191
- subtitle_dict = subtitle.to_dict()
192
- # Remove special characters and convert to uppercase
193
- filename_clean = re.sub(
194
- r"\W+", " ", subtitle_dict["file_name"]
195
- ).upper()
196
- if f"E{episode:02d}" in filename_clean:
197
- logger.info(f"Original filename: {subtitle_dict['file_name']}")
198
- srt_file = subtitles.download_and_save(subtitle)
199
- series_name = series_name.replace(":", " -")
200
- shutil.move(srt_file, srt_filepath)
201
- logger.info(f"Subtitle saved to {srt_filepath}")
202
- break
203
- else:
204
- continue
205
- else:
206
- logger.info(
207
- f"Subtitle already exists for {series_name} - S{season:02d}E{episode:02d}"
184
+
185
+ # get the episode info from TMDB
186
+ url = f"https://api.themoviedb.org/3/tv/{show_id}/season/{season}/episode/{episode}?api_key={tmdb_api_key}"
187
+ response = requests.get(url)
188
+ response.raise_for_status()
189
+ episode_data = response.json()
190
+ episode_id = episode_data["id"]
191
+
192
+ # search for the subtitle
193
+ response = subtitles.search(tmdb_id=episode_id, languages="en")
194
+ if len(response.data) == 0:
195
+ logger.warning(
196
+ f"No subtitles found for {series_name} - S{season:02d}E{episode:02d}"
208
197
  )
209
198
  continue
210
199
 
200
+ for subtitle in response.data:
201
+ subtitle_dict = subtitle.to_dict()
202
+ # Remove special characters and convert to uppercase
203
+ filename_clean = re.sub(r"\W+", " ", subtitle_dict["file_name"]).upper()
204
+ if f"E{episode:02d}" in filename_clean:
205
+ logger.info(f"Original filename: {subtitle_dict['file_name']}")
206
+ srt_file = subtitles.download_and_save(subtitle)
207
+ shutil.move(srt_file, srt_filepath)
208
+ logger.info(f"Subtitle saved to {srt_filepath}")
209
+ break
210
+
211
211
 
212
212
  def cleanup_ocr_files(show_dir):
213
213
  """
@@ -236,7 +236,7 @@ def clean_text(text):
236
236
  # Strip leading/trailing whitespace
237
237
  return cleaned_text.strip()
238
238
 
239
-
239
+ @logger.catch
240
240
  def process_reference_srt_files(series_name):
241
241
  """
242
242
  Process reference SRT files for a given series.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mkv-episode-matcher
3
- Version: 0.3.3
3
+ Version: 0.3.4
4
4
  Summary: The MKV Episode Matcher is a tool for identifying TV series episodes from MKV files and renaming the files accordingly.
5
5
  Home-page: https://github.com/Jsakkos/mkv-episode-matcher
6
6
  Author: Jonathan Sakkos
@@ -6,8 +6,9 @@ mkv_episode_matcher/episode_identification.py,sha256=NopEkcBFFUjjrAujogeVcdISv8U
6
6
  mkv_episode_matcher/episode_matcher.py,sha256=BJ76DPxsmZs-KfHZZ_0WvKSBZWXsUEO6lW34YdYEaxM,3979
7
7
  mkv_episode_matcher/mkv_to_srt.py,sha256=4yxBHRVhgVby0UtQ2aTXGuoQpid8pkgjMIaHU6GCdzc,10857
8
8
  mkv_episode_matcher/speech_to_text.py,sha256=-bnGvmtPCKyHFPEaXwIcEYTf_P13rNpAJA-2UFeRFrs,2806
9
+ mkv_episode_matcher/subtitle_utils.py,sha256=rYSbd393pKYQW0w4sXgals02WFGqMYYYkQHDbEkWF8c,2666
9
10
  mkv_episode_matcher/tmdb_client.py,sha256=LbMCgjmp7sCbrQo_CDlpcnryKPz5S7inE24YY9Pyjk4,4172
10
- mkv_episode_matcher/utils.py,sha256=YthQByumTL5eGdFTJMoI8csAe4Vc-sPo8XqOKzbj4g4,13975
11
+ mkv_episode_matcher/utils.py,sha256=Txnn24ou7Pg3iMq9WrT3nwBRlRP8JEuZQ2ZYW7uesp4,13972
11
12
  mkv_episode_matcher/libraries/pgs2srt/.gitignore,sha256=mt3uxWYZaFurMw_yGE258gWhtGKPVR7e3Ll4ALJpyj4,23
12
13
  mkv_episode_matcher/libraries/pgs2srt/README.md,sha256=olb25G17tj0kxPgp_LcH5I2QWXjgP1m8JFyjYRGz4UU,1374
13
14
  mkv_episode_matcher/libraries/pgs2srt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -18,8 +19,8 @@ mkv_episode_matcher/libraries/pgs2srt/requirements.txt,sha256=sg87dqWw_qpbwciw-M
18
19
  mkv_episode_matcher/libraries/pgs2srt/Libraries/SubZero/SubZero.py,sha256=geT1LXdVd8yED9zoJ9K1XfP2JzGcM7u1SslHYrJI09o,10061
19
20
  mkv_episode_matcher/libraries/pgs2srt/Libraries/SubZero/post_processing.py,sha256=GKtVy_Lxv-z27mkRG8pJF2znKWXwZTot7jL6kN-zIxM,10503
20
21
  mkv_episode_matcher/libraries/pgs2srt/Libraries/SubZero/dictionaries/data.py,sha256=AlJHUYXl85J95OzGRik-AHVfzDd7Q8BJCvD4Nr8kRIk,938598
21
- mkv_episode_matcher-0.3.3.dist-info/METADATA,sha256=bAgcQzwsAaYHZ9YgKdTtSB1KtKjc0Y4Ylx_mQyll-I4,4640
22
- mkv_episode_matcher-0.3.3.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
23
- mkv_episode_matcher-0.3.3.dist-info/entry_points.txt,sha256=IglJ43SuCZq2eQ3shMFILCkmQASJHnDCI3ogohW2Hn4,64
24
- mkv_episode_matcher-0.3.3.dist-info/top_level.txt,sha256=XRLbd93HUaedeWLtkyTvQjFcE5QcBRYa3V-CfHrq-OI,20
25
- mkv_episode_matcher-0.3.3.dist-info/RECORD,,
22
+ mkv_episode_matcher-0.3.4.dist-info/METADATA,sha256=QPa0StsF0ADrzxSEswvQ4tTbBkztRjR82hNFsXnJwCc,4640
23
+ mkv_episode_matcher-0.3.4.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
24
+ mkv_episode_matcher-0.3.4.dist-info/entry_points.txt,sha256=IglJ43SuCZq2eQ3shMFILCkmQASJHnDCI3ogohW2Hn4,64
25
+ mkv_episode_matcher-0.3.4.dist-info/top_level.txt,sha256=XRLbd93HUaedeWLtkyTvQjFcE5QcBRYa3V-CfHrq-OI,20
26
+ mkv_episode_matcher-0.3.4.dist-info/RECORD,,