Updates processes and scripts

This commit is contained in:
2025-07-20 15:44:17 +02:00
parent 58a9c8ce3b
commit 7a8a85d953
3 changed files with 344 additions and 109 deletions

View File

@@ -4,6 +4,17 @@ import os
import sys
import argparse
import shutil
import multiprocessing
import re
import math
# --- Global lock for synchronized screen writing ---
LOCK = None
def init_worker(lock):
"""Initializer for each worker process to share the lock."""
global LOCK
LOCK = lock
def get_video_resolution(video_path):
"""Gets video resolution using ffprobe."""
@@ -20,86 +31,94 @@ def get_video_resolution(video_path):
print(f" Error getting video resolution for '{video_path}': {e}")
return None, None
def encode_segment(segment_path, crf):
def get_frame_count(video_path):
"""Gets the total number of frames in a video file using ffprobe."""
command = [
'ffprobe', '-v', 'error', '-select_streams', 'v:0',
'-count_frames', '-show_entries', 'stream=nb_read_frames',
'-of', 'default=nokey=1:noprint_wrappers=1', video_path
]
try:
result = subprocess.run(command, capture_output=True, text=True, check=True)
return int(result.stdout.strip())
except (subprocess.CalledProcessError, ValueError):
return 0 # Return 0 if we can't get the frame count
def encode_segment_worker(task_args):
"""
Encodes a single segment with a static CRF value.
Uses ffmpeg to pipe raw video to the external SvtAv1EncApp.exe encoder.
Wrapper function for multiprocessing pool.
Unpacks arguments and calls the main encoding function.
"""
segment_path, crf, pbar_pos, total_frames = task_args
return encode_segment(segment_path, crf, pbar_pos, total_frames)
def encode_segment(segment_path, crf, pbar_pos, total_frames):
"""
Encodes a single segment, drawing a manual progress bar to the console.
"""
output_dir = "segments"
base_name = os.path.basename(segment_path)
final_output_path = os.path.join(output_dir, base_name)
# Get video resolution to pass to the encoder
width, height = get_video_resolution(segment_path)
if not width or not height:
print(f" Could not determine resolution for '{base_name}'. Skipping segment.")
return False
print(f" -> Encoding with static CRF {crf} using SvtAv1EncApp.exe...")
# Command to use ffmpeg as a frameserver, decoding the segment
# and piping raw 10-bit y4m video to stdout.
ffmpeg_command = [
'ffmpeg', '-hide_banner', '-loglevel', 'error',
'-i', segment_path,
'-pix_fmt', 'yuv420p10le', # Force 10-bit pixel format for the pipe
'-f', 'yuv4mpegpipe',
'-strict', '-1', # Allow non-standard pixel format in y4m pipe
'-' # to stdout
'ffmpeg', '-hide_banner', '-loglevel', 'error', '-i', segment_path,
'-pix_fmt', 'yuv420p10le', '-f', 'yuv4mpegpipe', '-strict', '-1', '-'
]
# Command for the external SVT-AV1-PSY encoder, reading from stdin
svt_command = [
'SvtAv1EncApp.exe',
'-i', 'stdin',
'--width', str(width),
'--height', str(height),
'--progress', '2',
'--preset', '2',
'--input-depth', '10',
'--crf', str(crf),
'--film-grain', '8',
'--tune', '2',
'--keyint', '-1',
'--color-primaries', '1',
'--transfer-characteristics', '1',
'--matrix-coefficients', '1',
'-b', final_output_path # Encode directly to the final path
'SvtAv1EncApp.exe', '-i', 'stdin', '--width', str(width), '--height', str(height),
'--progress', '2', '--preset', '2', '--lp', '1', '--input-depth', '10',
'--crf', str(crf), '--film-grain', '8', '--tune', '2', '--keyint', '-1',
'--color-primaries', '1', '--transfer-characteristics', '1', '--matrix-coefficients', '1',
'-b', final_output_path
]
ffmpeg_process = None
svt_process = None
ffmpeg_process = subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE)
svt_process = subprocess.Popen(svt_command, stdin=ffmpeg_process.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding='utf-8')
if ffmpeg_process.stdout:
ffmpeg_process.stdout.close()
progress_regex = re.compile(r"Encoding frame\s+(\d+)/")
def draw_progress(frame, total):
if total == 0: return
bar_width = 40
percentage = frame / total
filled_len = int(round(bar_width * percentage))
bar = '' * filled_len + '-' * (bar_width - filled_len)
# Truncate filename for display
display_name = (base_name[:23] + '..') if len(base_name) > 25 else base_name
progress_str = f"{display_name:<25} |{bar}| {frame}/{total} ({percentage:.1%})"
with LOCK:
sys.stdout.write(f'\033[{pbar_pos};0H') # Move cursor to line `pbar_pos`
sys.stdout.write('\033[K') # Clear the line
sys.stdout.write(progress_str)
sys.stdout.flush()
try:
# Start the ffmpeg frameserver process
ffmpeg_process = subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE)
for line in iter(svt_process.stderr.readline, ''):
match = progress_regex.search(line)
if match:
current_frame = int(match.group(1))
draw_progress(current_frame, total_frames)
# Start the SVT encoder process, taking stdin from ffmpeg's stdout
# We pass sys.stderr to the encoder so progress is shown in real-time
svt_process = subprocess.Popen(svt_command, stdin=ffmpeg_process.stdout, stdout=subprocess.PIPE, stderr=sys.stderr)
# This allows ffmpeg to receive a SIGPIPE if svt_process exits before ffmpeg is done.
if ffmpeg_process.stdout:
ffmpeg_process.stdout.close()
# Wait for the encoder to finish.
svt_process.communicate()
ffmpeg_process.wait()
if svt_process.returncode != 0:
# Manually raise an error to be caught by the except block
raise subprocess.CalledProcessError(svt_process.returncode, svt_command)
print(f" -> Success! Finished encoding '{os.path.basename(final_output_path)}'")
draw_progress(total_frames, total_frames) # Final update to 100%
return True
except (subprocess.CalledProcessError, FileNotFoundError) as e:
print(f"\n Encoding failed for CRF {crf}.")
if isinstance(e, subprocess.CalledProcessError):
print(f" Encoder returned non-zero exit code {e.returncode}. See encoder output above for details.")
else: # FileNotFoundError
print(f" Error: '{e.filename}' not found. Please ensure SvtAv1EncApp.exe is in your PATH.")
# Clean up partially created file on failure
except Exception:
if os.path.exists(final_output_path):
os.remove(final_output_path)
return False
@@ -110,6 +129,12 @@ def main():
formatter_class=argparse.RawTextHelpFormatter
)
parser.add_argument("--crf", type=int, default=27, help="The static CRF value to use for all segments. Default: 27")
parser.add_argument(
"--workers",
type=int,
default=max(1, multiprocessing.cpu_count() // 2),
help="Number of segments to encode in parallel. Default: half of available CPU cores."
)
args = parser.parse_args()
@@ -129,22 +154,39 @@ def main():
os.makedirs(output_dir, exist_ok=True)
# --- Process Segments ---
segments = sorted([f for f in os.listdir(input_dir) if f.endswith('.mkv')])
segments = sorted([os.path.join(input_dir, f) for f in os.listdir(input_dir) if f.endswith('.mkv')])
total_segments = len(segments)
if total_segments == 0:
print(f"No segments found in '{input_dir}'.")
sys.exit(0)
print(f"Found {total_segments} segments to process from '{input_dir}'.")
print(f"Encoding with static CRF {args.crf}.")
print(f"Encoding with static CRF {args.crf} using {args.workers} parallel worker(s).")
print(f"Final files will be saved in '{output_dir}'.")
for i, segment_file in enumerate(segments):
print(f"\n--- Processing segment {i+1}/{total_segments}: {segment_file} ---")
segment_path = os.path.join(input_dir, segment_file)
encode_segment(segment_path, args.crf)
print("\nGathering segment information...")
frame_counts = [get_frame_count(s) for s in segments]
tasks = [(segments[i], args.crf, i + 1, frame_counts[i]) for i in range(total_segments)]
print("\n--- All segments processed. ---")
# --- Run Encoding ---
# Clear screen and reserve space for progress bars
os.system('cls' if os.name == 'nt' else 'clear')
print(f"--- Starting {args.workers} encoding worker(s) ---")
sys.stdout.write('\n' * args.workers)
sys.stdout.flush()
lock = multiprocessing.Lock()
with multiprocessing.Pool(processes=args.workers, initializer=init_worker, initargs=(lock,)) as pool:
# Use imap to maintain order, so pbar_pos is consistent
results = list(pool.imap(encode_segment_worker, tasks))
# Move cursor below the progress bars
sys.stdout.write(f'\033[{args.workers + 1};0H')
sys.stdout.write('\n')
successful_encodes = sum(1 for r in results if r)
print(f"--- All segments processed. ---")
print(f"Successfully encoded {successful_encodes}/{total_segments} segments.")
if __name__ == "__main__":
main()