Skip to content

Instantly share code, notes, and snippets.

@ChronoMonochrome
Last active April 7, 2024 17:05
Show Gist options
  • Save ChronoMonochrome/54a6e105e49495b5b294cfbc0cf82cfb to your computer and use it in GitHub Desktop.
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
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