Last active
May 6, 2025 20:59
-
-
Save uwezi/faec101ed5d7c20222b33eee4b6c7d63 to your computer and use it in GitHub Desktop.
[video inclusion in Manim] Include video objects picture-in-picture. #manim #animate #video #opencv #videomobject
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 cv2 # needs opencv-python https://pypi.org/project/opencv-python/ | |
from PIL import Image, ImageOps | |
from dataclasses import dataclass | |
@dataclass | |
class VideoStatus: | |
time: float = 0 | |
videoObject: cv2.VideoCapture = None | |
def __deepcopy__(self, memo): | |
return self | |
class VideoMobject(ImageMobject): | |
''' | |
Following a discussion on Discord about animated GIF images. | |
Modified for videos | |
Parameters | |
---------- | |
filename | |
the filename of the video file | |
imageops | |
(optional) possibility to include a PIL.ImageOps operation, e.g. | |
PIL.ImageOps.mirror | |
speed | |
(optional) speed-up/slow-down the playback | |
loop | |
(optional) replay the video from the start in an endless loop | |
https://discord.com/channels/581738731934056449/1126245755607339250/1126245755607339250 | |
2023-07-06 Uwe Zimmermann & Abulafia | |
2024-03-09 Uwe Zimmermann | |
''' | |
def __init__(self, filename=None, imageops=None, speed=1.0, loop=False, **kwargs): | |
self.filename = filename | |
self.imageops = imageops | |
self.speed = speed | |
self.loop = loop | |
self._id = id(self) | |
self.status = VideoStatus() | |
self.status.videoObject = cv2.VideoCapture(filename) | |
self.status.videoObject.set(cv2.CAP_PROP_POS_FRAMES, 1) | |
ret, frame = self.status.videoObject.read() | |
if ret: | |
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) | |
img = Image.fromarray(frame) | |
if imageops != None: | |
img = imageops(img) | |
else: | |
img = Image.fromarray(np.uint8([[63, 0, 0, 0], | |
[0, 127, 0, 0], | |
[0, 0, 191, 0], | |
[0, 0, 0, 255] | |
])) | |
super().__init__(img, **kwargs) | |
if ret: | |
self.add_updater(self.videoUpdater) | |
def videoUpdater(self, mobj, dt): | |
if dt == 0: | |
return | |
status = self.status | |
status.time += 1000*dt*mobj.speed | |
self.status.videoObject.set(cv2.CAP_PROP_POS_MSEC, status.time) | |
ret, frame = self.status.videoObject.read() | |
if (ret == False) and self.loop: | |
status.time = 0 | |
self.status.videoObject.set(cv2.CAP_PROP_POS_MSEC, status.time) | |
ret, frame = self.status.videoObject.read() | |
if ret: | |
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # needed here? | |
img = Image.fromarray(frame) | |
if mobj.imageops != None: | |
img = mobj.imageops(img) | |
mobj.pixel_array = change_to_rgba_array( | |
np.asarray(img), mobj.pixel_array_dtype | |
) | |
class test(Scene): | |
def construct(self): | |
video1 = VideoMobject( | |
filename=r"D:\Programming\Python\manim\discord\media\videos\20240304_01\480p15\Countdown.mp4", | |
speed=1.0 | |
).scale_to_fit_width(5).to_corner(UL) | |
video2 = VideoMobject( | |
filename=r"D:\Programming\Python\manim\discord\media\videos\20240304_01\480p15\Countdown.mp4", | |
speed=3.0, | |
loop=True, | |
imageops=ImageOps.mirror | |
).scale_to_fit_width(5).to_corner(UR) | |
v1 = Group(video1, SurroundingRectangle(video1)) | |
v2 = Group(video2, SurroundingRectangle(video2)) | |
self.add(v1,v2) | |
self.wait(2) | |
self.play(v2.animate.shift(3*DL), run_time=6) | |
self.wait(2) |
Hi @uwezi, Why is this
VideoMobject
class not included into manim official class?
...because nobody has yet bothered to include it into the codebase - and I personally didn't have the time to bother...
Uwe.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi @uwezi, Why is this
VideoMobject
class not included into manim official class?