init
This commit is contained in:
3
.cursorindexingignore
Normal file
3
.cursorindexingignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
# Don't index SpecStory auto-save files, but allow explicit context inclusion via @ references
|
||||||
|
.specstory/**
|
||||||
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# SpecStory explanation file
|
||||||
|
.specstory/.what-is-this.md
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs/*
|
||||||
|
|
||||||
|
# Encoded files
|
||||||
|
input/*
|
||||||
|
output/*
|
||||||
|
# Video files
|
||||||
|
*.mp4
|
||||||
|
*.mkv
|
||||||
|
*.avi
|
||||||
|
*.mov
|
||||||
|
*.wmv
|
||||||
|
*.flv
|
||||||
|
*.webm
|
||||||
|
*.DVR.mp4
|
||||||
|
*.ts
|
||||||
|
*.m2ts
|
||||||
|
*.mts
|
||||||
2
.specstory/.gitignore
vendored
Normal file
2
.specstory/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# SpecStory explanation file
|
||||||
|
/.what-is-this.md
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,335 @@
|
|||||||
|
<!-- Generated by SpecStory -->
|
||||||
|
|
||||||
|
# Test encode for one minute (2025-06-26 04:00Z)
|
||||||
|
|
||||||
|
_**User**_
|
||||||
|
|
||||||
|
can we do a test encode for 1 min?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_**Assistant**_
|
||||||
|
|
||||||
|
Yes, you can perform a test encode for just 1 minute of each file by modifying the FFmpeg command to only process the first 60 seconds. In FFmpeg, you can use the -t 60 option to limit the output duration to 60 seconds.
|
||||||
|
|
||||||
|
Here's how you can do it concisely:
|
||||||
|
|
||||||
|
- Add '-t', '60' to the FFmpeg command list in the encode_dvr function, right after the input file argument.
|
||||||
|
|
||||||
|
Would you like this as a permanent option (e.g., via a command-line flag or prompt), or just a quick hardcoded test for now? If you want it as a toggle, let me know your preferred method (prompt, argument, etc.). Otherwise, I can make the quick change for a 1-minute encode.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_**User**_
|
||||||
|
|
||||||
|
the output got stuck on this, but it shows my download rate @ ~300-400 mbps (it's encoding on a network share) do I just wait?
|
||||||
|
|
||||||
|
FFmpeg: frame= 898 fps=3.1 q=0.0 size= 176384KiB time=N/A bitrate=N/A dup=0 drop=1 speed=N/A
|
||||||
|
FFmpeg: frame= 898 fps=3.1 q=0.0 size= 176384KiB time=N/A bitrate=N/A dup=0 drop=1 speed=N/A
|
||||||
|
FFmpeg: frame= 898 fps=3.1 q=0.0 size= 176384KiB time=N/A bitrate=N/A dup=0 drop=1 speed=N/A
|
||||||
|
FFmpeg: frame= 898 fps=3.1 q=0.0 size= 176384KiB time=N/A bitrate=N/A dup=0 drop=1 speed=N/A
|
||||||
|
FFmpeg: frame= 898 fps=3.1 q=0.0 size= 176384KiB time=N/A bitrate=N/A dup=0 drop=1 speed=N/A
|
||||||
|
FFmpeg: frame= 898 fps=3.1 q=0.0 size= 176384KiB time=N/A bitrate=N/A dup=0 drop=1 speed=N/A
|
||||||
|
FFmpeg: frame= 898 fps=3.1 q=0.0 size= 176384KiB time=N/A bitrate=N/A dup=0 drop=1 speed=N/A
|
||||||
|
FFmpeg: [out#0/mp4 @ 00000287de38b840] All streams finished
|
||||||
|
FFmpeg: [out#0/mp4 @ 00000287de38b840] Terminating thread with return code 0 (success)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_**Assistant**_
|
||||||
|
|
||||||
|
Yes, you should wait! The output shows that FFmpeg is still processing and hasn't actually gotten stuck. Here's what's happening:
|
||||||
|
|
||||||
|
1. **Frame 898** - FFmpeg is still encoding frames (you're at frame 898)
|
||||||
|
2. **fps=3.1** - It's processing about 3.1 frames per second, which is normal for lossless VP9 encoding
|
||||||
|
3. **size=176384KiB** - The output file is growing (about 172MB so far)
|
||||||
|
4. **time=N/A** - This is normal during encoding, especially with lossless encoding
|
||||||
|
5. **speed=N/A** - Also normal during lossless encoding
|
||||||
|
|
||||||
|
The key indicators that it's working:
|
||||||
|
- Frame count is increasing (898 frames processed)
|
||||||
|
- File size is growing (176384KiB)
|
||||||
|
- No error messages
|
||||||
|
- The final lines show "All streams finished" and "Terminating thread with return code 0 (success)"
|
||||||
|
|
||||||
|
**Lossless VP9 encoding is very slow** - 3.1 fps is actually quite good for lossless encoding. A 15-second clip might take several minutes to encode losslessly.
|
||||||
|
|
||||||
|
The 300-400 Mbps network activity you're seeing is likely the input file being read from the network share, which is normal.
|
||||||
|
|
||||||
|
**Recommendation**: Let it finish. The process appears to be working correctly, just slowly (which is expected for lossless encoding).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_**User**_
|
||||||
|
|
||||||
|
what does this mean then? keep in mind my cpu is no longer being utilized
|
||||||
|
FFmpeg: [out#0/mp4 @ 00000287de38b840] All streams finished
|
||||||
|
FFmpeg: [out#0/mp4 @ 00000287de38b840] Terminating thread with return code 0 (success)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_**Assistant**_
|
||||||
|
|
||||||
|
That output means **the encoding is complete and successful!**
|
||||||
|
|
||||||
|
The messages you're seeing:
|
||||||
|
- `All streams finished` - FFmpeg has finished processing all video and audio streams
|
||||||
|
- `Terminating thread with return code 0 (success)` - The process completed successfully with no errors
|
||||||
|
|
||||||
|
The fact that your CPU is no longer being utilized confirms this - FFmpeg has finished the encoding job and is just cleaning up.
|
||||||
|
|
||||||
|
**The process should exit automatically** and return you to the command prompt. If it's still showing those messages repeatedly without exiting, that might indicate the script is stuck in a loop reading the output.
|
||||||
|
|
||||||
|
You can:
|
||||||
|
1. **Wait a few more seconds** - it might just be finishing up
|
||||||
|
2. **Press Ctrl+C** if it doesn't exit - the encoding is already done
|
||||||
|
3. **Check your output folder** - the encoded file should be there
|
||||||
|
|
||||||
|
The 15-second test encode is complete! You should find the output file in your `output/` directory.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_**User**_
|
||||||
|
|
||||||
|
why would ffmpeg.exe still be accessing files at this point?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_**Assistant**_
|
||||||
|
|
||||||
|
FFmpeg might still be accessing files for several reasons even after the encoding is "complete":
|
||||||
|
|
||||||
|
1. **Finalizing the output file** - Writing the final metadata, headers, and closing the file properly
|
||||||
|
2. **Cleanup operations** - Releasing file handles, flushing buffers to disk
|
||||||
|
3. **Network share overhead** - If writing to a network location, there might be additional I/O for file locking, permissions, or network protocol cleanup
|
||||||
|
4. **File system operations** - Updating timestamps, file attributes, or directory entries
|
||||||
|
5. **Buffer flushing** - Ensuring all data is actually written to disk before closing
|
||||||
|
|
||||||
|
The "All streams finished" message indicates the encoding work is done, but FFmpeg still needs to:
|
||||||
|
- Write the final file headers
|
||||||
|
- Close the output file properly
|
||||||
|
- Release system resources
|
||||||
|
|
||||||
|
This is normal behavior, especially when working with network shares where file operations can take longer due to network latency and protocol overhead. The process should exit once all file operations are complete.
|
||||||
|
|
||||||
|
If it's taking an unusually long time (>30 seconds), you could check if there are any file system issues or network connectivity problems.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_**User**_
|
||||||
|
|
||||||
|
the resultant file is broken.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_**Assistant**_
|
||||||
|
|
||||||
|
The broken output file could be due to several issues. Let me help you troubleshoot:
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Read file: encode_dvr_lossless_vp9.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
The issue is likely that the process was interrupted or didn't complete properly. Here are the most common causes and solutions:
|
||||||
|
|
||||||
|
**Potential Issues:**
|
||||||
|
|
||||||
|
1. **Process interruption** - The script might have been stopped before FFmpeg could properly finalize the file
|
||||||
|
2. **Network share issues** - Writing to network locations can cause file corruption if interrupted
|
||||||
|
3. **Incomplete encoding** - The 15-second limit might have caused issues with the VP9 encoder
|
||||||
|
|
||||||
|
**Quick fixes to try:**
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```diff
|
||||||
|
'-c:a', 'copy',
|
||||||
|
'-map', '0',
|
||||||
|
+ '-f', 'mp4', # Explicitly specify output format
|
||||||
|
+ '-movflags', '+faststart', # Optimize for streaming
|
||||||
|
]
|
||||||
|
# Add metadata for each audio stream if label exists
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Also, let's add a simple test to verify the output file:**
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```diff
|
||||||
|
return f"{size_bytes:.2f} TB"
|
||||||
|
|
||||||
|
+ def verify_output_file(output_path):
|
||||||
|
+ """Verify that the output file is valid and playable"""
|
||||||
|
+ try:
|
||||||
|
+ cmd = ['ffprobe', '-v', 'error', str(output_path)]
|
||||||
|
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
||||||
|
+ return result.returncode == 0
|
||||||
|
+ except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
|
||||||
|
+ return False
|
||||||
|
+
|
||||||
|
def encode_dvr(input_file, output_dir, gpu):
|
||||||
|
input_path = Path(input_file)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```diff
|
||||||
|
compression_ratio = input_size / output_size if output_size > 0 else 0
|
||||||
|
|
||||||
|
+ # Verify output file integrity
|
||||||
|
+ if verify_output_file(output_path):
|
||||||
|
logging.info(f"Successfully encoded: {output_path}")
|
||||||
|
logging.info(f"Output size: {format_size(output_size)}")
|
||||||
|
logging.info(f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
|
print(f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
|
||||||
|
print(f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
|
+ else:
|
||||||
|
+ logging.error(f"Output file verification failed: {output_path}")
|
||||||
|
+ print(f"{Colors.RED}Output file verification failed: {output_path}{Colors.ENDC}")
|
||||||
|
+ # Remove corrupted file
|
||||||
|
+ if output_path.exists():
|
||||||
|
+ output_path.unlink()
|
||||||
|
else:
|
||||||
|
logging.error(f"FFmpeg process failed with return code {process.returncode}")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Now try the test again with these improvements:**
|
||||||
|
|
||||||
|
1. **Explicit format specification** - Added `-f mp4` to ensure proper container format
|
||||||
|
2. **Fast start optimization** - Added `-movflags +faststart` for better file structure
|
||||||
|
3. **File verification** - The script now checks if the output file is valid and removes corrupted files
|
||||||
|
|
||||||
|
**Additional troubleshooting steps:**
|
||||||
|
- Delete the broken output file before running again
|
||||||
|
- Try encoding to a local drive first (not network share) to rule out network issues
|
||||||
|
- Check if your input file is valid: `ffprobe -v error your_input_file.mp4`
|
||||||
|
|
||||||
|
The updated script should be more robust and will automatically detect and remove corrupted output files.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_**User**_
|
||||||
|
|
||||||
|
results are working, but I'm noticing massive sizes. is there a cq 0 option?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_**Assistant**_
|
||||||
|
|
||||||
|
Yes! The issue is that you're using lossless VP9 encoding, which creates very large files. VP9 doesn't have a traditional "CQ 0" option like H.264/H.265, but you can use **CRF (Constant Rate Factor)** which is similar.
|
||||||
|
|
||||||
|
For VP9, the CRF range is **0-63**, where:
|
||||||
|
- **0** = lossless (what you're currently using)
|
||||||
|
- **10-20** = visually lossless (very high quality)
|
||||||
|
- **20-30** = high quality
|
||||||
|
- **30+** = lower quality
|
||||||
|
|
||||||
|
Let me modify the script to use CRF instead of lossless:
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Key changes:**
|
||||||
|
- **Removed `-lossless 1`** - This was causing the massive file sizes
|
||||||
|
- **Added `-crf 15`** - High quality, visually lossless setting
|
||||||
|
- **Added `-b:v 0`** - Let CRF control the bitrate automatically
|
||||||
|
- **Changed `-cpu-used` from 0 to 2** - Faster encoding while maintaining quality
|
||||||
|
|
||||||
|
**CRF values you can try:**
|
||||||
|
- **10** - Very high quality (larger files)
|
||||||
|
- **15** - High quality (good balance)
|
||||||
|
- **20** - Still high quality (smaller files)
|
||||||
|
- **25** - Good quality (much smaller files)
|
||||||
|
|
||||||
|
The current setting of CRF 15 should give you excellent quality with much smaller file sizes than lossless. You can adjust the CRF value based on your quality vs. size preferences.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_**User**_
|
||||||
|
|
||||||
|
these look good, can I test the full video lengths now?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_**Assistant**_
|
||||||
|
|
||||||
|
Yes! To test the full video lengths, you just need to remove the `-t 1` parameter from the FFmpeg command. This will encode the entire duration of each video.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Perfect! Now the script will encode the full duration of each video. With your current settings:
|
||||||
|
|
||||||
|
- **CRF 15** - High quality, visually lossless
|
||||||
|
- **Max bitrate 10 Mbps** - Good for 1080p content
|
||||||
|
- **CPU-used 2** - Reasonable encoding speed
|
||||||
|
|
||||||
|
The encoding will take much longer now (probably 10-30 minutes per video depending on your hardware), but you'll get the full videos encoded with good quality and reasonable file sizes.
|
||||||
|
|
||||||
|
You can run the script now and it will process all 14 files in your input directory with their full durations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_**User**_
|
||||||
|
|
||||||
|
other than increasing the rate limit, how can I make this higher quality?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_**Assistant**_
|
||||||
|
|
||||||
|
For higher quality VP9 encoding, here are the key parameters you can adjust:
|
||||||
|
|
||||||
|
**1. CRF (Constant Rate Factor) - Most Important:**
|
||||||
|
- **0** = Lossless (what you're using now)
|
||||||
|
- **5-10** = Extremely high quality
|
||||||
|
- **10-15** = Very high quality
|
||||||
|
|
||||||
|
**2. CPU-used (Encoding Speed vs Quality):**
|
||||||
|
- **0** = Slowest, highest quality (what you're using)
|
||||||
|
- **1** = Slightly faster, still very high quality
|
||||||
|
- **2** = Good balance
|
||||||
|
|
||||||
|
**3. Additional Quality Parameters:**
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Quality improvements added:**
|
||||||
|
|
||||||
|
1. **CRF 5** - Extremely high quality (vs 0 which is lossless but much larger)
|
||||||
|
2. **tile-columns 2** - Optimizes encoding for 1080p content
|
||||||
|
3. **frame-parallel 1** - Enables parallel frame processing
|
||||||
|
4. **lag-in-frames 25** - Uses more frames for better compression analysis
|
||||||
|
|
||||||
|
**Other quality options you could try:**
|
||||||
|
|
||||||
|
- **Increase bitrate limit** to 15-20 Mbps for even higher quality
|
||||||
|
- **Use CRF 0** for true lossless (but much larger files)
|
||||||
|
- **Add `-deadline best`** for maximum quality (very slow)
|
||||||
|
|
||||||
|
The current settings should give you excellent quality while being more reasonable in file size than lossless encoding.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
3
check_files.bat
Normal file
3
check_files.bat
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@echo off
|
||||||
|
python check_files.py
|
||||||
|
pause
|
||||||
293
check_files.py
Normal file
293
check_files.py
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# ANSI color codes
|
||||||
|
class Colors:
|
||||||
|
GREEN = '\033[92m'
|
||||||
|
RED = '\033[91m'
|
||||||
|
YELLOW = '\033[93m'
|
||||||
|
ENDC = '\033[0m'
|
||||||
|
|
||||||
|
# Set up logging
|
||||||
|
log_dir = "logs"
|
||||||
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
log_file = os.path.join(log_dir, f"check_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
|
||||||
|
logging.basicConfig(filename=log_file, level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
|
||||||
|
def write_debug_info(message):
|
||||||
|
with open("debug_output.txt", "a", encoding='utf-8') as f:
|
||||||
|
f.write(f"{message}\n")
|
||||||
|
|
||||||
|
def get_file_info(file_path):
|
||||||
|
try:
|
||||||
|
cmd = [
|
||||||
|
'ffprobe',
|
||||||
|
'-v', 'error',
|
||||||
|
'-show_entries', 'format=duration,size:stream=codec_name,codec_type,width,height,r_frame_rate,channels,channel_layout',
|
||||||
|
'-of', 'json',
|
||||||
|
str(file_path)
|
||||||
|
]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
return None, f"ffprobe failed: {result.stderr}"
|
||||||
|
info = json.loads(result.stdout)
|
||||||
|
if 'format' not in info:
|
||||||
|
return None, "No format information in ffprobe output"
|
||||||
|
return info, None
|
||||||
|
except Exception as e:
|
||||||
|
return None, f"Error running ffprobe: {str(e)}"
|
||||||
|
|
||||||
|
def format_file_size(size_bytes):
|
||||||
|
size_mb = size_bytes / (1024 * 1024)
|
||||||
|
if size_mb >= 1024: # If size is 1GB or larger
|
||||||
|
size_gb = size_mb / 1024
|
||||||
|
return f"{size_gb:.2f} GB"
|
||||||
|
return f"{size_mb:.2f} MB"
|
||||||
|
|
||||||
|
def check_file(input_path, output_path):
|
||||||
|
try:
|
||||||
|
input_info, input_error = get_file_info(input_path)
|
||||||
|
if input_error:
|
||||||
|
return f"Input file error: {input_error}"
|
||||||
|
|
||||||
|
output_info, output_error = get_file_info(output_path)
|
||||||
|
if output_error:
|
||||||
|
return f"Output file error: {output_error}"
|
||||||
|
|
||||||
|
# Check if output file exists
|
||||||
|
if not os.path.exists(output_path):
|
||||||
|
return f"Missing output file: {output_path}"
|
||||||
|
|
||||||
|
# Check duration difference
|
||||||
|
input_duration = float(input_info['format']['duration'])
|
||||||
|
output_duration = float(output_info['format']['duration'])
|
||||||
|
duration_diff = abs(input_duration - output_duration)
|
||||||
|
|
||||||
|
if duration_diff > 0.1: # More than 0.1 seconds difference
|
||||||
|
return f"Duration mismatch: Input={input_duration:.2f}s, Output={output_duration:.2f}s, Diff={duration_diff:.2f}s"
|
||||||
|
|
||||||
|
# Check video streams
|
||||||
|
input_video = next((s for s in input_info['streams'] if s['codec_type'] == 'video'), None)
|
||||||
|
output_video = next((s for s in output_info['streams'] if s['codec_type'] == 'video'), None)
|
||||||
|
|
||||||
|
if not input_video or not output_video:
|
||||||
|
return "Missing video stream in input or output"
|
||||||
|
|
||||||
|
# Check video resolution
|
||||||
|
if input_video['width'] != output_video['width'] or input_video['height'] != output_video['height']:
|
||||||
|
return f"Resolution mismatch: Input={input_video['width']}x{input_video['height']}, Output={output_video['width']}x{output_video['height']}"
|
||||||
|
|
||||||
|
# Check audio streams
|
||||||
|
input_audio = [s for s in input_info['streams'] if s['codec_type'] == 'audio']
|
||||||
|
output_audio = [s for s in output_info['streams'] if s['codec_type'] == 'audio']
|
||||||
|
|
||||||
|
if len(input_audio) != len(output_audio):
|
||||||
|
return f"Audio stream count mismatch: Input={len(input_audio)}, Output={len(output_audio)}"
|
||||||
|
|
||||||
|
# Check audio channels
|
||||||
|
for i, (in_audio, out_audio) in enumerate(zip(input_audio, output_audio)):
|
||||||
|
if in_audio['channels'] != out_audio['channels']:
|
||||||
|
return f"Audio channel mismatch in stream {i}: Input={in_audio['channels']}, Output={out_audio['channels']}"
|
||||||
|
|
||||||
|
# Check file size
|
||||||
|
input_size = int(input_info['format']['size'])
|
||||||
|
output_size = int(output_info['format']['size'])
|
||||||
|
|
||||||
|
if output_size < input_size * 0.1: # Output less than 10% of input size
|
||||||
|
return f"{Colors.YELLOW}Suspiciously small output: Input={format_file_size(input_size)}, Output={format_file_size(output_size)}{Colors.ENDC}"
|
||||||
|
|
||||||
|
return None # No issues found
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return f"Error checking file: {str(e)}"
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Clear previous debug output
|
||||||
|
with open("debug_output.txt", "w", encoding='utf-8') as f:
|
||||||
|
f.write("=== Debug Output ===\n")
|
||||||
|
|
||||||
|
input_dir = "input"
|
||||||
|
output_dir = "output"
|
||||||
|
|
||||||
|
# Initialize issue counters
|
||||||
|
issue_counts = {
|
||||||
|
"Missing from output": 0,
|
||||||
|
"Missing from input": 0,
|
||||||
|
"Duration mismatch": 0,
|
||||||
|
"Missing video stream": 0,
|
||||||
|
"Resolution mismatch": 0,
|
||||||
|
"Audio stream count mismatch": 0,
|
||||||
|
"Audio channel mismatch": 0,
|
||||||
|
"Suspiciously small output": 0,
|
||||||
|
"Other errors": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
write_debug_info(f"\nChecking directories at {datetime.now()}")
|
||||||
|
write_debug_info(f"Input dir: {input_dir}")
|
||||||
|
write_debug_info(f"Output dir: {output_dir}")
|
||||||
|
|
||||||
|
# Check if directories exist
|
||||||
|
write_debug_info(f"\nDirectory existence check:")
|
||||||
|
write_debug_info(f"Input directory exists: {os.path.exists(input_dir)}")
|
||||||
|
write_debug_info(f"Output directory exists: {os.path.exists(output_dir)}")
|
||||||
|
|
||||||
|
# Try to list contents using different methods
|
||||||
|
write_debug_info("\nTrying 'dir' command:")
|
||||||
|
try:
|
||||||
|
result = subprocess.run(['dir', input_dir], capture_output=True, text=True)
|
||||||
|
write_debug_info("Input directory contents (dir command):")
|
||||||
|
write_debug_info(result.stdout)
|
||||||
|
except Exception as e:
|
||||||
|
write_debug_info(f"Error with dir command: {str(e)}")
|
||||||
|
|
||||||
|
write_debug_info("\nTrying os.listdir():")
|
||||||
|
try:
|
||||||
|
write_debug_info("Input directory contents (os.listdir):")
|
||||||
|
for item in os.listdir(input_dir):
|
||||||
|
full_path = os.path.join(input_dir, item)
|
||||||
|
write_debug_info(f" {item} - Exists: {os.path.exists(full_path)} - Is file: {os.path.isfile(full_path)}")
|
||||||
|
except Exception as e:
|
||||||
|
write_debug_info(f"Error with os.listdir: {str(e)}")
|
||||||
|
|
||||||
|
write_debug_info("\nTrying Path.iterdir():")
|
||||||
|
try:
|
||||||
|
write_debug_info("Input directory contents (Path.iterdir):")
|
||||||
|
for item in Path(input_dir).iterdir():
|
||||||
|
write_debug_info(f" {item.name} - Exists: {item.exists()} - Is file: {item.is_file()}")
|
||||||
|
except Exception as e:
|
||||||
|
write_debug_info(f"Error with Path.iterdir: {str(e)}")
|
||||||
|
|
||||||
|
print("\nDebug information has been written to debug_output.txt")
|
||||||
|
print("Please check that file to see what's actually in the directories")
|
||||||
|
|
||||||
|
# Continue with the rest of the script...
|
||||||
|
print("\nChecking files for inconsistencies...")
|
||||||
|
logging.info("Starting file check")
|
||||||
|
|
||||||
|
# First verify the directories exist
|
||||||
|
print("\nVerifying directories:")
|
||||||
|
print(f"Input directory exists: {os.path.isdir(input_dir)}")
|
||||||
|
print(f"Output directory exists: {os.path.isdir(output_dir)}")
|
||||||
|
|
||||||
|
if not os.path.isdir(input_dir) or not os.path.isdir(output_dir):
|
||||||
|
print("Error: One or both directories do not exist!")
|
||||||
|
return
|
||||||
|
|
||||||
|
issues_found = []
|
||||||
|
|
||||||
|
# Get list of files that exist in both directories, verifying each file exists
|
||||||
|
input_files = set()
|
||||||
|
for f in os.listdir(input_dir):
|
||||||
|
if f.endswith(('.mp4', '.DVR.mp4')):
|
||||||
|
full_path = os.path.join(input_dir, f)
|
||||||
|
if os.path.isfile(full_path):
|
||||||
|
input_files.add(f)
|
||||||
|
write_debug_info(f"Found real file in input: {f}")
|
||||||
|
else:
|
||||||
|
write_debug_info(f"Found non-existent file in input: {f}")
|
||||||
|
|
||||||
|
output_files = set()
|
||||||
|
for f in os.listdir(output_dir):
|
||||||
|
if f.endswith(('.mp4', '.DVR.mp4')):
|
||||||
|
full_path = os.path.join(output_dir, f)
|
||||||
|
if os.path.isfile(full_path):
|
||||||
|
output_files.add(f)
|
||||||
|
write_debug_info(f"Found real file in output: {f}")
|
||||||
|
else:
|
||||||
|
write_debug_info(f"Found non-existent file in output: {f}")
|
||||||
|
|
||||||
|
# Debug: Print and log what files were found
|
||||||
|
logging.info("Files found in input directory:")
|
||||||
|
for f in sorted(input_files):
|
||||||
|
logging.info(f" {f}")
|
||||||
|
|
||||||
|
logging.info("Files found in output directory:")
|
||||||
|
for f in sorted(output_files):
|
||||||
|
logging.info(f" {f}")
|
||||||
|
|
||||||
|
# Report missing files
|
||||||
|
missing_in_output = input_files - output_files
|
||||||
|
missing_in_input = output_files - input_files
|
||||||
|
|
||||||
|
if missing_in_output:
|
||||||
|
print(f"\n{Colors.YELLOW}Files missing from output directory:{Colors.ENDC}")
|
||||||
|
for f in sorted(missing_in_output):
|
||||||
|
print(f" {f}")
|
||||||
|
logging.warning(f"File missing from output: {f}")
|
||||||
|
issues_found.append((f, "Missing from output"))
|
||||||
|
issue_counts["Missing from output"] += 1
|
||||||
|
|
||||||
|
if missing_in_input:
|
||||||
|
print(f"\n{Colors.YELLOW}Files missing from input directory:{Colors.ENDC}")
|
||||||
|
for f in sorted(missing_in_input):
|
||||||
|
print(f" {f}")
|
||||||
|
logging.warning(f"File missing from input: {f}")
|
||||||
|
issues_found.append((f, "Missing from input"))
|
||||||
|
issue_counts["Missing from input"] += 1
|
||||||
|
|
||||||
|
files_to_check = input_files.intersection(output_files)
|
||||||
|
logging.info(f"Number of matching files found: {len(files_to_check)}")
|
||||||
|
|
||||||
|
if not files_to_check:
|
||||||
|
msg = "No matching files found in both input and output directories"
|
||||||
|
print(f"\n{Colors.YELLOW}{msg}{Colors.ENDC}")
|
||||||
|
logging.warning(msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
for input_file in sorted(files_to_check):
|
||||||
|
input_path = os.path.join(input_dir, input_file)
|
||||||
|
output_path = os.path.join(output_dir, input_file)
|
||||||
|
|
||||||
|
print(f"\nChecking: {input_file}")
|
||||||
|
logging.info(f"Checking file: {input_file}")
|
||||||
|
|
||||||
|
issue = check_file(input_path, output_path)
|
||||||
|
if issue:
|
||||||
|
issues_found.append((input_file, issue))
|
||||||
|
print(f"{Colors.RED}ISSUE FOUND: {issue}{Colors.ENDC}")
|
||||||
|
logging.warning(f"Issue with {input_file}: {issue}")
|
||||||
|
|
||||||
|
# Count the type of issue
|
||||||
|
if "Duration mismatch" in issue:
|
||||||
|
issue_counts["Duration mismatch"] += 1
|
||||||
|
elif "Missing video stream" in issue:
|
||||||
|
issue_counts["Missing video stream"] += 1
|
||||||
|
elif "Resolution mismatch" in issue:
|
||||||
|
issue_counts["Resolution mismatch"] += 1
|
||||||
|
elif "Audio stream count mismatch" in issue:
|
||||||
|
issue_counts["Audio stream count mismatch"] += 1
|
||||||
|
elif "Audio channel mismatch" in issue:
|
||||||
|
issue_counts["Audio channel mismatch"] += 1
|
||||||
|
elif "Suspiciously small output" in issue:
|
||||||
|
issue_counts["Suspiciously small output"] += 1
|
||||||
|
else:
|
||||||
|
issue_counts["Other errors"] += 1
|
||||||
|
else:
|
||||||
|
print(f"{Colors.GREEN}No issues found{Colors.ENDC}")
|
||||||
|
logging.info(f"No issues found for {input_file}")
|
||||||
|
|
||||||
|
# Print summary
|
||||||
|
print("\nSummary of issues found:")
|
||||||
|
if issues_found:
|
||||||
|
print("\nIssue counts:")
|
||||||
|
for issue_type, count in issue_counts.items():
|
||||||
|
color = Colors.RED if count > 0 else Colors.GREEN
|
||||||
|
print(f"{issue_type}: {color}{count}{Colors.ENDC}")
|
||||||
|
|
||||||
|
print("\nDetailed issues:")
|
||||||
|
for file, issue in issues_found:
|
||||||
|
print(f"\n{file}:")
|
||||||
|
print(f"{Colors.RED} {issue}{Colors.ENDC}")
|
||||||
|
else:
|
||||||
|
print(f"{Colors.GREEN}No issues found in any files!{Colors.ENDC}")
|
||||||
|
print("\nIssue counts:")
|
||||||
|
for issue_type in issue_counts:
|
||||||
|
print(f"{issue_type}: {Colors.GREEN}0{Colors.ENDC}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
38
debug_output.txt
Normal file
38
debug_output.txt
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
=== Debug Output ===
|
||||||
|
|
||||||
|
Checking directories at 2025-08-16 01:57:13.833883
|
||||||
|
Input dir: input
|
||||||
|
Output dir: output
|
||||||
|
|
||||||
|
Directory existence check:
|
||||||
|
Input directory exists: True
|
||||||
|
Output directory exists: True
|
||||||
|
|
||||||
|
Trying 'dir' command:
|
||||||
|
Error with dir command: [WinError 2] The system cannot find the file specified
|
||||||
|
|
||||||
|
Trying os.listdir():
|
||||||
|
Input directory contents (os.listdir):
|
||||||
|
Repo 2025.08.14 - 20.49.10.02.DVR.mp4 - Exists: True - Is file: True
|
||||||
|
Repo 2025.08.14 - 20.58.54.03.DVR.mp4 - Exists: True - Is file: True
|
||||||
|
Repo 2025.08.14 - 21.42.52.02.DVR.mp4 - Exists: True - Is file: True
|
||||||
|
Repo 2025.08.14 - 21.58.34.03.DVR.mp4 - Exists: True - Is file: True
|
||||||
|
Repo 2025.08.14 - 22.00.55.04.DVR.mp4 - Exists: True - Is file: True
|
||||||
|
|
||||||
|
Trying Path.iterdir():
|
||||||
|
Input directory contents (Path.iterdir):
|
||||||
|
Repo 2025.08.14 - 20.49.10.02.DVR.mp4 - Exists: True - Is file: True
|
||||||
|
Repo 2025.08.14 - 20.58.54.03.DVR.mp4 - Exists: True - Is file: True
|
||||||
|
Repo 2025.08.14 - 21.42.52.02.DVR.mp4 - Exists: True - Is file: True
|
||||||
|
Repo 2025.08.14 - 21.58.34.03.DVR.mp4 - Exists: True - Is file: True
|
||||||
|
Repo 2025.08.14 - 22.00.55.04.DVR.mp4 - Exists: True - Is file: True
|
||||||
|
Found real file in input: Repo 2025.08.14 - 20.49.10.02.DVR.mp4
|
||||||
|
Found real file in input: Repo 2025.08.14 - 20.58.54.03.DVR.mp4
|
||||||
|
Found real file in input: Repo 2025.08.14 - 21.42.52.02.DVR.mp4
|
||||||
|
Found real file in input: Repo 2025.08.14 - 21.58.34.03.DVR.mp4
|
||||||
|
Found real file in input: Repo 2025.08.14 - 22.00.55.04.DVR.mp4
|
||||||
|
Found real file in output: Repo 2025.08.14 - 20.49.10.02.DVR.mp4
|
||||||
|
Found real file in output: Repo 2025.08.14 - 20.58.54.03.DVR.mp4
|
||||||
|
Found real file in output: Repo 2025.08.14 - 21.42.52.02.DVR.mp4
|
||||||
|
Found real file in output: Repo 2025.08.14 - 21.58.34.03.DVR.mp4
|
||||||
|
Found real file in output: Repo 2025.08.14 - 22.00.55.04.DVR.mp4
|
||||||
3
encode_VOD.bat
Normal file
3
encode_VOD.bat
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@echo off
|
||||||
|
python encode_VOD.py
|
||||||
|
pause
|
||||||
193
encode_VOD.py
Normal file
193
encode_VOD.py
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
# ANSI color codes
|
||||||
|
class Colors:
|
||||||
|
PURPLE = '\033[95m'
|
||||||
|
BLUE = '\033[94m'
|
||||||
|
GREEN = '\033[92m'
|
||||||
|
YELLOW = '\033[93m'
|
||||||
|
RED = '\033[91m'
|
||||||
|
ENDC = '\033[0m'
|
||||||
|
|
||||||
|
def get_gpu_selection():
|
||||||
|
while True:
|
||||||
|
print(f"\n{Colors.BLUE}Select GPU slot:{Colors.ENDC}")
|
||||||
|
print("0 - First GPU")
|
||||||
|
print("1 - Second GPU")
|
||||||
|
print("2 - Third GPU")
|
||||||
|
gpu = input(f"{Colors.YELLOW}Enter GPU number (0-2):{Colors.ENDC} ").strip()
|
||||||
|
if gpu in ['0', '1', '2']:
|
||||||
|
return gpu
|
||||||
|
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
|
||||||
|
|
||||||
|
# Set up logging
|
||||||
|
log_dir = "logs"
|
||||||
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
log_file = os.path.join(log_dir, f"encode_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
|
||||||
|
logging.basicConfig(filename=log_file, level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
|
||||||
|
def get_file_info(input_file):
|
||||||
|
cmd = [
|
||||||
|
'ffprobe',
|
||||||
|
'-v', 'error',
|
||||||
|
'-show_entries', 'format=duration,size:stream=codec_name,width,height,r_frame_rate,channels,channel_layout',
|
||||||
|
'-of', 'json',
|
||||||
|
input_file
|
||||||
|
]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
return json.loads(result.stdout)
|
||||||
|
|
||||||
|
def get_audio_labels(input_file):
|
||||||
|
cmd = [
|
||||||
|
'ffprobe',
|
||||||
|
'-v', 'error',
|
||||||
|
'-select_streams', 'a',
|
||||||
|
'-show_entries', 'stream=index:stream_tags=title',
|
||||||
|
'-of', 'json',
|
||||||
|
input_file
|
||||||
|
]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
info = json.loads(result.stdout)
|
||||||
|
labels = []
|
||||||
|
for stream in info.get('streams', []):
|
||||||
|
title = stream.get('tags', {}).get('title', None)
|
||||||
|
labels.append(title)
|
||||||
|
return labels
|
||||||
|
|
||||||
|
def format_size(size_bytes):
|
||||||
|
for unit in ['B', 'KB', 'MB', 'GB']:
|
||||||
|
if size_bytes < 1024:
|
||||||
|
return f"{size_bytes:.2f} {unit}"
|
||||||
|
size_bytes /= 1024
|
||||||
|
return f"{size_bytes:.2f} TB"
|
||||||
|
|
||||||
|
def encode_dvr(input_file, output_dir, gpu):
|
||||||
|
input_path = Path(input_file)
|
||||||
|
output_path = Path(output_dir) / f"{input_path.stem}{input_path.suffix}"
|
||||||
|
|
||||||
|
# Get file info for logging
|
||||||
|
file_info = get_file_info(str(input_path))
|
||||||
|
input_size = int(file_info['format']['size'])
|
||||||
|
duration = float(file_info['format']['duration'])
|
||||||
|
|
||||||
|
logging.info(f"Processing file: {input_path}")
|
||||||
|
logging.info(f"Input size: {format_size(input_size)}")
|
||||||
|
logging.info(f"Duration: {duration:.2f} seconds")
|
||||||
|
|
||||||
|
print(f"\n{Colors.BLUE}Processing file: {input_path}{Colors.ENDC}")
|
||||||
|
print(f"Input size: {format_size(input_size)}")
|
||||||
|
print(f"Duration: {duration:.2f} seconds")
|
||||||
|
|
||||||
|
# Log stream information
|
||||||
|
for i, stream in enumerate(file_info.get('streams', [])):
|
||||||
|
stream_type = 'Video' if stream.get('codec_name', '').startswith('h') else 'Audio'
|
||||||
|
logging.info(f"Stream {i} ({stream_type}):")
|
||||||
|
for key, value in stream.items():
|
||||||
|
logging.info(f" {key}: {value}")
|
||||||
|
|
||||||
|
# Skip if output file already exists
|
||||||
|
if output_path.exists():
|
||||||
|
output_size = output_path.stat().st_size
|
||||||
|
logging.info(f"Skipping {input_path} - output already exists: {output_path}")
|
||||||
|
logging.info(f"Output size: {format_size(output_size)}")
|
||||||
|
print(f"{Colors.YELLOW}Skipping {input_path} - output already exists{Colors.ENDC}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get audio labels
|
||||||
|
audio_labels = get_audio_labels(str(input_path))
|
||||||
|
logging.info(f"Audio labels: {audio_labels}")
|
||||||
|
|
||||||
|
# FFmpeg command with NVIDIA HEVC encoder and maximum quality
|
||||||
|
cmd = [
|
||||||
|
'ffmpeg',
|
||||||
|
'-v', 'verbose',
|
||||||
|
'-i', str(input_path),
|
||||||
|
'-c:v', 'hevc_nvenc',
|
||||||
|
'-gpu', gpu,
|
||||||
|
'-preset', 'p7',
|
||||||
|
'-tune', 'hq',
|
||||||
|
'-rc', 'vbr',
|
||||||
|
'-rc-lookahead', '32',
|
||||||
|
'-spatial-aq', '1',
|
||||||
|
'-aq-strength', '15',
|
||||||
|
'-cq', '0',
|
||||||
|
'-b:v', '10000k',
|
||||||
|
'-maxrate', '10000k',
|
||||||
|
'-bufsize', '20000k',
|
||||||
|
'-c:a', 'copy',
|
||||||
|
'-map', '0',
|
||||||
|
]
|
||||||
|
# Add metadata for each audio stream if label exists
|
||||||
|
for idx, label in enumerate(audio_labels):
|
||||||
|
if label:
|
||||||
|
cmd += [f'-metadata:s:a:{idx}', f'title={label}']
|
||||||
|
cmd.append(str(output_path))
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Run FFmpeg and capture output
|
||||||
|
process = subprocess.Popen(
|
||||||
|
cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
universal_newlines=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Log FFmpeg output in real-time
|
||||||
|
while True:
|
||||||
|
output = process.stderr.readline()
|
||||||
|
if output == '' and process.poll() is not None:
|
||||||
|
break
|
||||||
|
if output:
|
||||||
|
logging.info(f"FFmpeg: {output.strip()}")
|
||||||
|
print(f"{Colors.PURPLE}FFmpeg: {output.strip()}{Colors.ENDC}")
|
||||||
|
|
||||||
|
if process.returncode == 0:
|
||||||
|
# Get output file info
|
||||||
|
output_info = get_file_info(str(output_path))
|
||||||
|
output_size = int(output_info['format']['size'])
|
||||||
|
compression_ratio = input_size / output_size if output_size > 0 else 0
|
||||||
|
|
||||||
|
logging.info(f"Successfully encoded: {output_path}")
|
||||||
|
logging.info(f"Output size: {format_size(output_size)}")
|
||||||
|
logging.info(f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
|
print(f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
|
||||||
|
print(f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
|
else:
|
||||||
|
logging.error(f"FFmpeg process failed with return code {process.returncode}")
|
||||||
|
print(f"{Colors.RED}FFmpeg process failed with return code {process.returncode}{Colors.ENDC}")
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logging.error(f"Error encoding {input_path}: {e}")
|
||||||
|
print(f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Get GPU selection
|
||||||
|
gpu = get_gpu_selection()
|
||||||
|
logging.info(f"Selected GPU: {gpu}")
|
||||||
|
|
||||||
|
input_dir = "input"
|
||||||
|
output_dir = "output"
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Get list of files to process
|
||||||
|
files = [f for f in os.listdir(input_dir) if f.endswith(('.mp4', '.DVR.mp4'))]
|
||||||
|
total_files = len(files)
|
||||||
|
|
||||||
|
if total_files == 0:
|
||||||
|
logging.info("No files to process in input directory")
|
||||||
|
print(f"{Colors.YELLOW}No files to process in input directory{Colors.ENDC}")
|
||||||
|
else:
|
||||||
|
logging.info(f"Found {total_files} files to process")
|
||||||
|
print(f"{Colors.BLUE}Found {total_files} files to process{Colors.ENDC}")
|
||||||
|
|
||||||
|
for i, file in enumerate(files, 1):
|
||||||
|
input_file = os.path.join(input_dir, file)
|
||||||
|
logging.info(f"Processing file {i}/{total_files}: {file}")
|
||||||
|
print(f"\n{Colors.BLUE}Processing file {i}/{total_files}: {file}{Colors.ENDC}")
|
||||||
|
encode_dvr(input_file, output_dir, gpu)
|
||||||
3
encode_dvr.bat
Normal file
3
encode_dvr.bat
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@echo off
|
||||||
|
python encode_dvr.py
|
||||||
|
pause
|
||||||
209
encode_dvr.py
Normal file
209
encode_dvr.py
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
# ANSI color codes
|
||||||
|
class Colors:
|
||||||
|
PURPLE = '\033[95m'
|
||||||
|
BLUE = '\033[94m'
|
||||||
|
GREEN = '\033[92m'
|
||||||
|
YELLOW = '\033[93m'
|
||||||
|
RED = '\033[91m'
|
||||||
|
ENDC = '\033[0m'
|
||||||
|
|
||||||
|
def get_gpu_selection():
|
||||||
|
while True:
|
||||||
|
print(f"\n{Colors.BLUE}Select GPU slot:{Colors.ENDC}")
|
||||||
|
print("0 - First GPU")
|
||||||
|
print("1 - Second GPU")
|
||||||
|
print("2 - Third GPU")
|
||||||
|
gpu = input(f"{Colors.YELLOW}Enter GPU number (0-2):{Colors.ENDC} ").strip()
|
||||||
|
if gpu in ['0', '1', '2']:
|
||||||
|
return gpu
|
||||||
|
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
|
||||||
|
|
||||||
|
# Set up logging
|
||||||
|
log_dir = "logs"
|
||||||
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
log_file = os.path.join(log_dir, f"encode_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
|
||||||
|
logging.basicConfig(filename=log_file, level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
|
||||||
|
def get_file_info(input_file):
|
||||||
|
cmd = [
|
||||||
|
'ffprobe',
|
||||||
|
'-v', 'error',
|
||||||
|
'-show_entries', 'format=duration,size:stream=codec_name,width,height,r_frame_rate,channels,channel_layout',
|
||||||
|
'-of', 'json',
|
||||||
|
input_file
|
||||||
|
]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
return json.loads(result.stdout)
|
||||||
|
|
||||||
|
def get_audio_labels(input_file):
|
||||||
|
cmd = [
|
||||||
|
'ffprobe',
|
||||||
|
'-v', 'error',
|
||||||
|
'-select_streams', 'a',
|
||||||
|
'-show_entries', 'stream=index:stream_tags=title',
|
||||||
|
'-of', 'json',
|
||||||
|
input_file
|
||||||
|
]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
info = json.loads(result.stdout)
|
||||||
|
labels = []
|
||||||
|
for stream in info.get('streams', []):
|
||||||
|
title = stream.get('tags', {}).get('title', None)
|
||||||
|
labels.append(title)
|
||||||
|
return labels
|
||||||
|
|
||||||
|
def format_size(size_bytes):
|
||||||
|
for unit in ['B', 'KB', 'MB', 'GB']:
|
||||||
|
if size_bytes < 1024:
|
||||||
|
return f"{size_bytes:.2f} {unit}"
|
||||||
|
size_bytes /= 1024
|
||||||
|
return f"{size_bytes:.2f} TB"
|
||||||
|
|
||||||
|
def encode_dvr(input_file, output_dir, gpu):
|
||||||
|
input_path = Path(input_file)
|
||||||
|
output_path = Path(output_dir) / f"{input_path.stem}{input_path.suffix}"
|
||||||
|
|
||||||
|
# Get file info for logging
|
||||||
|
file_info = get_file_info(str(input_path))
|
||||||
|
input_size = int(file_info['format']['size'])
|
||||||
|
duration = float(file_info['format']['duration'])
|
||||||
|
|
||||||
|
logging.info(f"Processing file: {input_path}")
|
||||||
|
logging.info(f"Input size: {format_size(input_size)}")
|
||||||
|
logging.info(f"Duration: {duration:.2f} seconds")
|
||||||
|
|
||||||
|
print(f"\n{Colors.BLUE}Processing file: {input_path}{Colors.ENDC}")
|
||||||
|
print(f"Input size: {format_size(input_size)}")
|
||||||
|
print(f"Duration: {duration:.2f} seconds")
|
||||||
|
|
||||||
|
# Log stream information
|
||||||
|
for i, stream in enumerate(file_info.get('streams', [])):
|
||||||
|
stream_type = 'Video' if stream.get('codec_name', '').startswith('h') else 'Audio'
|
||||||
|
logging.info(f"Stream {i} ({stream_type}):")
|
||||||
|
for key, value in stream.items():
|
||||||
|
logging.info(f" {key}: {value}")
|
||||||
|
|
||||||
|
# Skip if output file already exists
|
||||||
|
if output_path.exists():
|
||||||
|
output_size = output_path.stat().st_size
|
||||||
|
logging.info(f"Skipping {input_path} - output already exists: {output_path}")
|
||||||
|
logging.info(f"Output size: {format_size(output_size)}")
|
||||||
|
print(f"{Colors.YELLOW}Skipping {input_path} - output already exists{Colors.ENDC}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get audio labels
|
||||||
|
audio_labels = get_audio_labels(str(input_path))
|
||||||
|
logging.info(f"Audio labels: {audio_labels}")
|
||||||
|
|
||||||
|
# FFmpeg command with NVIDIA HEVC encoder and maximum quality
|
||||||
|
cmd = [
|
||||||
|
'ffmpeg',
|
||||||
|
'-v', 'verbose',
|
||||||
|
'-progress', 'pipe:1', # Force progress output to stdout
|
||||||
|
'-stats', # Show encoding statistics
|
||||||
|
'-stats_period', '0.5', # Update stats every 0.5 seconds
|
||||||
|
'-i', str(input_path),
|
||||||
|
'-c:v', 'hevc_nvenc',
|
||||||
|
'-gpu', gpu,
|
||||||
|
'-preset', 'p7',
|
||||||
|
'-tune', 'hq',
|
||||||
|
'-rc', 'vbr', # Variable bitrate mode
|
||||||
|
'-rc-lookahead', '32', # Increase lookahead for better VBR decisions
|
||||||
|
'-spatial-aq', '1', # Enable spatial AQ for better quality
|
||||||
|
'-aq-strength', '15', # Maximum allowed AQ strength
|
||||||
|
'-cq', '35',
|
||||||
|
'-b:v', '0', # Let VBR determine bitrate
|
||||||
|
'-c:a', 'copy',
|
||||||
|
'-map', '0',
|
||||||
|
]
|
||||||
|
# Add metadata for each audio stream if label exists
|
||||||
|
for idx, label in enumerate(audio_labels):
|
||||||
|
if label:
|
||||||
|
cmd += [f'-metadata:s:a:{idx}', f'title={label}']
|
||||||
|
cmd.append(str(output_path))
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Run FFmpeg with timeout
|
||||||
|
process = subprocess.Popen(
|
||||||
|
cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
universal_newlines=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set timeout to 5 minutes
|
||||||
|
timeout = 300
|
||||||
|
start_time = datetime.now()
|
||||||
|
|
||||||
|
# Log FFmpeg output in real-time
|
||||||
|
while True:
|
||||||
|
# Check for timeout
|
||||||
|
if (datetime.now() - start_time).total_seconds() > timeout:
|
||||||
|
process.kill()
|
||||||
|
raise TimeoutError(f"FFmpeg process timed out after {timeout} seconds")
|
||||||
|
|
||||||
|
output = process.stderr.readline()
|
||||||
|
if output == '' and process.poll() is not None:
|
||||||
|
break
|
||||||
|
if output:
|
||||||
|
logging.info(f"FFmpeg: {output.strip()}")
|
||||||
|
print(f"{Colors.PURPLE}FFmpeg: {output.strip()}{Colors.ENDC}")
|
||||||
|
|
||||||
|
if process.returncode == 0:
|
||||||
|
# Get output file info
|
||||||
|
output_info = get_file_info(str(output_path))
|
||||||
|
output_size = int(output_info['format']['size'])
|
||||||
|
compression_ratio = input_size / output_size if output_size > 0 else 0
|
||||||
|
|
||||||
|
logging.info(f"Successfully encoded: {output_path}")
|
||||||
|
logging.info(f"Output size: {format_size(output_size)}")
|
||||||
|
logging.info(f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
|
print(f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
|
||||||
|
print(f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
|
else:
|
||||||
|
logging.error(f"FFmpeg process failed with return code {process.returncode}")
|
||||||
|
print(f"{Colors.RED}FFmpeg process failed with return code {process.returncode}{Colors.ENDC}")
|
||||||
|
|
||||||
|
except TimeoutError as e:
|
||||||
|
logging.error(f"Timeout error: {e}")
|
||||||
|
print(f"{Colors.RED}Timeout error: {e}{Colors.ENDC}")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logging.error(f"Error encoding {input_path}: {e}")
|
||||||
|
print(f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Unexpected error: {e}")
|
||||||
|
print(f"{Colors.RED}Unexpected error: {e}{Colors.ENDC}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Get GPU selection
|
||||||
|
gpu = get_gpu_selection()
|
||||||
|
logging.info(f"Selected GPU: {gpu}")
|
||||||
|
|
||||||
|
input_dir = "input"
|
||||||
|
output_dir = "output"
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Get list of files to process
|
||||||
|
files = [f for f in os.listdir(input_dir) if f.endswith(('.mp4', '.DVR.mp4'))]
|
||||||
|
total_files = len(files)
|
||||||
|
|
||||||
|
if total_files == 0:
|
||||||
|
logging.info("No files to process in input directory")
|
||||||
|
print(f"{Colors.YELLOW}No files to process in input directory{Colors.ENDC}")
|
||||||
|
else:
|
||||||
|
logging.info(f"Found {total_files} files to process")
|
||||||
|
print(f"{Colors.BLUE}Found {total_files} files to process{Colors.ENDC}")
|
||||||
|
|
||||||
|
for i, file in enumerate(files, 1):
|
||||||
|
input_file = os.path.join(input_dir, file)
|
||||||
|
logging.info(f"Processing file {i}/{total_files}: {file}")
|
||||||
|
print(f"\n{Colors.BLUE}Processing file {i}/{total_files}: {file}{Colors.ENDC}")
|
||||||
|
encode_dvr(input_file, output_dir, gpu)
|
||||||
3
encode_dvr_23.bat
Normal file
3
encode_dvr_23.bat
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@echo off
|
||||||
|
python encode_dvr_23.py
|
||||||
|
pause
|
||||||
191
encode_dvr_23.py
Normal file
191
encode_dvr_23.py
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
# ANSI color codes
|
||||||
|
class Colors:
|
||||||
|
PURPLE = '\033[95m'
|
||||||
|
BLUE = '\033[94m'
|
||||||
|
GREEN = '\033[92m'
|
||||||
|
YELLOW = '\033[93m'
|
||||||
|
RED = '\033[91m'
|
||||||
|
ENDC = '\033[0m'
|
||||||
|
|
||||||
|
def get_gpu_selection():
|
||||||
|
while True:
|
||||||
|
print(f"\n{Colors.BLUE}Select GPU slot:{Colors.ENDC}")
|
||||||
|
print("0 - First GPU")
|
||||||
|
print("1 - Second GPU")
|
||||||
|
print("2 - Third GPU")
|
||||||
|
gpu = input(f"{Colors.YELLOW}Enter GPU number (0-2):{Colors.ENDC} ").strip()
|
||||||
|
if gpu in ['0', '1', '2']:
|
||||||
|
return gpu
|
||||||
|
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
|
||||||
|
|
||||||
|
# Set up logging
|
||||||
|
log_dir = "logs"
|
||||||
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
log_file = os.path.join(log_dir, f"encode_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
|
||||||
|
logging.basicConfig(filename=log_file, level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
|
||||||
|
def get_file_info(input_file):
|
||||||
|
cmd = [
|
||||||
|
'ffprobe',
|
||||||
|
'-v', 'error',
|
||||||
|
'-show_entries', 'format=duration,size:stream=codec_name,width,height,r_frame_rate,channels,channel_layout',
|
||||||
|
'-of', 'json',
|
||||||
|
input_file
|
||||||
|
]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
return json.loads(result.stdout)
|
||||||
|
|
||||||
|
def get_audio_labels(input_file):
|
||||||
|
cmd = [
|
||||||
|
'ffprobe',
|
||||||
|
'-v', 'error',
|
||||||
|
'-select_streams', 'a',
|
||||||
|
'-show_entries', 'stream=index:stream_tags=title',
|
||||||
|
'-of', 'json',
|
||||||
|
input_file
|
||||||
|
]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
info = json.loads(result.stdout)
|
||||||
|
labels = []
|
||||||
|
for stream in info.get('streams', []):
|
||||||
|
title = stream.get('tags', {}).get('title', None)
|
||||||
|
labels.append(title)
|
||||||
|
return labels
|
||||||
|
|
||||||
|
def format_size(size_bytes):
|
||||||
|
for unit in ['B', 'KB', 'MB', 'GB']:
|
||||||
|
if size_bytes < 1024:
|
||||||
|
return f"{size_bytes:.2f} {unit}"
|
||||||
|
size_bytes /= 1024
|
||||||
|
return f"{size_bytes:.2f} TB"
|
||||||
|
|
||||||
|
def encode_dvr(input_file, output_dir, gpu):
|
||||||
|
input_path = Path(input_file)
|
||||||
|
output_path = Path(output_dir) / f"{input_path.stem}{input_path.suffix}"
|
||||||
|
|
||||||
|
# Get file info for logging
|
||||||
|
file_info = get_file_info(str(input_path))
|
||||||
|
input_size = int(file_info['format']['size'])
|
||||||
|
duration = float(file_info['format']['duration'])
|
||||||
|
|
||||||
|
logging.info(f"Processing file: {input_path}")
|
||||||
|
logging.info(f"Input size: {format_size(input_size)}")
|
||||||
|
logging.info(f"Duration: {duration:.2f} seconds")
|
||||||
|
|
||||||
|
print(f"\n{Colors.BLUE}Processing file: {input_path}{Colors.ENDC}")
|
||||||
|
print(f"Input size: {format_size(input_size)}")
|
||||||
|
print(f"Duration: {duration:.2f} seconds")
|
||||||
|
|
||||||
|
# Log stream information
|
||||||
|
for i, stream in enumerate(file_info.get('streams', [])):
|
||||||
|
stream_type = 'Video' if stream.get('codec_name', '').startswith('h') else 'Audio'
|
||||||
|
logging.info(f"Stream {i} ({stream_type}):")
|
||||||
|
for key, value in stream.items():
|
||||||
|
logging.info(f" {key}: {value}")
|
||||||
|
|
||||||
|
# Skip if output file already exists
|
||||||
|
if output_path.exists():
|
||||||
|
output_size = output_path.stat().st_size
|
||||||
|
logging.info(f"Skipping {input_path} - output already exists: {output_path}")
|
||||||
|
logging.info(f"Output size: {format_size(output_size)}")
|
||||||
|
print(f"{Colors.YELLOW}Skipping {input_path} - output already exists{Colors.ENDC}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get audio labels
|
||||||
|
audio_labels = get_audio_labels(str(input_path))
|
||||||
|
logging.info(f"Audio labels: {audio_labels}")
|
||||||
|
|
||||||
|
# FFmpeg command with NVIDIA HEVC encoder and maximum quality
|
||||||
|
cmd = [
|
||||||
|
'ffmpeg',
|
||||||
|
'-v', 'verbose', # Enable verbose output
|
||||||
|
'-i', str(input_path),
|
||||||
|
'-c:v', 'hevc_nvenc',
|
||||||
|
'-gpu', gpu,
|
||||||
|
'-preset', 'p7',
|
||||||
|
'-tune', 'hq',
|
||||||
|
'-rc', 'vbr', # Variable bitrate mode
|
||||||
|
'-rc-lookahead', '32', # Increase lookahead for better VBR decisions
|
||||||
|
'-spatial-aq', '1', # Enable spatial AQ for better quality
|
||||||
|
'-aq-strength', '15', # Maximum allowed AQ strength
|
||||||
|
'-cq', '23',
|
||||||
|
'-b:v', '0', # Let VBR determine bitrate
|
||||||
|
'-c:a', 'copy',
|
||||||
|
'-map', '0',
|
||||||
|
]
|
||||||
|
# Add metadata for each audio stream if label exists
|
||||||
|
for idx, label in enumerate(audio_labels):
|
||||||
|
if label:
|
||||||
|
cmd += [f'-metadata:s:a:{idx}', f'title={label}']
|
||||||
|
cmd.append(str(output_path))
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Run FFmpeg and capture output
|
||||||
|
process = subprocess.Popen(
|
||||||
|
cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
universal_newlines=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Log FFmpeg output in real-time
|
||||||
|
while True:
|
||||||
|
output = process.stderr.readline()
|
||||||
|
if output == '' and process.poll() is not None:
|
||||||
|
break
|
||||||
|
if output:
|
||||||
|
logging.info(f"FFmpeg: {output.strip()}")
|
||||||
|
print(f"{Colors.PURPLE}FFmpeg: {output.strip()}{Colors.ENDC}")
|
||||||
|
|
||||||
|
if process.returncode == 0:
|
||||||
|
# Get output file info
|
||||||
|
output_info = get_file_info(str(output_path))
|
||||||
|
output_size = int(output_info['format']['size'])
|
||||||
|
compression_ratio = input_size / output_size if output_size > 0 else 0
|
||||||
|
|
||||||
|
logging.info(f"Successfully encoded: {output_path}")
|
||||||
|
logging.info(f"Output size: {format_size(output_size)}")
|
||||||
|
logging.info(f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
|
print(f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
|
||||||
|
print(f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
|
else:
|
||||||
|
logging.error(f"FFmpeg process failed with return code {process.returncode}")
|
||||||
|
print(f"{Colors.RED}FFmpeg process failed with return code {process.returncode}{Colors.ENDC}")
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logging.error(f"Error encoding {input_path}: {e}")
|
||||||
|
print(f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Get GPU selection
|
||||||
|
gpu = get_gpu_selection()
|
||||||
|
logging.info(f"Selected GPU: {gpu}")
|
||||||
|
|
||||||
|
input_dir = "input"
|
||||||
|
output_dir = "output"
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Get list of files to process
|
||||||
|
files = [f for f in os.listdir(input_dir) if f.endswith(('.mp4', '.DVR.mp4'))]
|
||||||
|
total_files = len(files)
|
||||||
|
|
||||||
|
if total_files == 0:
|
||||||
|
logging.info("No files to process in input directory")
|
||||||
|
print(f"{Colors.YELLOW}No files to process in input directory{Colors.ENDC}")
|
||||||
|
else:
|
||||||
|
logging.info(f"Found {total_files} files to process")
|
||||||
|
print(f"{Colors.BLUE}Found {total_files} files to process{Colors.ENDC}")
|
||||||
|
|
||||||
|
for i, file in enumerate(files, 1):
|
||||||
|
input_file = os.path.join(input_dir, file)
|
||||||
|
logging.info(f"Processing file {i}/{total_files}: {file}")
|
||||||
|
print(f"\n{Colors.BLUE}Processing file {i}/{total_files}: {file}{Colors.ENDC}")
|
||||||
|
encode_dvr(input_file, output_dir, gpu)
|
||||||
3
encode_dvr_high.bat
Normal file
3
encode_dvr_high.bat
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@echo off
|
||||||
|
python encode_dvr_high.py
|
||||||
|
pause
|
||||||
191
encode_dvr_high.py
Normal file
191
encode_dvr_high.py
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
# ANSI color codes
|
||||||
|
class Colors:
|
||||||
|
PURPLE = '\033[95m'
|
||||||
|
BLUE = '\033[94m'
|
||||||
|
GREEN = '\033[92m'
|
||||||
|
YELLOW = '\033[93m'
|
||||||
|
RED = '\033[91m'
|
||||||
|
ENDC = '\033[0m'
|
||||||
|
|
||||||
|
def get_gpu_selection():
|
||||||
|
while True:
|
||||||
|
print(f"\n{Colors.BLUE}Select GPU slot:{Colors.ENDC}")
|
||||||
|
print("0 - First GPU")
|
||||||
|
print("1 - Second GPU")
|
||||||
|
print("2 - Third GPU")
|
||||||
|
gpu = input(f"{Colors.YELLOW}Enter GPU number (0-2):{Colors.ENDC} ").strip()
|
||||||
|
if gpu in ['0', '1', '2']:
|
||||||
|
return gpu
|
||||||
|
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
|
||||||
|
|
||||||
|
# Set up logging
|
||||||
|
log_dir = "logs"
|
||||||
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
log_file = os.path.join(log_dir, f"encode_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
|
||||||
|
logging.basicConfig(filename=log_file, level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
|
||||||
|
def get_file_info(input_file):
|
||||||
|
cmd = [
|
||||||
|
'ffprobe',
|
||||||
|
'-v', 'error',
|
||||||
|
'-show_entries', 'format=duration,size:stream=codec_name,width,height,r_frame_rate,channels,channel_layout',
|
||||||
|
'-of', 'json',
|
||||||
|
input_file
|
||||||
|
]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
return json.loads(result.stdout)
|
||||||
|
|
||||||
|
def get_audio_labels(input_file):
|
||||||
|
cmd = [
|
||||||
|
'ffprobe',
|
||||||
|
'-v', 'error',
|
||||||
|
'-select_streams', 'a',
|
||||||
|
'-show_entries', 'stream=index:stream_tags=title',
|
||||||
|
'-of', 'json',
|
||||||
|
input_file
|
||||||
|
]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
info = json.loads(result.stdout)
|
||||||
|
labels = []
|
||||||
|
for stream in info.get('streams', []):
|
||||||
|
title = stream.get('tags', {}).get('title', None)
|
||||||
|
labels.append(title)
|
||||||
|
return labels
|
||||||
|
|
||||||
|
def format_size(size_bytes):
|
||||||
|
for unit in ['B', 'KB', 'MB', 'GB']:
|
||||||
|
if size_bytes < 1024:
|
||||||
|
return f"{size_bytes:.2f} {unit}"
|
||||||
|
size_bytes /= 1024
|
||||||
|
return f"{size_bytes:.2f} TB"
|
||||||
|
|
||||||
|
def encode_dvr(input_file, output_dir, gpu):
|
||||||
|
input_path = Path(input_file)
|
||||||
|
output_path = Path(output_dir) / f"{input_path.stem}{input_path.suffix}"
|
||||||
|
|
||||||
|
# Get file info for logging
|
||||||
|
file_info = get_file_info(str(input_path))
|
||||||
|
input_size = int(file_info['format']['size'])
|
||||||
|
duration = float(file_info['format']['duration'])
|
||||||
|
|
||||||
|
logging.info(f"Processing file: {input_path}")
|
||||||
|
logging.info(f"Input size: {format_size(input_size)}")
|
||||||
|
logging.info(f"Duration: {duration:.2f} seconds")
|
||||||
|
|
||||||
|
print(f"\n{Colors.BLUE}Processing file: {input_path}{Colors.ENDC}")
|
||||||
|
print(f"Input size: {format_size(input_size)}")
|
||||||
|
print(f"Duration: {duration:.2f} seconds")
|
||||||
|
|
||||||
|
# Log stream information
|
||||||
|
for i, stream in enumerate(file_info.get('streams', [])):
|
||||||
|
stream_type = 'Video' if stream.get('codec_name', '').startswith('h') else 'Audio'
|
||||||
|
logging.info(f"Stream {i} ({stream_type}):")
|
||||||
|
for key, value in stream.items():
|
||||||
|
logging.info(f" {key}: {value}")
|
||||||
|
|
||||||
|
# Skip if output file already exists
|
||||||
|
if output_path.exists():
|
||||||
|
output_size = output_path.stat().st_size
|
||||||
|
logging.info(f"Skipping {input_path} - output already exists: {output_path}")
|
||||||
|
logging.info(f"Output size: {format_size(output_size)}")
|
||||||
|
print(f"{Colors.YELLOW}Skipping {input_path} - output already exists{Colors.ENDC}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get audio labels
|
||||||
|
audio_labels = get_audio_labels(str(input_path))
|
||||||
|
logging.info(f"Audio labels: {audio_labels}")
|
||||||
|
|
||||||
|
# FFmpeg command with NVIDIA HEVC encoder and maximum quality
|
||||||
|
cmd = [
|
||||||
|
'ffmpeg',
|
||||||
|
'-v', 'verbose', # Enable verbose output
|
||||||
|
'-i', str(input_path),
|
||||||
|
'-c:v', 'hevc_nvenc',
|
||||||
|
'-gpu', gpu,
|
||||||
|
'-preset', 'p7',
|
||||||
|
'-tune', 'hq',
|
||||||
|
'-rc', 'vbr', # Variable bitrate mode
|
||||||
|
'-rc-lookahead', '32', # Increase lookahead for better VBR decisions
|
||||||
|
'-spatial-aq', '1', # Enable spatial AQ for better quality
|
||||||
|
'-aq-strength', '15', # Maximum allowed AQ strength
|
||||||
|
'-cq', '30',
|
||||||
|
'-b:v', '0', # Let VBR determine bitrate
|
||||||
|
'-c:a', 'copy',
|
||||||
|
'-map', '0',
|
||||||
|
]
|
||||||
|
# Add metadata for each audio stream if label exists
|
||||||
|
for idx, label in enumerate(audio_labels):
|
||||||
|
if label:
|
||||||
|
cmd += [f'-metadata:s:a:{idx}', f'title={label}']
|
||||||
|
cmd.append(str(output_path))
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Run FFmpeg and capture output
|
||||||
|
process = subprocess.Popen(
|
||||||
|
cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
universal_newlines=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Log FFmpeg output in real-time
|
||||||
|
while True:
|
||||||
|
output = process.stderr.readline()
|
||||||
|
if output == '' and process.poll() is not None:
|
||||||
|
break
|
||||||
|
if output:
|
||||||
|
logging.info(f"FFmpeg: {output.strip()}")
|
||||||
|
print(f"{Colors.PURPLE}FFmpeg: {output.strip()}{Colors.ENDC}")
|
||||||
|
|
||||||
|
if process.returncode == 0:
|
||||||
|
# Get output file info
|
||||||
|
output_info = get_file_info(str(output_path))
|
||||||
|
output_size = int(output_info['format']['size'])
|
||||||
|
compression_ratio = input_size / output_size if output_size > 0 else 0
|
||||||
|
|
||||||
|
logging.info(f"Successfully encoded: {output_path}")
|
||||||
|
logging.info(f"Output size: {format_size(output_size)}")
|
||||||
|
logging.info(f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
|
print(f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
|
||||||
|
print(f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
|
else:
|
||||||
|
logging.error(f"FFmpeg process failed with return code {process.returncode}")
|
||||||
|
print(f"{Colors.RED}FFmpeg process failed with return code {process.returncode}{Colors.ENDC}")
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logging.error(f"Error encoding {input_path}: {e}")
|
||||||
|
print(f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Get GPU selection
|
||||||
|
gpu = get_gpu_selection()
|
||||||
|
logging.info(f"Selected GPU: {gpu}")
|
||||||
|
|
||||||
|
input_dir = "input"
|
||||||
|
output_dir = "output"
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Get list of files to process
|
||||||
|
files = [f for f in os.listdir(input_dir) if f.endswith(('.mp4', '.DVR.mp4'))]
|
||||||
|
total_files = len(files)
|
||||||
|
|
||||||
|
if total_files == 0:
|
||||||
|
logging.info("No files to process in input directory")
|
||||||
|
print(f"{Colors.YELLOW}No files to process in input directory{Colors.ENDC}")
|
||||||
|
else:
|
||||||
|
logging.info(f"Found {total_files} files to process")
|
||||||
|
print(f"{Colors.BLUE}Found {total_files} files to process{Colors.ENDC}")
|
||||||
|
|
||||||
|
for i, file in enumerate(files, 1):
|
||||||
|
input_file = os.path.join(input_dir, file)
|
||||||
|
logging.info(f"Processing file {i}/{total_files}: {file}")
|
||||||
|
print(f"\n{Colors.BLUE}Processing file {i}/{total_files}: {file}{Colors.ENDC}")
|
||||||
|
encode_dvr(input_file, output_dir, gpu)
|
||||||
3
encode_dvr_lossless.bat
Normal file
3
encode_dvr_lossless.bat
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@echo off
|
||||||
|
python encode_dvr_lossless.py
|
||||||
|
pause
|
||||||
191
encode_dvr_lossless.py
Normal file
191
encode_dvr_lossless.py
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
# ANSI color codes
|
||||||
|
class Colors:
|
||||||
|
PURPLE = '\033[95m'
|
||||||
|
BLUE = '\033[94m'
|
||||||
|
GREEN = '\033[92m'
|
||||||
|
YELLOW = '\033[93m'
|
||||||
|
RED = '\033[91m'
|
||||||
|
ENDC = '\033[0m'
|
||||||
|
|
||||||
|
def get_gpu_selection():
|
||||||
|
while True:
|
||||||
|
print(f"\n{Colors.BLUE}Select GPU slot:{Colors.ENDC}")
|
||||||
|
print("0 - First GPU")
|
||||||
|
print("1 - Second GPU")
|
||||||
|
print("2 - Third GPU")
|
||||||
|
gpu = input(f"{Colors.YELLOW}Enter GPU number (0-2):{Colors.ENDC} ").strip()
|
||||||
|
if gpu in ['0', '1', '2']:
|
||||||
|
return gpu
|
||||||
|
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
|
||||||
|
|
||||||
|
# Set up logging
|
||||||
|
log_dir = "logs"
|
||||||
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
log_file = os.path.join(log_dir, f"encode_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
|
||||||
|
logging.basicConfig(filename=log_file, level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
|
||||||
|
def get_file_info(input_file):
|
||||||
|
cmd = [
|
||||||
|
'ffprobe',
|
||||||
|
'-v', 'error',
|
||||||
|
'-show_entries', 'format=duration,size:stream=codec_name,width,height,r_frame_rate,channels,channel_layout',
|
||||||
|
'-of', 'json',
|
||||||
|
input_file
|
||||||
|
]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
return json.loads(result.stdout)
|
||||||
|
|
||||||
|
def get_audio_labels(input_file):
|
||||||
|
cmd = [
|
||||||
|
'ffprobe',
|
||||||
|
'-v', 'error',
|
||||||
|
'-select_streams', 'a',
|
||||||
|
'-show_entries', 'stream=index:stream_tags=title',
|
||||||
|
'-of', 'json',
|
||||||
|
input_file
|
||||||
|
]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
info = json.loads(result.stdout)
|
||||||
|
labels = []
|
||||||
|
for stream in info.get('streams', []):
|
||||||
|
title = stream.get('tags', {}).get('title', None)
|
||||||
|
labels.append(title)
|
||||||
|
return labels
|
||||||
|
|
||||||
|
def format_size(size_bytes):
|
||||||
|
for unit in ['B', 'KB', 'MB', 'GB']:
|
||||||
|
if size_bytes < 1024:
|
||||||
|
return f"{size_bytes:.2f} {unit}"
|
||||||
|
size_bytes /= 1024
|
||||||
|
return f"{size_bytes:.2f} TB"
|
||||||
|
|
||||||
|
def encode_dvr(input_file, output_dir, gpu):
|
||||||
|
input_path = Path(input_file)
|
||||||
|
output_path = Path(output_dir) / f"{input_path.stem}{input_path.suffix}"
|
||||||
|
|
||||||
|
# Get file info for logging
|
||||||
|
file_info = get_file_info(str(input_path))
|
||||||
|
input_size = int(file_info['format']['size'])
|
||||||
|
duration = float(file_info['format']['duration'])
|
||||||
|
|
||||||
|
logging.info(f"Processing file: {input_path}")
|
||||||
|
logging.info(f"Input size: {format_size(input_size)}")
|
||||||
|
logging.info(f"Duration: {duration:.2f} seconds")
|
||||||
|
|
||||||
|
print(f"\n{Colors.BLUE}Processing file: {input_path}{Colors.ENDC}")
|
||||||
|
print(f"Input size: {format_size(input_size)}")
|
||||||
|
print(f"Duration: {duration:.2f} seconds")
|
||||||
|
|
||||||
|
# Log stream information
|
||||||
|
for i, stream in enumerate(file_info.get('streams', [])):
|
||||||
|
stream_type = 'Video' if stream.get('codec_name', '').startswith('h') else 'Audio'
|
||||||
|
logging.info(f"Stream {i} ({stream_type}):")
|
||||||
|
for key, value in stream.items():
|
||||||
|
logging.info(f" {key}: {value}")
|
||||||
|
|
||||||
|
# Skip if output file already exists
|
||||||
|
if output_path.exists():
|
||||||
|
output_size = output_path.stat().st_size
|
||||||
|
logging.info(f"Skipping {input_path} - output already exists: {output_path}")
|
||||||
|
logging.info(f"Output size: {format_size(output_size)}")
|
||||||
|
print(f"{Colors.YELLOW}Skipping {input_path} - output already exists{Colors.ENDC}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get audio labels
|
||||||
|
audio_labels = get_audio_labels(str(input_path))
|
||||||
|
logging.info(f"Audio labels: {audio_labels}")
|
||||||
|
|
||||||
|
# FFmpeg command with NVIDIA HEVC encoder and maximum quality
|
||||||
|
cmd = [
|
||||||
|
'ffmpeg',
|
||||||
|
'-v', 'verbose', # Enable verbose output
|
||||||
|
'-i', str(input_path),
|
||||||
|
'-c:v', 'hevc_nvenc',
|
||||||
|
'-gpu', gpu,
|
||||||
|
'-preset', 'p7',
|
||||||
|
'-tune', 'hq',
|
||||||
|
'-rc', 'vbr', # Variable bitrate mode
|
||||||
|
'-rc-lookahead', '32', # Increase lookahead for better VBR decisions
|
||||||
|
'-spatial-aq', '1', # Enable spatial AQ for better quality
|
||||||
|
'-aq-strength', '15', # Maximum allowed AQ strength
|
||||||
|
'-cq', '0',
|
||||||
|
'-b:v', '0', # Let VBR determine bitrate
|
||||||
|
'-c:a', 'copy',
|
||||||
|
'-map', '0',
|
||||||
|
]
|
||||||
|
# Add metadata for each audio stream if label exists
|
||||||
|
for idx, label in enumerate(audio_labels):
|
||||||
|
if label:
|
||||||
|
cmd += [f'-metadata:s:a:{idx}', f'title={label}']
|
||||||
|
cmd.append(str(output_path))
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Run FFmpeg and capture output
|
||||||
|
process = subprocess.Popen(
|
||||||
|
cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
universal_newlines=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Log FFmpeg output in real-time
|
||||||
|
while True:
|
||||||
|
output = process.stderr.readline()
|
||||||
|
if output == '' and process.poll() is not None:
|
||||||
|
break
|
||||||
|
if output:
|
||||||
|
logging.info(f"FFmpeg: {output.strip()}")
|
||||||
|
print(f"{Colors.PURPLE}FFmpeg: {output.strip()}{Colors.ENDC}")
|
||||||
|
|
||||||
|
if process.returncode == 0:
|
||||||
|
# Get output file info
|
||||||
|
output_info = get_file_info(str(output_path))
|
||||||
|
output_size = int(output_info['format']['size'])
|
||||||
|
compression_ratio = input_size / output_size if output_size > 0 else 0
|
||||||
|
|
||||||
|
logging.info(f"Successfully encoded: {output_path}")
|
||||||
|
logging.info(f"Output size: {format_size(output_size)}")
|
||||||
|
logging.info(f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
|
print(f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
|
||||||
|
print(f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
|
else:
|
||||||
|
logging.error(f"FFmpeg process failed with return code {process.returncode}")
|
||||||
|
print(f"{Colors.RED}FFmpeg process failed with return code {process.returncode}{Colors.ENDC}")
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logging.error(f"Error encoding {input_path}: {e}")
|
||||||
|
print(f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Get GPU selection
|
||||||
|
gpu = get_gpu_selection()
|
||||||
|
logging.info(f"Selected GPU: {gpu}")
|
||||||
|
|
||||||
|
input_dir = "input"
|
||||||
|
output_dir = "output"
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Get list of files to process
|
||||||
|
files = [f for f in os.listdir(input_dir) if f.endswith(('.mp4', '.DVR.mp4'))]
|
||||||
|
total_files = len(files)
|
||||||
|
|
||||||
|
if total_files == 0:
|
||||||
|
logging.info("No files to process in input directory")
|
||||||
|
print(f"{Colors.YELLOW}No files to process in input directory{Colors.ENDC}")
|
||||||
|
else:
|
||||||
|
logging.info(f"Found {total_files} files to process")
|
||||||
|
print(f"{Colors.BLUE}Found {total_files} files to process{Colors.ENDC}")
|
||||||
|
|
||||||
|
for i, file in enumerate(files, 1):
|
||||||
|
input_file = os.path.join(input_dir, file)
|
||||||
|
logging.info(f"Processing file {i}/{total_files}: {file}")
|
||||||
|
print(f"\n{Colors.BLUE}Processing file {i}/{total_files}: {file}{Colors.ENDC}")
|
||||||
|
encode_dvr(input_file, output_dir, gpu)
|
||||||
3
encode_dvr_lossless_maxrate.bat
Normal file
3
encode_dvr_lossless_maxrate.bat
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@echo off
|
||||||
|
python encode_dvr_lossless_maxrate.py
|
||||||
|
pause
|
||||||
192
encode_dvr_lossless_maxrate.py
Normal file
192
encode_dvr_lossless_maxrate.py
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
# ANSI color codes
|
||||||
|
class Colors:
|
||||||
|
PURPLE = '\033[95m'
|
||||||
|
BLUE = '\033[94m'
|
||||||
|
GREEN = '\033[92m'
|
||||||
|
YELLOW = '\033[93m'
|
||||||
|
RED = '\033[91m'
|
||||||
|
ENDC = '\033[0m'
|
||||||
|
|
||||||
|
def get_gpu_selection():
|
||||||
|
while True:
|
||||||
|
print(f"\n{Colors.BLUE}Select GPU slot:{Colors.ENDC}")
|
||||||
|
print("0 - First GPU")
|
||||||
|
print("1 - Second GPU")
|
||||||
|
print("2 - Third GPU")
|
||||||
|
gpu = input(f"{Colors.YELLOW}Enter GPU number (0-2):{Colors.ENDC} ").strip()
|
||||||
|
if gpu in ['0', '1', '2']:
|
||||||
|
return gpu
|
||||||
|
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
|
||||||
|
|
||||||
|
# Set up logging
|
||||||
|
log_dir = "logs"
|
||||||
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
log_file = os.path.join(log_dir, f"encode_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
|
||||||
|
logging.basicConfig(filename=log_file, level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
|
||||||
|
def get_file_info(input_file):
|
||||||
|
cmd = [
|
||||||
|
'ffprobe',
|
||||||
|
'-v', 'error',
|
||||||
|
'-show_entries', 'format=duration,size:stream=codec_name,width,height,r_frame_rate,channels,channel_layout',
|
||||||
|
'-of', 'json',
|
||||||
|
input_file
|
||||||
|
]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
return json.loads(result.stdout)
|
||||||
|
|
||||||
|
def get_audio_labels(input_file):
|
||||||
|
cmd = [
|
||||||
|
'ffprobe',
|
||||||
|
'-v', 'error',
|
||||||
|
'-select_streams', 'a',
|
||||||
|
'-show_entries', 'stream=index:stream_tags=title',
|
||||||
|
'-of', 'json',
|
||||||
|
input_file
|
||||||
|
]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
info = json.loads(result.stdout)
|
||||||
|
labels = []
|
||||||
|
for stream in info.get('streams', []):
|
||||||
|
title = stream.get('tags', {}).get('title', None)
|
||||||
|
labels.append(title)
|
||||||
|
return labels
|
||||||
|
|
||||||
|
def format_size(size_bytes):
|
||||||
|
for unit in ['B', 'KB', 'MB', 'GB']:
|
||||||
|
if size_bytes < 1024:
|
||||||
|
return f"{size_bytes:.2f} {unit}"
|
||||||
|
size_bytes /= 1024
|
||||||
|
return f"{size_bytes:.2f} TB"
|
||||||
|
|
||||||
|
def encode_dvr(input_file, output_dir, gpu):
|
||||||
|
input_path = Path(input_file)
|
||||||
|
output_path = Path(output_dir) / f"{input_path.stem}{input_path.suffix}"
|
||||||
|
|
||||||
|
# Get file info for logging
|
||||||
|
file_info = get_file_info(str(input_path))
|
||||||
|
input_size = int(file_info['format']['size'])
|
||||||
|
duration = float(file_info['format']['duration'])
|
||||||
|
|
||||||
|
logging.info(f"Processing file: {input_path}")
|
||||||
|
logging.info(f"Input size: {format_size(input_size)}")
|
||||||
|
logging.info(f"Duration: {duration:.2f} seconds")
|
||||||
|
|
||||||
|
print(f"\n{Colors.BLUE}Processing file: {input_path}{Colors.ENDC}")
|
||||||
|
print(f"Input size: {format_size(input_size)}")
|
||||||
|
print(f"Duration: {duration:.2f} seconds")
|
||||||
|
|
||||||
|
# Log stream information
|
||||||
|
for i, stream in enumerate(file_info.get('streams', [])):
|
||||||
|
stream_type = 'Video' if stream.get('codec_name', '').startswith('h') else 'Audio'
|
||||||
|
logging.info(f"Stream {i} ({stream_type}):")
|
||||||
|
for key, value in stream.items():
|
||||||
|
logging.info(f" {key}: {value}")
|
||||||
|
|
||||||
|
# Skip if output file already exists
|
||||||
|
if output_path.exists():
|
||||||
|
output_size = output_path.stat().st_size
|
||||||
|
logging.info(f"Skipping {input_path} - output already exists: {output_path}")
|
||||||
|
logging.info(f"Output size: {format_size(output_size)}")
|
||||||
|
print(f"{Colors.YELLOW}Skipping {input_path} - output already exists{Colors.ENDC}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get audio labels
|
||||||
|
audio_labels = get_audio_labels(str(input_path))
|
||||||
|
logging.info(f"Audio labels: {audio_labels}")
|
||||||
|
|
||||||
|
# FFmpeg command with NVIDIA HEVC encoder and maximum quality
|
||||||
|
cmd = [
|
||||||
|
'ffmpeg',
|
||||||
|
'-v', 'verbose',
|
||||||
|
'-i', str(input_path),
|
||||||
|
'-c:v', 'hevc_nvenc',
|
||||||
|
'-gpu', gpu,
|
||||||
|
'-preset', 'p7',
|
||||||
|
'-tune', 'hq',
|
||||||
|
'-rc', 'vbr',
|
||||||
|
'-rc-lookahead', '32',
|
||||||
|
'-spatial-aq', '1',
|
||||||
|
'-aq-strength', '15',
|
||||||
|
'-cq', '0',
|
||||||
|
'-b:v', '0',
|
||||||
|
'-maxrate', '10000k',
|
||||||
|
'-c:a', 'copy',
|
||||||
|
'-map', '0',
|
||||||
|
]
|
||||||
|
# Add metadata for each audio stream if label exists
|
||||||
|
for idx, label in enumerate(audio_labels):
|
||||||
|
if label:
|
||||||
|
cmd += [f'-metadata:s:a:{idx}', f'title={label}']
|
||||||
|
cmd.append(str(output_path))
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Run FFmpeg and capture output
|
||||||
|
process = subprocess.Popen(
|
||||||
|
cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
universal_newlines=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Log FFmpeg output in real-time
|
||||||
|
while True:
|
||||||
|
output = process.stderr.readline()
|
||||||
|
if output == '' and process.poll() is not None:
|
||||||
|
break
|
||||||
|
if output:
|
||||||
|
logging.info(f"FFmpeg: {output.strip()}")
|
||||||
|
print(f"{Colors.PURPLE}FFmpeg: {output.strip()}{Colors.ENDC}")
|
||||||
|
|
||||||
|
if process.returncode == 0:
|
||||||
|
# Get output file info
|
||||||
|
output_info = get_file_info(str(output_path))
|
||||||
|
output_size = int(output_info['format']['size'])
|
||||||
|
compression_ratio = input_size / output_size if output_size > 0 else 0
|
||||||
|
|
||||||
|
logging.info(f"Successfully encoded: {output_path}")
|
||||||
|
logging.info(f"Output size: {format_size(output_size)}")
|
||||||
|
logging.info(f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
|
print(f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
|
||||||
|
print(f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
|
else:
|
||||||
|
logging.error(f"FFmpeg process failed with return code {process.returncode}")
|
||||||
|
print(f"{Colors.RED}FFmpeg process failed with return code {process.returncode}{Colors.ENDC}")
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logging.error(f"Error encoding {input_path}: {e}")
|
||||||
|
print(f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Get GPU selection
|
||||||
|
gpu = get_gpu_selection()
|
||||||
|
logging.info(f"Selected GPU: {gpu}")
|
||||||
|
|
||||||
|
input_dir = "input"
|
||||||
|
output_dir = "output"
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Get list of files to process
|
||||||
|
files = [f for f in os.listdir(input_dir) if f.endswith(('.mp4', '.DVR.mp4'))]
|
||||||
|
total_files = len(files)
|
||||||
|
|
||||||
|
if total_files == 0:
|
||||||
|
logging.info("No files to process in input directory")
|
||||||
|
print(f"{Colors.YELLOW}No files to process in input directory{Colors.ENDC}")
|
||||||
|
else:
|
||||||
|
logging.info(f"Found {total_files} files to process")
|
||||||
|
print(f"{Colors.BLUE}Found {total_files} files to process{Colors.ENDC}")
|
||||||
|
|
||||||
|
for i, file in enumerate(files, 1):
|
||||||
|
input_file = os.path.join(input_dir, file)
|
||||||
|
logging.info(f"Processing file {i}/{total_files}: {file}")
|
||||||
|
print(f"\n{Colors.BLUE}Processing file {i}/{total_files}: {file}{Colors.ENDC}")
|
||||||
|
encode_dvr(input_file, output_dir, gpu)
|
||||||
3
encode_dvr_lossless_vp9.bat
Normal file
3
encode_dvr_lossless_vp9.bat
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@echo off
|
||||||
|
python encode_dvr_lossless_vp9.py
|
||||||
|
pause
|
||||||
191
encode_dvr_lossless_vp9.py
Normal file
191
encode_dvr_lossless_vp9.py
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
# ANSI color codes
|
||||||
|
class Colors:
|
||||||
|
PURPLE = '\033[95m'
|
||||||
|
BLUE = '\033[94m'
|
||||||
|
GREEN = '\033[92m'
|
||||||
|
YELLOW = '\033[93m'
|
||||||
|
RED = '\033[91m'
|
||||||
|
ENDC = '\033[0m'
|
||||||
|
|
||||||
|
def get_gpu_selection():
|
||||||
|
while True:
|
||||||
|
print(f"\n{Colors.BLUE}Select GPU slot:{Colors.ENDC}")
|
||||||
|
print("0 - First GPU")
|
||||||
|
print("1 - Second GPU")
|
||||||
|
print("2 - Third GPU")
|
||||||
|
gpu = input(f"{Colors.YELLOW}Enter GPU number (0-2):{Colors.ENDC} ").strip()
|
||||||
|
if gpu in ['0', '1', '2']:
|
||||||
|
return gpu
|
||||||
|
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
|
||||||
|
|
||||||
|
# Set up logging
|
||||||
|
log_dir = "logs"
|
||||||
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
log_file = os.path.join(log_dir, f"encode_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
|
||||||
|
logging.basicConfig(filename=log_file, level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
|
||||||
|
def get_file_info(input_file):
|
||||||
|
cmd = [
|
||||||
|
'ffprobe',
|
||||||
|
'-v', 'error',
|
||||||
|
'-show_entries', 'format=duration,size:stream=codec_name,width,height,r_frame_rate,channels,channel_layout',
|
||||||
|
'-of', 'json',
|
||||||
|
input_file
|
||||||
|
]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
return json.loads(result.stdout)
|
||||||
|
|
||||||
|
def get_audio_labels(input_file):
|
||||||
|
cmd = [
|
||||||
|
'ffprobe',
|
||||||
|
'-v', 'error',
|
||||||
|
'-select_streams', 'a',
|
||||||
|
'-show_entries', 'stream=index:stream_tags=title',
|
||||||
|
'-of', 'json',
|
||||||
|
input_file
|
||||||
|
]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
info = json.loads(result.stdout)
|
||||||
|
labels = []
|
||||||
|
for stream in info.get('streams', []):
|
||||||
|
title = stream.get('tags', {}).get('title', None)
|
||||||
|
labels.append(title)
|
||||||
|
return labels
|
||||||
|
|
||||||
|
def format_size(size_bytes):
|
||||||
|
for unit in ['B', 'KB', 'MB', 'GB']:
|
||||||
|
if size_bytes < 1024:
|
||||||
|
return f"{size_bytes:.2f} {unit}"
|
||||||
|
size_bytes /= 1024
|
||||||
|
return f"{size_bytes:.2f} TB"
|
||||||
|
|
||||||
|
def encode_dvr(input_file, output_dir, gpu):
|
||||||
|
input_path = Path(input_file)
|
||||||
|
output_path = Path(output_dir) / f"{input_path.stem}{input_path.suffix}"
|
||||||
|
|
||||||
|
# Get file info for logging
|
||||||
|
file_info = get_file_info(str(input_path))
|
||||||
|
input_size = int(file_info['format']['size'])
|
||||||
|
duration = float(file_info['format']['duration'])
|
||||||
|
|
||||||
|
logging.info(f"Processing file: {input_path}")
|
||||||
|
logging.info(f"Input size: {format_size(input_size)}")
|
||||||
|
logging.info(f"Duration: {duration:.2f} seconds")
|
||||||
|
|
||||||
|
print(f"\n{Colors.BLUE}Processing file: {input_path}{Colors.ENDC}")
|
||||||
|
print(f"Input size: {format_size(input_size)}")
|
||||||
|
print(f"Duration: {duration:.2f} seconds")
|
||||||
|
|
||||||
|
# Log stream information
|
||||||
|
for i, stream in enumerate(file_info.get('streams', [])):
|
||||||
|
stream_type = 'Video' if stream.get('codec_name', '').startswith('h') else 'Audio'
|
||||||
|
logging.info(f"Stream {i} ({stream_type}):")
|
||||||
|
for key, value in stream.items():
|
||||||
|
logging.info(f" {key}: {value}")
|
||||||
|
|
||||||
|
# Skip if output file already exists
|
||||||
|
if output_path.exists():
|
||||||
|
output_size = output_path.stat().st_size
|
||||||
|
logging.info(f"Skipping {input_path} - output already exists: {output_path}")
|
||||||
|
logging.info(f"Output size: {format_size(output_size)}")
|
||||||
|
print(f"{Colors.YELLOW}Skipping {input_path} - output already exists{Colors.ENDC}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get audio labels
|
||||||
|
audio_labels = get_audio_labels(str(input_path))
|
||||||
|
logging.info(f"Audio labels: {audio_labels}")
|
||||||
|
|
||||||
|
# FFmpeg command for high-quality VP9 encoding
|
||||||
|
cmd = [
|
||||||
|
'ffmpeg',
|
||||||
|
'-v', 'verbose',
|
||||||
|
'-i', str(input_path),
|
||||||
|
'-c:v', 'libvpx-vp9',
|
||||||
|
'-crf', '0', # Extremely high quality (0=lossless, 5-10=extremely high)
|
||||||
|
'-b:v', '10000k',
|
||||||
|
'-maxrate', '10000k',
|
||||||
|
'-cpu-used', '0', # Slowest encoding for highest quality
|
||||||
|
'-row-mt', '1',
|
||||||
|
'-threads', '0',
|
||||||
|
'-tile-columns', '2', # Better quality for 1080p
|
||||||
|
'-frame-parallel', '1', # Parallel frame processing
|
||||||
|
'-lag-in-frames', '25', # More frames for better compression
|
||||||
|
'-c:a', 'copy',
|
||||||
|
'-map', '0',
|
||||||
|
]
|
||||||
|
# Add metadata for each audio stream if label exists
|
||||||
|
for idx, label in enumerate(audio_labels):
|
||||||
|
if label:
|
||||||
|
cmd += [f'-metadata:s:a:{idx}', f'title={label}']
|
||||||
|
cmd.append(str(output_path))
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Run FFmpeg and capture output
|
||||||
|
process = subprocess.Popen(
|
||||||
|
cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
universal_newlines=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Log FFmpeg output in real-time
|
||||||
|
while True:
|
||||||
|
output = process.stderr.readline()
|
||||||
|
if output == '' and process.poll() is not None:
|
||||||
|
break
|
||||||
|
if output:
|
||||||
|
logging.info(f"FFmpeg: {output.strip()}")
|
||||||
|
print(f"{Colors.PURPLE}FFmpeg: {output.strip()}{Colors.ENDC}")
|
||||||
|
|
||||||
|
if process.returncode == 0:
|
||||||
|
# Get output file info
|
||||||
|
output_info = get_file_info(str(output_path))
|
||||||
|
output_size = int(output_info['format']['size'])
|
||||||
|
compression_ratio = input_size / output_size if output_size > 0 else 0
|
||||||
|
|
||||||
|
logging.info(f"Successfully encoded: {output_path}")
|
||||||
|
logging.info(f"Output size: {format_size(output_size)}")
|
||||||
|
logging.info(f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
|
print(f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
|
||||||
|
print(f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
|
else:
|
||||||
|
logging.error(f"FFmpeg process failed with return code {process.returncode}")
|
||||||
|
print(f"{Colors.RED}FFmpeg process failed with return code {process.returncode}{Colors.ENDC}")
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logging.error(f"Error encoding {input_path}: {e}")
|
||||||
|
print(f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Get GPU selection
|
||||||
|
gpu = get_gpu_selection()
|
||||||
|
logging.info(f"Selected GPU: {gpu}")
|
||||||
|
|
||||||
|
input_dir = "input"
|
||||||
|
output_dir = "output"
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Get list of files to process
|
||||||
|
files = [f for f in os.listdir(input_dir) if f.endswith(('.mp4', '.DVR.mp4'))]
|
||||||
|
total_files = len(files)
|
||||||
|
|
||||||
|
if total_files == 0:
|
||||||
|
logging.info("No files to process in input directory")
|
||||||
|
print(f"{Colors.YELLOW}No files to process in input directory{Colors.ENDC}")
|
||||||
|
else:
|
||||||
|
logging.info(f"Found {total_files} files to process")
|
||||||
|
print(f"{Colors.BLUE}Found {total_files} files to process{Colors.ENDC}")
|
||||||
|
|
||||||
|
for i, file in enumerate(files, 1):
|
||||||
|
input_file = os.path.join(input_dir, file)
|
||||||
|
logging.info(f"Processing file {i}/{total_files}: {file}")
|
||||||
|
print(f"\n{Colors.BLUE}Processing file {i}/{total_files}: {file}{Colors.ENDC}")
|
||||||
|
encode_dvr(input_file, output_dir, gpu)
|
||||||
3
encode_vtubrec2500.bat
Normal file
3
encode_vtubrec2500.bat
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@echo off
|
||||||
|
python encode_vtubrec2500.py
|
||||||
|
pause
|
||||||
193
encode_vtubrec2500.py
Normal file
193
encode_vtubrec2500.py
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
# ANSI color codes
|
||||||
|
class Colors:
|
||||||
|
PURPLE = '\033[95m'
|
||||||
|
BLUE = '\033[94m'
|
||||||
|
GREEN = '\033[92m'
|
||||||
|
YELLOW = '\033[93m'
|
||||||
|
RED = '\033[91m'
|
||||||
|
ENDC = '\033[0m'
|
||||||
|
|
||||||
|
def get_gpu_selection():
|
||||||
|
while True:
|
||||||
|
print(f"\n{Colors.BLUE}Select GPU slot:{Colors.ENDC}")
|
||||||
|
print("0 - First GPU")
|
||||||
|
print("1 - Second GPU")
|
||||||
|
print("2 - Third GPU")
|
||||||
|
gpu = input(f"{Colors.YELLOW}Enter GPU number (0-2):{Colors.ENDC} ").strip()
|
||||||
|
if gpu in ['0', '1', '2']:
|
||||||
|
return gpu
|
||||||
|
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
|
||||||
|
|
||||||
|
# Set up logging
|
||||||
|
log_dir = "logs"
|
||||||
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
log_file = os.path.join(log_dir, f"encode_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
|
||||||
|
logging.basicConfig(filename=log_file, level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
|
||||||
|
def get_file_info(input_file):
|
||||||
|
cmd = [
|
||||||
|
'ffprobe',
|
||||||
|
'-v', 'error',
|
||||||
|
'-show_entries', 'format=duration,size:stream=codec_name,width,height,r_frame_rate,channels,channel_layout',
|
||||||
|
'-of', 'json',
|
||||||
|
input_file
|
||||||
|
]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
return json.loads(result.stdout)
|
||||||
|
|
||||||
|
def get_audio_labels(input_file):
|
||||||
|
cmd = [
|
||||||
|
'ffprobe',
|
||||||
|
'-v', 'error',
|
||||||
|
'-select_streams', 'a',
|
||||||
|
'-show_entries', 'stream=index:stream_tags=title',
|
||||||
|
'-of', 'json',
|
||||||
|
input_file
|
||||||
|
]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
info = json.loads(result.stdout)
|
||||||
|
labels = []
|
||||||
|
for stream in info.get('streams', []):
|
||||||
|
title = stream.get('tags', {}).get('title', None)
|
||||||
|
labels.append(title)
|
||||||
|
return labels
|
||||||
|
|
||||||
|
def format_size(size_bytes):
|
||||||
|
for unit in ['B', 'KB', 'MB', 'GB']:
|
||||||
|
if size_bytes < 1024:
|
||||||
|
return f"{size_bytes:.2f} {unit}"
|
||||||
|
size_bytes /= 1024
|
||||||
|
return f"{size_bytes:.2f} TB"
|
||||||
|
|
||||||
|
def encode_dvr(input_file, output_dir, gpu):
|
||||||
|
input_path = Path(input_file)
|
||||||
|
output_path = Path(output_dir) / f"{input_path.stem}{input_path.suffix}"
|
||||||
|
|
||||||
|
# Get file info for logging
|
||||||
|
file_info = get_file_info(str(input_path))
|
||||||
|
input_size = int(file_info['format']['size'])
|
||||||
|
duration = float(file_info['format']['duration'])
|
||||||
|
|
||||||
|
logging.info(f"Processing file: {input_path}")
|
||||||
|
logging.info(f"Input size: {format_size(input_size)}")
|
||||||
|
logging.info(f"Duration: {duration:.2f} seconds")
|
||||||
|
|
||||||
|
print(f"\n{Colors.BLUE}Processing file: {input_path}{Colors.ENDC}")
|
||||||
|
print(f"Input size: {format_size(input_size)}")
|
||||||
|
print(f"Duration: {duration:.2f} seconds")
|
||||||
|
|
||||||
|
# Log stream information
|
||||||
|
for i, stream in enumerate(file_info.get('streams', [])):
|
||||||
|
stream_type = 'Video' if stream.get('codec_name', '').startswith('h') else 'Audio'
|
||||||
|
logging.info(f"Stream {i} ({stream_type}):")
|
||||||
|
for key, value in stream.items():
|
||||||
|
logging.info(f" {key}: {value}")
|
||||||
|
|
||||||
|
# Skip if output file already exists
|
||||||
|
if output_path.exists():
|
||||||
|
output_size = output_path.stat().st_size
|
||||||
|
logging.info(f"Skipping {input_path} - output already exists: {output_path}")
|
||||||
|
logging.info(f"Output size: {format_size(output_size)}")
|
||||||
|
print(f"{Colors.YELLOW}Skipping {input_path} - output already exists{Colors.ENDC}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get audio labels
|
||||||
|
audio_labels = get_audio_labels(str(input_path))
|
||||||
|
logging.info(f"Audio labels: {audio_labels}")
|
||||||
|
|
||||||
|
# FFmpeg command with NVIDIA HEVC encoder and maximum quality
|
||||||
|
cmd = [
|
||||||
|
'ffmpeg',
|
||||||
|
'-v', 'verbose',
|
||||||
|
'-i', str(input_path),
|
||||||
|
'-c:v', 'hevc_nvenc',
|
||||||
|
'-gpu', gpu,
|
||||||
|
'-preset', 'p7',
|
||||||
|
'-tune', 'hq',
|
||||||
|
'-rc', 'vbr',
|
||||||
|
'-rc-lookahead', '32',
|
||||||
|
'-spatial-aq', '1',
|
||||||
|
'-aq-strength', '15',
|
||||||
|
'-cq', '26',
|
||||||
|
'-b:v', '2500k',
|
||||||
|
'-maxrate', '2500k',
|
||||||
|
'-bufsize', '5000k',
|
||||||
|
'-c:a', 'copy',
|
||||||
|
'-map', '0',
|
||||||
|
]
|
||||||
|
# Add metadata for each audio stream if label exists
|
||||||
|
for idx, label in enumerate(audio_labels):
|
||||||
|
if label:
|
||||||
|
cmd += [f'-metadata:s:a:{idx}', f'title={label}']
|
||||||
|
cmd.append(str(output_path))
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Run FFmpeg and capture output
|
||||||
|
process = subprocess.Popen(
|
||||||
|
cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
universal_newlines=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Log FFmpeg output in real-time
|
||||||
|
while True:
|
||||||
|
output = process.stderr.readline()
|
||||||
|
if output == '' and process.poll() is not None:
|
||||||
|
break
|
||||||
|
if output:
|
||||||
|
logging.info(f"FFmpeg: {output.strip()}")
|
||||||
|
print(f"{Colors.PURPLE}FFmpeg: {output.strip()}{Colors.ENDC}")
|
||||||
|
|
||||||
|
if process.returncode == 0:
|
||||||
|
# Get output file info
|
||||||
|
output_info = get_file_info(str(output_path))
|
||||||
|
output_size = int(output_info['format']['size'])
|
||||||
|
compression_ratio = input_size / output_size if output_size > 0 else 0
|
||||||
|
|
||||||
|
logging.info(f"Successfully encoded: {output_path}")
|
||||||
|
logging.info(f"Output size: {format_size(output_size)}")
|
||||||
|
logging.info(f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
|
print(f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
|
||||||
|
print(f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
|
else:
|
||||||
|
logging.error(f"FFmpeg process failed with return code {process.returncode}")
|
||||||
|
print(f"{Colors.RED}FFmpeg process failed with return code {process.returncode}{Colors.ENDC}")
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logging.error(f"Error encoding {input_path}: {e}")
|
||||||
|
print(f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Get GPU selection
|
||||||
|
gpu = get_gpu_selection()
|
||||||
|
logging.info(f"Selected GPU: {gpu}")
|
||||||
|
|
||||||
|
input_dir = "input"
|
||||||
|
output_dir = "output"
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Get list of files to process
|
||||||
|
files = [f for f in os.listdir(input_dir) if f.endswith(('.mp4', '.DVR.mp4'))]
|
||||||
|
total_files = len(files)
|
||||||
|
|
||||||
|
if total_files == 0:
|
||||||
|
logging.info("No files to process in input directory")
|
||||||
|
print(f"{Colors.YELLOW}No files to process in input directory{Colors.ENDC}")
|
||||||
|
else:
|
||||||
|
logging.info(f"Found {total_files} files to process")
|
||||||
|
print(f"{Colors.BLUE}Found {total_files} files to process{Colors.ENDC}")
|
||||||
|
|
||||||
|
for i, file in enumerate(files, 1):
|
||||||
|
input_file = os.path.join(input_dir, file)
|
||||||
|
logging.info(f"Processing file {i}/{total_files}: {file}")
|
||||||
|
print(f"\n{Colors.BLUE}Processing file {i}/{total_files}: {file}{Colors.ENDC}")
|
||||||
|
encode_dvr(input_file, output_dir, gpu)
|
||||||
3
encode_vtubrec5k.bat
Normal file
3
encode_vtubrec5k.bat
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@echo off
|
||||||
|
python encode_vtubrec5k.py
|
||||||
|
pause
|
||||||
194
encode_vtubrec5k.py
Normal file
194
encode_vtubrec5k.py
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
# ANSI color codes
|
||||||
|
class Colors:
|
||||||
|
PURPLE = '\033[95m'
|
||||||
|
BLUE = '\033[94m'
|
||||||
|
GREEN = '\033[92m'
|
||||||
|
YELLOW = '\033[93m'
|
||||||
|
RED = '\033[91m'
|
||||||
|
ENDC = '\033[0m'
|
||||||
|
|
||||||
|
def get_gpu_selection():
|
||||||
|
while True:
|
||||||
|
print(f"\n{Colors.BLUE}Select GPU slot:{Colors.ENDC}")
|
||||||
|
print("0 - First GPU")
|
||||||
|
print("1 - Second GPU")
|
||||||
|
print("2 - Third GPU")
|
||||||
|
gpu = input(f"{Colors.YELLOW}Enter GPU number (0-2):{Colors.ENDC} ").strip()
|
||||||
|
if gpu in ['0', '1', '2']:
|
||||||
|
return gpu
|
||||||
|
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
|
||||||
|
|
||||||
|
# Set up logging
|
||||||
|
log_dir = "logs"
|
||||||
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
log_file = os.path.join(log_dir, f"encode_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
|
||||||
|
logging.basicConfig(filename=log_file, level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
|
||||||
|
def get_file_info(input_file):
|
||||||
|
cmd = [
|
||||||
|
'ffprobe',
|
||||||
|
'-v', 'error',
|
||||||
|
'-show_entries', 'format=duration,size:stream=codec_name,width,height,r_frame_rate,channels,channel_layout',
|
||||||
|
'-of', 'json',
|
||||||
|
input_file
|
||||||
|
]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
return json.loads(result.stdout)
|
||||||
|
|
||||||
|
def get_audio_labels(input_file):
|
||||||
|
cmd = [
|
||||||
|
'ffprobe',
|
||||||
|
'-v', 'error',
|
||||||
|
'-select_streams', 'a',
|
||||||
|
'-show_entries', 'stream=index:stream_tags=title',
|
||||||
|
'-of', 'json',
|
||||||
|
input_file
|
||||||
|
]
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
info = json.loads(result.stdout)
|
||||||
|
labels = []
|
||||||
|
for stream in info.get('streams', []):
|
||||||
|
title = stream.get('tags', {}).get('title', None)
|
||||||
|
labels.append(title)
|
||||||
|
return labels
|
||||||
|
|
||||||
|
def format_size(size_bytes):
|
||||||
|
for unit in ['B', 'KB', 'MB', 'GB']:
|
||||||
|
if size_bytes < 1024:
|
||||||
|
return f"{size_bytes:.2f} {unit}"
|
||||||
|
size_bytes /= 1024
|
||||||
|
return f"{size_bytes:.2f} TB"
|
||||||
|
|
||||||
|
def encode_dvr(input_file, output_dir, gpu):
|
||||||
|
input_path = Path(input_file)
|
||||||
|
output_path = Path(output_dir) / f"{input_path.stem}{input_path.suffix}"
|
||||||
|
|
||||||
|
# Get file info for logging
|
||||||
|
file_info = get_file_info(str(input_path))
|
||||||
|
input_size = int(file_info['format']['size'])
|
||||||
|
duration = float(file_info['format']['duration'])
|
||||||
|
|
||||||
|
logging.info(f"Processing file: {input_path}")
|
||||||
|
logging.info(f"Input size: {format_size(input_size)}")
|
||||||
|
logging.info(f"Duration: {duration:.2f} seconds")
|
||||||
|
|
||||||
|
print(f"\n{Colors.BLUE}Processing file: {input_path}{Colors.ENDC}")
|
||||||
|
print(f"Input size: {format_size(input_size)}")
|
||||||
|
print(f"Duration: {duration:.2f} seconds")
|
||||||
|
|
||||||
|
# Log stream information
|
||||||
|
for i, stream in enumerate(file_info.get('streams', [])):
|
||||||
|
stream_type = 'Video' if stream.get('codec_name', '').startswith('h') else 'Audio'
|
||||||
|
logging.info(f"Stream {i} ({stream_type}):")
|
||||||
|
for key, value in stream.items():
|
||||||
|
logging.info(f" {key}: {value}")
|
||||||
|
|
||||||
|
# Skip if output file already exists
|
||||||
|
if output_path.exists():
|
||||||
|
output_size = output_path.stat().st_size
|
||||||
|
logging.info(f"Skipping {input_path} - output already exists: {output_path}")
|
||||||
|
logging.info(f"Output size: {format_size(output_size)}")
|
||||||
|
print(f"{Colors.YELLOW}Skipping {input_path} - output already exists{Colors.ENDC}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get audio labels
|
||||||
|
audio_labels = get_audio_labels(str(input_path))
|
||||||
|
logging.info(f"Audio labels: {audio_labels}")
|
||||||
|
|
||||||
|
# FFmpeg command with NVIDIA HEVC encoder and maximum quality
|
||||||
|
cmd = [
|
||||||
|
'ffmpeg',
|
||||||
|
'-v', 'verbose',
|
||||||
|
'-i', str(input_path),
|
||||||
|
'-c:v', 'hevc_nvenc',
|
||||||
|
'-gpu', gpu,
|
||||||
|
'-preset', 'p7',
|
||||||
|
'-tune', 'hq',
|
||||||
|
'-rc', 'vbr',
|
||||||
|
'-rc-lookahead', '32',
|
||||||
|
'-spatial-aq', '1',
|
||||||
|
'-aq-strength', '15',
|
||||||
|
'-cq', '23',
|
||||||
|
'-b:v', '5000k',
|
||||||
|
'-maxrate', '5000k',
|
||||||
|
'-bufsize', '10000k',
|
||||||
|
'-b:a', '192k',
|
||||||
|
'-c:a', 'copy',
|
||||||
|
'-map', '0',
|
||||||
|
]
|
||||||
|
# Add metadata for each audio stream if label exists
|
||||||
|
for idx, label in enumerate(audio_labels):
|
||||||
|
if label:
|
||||||
|
cmd += [f'-metadata:s:a:{idx}', f'title={label}']
|
||||||
|
cmd.append(str(output_path))
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Run FFmpeg and capture output
|
||||||
|
process = subprocess.Popen(
|
||||||
|
cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
universal_newlines=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Log FFmpeg output in real-time
|
||||||
|
while True:
|
||||||
|
output = process.stderr.readline()
|
||||||
|
if output == '' and process.poll() is not None:
|
||||||
|
break
|
||||||
|
if output:
|
||||||
|
logging.info(f"FFmpeg: {output.strip()}")
|
||||||
|
print(f"{Colors.PURPLE}FFmpeg: {output.strip()}{Colors.ENDC}")
|
||||||
|
|
||||||
|
if process.returncode == 0:
|
||||||
|
# Get output file info
|
||||||
|
output_info = get_file_info(str(output_path))
|
||||||
|
output_size = int(output_info['format']['size'])
|
||||||
|
compression_ratio = input_size / output_size if output_size > 0 else 0
|
||||||
|
|
||||||
|
logging.info(f"Successfully encoded: {output_path}")
|
||||||
|
logging.info(f"Output size: {format_size(output_size)}")
|
||||||
|
logging.info(f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
|
print(f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
|
||||||
|
print(f"Compression ratio: {compression_ratio:.2f}x")
|
||||||
|
else:
|
||||||
|
logging.error(f"FFmpeg process failed with return code {process.returncode}")
|
||||||
|
print(f"{Colors.RED}FFmpeg process failed with return code {process.returncode}{Colors.ENDC}")
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logging.error(f"Error encoding {input_path}: {e}")
|
||||||
|
print(f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Get GPU selection
|
||||||
|
gpu = get_gpu_selection()
|
||||||
|
logging.info(f"Selected GPU: {gpu}")
|
||||||
|
|
||||||
|
input_dir = "input"
|
||||||
|
output_dir = "output"
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Get list of files to process
|
||||||
|
files = [f for f in os.listdir(input_dir) if f.endswith(('.mp4', '.DVR.mp4'))]
|
||||||
|
total_files = len(files)
|
||||||
|
|
||||||
|
if total_files == 0:
|
||||||
|
logging.info("No files to process in input directory")
|
||||||
|
print(f"{Colors.YELLOW}No files to process in input directory{Colors.ENDC}")
|
||||||
|
else:
|
||||||
|
logging.info(f"Found {total_files} files to process")
|
||||||
|
print(f"{Colors.BLUE}Found {total_files} files to process{Colors.ENDC}")
|
||||||
|
|
||||||
|
for i, file in enumerate(files, 1):
|
||||||
|
input_file = os.path.join(input_dir, file)
|
||||||
|
logging.info(f"Processing file {i}/{total_files}: {file}")
|
||||||
|
print(f"\n{Colors.BLUE}Processing file {i}/{total_files}: {file}{Colors.ENDC}")
|
||||||
|
encode_dvr(input_file, output_dir, gpu)
|
||||||
Reference in New Issue
Block a user