Skip to content

Commit

Permalink
Merge pull request #5 from NationalLibraryOfNorway/beta-patch
Browse files Browse the repository at this point in the history
Beta patch
  • Loading branch information
EmilJohan authored Jul 1, 2024
2 parents ff0801e + f7425a4 commit 538b591
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 57 deletions.
Binary file added Smelt
Binary file not shown.
166 changes: 109 additions & 57 deletions Smelt.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
"""
import glob
import queue
import re
import subprocess
import sys
import os
import threading
import time

from PyQt5.QtWidgets import *
Expand Down Expand Up @@ -80,6 +82,26 @@ def extract_number(filename):
return int(match.group(1)) if match else float('inf')


def cuda_available():
try:
# Check if ffmpeg has CUDA support
result = subprocess.run(['ffmpeg', '-hwaccels'], stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True)
if 'cuda' in result.stdout:
# Check if nvidia-smi is available (indicates presence of NVIDIA GPU)
result = subprocess.run(['nvidia-smi'], stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True)
if result.returncode == 0:
return True
return False
except FileNotFoundError:
# Handle case where 'nvidia-smi' is not found
return False
except Exception as e:
print("CUDA availability check failed: {}".format(e))
return False


class Smelt(QWidget):
"""
Smelt GUI application for video and audio processing.
Expand All @@ -89,6 +111,8 @@ def __init__(self):
super(Smelt, self).__init__()

# Initialize paths and filenames
self.ffmpeg_encoder = None
self.video_encoder = None
self.ffmpeg_lossless_audio_cmd = None
self.ffmpeg_audio_cmd = None
self.ffmpeg_h264_from_prores_cmd = None
Expand All @@ -98,7 +122,7 @@ def __init__(self):
self.ffmpeg_prores_cmd = None
self.ffmpeg_h264_cmd_direct = None
self.ffmpeg_lossless_cmd = None
self.ffmpeg_base_cmd = None
self.ffmpeg_base = None
self.step_label = None
self.images_path = None
self.proceed_prores = None
Expand Down Expand Up @@ -590,6 +614,23 @@ def initial_setup(self):
else:
self.video = self.mappe_input_field.text()

if cuda_available():
self.ffmpeg_hardware_accel = [
'-hwaccel', 'cuda',
]
self.ffmpeg_encoder = [
'-c:v', 'hevc_nvenc',
'-pix_fmt', 'yuv422p10le',
]
else:
self.ffmpeg_hardware_accel = [
'-hwaccel', 'auto',
]
self.ffmpeg_encoder = [
'-c:v', 'libx264',
'-pix_fmt', 'yuv422p10le',
]

self.fps = self.fpsCounter.currentText()
self.folder_name = os.path.basename(folder_path)
self.audio_file = self.audio_file_path or ''
Expand Down Expand Up @@ -666,11 +707,13 @@ def construct_dpx_commands(self):
prefix = re.match(r'^\D*', base_filename).group()
ffmpeg_input_pattern = os.path.join(self.folder_path, '{}%06d.dpx'.format(prefix))

