mkv-episode-matcher 0.9.6__tar.gz → 0.9.7__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.

Potentially problematic release.


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

Files changed (51) hide show
  1. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/.coverage +0 -0
  2. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/PKG-INFO +1 -1
  3. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher/utils.py +4 -0
  4. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher.egg-info/PKG-INFO +1 -1
  5. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher.egg-info/SOURCES.txt +1 -0
  6. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/setup.cfg +1 -1
  7. mkv_episode_matcher-0.9.7/tests/test_path_spaces_quotes.py +309 -0
  8. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/.claude/settings.local.json +0 -0
  9. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/.gitattributes +0 -0
  10. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/.github/funding.yml +0 -0
  11. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/.github/workflows/claude-code-review.yml +0 -0
  12. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/.github/workflows/claude.yml +0 -0
  13. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/.github/workflows/documentation.yml +0 -0
  14. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/.github/workflows/python-publish.yml +0 -0
  15. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/.github/workflows/tests.yml +0 -0
  16. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/.gitignore +0 -0
  17. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/.gitmodules +0 -0
  18. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/.python-version +0 -0
  19. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/.vscode/settings.json +0 -0
  20. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/CHANGELOG.md +0 -0
  21. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/LICENSE +0 -0
  22. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/README.md +0 -0
  23. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/docs/api/index.md +0 -0
  24. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/docs/changelog.md +0 -0
  25. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/docs/cli.md +0 -0
  26. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/docs/configuration.md +0 -0
  27. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/docs/installation.md +0 -0
  28. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/docs/quickstart.md +0 -0
  29. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/docs/tips.md +0 -0
  30. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/mkdocs.yml +0 -0
  31. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher/.gitattributes +0 -0
  32. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher/__init__.py +0 -0
  33. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher/__main__.py +0 -0
  34. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher/config.py +0 -0
  35. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher/episode_identification.py +0 -0
  36. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher/episode_matcher.py +0 -0
  37. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher/subtitle_utils.py +0 -0
  38. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher/tmdb_client.py +0 -0
  39. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher.egg-info/dependency_links.txt +0 -0
  40. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher.egg-info/entry_points.txt +0 -0
  41. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher.egg-info/requires.txt +0 -0
  42. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher.egg-info/top_level.txt +0 -0
  43. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/pyproject.toml +0 -0
  44. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/setup.py +0 -0
  45. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/tests/__init__.py +0 -0
  46. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/tests/test_config_special_characters.py +0 -0
  47. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/tests/test_episode_identification.py +0 -0
  48. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/tests/test_main.py +0 -0
  49. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/tests/test_path_handling.py +0 -0
  50. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/tests/test_trailing_slash.py +0 -0
  51. {mkv_episode_matcher-0.9.6 → mkv_episode_matcher-0.9.7}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkv-episode-matcher
