Last active
November 6, 2024 10: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 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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For GIF images I have another code using PIL rather than OpenCV:
https://gist.github.com/uwezi/87a65edf71adbe7a6767a67f01434625/revisions
it should handle transparent GIF images just find.
Even though my individual GISTs don't have individual licenses attached you can see all my stuff on Github as MIT licensed
https://github.com/uwezi/manim-related?tab=MIT-1-ov-file#readme