added message cleanup and some better logic
This commit is contained in:
TylerCG 2026-02-22 22:24:07 -05:00
parent 13912636ea
commit 8d3aa03d72
8 changed files with 2156 additions and 48 deletions

101
LOGGING_STRUCTURE.md Normal file
View File

@ -0,0 +1,101 @@
# Structured Logging with Media Context
## Overview
The conversion system now uses **structured JSON logging** to enable organized analysis and filtering of conversion results by media type, show name, season, and episode.
## Terminal vs Log Output
- **Terminal Output**: Clean, human-readable print statements (VIDEO/AUDIO/PROGRESS sections)
- **Log Output**: Rich structured JSON with full media context for programmatic analysis
## Media Context Fields
Extracted automatically from file path structure:
```python
{
"video_filename": "episode01.mkv",
"media_type": "tv", # "tv", "anime", "movie", or "other"
"show_name": "Breaking Bad",
"season": "01", # Optional (TV/anime only)
"episode": "01" # Optional (TV/anime only)
}
```
## Usage Examples
### Path Structure Recognition
**TV Show**:
```
P:\tv\Breaking Bad\season01\episode01.mkv
→ media_type: "tv", show_name: "Breaking Bad", season: "01", episode: "01"
```
**Anime**:
```
P:\anime\Demon Slayer\season02\e12.mkv
→ media_type: "anime", show_name: "Demon Slayer", season: "02", episode: "12"
```
**Movie**:
```
P:\movies\Inception.mkv
→ media_type: "movie", show_name: "Inception"
```
## Log Output Format
JSON logs contain both the message and media context:
```json
{
"timestamp": "2026-02-22 15:30:45",
"level": "INFO",
"message": "✅ CONVERSION COMPLETE: episode01[EHX].mkv",
"video_filename": "episode01.mkv",
"media_type": "tv",
"show_name": "Breaking Bad",
"season": "01",
"episode": "01",
"method": "CQ",
"original_size_mb": 4096.5,
"output_size_mb": 1843.2,
"reduction_pct": 55.0
}
```
## Filtering Logs Later
You can parse the JSON logs to group by show/season/episode:
```python
import json
# Filter all Breaking Bad conversions
with open("logs/conversion.log") as f:
for line in f:
entry = json.loads(line)
if entry.get("show_name") == "Breaking Bad":
print(f"S{entry['season']}E{entry['episode']}: {entry['reduction_pct']}% reduction")
```
## Current Implementation
**Files Updated**:
- `core/process_manager.py`:
- Added `get_media_context()` function to parse file paths
- Extracts media context once per file processing
- Passes context to all logging calls via `extra={}` parameter
- `core/logger_helper.py`:
- JsonFormatter automatically includes all extra fields in output
- Added `log_event()` helper for consistent structured logging
## Best Practices
1. Always call `get_media_context()` once per file
2. Pass result to all logging calls for that file: `logger.info(msg, extra=media_context)`
3. For additional context: `logger.info(msg, extra={**media_context, "custom_field": value})`
4. Parse logs with JSON reader for reliable data extraction

View File

