releascenify 0.1.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Denis Machard
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.
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.4
2
+ Name: releascenify
3
+ Version: 0.1.0
4
+ Summary: A regex-based Python library to parse scene release names and extract technical metadata
5
+ License: MIT License
6
+
7
+ Copyright (c) 2026 Denis Machard
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+
27
+ Classifier: Programming Language :: Python :: 3
28
+ Classifier: License :: OSI Approved :: MIT License
29
+ Classifier: Operating System :: OS Independent
30
+ Requires-Python: >=3.7
31
+ Description-Content-Type: text/markdown
32
+ License-File: LICENSE
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ # Releascenify
38
+
39
+ A robust, regex-based Python library to parse scene release names and extract technical metadata (title, year, resolution, quality, codec, audio, languages, etc.).
40
+
41
+ **Live Demo & Web Interface:** [https://dmachard.github.io/releascenify/docs/](https://dmachard.github.io/releascenify/docs/)
42
+
43
+ ## Why?
44
+ Release names follow a strict grammar inherited from the Scene and P2P groups. Once decoded, you can predict the quality, source, language, and release group just from the filename. This library is a modernized alternative to PTN (Parse Torrent Name).
45
+
46
+ ## Installation
47
+ *(To be published on PyPI)*
48
+
49
+ ## Usage
50
+
51
+ ```python
52
+ from releascenify import parse_filename
53
+ from releascenify.comparator import get_quality_score, is_better_release
54
+
55
+ filename = "Gladiator.II.2024.MULTi.2160p.WEB-DL.DV.HDR.H265-GROUP"
56
+ parsed = parse_filename(filename)
57
+
58
+ print(parsed)
59
+ # {
60
+ # "title": "Gladiator II",
61
+ # "year": 2024,
62
+ # "resolution": "2160P",
63
+ # "quality": "WEB-DL",
64
+ # "v_quality": "HDR DV",
65
+ # "codec": "H265",
66
+ # "languages": ["MULTI"]
67
+ # ...
68
+ # }
69
+
70
+ # Calculate quality score
71
+ score = get_quality_score(parsed)
72
+ print(f"Quality Score: {score}")
73
+
74
+ # Compare two releases
75
+ rel_a = parse_filename("Gladiator.II.2024.1080p.BluRay.x264-GROUP")
76
+ rel_b = parse_filename("Gladiator.II.2024.2160p.WEB-DL.x265-GROUP")
77
+
78
+ if is_better_release(rel_b, rel_a):
79
+ print("Release B is better than Release A")
80
+ ```
81
+
82
+ ## Development & Tests
83
+
84
+ ### Setup Local Virtual Environment
85
+
86
+ To set up the development environment, create a virtual environment and install the package in editable mode with development dependencies:
87
+
88
+ ```bash
89
+ # Create the virtual environment
90
+ virtualenv .venv
91
+
92
+ # Activate the virtual environment
93
+ source .venv/bin/activate
94
+
95
+ # Install the package in editable/development mode
96
+ pip install -e .[dev]
97
+ ```
98
+
99
+ ### Run Tests
100
+
101
+ Run the test suite using `pytest`:
102
+
103
+ ```bash
104
+ pytest tests/ -v
105
+ ```
106
+
107
+ ### Run Website Locally
108
+
109
+ Due to browser CORS security policies, opening the HTML files directly as a local file (via `file://`) will block loading the relative Python files. You need to run a local web server from the repository root:
110
+
111
+ ```bash
112
+ # Start a local HTTP server
113
+ python3 -m http.server 8000
114
+ ```
115
+
116
+ Then navigate to: [http://localhost:8000/](http://localhost:8000/) (which redirects to `/docs/`) or directly to [http://localhost:8000/docs/](http://localhost:8000/docs/).
@@ -0,0 +1,80 @@
1
+ # Releascenify
2
+
3
+ A robust, regex-based Python library to parse scene release names and extract technical metadata (title, year, resolution, quality, codec, audio, languages, etc.).
4
+
5
+ **Live Demo & Web Interface:** [https://dmachard.github.io/releascenify/docs/](https://dmachard.github.io/releascenify/docs/)
6
+
7
+ ## Why?
8
+ Release names follow a strict grammar inherited from the Scene and P2P groups. Once decoded, you can predict the quality, source, language, and release group just from the filename. This library is a modernized alternative to PTN (Parse Torrent Name).
9
+
10
+ ## Installation
11
+ *(To be published on PyPI)*
12
+
13
+ ## Usage
14
+
15
+ ```python
16
+ from releascenify import parse_filename
17
+ from releascenify.comparator import get_quality_score, is_better_release
18
+
19
+ filename = "Gladiator.II.2024.MULTi.2160p.WEB-DL.DV.HDR.H265-GROUP"
20
+ parsed = parse_filename(filename)
21
+
22
+ print(parsed)
23
+ # {
24
+ # "title": "Gladiator II",
25
+ # "year": 2024,
26
+ # "resolution": "2160P",
27
+ # "quality": "WEB-DL",
28
+ # "v_quality": "HDR DV",
29
+ # "codec": "H265",
30
+ # "languages": ["MULTI"]
31
+ # ...
32
+ # }
33
+
34
+ # Calculate quality score
35
+ score = get_quality_score(parsed)
36
+ print(f"Quality Score: {score}")
37
+
38
+ # Compare two releases
39
+ rel_a = parse_filename("Gladiator.II.2024.1080p.BluRay.x264-GROUP")
40
+ rel_b = parse_filename("Gladiator.II.2024.2160p.WEB-DL.x265-GROUP")
41
+
42
+ if is_better_release(rel_b, rel_a):
43
+ print("Release B is better than Release A")
44
+ ```
45
+
46
+ ## Development & Tests
47
+
48
+ ### Setup Local Virtual Environment
49
+
50
+ To set up the development environment, create a virtual environment and install the package in editable mode with development dependencies:
51
+
52
+ ```bash
53
+ # Create the virtual environment
54
+ virtualenv .venv
55
+
56
+ # Activate the virtual environment
57
+ source .venv/bin/activate
58
+
59
+ # Install the package in editable/development mode
60
+ pip install -e .[dev]
61
+ ```
62
+
63
+ ### Run Tests
64
+
65
+ Run the test suite using `pytest`:
66
+
67
+ ```bash
68
+ pytest tests/ -v
69
+ ```
70
+
71
+ ### Run Website Locally
72
+
73
+ Due to browser CORS security policies, opening the HTML files directly as a local file (via `file://`) will block loading the relative Python files. You need to run a local web server from the repository root:
74
+
75
+ ```bash
76
+ # Start a local HTTP server
77
+ python3 -m http.server 8000
78
+ ```
79
+
80
+ Then navigate to: [http://localhost:8000/](http://localhost:8000/) (which redirects to `/docs/`) or directly to [http://localhost:8000/docs/](http://localhost:8000/docs/).
@@ -0,0 +1,25 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "releascenify"
7
+ version = "0.1.0"
8
+ description = "A regex-based Python library to parse scene release names and extract technical metadata"
9
+ readme = "README.md"
10
+ requires-python = ">=3.7"
11
+ license = { file = "LICENSE" }
12
+ classifiers = [
13
+ "Programming Language :: Python :: 3",
14
+ "License :: OSI Approved :: MIT License",
15
+ "Operating System :: OS Independent",
16
+ ]
17
+ dependencies = []
18
+
19
+ [project.optional-dependencies]
20
+ dev = [
21
+ "pytest>=7.0.0",
22
+ ]
23
+
24
+ [tool.setuptools]
25
+ packages = ["releascenify"]
@@ -0,0 +1,4 @@
1
+ from .parser import ReleaseParser, parse_filename
2
+ from .comparator import get_quality_score, is_better_release
3
+
4
+ __all__ = ['ReleaseParser', 'parse_filename', 'get_quality_score', 'is_better_release']
@@ -0,0 +1,56 @@
1
+ from typing import Dict, Any
2
+
3
+ def get_quality_score(parsed_release: Dict[str, Any]) -> int:
4
+ """
5
+ Returns a numeric score for quality comparison.
6
+ Higher score means better quality.
7
+ Scoring priority: Resolution > Language > Video Quality > Audio > Source > Codec
8
+ """
9
+ score = 0
10
+
11
+ # 1. Resolution
12
+ res = (parsed_release.get("resolution") or "").lower()
13
+ # Assuming 4K is best, then 4KLIGHT, 1080p, 720p, 480p
14
+ # Note: DDLtower logic penalizes 4KLight vs True 4K.
15
+ # True 4K: 450, 4KLight: 400 (or vice versa depending on preference, here we stick to generic)
16
+ if "4klight" in res: score += 400
17
+ elif "2160" in res or "4k" in res: score += 450
18
+ elif "1080" in res: score += 300
19
+ elif "720" in res: score += 200
20
+ elif "480" in res: score += 100
21
+
22
+ # 2. Language (Multi > VF > VOSTFR)
23
+ langs = [l.lower() for l in parsed_release.get("languages", [])]
24
+ if "multi" in langs: score += 50
25
+ elif "french" in langs or "vf" in langs: score += 30
26
+ elif "vostfr" in langs: score += 10
27
+
28
+ # 3. Video Quality (DV / HDR / 10bit)
29
+ vq = (parsed_release.get("v_quality") or "").lower()
30
+ if "dv" in vq or "dovi" in vq: score += 25
31
+ if "hdr" in vq: score += 20
32
+ if "10bit" in vq: score += 10
33
+
34
+ # 4. Audio Quality (Atmos / TrueHD > DTS > AC3)
35
+ aud = (parsed_release.get("audio") or "").lower()
36
+ if "atmos" in aud or "truehd" in aud: score += 15
37
+ elif "dts" in aud: score += 10
38
+ elif "ac3" in aud or "ddp" in aud: score += 5
39
+
40
+ # 5. Source Quality (BluRay > WEB-DL > HDTV)
41
+ q = (parsed_release.get("quality") or "").lower()
42
+ if "bluray" in q or "bdrip" in q: score += 10
43
+ elif "web" in q: score += 7
44
+ elif "hdtv" in q: score += 3
45
+
46
+ # 6. Codec (HEVC / x265 > x264)
47
+ c = (parsed_release.get("codec") or "").lower()
48
+ if "265" in c or "hevc" in c: score += 5
49
+
50
+ return score
51
+
52
+ def is_better_release(release_a: Dict[str, Any], release_b: Dict[str, Any]) -> bool:
53
+ """
54
+ Returns True if release_a is considered better than release_b.
55
+ """
56
+ return get_quality_score(release_a) > get_quality_score(release_b)
@@ -0,0 +1,135 @@
1
+ import re
2
+ import html
3
+ import unicodedata
4
+ from typing import Dict, Any, Optional, List
5
+
6
+ class ReleaseParser:
7
+ def __init__(self):
8
+ # Base patterns to extract common release info
9
+ self.patterns = {
10
+ 'season': r'(?i)\b(?:s|saison)[\.\-\s]*(\d{1,2})\b',
11
+ 'episode': r'(?i)\b(?:e|ep|episode)[\.\-\s]*(\d{1,3})\b',
12
+ 'year': r'\b(19\d{2}|20[0-2]\d)\b',
13
+ 'resolution': r'(?i)(4KLIGHT|4K|2160[pP]|1080[pP]|720[pP]|UHD)',
14
+ 'quality': r'(?i)(WEB-DL|WEBRIP|WEBLIGHT|WEB|BLURAY|BDRIP|BRRIP|DVDRIP|HDTV)',
15
+ 'codec': r'(?i)(x265|x264|h265|h264|HEVC)',
16
+ 'audio': r'(?i)(AAC|AC3|E-AC3|DTS-HD|DTS|ATMOS|TRUEHD|DDP\d\.\d)',
17
+ 'channels': r'(7\.1|5\.1|2\.0)\b',
18
+ }
19
+
20
+ def clean_network_name(self, name: str) -> str:
21
+ """Normalizes network names for better UI display."""
22
+ if not name: return name
23
+ mapping = {
24
+ "Disney Plus": "Disney+", "Amazon Studios": "Amazon", "Amazon Prime": "Amazon",
25
+ "HBO Max": "HBO", "Apple TV Plus": "Apple TV+", "Paramount Plus": "Paramount+"
26
+ }
27
+ return mapping.get(name, name)
28
+
29
+ def extract_v_quality(self, filename: str) -> Optional[str]:
30
+ """Detects HDR, DV, etc. from filename."""
31
+ if not filename: return None
32
+ fn = filename.upper()
33
+ tags = []
34
+ if any(x in fn for x in ["DV", "DOVI"]) or re.search(r'DOLBY[\.\-\s]VISION', fn): tags.append("DV")
35
+ if any(x in fn for x in ["HDR", "HDR10", "HDR10PLUS", "HDR10+"]): tags.append("HDR")
36
+ if "HLG" in fn: tags.append("HLG")
37
+ return " ".join(sorted(list(set(tags)), reverse=True)) if tags else None
38
+
39
+ def _extract_langs(self, fn_up: str) -> List[str]:
40
+ langs = []
41
+ if "TRUEFRENCH" in fn_up or "VFF" in fn_up or "FRENCH" in fn_up: langs.append("FRENCH")
42
+ if "MULTI" in fn_up: langs.append("MULTI")
43
+ if "VOSTFR" in fn_up or "VOST" in fn_up: langs.append("VOSTFR")
44
+ if "VFI" in fn_up or "VFQ" in fn_up or "VF2" in fn_up or re.search(r'\bVF\b', fn_up.replace('.', ' ').replace('-', ' ').replace('_', ' ')): langs.append("VF")
45
+ return list(dict.fromkeys(langs))
46
+
47
+ def parse(self, filename: str) -> Dict[str, Any]:
48
+ if not filename: return {}
49
+
50
+ result = {
51
+ "title": "", "category": "movie", "year": None, "season": None, "episode": None,
52
+ "resolution": None, "quality": None, "codec": None, "audio": None,
53
+ "channels": None, "network": "", "v_quality": "", "languages": [], "group": None
54
+ }
55
+
56
+ # Extract group
57
+ group_match = re.search(r'-([A-Za-z0-9_@]+)(?:\s*\(.*?\))?(?:\.[a-z0-9]{3,4})?$', filename.strip())
58
+ if group_match:
59
+ grp = group_match.group(1)
60
+ if grp.upper() not in ['DL', 'HDMA', 'FR', 'EN', 'HD']:
61
+ result['group'] = grp
62
+
63
+ # Check for joint SxxExx
64
+ se_match = re.search(r'(?i)\bs(\d{1,2})[\.\-\s]?[ex](\d{1,3})\b', filename)
65
+ if se_match:
66
+ result['season'] = str(int(se_match.group(1)))
67
+ result['episode'] = str(int(se_match.group(2)))
68
+ else:
69
+ # Check individual fallbacks
70
+ s_match = re.search(self.patterns['season'], filename)
71
+ if s_match: result['season'] = str(int(s_match.group(1)))
72
+ e_match = re.search(self.patterns['episode'], filename)
73
+ if e_match: result['episode'] = str(int(e_match.group(1)))
74
+
75
+ # Extract basic info using regex (except audio and year, handled below)
76
+ for key, pattern in self.patterns.items():
77
+ if key in ['season', 'episode', 'audio', 'year']: continue # Already handled
78
+ match = re.search(pattern, filename)
79
+ if match:
80
+ result[key] = match.group(1).upper()
81
+
82
+ # Extract year (prefer the last matching year if multiple are present, e.g., 1917 (2019))
83
+ years = re.findall(self.patterns['year'], filename)
84
+ if years:
85
+ result['year'] = int(years[-1])
86
+
87
+ # Extract audio with priority (Atmos/TrueHD > DTS > AC3/DDP/AAC)
88
+ for pat in [r'(?i)(ATMOS|TRUEHD)', r'(?i)(DTS-HD|DTS)', r'(?i)(E-AC3|AC3|AAC|DDP\d\.\d|DDP)']:
89
+ match = re.search(pat, filename)
90
+ if match:
91
+ result['audio'] = match.group(1).upper()
92
+ break
93
+
94
+ if result['season'] or result['episode']:
95
+ result['category'] = 'series'
96
+
97
+ # V_Quality
98
+ result['v_quality'] = self.extract_v_quality(filename) or ""
99
+
100
+ # Clean title
101
+ fn_clean = html.unescape(filename)
102
+ fn_clean = unicodedata.normalize('NFKD', fn_clean).encode('ASCII', 'ignore').decode('utf-8')
103
+
104
+ # Remove Volume/Part markers
105
+ fn_clean = re.sub(r'\b(Vol|Pt|Part|Partie)[\.\s]?\d+\b', ' ', fn_clean, flags=re.I)
106
+ fn_clean = re.sub(r'\b\d+(?:e|ème|re|nd|rd|th)?\s+partie\b', ' ', fn_clean, flags=re.I)
107
+
108
+ # Split point for title
109
+ tags_to_split = [
110
+ r'S\d+', r'E\d+', r'S\d+E\d+', r'SAISON[\.\-\s]?\d+', r'EPISODE[\.\-\s]?\d+', 'MULTI', 'FRENCH', 'TRUEFRENCH', 'VOSTFR', 'SUBFRENCH', 'VFF', 'VFI', 'VFQ', 'VOST', 'STFI',
111
+ '1080P', '720P', '2160P', '4K', '4KLIGHT', 'UHD', 'BLURAY', 'BDRIP', 'DVDRIP', 'WEBRIP', 'WEB-DL', 'WEBLIGHT', 'WEB',
112
+ 'HDR', 'DV', 'HEVC', 'X264', 'X265', 'H264', 'H265', 'REPACK', 'PROPER', 'FINAL', 'INTERNAL', 'CUSTOM', 'AC3', 'DDP', 'DTS', 'ATMOS',
113
+ r'19\d{2}', r'20[0-2]\d'
114
+ ]
115
+ pattern = r'[\.\[\(\s\-\_](?:' + '|'.join(tags_to_split) + r')\b'
116
+ title = re.split(pattern, fn_clean, flags=re.I)[0]
117
+
118
+ title = title.replace('.', ' ').replace('_', ' ').strip()
119
+ title = re.sub(r'\s+', ' ', title).strip()
120
+
121
+ result['title'] = title
122
+
123
+ # Languages
124
+ fn_up = filename.upper().replace('[', '.').replace(']', '.').replace('_', '.')
125
+ result['languages'] = self._extract_langs(fn_up)
126
+
127
+ # Fix resolution for 4KLIGHT
128
+ if "4KLIGHT" in fn_up:
129
+ result['resolution'] = "4KLIGHT"
130
+
131
+ return result
132
+
133
+ def parse_filename(filename: str) -> Dict[str, Any]:
134
+ parser = ReleaseParser()
135
+ return parser.parse(filename)
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.4
2
+ Name: releascenify
3
+ Version: 0.1.0
4
+ Summary: A regex-based Python library to parse scene release names and extract technical metadata
5
+ License: MIT License
6
+
7
+ Copyright (c) 2026 Denis Machard
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+
27
+ Classifier: Programming Language :: Python :: 3
28
+ Classifier: License :: OSI Approved :: MIT License
29
+ Classifier: Operating System :: OS Independent
30
+ Requires-Python: >=3.7
31
+ Description-Content-Type: text/markdown
32
+ License-File: LICENSE
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ # Releascenify
38
+
39
+ A robust, regex-based Python library to parse scene release names and extract technical metadata (title, year, resolution, quality, codec, audio, languages, etc.).
40
+
41
+ **Live Demo & Web Interface:** [https://dmachard.github.io/releascenify/docs/](https://dmachard.github.io/releascenify/docs/)
42
+
43
+ ## Why?
44
+ Release names follow a strict grammar inherited from the Scene and P2P groups. Once decoded, you can predict the quality, source, language, and release group just from the filename. This library is a modernized alternative to PTN (Parse Torrent Name).
45
+
46
+ ## Installation
47
+ *(To be published on PyPI)*
48
+
49
+ ## Usage
50
+
51
+ ```python
52
+ from releascenify import parse_filename
53
+ from releascenify.comparator import get_quality_score, is_better_release
54
+
55
+ filename = "Gladiator.II.2024.MULTi.2160p.WEB-DL.DV.HDR.H265-GROUP"
56
+ parsed = parse_filename(filename)
57
+
58
+ print(parsed)
59
+ # {
60
+ # "title": "Gladiator II",
61
+ # "year": 2024,
62
+ # "resolution": "2160P",
63
+ # "quality": "WEB-DL",
64
+ # "v_quality": "HDR DV",
65
+ # "codec": "H265",
66
+ # "languages": ["MULTI"]
67
+ # ...
68
+ # }
69
+
70
+ # Calculate quality score
71
+ score = get_quality_score(parsed)
72
+ print(f"Quality Score: {score}")
73
+
74
+ # Compare two releases
75
+ rel_a = parse_filename("Gladiator.II.2024.1080p.BluRay.x264-GROUP")
76
+ rel_b = parse_filename("Gladiator.II.2024.2160p.WEB-DL.x265-GROUP")
77
+
78
+ if is_better_release(rel_b, rel_a):
79
+ print("Release B is better than Release A")
80
+ ```
81
+
82
+ ## Development & Tests
83
+
84
+ ### Setup Local Virtual Environment
85
+
86
+ To set up the development environment, create a virtual environment and install the package in editable mode with development dependencies:
87
+
88
+ ```bash
89
+ # Create the virtual environment
90
+ virtualenv .venv
91
+
92
+ # Activate the virtual environment
93
+ source .venv/bin/activate
94
+
95
+ # Install the package in editable/development mode
96
+ pip install -e .[dev]
97
+ ```
98
+
99
+ ### Run Tests
100
+
101
+ Run the test suite using `pytest`:
102
+
103
+ ```bash
104
+ pytest tests/ -v
105
+ ```
106
+
107
+ ### Run Website Locally
108
+
109
+ Due to browser CORS security policies, opening the HTML files directly as a local file (via `file://`) will block loading the relative Python files. You need to run a local web server from the repository root:
110
+
111
+ ```bash
112
+ # Start a local HTTP server
113
+ python3 -m http.server 8000
114
+ ```
115
+
116
+ Then navigate to: [http://localhost:8000/](http://localhost:8000/) (which redirects to `/docs/`) or directly to [http://localhost:8000/docs/](http://localhost:8000/docs/).
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ releascenify/__init__.py
5
+ releascenify/comparator.py
6
+ releascenify/parser.py
7
+ releascenify.egg-info/PKG-INFO
8
+ releascenify.egg-info/SOURCES.txt
9
+ releascenify.egg-info/dependency_links.txt
10
+ releascenify.egg-info/requires.txt
11
+ releascenify.egg-info/top_level.txt
12
+ tests/test_parser.py
@@ -0,0 +1,3 @@
1
+
2
+ [dev]
3
+ pytest>=7.0.0
@@ -0,0 +1 @@
1
+ releascenify
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,37 @@
1
+ import json
2
+ import os
3
+ import pytest
4
+ from releascenify.parser import parse_filename
5
+
6
+ def load_test_cases(category):
7
+ filename = f'test_cases_{category}.json'
8
+ cases_file = os.path.join(os.path.dirname(__file__), filename)
9
+ with open(cases_file, 'r', encoding='utf-8') as f:
10
+ data = json.load(f)
11
+
12
+ cases = []
13
+ for case in data:
14
+ # Use filename as ID for nice pytest output
15
+ cases.append(pytest.param(case['filename'], case['expected'], id=case['filename']))
16
+ return cases
17
+
18
+ def run_parse_test(filename, expected):
19
+ result = parse_filename(filename)
20
+ for key, expected_val in expected.items():
21
+ actual_val = result.get(key)
22
+ if isinstance(expected_val, list):
23
+ # Sort lists for comparison (like languages)
24
+ assert sorted(actual_val) == sorted(expected_val), f"Failed on '{filename}': expected {key}={expected_val}, got {actual_val}"
25
+ else:
26
+ assert actual_val == expected_val, f"Failed on '{filename}': expected {key}={expected_val}, got {actual_val}"
27
+
28
+ @pytest.mark.parametrize("filename,expected", load_test_cases('movies'))
29
+ def test_movie_parser(filename, expected):
30
+ run_parse_test(filename, expected)
31
+
32
+ @pytest.mark.parametrize("filename,expected", load_test_cases('series'))
33
+ def test_series_parser(filename, expected):
34
+ run_parse_test(filename, expected)
35
+
36
+ if __name__ == '__main__':
37
+ pytest.main([__file__, '-v'])