Last active
April 7, 2024 17:05
-
-
Save ChronoMonochrome/54a6e105e49495b5b294cfbc0cf82cfb to your computer and use it in GitHub Desktop.
Python script to create video from the specified audio track and an image
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import argparse | |
import numpy as np | |
from PIL import Image | |
from moviepy.editor import * | |
import os | |
import errno | |
TARGET_WIDTH = 1920 | |
TARGET_HEIGHT = 1080 | |
TARGET_FPS = 24 | |
FRAME_BASE_NUMBER = 100 | |
def create_image(image_file, target_width, target_height): | |
# Create a black background image | |
image = Image.new('RGB', (target_width, target_height), (0, 0, 0)) | |
# Load image and resize to fit on the black background | |
if image_file: | |
image_pil = Image.open(image_file) | |
image_pil.thumbnail((target_width, target_height)) | |
width, height = image_pil.size | |
offset = ((target_width - width) // 2, (target_height - height) // 2) | |
image.paste(image_pil, offset) | |
image_name = "tmp_image.png" | |
image.save(image_name, format="PNG") | |
return image_name | |
# Render a 100 frames long video using an image file with MoviePy | |
def render_video(image_file, output_file, frame_number, fps): | |
clip_list = [] | |
image_clip = ImageClip(image_file) | |
for i in range(frame_number): | |
frame = image_clip.set_duration(1/fps) | |
clip_list.append(frame) | |
final_clip = concatenate_videoclips(clip_list) | |
final_clip.write_videofile(output_file, fps=fps) | |
# Create temporary video files to concatenate multiple times to reach the desired frame number | |
def create_temporary_video(image_file, frame_number, fps): | |
num_videos = frame_number // FRAME_BASE_NUMBER | |
extra_frames = frame_number % FRAME_BASE_NUMBER | |
with open('mylist.txt', 'w') as f: | |
render_video(image_file, f'temp_video.mp4', FRAME_BASE_NUMBER, fps) | |
for _ in range(num_videos): | |
f.write(f"file 'temp_video.mp4'\n") | |
if extra_frames > 0: | |
render_video(image_file, f'temp_video_extra.mp4', extra_frames, fps) | |
with open('mylist.txt', 'a') as f: | |
f.write(f"file 'temp_video_extra.mp4'\n") | |
os.remove(image_file) | |
return num_videos | |
def silentremove(filename): | |
if os.path.isfile(filename): | |
os.remove(filename) | |
def cleanup(temp_files_list): | |
for file in temp_files_list: | |
silentremove(file) | |
def create_video(audio_file, image_file, output_file, width, height, fps): | |
audio = AudioFileClip(audio_file) | |
frame_number = int(audio.duration * fps) | |
image_file = create_image(image_file, width, height) | |
# Create temporary video files as necessary to reach the desired frame number | |
num_videos_needed = create_temporary_video(image_file, frame_number, fps) | |
# Concatenate the temporary video files using ffmpeg | |
try: | |
os.system('ffmpeg -y -f concat -safe 0 -i mylist.txt -c copy temp_concat.mp4') | |
os.system(f'ffmpeg -y -i temp_concat.mp4 -i \"{audio_file}\" -map 0 -map 1:a -c:v copy -shortest \"{output_file}\"') | |
except: | |
raise | |
finally: | |
cleanup(["temp_video.mp4", "temp_concat.mp4", "temp_video_extra.mp4", "mylist.txt"]) | |
print(f"Video has been successfully rendered and saved as {output_file}") | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser(description="Create a video from an audio file and an image.") | |
parser.add_argument("-a", "--audio", required=True, help="Path to audio file") | |
parser.add_argument("-i", "--image", help="Path to image file") | |
parser.add_argument("--width", help="Output video width", type=int, default=TARGET_WIDTH) | |
parser.add_argument("--height", help="Output video height", type=int, default=TARGET_HEIGHT) | |
parser.add_argument("-f", "--fps", help="Output video FPS", type=int, default=TARGET_FPS) | |
parser.add_argument("-o", "--output", required=True, help="Output file name") | |
args = parser.parse_args() | |
create_video(args.audio, args.image, args.output, args.width, args.height, args.fps) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment