Skip to content

Instantly share code, notes, and snippets.

@D221
Last active January 15, 2024 19:40
Show Gist options
  • Save D221/1ad2f610c0d44ae1263f894c37383b11 to your computer and use it in GitHub Desktop.
Save D221/1ad2f610c0d44ae1263f894c37383b11 to your computer and use it in GitHub Desktop.
dynamic video rotation / spin for TikTok etc using ffmpeg+ffprobe
import argparse
import json
import os
import subprocess
IMAGE_DIR = "./images/"
WEBM_DIR = "./mp4/"
TEMP_VIDEO = "./temp.mp4"
SEQUENCE_LIST = "./mylist.txt"
VIDEO_NAME = "./video.mp4"
SOUND_NAME = "./sound.aac"
def check_dirs():
if not os.path.exists(IMAGE_DIR):
os.makedirs(IMAGE_DIR)
if not os.path.exists(WEBM_DIR):
os.makedirs(WEBM_DIR)
def delete_temp_files():
for file in os.listdir(IMAGE_DIR):
os.remove(IMAGE_DIR + file)
for file in os.listdir(WEBM_DIR):
os.remove(WEBM_DIR + file)
os.remove(SOUND_NAME)
os.remove(SEQUENCE_LIST)
os.remove(TEMP_VIDEO)
def extract_frame_rate(video):
print("Extracting video information for frame rate")
# Get video information using ffprobe
ffprobe_cmd = [
"ffprobe",
"-v",
"error",
"-select_streams",
"v",
"-show_entries",
"stream=avg_frame_rate",
"-of",
"json",
video,
]
result = subprocess.run(
ffprobe_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
)
if result.returncode != 0:
print(f"Error getting video information: {result.stderr}")
return None
try:
# Parse the JSON output to get the frame rate
info = json.loads(result.stdout)
frame_rate = eval(info["streams"][0]["avg_frame_rate"])
print(f"Video frame rate: {frame_rate}")
return frame_rate
except (json.JSONDecodeError, KeyError):
print("Error parsing video information.")
return None
def extract(video, frame_rate):
print("Converting video to sequence")
# extract frames from video
subprocess.run(
[
"ffmpeg",
"-hide_banner",
"-i",
video,
"-r",
str(frame_rate),
"./images/%03d.png",
],
)
# extract sound
subprocess.run(
["ffmpeg", "-i", video, "-vn", SOUND_NAME],
stdout=subprocess.DEVNULL,
stderr=subprocess.STDOUT,
)
def convert_images(frame_rate):
images = os.listdir(IMAGE_DIR)
total_images = len(images)
with open("mylist.txt", "w") as file_list:
for idx, image in enumerate(images, start=1):
image_name = image.split(".")[0]
print(f"Converting images to videos: {idx}/{total_images}", end="\r")
subprocess.run(
[
"ffmpeg",
"-i",
IMAGE_DIR + str(image_name) + ".png",
"-r",
str(frame_rate),
WEBM_DIR + str(image_name) + ".mp4",
],
stdout=subprocess.DEVNULL,
stderr=subprocess.STDOUT,
)
file_list.write(f"file '{WEBM_DIR}{image_name}.mp4'\n")
def create_webm(frame_rate, output_filename):
subprocess.run(
[
"ffmpeg",
"-f",
"concat",
"-r",
str(frame_rate),
"-safe",
"0",
"-i",
"mylist.txt",
"-c",
"copy",
"./temp.mp4",
],
stdout=subprocess.DEVNULL,
stderr=subprocess.STDOUT,
)
# add sound
subprocess.run(
[
"ffmpeg",
"-i",
SOUND_NAME,
"-i",
"./temp.mp4",
"-c",
"copy",
str(output_filename),
],
stdout=subprocess.DEVNULL,
stderr=subprocess.STDOUT,
)
def main():
parser = argparse.ArgumentParser(
description="Process a video and create a rotated mp4 file."
)
parser.add_argument("video", help="Input video file path")
args = parser.parse_args()
video_name, video_extension = os.path.splitext(os.path.basename(args.video))
input_video_dir = os.path.dirname(args.video)
check_dirs()
frame_rate = extract_frame_rate(args.video)
extract(args.video, frame_rate)
input(
"\n\nPlease rotate the images manually and press Enter!\nOn Windows just right click and rotate"
)
convert_images(frame_rate)
# Save spinned video in the same folder as the input video
output_filename = f"{video_name}_spinned{video_extension}"
output_filepath = os.path.join(input_video_dir, output_filename)
create_webm(frame_rate, output_filepath)
delete_temp_files()
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\nExiting...")
delete_temp_files()
exit(0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment