mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-03-17 16:47:29 +00:00
Compare commits
7 Commits
1bd63ddc91
...
ead05d08ac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ead05d08ac | ||
|
|
8c1f51a431 | ||
|
|
1d4e8bf9ec | ||
|
|
b4a1f2236e | ||
|
|
3277ab0d77 | ||
|
|
be0f7299f8 | ||
|
|
948ef30de7 |
@@ -1,62 +0,0 @@
|
|||||||
# Logs and temporary files
|
|
||||||
|
|
||||||
Logs/
|
|
||||||
logs/
|
|
||||||
temp/
|
|
||||||
\*.log
|
|
||||||
|
|
||||||
# Sensitive files
|
|
||||||
|
|
||||||
key_vault.db
|
|
||||||
unshackle/WVDs/
|
|
||||||
unshackle/PRDs/
|
|
||||||
unshackle/cookies/
|
|
||||||
_.prd
|
|
||||||
_.wvd
|
|
||||||
|
|
||||||
# Cache directories
|
|
||||||
|
|
||||||
unshackle/cache/
|
|
||||||
**pycache**/
|
|
||||||
_.pyc
|
|
||||||
_.pyo
|
|
||||||
\*.pyd
|
|
||||||
.Python
|
|
||||||
|
|
||||||
# Development files
|
|
||||||
|
|
||||||
.git/
|
|
||||||
.gitignore
|
|
||||||
.vscode/
|
|
||||||
.idea/
|
|
||||||
_.swp
|
|
||||||
_.swo
|
|
||||||
|
|
||||||
# Documentation and plans
|
|
||||||
|
|
||||||
plan/
|
|
||||||
CONTRIBUTING.md
|
|
||||||
CONFIG.md
|
|
||||||
AGENTS.md
|
|
||||||
OLD-CHANGELOG.md
|
|
||||||
cliff.toml
|
|
||||||
|
|
||||||
# Installation scripts
|
|
||||||
|
|
||||||
install.bat
|
|
||||||
|
|
||||||
# Test files
|
|
||||||
|
|
||||||
_test_
|
|
||||||
_Test_
|
|
||||||
|
|
||||||
# Virtual environments
|
|
||||||
|
|
||||||
venv/
|
|
||||||
env/
|
|
||||||
.venv/
|
|
||||||
|
|
||||||
# OS generated files
|
|
||||||
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
54
CHANGELOG.md
54
CHANGELOG.md
@@ -5,6 +5,60 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.4.0] - 2025-08-05
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **HLG Transfer Characteristics Preservation**: Enhanced video muxing to preserve HLG color metadata
|
||||||
|
- Added automatic detection of HLG video tracks during muxing process
|
||||||
|
- Implemented `--color-transfer-characteristics 0:18` argument for mkvmerge when processing HLG content
|
||||||
|
- Prevents incorrect conversion from HLG (18) to BT.2020 (14) transfer characteristics
|
||||||
|
- Ensures proper HLG playback support on compatible hardware without manual editing
|
||||||
|
- **Original Language Support**: Enhanced language selection with 'orig' keyword support
|
||||||
|
- Added support for 'orig' language selector for both video and audio tracks
|
||||||
|
- Automatically detects and uses the title's original language when 'orig' is specified
|
||||||
|
- Improved language processing logic with better duplicate handling
|
||||||
|
- Enhanced help text to document original language selection usage
|
||||||
|
- **Forced Subtitle Support**: Added option to include forced subtitle tracks
|
||||||
|
- New functionality to download and include forced subtitle tracks alongside regular subtitles
|
||||||
|
- **WebVTT Subtitle Filtering**: Enhanced subtitle processing capabilities
|
||||||
|
- Added filtering for unwanted cues in WebVTT subtitles
|
||||||
|
- Improved subtitle quality by removing unnecessary metadata
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **DRM Track Decryption**: Improved DRM decryption track selection logic
|
||||||
|
- Enhanced `get_drm_for_cdm()` method usage for better DRM-CDM matching
|
||||||
|
- Added warning messages when no matching DRM is found for tracks
|
||||||
|
- Improved error handling and logging for DRM decryption failures
|
||||||
|
- **Series Tree Representation**: Enhanced episode tree display formatting
|
||||||
|
- Updated series tree to show season breakdown with episode counts
|
||||||
|
- Improved visual representation with "S{season}({count})" format
|
||||||
|
- Better organization of series information in console output
|
||||||
|
- **Hybrid Processing UI**: Enhanced extraction and conversion processes
|
||||||
|
- Added dynamic spinning bars to follow the rest of the codebase design
|
||||||
|
- Improved visual feedback during hybrid HDR processing operations
|
||||||
|
- **Track Selection Logic**: Enhanced multi-track selection capabilities
|
||||||
|
- Fixed track selection to support combining -V, -A, -S flags properly
|
||||||
|
- Improved flexibility in selecting multiple track types simultaneously
|
||||||
|
- **Service Subtitle Support**: Added configuration for services without subtitle support
|
||||||
|
- Services can now indicate if they don't support subtitle downloads
|
||||||
|
- Prevents unnecessary subtitle download attempts for unsupported services
|
||||||
|
- **Update Checker**: Enhanced update checking logic and cache handling
|
||||||
|
- Improved rate limiting and caching mechanisms for update checks
|
||||||
|
- Better performance and reduced API calls to GitHub
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **PlayReady KID Extraction**: Enhanced KID extraction from PSSH data
|
||||||
|
- Added base64 support and XML parsing for better KID detection
|
||||||
|
- Fixed issue where only one KID was being extracted for certain services
|
||||||
|
- Improved multi-KID support for PlayReady protected content
|
||||||
|
- **Dolby Vision Detection**: Improved DV codec detection across all formats
|
||||||
|
- Fixed detection of dvhe.05.06 codec which was not being recognized correctly
|
||||||
|
- Enhanced detection logic in Episode and Movie title classes
|
||||||
|
- Better support for various Dolby Vision codec variants
|
||||||
|
|
||||||
## [1.3.0] - 2025-08-03
|
## [1.3.0] - 2025-08-03
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
78
Dockerfile
78
Dockerfile
@@ -1,78 +0,0 @@
|
|||||||
FROM python:3.12-slim
|
|
||||||
|
|
||||||
# Set environment variables to reduce image size
|
|
||||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
|
||||||
PYTHONUNBUFFERED=1 \
|
|
||||||
UV_CACHE_DIR=/tmp/uv-cache
|
|
||||||
|
|
||||||
# Add container metadata
|
|
||||||
LABEL org.opencontainers.image.description="Docker image for Unshackle with all required dependencies for downloading media content"
|
|
||||||
|
|
||||||
# Install base dependencies
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
wget \
|
|
||||||
gnupg \
|
|
||||||
git \
|
|
||||||
curl \
|
|
||||||
build-essential \
|
|
||||||
cmake \
|
|
||||||
pkg-config \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Set up repos for mkvtools and bullseye for ccextractor
|
|
||||||
RUN wget -O /etc/apt/keyrings/gpg-pub-moritzbunkus.gpg https://mkvtoolnix.download/gpg-pub-moritzbunkus.gpg \
|
|
||||||
&& echo "deb [signed-by=/etc/apt/keyrings/gpg-pub-moritzbunkus.gpg] https://mkvtoolnix.download/debian/ bookworm main" >> /etc/apt/sources.list \
|
|
||||||
&& echo "deb-src [signed-by=/etc/apt/keyrings/gpg-pub-moritzbunkus.gpg] https://mkvtoolnix.download/debian/ bookworm main" >> /etc/apt/sources.list \
|
|
||||||
&& echo "deb http://ftp.debian.org/debian bullseye main" >> /etc/apt/sources.list
|
|
||||||
|
|
||||||
# Install all dependencies from apt
|
|
||||||
RUN apt-get update && apt-get install -y \
|
|
||||||
ffmpeg \
|
|
||||||
ccextractor \
|
|
||||||
mkvtoolnix \
|
|
||||||
aria2 \
|
|
||||||
libmediainfo-dev \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Install shaka packager
|
|
||||||
RUN wget https://github.com/shaka-project/shaka-packager/releases/download/v2.6.1/packager-linux-x64 \
|
|
||||||
&& chmod +x packager-linux-x64 \
|
|
||||||
&& mv packager-linux-x64 /usr/local/bin/packager
|
|
||||||
|
|
||||||
# Install N_m3u8DL-RE
|
|
||||||
RUN wget https://github.com/nilaoda/N_m3u8DL-RE/releases/download/v0.3.0-beta/N_m3u8DL-RE_v0.3.0-beta_linux-x64_20241203.tar.gz \
|
|
||||||
&& tar -xzf N_m3u8DL-RE_v0.3.0-beta_linux-x64_20241203.tar.gz \
|
|
||||||
&& mv N_m3u8DL-RE /usr/local/bin/ \
|
|
||||||
&& chmod +x /usr/local/bin/N_m3u8DL-RE \
|
|
||||||
&& rm N_m3u8DL-RE_v0.3.0-beta_linux-x64_20241203.tar.gz
|
|
||||||
|
|
||||||
# Create binaries directory and add symlinks for all required executables
|
|
||||||
RUN mkdir -p /app/binaries && \
|
|
||||||
ln -sf /usr/bin/ffprobe /app/binaries/ffprobe && \
|
|
||||||
ln -sf /usr/bin/ffmpeg /app/binaries/ffmpeg && \
|
|
||||||
ln -sf /usr/bin/mkvmerge /app/binaries/mkvmerge && \
|
|
||||||
ln -sf /usr/local/bin/N_m3u8DL-RE /app/binaries/N_m3u8DL-RE && \
|
|
||||||
ln -sf /usr/local/bin/packager /app/binaries/packager && \
|
|
||||||
ln -sf /usr/local/bin/packager /usr/local/bin/shaka-packager && \
|
|
||||||
ln -sf /usr/local/bin/packager /usr/local/bin/packager-linux-x64
|
|
||||||
|
|
||||||
# Install uv
|
|
||||||
RUN pip install --no-cache-dir uv
|
|
||||||
|
|
||||||
# Set working directory
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy dependency files and README (required by pyproject.toml)
|
|
||||||
COPY pyproject.toml uv.lock README.md ./
|
|
||||||
|
|
||||||
# Copy source code first
|
|
||||||
COPY unshackle/ ./unshackle/
|
|
||||||
|
|
||||||
# Install dependencies with uv (including the project itself)
|
|
||||||
RUN uv sync --frozen --no-dev
|
|
||||||
|
|
||||||
# Set entrypoint to allow passing commands directly to unshackle
|
|
||||||
ENTRYPOINT ["uv", "run", "unshackle"]
|
|
||||||
CMD ["-h"]
|
|
||||||
39
README.md
39
README.md
@@ -42,45 +42,6 @@ uv tool install git+https://github.com/unshackle-dl/unshackle.git
|
|||||||
uvx unshackle --help # or just `unshackle` once PATH updated
|
uvx unshackle --help # or just `unshackle` once PATH updated
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docker Installation
|
|
||||||
|
|
||||||
Run unshackle using our pre-built Docker image from GitHub Container Registry:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run with default help command
|
|
||||||
docker run --rm ghcr.io/unshackle-dl/unshackle:latest
|
|
||||||
|
|
||||||
# Check environment dependencies
|
|
||||||
docker run --rm ghcr.io/unshackle-dl/unshackle:latest env check
|
|
||||||
|
|
||||||
# Download content (mount directories for persistent data)
|
|
||||||
docker run --rm \
|
|
||||||
-v "$(pwd)/unshackle/downloads:/app/downloads" \
|
|
||||||
-v "$(pwd)/unshackle/cookies:/app/unshackle/cookies" \
|
|
||||||
-v "$(pwd)/unshackle/services:/app/unshackle/services" \
|
|
||||||
-v "$(pwd)/unshackle/WVDs:/app/unshackle/WVDs" \
|
|
||||||
-v "$(pwd)/unshackle/PRDs:/app/unshackle/PRDs" \
|
|
||||||
-v "$(pwd)/unshackle/unshackle.yaml:/app/unshackle.yaml" \
|
|
||||||
ghcr.io/unshackle-dl/unshackle:latest dl SERVICE_NAME CONTENT_ID
|
|
||||||
|
|
||||||
# Run interactively for configuration
|
|
||||||
docker run --rm -it \
|
|
||||||
-v "$(pwd)/unshackle/cookies:/app/unshackle/cookies" \
|
|
||||||
-v "$(pwd)/unshackle/services:/app/unshackle/services" \
|
|
||||||
-v "$(pwd)/unshackle.yaml:/app/unshackle.yaml" \
|
|
||||||
ghcr.io/unshackle-dl/unshackle:latest cfg
|
|
||||||
```
|
|
||||||
|
|
||||||
**Alternative: Build locally**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Clone and build your own image
|
|
||||||
git clone https://github.com/unshackle-dl/unshackle.git
|
|
||||||
cd unshackle
|
|
||||||
docker build -t unshackle .
|
|
||||||
docker run --rm unshackle env check
|
|
||||||
```
|
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> After installation, you may need to add the installation path to your PATH environment variable if prompted.
|
> After installation, you may need to add the installation path to your PATH environment variable if prompted.
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "unshackle"
|
name = "unshackle"
|
||||||
version = "1.3.0"
|
version = "1.4.0"
|
||||||
description = "Modular Movie, TV, and Music Archival Software."
|
description = "Modular Movie, TV, and Music Archival Software."
|
||||||
authors = [{ name = "unshackle team" }]
|
authors = [{ name = "unshackle team" }]
|
||||||
requires-python = ">=3.10,<3.13"
|
requires-python = ">=3.10,<3.13"
|
||||||
|
|||||||
@@ -541,7 +541,12 @@ class dl:
|
|||||||
events.subscribe(events.Types.TRACK_REPACKED, service.on_track_repacked)
|
events.subscribe(events.Types.TRACK_REPACKED, service.on_track_repacked)
|
||||||
events.subscribe(events.Types.TRACK_MULTIPLEX, service.on_track_multiplex)
|
events.subscribe(events.Types.TRACK_MULTIPLEX, service.on_track_multiplex)
|
||||||
|
|
||||||
if no_subs:
|
if hasattr(service, "NO_SUBTITLES") and service.NO_SUBTITLES:
|
||||||
|
console.log("Skipping subtitles - service does not support subtitle downloads")
|
||||||
|
no_subs = True
|
||||||
|
s_lang = None
|
||||||
|
title.tracks.subtitles = []
|
||||||
|
elif no_subs:
|
||||||
console.log("Skipped subtitles as --no-subs was used...")
|
console.log("Skipped subtitles as --no-subs was used...")
|
||||||
s_lang = None
|
s_lang = None
|
||||||
title.tracks.subtitles = []
|
title.tracks.subtitles = []
|
||||||
@@ -906,6 +911,7 @@ class dl:
|
|||||||
while (
|
while (
|
||||||
not title.tracks.subtitles
|
not title.tracks.subtitles
|
||||||
and not no_subs
|
and not no_subs
|
||||||
|
and not (hasattr(service, "NO_SUBTITLES") and service.NO_SUBTITLES)
|
||||||
and not video_only
|
and not video_only
|
||||||
and len(title.tracks.videos) > video_track_n
|
and len(title.tracks.videos) > video_track_n
|
||||||
and any(
|
and any(
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "1.3.0"
|
__version__ = "1.4.0"
|
||||||
|
|||||||
@@ -39,17 +39,23 @@ class PlayReady:
|
|||||||
if not isinstance(pssh, PSSH):
|
if not isinstance(pssh, PSSH):
|
||||||
raise TypeError(f"Expected pssh to be a {PSSH}, not {pssh!r}")
|
raise TypeError(f"Expected pssh to be a {PSSH}, not {pssh!r}")
|
||||||
|
|
||||||
kids: list[UUID] = []
|
if pssh_b64:
|
||||||
for header in pssh.wrm_headers:
|
kids = self._extract_kids_from_pssh_b64(pssh_b64)
|
||||||
try:
|
else:
|
||||||
signed_ids, _, _, _ = header.read_attributes()
|
kids = []
|
||||||
except Exception:
|
|
||||||
continue
|
# Extract KIDs using pyplayready's method (may miss some KIDs)
|
||||||
for signed_id in signed_ids:
|
if not kids:
|
||||||
|
for header in pssh.wrm_headers:
|
||||||
try:
|
try:
|
||||||
kids.append(UUID(bytes_le=base64.b64decode(signed_id.value)))
|
signed_ids, _, _, _ = header.read_attributes()
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
|
for signed_id in signed_ids:
|
||||||
|
try:
|
||||||
|
kids.append(UUID(bytes_le=base64.b64decode(signed_id.value)))
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
if kid:
|
if kid:
|
||||||
if isinstance(kid, str):
|
if isinstance(kid, str):
|
||||||
@@ -72,6 +78,66 @@ class PlayReady:
|
|||||||
if pssh_b64:
|
if pssh_b64:
|
||||||
self.data.setdefault("pssh_b64", pssh_b64)
|
self.data.setdefault("pssh_b64", pssh_b64)
|
||||||
|
|
||||||
|
def _extract_kids_from_pssh_b64(self, pssh_b64: str) -> list[UUID]:
|
||||||
|
"""Extract all KIDs from base64-encoded PSSH data."""
|
||||||
|
try:
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
# Decode the PSSH
|
||||||
|
pssh_bytes = base64.b64decode(pssh_b64)
|
||||||
|
|
||||||
|
# Try to find XML in the PSSH data
|
||||||
|
# PlayReady PSSH usually has XML embedded in it
|
||||||
|
pssh_str = pssh_bytes.decode("utf-16le", errors="ignore")
|
||||||
|
|
||||||
|
# Find WRMHEADER
|
||||||
|
xml_start = pssh_str.find("<WRMHEADER")
|
||||||
|
if xml_start == -1:
|
||||||
|
# Try UTF-8
|
||||||
|
pssh_str = pssh_bytes.decode("utf-8", errors="ignore")
|
||||||
|
xml_start = pssh_str.find("<WRMHEADER")
|
||||||
|
|
||||||
|
if xml_start != -1:
|
||||||
|
clean_xml = pssh_str[xml_start:]
|
||||||
|
xml_end = clean_xml.find("</WRMHEADER>") + len("</WRMHEADER>")
|
||||||
|
clean_xml = clean_xml[:xml_end]
|
||||||
|
|
||||||
|
root = ET.fromstring(clean_xml)
|
||||||
|
ns = {"pr": "http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader"}
|
||||||
|
|
||||||
|
kids = []
|
||||||
|
|
||||||
|
# Extract from CUSTOMATTRIBUTES/KIDS
|
||||||
|
kid_elements = root.findall(".//pr:CUSTOMATTRIBUTES/pr:KIDS/pr:KID", ns)
|
||||||
|
for kid_elem in kid_elements:
|
||||||
|
value = kid_elem.get("VALUE")
|
||||||
|
if value:
|
||||||
|
try:
|
||||||
|
kid_bytes = base64.b64decode(value + "==")
|
||||||
|
kid_uuid = UUID(bytes_le=kid_bytes)
|
||||||
|
kids.append(kid_uuid)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Also get individual KID
|
||||||
|
individual_kids = root.findall(".//pr:DATA/pr:KID", ns)
|
||||||
|
for kid_elem in individual_kids:
|
||||||
|
if kid_elem.text:
|
||||||
|
try:
|
||||||
|
kid_bytes = base64.b64decode(kid_elem.text.strip() + "==")
|
||||||
|
kid_uuid = UUID(bytes_le=kid_bytes)
|
||||||
|
if kid_uuid not in kids:
|
||||||
|
kids.append(kid_uuid)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return kids
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_track(cls, track: AnyTrack, session: Optional[Session] = None) -> PlayReady:
|
def from_track(cls, track: AnyTrack, session: Optional[Session] = None) -> PlayReady:
|
||||||
if not session:
|
if not session:
|
||||||
|
|||||||
@@ -870,7 +870,18 @@ class Subtitle(Track):
|
|||||||
elif sdh_method == "filter-subs":
|
elif sdh_method == "filter-subs":
|
||||||
# Force use of filter-subs
|
# Force use of filter-subs
|
||||||
sub = Subtitles(self.path)
|
sub = Subtitles(self.path)
|
||||||
sub.filter(rm_fonts=True, rm_ast=True, rm_music=True, rm_effects=True, rm_names=True, rm_author=True)
|
try:
|
||||||
|
sub.filter(rm_fonts=True, rm_ast=True, rm_music=True, rm_effects=True, rm_names=True, rm_author=True)
|
||||||
|
except ValueError as e:
|
||||||
|
if "too many values to unpack" in str(e):
|
||||||
|
# Retry without name removal if the error is due to multiple colons in time references
|
||||||
|
# This can happen with lines like "at 10:00 and 2:00"
|
||||||
|
sub = Subtitles(self.path)
|
||||||
|
sub.filter(
|
||||||
|
rm_fonts=True, rm_ast=True, rm_music=True, rm_effects=True, rm_names=False, rm_author=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
sub.save()
|
sub.save()
|
||||||
return
|
return
|
||||||
elif sdh_method == "auto":
|
elif sdh_method == "auto":
|
||||||
@@ -906,7 +917,18 @@ class Subtitle(Track):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
sub = Subtitles(self.path)
|
sub = Subtitles(self.path)
|
||||||
sub.filter(rm_fonts=True, rm_ast=True, rm_music=True, rm_effects=True, rm_names=True, rm_author=True)
|
try:
|
||||||
|
sub.filter(rm_fonts=True, rm_ast=True, rm_music=True, rm_effects=True, rm_names=True, rm_author=True)
|
||||||
|
except ValueError as e:
|
||||||
|
if "too many values to unpack" in str(e):
|
||||||
|
# Retry without name removal if the error is due to multiple colons in time references
|
||||||
|
# This can happen with lines like "at 10:00 and 2:00"
|
||||||
|
sub = Subtitles(self.path)
|
||||||
|
sub.filter(
|
||||||
|
rm_fonts=True, rm_ast=True, rm_music=True, rm_effects=True, rm_names=False, rm_author=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
sub.save()
|
sub.save()
|
||||||
|
|
||||||
def reverse_rtl(self) -> None:
|
def reverse_rtl(self) -> None:
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class EXAMPLE(Service):
|
|||||||
|
|
||||||
TITLE_RE = r"^(?:https?://?domain\.com/details/)?(?P<title_id>[^/]+)"
|
TITLE_RE = r"^(?:https?://?domain\.com/details/)?(?P<title_id>[^/]+)"
|
||||||
GEOFENCE = ("US", "UK")
|
GEOFENCE = ("US", "UK")
|
||||||
|
NO_SUBTITLES = True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@click.command(name="EXAMPLE", short_help="https://domain.com")
|
@click.command(name="EXAMPLE", short_help="https://domain.com")
|
||||||
|
|||||||
Reference in New Issue
Block a user