3
- Version: 0.9.6
3
+ Version: 0.9.7
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
@@ -24,6 +24,7 @@ def normalize_path(path_str):
24
24
  """
25
25
  Normalize a path string to handle cross-platform path issues.
26
26
  Properly handles trailing slashes and backslashes in both Windows and Unix paths.
27
+ Also strips surrounding quotes that might be present in command line arguments.
27
28
 
28
29
  Args:
29
30
  path_str (str): The path string to normalize
@@ -35,6 +36,9 @@ def normalize_path(path_str):
35
36
  if isinstance(path_str, Path):
36
37
  path_str = str(path_str)
37
38
 
39
+ # Strip surrounding quotes (both single and double)
40
+ path_str = path_str.strip().strip('"').strip("'")
41
+
38
42
  # Remove trailing slashes or backslashes
39
43
  path_str = path_str.rstrip("/").rstrip("\\")
40
44
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkv-episode-matcher
3
- Version: 0.9.6
3
+ Version: 0.9.7
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
@@ -46,4 +46,5 @@ tests/test_config_special_characters.py
46
46
  tests/test_episode_identification.py
47
47
  tests/test_main.py
48
48
  tests/test_path_handling.py
49
+ tests/test_path_spaces_quotes.py
49
50
  tests/test_trailing_slash.py
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = mkv_episode_matcher
3
- version = 0.9.6
3
+ version = 0.9.7
4
4
  author = Jonathan Sakkos
5
5
  author_email = jonathansakkos@gmail.com
6
6
  description = The MKV Episode Matcher is a tool for identifying TV series episodes from MKV files and renaming the files accordingly.
@@ -0,0 +1,309 @@
1
+ import unittest
2
+ import tempfile
3
+ import argparse
4
+ from pathlib import Path
5
+ from unittest.mock import Mock, patch, MagicMock
6
+
7
+ from mkv_episode_matcher.utils import normalize_path, get_subtitles, get_valid_seasons
8
+ from mkv_episode_matcher.config import get_config, set_config
9
+
10
+
11
+ class TestPathHandlingWithSpacesAndQuotes(unittest.TestCase):
12
+ """Test path handling for issues 61 and 62 - paths with spaces and quotes."""
13
+
14
+ def setUp(self):
15
+ """Set up test fixtures."""
16
+ self.temp_dir = tempfile.TemporaryDirectory()
17
+ self.temp_path = Path(self.temp_dir.name)
18
+
19
+ def tearDown(self):
20
+ """Clean up test fixtures."""
21
+ self.temp_dir.cleanup()
22
+
23
+ def test_normalize_path_with_spaces(self):
24
+ """Test that normalize_path handles paths with spaces correctly."""
25
+ paths_with_spaces = [
26
+ "C:\\my rips\\show name",
27
+ "/home/user/TV Shows/My Show",
28
+ "C:/Program Files/My TV Shows/Show With Spaces",
29
+ "/mnt/c/Users/John Doe/Shows/Breaking Bad",
30
+ "D:\\Users\\Jane Smith\\My Videos\\Game of Thrones\\",
31
+ ]
32
+
33
+ for path_str in paths_with_spaces:
34
+ with self.subTest(path=path_str):
35
+ normalized = normalize_path(path_str)
36
+ # Should return a valid Path object
37
+ self.assertIsInstance(normalized, Path)
38
+ # Should preserve the directory name with spaces
39
+ self.assertIn(" ", normalized.name,
40
+ f"Path with spaces should preserve spaces: {path_str}")
41
+
42
+ def test_normalize_path_with_quotes(self):
43
+ """Test that normalize_path handles paths with quotes correctly."""
44
+ paths_with_quotes = [
45
+ '"C:\\my rips\\show name"',
46
+ '"/home/user/TV Shows/My Show"',
47
+ "'C:/Program Files/My TV Shows/Show With Spaces'",
48
+ '"D:\\Users\\Jane Smith\\My Videos\\Game of Thrones\\"',
49
+ ]
50
+
51
+ for path_str in paths_with_quotes:
52
+ with self.subTest(path=path_str):
53
+ normalized = normalize_path(path_str)
54
+ # Should return a valid Path object
55
+ self.assertIsInstance(normalized, Path)
56
+ # Should not contain quotes in the final path
57
+ self.assertNotIn('"', str(normalized))
58
+ self.assertNotIn("'", str(normalized))
59
+
60
+ def test_get_valid_seasons_with_spaces(self):
61
+ """Test that get_valid_seasons works with directories containing spaces."""
62
+ # Create test directory structure with spaces
63
+ show_dir = self.temp_path / "My Test Show"
64
+ show_dir.mkdir()
65
+
66
+ season1_dir = show_dir / "Season 1"
67
+ season2_dir = show_dir / "Season 2 - Special Edition"
68
+ season1_dir.mkdir()
69
+ season2_dir.mkdir()
70
+
71
+ # Create some .mkv files
72
+ (season1_dir / "Episode 1.mkv").touch()
73
+ (season1_dir / "Episode 2.mkv").touch()
74
+ (season2_dir / "Special Episode.mkv").touch()
75
+
76
+ # Test with path containing spaces
77
+ seasons = get_valid_seasons(str(show_dir))
78
+
79
+ # Should find both seasons
80
+ self.assertEqual(len(seasons), 2)
81
+ self.assertTrue(any("Season 1" in str(season) for season in seasons))
82
+ self.assertTrue(any("Season 2 - Special Edition" in str(season) for season in seasons))
83
+
84
+ def test_command_line_parsing_with_spaces(self):
85
+ """Test that command line argument parsing handles paths with spaces."""
86
+ from mkv_episode_matcher.__main__ import main
87
+
88
+ test_cases = [
89
+ ['--show-dir', 'C:\\my rips\\show name'],
90
+ ['--show-dir', '/home/user/TV Shows/My Show'],
91
+ ['--show-dir', 'D:\\Users\\Jane Smith\\My Videos'],
92
+ ]
93
+
94
+ for args in test_cases:
95
+ with self.subTest(args=args):
96
+ parser = argparse.ArgumentParser()
97
+ parser.add_argument('--show-dir', help='Main directory of the show')
98
+
99
+ # This should not raise an exception
100
+ parsed_args = parser.parse_args(args)
101
+ self.assertIsNotNone(parsed_args.show_dir)
102
+ # Spaces should be preserved
103
+ self.assertIn(" ", parsed_args.show_dir)
104
+
105
+ def test_command_line_parsing_with_quotes(self):
106
+ """Test that command line argument parsing handles quoted paths."""
107
+ test_cases = [
108
+ ['--show-dir', '"C:\\my rips\\show name"'],
109
+ ['--show-dir', '"/home/user/TV Shows/My Show"'],
110
+ ['--show-dir', "'D:\\Users\\Jane Smith\\My Videos'"],
111
+ ]
112
+
113
+ for args in test_cases:
114
+ with self.subTest(args=args):
115
+ parser = argparse.ArgumentParser()
116
+ parser.add_argument('--show-dir', help='Main directory of the show')
117
+
118
+ # This should not raise an exception
119
+ parsed_args = parser.parse_args(args)
120
+ self.assertIsNotNone(parsed_args.show_dir)
121
+ # Should handle quotes appropriately
122
+ # Note: argparse typically strips outer quotes automatically
123
+
124
+ @patch('mkv_episode_matcher.utils.get_config')
125
+ def test_config_storage_with_spaces(self, mock_get_config):
126
+ """Test that configuration correctly stores and retrieves paths with spaces."""
127
+ # Test path with spaces
128
+ test_path = "C:\\Users\\John Doe\\My TV Shows\\Breaking Bad"
129
+
130
+ # Create a temporary config file
131
+ config_file = self.temp_path / "test_config.ini"
132
+
133
+ # Set config with path containing spaces
134
+ set_config(
135
+ tmdb_api_key="test_key",
136
+ open_subtitles_api_key="test_key",
137
+ open_subtitles_user_agent="test_agent",
138
+ open_subtitles_username="test_user",
139
+ open_subtitles_password="test_pass",
140
+ show_dir=test_path,
141
+ file=config_file
142
+ )
143
+
144
+ # Read back the config
145
+ config = get_config(config_file)
146
+
147
+ # Should preserve spaces in the path
148
+ self.assertEqual(config.get("show_dir"), test_path)
149
+ self.assertIn(" ", config.get("show_dir"))
150
+
151
+ @patch('mkv_episode_matcher.utils.get_config')
152
+ @patch('mkv_episode_matcher.utils.OpenSubtitles')
153
+ @patch('mkv_episode_matcher.tmdb_client.fetch_season_details')
154
+ def test_get_subtitles_with_spaces_in_path(self, mock_fetch_season, mock_opensubtitles, mock_get_config):
155
+ """Test that get_subtitles function works with show directories containing spaces."""
156
+ # Mock configuration with path containing spaces
157
+ mock_config = {
158
+ "show_dir": "C:\\Users\\John Doe\\My TV Shows\\Breaking Bad",
159
+ "tmdb_api_key": "test_tmdb_key",
160
+ "open_subtitles_api_key": "test_os_key",
161
+ "open_subtitles_user_agent": "test_agent",
162
+ "open_subtitles_username": "test_user",
163
+ "open_subtitles_password": "test_pass"
164
+ }
165
+ mock_get_config.return_value = mock_config
166
+
167
+ # Mock OpenSubtitles client
168
+ mock_client = Mock()
169
+ mock_opensubtitles.return_value = mock_client
170
+
171
+ # Mock season details
172
+ mock_fetch_season.return_value = {
173
+ 'season_number': 1,
174
+ 'episodes': [
175
+ {'episode_number': 1, 'name': 'Pilot'},
176
+ {'episode_number': 2, 'name': 'Cat\'s in the Bag...'}
177
+ ]
178
+ }
179
+
180
+ # This should not raise an exception with spaces in the path
181
+ try:
182
+ get_subtitles(show_id=1396, seasons={1}, config=mock_config)
183
+ except Exception as e:
184
+ # If there's an exception, it shouldn't be related to path handling
185
+ # OpenSubtitles exceptions are expected in a test environment
186
+ self.assertNotIn("path", str(e).lower())
187
+ self.assertNotIn("space", str(e).lower())
188
+
189
+ def test_path_edge_cases(self):
190
+ """Test edge cases for path handling."""
191
+ edge_cases = [
192
+ # Multiple spaces
193
+ "C:\\My TV Shows\\Show With Multiple Spaces",
194
+ # Leading/trailing spaces
195
+ " C:\\My TV Shows\\Show With Spaces ",
196
+ # Mixed quotes and spaces
197
+ '"C:\\My TV Shows"\\Show Name',
198
+ # Unicode characters with spaces
199
+ "C:\\My TV Shows\\Café Français",
200
+ # Very long path with spaces
201
+ "C:\\A Very Long Path Name With Many Spaces\\And Another Very Long Directory Name\\Show Name",
202
+ ]
203
+
204
+ for path_str in edge_cases:
205
+ with self.subTest(path=path_str):
206
+ # normalize_path should handle these without raising exceptions
207
+ try:
208
+ result = normalize_path(path_str)
209
+ self.assertIsInstance(result, Path)
210
+ except Exception as e:
211
+ self.fail(f"normalize_path failed on edge case '{path_str}': {e}")
212
+
213
+ def test_issue_61_reproduction(self):
214
+ """Reproduce issue 61: Show-dir path cannot contain spaces."""
215
+ # This test reproduces the exact issue described in #61
216
+ problem_path = "C:\\my rips\\show_name"
217
+
218
+ # The issue is that --get-subs fails when the path has spaces
219
+ # Let's test the path normalization that's used in get_subtitles
220
+ normalized = normalize_path(problem_path)
221
+
222
+ # Should successfully normalize without losing information
223
+ self.assertIsInstance(normalized, Path)
224
+ self.assertEqual(normalized.name, "show_name")
225
+
226
+ # The normalized path should be usable for directory operations
227
+ self.assertTrue(str(normalized)) # Should not be empty
228
+
229
+ def test_issue_62_reproduction(self):
230
+ """Reproduce issue 62: show-dir cannot have quotes."""
231
+ # This test reproduces the exact issue described in #62
232
+ quoted_paths = [
233
+ '"C:\\my rips\\show name"',
234
+ "'C:\\my rips\\show name'",
235
+ '"C:/my rips/show name"',
236
+ ]
237
+
238
+ for quoted_path in quoted_paths:
239
+ with self.subTest(path=quoted_path):
240
+ # The issue is that quoted paths aren't handled properly
241
+ normalized = normalize_path(quoted_path)
242
+
243
+ # Should successfully normalize and remove quotes
244
+ self.assertIsInstance(normalized, Path)
245
+ self.assertNotIn('"', str(normalized))
246
+ self.assertNotIn("'", str(normalized))
247
+
248
+
249
+ class TestPathIntegration(unittest.TestCase):
250
+ """Integration tests for path handling across the application."""
251
+
252
+ def setUp(self):
253
+ """Set up test fixtures."""
254
+ self.temp_dir = tempfile.TemporaryDirectory()
255
+ self.temp_path = Path(self.temp_dir.name)
256
+
257
+ def tearDown(self):
258
+ """Clean up test fixtures."""
259
+ self.temp_dir.cleanup()
260
+
261
+ @patch('mkv_episode_matcher.episode_matcher.get_config')
262
+ def test_episode_matcher_with_spaces(self, mock_get_config):
263
+ """Test that EpisodeMatcher handles show names with spaces correctly."""
264
+ from mkv_episode_matcher.episode_identification import EpisodeMatcher
265
+
266
+ # Mock config with path containing spaces
267
+ mock_config = {
268
+ "show_dir": "C:\\Users\\John Doe\\My TV Shows\\Breaking Bad"
269
+ }
270
+ mock_get_config.return_value = mock_config
271
+
272
+ # This should not raise an exception
273
+ try:
274
+ matcher = EpisodeMatcher(
275
+ cache_dir=self.temp_path,
276
+ show_name="Breaking Bad"
277
+ )
278
+ self.assertIsNotNone(matcher)
279
+ self.assertEqual(matcher.show_name, "Breaking Bad")
280
+ except Exception as e:
281
+ self.fail(f"EpisodeMatcher failed with spaces in show name: {e}")
282
+
283
+ def test_end_to_end_path_handling(self):
284
+ """Test end-to-end path handling from command line to processing."""
285
+ # Create a test directory structure with spaces
286
+ show_dir = self.temp_path / "My Test Show With Spaces"
287
+ show_dir.mkdir()
288
+
289
+ season_dir = show_dir / "Season 1"
290
+ season_dir.mkdir()
291
+
292
+ # Create a test .mkv file
293
+ test_file = season_dir / "Test Episode.mkv"
294
+ test_file.touch()
295
+
296
+ # Test that the path can be processed through the whole pipeline
297
+ normalized = normalize_path(str(show_dir))
298
+ self.assertIsInstance(normalized, Path)
299
+
300
+ # Should be able to find seasons
301
+ seasons = get_valid_seasons(str(show_dir))
302
+ self.assertEqual(len(seasons), 1)
303
+
304
+ # Season path should also contain spaces
305
+ self.assertIn("Season 1", str(seasons[0]))
306
+
307
+
308
+ if __name__ == '__main__':
309
+ unittest.main()