From 9128a47bc928b70927c3e90d27c2108afd606c17 Mon Sep 17 00:00:00 2001 From: Manuel Raynaud Date: Tue, 12 Nov 2024 17:29:52 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(thumbnail)=20do=20not=20process=20?= =?UTF-8?q?thumbnail=20for=20video=20without=20video=20stream?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In somes cases video uploaded do not have a video stream. In that case, we should not generate a thumbnail has there is nothing to process. --- CHANGELOG.md | 4 ++ .../transcode.py | 4 +- .../utils/thumbnail.py | 7 +- .../probe_response.py | 71 +++++++++++++++++++ .../test_transcode.py | 4 +- .../utils/test_thumbnail.py | 17 +++++ 6 files changed, 104 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a77e217..94859fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to ## [Unreleased] +### Fixed + +- Do not process thumbnail for video without video stream + ## [0.12.0] - 2024-11-12 ### Changed diff --git a/src/django_peertube_runner_connector/transcode.py b/src/django_peertube_runner_connector/transcode.py index 0a71b1f..61559c8 100644 --- a/src/django_peertube_runner_connector/transcode.py +++ b/src/django_peertube_runner_connector/transcode.py @@ -30,7 +30,9 @@ def _process_transcoding(video: Video, video_path: str, domain: str): video.duration = get_video_stream_duration(video_path, existing_probe=probe) - video.thumbnailFilename = build_video_thumbnails(video=video, video_file=video_file) + video.thumbnailFilename = build_video_thumbnails( + video=video, video_file=video_file, existing_probe=probe + ) video.save() logger.info("Video at %s and uuid %s created.", video_path, video.uuid) diff --git a/src/django_peertube_runner_connector/utils/thumbnail.py b/src/django_peertube_runner_connector/utils/thumbnail.py index 0b4c098..bb3b7b5 100644 --- a/src/django_peertube_runner_connector/utils/thumbnail.py +++ b/src/django_peertube_runner_connector/utils/thumbnail.py @@ -7,12 +7,17 @@ from django_peertube_runner_connector.models import Video, VideoFile from django_peertube_runner_connector.storage import video_storage +from .ffprobe import get_video_stream from .files import get_video_directory -def build_video_thumbnails(video=Video, video_file=VideoFile): +def build_video_thumbnails(video=Video, video_file=VideoFile, existing_probe=None): """Create a video thumbnails with ffmpeg and save it to a file.""" video_url = video_storage.url(video_file.filename) + + if get_video_stream(video_url, existing_probe=existing_probe) is None: + return None + thumbnail_filename = get_video_directory(video, "thumbnail.jpg") with tempfile.NamedTemporaryFile(suffix=".jpg") as temp_file: diff --git a/tests/tests_django_peertube_runner_connector/probe_response.py b/tests/tests_django_peertube_runner_connector/probe_response.py index 03c4396..d576325 100644 --- a/tests/tests_django_peertube_runner_connector/probe_response.py +++ b/tests/tests_django_peertube_runner_connector/probe_response.py @@ -88,3 +88,74 @@ }, }, } + +probe_response_without_video_stream = { + "streams": [ + { + "index": 0, + "codec_name": "aac", + "codec_long_name": "AAC (Advanced Audio Coding)", + "profile": "LC", + "codec_type": "audio", + "codec_tag_string": "mp4a", + "codec_tag": "0x6134706d", + "sample_fmt": "fltp", + "sample_rate": "32000", + "channels": 1, + "channel_layout": "mono", + "bits_per_sample": 0, + "id": "0x1", + "r_frame_rate": "0/0", + "avg_frame_rate": "0/0", + "time_base": "1/32000", + "start_pts": 0, + "start_time": "0.000000", + "duration_ts": 682251264, + "duration": "21320.352000", + "bit_rate": "126248", + "extradata_size": 2, + "disposition": { + "default": 1, + "dub": 0, + "original": 0, + "comment": 0, + "lyrics": 0, + "karaoke": 0, + "forced": 0, + "hearing_impaired": 0, + "visual_impaired": 0, + "clean_effects": 0, + "attached_pic": 0, + "timed_thumbnails": 0, + "captions": 0, + "descriptions": 0, + "metadata": 0, + "dependent": 0, + "still_image": 0, + }, + "tags": { + "language": "und", + "handler_name": "SoundHandler", + "vendor_id": "[0][0][0][0]", + }, + } + ], + "format": { + "filename": "path/to/video.mp4", + "nb_streams": 1, + "nb_programs": 0, + "format_name": "mov,mp4,m4a,3gp,3g2,mj2", + "format_long_name": "QuickTime / MOV", + "start_time": "0.000000", + "duration": "21320.352000", + "size": "339980572", + "bit_rate": "127570", + "probe_score": 100, + "tags": { + "major_brand": "iso5", + "minor_version": "512", + "compatible_brands": "iso5iso6mp41", + "encoder": "Lavf59.27.100", + }, + }, +} diff --git a/tests/tests_django_peertube_runner_connector/test_transcode.py b/tests/tests_django_peertube_runner_connector/test_transcode.py index 3a00232..52e369b 100644 --- a/tests/tests_django_peertube_runner_connector/test_transcode.py +++ b/tests/tests_django_peertube_runner_connector/test_transcode.py @@ -85,7 +85,9 @@ def test_process_transcoding( mock_duration.assert_called_with( video_url, existing_probe=mock_probe.return_value ) - mock_thumbnails.assert_called_with(video=video, video_file=video_file) + mock_thumbnails.assert_called_with( + video=video, video_file=video_file, existing_probe=mock_probe.return_value + ) mock_transcoding.assert_called_with( video=video, video_file=video_file, diff --git a/tests/tests_django_peertube_runner_connector/utils/test_thumbnail.py b/tests/tests_django_peertube_runner_connector/utils/test_thumbnail.py index 15f1f16..c0a14f9 100644 --- a/tests/tests_django_peertube_runner_connector/utils/test_thumbnail.py +++ b/tests/tests_django_peertube_runner_connector/utils/test_thumbnail.py @@ -5,6 +5,10 @@ from django.test import TestCase import ffmpeg +from tests_django_peertube_runner_connector.probe_response import ( + probe_response, + probe_response_without_video_stream, +) from django_peertube_runner_connector.factories import VideoFactory, VideoFileFactory from django_peertube_runner_connector.storage import video_storage @@ -31,8 +35,21 @@ def test_build_video_thumbnails(self, mock_run): thumbnail_filename = build_video_thumbnails( video=self.video, video_file=self.video_file, + existing_probe=probe_response, ) mock_run.assert_called_once() self.assertEqual(thumbnail_filename, self.thumbnail_filename) self.assertTrue(video_storage.exists(thumbnail_filename)) + + @patch.object(ffmpeg, "run") + def test_build_video_thumbnails_with_no_video_stream(self, mock_run): + """Should create a thumbnail file.""" + thumbnail_filename = build_video_thumbnails( + video=self.video, + video_file=self.video_file, + existing_probe=probe_response_without_video_stream, + ) + + mock_run.assert_not_called() + self.assertIsNone(thumbnail_filename)