Skip to content

Instantly share code, notes, and snippets.

@gurland
Created December 16, 2019 07:14
Show Gist options
  • Save gurland/af2f21e11c0935445bc662b7db3c50c4 to your computer and use it in GitHub Desktop.
Save gurland/af2f21e11c0935445bc662b7db3c50c4 to your computer and use it in GitHub Desktop.
3 Face Hologram video generator
python -m venv venv
call venv/Scripts/activate.bat
pip install -r requirements.txt
pip install pyinstaller
pyinstaller --workpath temp --onefile main.py
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()
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))
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