@ -1645,3 +1645,111 @@ tv,Taskmaster,Taskmaster - S00E54 - Taskmasters New Year Treat h265 AAC WEBRi
tv,Taskmaster,Taskmaster - S00E73 - Taskmasters New Year Treat 2022 - Basic Recipe 28 h265 AAC WEBRip-1080p EHX.mkv,695.65,695.65,100.0,1920x1080,1920x1080,1,28,CQ
tv,Taskmaster,Taskmaster - S00E85 - Taskmaster's New Year Treat 2023 - That's a Swizz h265 AAC WEBRip-1080p EHX.mkv,642.96,642.96,100.0,1920x1080,1920x1080,1,28,CQ
tv,Taskmaster,Taskmaster - S00E98 - Taskmaster's New Year Treat 2024 - Huh h265 AAC WEBRip-1080p EHX.mkv,707.51,707.51,100.0,1920x1080,1920x1080,1,28,CQ
tv,Tulsa King,"Tulsa King - S01E01 - Go West, Old Man x265 EAC3 Bluray-1080p t3nzin - [EHX].mkv",1814.72,488.3,26.9,1920x960,1920x960,1,28,CQ
tv,Tulsa King,Tulsa King - S01E02 - Center of the Universe x265 EAC3 Bluray-1080p t3nzin - [EHX].mkv,1626.91,447.12,27.5,1920x804,1920x804,1,28,CQ
tv,Tulsa King,Tulsa King - S01E03 - Caprice x265 EAC3 Bluray-1080p t3nzin - [EHX].mkv,1703.8,511.73,30.0,1920x804,1920x804,1,28,CQ
tv,Tulsa King,Tulsa King - S01E04 - Visitation Place x265 EAC3 Bluray-1080p t3nzin - [EHX].mkv,1713.64,457.66,26.7,1920x804,1920x804,1,28,CQ
tv,Tulsa King,Tulsa King - S01E05 - Token Joe x265 EAC3 Bluray-1080p t3nzin - [EHX].mkv,1584.63,428.51,27.0,1920x960,1920x960,1,28,CQ
tv,Tulsa King,Tulsa King - S01E06 - Stable x265 EAC3 Bluray-1080p t3nzin - [EHX].mkv,1730.31,432.13,25.0,1920x956,1920x956,1,28,CQ
tv,Tulsa King,Tulsa King - S01E07 - Warr Acres x265 EAC3 Bluray-1080p t3nzin - [EHX].mkv,1856.49,450.09,24.2,1920x960,1920x960,1,28,CQ
tv,Tulsa King,Tulsa King - S01E08 - Adobe Walls x265 EAC3 Bluray-1080p t3nzin - [EHX].mkv,1569.86,439.54,28.0,1920x804,1920x804,1,28,CQ
tv,Tulsa King,Tulsa King - S01E09 - Happy Trails x265 EAC3 Bluray-1080p t3nzin - [EHX].mkv,1608.93,502.05,31.2,1920x804,1920x804,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD - 01 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2133.69,340.32,15.9,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD - 02 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2000.63,316.87,15.8,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD - 03 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2388.71,327.7,13.7,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD - 04 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2298.9,358.96,15.6,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD - 05 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2224.88,276.85,12.4,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD - 06 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,1879.17,311.66,16.6,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD - 07 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2029.98,337.99,16.6,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD - 08 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2027.96,306.39,15.1,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD - 09 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,1916.23,324.79,16.9,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD - 10 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2184.08,328.82,15.1,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD - 11 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2481.42,418.22,16.9,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD - 12 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2305.83,326.33,14.2,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD NEW - 01 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,1977.67,287.94,14.6,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD NEW - 02 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,1981.07,285.77,14.4,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD NEW - 03 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2227.27,355.11,15.9,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD NEW - 04 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2304.24,342.7,14.9,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD NEW - 05 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2718.84,419.84,15.4,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD NEW - 06 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2364.74,342.99,14.5,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD NEW - 07 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,1992.49,308.29,15.5,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD NEW - 08 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,1986.14,316.75,15.9,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD NEW - 09 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,1980.58,297.66,15.0,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD NEW - 10 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,1903.95,282.62,14.8,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD NEW - 11 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2365.17,387.04,16.4,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD NEW - 12 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2371.71,389.02,16.4,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - 01 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2153.09,344.79,16.0,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - 02 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,1975.28,274.59,13.9,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - 03 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2157.15,307.58,14.3,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - 04 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2446.97,387.79,15.8,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - 05 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2075.37,291.7,14.1,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - 06 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,1917.51,292.27,15.2,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - 07 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2159.91,335.62,15.5,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - 08 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2525.01,394.22,15.6,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - 09 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2511.14,427.56,17.0,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - 10 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,1981.0,275.71,13.9,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - 11 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2376.41,402.42,16.9,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - 12 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2268.85,375.29,16.5,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD HERO - 00 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2144.67,333.13,15.5,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD HERO - 01 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,1965.41,307.32,15.6,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD HERO - 02 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,1859.7,319.66,17.2,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD HERO - 03 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,1948.02,311.6,16.0,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD HERO - 04 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2097.75,320.2,15.3,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD HERO - 05 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2531.16,367.06,14.5,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD HERO - 06 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2165.79,312.71,14.4,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD HERO - 07 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,1698.21,279.77,16.5,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD HERO - 08 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,1908.73,284.91,14.9,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD HERO - 09 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,1781.66,300.93,16.9,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD HERO - 10 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2280.0,372.33,16.3,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD HERO - 11 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2314.99,370.49,16.0,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD HERO - 12 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,1825.25,333.69,18.3,1920x1080,1920x1080,2,28,CQ
anime,High School D×D (2012),[IK] High School DxD HERO - NCED (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,203.33,39.91,19.6,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD HERO - NCOP 01 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,190.59,25.6,13.4,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD HERO - NCOP 02 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,193.2,26.99,14.0,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - 13 (OVA) (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2131.25,309.54,14.5,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - Special 01 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,238.41,25.52,10.7,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - Special 02 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,239.71,24.76,10.3,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - Special 03 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,275.15,30.81,11.2,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - Special 04 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,235.34,29.8,12.7,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - Special 05 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,312.28,37.7,12.1,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - Special 06 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,247.67,27.08,10.9,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - NCED (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,136.96,17.2,12.6,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - NCOP 01 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,157.29,26.65,16.9,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD BorN - NCOP 02 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,157.18,26.73,17.0,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD NEW - 13 (OVA) (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,1186.27,238.43,20.1,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD NEW - NCED 01 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,112.28,13.52,12.0,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD NEW - NCED 02 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,92.92,23.07,24.8,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD NEW - NCOP 01 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,187.62,30.65,16.3,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD NEW - NCOP 02 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,190.33,31.29,16.4,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD - 13 (OVA) (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,1558.46,250.68,16.1,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD - 14 (OVA) (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,2244.67,237.94,10.6,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD - Special 01 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,218.25,35.79,16.4,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD - Special 02 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,250.69,36.44,14.5,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD - Special 03 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,182.05,34.82,19.1,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD - Special 04 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,171.06,26.42,15.4,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD - Special 05 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,263.3,48.39,18.4,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD - Special 06 (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,156.18,22.79,14.6,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD - NCED (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,181.09,47.83,26.4,1920x1080,1920x1080,1,28,CQ
anime,High School D×D (2012),[IK] High School DxD - NCOP (BD 1920x1080 Hi10 FLAC) - [EHX].mkv,194.96,29.79,15.3,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,SHOSHIMIN - How to Become Ordinary - S01E01 - Sheep Costume x265 FLAC Bluray-1080p YURASUKA - [EHX].mkv,753.08,160.08,21.3,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,SHOSHIMIN - How to Become Ordinary - S01E02 - How to Make Delicious Hot Cocoa x265 FLAC Bluray-1080p YURASUKA - [EHX].mkv,782.89,147.31,18.8,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,SHOSHIMIN - How to Become Ordinary - S01E03 - Humpty Dumpty x265 FLAC Bluray-1080p YURASUKA - [EHX].mkv,706.75,146.46,20.7,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,SHOSHIMIN - How to Become Ordinary - S01E04 - Mind of a Lone Wolf x265 FLAC Bluray-1080p YURASUKA - [EHX].mkv,684.77,139.25,20.3,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,SHOSHIMIN - How to Become Ordinary - S01E05 - Berliner Mystery x265 FLAC Bluray-1080p YURASUKA - [EHX].mkv,762.44,135.78,17.8,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,SHOSHIMIN - How to Become Ordinary - S01E06 - But I Get to Keep Charlotte x265 FLAC Bluray-1080p YURASUKA - [EHX].mkv,769.93,149.39,19.4,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,SHOSHIMIN - How to Become Ordinary - S01E07 - Shake Half x265 FLAC Bluray-1080p YURASUKA - [EHX].mkv,738.83,144.63,19.6,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,"SHOSHIMIN - How to Become Ordinary - S01E08 - C'mere, You Want Some Free Candy x265 FLAC Bluray-1080p YURASUKA - [EHX].mkv",743.89,142.98,19.2,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,SHOSHIMIN - How to Become Ordinary - S01E09 - Sweet Memory (Part 1) x265 FLAC Bluray-1080p YURASUKA - [EHX].mkv,659.52,134.14,20.3,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,SHOSHIMIN - How to Become Ordinary - S01E10 - Sweet Memory (Part 2) x265 FLAC Bluray-1080p YURASUKA - [EHX].mkv,729.55,144.02,19.7,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,SHOSHIMIN - How to Become Ordinary - S02E01 - A Warm Winter (Part 1) x265 Opus Bluray-1080p YURASUKA - [EHX].mkv,1259.13,150.42,11.9,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,SHOSHIMIN - How to Become Ordinary - S02E02 - A Warm Winter (Part 2) x265 Opus Bluray-1080p YURASUKA - [EHX].mkv,1081.2,125.75,11.6,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,SHOSHIMIN - How to Become Ordinary - S02E03 - Hesitant Spring x265 Opus Bluray-1080p YURASUKA - [EHX].mkv,1051.26,126.74,12.1,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,SHOSHIMIN - How to Become Ordinary - S02E04 - Suspicious Summer (Part 1) x265 Opus Bluray-1080p YURASUKA - [EHX].mkv,1164.91,143.01,12.3,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,SHOSHIMIN - How to Become Ordinary - S02E05 - Suspicious Summer (Part 2) x265 Opus Bluray-1080p YURASUKA - [EHX].mkv,1209.37,140.26,11.6,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,SHOSHIMIN - How to Become Ordinary - S02E06 - Midsummer Night x265 Opus Bluray-1080p YURASUKA - [EHX].mkv,1145.83,128.41,11.2,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,SHOSHIMIN - How to Become Ordinary - S02E07 - Autumn Returns x265 Opus Bluray-1080p YURASUKA - [EHX].mkv,1187.86,135.27,11.4,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,SHOSHIMIN - How to Become Ordinary - S02E08 - Should We Really Have Met x265 Opus Bluray-1080p YURASUKA - [EHX].mkv,1189.66,131.28,11.0,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,SHOSHIMIN - How to Become Ordinary - S02E09 - Kobato-kun and Osanai-san x265 Opus Bluray-1080p YURASUKA - [EHX].mkv,1243.46,141.37,11.4,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,SHOSHIMIN - How to Become Ordinary - S02E10 - Please Water the Dried Flowers x265 Opus Bluray-1080p YURASUKA - [EHX].mkv,1171.12,134.31,11.5,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,SHOSHIMIN - How to Become Ordinary - S02E11 - The End of What Seemed Like a Golden Age x265 Opus Bluray-1080p YURASUKA - [EHX].mkv,1084.98,123.48,11.4,1920x1080,1920x1080,1,28,CQ
anime,SHOSHIMIN - How to Become Ordinary,SHOSHIMIN - How to Become Ordinary - S02E12 - Just Deserts x265 Opus Bluray-1080p YURASUKA - [EHX].mkv,1031.82,124.04,12.0,1920x1080,1920x1080,1,28,CQ

Can't render this file because it has a wrong number of fields in line 14.

View File

@ -15,16 +15,30 @@ def run_ffmpeg(input_file: Path, output_file: Path, cq: int, scale_width: int, s
method: str, bitrate_config: dict, encoder: str = "nvenc", subtitle_files: list = None, audio_language: str = None,
audio_filter_config: dict = None, test_mode: bool = False, strip_all_titles: bool = False, src_bit_depth: int = None, unforce_subs: bool = False, no_encode: bool = False):
"""
Run FFmpeg encode with comprehensive logging.
Execute FFmpeg encoding/re-muxing with structured console output.
Args:
src_bit_depth: Source video bit depth (8, 10, or 12). If provided, encoder will be auto-selected:
10+ bit HEVC NVENC, 8-bit AV1 NVENC
strip_all_titles: If True, strip all title metadata from all audio tracks
unforce_subs: If True, remove forced flag from all subtitle tracks
no_encode: If True, copy video/audio streams without encoding (only re-mux with subtitle processing)
input_file: Path to source video file
output_file: Path for encoded output file
cq: Quality value (0-63, lower=better) for CQ mode
scale_width/height: Target resolution dimensions
src_width/height: Source resolution dimensions
filter_flags: Scaling filter algorithm (lanczos, bicubic, etc)
audio_config: Audio bitrate configuration dict
method: Encoding method - "CQ" or "Bitrate"
bitrate_config: Bitrate/maxrate/bufsize configuration dict
encoder: Video codec - "hevc", "av1", or "nvenc"
subtitle_files: List of external subtitle file paths (if any)
audio_language: ISO 639-2 language code to tag audio (e.g., "eng", "spa")
audio_filter_config: Audio filtering/selection configuration
test_mode: If True, only encode first 15 minutes, don't move files
strip_all_titles: If True, strip title metadata from all audio tracks
src_bit_depth: Source bit depth (8/10/12) for encoder auto-selection
unforce_subs: If True, remove forced flag from subtitle tracks
no_encode: If True, copy video/audio (re-mux only, skip encoding)
Returns tuple: (orig_size, out_size, reduction_ratio)
Returns:
tuple: (orig_size_bytes, output_size_bytes, reduction_ratio)
"""
streams = get_audio_streams(input_file)

View File

@ -7,6 +7,7 @@ from datetime import datetime
class JsonFormatter(logging.Formatter):
"""
Custom JSON log formatter for structured logging.
Outputs rich JSON objects with context for programmatic parsing and analysis.
"""
def format(self, record: logging.LogRecord) -> str:
log_object = {
@ -14,15 +15,16 @@ class JsonFormatter(logging.Formatter):
"level": record.levelname,
"message": record.getMessage(),
"module": record.module,
"funcName": record.funcName,
"function": record.funcName,
"line": record.lineno,
}
# Include any extra fields added via logger.info("msg", extra={...})
# This allows passing structured context: logger.info("msg", extra={"file": "video.mkv", "size": 1024})
if hasattr(record, "extra") and isinstance(record.extra, dict):
log_object.update(record.extra)
# Include exception info if present
# Include exception info if present (for error tracking)
if record.exc_info:
log_object["exception"] = self.formatException(record.exc_info)
@ -30,7 +32,18 @@ class JsonFormatter(logging.Formatter):
def setup_logger(log_folder: Path, log_file_name: str = "conversion.log", level=logging.INFO) -> logging.Logger:
"""
Sets up a logger that prints to console and writes to a rotating JSON log file.
Setup logger with structured JSON file output and disabled console output.
Output:
- File (logs/conversion.log): JSON format with full context for programmatic parsing
- Console: Disabled (all user output handled via print() for clean terminal UI)
Usage:
logger.info("Processing complete", extra={
"file": "video.mkv",
"size_mb": 1024,
"duration_sec": 3600
})
"""
log_folder.mkdir(parents=True, exist_ok=True)
log_file = log_folder / log_file_name
@ -46,10 +59,11 @@ def setup_logger(log_folder: Path, log_file_name: str = "conversion.log", level=
)
json_formatter = JsonFormatter()
# Console handler (human-readable)
# Console handler (disabled - use print() for user-facing output)
# This prevents duplicate/ugly output mixing with terminal UI
console_handler = logging.StreamHandler()
console_handler.setFormatter(text_formatter)
console_handler.setLevel(level)
console_handler.setLevel(logging.CRITICAL + 1) # Effectively disable (above CRITICAL)
# File handler (JSON logs)
file_handler = RotatingFileHandler(log_file, maxBytes=5 * 1024 * 1024, backupCount=3, encoding="utf-8")
@ -66,8 +80,14 @@ def setup_logger(log_folder: Path, log_file_name: str = "conversion.log", level=
def setup_failure_logger(log_folder: Path) -> logging.Logger:
"""
Setup a dedicated logger for encoding failures.
Returns a logger that writes to logs/failure.log
Setup dedicated failure logger for encoding/processing failures.
Output:
- File (logs/failure.log): Simple text format with timestamp and failure message
- Use this for tracking files that failed processing for later analysis
Usage:
failure_logger.warning(f"{file.name} | CQ mode failed: size threshold not met (95%)")
"""
log_folder.mkdir(parents=True, exist_ok=True)
log_file = log_folder / "failure.log"
@ -94,3 +114,21 @@ def setup_failure_logger(log_folder: Path) -> logging.Logger:
logger.propagate = False
return logger
def log_event(logger: logging.Logger, level: str, message: str, **context):
"""
Log a structured event with context fields.
Args:
logger: Logger instance
level: Log level ("debug", "info", "warning", "error")
message: Main message text
**context: Additional context fields (file, size, duration, etc)
Example:
log_event(logger, "info", "Encoding complete",
file="video.mkv", size_mb=1024, method="CQ", reduction_pct=45)
"""
log_func = getattr(logger, level.lower(), logger.info)
log_func(message, extra=context)

View File

@ -3,6 +3,7 @@
import csv
import os
import re
import shutil
import subprocess
import time
@ -48,6 +49,82 @@ def get_default_cq(folder: Path, config: dict, resolution: str, encoder: str = "
return cq_config.get(key, 28) # Default fallback to 28
def get_media_context(file: Path, root_folder: Path) -> dict:
"""
Extract media context from file path for structured logging.
Parses directory structure to identify show name, media type (TV/Movie),
season/episode numbers for grouping logs later.
Args:
file: File path to analyze
root_folder: Root processing folder to use as reference
Returns:
dict with keys: media_type, show_name, season (optional), episode (optional), video_filename
Examples:
P:\\tv\\Breaking Bad\\season01\\episode01.mkv
{"media_type": "tv", "show_name": "Breaking Bad", "season": "01", "episode": "01"}
P:\\movies\\Inception.mkv
{"media_type": "movie", "show_name": "Inception"}
"""
parts = file.parts
root_parts = root_folder.parts
context = {
"video_filename": file.name,
"media_type": None,
"show_name": None,
"season": None,
"episode": None
}
# Find where media type (tv/movie/anime) appears in path
path_lower = str(file).lower()
if "\\tv\\" in path_lower or "/tv/" in path_lower:
context["media_type"] = "tv"
elif "\\anime\\" in path_lower or "/anime/" in path_lower:
context["media_type"] = "anime"
elif "\\movies\\" in path_lower or "/movies/" in path_lower:
context["media_type"] = "movie"
else:
# Default to movie if path structure unclear
context["media_type"] = "other"
# Extract show name (directory immediately after media type)
try:
for i, part in enumerate(parts):
part_lower = part.lower()
if part_lower in ("tv", "anime", "movies"):
# Next part is show name
if i + 1 < len(parts):
context["show_name"] = parts[i + 1]
# For TV/anime, check if there's a season folder
if context["media_type"] in ("tv", "anime") and i + 2 < len(parts):
season_part = parts[i + 2].lower()
# Pattern: "season01", "s01", "season 1", etc.
import re
season_match = re.search(r's(?:eason)?\s*(\d+)', season_part)
if season_match:
context["season"] = season_match.group(1).zfill(2)
# Extract episode from filename
# Pattern: "e01", "episode01", "01", etc.
filename_lower = file.stem.lower()
ep_match = re.search(r'e(?:pisode)?\s*(\d+)', filename_lower)
if ep_match:
context["episode"] = ep_match.group(1).zfill(2)
break
except Exception as e:
logger.warning(f"Could not parse media context from {file}: {e}")
return context
def _cleanup_temp_files(temp_input: Path, temp_output: Path):
"""Helper function to clean up temporary input and output files."""
try:
@ -65,6 +142,39 @@ def _cleanup_temp_files(temp_input: Path, temp_output: Path):
logger.warning(f"Could not delete temp output {temp_output.name}: {e}")
def should_skip_file(file: Path, no_encode: bool, unforce_subs: bool, force_process: bool, ignore_tags: list, travel_output_folder: Path) -> tuple:
"""
Determine if a file should be skipped from processing based on multiple criteria.
Skip conditions (in order):
1. If --no-encode + --unforce-subs: skip if file has no forced subtitles
2. If --force-process NOT set: skip if filename contains any ignore_tags (e.g., [EHX])
3. Travel mode always processes files (overrides ignore tags)
Args:
file: File path to check
no_encode: True if --no-encode flag is set
unforce_subs: True if --unforce-subs flag is set
force_process: True if --force-process flag is set (bypass ignore_tags)
ignore_tags: List of filename tags to skip (from config)
travel_output_folder: If set, travel mode is active (process all files)
Returns:
tuple: (should_skip: bool, reason: str or None)
"""
# Check for forced subtitles if using --no-encode + --unforce-subs
if no_encode and unforce_subs:
if not has_forced_subtitles(file):
return True, "no forced subtitles found (--no-encode + --unforce-subs)"
# Skip files with ignore tags (unless force_process is enabled)
# In travel mode, don't skip files based on tags
if not force_process and not travel_output_folder and any(tag.lower() in file.name.lower() for tag in ignore_tags):
return True, "matches ignore tags"
return False, None
def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str, config: dict, tracker_file: Path, test_mode: bool = False, audio_language: str = None, filter_audio: bool = None, audio_select: str = None, encoder: str = "hevc", strip_all_titles: bool = False, travel_output_folder: Path = None, unforce_subs: bool = False, no_encode: bool = False, force_process: bool = False, replace_file: bool = False, wait_seconds: int = 0):
"""
Process all video files in folder with appropriate encoding settings.
@ -139,20 +249,18 @@ def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str,
skipped_count = 0
for file in folder.rglob("*"):
# Skip hidden files/directories (starting with . or ._)
if file.name.startswith('.') or file.name.startswith('._'):
continue
if file.suffix.lower() not in extensions:
continue
# Check if using --no-encode with --unforce-subs: skip files with no forced subs
if no_encode and unforce_subs:
if not has_forced_subtitles(file):
logger.info(f"Skipping {file.name}: no forced subtitles found (--no-encode + --unforce-subs)")
print(f"⏭️ Skipping {file.name}: no forced subtitles found")
skipped_count += 1
continue
# Skip files with ignore tags (unless force_process is enabled)
# In travel mode, don't skip files based on tags - we process everything
if not force_process and not travel_output_folder and any(tag.lower() in file.name.lower() for tag in ignore_tags):
# Check if file should be skipped
should_skip, skip_reason = should_skip_file(file, no_encode, unforce_subs, force_process, ignore_tags, travel_output_folder)
if should_skip:
logger.info(f"Skipping {file.name}: {skip_reason}")
print(f"⏭️ Skipping {file.name}: {skip_reason}")
skipped_count += 1
continue
@ -161,8 +269,11 @@ def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str,
logger.info(f"Skipped {skipped_count} file(s)")
skipped_count = 0
# Extract media context for structured logging
media_context = get_media_context(file, folder)
print("="*60)
logger.info(f"Processing: {file.name}")
logger.info(f"Processing: {file.name}", extra=media_context)
print(f"📁 Processing: {file.name}")
temp_input = (processing_folder / file.name).resolve()
@ -265,7 +376,7 @@ def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str,
content_cq = encoder_cq_config.get(cq_key, 32)
file_cq = cq if cq is not None else content_cq
# Always output as .mkv (AV1 video codec) with [EHX] suffix
# Output file with suffix in processing folder (always .mkv container)
temp_output = (processing_folder / f"{file.stem}{suffix}.mkv").resolve()
# Determine which method to try first
@ -336,7 +447,8 @@ def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str,
'is_tv': is_tv,
'subtitle_file': subtitle_file,
'src_bit_depth': src_bit_depth,
'encoder': actual_encoder
'encoder': actual_encoder,
'media_context': media_context
})
consecutive_failures += 1
if consecutive_failures >= max_consecutive:
@ -387,7 +499,8 @@ def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str,
'target_resolution': target_resolution,
'file_cq': file_cq,
'is_tv': is_tv,
'subtitle_file': subtitle_file
'subtitle_file': subtitle_file,
'media_context': media_context
})
consecutive_failures += 1
if consecutive_failures >= max_consecutive:
@ -420,7 +533,7 @@ def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str,
_save_successful_encoding(
file, temp_input, temp_output, orig_size, out_size,
reduction_ratio, method, src_width, src_height, res_width, res_height,
file_cq, tracker_file, folder, is_tv, suffix, config, test_mode, subtitle_file, travel_output_folder, replace_file, wait_seconds
file_cq, tracker_file, folder, is_tv, suffix, config, test_mode, subtitle_file, travel_output_folder, replace_file, wait_seconds, media_context
)
# In test mode, stop after first successful file
@ -498,7 +611,8 @@ def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str,
file_data['res_width'], file_data['res_height'],
file_data['file_cq'], tracker_file,
folder, file_data['is_tv'], suffix, config, False,
file_data.get('subtitle_file'), travel_output_folder, replace_file, wait_seconds
file_data.get('subtitle_file'), travel_output_folder, replace_file, wait_seconds,
file_data.get('media_context')
)
except subprocess.CalledProcessError as e:
@ -547,9 +661,12 @@ def process_folder(folder: Path, cq: int, transcode_mode: str, resolution: str,
def _save_successful_encoding(file, temp_input, temp_output, orig_size, out_size,
reduction_ratio, method, src_width, src_height, res_width, res_height,
file_cq, tracker_file, folder, is_tv, suffix, config=None, test_mode=False, subtitle_file=None, travel_output_folder=None, replace_file: bool = False, wait_seconds: int = 0):
file_cq, tracker_file, folder, is_tv, suffix, config=None, test_mode=False, subtitle_file=None, travel_output_folder=None, replace_file: bool = False, wait_seconds: int = 0, media_context: dict = None):
"""Helper function to save successfully encoded files with [EHX] tag and clean up subtitle files."""
if media_context is None:
media_context = {}
# In test mode, show ratio and skip file move/cleanup
if test_mode:
orig_size_mb = round(orig_size / 1e6, 2)
@ -565,7 +682,7 @@ def _save_successful_encoding(file, temp_input, temp_output, orig_size, out_size
print(f"Method: {method} (CQ={file_cq if method == 'CQ' else 'N/A'})")
print(f"{'='*60}")
print(f"📁 Encoded file location: {temp_output}")
logger.info(f"TEST MODE - File: {file.name} | Ratio: {percentage}% | Method: {method}")
logger.info(f"TEST MODE - File: {file.name} | Ratio: {percentage}% | Method: {method}", extra=media_context)
return
# Check if file is in a Featurettes folder - if so, remove suffix from destination filename
@ -592,11 +709,11 @@ def _save_successful_encoding(file, temp_input, temp_output, orig_size, out_size
travel_dest_dir.mkdir(parents=True, exist_ok=True)
dest_file = travel_dest_dir / temp_output.name
print(f"🧳 Travel mode: Moving to {dest_file}")
logger.info(f"Travel mode destination: {dest_file}")
logger.info(f"Travel mode destination: {dest_file}", extra=media_context)
shutil.move(temp_output, dest_file)
print(f"🚚 Moved {temp_output.name}{dest_file.name}")
logger.info(f"Moved {temp_output.name}{dest_file.name}")
logger.info(f"Moved {temp_output.name}{dest_file.name}", extra=media_context)
# Classify file type based on folder (folder_parts already defined earlier)
if "tv" in folder_parts:
@ -635,10 +752,11 @@ def _save_successful_encoding(file, temp_input, temp_output, orig_size, out_size
])
# Enhanced logging with all conversion details
logger.info(f"\n✅ CONVERSION COMPLETE: {dest_file.name}")
logger.info(f" Type: {f_type.upper()} | Show: {show}")
logger.info(f" Size: {orig_size_mb}MB → {proc_size_mb}MB ({percentage}% of original, {100-percentage:.1f}% reduction)")
logger.info(f" Method: {method} | Status: SUCCESS")
log_context = {**media_context, "method": method, "original_size_mb": orig_size_mb, "output_size_mb": proc_size_mb, "reduction_pct": 100 - percentage}
logger.info(f"✅ CONVERSION COMPLETE: {dest_file.name}", extra=log_context)
logger.info(f" Type: {f_type.upper()} | Show: {show}", extra=log_context)
logger.info(f" Size: {orig_size_mb}MB → {proc_size_mb}MB ({percentage}% of original, {100-percentage:.1f}% reduction)", extra=log_context)
logger.info(f" Method: {method} | Status: SUCCESS", extra=log_context)
print(f"📝 Logged conversion: {dest_file.name} ({percentage}%), method={method}")
try:
@ -646,9 +764,9 @@ def _save_successful_encoding(file, temp_input, temp_output, orig_size, out_size
# Keep original file if in travel mode, replace mode, or if in Featurettes folder
if travel_output_folder:
logger.info(f"Travel mode: Kept original file {file.name}")
logger.info(f"Travel mode: Kept original file {file.name}", extra=media_context)
elif replace_file:
logger.info(f"Replace mode: Original file has been replaced with processed version at {file.name}")
logger.info(f"Replace mode: Original file has been replaced with processed version at {file.name}", extra=media_context)
elif not is_featurette:
file.unlink()
logger.info(f"Deleted original and processing copy for {file.name}")

View File

@ -1,6 +1,7 @@
# core/video_handler.py
"""Video resolution detection and encoding logic."""
import json
import subprocess
from pathlib import Path
@ -190,8 +191,6 @@ def has_forced_subtitles(input_file: Path) -> bool:
Returns True if at least one subtitle stream has forced=1 disposition.
"""
try:
import json
# Method 1: Try JSON output (most reliable)
cmd = [
"ffprobe", "-v", "error",

File diff suppressed because it is too large Load Diff

10
main.py
View File

@ -106,12 +106,12 @@ Examples:
parser.add_argument(
"--encoder", dest="encoder", default="hevc",
choices=["hevc", "av1"],
help="Video encoder: 'hevc' for HEVC NVENC 10-bit (default), 'av1' for AV1 NVENC 8-bit"
help="Video encoder: 'hevc' for HEVC NVENC 10-bit (default), 'av1' for AV1 NVENC 8-bit. Auto-selected based on source bit depth if not specified"
)
parser.add_argument(
"--r", "--resolution", dest="resolution", default=None,
choices=["480", "720", "1080"],
help="Force target resolution (if not specified: 4K->1080p, else preserve)"
help="Target resolution (acts as max, downscales if source is larger). If not specified: 4K→1080p, else preserve source"
)
parser.add_argument(
"--test", dest="test_mode", default=False, action="store_true",
@ -127,11 +127,11 @@ Examples:
)
parser.add_argument(
"--audio-select", dest="audio_select", default=None,
help="Pre-select audio streams to keep (comma-separated, e.g., 1,2). Skips interactive prompt. Requires --filter-audio"
help="Pre-select audio streams to keep (comma-separated, e.g., 1,2). Skips interactive prompt"
)
parser.add_argument(
"--keep-all-titles", dest="strip_all_titles", default=True, action="store_false",
help="Keep title metadata from all audio tracks (default: False, titles are stripped)"
help="Preserve title metadata on audio tracks (default: titles are stripped)"
)
parser.add_argument(
"--unforce-subs", dest="unforce_subs", default=False, action="store_true",
@ -151,7 +151,7 @@ Examples:
)
parser.add_argument(
"--wait", "-w", dest="wait_seconds", type=int, nargs='?', const=-1, default=None,
help="Wait after each successful file (default: 30s if --no-encode, 0s otherwise). Use --wait 0 to disable, --wait 60 for custom"
help="Wait after each file (default: 30s with --no-encode, 0s otherwise). Gives Plex time to detect changes"
)
parser.add_argument(
"--travel", dest="travel_mode", default=False, action="store_true",