self.ffmpeg_base_cmd = [
'ffmpeg',
'-v', 'error',
'-stats',
self.ffmpeg_base = [
'ffmpeg', '-v',
'info', '-stats',
'-progress', '-',
]

ffmpeg_dpx = [
'-f', 'image2',
'-vsync', '0',
'-framerate', self.fps,
Expand All @@ -679,103 +722,90 @@ def construct_dpx_commands(self):
]

if self.inkluderLydCheckBox.isChecked() and os.path.exists(self.audio_file):
self.ffmpeg_base_cmd.extend(['-i', self.audio_file])
ffmpeg_dpx.extend(['-i', self.audio_file])
audio_cmd = ['-c:a', 'copy']
else:
audio_cmd = []

self.ffmpeg_lossless_cmd = self.ffmpeg_base_cmd + audio_cmd + [
'-c:v', 'libx264',
'-pix_fmt', 'yuv422p10le',
'-qp', '0',
'-v', 'info',
self.lossless_mov,
self.proceed_lossless
]
if not self.inkluderLydCheckBox:
self.ffmpeg_h264_cmd_direct = self.ffmpeg_base_cmd + [
self.ffmpeg_lossless_cmd = self.ffmpeg_base + self.ffmpeg_hardware_accel + ffmpeg_dpx + self.ffmpeg_encoder + audio_cmd + [
'-qp', '0',
self.lossless_mov,
self.proceed_lossless
]

if self.inkluderLydCheckBox.isChecked():
self.ffmpeg_h264_cmd_direct = self.ffmpeg_base + self.ffmpeg_hardware_accel + ffmpeg_dpx + audio_cmd + [
'-c:v', 'libx264',
'-pix_fmt', 'yuv420p',
'-vf', 'scale=-2:1080',
'-preset', 'slow',
'-crf', '23',
'-c:a', 'aac',
'-b:a', '224k',
'-map', '0:v:0',
'-v', 'info',
'-map', '1:a:0',
self.h264_mp4,
self.proceed_h264
]
else:
self.ffmpeg_h264_cmd_direct = self.ffmpeg_base_cmd + [
'-i', self.audio_file,
self.ffmpeg_h264_cmd_direct = self.ffmpeg_base + self.ffmpeg_hardware_accel + ffmpeg_dpx + [
'-c:v', 'libx264',
'-pix_fmt', 'yuv420p',
'-vf', 'scale=-2:1080',
'-preset', 'slow',
'-crf', '23',
'-c:a', 'aac',
'-b:a', '224k',
'-map', '0:v:0',
'-map', '1:a:0',
'-v', 'info',
self.h264_mp4,
self.proceed_h264
]

self.ffmpeg_prores_cmd = [
'ffmpeg',
self.ffmpeg_prores_cmd = self.ffmpeg_base + self.ffmpeg_hardware_accel + [
'-i', self.lossless_mov,
'-c:v', 'prores',
'-profile:v', '3',
'-vf', 'scale=-2:1080',
'-c:a', 'pcm_s16le',
'-v', 'info',
self.prores_mov,
self.proceed_prores
]

self.ffmpeg_h264_cmd = [
'ffmpeg',
self.ffmpeg_h264_cmd = self.ffmpeg_base + self.ffmpeg_hardware_accel + [
'-i', self.lossless_mov,
'-c:v', 'libx264',
'-vf', 'scale=-2:1080',
'-pix_fmt', 'yuv420p',
'-preset', 'slow',
'-crf', '23',
'-c:a', 'aac',
'-b:a', '224k',
'-v', 'info',
self.h264_mp4,
self.proceed_h264
]
] + self.ffmpeg_encoder + [
'-vf', 'scale=-2:1080',
'-pix_fmt', 'yuv420p',
'-preset', 'slow',
'-crf', '23',
'-c:a', 'aac',
'-b:a', '224k',
self.h264_mp4,
self.proceed_h264
]

def construct_mxf_mov_commands(self):
"""
Construct FFmpeg commands for MXF and MOV file processing.
"""
ffmpeg_base = [
'ffmpeg',
'-i', self.video,
]
ffmpeg_base = ['ffmpeg',]

if self.inkluderLydCheckBox.isChecked and self.audio_file:
ffmpeg_audio = ['-i', self.audio_file, ]
ffmpeg_video_audio = ['-i', self.video, '-i', self.audio_file, ]
ffmpeg_audio_param = [
'-c:a', 'aac',
'-b:a', '224k',
]
else:
ffmpeg_audio = []
ffmpeg_video_audio = ['-i', self.video,]
ffmpeg_audio_param = []
self.ffmpeg_dcp_cmd = ffmpeg_base + ffmpeg_audio + [
'-c:v', 'libx264',
'-pix_fmt', 'yuv422p10le',
self.ffmpeg_dcp_cmd = ffmpeg_base + self.ffmpeg_hardware_accel + ffmpeg_video_audio + self.ffmpeg_encoder + [
'-preset', 'slow',
'-qp', '0',
'-c:a', 'copy',
'-v', 'info',
self.lossless_mov,
self.proceed_lossless
]
self.ffmpeg_dcp_h264_cmd = ffmpeg_base + ffmpeg_audio + [
self.ffmpeg_dcp_h264_cmd = ffmpeg_base + self.ffmpeg_hardware_accel + ffmpeg_video_audio + [
'-c:v', 'libx264',
'-pix_fmt', 'yuv420p',
'-preset', 'slow',
Expand All @@ -786,7 +816,7 @@ def construct_mxf_mov_commands(self):
self.h264_mp4,
self.proceed_h264
]
self.ffmpeg_h264_from_prores_cmd = ffmpeg_base + ffmpeg_audio + [
self.ffmpeg_h264_from_prores_cmd = ffmpeg_base + self.ffmpeg_hardware_accel + ffmpeg_video_audio + [
'-c:v', 'libx264',
'-vf', 'scale=-2:1080',
'-pix_fmt', 'yuv420p',
Expand Down Expand Up @@ -876,11 +906,34 @@ def run_ffmpeg_command(self, command):
bool: True if the command was successful, False otherwise.
"""
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
stdout_queue = queue.Queue()
stderr_queue = queue.Queue()

def enqueue_output(pipe, queue):
for line in iter(pipe.readline, ''):
queue.put(line)
pipe.close()

stdout_thread = threading.Thread(target=enqueue_output, args=(process.stdout, stdout_queue))
stderr_thread = threading.Thread(target=enqueue_output, args=(process.stderr, stderr_queue))
stdout_thread.start()
stderr_thread.start()

total_duration = None
start_time = None
start_time = time.time()
stderr_output = []

while True:
try:
line = stderr_queue.get_nowait()
except queue.Empty:
if process.poll() is not None:
break
time.sleep(0.1)
continue

for line in iter(process.stderr.readline, ''):
self.output_text.append(line.strip())
stderr_output.append(line.strip())
QApplication.processEvents()

if total_duration is None:
Expand All @@ -902,15 +955,14 @@ def run_ffmpeg_command(self, command):
elapsed_time = time.time() - start_time
remaining_time = elapsed_time * (100 - progress) / progress if progress > 0 else 0
self.progress_bar.setValue(int(progress))
self.progress_bar.setFormat(
"{}% - Estimated time left: {}m {}s".format(int(progress), int(remaining_time // 60),
int(remaining_time % 60)))
self.progress_bar.setFormat("{}% - Estimated time left: {}m {}s".format(int(progress), int(remaining_time // 60), int(remaining_time % 60)))

stdout_thread.join()
stderr_thread.join()
process.wait()

if process.returncode != 0:
error_output = process.stderr.read()
self.output_text.append('Error: {}'.format(error_output))
self.output_text.append('Error: {}'.format('\n'.join(stderr_output)))
return False
return True

Expand Down

0 comments on commit 538b591

Please sign in to comment.