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('',self.clickPos) self.root.bind('', 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('', 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()