309 lines
8.8 KiB
Python
309 lines
8.8 KiB
Python
import cv2
|
|
import random
|
|
import time
|
|
from decimal import Decimal
|
|
from tkinter import *
|
|
from ffpyplayer.player import MediaPlayer
|
|
from PIL import Image, ImageTk
|
|
import tempfile
|
|
import threading
|
|
import queue
|
|
import os
|
|
import sys
|
|
import contextlib
|
|
|
|
class VideoPlayer:
|
|
def __init__(self, root, file, **kwargs):
|
|
|
|
self.dest = file
|
|
self.cap = cv2.VideoCapture(self.dest)
|
|
|
|
self.frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
self.fps = self.cap.get(cv2.CAP_PROP_FPS)
|
|
self.newfps = self.fps
|
|
self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
|
self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
|
|
|
self.root = root
|
|
self.root.overrideredirect(1)
|
|
self.root.geometry('+0+0')
|
|
self.resize = False
|
|
self.root.withdraw()
|
|
|
|
for k, val in kwargs.items():
|
|
if k == "fps":
|
|
if val > self.fps:
|
|
print("Error: requested FPS is higher than file FPS")
|
|
self.root.destroy()
|
|
return
|
|
self.newfps = val
|
|
elif k == "pos":
|
|
self.root.geometry("+%d+%d" %val)
|
|
elif k == "draggable" and val == True:
|
|
self.root.bind('<Button-1>',self.clickPos)
|
|
self.root.bind('<B1-Motion>', self.dragWin)
|
|
self.clickx = None
|
|
self.clicky = None
|
|
elif k == "dim":
|
|
if (val[0] == self.width and val[1] == self.height):
|
|
continue
|
|
self.width = val[0]
|
|
self.height = val[1]
|
|
self.resize = True
|
|
elif k == "videoOptions" and val == True:
|
|
self.root.bind('<Button-3>', self.options)
|
|
|
|
def play(self):
|
|
self.root.deiconify()
|
|
|
|
self.canvas = Canvas(self.root, width = self.width, height = self.height)
|
|
self.canvas.pack()
|
|
|
|
self.fr_lock = threading.Lock()
|
|
self.frames_read = queue.Queue()
|
|
self.frame_files = queue.Queue() # list of temp files
|
|
self.frame_times = []
|
|
self.kill_threads = False
|
|
|
|
self.player = MediaPlayer(self.dest)
|
|
self.player.set_pause(True)
|
|
|
|
time.sleep(1)
|
|
|
|
self.t1 = threading.Thread(target=self.readFrames)
|
|
self.t2 = threading.Thread(target=self.writeFrames)
|
|
self.t3 = threading.Thread(target=self.writeFrames)
|
|
|
|
self.t1.start()
|
|
time.sleep(2)
|
|
self.t2.start()
|
|
self.t3.start()
|
|
time.sleep(1)
|
|
|
|
self.playVideo()
|
|
|
|
self.t1.join()
|
|
self.t2.join()
|
|
self.t3.join()
|
|
|
|
def clickPos(self, event):
|
|
time.sleep(.2)
|
|
self.clickx = event.x
|
|
self.clicky = event.y
|
|
|
|
def dragWin(self,event):
|
|
winx = self.root.winfo_x()
|
|
winy = self.root.winfo_y()
|
|
|
|
x = event.x - self.clickx + winx
|
|
y = event.y - self.clicky + winy
|
|
self.root.geometry("+%d+%d" %(x,y))
|
|
|
|
def kill(self):
|
|
self.kill_threads = True
|
|
time.sleep(1)
|
|
self.player.set_pause(True)
|
|
self.root.destroy()
|
|
os._exit(1)
|
|
|
|
def options(self,event):
|
|
|
|
def restart():
|
|
os.execl(sys.executable, sys.executable, *sys.argv)
|
|
|
|
m = Menu(self.root, tearoff = 0)
|
|
m.add_command(label = "restart", command = restart)
|
|
m.add_command(label = "quit", command=self.kill)
|
|
m.tk_popup(event.x_root, event.y_root)
|
|
|
|
def randSelect(self):
|
|
frameList = []
|
|
accuracy = 2
|
|
|
|
ratio = round(self.newfps/self.fps,accuracy)
|
|
dec_ratio = Decimal(str(ratio))
|
|
self.newfps = ratio*self.fps
|
|
b = (dec_ratio).as_integer_ratio()
|
|
|
|
frame_guarantee = b[0]
|
|
frame_chunk = b[1]
|
|
|
|
for i in range(1, self.frames, frame_chunk):
|
|
select = range(i,i+frame_chunk-1)
|
|
samp = random.sample(select,frame_guarantee)
|
|
samp.sort()
|
|
frameList.extend(samp)
|
|
|
|
return frameList
|
|
|
|
def generateFrameTimes(self):
|
|
newFrames = int(self.frames * self.newfps/self.fps)
|
|
|
|
targetTime = 1/self.newfps
|
|
times = 0
|
|
|
|
for i in range(newFrames):
|
|
self.frame_times.append(times)
|
|
times += targetTime
|
|
|
|
def readFrames(self):
|
|
if(self.fps == self.newfps):
|
|
self.generateFrameTimes()
|
|
while(self.cap.isOpened()):
|
|
if(self.kill_threads == True):
|
|
return
|
|
|
|
if(self.frames_read.qsize() > 10):
|
|
time.sleep(.01)
|
|
continue
|
|
|
|
ret, frame = self.cap.read()
|
|
if ret == True:
|
|
self.frames_read.put(frame)
|
|
else:
|
|
break
|
|
else:
|
|
|
|
select_list = self.randSelect()
|
|
self.generateFrameTimes()
|
|
|
|
counter = 0
|
|
walker = 0
|
|
|
|
while(self.cap.isOpened()):
|
|
if(self.kill_threads == True):
|
|
return
|
|
|
|
if(self.frames_read.qsize() > 10):
|
|
time.sleep(.01)
|
|
continue
|
|
|
|
counter += 1
|
|
ret, frame = self.cap.read()
|
|
if ret == False:
|
|
break
|
|
|
|
if(select_list[walker] == counter):
|
|
self.frames_read.put(frame)
|
|
|
|
walker += 1
|
|
|
|
# https://stackoverflow.com/questions/13379742/
|
|
# right-way-to-clean-up-a-temporary-folder-in-python-class
|
|
@contextlib.contextmanager
|
|
def make_temp_directory(self):
|
|
temp_dir = tempfile.TemporaryDirectory()
|
|
try:
|
|
yield temp_dir
|
|
finally:
|
|
temp_dir.cleanup()
|
|
|
|
def writeFrames(self):
|
|
|
|
with self.make_temp_directory() as temp_dir:
|
|
while True:
|
|
|
|
if(self.kill_threads == True):
|
|
temp_dir.cleanup()
|
|
return
|
|
|
|
if self.frame_files.qsize() > 20:
|
|
time.sleep(.1)
|
|
continue
|
|
|
|
p = tempfile.NamedTemporaryFile('wb', suffix = '.jpg',
|
|
dir = temp_dir.name,
|
|
delete = False)
|
|
|
|
with self.fr_lock:
|
|
if self.frames_read.empty():
|
|
break
|
|
|
|
frame = self.frames_read.get()
|
|
self.frame_files.put(p)
|
|
|
|
if self.resize == True:
|
|
frame = cv2.resize(frame, (self.width,self.height),
|
|
interpolation = cv2.INTER_AREA)
|
|
|
|
cv2.imwrite(p.name,frame)
|
|
p.close()
|
|
|
|
def playVideo(self):
|
|
counter = 0
|
|
|
|
fps = self.newfps # for testing max frame rate
|
|
print(fps)
|
|
targetTime = 1/fps
|
|
|
|
img = None
|
|
pop = None
|
|
self.player.set_pause(False)
|
|
|
|
# load up the audio
|
|
audio_frame, val = self.player.get_frame()
|
|
while audio_frame == None:
|
|
audio_frame, val = self.player.get_frame()
|
|
|
|
running_time = time.time()
|
|
while(not self.frame_files.empty()):
|
|
|
|
if(self.kill_threads == True):
|
|
return
|
|
|
|
audio_frame, val = self.player.get_frame()
|
|
|
|
if(val == 'eof' or len(self.frame_times) == 0):
|
|
break
|
|
|
|
if(audio_frame == None):
|
|
continue
|
|
|
|
# for any lag due to cpu, especially for dragging
|
|
if(self.frame_files.qsize() < 5):
|
|
time.sleep(.08)
|
|
|
|
t = self.frame_times.pop(0)
|
|
pop = self.frame_files.get()
|
|
|
|
cur_time = time.time() - running_time
|
|
delay = t - cur_time
|
|
|
|
# frame skipping
|
|
if (delay < -targetTime):
|
|
os.remove(pop.name)
|
|
continue
|
|
|
|
prevIm = img
|
|
|
|
# diplay image
|
|
self.canvas.delete("all")
|
|
|
|
try:
|
|
load = Image.open(pop.name)
|
|
except:
|
|
os.remove(pop.name)
|
|
continue
|
|
|
|
#load.draft("RGB",(2560,1080)) # doesn't do anything?
|
|
render = ImageTk.PhotoImage(load)
|
|
img = Label(image=render)
|
|
img.image = render
|
|
img.place(x=0, y=0)
|
|
load.close()
|
|
pop.close()
|
|
|
|
os.remove(pop.name)
|
|
|
|
self.root.update()
|
|
|
|
if prevIm != None:
|
|
prevIm.destroy()
|
|
|
|
cur_time = time.time() - running_time
|
|
delay = t - cur_time
|
|
|
|
if (delay > targetTime):
|
|
time.sleep(targetTime)
|
|
|
|
self.kill() |