v1.02a
added message cleanup and some better logic
This commit is contained in:
parent
13912636ea
commit
8d3aa03d72
101
LOGGING_STRUCTURE.md
Normal file
101
LOGGING_STRUCTURE.md
Normal 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
|
||||
@ -1645,3 +1645,111 @@ tv,Taskmaster,Taskmaster - S00E54 - Taskmaster’s New Year Treat h265 AAC WEBRi
|
||||
tv,Taskmaster,Taskmaster - S00E73 - Taskmaster’s 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.
|
@ -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)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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}")
|
||||
|
||||
@ -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",
|
||||
|
||||
1730
logs/conversion.log
1730
logs/conversion.log
File diff suppressed because it is too large
Load Diff
10
main.py
10
main.py
@ -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",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user