mkv-episode-matcher 0.9.5__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.
- mkv_episode_matcher-0.9.7/.claude/settings.local.json +20 -0
- mkv_episode_matcher-0.9.7/.coverage +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/PKG-INFO +1 -1
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher/episode_identification.py +5 -3
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher/utils.py +4 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher.egg-info/PKG-INFO +1 -1
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher.egg-info/SOURCES.txt +3 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/setup.cfg +1 -1
- mkv_episode_matcher-0.9.7/tests/test_episode_identification.py +286 -0
- mkv_episode_matcher-0.9.7/tests/test_path_spaces_quotes.py +309 -0
- mkv_episode_matcher-0.9.5/.coverage +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/.gitattributes +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/.github/funding.yml +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/.github/workflows/claude-code-review.yml +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/.github/workflows/claude.yml +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/.github/workflows/documentation.yml +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/.github/workflows/python-publish.yml +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/.github/workflows/tests.yml +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/.gitignore +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/.gitmodules +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/.python-version +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/.vscode/settings.json +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/CHANGELOG.md +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/LICENSE +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/README.md +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/docs/api/index.md +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/docs/changelog.md +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/docs/cli.md +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/docs/configuration.md +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/docs/installation.md +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/docs/quickstart.md +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/docs/tips.md +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkdocs.yml +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher/.gitattributes +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher/__init__.py +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher/__main__.py +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher/config.py +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher/episode_matcher.py +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher/subtitle_utils.py +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher/tmdb_client.py +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher.egg-info/dependency_links.txt +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher.egg-info/entry_points.txt +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher.egg-info/requires.txt +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher.egg-info/top_level.txt +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/pyproject.toml +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/setup.py +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/tests/__init__.py +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/tests/test_config_special_characters.py +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/tests/test_main.py +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/tests/test_path_handling.py +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/tests/test_trailing_slash.py +0 -0
- {mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/uv.lock +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(gh issue view:*)",
|
|
5
|
+
"Bash(git checkout:*)",
|
|
6
|
+
"Bash(python -m pytest tests/ -v)",
|
|
7
|
+
"Bash(uv run pytest:*)",
|
|
8
|
+
"Bash(gh pr view:*)",
|
|
9
|
+
"Bash(gh pr checks:*)",
|
|
10
|
+
"WebFetch(domain:github.com)",
|
|
11
|
+
"Bash(gh run view:*)",
|
|
12
|
+
"Bash(uv run:*)",
|
|
13
|
+
"Bash(git add:*)",
|
|
14
|
+
"Bash(git commit:*)",
|
|
15
|
+
"Bash(git push:*)",
|
|
16
|
+
"Bash(rm:*)"
|
|
17
|
+
],
|
|
18
|
+
"deny": []
|
|
19
|
+
}
|
|
20
|
+
}
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mkv-episode-matcher
|
|
3
|
-
Version: 0.9.
|
|
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
|
|
@@ -155,11 +155,13 @@ class EpisodeMatcher:
|
|
|
155
155
|
]
|
|
156
156
|
|
|
157
157
|
reference_files = []
|
|
158
|
-
for
|
|
158
|
+
for pattern in patterns:
|
|
159
|
+
# Use case-insensitive file extension matching by checking both .srt and .SRT
|
|
160
|
+
srt_files = list(reference_dir.glob("*.srt")) + list(reference_dir.glob("*.SRT"))
|
|
159
161
|
files = [
|
|
160
162
|
f
|
|
161
|
-
for f in
|
|
162
|
-
if
|
|
163
|
+
for f in srt_files
|
|
164
|
+
if re.search(f"{pattern}\\d+", f.name, re.IGNORECASE)
|
|
163
165
|
]
|
|
164
166
|
reference_files.extend(files)
|
|
165
167
|
|
|
@@ -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
|
|
{mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mkv-episode-matcher
|
|
3
|
-
Version: 0.9.
|
|
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
|
{mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher.egg-info/SOURCES.txt
RENAMED
|
@@ -11,6 +11,7 @@ pyproject.toml
|
|
|
11
11
|
setup.cfg
|
|
12
12
|
setup.py
|
|
13
13
|
uv.lock
|
|
14
|
+
.claude/settings.local.json
|
|
14
15
|
.github/funding.yml
|
|
15
16
|
.github/workflows/claude-code-review.yml
|
|
16
17
|
.github/workflows/claude.yml
|
|
@@ -42,6 +43,8 @@ mkv_episode_matcher.egg-info/requires.txt
|
|
|
42
43
|
mkv_episode_matcher.egg-info/top_level.txt
|
|
43
44
|
tests/__init__.py
|
|
44
45
|
tests/test_config_special_characters.py
|
|
46
|
+
tests/test_episode_identification.py
|
|
45
47
|
tests/test_main.py
|
|
46
48
|
tests/test_path_handling.py
|
|
49
|
+
tests/test_path_spaces_quotes.py
|
|
47
50
|
tests/test_trailing_slash.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[metadata]
|
|
2
2
|
name = mkv_episode_matcher
|
|
3
|
-
version = 0.9.
|
|
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,286 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import tempfile
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from unittest.mock import Mock, patch
|
|
6
|
+
|
|
7
|
+
from mkv_episode_matcher.episode_identification import EpisodeMatcher
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestEpisodeIdentificationPatternMatching(unittest.TestCase):
|
|
11
|
+
"""Test the pattern matching functionality in episode identification."""
|
|
12
|
+
|
|
13
|
+
def setUp(self):
|
|
14
|
+
"""Set up test fixtures."""
|
|
15
|
+
self.temp_dir = tempfile.TemporaryDirectory()
|
|
16
|
+
self.cache_dir = Path(self.temp_dir.name)
|
|
17
|
+
|
|
18
|
+
# Mock subtitle cache
|
|
19
|
+
self.mock_subtitle_cache = Mock()
|
|
20
|
+
|
|
21
|
+
# Create identifier instance
|
|
22
|
+
self.identifier = EpisodeMatcher(
|
|
23
|
+
cache_dir=self.cache_dir,
|
|
24
|
+
show_name="Test Show (2023)"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def tearDown(self):
|
|
28
|
+
"""Clean up test fixtures."""
|
|
29
|
+
self.temp_dir.cleanup()
|
|
30
|
+
|
|
31
|
+
def _create_test_files(self, filenames, show_name=None):
|
|
32
|
+
"""Create test subtitle files in the cache directory."""
|
|
33
|
+
if show_name is None:
|
|
34
|
+
show_name = self.identifier.show_name
|
|
35
|
+
|
|
36
|
+
show_dir = self.cache_dir / "data" / show_name
|
|
37
|
+
show_dir.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
|
|
39
|
+
created_files = []
|
|
40
|
+
for filename in filenames:
|
|
41
|
+
file_path = show_dir / filename
|
|
42
|
+
file_path.write_text("dummy subtitle content")
|
|
43
|
+
created_files.append(file_path)
|
|
44
|
+
|
|
45
|
+
return created_files
|
|
46
|
+
|
|
47
|
+
def test_pattern_matching_s01e_format(self):
|
|
48
|
+
"""Test pattern matching for S01E format files."""
|
|
49
|
+
# Create test files with S01E format
|
|
50
|
+
test_files = [
|
|
51
|
+
"Test Show (2023) - S01E01.srt",
|
|
52
|
+
"Test Show (2023) - S01E02.srt",
|
|
53
|
+
"Test Show (2023) - S01E10.srt",
|
|
54
|
+
"Test Show (2023) - S02E01.srt", # Should not match season 1
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
self._create_test_files(test_files)
|
|
58
|
+
|
|
59
|
+
# Test season 1 matching
|
|
60
|
+
reference_files = self.identifier.get_reference_files(1)
|
|
61
|
+
|
|
62
|
+
# Should find 3 files for season 1
|
|
63
|
+
self.assertEqual(len(reference_files), 3)
|
|
64
|
+
|
|
65
|
+
# Verify correct files are matched
|
|
66
|
+
matched_names = [f.name for f in reference_files]
|
|
67
|
+
self.assertIn("Test Show (2023) - S01E01.srt", matched_names)
|
|
68
|
+
self.assertIn("Test Show (2023) - S01E02.srt", matched_names)
|
|
69
|
+
self.assertIn("Test Show (2023) - S01E10.srt", matched_names)
|
|
70
|
+
self.assertNotIn("Test Show (2023) - S02E01.srt", matched_names)
|
|
71
|
+
|
|
72
|
+
def test_pattern_matching_s1e_format(self):
|
|
73
|
+
"""Test pattern matching for S1E format files."""
|
|
74
|
+
test_files = [
|
|
75
|
+
"Show - S1E01.srt",
|
|
76
|
+
"Show - S1E05.srt",
|
|
77
|
+
"Show - S2E01.srt", # Should not match season 1
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
self._create_test_files(test_files)
|
|
81
|
+
|
|
82
|
+
reference_files = self.identifier.get_reference_files(1)
|
|
83
|
+
|
|
84
|
+
# Should find 2 files for season 1
|
|
85
|
+
self.assertEqual(len(reference_files), 2)
|
|
86
|
+
|
|
87
|
+
matched_names = [f.name for f in reference_files]
|
|
88
|
+
self.assertIn("Show - S1E01.srt", matched_names)
|
|
89
|
+
self.assertIn("Show - S1E05.srt", matched_names)
|
|
90
|
+
self.assertNotIn("Show - S2E01.srt", matched_names)
|
|
91
|
+
|
|
92
|
+
def test_pattern_matching_01x_format(self):
|
|
93
|
+
"""Test pattern matching for 01x format files."""
|
|
94
|
+
test_files = [
|
|
95
|
+
"Show - 01x01.srt",
|
|
96
|
+
"Show - 01x15.srt",
|
|
97
|
+
"Show - 02x01.srt", # Should not match season 1
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
self._create_test_files(test_files)
|
|
101
|
+
|
|
102
|
+
reference_files = self.identifier.get_reference_files(1)
|
|
103
|
+
|
|
104
|
+
# Should find 2 files for season 1
|
|
105
|
+
self.assertEqual(len(reference_files), 2)
|
|
106
|
+
|
|
107
|
+
matched_names = [f.name for f in reference_files]
|
|
108
|
+
self.assertIn("Show - 01x01.srt", matched_names)
|
|
109
|
+
self.assertIn("Show - 01x15.srt", matched_names)
|
|
110
|
+
self.assertNotIn("Show - 02x01.srt", matched_names)
|
|
111
|
+
|
|
112
|
+
def test_pattern_matching_1x_format(self):
|
|
113
|
+
"""Test pattern matching for 1x format files."""
|
|
114
|
+
test_files = [
|
|
115
|
+
"Show - 1x01.srt",
|
|
116
|
+
"Show - 1x08.srt",
|
|
117
|
+
"Show - 2x01.srt", # Should not match season 1
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
self._create_test_files(test_files)
|
|
121
|
+
|
|
122
|
+
reference_files = self.identifier.get_reference_files(1)
|
|
123
|
+
|
|
124
|
+
# Should find 2 files for season 1
|
|
125
|
+
self.assertEqual(len(reference_files), 2)
|
|
126
|
+
|
|
127
|
+
matched_names = [f.name for f in reference_files]
|
|
128
|
+
self.assertIn("Show - 1x01.srt", matched_names)
|
|
129
|
+
self.assertIn("Show - 1x08.srt", matched_names)
|
|
130
|
+
self.assertNotIn("Show - 2x01.srt", matched_names)
|
|
131
|
+
|
|
132
|
+
def test_pattern_matching_mixed_formats(self):
|
|
133
|
+
"""Test pattern matching with mixed file formats."""
|
|
134
|
+
test_files = [
|
|
135
|
+
"Show - S01E01.srt",
|
|
136
|
+
"Show - S1E02.srt",
|
|
137
|
+
"Show - 01x03.srt",
|
|
138
|
+
"Show - 1x04.srt",
|
|
139
|
+
"Show - S02E01.srt", # Different season
|
|
140
|
+
"Show - episode1.srt", # No matching pattern
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
self._create_test_files(test_files)
|
|
144
|
+
|
|
145
|
+
reference_files = self.identifier.get_reference_files(1)
|
|
146
|
+
|
|
147
|
+
# Should find 4 files for season 1 (all matching patterns)
|
|
148
|
+
self.assertEqual(len(reference_files), 4)
|
|
149
|
+
|
|
150
|
+
matched_names = [f.name for f in reference_files]
|
|
151
|
+
self.assertIn("Show - S01E01.srt", matched_names)
|
|
152
|
+
self.assertIn("Show - S1E02.srt", matched_names)
|
|
153
|
+
self.assertIn("Show - 01x03.srt", matched_names)
|
|
154
|
+
self.assertIn("Show - 1x04.srt", matched_names)
|
|
155
|
+
self.assertNotIn("Show - S02E01.srt", matched_names)
|
|
156
|
+
self.assertNotIn("Show - episode1.srt", matched_names)
|
|
157
|
+
|
|
158
|
+
def test_case_insensitive_matching(self):
|
|
159
|
+
"""Test that pattern matching is case insensitive."""
|
|
160
|
+
test_files = [
|
|
161
|
+
"show - s01e01.srt", # lowercase s and e
|
|
162
|
+
"SHOW - S01E02.SRT", # uppercase everything
|
|
163
|
+
"Show - S01e03.srt", # mixed case
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
self._create_test_files(test_files)
|
|
167
|
+
|
|
168
|
+
reference_files = self.identifier.get_reference_files(1)
|
|
169
|
+
|
|
170
|
+
# Should find all 3 files regardless of case
|
|
171
|
+
self.assertEqual(len(reference_files), 3)
|
|
172
|
+
|
|
173
|
+
def test_dexter_new_blood_issue_reproduction(self):
|
|
174
|
+
"""Test the specific issue from bug report with Dexter New Blood files."""
|
|
175
|
+
# Create files matching the exact pattern from the bug report
|
|
176
|
+
test_files = [
|
|
177
|
+
"Dexter - New Blood (2021) - S01E01.srt",
|
|
178
|
+
"Dexter - New Blood (2021) - S01E02.srt",
|
|
179
|
+
"Dexter - New Blood (2021) - S01E03.srt",
|
|
180
|
+
"Dexter - New Blood (2021) - S01E04.srt",
|
|
181
|
+
"Dexter - New Blood (2021) - S01E05.srt",
|
|
182
|
+
"Dexter - New Blood (2021) - S01E06.srt",
|
|
183
|
+
"Dexter - New Blood (2021) - S01E07.srt",
|
|
184
|
+
"Dexter - New Blood (2021) - S01E08.srt",
|
|
185
|
+
"Dexter - New Blood (2021) - S01E09.srt",
|
|
186
|
+
"Dexter - New Blood (2021) - S01E10.srt",
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
# Update the identifier to use the exact show name from the bug report
|
|
190
|
+
show_name = "Dexter - New Blood (2021)"
|
|
191
|
+
self.identifier.show_name = show_name
|
|
192
|
+
|
|
193
|
+
self._create_test_files(test_files, show_name)
|
|
194
|
+
|
|
195
|
+
reference_files = self.identifier.get_reference_files(1)
|
|
196
|
+
|
|
197
|
+
# Should find all 10 files - this was the bug that was failing
|
|
198
|
+
self.assertEqual(len(reference_files), 10,
|
|
199
|
+
f"Expected 10 files but found {len(reference_files)}. "
|
|
200
|
+
f"Files found: {[f.name for f in reference_files]}")
|
|
201
|
+
|
|
202
|
+
def test_no_duplicate_files(self):
|
|
203
|
+
"""Test that duplicate matches are removed properly."""
|
|
204
|
+
# Create a file that could match multiple patterns
|
|
205
|
+
test_files = [
|
|
206
|
+
"Show - S01E01.srt", # Could match both S01E and 01x patterns if logic is wrong
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
self._create_test_files(test_files)
|
|
210
|
+
|
|
211
|
+
reference_files = self.identifier.get_reference_files(1)
|
|
212
|
+
|
|
213
|
+
# Should find only 1 unique file, not duplicates
|
|
214
|
+
self.assertEqual(len(reference_files), 1)
|
|
215
|
+
|
|
216
|
+
# Verify it's the correct file
|
|
217
|
+
self.assertEqual(reference_files[0].name, "Show - S01E01.srt")
|
|
218
|
+
|
|
219
|
+
def test_empty_directory(self):
|
|
220
|
+
"""Test behavior when no matching files exist."""
|
|
221
|
+
# Create the directory but no files
|
|
222
|
+
show_dir = self.cache_dir / "data" / "Test Show (2023)"
|
|
223
|
+
show_dir.mkdir(parents=True, exist_ok=True)
|
|
224
|
+
|
|
225
|
+
reference_files = self.identifier.get_reference_files(1)
|
|
226
|
+
|
|
227
|
+
# Should find no files
|
|
228
|
+
self.assertEqual(len(reference_files), 0)
|
|
229
|
+
|
|
230
|
+
def test_directory_does_not_exist(self):
|
|
231
|
+
"""Test behavior when the show directory doesn't exist."""
|
|
232
|
+
# Don't create the directory
|
|
233
|
+
reference_files = self.identifier.get_reference_files(1)
|
|
234
|
+
|
|
235
|
+
# Should find no files (glob returns empty on non-existent directory)
|
|
236
|
+
self.assertEqual(len(reference_files), 0)
|
|
237
|
+
|
|
238
|
+
def test_caching_functionality(self):
|
|
239
|
+
"""Test that results are properly cached."""
|
|
240
|
+
test_files = ["Show - S01E01.srt"]
|
|
241
|
+
self._create_test_files(test_files)
|
|
242
|
+
|
|
243
|
+
# First call
|
|
244
|
+
reference_files_1 = self.identifier.get_reference_files(1)
|
|
245
|
+
|
|
246
|
+
# Second call should return cached result
|
|
247
|
+
reference_files_2 = self.identifier.get_reference_files(1)
|
|
248
|
+
|
|
249
|
+
# Should be the same result
|
|
250
|
+
self.assertEqual(reference_files_1, reference_files_2)
|
|
251
|
+
|
|
252
|
+
# Verify it's actually using the cache by checking the cache directly
|
|
253
|
+
cache_key = ("Test Show (2023)", 1)
|
|
254
|
+
self.assertIn(cache_key, self.identifier.reference_files_cache)
|
|
255
|
+
|
|
256
|
+
def test_different_seasons(self):
|
|
257
|
+
"""Test that different seasons are handled correctly."""
|
|
258
|
+
test_files = [
|
|
259
|
+
"Show - S01E01.srt",
|
|
260
|
+
"Show - S01E02.srt",
|
|
261
|
+
"Show - S02E01.srt",
|
|
262
|
+
"Show - S02E02.srt",
|
|
263
|
+
"Show - S03E01.srt",
|
|
264
|
+
]
|
|
265
|
+
|
|
266
|
+
self._create_test_files(test_files)
|
|
267
|
+
|
|
268
|
+
# Test season 1
|
|
269
|
+
season1_files = self.identifier.get_reference_files(1)
|
|
270
|
+
self.assertEqual(len(season1_files), 2)
|
|
271
|
+
|
|
272
|
+
# Test season 2
|
|
273
|
+
season2_files = self.identifier.get_reference_files(2)
|
|
274
|
+
self.assertEqual(len(season2_files), 2)
|
|
275
|
+
|
|
276
|
+
# Test season 3
|
|
277
|
+
season3_files = self.identifier.get_reference_files(3)
|
|
278
|
+
self.assertEqual(len(season3_files), 1)
|
|
279
|
+
|
|
280
|
+
# Test non-existent season
|
|
281
|
+
season4_files = self.identifier.get_reference_files(4)
|
|
282
|
+
self.assertEqual(len(season4_files), 0)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
if __name__ == '__main__':
|
|
286
|
+
unittest.main()
|
|
@@ -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()
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
{mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/.github/workflows/claude-code-review.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/.github/workflows/python-publish.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher/episode_matcher.py
RENAMED
|
File without changes
|
{mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher/subtitle_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher.egg-info/requires.txt
RENAMED
|
File without changes
|
{mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/mkv_episode_matcher.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mkv_episode_matcher-0.9.5 → mkv_episode_matcher-0.9.7}/tests/test_config_special_characters.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|