前言
manim 动画是在场景中加入:文本、数学公式、坐标及函数图等动画对象,再附上背景音乐已是高大上了。但最近有个需求是在场景中指定的地方嵌入外部视频文件,可以说是动画中的动画,当时以为manim具备这个功能,但查了帮助文档和资料,都没有找到,怎么办呢,实际需求却是存在的。想了好大一会,于是决定自已来实现这个功能,思路是把原始视频音频和视频先分离开,视频文件中的每一Frame都用MObject来包装,最后再加上分离的音频文件,终于实现了这个需求。下面是我的具体实现。
先上效果:
1、先安装视频处理的包
前提条件是已经安装了 manim
pip install opencv-python movipy
2、具体实现,自已新建一个VideoUtil.py文件,在该文件中加入代码。我直接上代码
from manim import Animation,ImageMobject
from manim.typing import Vector3D
import numpy as np
import os
from PIL import Image
import cv2
from moviepy import VideoFileClip
class VideoCustomUtil:
"""
videoCustomUtil = VideoCustomUtil("./assets/videos/biba.mp4", sample_inter=3)
video_img_objs, r_time = videoCustomUtil.create_animations(position=DOWN + LEFT * 4.85)
audio_file = videoCustomUtil.audio_file
if audio_file is not None:
self.add_sound(audio_file)
for video_img_obj in video_img_objs:
video_img_obj.scale(0.5)
animal = Animation(video_img_obj)
self.play(animal, run_time=r_time)
Parameters
video_file is video file path
sample_inter is sample interval default is 3
scene is scene that animation to play Scene
"""
def __init__(self, video_file,sample_inter=1):
self.video_file = video_file
self.audio_file = None
self.sample_inter = sample_inter
self.video_w = None
self.video_h = None
self.video_duration = None
def create_animations(self, position=None):
#-----create manim animations------------------
#param postion is mobject's position in Scene
v_w,v_h,duration, fps, audio_file_name = self.get_video_info()
file_folder = "./assets/images/temp/"
self.create_folder(file_folder)
cap = cv2.VideoCapture(self.video_file)
i = -1;
video_img_objs: list[ImageMobject] = []
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
i = i + 1
if i % self.sample_inter != 0 :
continue
temp_file = file_folder + "temp_cv2.png"
cv2.imwrite(temp_file, frame)
image_array = np.array(Image.open(temp_file))
img_obj = ImageMobject(image_array)
video_img_objs.append(img_obj)
if os.path.exists(temp_file):
os.unlink(temp_file)
cap.release()
#------start ready animations------------------------------
for video_img_obj in video_img_objs:
video_img_obj.move_to(position)
# animal = Animation(video_img_obj)
# self.scene.play(animal, run_time=self.sample_inter/fps)
return video_img_objs,self.sample_inter/fps
def get_video_info(self):
video_clip = VideoFileClip(self.video_file)
duration = video_clip.duration
v_w = video_clip.w
v_h = video_clip.h
fps = video_clip.fps
audio = video_clip.audio
audio_file_name = self.video_file + ".mp3"
if os.path.exists(audio_file_name):
os.unlink(audio_file_name)
audio.write_audiofile(audio_file_name)
self.audio_file = audio_file_name
self.video_w = v_w
self.video_h = v_h
self.video_duration = duration
return v_w,v_h,duration, fps, audio_file_name
def create_folder(self,folder_path):
if not os.path.exists(folder_path):
os.makedirs(folder_path)
else:
print(f"Folder '{folder_path}' already exists.")
def create_image_obj(self, image_file:str = None, position:Vector3D = None):
"""
image_array = np.array(Image.open("./assets/images/hs.png"))
img_obj = ImageMobject(image_array)
img_obj.move_to(DOWN + LEFT * 6.3, aligned_edge=LEFT)
fade_in_animation5 = FadeIn(img_obj)
self.play(fade_in_animation5, run_time=2)
:param image_file:
:param position:
:return:
"""
image_array = np.array(Image.open(image_file))
img_obj = ImageMobject(image_array)
img_obj.move_to(position)
return img_obj
讲一下这个总体思路:先用movepy把原始视频文件中的视频与音频分开,音频保存成mp3文件,再用opencv对这个视频文件按帧读取,每读取一帧用MObject进行包装,所有的帧就都成了MObject,这样就可以在manim的Scene中加入动画了。
3、测试,直接上代码
# -*- coding=utf-8 -*-
from manim import *
import numpy as np
import os
from PIL import Image
from ppt.utils.VideoUtil import VideoCustomUtil
class FlyLevelPPT_2(Scene):
def setBackground(self):
self.camera.background_color = (68, 84, 106)
def construct(self):
config.tex_template = TexTemplate()
config.tex_template.add_to_preamble(r"\usepackage{ctex}")
self.setBackground()
self.clear()
# ------to do start----------------------
tx1,tx2,tx3,tx4 = self.addTxt_1()
self.wait(2)
#self.add_image()
#self.wait(2)
self.add_video_1()
def add_image(self, mobject: VMobject = None):
pass
def addTxt_1(self, mobject: VMobject = None):
tx1 = MathTex(
"除了幂函数,最常用的函数就是",
"三角函数",
",三角函数有一个非常重要的特点就",
arg_separator="",
tex_template=config.tex_template,
font_size=32
)
tx1[1].color = ManimColor("#f6f208")
tx1.move_to(UP * 3.5 + LEFT * 5.6, aligned_edge=LEFT)
fade_in_animation1 = FadeIn(tx1)
self.play(fade_in_animation1, run_time=2)
self.wait(2)
tx2 = MathTex(
"是",
"周期性",
",而许多事物的发展变化都呈现出",
"周期性",
",如发动机活塞的运动,交流电",
arg_separator="",
tex_template=config.tex_template,
font_size=32
)
tx2[1].color = ManimColor("#f6f208")
tx2[3].color = ManimColor("#f6f208")
tx2.move_to(UP * 3.0 + LEFT * 6.3, aligned_edge=LEFT)
fade_in_animation2 = FadeIn(tx2)
self.play(fade_in_animation2, run_time=2)
self.wait(2)
tx3 = MathTex(
"的电流电压变化,信号的周期变化等。因此我们自然会想到能不能将这些",
"周期函数",
arg_separator="",
tex_template=config.tex_template,
font_size=32
)
tx3[1].color = ManimColor("#f6f208")
tx3.move_to(UP * 2.5 + LEFT * 6.3, aligned_edge=LEFT)
fade_in_animation3 = FadeIn(tx3)
self.play(fade_in_animation3, run_time=2)
self.wait(2)
tx4 = MathTex(
"用",
"三角函数",
"来展开呢?答案是肯定的。",
arg_separator="",
tex_template=config.tex_template,
font_size=32
)
tx4[1].color = ManimColor("#f6f208")
tx4.move_to(UP * 2.0 + LEFT * 6.3, aligned_edge=LEFT)
fade_in_animation4 = FadeIn(tx4)
self.play(fade_in_animation4, run_time=2)
return tx1,tx2,tx3,tx4
def add_video_1(self, video_obj: VMobject=None):
videoCustomUtil = VideoCustomUtil("./assets/videos/biba.mp4", sample_inter=3)
video_img_objs, r_time = videoCustomUtil.create_animations(position=DOWN + LEFT * 4.85)
audio_file = videoCustomUtil.audio_file
if audio_file is not None:
self.add_sound(audio_file)
for video_img_obj in video_img_objs:
video_img_obj.scale(0.5)
animal = Animation(video_img_obj)
self.play(animal, run_time=r_time)
if __name__ == "__main__":
module_name = os.path.basename(__file__)
print(module_name)
scene_name = "FlyLevelPPT_2"
os.system(f"manim --disable_caching -pqh {module_name} {scene_name}")
到此为止,全部完成了。