Add NVIDIA support for FFmpeg in Docker and enhance chat rendering functionality
All checks were successful
Publish Twitch Archive Container / publish (push) Successful in 7m36s
All checks were successful
Publish Twitch Archive Container / publish (push) Successful in 7m36s
- Introduced a new docker-compose.nvidia.yml for NVIDIA GPU support. - Updated dockerstart.bat to allow optional NVIDIA runtime. - Enhanced ContentDownloader to manage chat rendering status and font settings. - Improved hardware acceleration detection in utils.py. - Added tests for hardware acceleration and chat rendering behavior. Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
parent
f97e0200d6
commit
ec44981a9d
8 changed files with 226 additions and 18 deletions
|
|
@ -31,7 +31,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|||
from modules.constants import DEFAULT_CONFIG
|
||||
from modules.file_manager import FileManager
|
||||
from modules.downloader import ContentDownloader
|
||||
from modules.utils import get_ffmpeg_executable, get_twitch_downloader_executable
|
||||
from modules.utils import get_ffmpeg_executable, get_twitch_downloader_executable, detect_hardware_acceleration, resolve_hwaccel_type
|
||||
|
||||
|
||||
def load_twitch_archive_module():
|
||||
|
|
@ -541,6 +541,69 @@ class TestLinuxToolResolution(unittest.TestCase):
|
|||
|
||||
self.assertEqual(get_twitch_downloader_executable('linux'), '/usr/local/bin/TwitchDownloaderCLI')
|
||||
|
||||
@patch('modules.utils.os.path.exists', return_value=False)
|
||||
@patch('modules.utils.shutil.which', return_value=None)
|
||||
def test_linux_auto_hwaccel_falls_back_to_software_without_runtime(self, _mock_which, _mock_exists):
|
||||
detected = detect_hardware_acceleration('auto', 'linux')
|
||||
|
||||
self.assertEqual(detected, 'none')
|
||||
self.assertEqual(resolve_hwaccel_type(detected, 'linux'), 'none')
|
||||
|
||||
@patch('modules.utils.shutil.which', return_value='/usr/bin/nvidia-smi')
|
||||
def test_linux_auto_hwaccel_uses_nvenc_when_nvidia_runtime_visible(self, _mock_which):
|
||||
detected = detect_hardware_acceleration('auto', 'linux')
|
||||
|
||||
self.assertEqual(detected, 'nvenc')
|
||||
|
||||
|
||||
class TestChatRenderBehavior(unittest.TestCase):
|
||||
"""Regression tests for chat rendering defaults and retry behavior."""
|
||||
|
||||
@patch('modules.downloader.sys.platform', 'linux')
|
||||
@patch('modules.downloader.subprocess.Popen')
|
||||
def test_linux_chat_render_uses_container_safe_font(self, mock_popen):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
json_path = os.path.join(temp_dir, 'chat.json')
|
||||
video_path = os.path.join(temp_dir, 'chat.mp4')
|
||||
|
||||
with open(json_path, 'w', encoding='utf-8') as handle:
|
||||
json.dump(
|
||||
{
|
||||
'comments': [
|
||||
{
|
||||
'message': {'body': 'hello world ' * 20},
|
||||
'commenter': {'display_name': 'tester'}
|
||||
}
|
||||
]
|
||||
},
|
||||
handle
|
||||
)
|
||||
|
||||
class FakeProcess:
|
||||
def __init__(self, output_path: str):
|
||||
self.stdout = iter(['rendering'])
|
||||
self.output_path = output_path
|
||||
|
||||
def wait(self):
|
||||
with open(self.output_path, 'wb') as handle:
|
||||
handle.write(b'x' * 2048)
|
||||
return 0
|
||||
|
||||
captured = {}
|
||||
|
||||
def build_process(cmd, **_kwargs):
|
||||
captured['cmd'] = cmd
|
||||
return FakeProcess(video_path)
|
||||
|
||||
mock_popen.side_effect = build_process
|
||||
|
||||
downloader = ContentDownloader('TwitchDownloaderCLI', 'ffmpeg', {'downloadCHAT': True})
|
||||
|
||||
result = downloader.render_chat(json_path, video_path, '-c:v libx264 "{save_path}"')
|
||||
|
||||
self.assertTrue(result)
|
||||
self.assertIn('DejaVu Sans', captured['cmd'])
|
||||
|
||||
|
||||
class TestMultiStreamerCleanupRegression(unittest.TestCase):
|
||||
"""Regression tests for multi-streamer conversion and cleanup behavior."""
|
||||
|
|
@ -636,6 +699,59 @@ class TestMultiStreamerCleanupRegression(unittest.TestCase):
|
|||
upload_filename_base = archiver.file_manager.upload_to_cloud.call_args.args[0]
|
||||
self.assertFalse(upload_filename_base.startswith('LIVE_'))
|
||||
|
||||
def test_process_stream_preserves_files_when_chat_render_fails(self):
|
||||
manager = self.module.TwitchArchiveManager(specific_streamer='maddoscientist0')
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
archiver = self._build_archiver(temp_dir)
|
||||
archiver.downloadCHAT = True
|
||||
archiver.downloadVOD = True
|
||||
archiver.vodTimeout = 30
|
||||
archiver.processor.process_raw_stream.return_value = True
|
||||
archiver.file_manager.upload_to_cloud.return_value = True
|
||||
archiver.downloader.last_chat_render_attempted = False
|
||||
archiver.downloader.last_chat_render_succeeded = False
|
||||
|
||||
def write_raw_file(_stream_info, raw_path):
|
||||
with open(raw_path, 'wb') as handle:
|
||||
handle.write(b'x' * 4096)
|
||||
return True
|
||||
|
||||
def failed_chat_render(*_args, **_kwargs):
|
||||
archiver.downloader.last_chat_render_attempted = True
|
||||
archiver.downloader.last_chat_render_succeeded = False
|
||||
return False
|
||||
|
||||
archiver.recorder.record.side_effect = write_raw_file
|
||||
archiver.downloader.download_and_render_chat.side_effect = failed_chat_render
|
||||
archiver.stream_monitor.get_latest_vod.return_value = {
|
||||
'data': {
|
||||
'user': {
|
||||
'videos': {
|
||||
'edges': [
|
||||
{
|
||||
'node': {
|
||||
'id': '2756589076',
|
||||
'title': 'Test',
|
||||
'recordedAt': '2026-04-25T09:14:01Z'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stream_info = {
|
||||
'title': 'Test',
|
||||
'createdAt': '2026-04-25T09:14:01Z'
|
||||
}
|
||||
|
||||
manager._process_stream(archiver, stream_info, 'stream-id')
|
||||
|
||||
archiver.file_manager.clean_raw_file.assert_not_called()
|
||||
archiver.file_manager.delete_local_files.assert_not_called()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Run tests with verbose output
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue