import os from contextlib import contextmanager from django.core.files.temp import NamedTemporaryFile from django.db import transaction from django.db.models.signals import post_delete, post_save from wagtailvideos import ffmpeg, get_video_model @contextmanager def get_local_file(file): """ Get a local version of the file, downloading it from the remote storage if required. The returned value should be used as a context manager to ensure any temporary files are cleaned up afterwards. """ try: with open(file.path): yield file.path except NotImplementedError: _, ext = os.path.splitext(file.name) with NamedTemporaryFile(prefix='wagtailvideo-', suffix=ext) as tmp: try: file.open('rb') for chunk in file.chunks(): tmp.write(chunk) finally: file.close() tmp.flush() yield tmp.name def post_delete_file_cleanup(instance, **kwargs): # Pass false so FileField doesn't save the model. transaction.on_commit(lambda: instance.file.delete(False)) if hasattr(instance, 'thumbnail'): # Delete the thumbnail for videos too transaction.on_commit(lambda: instance.thumbnail.delete(False)) # Fields that need the actual video file to create using ffmpeg def video_post_save(instance, **kwargs): if not ffmpeg.installed(): return if hasattr(instance, '_from_signal'): # Sender was us, don't run post save return has_changed = instance._initial_file is not instance.file filled_out = instance.thumbnail is not None and instance.duration is not None if has_changed or not filled_out: with get_local_file(instance.file) as file_path: if has_changed or instance.thumbnail is None: instance.thumbnail = ffmpeg.get_thumbnail(file_path) if has_changed or instance.duration is None: instance.duration = ffmpeg.get_duration(file_path) instance.file_size = instance.file.size instance._from_signal = True instance.save() del instance._from_signal def register_signal_handlers(): Video = get_video_model() VideoTranscode = Video.get_transcode_model() TrackListing = Video.get_track_listing_model() VideoTrack = TrackListing.get_track_model() post_save.connect(video_post_save, sender=Video) post_delete.connect(post_delete_file_cleanup, sender=Video) post_delete.connect(post_delete_file_cleanup, sender=VideoTranscode) post_delete.connect(post_delete_file_cleanup, sender=VideoTrack)