Compare commits

..

32 Commits

Author SHA1 Message Date
Raincloud 3d64eb3094 Refactor file handling in check_files.py to improve clarity and maintainability. Introduce helper functions for input/output file checks and expected output naming. Update main logic to utilize these functions for better readability. Adjust output file handling in encoding scripts to ensure consistent .mp4 naming convention across all modules. 2026-05-06 19:00:05 -06:00
Raincloud d1221f83b3 ignore specst 2026-04-19 17:26:17 -06:00
Raincloud 1ad613429a specst 2026-03-21 11:42:11 -06:00
Raincloud 427abd6870 ren input files 2026-03-21 11:41:27 -06:00
Raincloud 9c8cc0faca handle UTF-8 encoding check files 2026-03-18 18:06:44 -06:00
Raincloud 6588cb7957 handle UTF-8 encoding, error fix 2026-03-16 17:13:41 -06:00
Raincloud baf17ec369 bitrate input for a vidarchv per-VOD basis 2026-03-08 16:28:27 -06:00
Raincloud 02d901e57e purge distributed
The result will never be good because the encoder will be starting fresh every single chunk (minute) so the only solution is either larger chunk sizes, which somewhat defeats the purpose, or single worker-only jobs, which we've already been doing. Tuff
2026-03-08 15:40:05 -06:00
Raincloud da21cafe1c the audio is synced? 2026-02-19 07:45:42 -07:00
Raincloud 0201d3e8bd getting closer with sync issues 2026-02-16 18:32:05 -07:00
Raincloud 7b48b709ce cap concat output 2026-02-16 00:53:48 -07:00
Raincloud 70684f5644 swap back to nvenc/HEVC and redefine hosts accordingly 2026-02-16 00:37:04 -07:00
Raincloud dc7b224005 adjust stopping behavior 2026-02-16 00:09:06 -07:00
Raincloud a76bb1e1e0 ffmpeg dist working with av1 2026-02-15 23:52:47 -07:00
Raincloud 0f29c0c457 ffmpeg dist setup 2026-02-15 17:26:55 -07:00
Raincloud 57f95fc74d append ffmpeg dist 2026-02-13 23:49:43 -07:00
Raincloud 66882233b0 fix time stdout 2026-02-13 23:41:41 -07:00
Raincloud 98a00caafc pyro 2026-02-13 22:35:17 -07:00
Raincloud 708a65031a user prompt to del files 2026-01-21 01:23:19 -07:00
Raincloud 155b5c25d1 rm suspiciously small error 2026-01-21 01:20:49 -07:00
Raincloud afdcc1c6fd todo 2026-01-18 11:35:01 -07:00
Raincloud 4db7819172 ffmpeg error handling 2026-01-18 11:12:40 -07:00
Raincloud ed30865e4f update links for 2026 2026-01-02 01:23:18 -07:00
Raincloud 7cf24df9ae nuke unused scripts 2026-01-01 12:58:35 -07:00
Raincloud 979c97b7f6 2025 shortcuts
with 11 days left in the year...
2025-12-20 23:07:27 -07:00
Raincloud c540a5fb04 also on dvr lossless maxrate idk why 2025-12-14 13:51:46 -07:00
Raincloud d52514c7b4 restore old bitrate - I may have overcompressed irreversibly due to this 2025-12-14 13:17:48 -07:00
Raincloud 8cdb75fa05 specst 2025-12-14 13:17:07 -07:00
Raincloud 28b0907c70 Merge remote-tracking branch 'origin/main' 2025-12-01 22:27:47 -07:00
Raincloud e895c22b6e bulk hydra 2025-12-01 22:27:39 -07:00
Raincloud 89b80225c1 bulk repair mp4 2025-12-01 22:25:51 -07:00
Raincloud 104c264af6 begin repair mp4 attempts 2025-10-28 22:19:22 -06:00
29 changed files with 706 additions and 5574 deletions
+9
View File
@@ -7,6 +7,7 @@ logs/*
# Encoded files
input/*
output/*
tmp/*
# Video files
*.mp4
*.mkv
@@ -19,3 +20,11 @@ output/*
*.ts
*.m2ts
*.mts
# Python cache files
__pycache__/
*.pyc
# IDE files
.specstory/
.vscode/
-2
View File
@@ -1,2 +0,0 @@
# SpecStory explanation file
/.what-is-this.md
File diff suppressed because it is too large Load Diff
@@ -1,462 +0,0 @@
<!-- Generated by SpecStory -->
<!-- cursor Session c905dc1e-3b07-499f-8906-d3b9d49bb984 (2025-06-26 04:00Z) -->
# Test encode for one minute (2025-06-26 04:00Z)
_**User (2025-07-03 15:00Z)**_
can we do a test encode for 1 min?
---
_**Assistant (default)**_
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 (2025-07-03 15:00Z)**_
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 (default)**_
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 (2025-07-03 15:00Z)**_
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 (default)**_
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 (2025-07-03 15:00Z)**_
why would ffmpeg.exe still be accessing files at this point?
---
_**Assistant (default)**_
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 (2025-07-03 15:00Z)**_
the resultant file is broken.
---
_**Assistant (default)**_
The broken output file could be due to several issues. Let me help you troubleshoot:
---
Tool use: **read_file**
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:**
---
Tool use: **edit_file**
<details><summary>Edit file: encode_dvr_lossless_vp9.py</summary>
I'll modify the FFmpeg command to be more robust by adding proper output format specification and ensuring clean termination
**Chunk 1**
Lines added: 2, lines removed: undefined
```diff
@@ -117,4 +117,6 @@
'-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
```
</details>
---
**Also, let's add a simple test to verify the output file:**
---
Tool use: **edit_file**
<details><summary>Edit file: encode_dvr_lossless_vp9.py</summary>
I'll add a function to verify the output file integrity after encoding
**Chunk 1**
Lines added: 9, lines removed: undefined
```diff
@@ -68,4 +68,13 @@
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)
```
</details>
---
Tool use: **edit_file**
<details><summary>Edit file: encode_dvr_lossless_vp9.py</summary>
I'll add file verification after successful encoding
**Chunk 1**
Lines added: 8, lines removed: undefined
```diff
@@ -159,9 +159,17 @@
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}")
```
</details>
---
**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 (2025-07-09 00:17Z)**_
results are working, but I'm noticing massive sizes. is there a cq 0 option?
---
_**Assistant (default)**_
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:
---
Tool use: **search_replace**
<details><summary>Edit file: encode_dvr_lossless_vp9.py</summary>
**Chunk 1**
Lines added: 1, lines removed: 1
```diff
@@ -104,5 +104,5 @@
logging.info(f"Audio labels: {audio_labels}")
- # FFmpeg command for lossless VP9 encoding with 60s timeout
+ # FFmpeg command for high-quality VP9 encoding with 60s timeout
cmd = [
'ffmpeg',
```
**Chunk 2**
Lines added: 3, lines removed: 2
```diff
@@ -111,6 +111,7 @@
'-t', '1',
'-c:v', 'libvpx-vp9',
- '-lossless', '1',
- '-cpu-used', '0',
+ '-crf', '15', # High quality (0=lossless, 10-20=visually lossless, 20-30=high quality)
+ '-b:v', '0', # Let CRF control bitrate
+ '-cpu-used', '2', # Faster encoding (0=slowest, 5=fastest)
'-row-mt', '1',
'-threads', '0',
```
</details>
---
**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 (2025-07-09 00:17Z)**_
these look good, can I test the full video lengths now?
---
_**Assistant (default)**_
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.
---
Tool use: **search_replace**
<details><summary>Edit file: encode_dvr_lossless_vp9.py</summary>
**Chunk 1**
Lines added: 1, lines removed: 2
```diff
@@ -104,10 +104,9 @@
logging.info(f"Audio labels: {audio_labels}")
- # FFmpeg command for high-quality VP9 encoding with 60s timeout
+ # FFmpeg command for high-quality VP9 encoding
cmd = [
'ffmpeg',
'-v', 'verbose',
'-i', str(input_path),
- '-t', '1',
'-c:v', 'libvpx-vp9',
'-crf', '15', # High quality (0=lossless, 10-20=visually lossless, 20-30=high quality)
```
</details>
---
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 (2025-07-09 00:17Z)**_
other than increasing the rate limit, how can I make this higher quality?
---
_**Assistant (default)**_
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:**
---
Tool use: **search_replace**
<details><summary>Edit file: encode_dvr_lossless_vp9.py</summary>
**Chunk 1**
Lines added: 5, lines removed: 2
```diff
@@ -111,10 +111,13 @@
'-t', '1', # Render only 1 second
'-c:v', 'libvpx-vp9',
- '-crf', '0',
+ '-crf', '5', # Extremely high quality (0=lossless, 5-10=extremely high)
'-b:v', '10000k',
'-maxrate', '10000k',
- '-cpu-used', '0',
+ '-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',
```
</details>
---
**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.
---
@@ -1,54 +0,0 @@
<!-- Generated by SpecStory -->
<!-- cursor Session fd1849e6-e572-4051-8efe-3106af3956ce (2025-10-01 20:10Z) -->
# Delete specific files from output (2025-10-01 20:10Z)
_**User (2025-10-01 20:08Z)**_
could you delete these 4 files from output? don't touch input, and don't touch any other files in output.
---
_**Assistant (default)**_
Deleting the 4 problematic files from the output directory.
---
Tool use: **delete_file**
undefined
---
Tool use: **delete_file**
undefined
---
Tool use: **delete_file**
undefined
---
Tool use: **delete_file**
undefined
---
Deleted the 4 files from the output directory:
- `2024-08-08 smithereens.ts.mp4` (audio stream count mismatch)
- `2024-08-28 dejection.ts.mp4` (duration mismatch)
- `2024-09-04 chaos.ts.mp4` (audio stream count mismatch)
- `2024-09-05 resolute.ts.mp4` (duration mismatch)
No other files were modified.
---
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
+33
View File
@@ -0,0 +1,33 @@
20260118_My ex wife has hired a lawyer.mp4
20260118_Wife left and took the kids.mp4
20260119_My ex wife has hired a lawyer.mp4
20260121_nothing ever happens.mp4
20260124_nothing ever happens.mp4
20260125_nothing ever happens.mp4
20260126_nothing ever happens.mp4
20260128_nothing ever happens.mp4
20260129_nothing ever happens.mp4
20260131_nothing ever happens.mp4
20260203_1 nothing ever happens.mp4
20260203_2 nothing ever happens.mp4
20260203_3 nothing ever happens.mp4
20260204_1 Recording Main Channel Video (Please make unfunny donos to get in the video).mp4
20260204_2 Recording Main Channel Video (Please make unfunny donos to get in the video).mp4
20260205_1 nothing ever happens.mp4
20260205_2 I trapped my wife inside a Black Soul Gem to boost the FPS of my Nintendo Switch.mp4
20260207_nothing ever happens.mp4
20260209_Five Night's At Freddy's 2.mp4
20260210_Five Night's At Freddy's 2.mp4
20260212_Paying off my Wife's alimony #Sponsored.mp4
20260221_POPPY PLAYTIME CHAPTER 5 (After react andy).mp4
20260222_POPPY PLAYTIME CHAPTER 5.mp4
20260223_1 Poppy Playtime Video Tonight.mp4
20260223_2 Poppy Playtime Video Tonight.mp4
20260224_HUGE REACTION ANDY.mp4
20260225_HUGE REACTION ANDY.mp4
20260303_RESIDENT EVIL REQUIEM.mp4
20260304_RESIDENT EVIL REQUIEM.mp4
20260305_RESIDENT EVIL REQUIEM.mp4
20260307_FIND MY PAGES.mp4
20260309_1 NOTHING EVER HAPPENS.mp4
20260309_2 NOTHING EVER HAPPENS.mp4
BIN
View File
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
* [ ] Overhaul: integrate basic functionality of Flamenco into UI
* [ ] Error handling
* [ ] Log errors
* [ ] Mark task as errored; the output will be deleted and tried again
+89 -53
View File
@@ -31,7 +31,7 @@ def get_file_info(file_path):
'-of', 'json',
str(file_path)
]
result = subprocess.run(cmd, capture_output=True, text=True)
result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8', errors='replace')
if result.returncode != 0:
return None, f"ffprobe failed: {result.stderr}"
info = json.loads(result.stdout)
@@ -41,12 +41,14 @@ def get_file_info(file_path):
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 expected_mp4_output_name(input_filename):
return Path(input_filename).stem + '.mp4'
def is_input_video_file(name):
return name.lower().endswith(('.mp4', '.mkv', '.mov'))
def is_output_mp4_file(name):
return name.lower().endswith('.mp4')
def check_file(input_path, output_path):
try:
@@ -93,13 +95,6 @@ def check_file(input_path, output_path):
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:
@@ -121,7 +116,6 @@ def main():
"Resolution mismatch": 0,
"Audio stream count mismatch": 0,
"Audio channel mismatch": 0,
"Suspiciously small output": 0,
"Other errors": 0
}
@@ -181,7 +175,7 @@ def main():
# 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')):
if is_input_video_file(f):
full_path = os.path.join(input_dir, f)
if os.path.isfile(full_path):
input_files.add(f)
@@ -191,7 +185,7 @@ def main():
output_files = set()
for f in os.listdir(output_dir):
if f.endswith(('.mp4', '.DVR.mp4')):
if is_output_mp4_file(f):
full_path = os.path.join(output_dir, f)
if os.path.isfile(full_path):
output_files.add(f)
@@ -208,47 +202,59 @@ def main():
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:
input_stems = {Path(f).stem for f in input_files}
# Report missing files (output is always {stem}.mp4 for encoder workflow)
missing_in_output_inputs = sorted(
inf for inf in input_files if expected_mp4_output_name(inf) not in output_files
)
missing_in_input_outputs = sorted(
outf for outf in output_files if Path(outf).stem not in input_stems
)
if missing_in_output_inputs:
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"))
for inf in missing_in_output_inputs:
outp = expected_mp4_output_name(inf)
print(f" {inf} → expected {outp}")
logging.warning(f"File missing from output: {inf} (expected {outp})")
issues_found.append((inf, outp, "Missing from output"))
issue_counts["Missing from output"] += 1
if missing_in_input:
if missing_in_input_outputs:
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"))
for outf in missing_in_input_outputs:
print(f" {outf}")
logging.warning(f"File missing from input: {outf}")
issues_found.append((None, outf, "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"
pairs_to_check = []
for inf in sorted(input_files):
outp = expected_mp4_output_name(inf)
if outp in output_files:
pairs_to_check.append((inf, outp))
logging.info(f"Number of input/output pairs to verify: {len(pairs_to_check)}")
if not pairs_to_check and not missing_in_output_inputs and not missing_in_input_outputs:
msg = "No input video files and no mismatches to report"
print(f"\n{Colors.YELLOW}{msg}{Colors.ENDC}")
logging.warning(msg)
return
for input_file in sorted(files_to_check):
for input_file, output_file in pairs_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}")
output_path = os.path.join(output_dir, output_file)
print(f"\nChecking: {input_file}{output_file}")
logging.info(f"Checking file: {input_file} vs output {output_file}")
issue = check_file(input_path, output_path)
if issue:
issues_found.append((input_file, issue))
issues_found.append((input_file, output_file, issue))
print(f"{Colors.RED}ISSUE FOUND: {issue}{Colors.ENDC}")
logging.warning(f"Issue with {input_file}: {issue}")
logging.warning(f"Issue with {input_file}{output_file}: {issue}")
# Count the type of issue
if "Duration mismatch" in issue:
@@ -261,14 +267,12 @@ def main():
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}")
logging.info(f"No issues found for {input_file}{output_file}")
# Print summary
print("\nSummary of issues found:")
if issues_found:
@@ -278,9 +282,41 @@ def main():
print(f"{issue_type}: {color}{count}{Colors.ENDC}")
print("\nDetailed issues:")
for file, issue in issues_found:
print(f"\n{file}:")
for in_base, out_base, issue in issues_found:
label = f"{in_base}{out_base}" if in_base else str(out_base)
print(f"\n{label}:")
print(f"{Colors.RED} {issue}{Colors.ENDC}")
# Prompt to delete problem files
print(f"\n{Colors.YELLOW}Files with issues detected.{Colors.ENDC}")
response = input("Delete all problematic output files? (yes/no): ").strip().lower()
if response in ['yes', 'y']:
deleted_count = 0
failed_count = 0
for in_base, out_base, issue in issues_found:
if issue == "Missing from output":
continue
if not out_base:
continue
output_path = os.path.join(output_dir, out_base)
if os.path.exists(output_path):
try:
os.remove(output_path)
deleted_count += 1
logging.info(f"Deleted problematic output file: {output_path} (Issue: {issue})")
print(f"{Colors.GREEN}Deleted: {out_base}{Colors.ENDC}")
except Exception as e:
failed_count += 1
logging.error(f"Failed to delete {output_path}: {str(e)}")
print(f"{Colors.RED}Failed to delete {out_base}: {str(e)}{Colors.ENDC}")
print(f"\n{Colors.GREEN}Deleted {deleted_count} file(s){Colors.ENDC}")
if failed_count > 0:
print(f"{Colors.RED}Failed to delete {failed_count} file(s){Colors.ENDC}")
else:
print("Deletion cancelled.")
else:
print(f"{Colors.GREEN}No issues found in any files!{Colors.ENDC}")
print("\nIssue counts:")
+133 -40
View File
@@ -27,18 +27,52 @@ def get_gpu_selection():
return gpu
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
# Custom file handler that silently handles I/O errors (for network shares)
class SafeFileHandler(logging.FileHandler):
"""File handler that silently handles I/O errors during flush"""
def flush(self):
"""Override flush to silently handle I/O errors"""
try:
super().flush()
except (OSError, IOError):
# Silently ignore I/O errors (network share issues)
pass
except Exception:
# Silently ignore all other errors during flush
pass
def emit(self, record):
"""Override emit to handle errors gracefully"""
try:
super().emit(record)
except (OSError, IOError):
# Silently ignore I/O errors - we'll fall back to console output
self.handleError(record)
except Exception:
# Handle other errors
self.handleError(record)
def handleError(self, record):
"""Override to prevent error messages from being printed"""
# Don't print "--- Logging error ---" messages
pass
# 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")
# Configure logging with Windows-safe settings
logging.basicConfig(
filename=log_file,
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
filemode='w' # Explicitly set write mode
)
# Configure logging with custom handler that handles network share errors
handler = SafeFileHandler(log_file, mode='w', encoding='utf-8')
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger = logging.getLogger()
logger.setLevel(logging.INFO)
logger.addHandler(handler)
# Remove default handlers to avoid duplicate output
logger.handlers = [handler]
def get_file_info(input_file):
cmd = [
@@ -48,7 +82,7 @@ def get_file_info(input_file):
'-of', 'json',
input_file
]
result = subprocess.run(cmd, capture_output=True, text=True)
result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8', errors='replace')
return json.loads(result.stdout)
def get_audio_labels(input_file):
@@ -60,7 +94,7 @@ def get_audio_labels(input_file):
'-of', 'json',
input_file
]
result = subprocess.run(cmd, capture_output=True, text=True)
result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8', errors='replace')
info = json.loads(result.stdout)
labels = []
for stream in info.get('streams', []):
@@ -75,18 +109,70 @@ def format_size(size_bytes):
size_bytes /= 1024
return f"{size_bytes:.2f} TB"
def safe_log_info(message, print_msg=None):
"""Safely log info message, ensuring console output even if logging fails"""
try:
logging.info(message)
except (OSError, IOError) as e:
# Logging failed (likely network share issue) - print to console
if print_msg is None:
print(f"{Colors.YELLOW}[Logging failed: {e}] {message}{Colors.ENDC}")
else:
print(print_msg)
print(f"{Colors.YELLOW}[Logging failed: {e}]{Colors.ENDC}")
return
except Exception as e:
# Other logging errors
if print_msg is None:
print(f"{Colors.YELLOW}[Logging error: {e}] {message}{Colors.ENDC}")
else:
print(print_msg)
print(f"{Colors.YELLOW}[Logging error: {e}]{Colors.ENDC}")
return
# Always print to console if message provided
if print_msg is not None:
print(print_msg)
def safe_log_error(message, print_msg=None):
"""Safely log error message, ensuring console output even if logging fails"""
try:
logging.error(message)
except (OSError, IOError) as e:
# Logging failed (likely network share issue) - print to console
if print_msg is None:
print(f"{Colors.RED}[Logging failed: {e}] {message}{Colors.ENDC}")
else:
print(print_msg)
print(f"{Colors.RED}[Logging failed: {e}]{Colors.ENDC}")
return
except Exception as e:
# Other logging errors
if print_msg is None:
print(f"{Colors.RED}[Logging error: {e}] {message}{Colors.ENDC}")
else:
print(print_msg)
print(f"{Colors.RED}[Logging error: {e}]{Colors.ENDC}")
return
# Always print to console if message provided
if print_msg is not None:
print(print_msg)
else:
print(f"{Colors.RED}{message}{Colors.ENDC}")
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}"
output_path = Path(output_dir) / f"{input_path.stem}.mp4"
# 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")
safe_log_info(f"Processing file: {input_path}")
safe_log_info(f"Input size: {format_size(input_size)}")
safe_log_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)}")
@@ -95,21 +181,21 @@ def encode_dvr(input_file, output_dir, gpu):
# 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}):")
safe_log_info(f"Stream {i} ({stream_type}):")
for key, value in stream.items():
logging.info(f" {key}: {value}")
safe_log_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)}")
safe_log_info(f"Skipping {input_path} - output already exists: {output_path}")
safe_log_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}")
safe_log_info(f"Audio labels: {audio_labels}")
# FFmpeg command with NVIDIA HEVC encoder and maximum quality
cmd = [
@@ -146,6 +232,8 @@ def encode_dvr(input_file, output_dir, gpu):
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
encoding='utf-8',
errors='replace',
bufsize=1
)
@@ -156,13 +244,16 @@ def encode_dvr(input_file, output_dir, gpu):
text = line.strip()
try:
if text.startswith('frame=') or ' fps=' in text:
logging.info(f"Progress: {text}")
print(f"{Colors.PURPLE}Progress: {text}{Colors.ENDC}")
safe_log_info(f"Progress: {text}", f"{Colors.PURPLE}Progress: {text}{Colors.ENDC}")
else:
logging.info(f"FFmpeg: {text}")
print(f"{Colors.GREEN}FFmpeg: {text}{Colors.ENDC}")
except (OSError, IOError):
pass
safe_log_info(f"FFmpeg: {text}", f"{Colors.GREEN}FFmpeg: {text}{Colors.ENDC}")
except (OSError, IOError) as e:
# I/O error reading from pipe - log it
safe_log_error(f"I/O error reading FFmpeg output: {e}")
break
except Exception as e:
# Unexpected error
safe_log_error(f"Unexpected error processing FFmpeg output: {e}")
process.wait()
@@ -172,41 +263,43 @@ def encode_dvr(input_file, output_dir, gpu):
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")
safe_log_info(f"Successfully encoded: {output_path}", f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
safe_log_info(f"Output size: {format_size(output_size)}")
safe_log_info(f"Compression ratio: {compression_ratio:.2f}x", 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}")
# Convert Windows error code to signed integer if needed
return_code = process.returncode
if return_code > 2147483647: # If it's a large unsigned int, convert to signed
return_code = return_code - 4294967296
safe_log_error(f"FFmpeg process failed with return code {return_code}",
f"{Colors.RED}FFmpeg process failed with return code {return_code}{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}")
safe_log_error(f"Error encoding {input_path}: {e}", f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
except Exception as e:
safe_log_error(f"Unexpected error encoding {input_path}: {type(e).__name__}: {e}",
f"{Colors.RED}Unexpected error encoding {input_path}: {e}{Colors.ENDC}")
if __name__ == "__main__":
# Get GPU selection
gpu = get_gpu_selection()
logging.info(f"Selected GPU: {gpu}")
safe_log_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'))]
files = [f for f in os.listdir(input_dir) if f.lower().endswith(('.mp4', '.mkv', '.mov'))]
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}")
safe_log_info("No files to process in input directory", 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}")
safe_log_info(f"Found {total_files} files to process", 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}")
safe_log_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
View File
@@ -0,0 +1,3 @@
@echo off
python encode_VOD_bitrate.py
pause
+298
View File
@@ -0,0 +1,298 @@
import os
import subprocess
from pathlib import Path
import json
import logging
from datetime import datetime
import shutil
import time
# 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}")
def get_bitrate():
while True:
print(f"\n{Colors.BLUE}Select video bitrate:{Colors.ENDC}")
print("Common values: 5000k (5 Mbps), 8000k (8 Mbps), 10000k (10 Mbps), 15000k (15 Mbps)")
bitrate = input(f"{Colors.YELLOW}Enter bitrate in kbps (e.g., 10000 for 10 Mbps):{Colors.ENDC} ").strip()
try:
bitrate_val = int(bitrate)
if bitrate_val > 0:
return bitrate_val
print(f"{Colors.RED}Bitrate must be positive. Please try again.{Colors.ENDC}")
except ValueError:
print(f"{Colors.RED}Invalid input. Please enter a number.{Colors.ENDC}")
# Custom file handler that silently handles I/O errors (for network shares)
class SafeFileHandler(logging.FileHandler):
"""File handler that silently handles I/O errors during flush"""
def flush(self):
"""Override flush to silently handle I/O errors"""
try:
super().flush()
except (OSError, IOError):
pass
except Exception:
pass
def emit(self, record):
"""Override emit to handle errors gracefully"""
try:
super().emit(record)
except (OSError, IOError):
self.handleError(record)
except Exception:
self.handleError(record)
def handleError(self, record):
"""Override to prevent error messages from being printed"""
pass
# 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")
handler = SafeFileHandler(log_file, mode='w', encoding='utf-8')
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger = logging.getLogger()
logger.setLevel(logging.INFO)
logger.addHandler(handler)
logger.handlers = [handler]
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, encoding='utf-8', errors='replace')
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, encoding='utf-8', errors='replace')
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 safe_log_info(message, print_msg=None):
try:
logging.info(message)
except (OSError, IOError) as e:
if print_msg is None:
print(f"{Colors.YELLOW}[Logging failed: {e}] {message}{Colors.ENDC}")
else:
print(print_msg)
print(f"{Colors.YELLOW}[Logging failed: {e}]{Colors.ENDC}")
return
except Exception as e:
if print_msg is None:
print(f"{Colors.YELLOW}[Logging error: {e}] {message}{Colors.ENDC}")
else:
print(print_msg)
print(f"{Colors.YELLOW}[Logging error: {e}]{Colors.ENDC}")
return
if print_msg is not None:
print(print_msg)
def safe_log_error(message, print_msg=None):
try:
logging.error(message)
except (OSError, IOError) as e:
if print_msg is None:
print(f"{Colors.RED}[Logging failed: {e}] {message}{Colors.ENDC}")
else:
print(print_msg)
print(f"{Colors.RED}[Logging failed: {e}]{Colors.ENDC}")
return
except Exception as e:
if print_msg is None:
print(f"{Colors.RED}[Logging error: {e}] {message}{Colors.ENDC}")
else:
print(print_msg)
print(f"{Colors.RED}[Logging error: {e}]{Colors.ENDC}")
return
if print_msg is not None:
print(print_msg)
else:
print(f"{Colors.RED}{message}{Colors.ENDC}")
def encode_dvr(input_file, output_dir, gpu, bitrate):
input_path = Path(input_file)
output_path = Path(output_dir) / f"{input_path.stem}.mp4"
b_v = f"{bitrate}k"
maxrate = f"{bitrate}k"
bufsize = f"{bitrate * 2}k"
file_info = get_file_info(str(input_path))
input_size = int(file_info['format']['size'])
duration = float(file_info['format']['duration'])
safe_log_info(f"Processing file: {input_path}")
safe_log_info(f"Input size: {format_size(input_size)}")
safe_log_info(f"Duration: {duration:.2f} seconds")
safe_log_info(f"Bitrate settings - b:v: {b_v}, maxrate: {maxrate}, bufsize: {bufsize}")
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")
print(f"Bitrate settings - b:v: {b_v}, maxrate: {maxrate}, bufsize: {bufsize}")
for i, stream in enumerate(file_info.get('streams', [])):
stream_type = 'Video' if stream.get('codec_name', '').startswith('h') else 'Audio'
safe_log_info(f"Stream {i} ({stream_type}):")
for key, value in stream.items():
safe_log_info(f" {key}: {value}")
if output_path.exists():
output_size = output_path.stat().st_size
safe_log_info(f"Skipping {input_path} - output already exists: {output_path}")
safe_log_info(f"Output size: {format_size(output_size)}")
print(f"{Colors.YELLOW}Skipping {input_path} - output already exists{Colors.ENDC}")
return
audio_labels = get_audio_labels(str(input_path))
safe_log_info(f"Audio labels: {audio_labels}")
cmd = [
'ffmpeg',
'-v', 'info',
'-stats',
'-stats_period', '1.0',
'-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', b_v,
'-maxrate', maxrate,
'-bufsize', bufsize,
'-c:a', 'copy',
'-map', '0',
]
for idx, label in enumerate(audio_labels):
if label:
cmd += [f'-metadata:s:a:{idx}', f'title={label}']
cmd.append(str(output_path))
try:
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
encoding='utf-8',
errors='replace',
bufsize=1
)
for line in iter(process.stdout.readline, ''):
if not line:
break
text = line.strip()
try:
if text.startswith('frame=') or ' fps=' in text:
safe_log_info(f"Progress: {text}", f"{Colors.PURPLE}Progress: {text}{Colors.ENDC}")
else:
safe_log_info(f"FFmpeg: {text}", f"{Colors.GREEN}FFmpeg: {text}{Colors.ENDC}")
except (OSError, IOError) as e:
safe_log_error(f"I/O error reading FFmpeg output: {e}")
break
except Exception as e:
safe_log_error(f"Unexpected error processing FFmpeg output: {e}")
process.wait()
if process.returncode == 0:
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
safe_log_info(f"Successfully encoded: {output_path}", f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
safe_log_info(f"Output size: {format_size(output_size)}")
safe_log_info(f"Compression ratio: {compression_ratio:.2f}x", f"Compression ratio: {compression_ratio:.2f}x")
else:
return_code = process.returncode
if return_code > 2147483647:
return_code = return_code - 4294967296
safe_log_error(f"FFmpeg process failed with return code {return_code}",
f"{Colors.RED}FFmpeg process failed with return code {return_code}{Colors.ENDC}")
except subprocess.CalledProcessError as e:
safe_log_error(f"Error encoding {input_path}: {e}", f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
except Exception as e:
safe_log_error(f"Unexpected error encoding {input_path}: {type(e).__name__}: {e}",
f"{Colors.RED}Unexpected error encoding {input_path}: {e}{Colors.ENDC}")
if __name__ == "__main__":
gpu = get_gpu_selection()
safe_log_info(f"Selected GPU: {gpu}")
bitrate = get_bitrate()
safe_log_info(f"Selected bitrate: {bitrate}k")
input_dir = "input"
output_dir = "output"
os.makedirs(output_dir, exist_ok=True)
files = [f for f in os.listdir(input_dir) if f.lower().endswith(('.mp4', '.mkv', '.mov'))]
total_files = len(files)
if total_files == 0:
safe_log_info("No files to process in input directory", f"{Colors.YELLOW}No files to process in input directory{Colors.ENDC}")
else:
safe_log_info(f"Found {total_files} files to process", 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)
safe_log_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, bitrate)
-3
View File
@@ -1,3 +0,0 @@
@echo off
python encode_dvr.py
pause
-209
View File
@@ -1,209 +0,0 @@
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
View File
@@ -1,3 +0,0 @@
@echo off
python encode_dvr_23.py
pause
-191
View File
@@ -1,191 +0,0 @@
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)
+137 -31
View File
@@ -26,11 +26,52 @@ def get_gpu_selection():
return gpu
print(f"{Colors.RED}Invalid selection. Please try again.{Colors.ENDC}")
# Custom file handler that silently handles I/O errors (for network shares)
class SafeFileHandler(logging.FileHandler):
"""File handler that silently handles I/O errors during flush"""
def flush(self):
"""Override flush to silently handle I/O errors"""
try:
super().flush()
except (OSError, IOError):
# Silently ignore I/O errors (network share issues)
pass
except Exception:
# Silently ignore all other errors during flush
pass
def emit(self, record):
"""Override emit to handle errors gracefully"""
try:
super().emit(record)
except (OSError, IOError):
# Silently ignore I/O errors - we'll fall back to console output
self.handleError(record)
except Exception:
# Handle other errors
self.handleError(record)
def handleError(self, record):
"""Override to prevent error messages from being printed"""
# Don't print "--- Logging error ---" messages
pass
# 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')
# Configure logging with custom handler that handles network share errors
handler = SafeFileHandler(log_file, mode='w', encoding='utf-8')
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger = logging.getLogger()
logger.setLevel(logging.INFO)
logger.addHandler(handler)
# Remove default handlers to avoid duplicate output
logger.handlers = [handler]
def get_file_info(input_file):
cmd = [
@@ -67,18 +108,70 @@ def format_size(size_bytes):
size_bytes /= 1024
return f"{size_bytes:.2f} TB"
def safe_log_info(message, print_msg=None):
"""Safely log info message, ensuring console output even if logging fails"""
try:
logging.info(message)
except (OSError, IOError) as e:
# Logging failed (likely network share issue) - print to console
if print_msg is None:
print(f"{Colors.YELLOW}[Logging failed: {e}] {message}{Colors.ENDC}")
else:
print(print_msg)
print(f"{Colors.YELLOW}[Logging failed: {e}]{Colors.ENDC}")
return
except Exception as e:
# Other logging errors
if print_msg is None:
print(f"{Colors.YELLOW}[Logging error: {e}] {message}{Colors.ENDC}")
else:
print(print_msg)
print(f"{Colors.YELLOW}[Logging error: {e}]{Colors.ENDC}")
return
# Always print to console if message provided
if print_msg is not None:
print(print_msg)
def safe_log_error(message, print_msg=None):
"""Safely log error message, ensuring console output even if logging fails"""
try:
logging.error(message)
except (OSError, IOError) as e:
# Logging failed (likely network share issue) - print to console
if print_msg is None:
print(f"{Colors.RED}[Logging failed: {e}] {message}{Colors.ENDC}")
else:
print(print_msg)
print(f"{Colors.RED}[Logging failed: {e}]{Colors.ENDC}")
return
except Exception as e:
# Other logging errors
if print_msg is None:
print(f"{Colors.RED}[Logging error: {e}] {message}{Colors.ENDC}")
else:
print(print_msg)
print(f"{Colors.RED}[Logging error: {e}]{Colors.ENDC}")
return
# Always print to console if message provided
if print_msg is not None:
print(print_msg)
else:
print(f"{Colors.RED}{message}{Colors.ENDC}")
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}"
output_path = Path(output_dir) / f"{input_path.stem}.mp4"
# 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")
safe_log_info(f"Processing file: {input_path}")
safe_log_info(f"Input size: {format_size(input_size)}")
safe_log_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)}")
@@ -87,21 +180,21 @@ def encode_dvr(input_file, output_dir, gpu):
# 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}):")
safe_log_info(f"Stream {i} ({stream_type}):")
for key, value in stream.items():
logging.info(f" {key}: {value}")
safe_log_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)}")
safe_log_info(f"Skipping {input_path} - output already exists: {output_path}")
safe_log_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}")
safe_log_info(f"Audio labels: {audio_labels}")
# FFmpeg command with NVIDIA HEVC encoder and maximum quality
cmd = [
@@ -138,12 +231,23 @@ def encode_dvr(input_file, output_dir, gpu):
# Log FFmpeg output in real-time
while True:
output = process.stderr.readline()
if output == '' and process.poll() is not None:
try:
output = process.stderr.readline()
if output == '' and process.poll() is not None:
break
if output:
text = output.strip()
if text.startswith('frame=') or ' fps=' in text:
safe_log_info(f"Progress: {text}", f"{Colors.PURPLE}Progress: {text}{Colors.ENDC}")
else:
safe_log_info(f"FFmpeg: {text}", f"{Colors.GREEN}FFmpeg: {text}{Colors.ENDC}")
except (OSError, IOError) as e:
# I/O error reading from pipe - log it
safe_log_error(f"I/O error reading FFmpeg output: {e}")
break
if output:
logging.info(f"FFmpeg: {output.strip()}")
print(f"{Colors.PURPLE}FFmpeg: {output.strip()}{Colors.ENDC}")
except Exception as e:
# Unexpected error
safe_log_error(f"Unexpected error processing FFmpeg output: {e}")
if process.returncode == 0:
# Get output file info
@@ -151,41 +255,43 @@ def encode_dvr(input_file, output_dir, gpu):
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")
safe_log_info(f"Successfully encoded: {output_path}", f"{Colors.GREEN}Successfully encoded: {output_path}{Colors.ENDC}")
safe_log_info(f"Output size: {format_size(output_size)}")
safe_log_info(f"Compression ratio: {compression_ratio:.2f}x", 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}")
# Convert Windows error code to signed integer if needed
return_code = process.returncode
if return_code > 2147483647: # If it's a large unsigned int, convert to signed
return_code = return_code - 4294967296
safe_log_error(f"FFmpeg process failed with return code {return_code}",
f"{Colors.RED}FFmpeg process failed with return code {return_code}{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}")
safe_log_error(f"Error encoding {input_path}: {e}", f"{Colors.RED}Error encoding {input_path}: {e}{Colors.ENDC}")
except Exception as e:
safe_log_error(f"Unexpected error encoding {input_path}: {type(e).__name__}: {e}",
f"{Colors.RED}Unexpected error encoding {input_path}: {e}{Colors.ENDC}")
if __name__ == "__main__":
# Get GPU selection
gpu = get_gpu_selection()
logging.info(f"Selected GPU: {gpu}")
safe_log_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'))]
files = [f for f in os.listdir(input_dir) if f.lower().endswith(('.mp4', '.mkv', '.mov'))]
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}")
safe_log_info("No files to process in input directory", 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}")
safe_log_info(f"Found {total_files} files to process", 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}")
safe_log_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
View File
@@ -1,3 +0,0 @@
@echo off
python encode_dvr_lossless.py
pause
-191
View File
@@ -1,191 +0,0 @@
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
View File
@@ -1,3 +0,0 @@
@echo off
python encode_dvr_lossless_maxrate.py
pause
-192
View File
@@ -1,192 +0,0 @@
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
View File
@@ -1,3 +0,0 @@
@echo off
python encode_dvr_lossless_vp9.py
pause
-191
View File
@@ -1,191 +0,0 @@
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
View File
@@ -1,3 +0,0 @@
@echo off
python encode_vtubrec2500.py
pause
-193
View File
@@ -1,193 +0,0 @@
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
View File
@@ -1,3 +0,0 @@
@echo off
python encode_vtubrec5k.py
pause
-194
View File
@@ -1,194 +0,0 @@
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)