Created
December 16, 2019 07:14
-
-
Save gurland/af2f21e11c0935445bc662b7db3c50c4 to your computer and use it in GitHub Desktop.
3 Face Hologram video generator
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
python -m venv venv | |
call venv/Scripts/activate.bat | |
pip install -r requirements.txt | |
pip install pyinstaller | |
pyinstaller --workpath temp --onefile main.py |
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
from tkinter import * | |
from tkinter import filedialog as fd | |
from pyramid import Pyramid | |
class Application(Frame): | |
def __init__(self, master): | |
super().__init__(master) | |
self.master = master | |
self.vcmd = (master.register(self.validate_numbers), '%d', '%P', '%S') | |
self.filename = "" | |
master.title("Створення відеоголограм") | |
master.geometry('500x400') | |
master.resizable(False, False) | |
self.create_inputs() | |
self.create_button() | |
self.pack() | |
def create_inputs(self): | |
inputs_frame = Frame(self) | |
file_frame = Frame(inputs_frame) | |
file_entry = Button(file_frame, text='Вибрати вхідний файл', font='Arial 20', command=self.select_file) | |
file_frame.pack(pady=10) | |
file_entry.pack(side=LEFT) | |
width_frame = Frame(inputs_frame) | |
width_label = Label(width_frame, text='Ширина у пікселях =', font='Arial 20') | |
width_entry = Entry(width_frame, width=15, font='Arial 20', validate='key', validatecommand=self.vcmd) | |
self.width_entry = width_entry | |
width_frame.pack(pady=10) | |
width_label.pack(side=LEFT) | |
width_entry.pack(side=LEFT) | |
height_frame = Frame(inputs_frame) | |
height_label = Label(height_frame, text='Висота у пікселях =', font='Arial 20') | |
height_entry = Entry(height_frame, width=15, font='Arial 20', validatecommand=self.vcmd) | |
self.height_entry = height_entry | |
height_frame.pack(pady=10) | |
height_label.pack(side=LEFT) | |
height_entry.pack(side=LEFT) | |
side_offset_frame = Frame(inputs_frame) | |
side_offset_label = Label(side_offset_frame, text='Зміщення на бічних гранях =', font='Arial 20') | |
side_offset_entry = Entry(side_offset_frame, width=15, font='Arial 20', validatecommand=self.vcmd) | |
self.side_offset_entry = side_offset_entry | |
side_offset_frame.pack(pady=10) | |
side_offset_label.pack(side=LEFT) | |
side_offset_entry.pack(side=LEFT) | |
main_offset_frame = Frame(inputs_frame) | |
main_offset_label = Label(main_offset_frame, text='Зміщення на основній грані =', font='Arial 20') | |
main_offset_entry = Entry(main_offset_frame, width=15, font='Arial 20', validatecommand=self.vcmd) | |
self.main_offset_entry = main_offset_entry | |
main_offset_frame.pack(pady=10) | |
main_offset_label.pack(side=LEFT) | |
main_offset_entry.pack(side=LEFT) | |
inputs_frame.pack(pady=(10, 10)) | |
def create_button(self): | |
button_frame = Frame(self) | |
button = Button(button_frame, text='Створити відеоголограмму', font='Arial 20', command=self.create_videohologram) | |
button.pack() | |
button_frame.pack() | |
def select_file(self): | |
self.filename = fd.askopenfilename(initialdir='.') | |
def create_videohologram(self): | |
width = self.width_entry.get() | |
height = self.height_entry.get() | |
if width and height: | |
output_filename = fd.asksaveasfilename(filetypes=(("MPEG4 files", "*.mp4"), | |
("AVI files", "*.avi"), | |
("All files", "*.*"))) | |
side_offset = int(self.side_offset_entry.get()) if self.side_offset_entry.get() else 0 | |
main_offset = int(self.main_offset_entry.get()) if self.main_offset_entry.get() else 0 | |
self.pyramid = Pyramid(int(width), int(height), self.filename) | |
self.pyramid.create_videohologram(side_offset=side_offset, main_offset=main_offset, output_file=output_filename) | |
@staticmethod | |
def validate_numbers(action, value_if_allowed, text): | |
if action == '0': # Delete | |
return True | |
else: # Insert | |
if text in '0123456789.+': | |
if len(value_if_allowed) == 1: | |
return True | |
try: | |
float(value_if_allowed) | |
return True | |
except ValueError: | |
return False | |
else: | |
return False | |
if __name__ == '__main__': | |
root = Tk() | |
my_gui = Application(root) | |
root.mainloop() |
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
from math import sqrt | |
import os | |
from video_file_clip import VideoFileClip | |
from moviepy.video.VideoClip import ColorClip | |
from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip | |
class Pyramid: | |
def __init__(self, display_width, display_height, clip_file): | |
self.clip = VideoFileClip(clip_file) | |
self.W = display_width | |
self.H = display_height | |
self.side = int(display_width/2) | |
self.main = int(sqrt((display_width/2)**2 + display_height**2)) | |
def create_videohologram(self, output_file, side_offset=20, main_offset=0): | |
background_clip = ColorClip((self.W, self.H), (0, 0, 0), duration=0) | |
resized_clip = self.resize_clip(self.clip) | |
left_clip = resized_clip.rotate(-90) | |
x = int((self.W * left_clip.size[1]) / (2 * self.H + side_offset)) | |
left_pos = (self.W / 2 - x - left_clip.size[0], side_offset) | |
left_clip = left_clip.set_position(left_pos) | |
main_pos = ((self.W / 2) - resized_clip.size[0] / 2, resized_clip.size[0] + main_offset) | |
main_clip = resized_clip.set_position(main_pos) | |
right_clip = resized_clip.rotate(90) | |
right_pos = (self.W / 2 + x, side_offset) | |
right_clip = right_clip.set_position(right_pos) | |
video = CompositeVideoClip([background_clip, main_clip, left_clip, right_clip]) | |
video.write_videofile(output_file) | |
os.startfile(output_file) | |
def resize_clip(self, clip): | |
width, height = clip.size | |
if width >= height: | |
return clip.resize(width=int(self.W/4)) | |
else: | |
return clip.resize(height=int(self.W/4)) |
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
from moviepy.video.io.VideoFileClip import VideoFileClip as VFC | |
from PIL import Image | |
import numpy as np | |
PIL_FOUND = True | |
def pil_rotater(pic, angle, resample, expand): | |
return np.array(Image.fromarray(pic).rotate(angle, expand=expand, | |
resample=resample)) | |
def resizer(pic, newsize): | |
newsize = list(map(int, newsize))[::-1] | |
shape = pic.shape | |
if len(shape) == 3: | |
newshape = (newsize[0], newsize[1], shape[2]) | |
else: | |
newshape = (newsize[0], newsize[1]) | |
pilim = Image.fromarray(pic) | |
resized_pil = pilim.resize(newsize[::-1], Image.ANTIALIAS) | |
# arr = np.fromstring(resized_pil.tostring(), dtype='uint8') | |
# arr.reshape(newshape) | |
return np.array(resized_pil) | |
resizer.origin = "PIL" | |
class VideoFileClip(VFC): | |
def __init__(self, *args, **kwargs): | |
super().__init__(*args, **kwargs) | |
def rotate(clip, angle, unit='deg', resample="bicubic", expand=True): | |
resample = {"bilinear": Image.BILINEAR, | |
"nearest": Image.NEAREST, | |
"bicubic": Image.BICUBIC}[resample] | |
if not hasattr(angle, '__call__'): | |
# if angle is a constant, convert to a constant function | |
a = +angle | |
angle = lambda t: a | |
transpo = [1, 0] if clip.ismask else [1, 0, 2] | |
def fl(gf, t): | |
a = angle(t) | |
im = gf(t) | |
if unit == 'rad': | |
a = 360.0 * a / (2 * np.pi) | |
if (a == 90) and expand: | |
return np.transpose(im, axes=transpo)[::-1] | |
elif (a == -90) and expand: | |
return np.transpose(im, axes=transpo)[:, ::-1] | |
elif (a in [180, -180]) and expand: | |
return im[::-1, ::-1] | |
elif not PIL_FOUND: | |
raise ValueError('Without "Pillow" installed, only angles 90, -90,' | |
'180 are supported, please install "Pillow" with' | |
"pip install pillow") | |
else: | |
return pil_rotater(im, a, resample=resample, expand=expand) | |
return clip.fl(fl, apply_to=["mask"]) | |
def resize(clip, newsize=None, height=None, width=None, apply_to_mask=True): | |
w, h = clip.size | |
if newsize is not None: | |
def trans_newsize(ns): | |
if isinstance(ns, (int, float)): | |
return [ns * w, ns * h] | |
else: | |
return ns | |
if hasattr(newsize, "__call__"): | |
newsize2 = lambda t: trans_newsize(newsize(t)) | |
if clip.ismask: | |
fun = lambda gf, t: (1.0 * resizer((255 * gf(t)).astype('uint8'), | |
newsize2(t)) / 255) | |
else: | |
fun = lambda gf, t: resizer(gf(t).astype('uint8'), | |
newsize2(t)) | |
return clip.fl(fun, keep_duration=True, | |
apply_to=(["mask"] if apply_to_mask else [])) | |
else: | |
newsize = trans_newsize(newsize) | |
elif height is not None: | |
if hasattr(height, "__call__"): | |
fun = lambda t: 1.0 * int(height(t)) / h | |
return clip.resize(clip, fun) | |
else: | |
newsize = [w * height / h, height] | |
elif width is not None: | |
if hasattr(width, "__call__"): | |
fun = lambda t: 1.0 * width(t) / w | |
return clip.resize(clip, fun) | |
newsize = [width, h * width / w] | |
# From here, the resizing is constant (not a function of time), size=newsize | |
if clip.ismask: | |
fl = lambda pic: 1.0 * resizer((255 * pic).astype('uint8'), newsize) / 255.0 | |
else: | |
fl = lambda pic: resizer(pic.astype('uint8'), newsize) | |
newclip = clip.fl_image(fl) | |
if apply_to_mask and clip.mask is not None: | |
newclip.mask = clip.resize(clip.mask, newsize, apply_to_mask=False) | |
return newclip |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment