mkv-episode-matcher 0.3.3__py3-none-any.whl → 1.0.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.
- mkv_episode_matcher/__init__.py +8 -0
- mkv_episode_matcher/__main__.py +2 -177
- mkv_episode_matcher/asr_models.py +506 -0
- mkv_episode_matcher/cli.py +558 -0
- mkv_episode_matcher/core/config_manager.py +100 -0
- mkv_episode_matcher/core/engine.py +577 -0
- mkv_episode_matcher/core/matcher.py +214 -0
- mkv_episode_matcher/core/models.py +91 -0
- mkv_episode_matcher/core/providers/asr.py +85 -0
- mkv_episode_matcher/core/providers/subtitles.py +341 -0
- mkv_episode_matcher/core/utils.py +148 -0
- mkv_episode_matcher/episode_identification.py +550 -118
- mkv_episode_matcher/subtitle_utils.py +82 -0
- mkv_episode_matcher/tmdb_client.py +56 -14
- mkv_episode_matcher/ui/flet_app.py +708 -0
- mkv_episode_matcher/utils.py +262 -139
- mkv_episode_matcher-1.0.0.dist-info/METADATA +242 -0
- mkv_episode_matcher-1.0.0.dist-info/RECORD +23 -0
- {mkv_episode_matcher-0.3.3.dist-info → mkv_episode_matcher-1.0.0.dist-info}/WHEEL +1 -1
- mkv_episode_matcher-1.0.0.dist-info/licenses/LICENSE +21 -0
- mkv_episode_matcher/config.py +0 -82
- mkv_episode_matcher/episode_matcher.py +0 -100
- mkv_episode_matcher/libraries/pgs2srt/.gitignore +0 -2
- mkv_episode_matcher/libraries/pgs2srt/Libraries/SubZero/SubZero.py +0 -321
- mkv_episode_matcher/libraries/pgs2srt/Libraries/SubZero/dictionaries/data.py +0 -16700
- mkv_episode_matcher/libraries/pgs2srt/Libraries/SubZero/post_processing.py +0 -260
- mkv_episode_matcher/libraries/pgs2srt/README.md +0 -26
- mkv_episode_matcher/libraries/pgs2srt/__init__.py +0 -0
- mkv_episode_matcher/libraries/pgs2srt/imagemaker.py +0 -89
- mkv_episode_matcher/libraries/pgs2srt/pgs2srt.py +0 -150
- mkv_episode_matcher/libraries/pgs2srt/pgsreader.py +0 -225
- mkv_episode_matcher/libraries/pgs2srt/requirements.txt +0 -4
- mkv_episode_matcher/mkv_to_srt.py +0 -302
- mkv_episode_matcher/speech_to_text.py +0 -90
- mkv_episode_matcher-0.3.3.dist-info/METADATA +0 -125
- mkv_episode_matcher-0.3.3.dist-info/RECORD +0 -25
- {mkv_episode_matcher-0.3.3.dist-info → mkv_episode_matcher-1.0.0.dist-info}/entry_points.txt +0 -0
- {mkv_episode_matcher-0.3.3.dist-info → mkv_episode_matcher-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mkv-episode-matcher
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: The MKV Episode Matcher is a tool for identifying TV series episodes from MKV files and renaming the files accordingly.
|
|
5
|
+
Home-page: https://github.com/Jsakkos/mkv-episode-matcher
|
|
6
|
+
Author: Jonathan Sakkos
|
|
7
|
+
Author-email: Jsakkos <jonathansakkos@gmail.com>
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2025 Jonathan Sakkos
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
Project-URL: Documentation, https://github.com/Jsakkos/mkv-episode-matcher#readme
|
|
30
|
+
Project-URL: Issues, https://github.com/Jsakkos/mkv-episode-matcher/issues
|
|
31
|
+
Project-URL: Source, https://github.com/Jsakkos/mkv-episode-matcher
|
|
32
|
+
Classifier: Development Status :: 4 - Beta
|
|
33
|
+
Classifier: Programming Language :: Python
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
35
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
36
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
37
|
+
Requires-Python: <3.13,>=3.10
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
License-File: LICENSE
|
|
40
|
+
Requires-Dist: chardet>=5.2.0
|
|
41
|
+
Requires-Dist: configparser>=7.1.0
|
|
42
|
+
Requires-Dist: ffmpeg>=1.4
|
|
43
|
+
Requires-Dist: librosa>=0.10.2
|
|
44
|
+
Requires-Dist: loguru>=0.7.2
|
|
45
|
+
Requires-Dist: nemo-toolkit[asr]>=2.0.0
|
|
46
|
+
Requires-Dist: opensubtitlescom>=0.1.5
|
|
47
|
+
Requires-Dist: rapidfuzz>=3.10.1
|
|
48
|
+
Requires-Dist: requests>=2.32.3
|
|
49
|
+
Requires-Dist: rich[jupyter]>=13.9.4
|
|
50
|
+
Requires-Dist: soundfile>=0.12.1
|
|
51
|
+
Requires-Dist: tmdb-client>=0.0.1
|
|
52
|
+
Requires-Dist: torch>=2.5.1
|
|
53
|
+
Requires-Dist: torchaudio>=2.5.1
|
|
54
|
+
Requires-Dist: torchvision>=0.20.1
|
|
55
|
+
Requires-Dist: typer>=0.9.0
|
|
56
|
+
Requires-Dist: flet[all]>=0.80.0
|
|
57
|
+
Requires-Dist: pydantic>=2.0.0
|
|
58
|
+
Requires-Dist: pydantic-settings>=2.0.0
|
|
59
|
+
Requires-Dist: ty>=0.0.7
|
|
60
|
+
Provides-Extra: cu128
|
|
61
|
+
Requires-Dist: torch>=2.9.1; sys_platform != "darwin" and extra == "cu128"
|
|
62
|
+
Requires-Dist: torchvision>=0.24.1; sys_platform != "darwin" and extra == "cu128"
|
|
63
|
+
Provides-Extra: nemo
|
|
64
|
+
Requires-Dist: nemo_toolkit[asr]>=2.0.0; extra == "nemo"
|
|
65
|
+
Dynamic: license-file
|
|
66
|
+
|
|
67
|
+
# MKV Episode Matcher
|
|
68
|
+
|
|
69
|
+
[](https://pypi.org/project/mkv-episode-matcher/)
|
|
70
|
+
[](https://pypi.org/project/mkv-episode-matcher/)
|
|
71
|
+
[](https://opensource.org/licenses/MIT)
|
|
72
|
+
[](https://jsakkos.github.io/mkv-episode-matcher/)
|
|
73
|
+
[](https://pepy.tech/project/mkv-episode-matcher)
|
|
74
|
+
[](https://github.com/Jsakkos/mkv-episode-matcher/commits/main)
|
|
75
|
+
[](https://github.com/Jsakkos/mkv-episode-matcher/issues)
|
|
76
|
+
[](https://github.com/Jsakkos/mkv-episode-matcher/actions/workflows/tests.yml)
|
|
77
|
+
[](https://codecov.io/gh/Jsakkos/mkv-episode-matcher/)
|
|
78
|
+
|
|
79
|
+
Automatically match and rename your MKV TV episodes using advanced speech recognition and subtitle matching.
|
|
80
|
+
|
|
81
|
+
## ✨ Key Features
|
|
82
|
+
|
|
83
|
+
- 🖥️ **Modern Desktop GUI**: Cross-platform Flet-based graphical interface with real-time progress tracking
|
|
84
|
+
- 🤖 **Advanced Speech Recognition**: NVIDIA Parakeet ASR for highly accurate episode identification
|
|
85
|
+
- 🎯 **Intelligent Matching**: Multi-segment analysis with confidence scoring and fallback strategies
|
|
86
|
+
- ⬇️ **Smart Subtitle Integration**: Automatic subtitle downloads from OpenSubtitles with local caching
|
|
87
|
+
- ✨ **Bulk Processing**: Handle entire libraries with automatic series/season detection
|
|
88
|
+
- 🧪 **Dry Run Mode**: Preview matches before making any changes
|
|
89
|
+
- 📊 **Rich Progress Tracking**: Real-time progress indicators and detailed match results
|
|
90
|
+
- ⚡ **Performance Optimized**: Caching, background model loading, and efficient processing
|
|
91
|
+
- 🌐 **Cross-Platform**: Available as desktop applications for Windows, macOS, and Linux
|
|
92
|
+
|
|
93
|
+
## Prerequisites
|
|
94
|
+
|
|
95
|
+
- Python 3.10-3.12
|
|
96
|
+
- [FFmpeg](https://ffmpeg.org/download.html) installed and available in system PATH
|
|
97
|
+
- TMDb API key (optional, for episode matching)
|
|
98
|
+
- OpenSubtitles.com account (required for subtitle downloads)
|
|
99
|
+
|
|
100
|
+
## 🚀 Quick Start
|
|
101
|
+
|
|
102
|
+
### 1. Install MKV Episode Matcher
|
|
103
|
+
|
|
104
|
+
**Option A: Using uv (Recommended)**
|
|
105
|
+
```bash
|
|
106
|
+
# Install with CUDA support for GPU acceleration
|
|
107
|
+
uv sync --extra cu128
|
|
108
|
+
|
|
109
|
+
# Or basic installation
|
|
110
|
+
uv sync
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Option B: Using pip**
|
|
114
|
+
```bash
|
|
115
|
+
pip install mkv-episode-matcher
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Option C: Download Standalone Desktop Apps**
|
|
119
|
+
- [Windows Executable](https://github.com/Jsakkos/mkv-episode-matcher/releases/latest/download/MKVEpisodeMatcher-windows.zip)
|
|
120
|
+
- [macOS Application](https://github.com/Jsakkos/mkv-episode-matcher/releases/latest/download/MKVEpisodeMatcher-macos.zip)
|
|
121
|
+
- [Linux AppImage](https://github.com/Jsakkos/mkv-episode-matcher/releases/latest/download/mkv-episode-matcher-linux.AppImage)
|
|
122
|
+
|
|
123
|
+
### 2. Launch the Application
|
|
124
|
+
|
|
125
|
+
**🖥️ GUI Mode (Recommended)**
|
|
126
|
+
Launches the modern desktop interface with real-time progress tracking:
|
|
127
|
+
```bash
|
|
128
|
+
uv run mkv-match gui
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**💻 CLI Mode**
|
|
132
|
+
For automation and advanced users:
|
|
133
|
+
```bash
|
|
134
|
+
uv run mkv-match match "/path/to/your/show"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**⚙️ Configuration**
|
|
138
|
+
```bash
|
|
139
|
+
uv run mkv-match config
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 3. Alternative Launch Methods
|
|
143
|
+
```bash
|
|
144
|
+
# GUI
|
|
145
|
+
python -m mkv_episode_matcher gui
|
|
146
|
+
|
|
147
|
+
# CLI
|
|
148
|
+
python -m mkv_episode_matcher match "/path/to/show"
|
|
149
|
+
```
|
|
150
|
+
## 🖥️ Desktop GUI Features
|
|
151
|
+
|
|
152
|
+
The modern Flet-based desktop interface provides:
|
|
153
|
+
|
|
154
|
+
- **🎨 Theme-Adaptive Interface**: Automatically adapts to your system's light/dark theme
|
|
155
|
+
- **📂 Folder Browser**: Easy directory selection with visual folder picker
|
|
156
|
+
- **⏱️ Real-time Progress**: Live progress bars showing "Processing file X of Y"
|
|
157
|
+
- **🔄 Background Model Loading**: Non-blocking ASR model initialization with status indicators
|
|
158
|
+
- **🧪 Dry Run Preview**: Test matches without making changes, with preview functionality
|
|
159
|
+
- **⚙️ Comprehensive Settings**: Built-in configuration dialog for all options
|
|
160
|
+
- **📊 Detailed Results**: Color-coded success/failure results with confidence scores
|
|
161
|
+
- **🚀 Performance Indicators**: Model loading status and processing statistics
|
|
162
|
+
|
|
163
|
+
## ⚙️ Configuration
|
|
164
|
+
|
|
165
|
+
### GUI Configuration
|
|
166
|
+
The desktop app includes a built-in settings dialog accessible via the gear icon. Configure all options including:
|
|
167
|
+
- Cache directory and confidence thresholds
|
|
168
|
+
- ASR and subtitle provider settings
|
|
169
|
+
- OpenSubtitles API credentials
|
|
170
|
+
- TMDb integration (optional)
|
|
171
|
+
|
|
172
|
+
### CLI Configuration
|
|
173
|
+
For command-line users:
|
|
174
|
+
```bash
|
|
175
|
+
mkv-match config # Interactive configuration
|
|
176
|
+
mkv-match --onboard # First-time setup wizard
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Required API Keys:**
|
|
180
|
+
- **OpenSubtitles API Key**: Required for subtitle downloads ([Get one here](https://www.opensubtitles.com/consumers))
|
|
181
|
+
- **TMDb API Key**: Optional, for enhanced episode metadata ([Get one here](https://www.themoviedb.org/settings/api))
|
|
182
|
+
|
|
183
|
+
**OpenSubtitles Setup:**
|
|
184
|
+
- Username and password (recommended for better rate limits)
|
|
185
|
+
- API key from the OpenSubtitles developer console
|
|
186
|
+
|
|
187
|
+
## Directory Structure
|
|
188
|
+
|
|
189
|
+
MKV Episode Matcher expects your TV shows to be organized as follows:
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
Show Name/
|
|
193
|
+
├── Season 1/
|
|
194
|
+
│ ├── episode1.mkv
|
|
195
|
+
│ ├── episode2.mkv
|
|
196
|
+
├── Season 2/
|
|
197
|
+
│ ├── episode1.mkv
|
|
198
|
+
│ └── episode2.mkv
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Reference Subtitle File Structure
|
|
202
|
+
|
|
203
|
+
Subtitle files that are not automatically downloaded using the `--get-subs` flag should be named as follows:
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
~/.mkv-episode-matcher/cache/data/Show Name/
|
|
208
|
+
├── Show Name - S01E01.srt
|
|
209
|
+
├── Show Name - S01E02.srt
|
|
210
|
+
└── ...
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
On Windows, the cache directory is located at `C:\Users\{username}\.mkv-episode-matcher\cache\data\`
|
|
214
|
+
|
|
215
|
+
Reference subtitle files should follow this naming pattern:
|
|
216
|
+
`{show_name} - S{season:02d}E{episode:02d}.srt`
|
|
217
|
+
|
|
218
|
+
## Contributing
|
|
219
|
+
|
|
220
|
+
1. Fork the repository
|
|
221
|
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
222
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
223
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
224
|
+
5. Open a Pull Request
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
Distributed under the MIT License. See `LICENSE` for more information.
|
|
229
|
+
|
|
230
|
+
## Acknowledgments
|
|
231
|
+
|
|
232
|
+
- [TMDb](https://www.themoviedb.org/) for their excellent API
|
|
233
|
+
- [OpenSubtitles](https://www.opensubtitles.com/) for subtitle integration
|
|
234
|
+
- All contributors who have helped improve this project
|
|
235
|
+
|
|
236
|
+
## Documentation
|
|
237
|
+
|
|
238
|
+
Full documentation is available at [https://jsakkos.github.io/mkv-episode-matcher/](https://jsakkos.github.io/mkv-episode-matcher/)
|
|
239
|
+
|
|
240
|
+
## Changelog
|
|
241
|
+
|
|
242
|
+
See [CHANGELOG.md](CHANGELOG.md) for a detailed list of changes.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
mkv_episode_matcher/.gitattributes,sha256=Gh2-F2vCM7SZ01pX23UT8pQcmauXWfF3gwyRSb6ZAFs,66
|
|
2
|
+
mkv_episode_matcher/__init__.py,sha256=u3yZcpuK0ICeUjxYKePvW-zS61E5ss5q2AvqnSHuz9E,240
|
|
3
|
+
mkv_episode_matcher/__main__.py,sha256=GB1fwuAaDzpYnr-XWG9pL9O5g_MRDeDVAEDsO8T5IXI,104
|
|
4
|
+
mkv_episode_matcher/asr_models.py,sha256=zydUzO2vPfUbBgbRStr3rECz7oi_0Lw1hRz84ASa3Ns,17067
|
|
5
|
+
mkv_episode_matcher/cli.py,sha256=x5ZOKHy-ut4XWkJe1i6KPgZYq2RfBJRQB-YPHS2Z_-E,17923
|
|
6
|
+
mkv_episode_matcher/episode_identification.py,sha256=dN_uwkLlP4_75bBHNjI7zFmQx8F20LMTdmUnfe8IEzM,21785
|
|
7
|
+
mkv_episode_matcher/subtitle_utils.py,sha256=3n5msR9H6PQj5gQDTHDhX-pN1GffK8zXzlVsGhY26BY,2521
|
|
8
|
+
mkv_episode_matcher/tmdb_client.py,sha256=7DraZs7-pdGJu9LgzJ2H3cct_Eiayhm_2oC9JDZVVZQ,5674
|
|
9
|
+
mkv_episode_matcher/utils.py,sha256=2EfMz9u2sfhAVJBpmImOn_LjI9ixHDNCRujLDcYN8PI,18699
|
|
10
|
+
mkv_episode_matcher/core/config_manager.py,sha256=q9_qwP6guVEgA01NrXCQ-TcmSlOi9arUOXzCoNYU_VQ,3567
|
|
11
|
+
mkv_episode_matcher/core/engine.py,sha256=4y659ElJ1B0yFUJujdX9cYXCf7ZCo80B4V0m61tZWAs,22046
|
|
12
|
+
mkv_episode_matcher/core/matcher.py,sha256=6_4_tex5UAf2df8afCSqIARCxypN2Q_IcdmABONMQns,8217
|
|
13
|
+
mkv_episode_matcher/core/models.py,sha256=gJ0QuIj_On1ilNQ-Nxt8J3muEC2R4owlTCnuJQc5JDs,2233
|
|
14
|
+
mkv_episode_matcher/core/utils.py,sha256=KU90CiF2H49ELV_6VwRQKOTa61ehfOf34siusoafuyk,4536
|
|
15
|
+
mkv_episode_matcher/core/providers/asr.py,sha256=7ATCBRuWYYaPAys69mSam1UK2E4fC532eHoF_KEzeXs,2405
|
|
16
|
+
mkv_episode_matcher/core/providers/subtitles.py,sha256=5IPTRkTvWYBsJkdQqKuKgUb7Tzmv3kl7NVH4Xj-lRKY,12339
|
|
17
|
+
mkv_episode_matcher/ui/flet_app.py,sha256=WgZXHf9UrbIsG8mP6b5D2aiBiE4tMozNapiSofpMvxw,28236
|
|
18
|
+
mkv_episode_matcher-1.0.0.dist-info/licenses/LICENSE,sha256=ScBvQ_sUEpaRMDYp01qmMDELT1nTknFP2w-VRAS54TY,1071
|
|
19
|
+
mkv_episode_matcher-1.0.0.dist-info/METADATA,sha256=ituP5GyTvTrT2xfKrRq6xtXRIMuIko70ZcX4UekSZkM,10005
|
|
20
|
+
mkv_episode_matcher-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
21
|
+
mkv_episode_matcher-1.0.0.dist-info/entry_points.txt,sha256=IglJ43SuCZq2eQ3shMFILCkmQASJHnDCI3ogohW2Hn4,64
|
|
22
|
+
mkv_episode_matcher-1.0.0.dist-info/top_level.txt,sha256=XRLbd93HUaedeWLtkyTvQjFcE5QcBRYa3V-CfHrq-OI,20
|
|
23
|
+
mkv_episode_matcher-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jonathan Sakkos
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
mkv_episode_matcher/config.py
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
# config.py
|
|
2
|
-
import configparser
|
|
3
|
-
import multiprocessing
|
|
4
|
-
import os
|
|
5
|
-
|
|
6
|
-
from loguru import logger
|
|
7
|
-
|
|
8
|
-
MAX_THREADS = 4
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def get_total_threads():
|
|
12
|
-
return multiprocessing.cpu_count()
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
total_threads = get_total_threads()
|
|
16
|
-
|
|
17
|
-
if total_threads < MAX_THREADS:
|
|
18
|
-
MAX_THREADS = total_threads
|
|
19
|
-
logger.info(f"Total available threads: {total_threads} -> Setting max to {MAX_THREADS}")
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def set_config(
|
|
23
|
-
tmdb_api_key,
|
|
24
|
-
open_subtitles_api_key,
|
|
25
|
-
open_subtitles_user_agent,
|
|
26
|
-
open_subtitles_username,
|
|
27
|
-
open_subtitles_password,
|
|
28
|
-
show_dir,
|
|
29
|
-
file,
|
|
30
|
-
tesseract_path=None,
|
|
31
|
-
):
|
|
32
|
-
"""
|
|
33
|
-
Sets the configuration values and writes them to a file.
|
|
34
|
-
|
|
35
|
-
Args:
|
|
36
|
-
tmdb_api_key (str): The API key for TMDB (The Movie Database).
|
|
37
|
-
open_subtitles_api_key (str): The API key for OpenSubtitles.
|
|
38
|
-
open_subtitles_user_agent (str): The user agent for OpenSubtitles.
|
|
39
|
-
open_subtitles_username (str): The username for OpenSubtitles.
|
|
40
|
-
open_subtitles_password (str): The password for OpenSubtitles.
|
|
41
|
-
show_dir (str): The directory where the TV show episodes are located.
|
|
42
|
-
file (str): The path to the configuration file.
|
|
43
|
-
tesseract_path (str, optional): The path to the Tesseract OCR executable.
|
|
44
|
-
|
|
45
|
-
Returns:
|
|
46
|
-
None
|
|
47
|
-
"""
|
|
48
|
-
config = configparser.ConfigParser()
|
|
49
|
-
config["Config"] = {
|
|
50
|
-
"tmdb_api_key": str(tmdb_api_key),
|
|
51
|
-
"show_dir": show_dir,
|
|
52
|
-
"max_threads": int(MAX_THREADS),
|
|
53
|
-
"open_subtitles_api_key": str(open_subtitles_api_key),
|
|
54
|
-
"open_subtitles_user_agent": str(open_subtitles_user_agent),
|
|
55
|
-
"open_subtitles_username": str(open_subtitles_username),
|
|
56
|
-
"open_subtitles_password": str(open_subtitles_password),
|
|
57
|
-
"tesseract_path": str(tesseract_path),
|
|
58
|
-
}
|
|
59
|
-
logger.info(
|
|
60
|
-
f"Setting config with API:{tmdb_api_key}, show_dir: {show_dir}, and max_threads: {MAX_THREADS}"
|
|
61
|
-
)
|
|
62
|
-
with open(file, "w") as configfile:
|
|
63
|
-
config.write(configfile)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def get_config(file):
|
|
67
|
-
"""
|
|
68
|
-
Read and return the configuration from the specified file.
|
|
69
|
-
|
|
70
|
-
Args:
|
|
71
|
-
file (str): The path to the configuration file.
|
|
72
|
-
|
|
73
|
-
Returns:
|
|
74
|
-
dict: The configuration settings as a dictionary.
|
|
75
|
-
|
|
76
|
-
"""
|
|
77
|
-
logger.info(f"Loading config from {file}")
|
|
78
|
-
config = configparser.ConfigParser()
|
|
79
|
-
if os.path.exists(file):
|
|
80
|
-
config.read(file)
|
|
81
|
-
return config["Config"] if "Config" in config else None
|
|
82
|
-
return {}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
# mkv_episode_matcher/episode_matcher.py
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
import shutil
|
|
5
|
-
import glob
|
|
6
|
-
import os
|
|
7
|
-
from loguru import logger
|
|
8
|
-
import re
|
|
9
|
-
from mkv_episode_matcher.__main__ import CONFIG_FILE, CACHE_DIR
|
|
10
|
-
from mkv_episode_matcher.config import get_config
|
|
11
|
-
from mkv_episode_matcher.mkv_to_srt import convert_mkv_to_srt
|
|
12
|
-
from mkv_episode_matcher.tmdb_client import fetch_show_id
|
|
13
|
-
from mkv_episode_matcher.utils import (
|
|
14
|
-
check_filename,
|
|
15
|
-
clean_text,
|
|
16
|
-
cleanup_ocr_files,
|
|
17
|
-
get_subtitles,
|
|
18
|
-
process_reference_srt_files,
|
|
19
|
-
process_srt_files,
|
|
20
|
-
compare_and_rename_files,get_valid_seasons,rename_episode_file
|
|
21
|
-
)
|
|
22
|
-
from mkv_episode_matcher.speech_to_text import process_speech_to_text
|
|
23
|
-
from mkv_episode_matcher.episode_identification import EpisodeMatcher
|
|
24
|
-
|
|
25
|
-
def process_show(season=None, dry_run=False, get_subs=False):
|
|
26
|
-
"""Process the show using streaming speech recognition with OCR fallback."""
|
|
27
|
-
config = get_config(CONFIG_FILE)
|
|
28
|
-
show_dir = config.get("show_dir")
|
|
29
|
-
show_name = clean_text(os.path.basename(show_dir))
|
|
30
|
-
matcher = EpisodeMatcher(CACHE_DIR, show_name)
|
|
31
|
-
|
|
32
|
-
season_paths = get_valid_seasons(show_dir)
|
|
33
|
-
if not season_paths:
|
|
34
|
-
logger.warning(f"No seasons with .mkv files found")
|
|
35
|
-
return
|
|
36
|
-
|
|
37
|
-
if season is not None:
|
|
38
|
-
season_path = os.path.join(show_dir, f"Season {season}")
|
|
39
|
-
if season_path not in season_paths:
|
|
40
|
-
logger.warning(f"Season {season} has no .mkv files to process")
|
|
41
|
-
return
|
|
42
|
-
season_paths = [season_path]
|
|
43
|
-
|
|
44
|
-
for season_path in season_paths:
|
|
45
|
-
mkv_files = [f for f in glob.glob(os.path.join(season_path, "*.mkv"))
|
|
46
|
-
if not check_filename(f)]
|
|
47
|
-
|
|
48
|
-
if not mkv_files:
|
|
49
|
-
logger.info(f"No new files to process in {season_path}")
|
|
50
|
-
continue
|
|
51
|
-
|
|
52
|
-
season_num = int(re.search(r'Season (\d+)', season_path).group(1))
|
|
53
|
-
temp_dir = Path(season_path) / "temp"
|
|
54
|
-
ocr_dir = Path(season_path) / "ocr"
|
|
55
|
-
temp_dir.mkdir(exist_ok=True)
|
|
56
|
-
ocr_dir.mkdir(exist_ok=True)
|
|
57
|
-
|
|
58
|
-
try:
|
|
59
|
-
if get_subs:
|
|
60
|
-
show_id = fetch_show_id(matcher.show_name)
|
|
61
|
-
if show_id:
|
|
62
|
-
get_subtitles(show_id, seasons={season_num})
|
|
63
|
-
|
|
64
|
-
unmatched_files = []
|
|
65
|
-
for mkv_file in mkv_files:
|
|
66
|
-
logger.info(f"Attempting speech recognition match for {mkv_file}")
|
|
67
|
-
match = matcher.identify_episode(mkv_file, temp_dir, season_num)
|
|
68
|
-
|
|
69
|
-
if match:
|
|
70
|
-
new_name = f"{matcher.show_name} - S{match['season']:02d}E{match['episode']:02d}.mkv"
|
|
71
|
-
new_path = os.path.join(season_path, new_name)
|
|
72
|
-
|
|
73
|
-
logger.info(f"Speech matched {os.path.basename(mkv_file)} to {new_name} "
|
|
74
|
-
f"(confidence: {match['confidence']:.2f})")
|
|
75
|
-
|
|
76
|
-
if not dry_run:
|
|
77
|
-
logger.info(f"Renaming {mkv_file} to {new_name}")
|
|
78
|
-
rename_episode_file(mkv_file, new_name)
|
|
79
|
-
else:
|
|
80
|
-
logger.info(f"Speech recognition match failed for {mkv_file}, trying OCR")
|
|
81
|
-
unmatched_files.append(mkv_file)
|
|
82
|
-
|
|
83
|
-
# OCR fallback for unmatched files
|
|
84
|
-
if unmatched_files:
|
|
85
|
-
logger.info(f"Attempting OCR matching for {len(unmatched_files)} unmatched files")
|
|
86
|
-
convert_mkv_to_srt(season_path, unmatched_files)
|
|
87
|
-
|
|
88
|
-
reference_text_dict = process_reference_srt_files(matcher.show_name)
|
|
89
|
-
srt_text_dict = process_srt_files(str(ocr_dir))
|
|
90
|
-
|
|
91
|
-
compare_and_rename_files(
|
|
92
|
-
srt_text_dict,
|
|
93
|
-
reference_text_dict,
|
|
94
|
-
dry_run=dry_run,
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
finally:
|
|
98
|
-
if not dry_run:
|
|
99
|
-
shutil.rmtree(temp_dir)
|
|
100
|
-
cleanup_ocr_files(show_dir)
|