1212from pynput .keyboard import Key , Listener
1313from dataclasses import dataclass
1414from funscript_editor .data .funscript import Funscript
15- from funscript_editor .data .filevideostream import FileVideoStream
1615from funscript_editor .algorithms .videotracker import StaticVideoTracker
1716from PyQt5 import QtGui , QtCore , QtWidgets
1817from matplotlib .figure import Figure
19- from funscript_editor .definitions import VIDEO_SCALING_CONFIG_FILE
2018from funscript_editor .utils .config import HYPERPARAMETER , SETTINGS
2119from datetime import datetime
22- from funscript_editor .data .equirectangularvideostream import EquirectangularVideoStream
20+ from funscript_editor .data .ffmpegstream import FFmpegStream , FFmpegStreamParameter , VideoInfo
2321
2422import funscript_editor .algorithms .signalprocessing as sp
2523import numpy as np
@@ -41,15 +39,10 @@ class FunscriptGeneratorParameter:
4139 shift_top_points :int = int (HYPERPARAMETER ['shift_top_points' ])
4240 top_points_offset :float = float (HYPERPARAMETER ['top_points_offset' ])
4341 bottom_points_offset :float = float (HYPERPARAMETER ['bottom_points_offset' ])
44- use_equirectangular :bool = SETTINGS ['use_equirectangular' ]
45- equirectangular_scaling :float = max ((0.2 , float (SETTINGS ['equirectangular_scaling' ])))
4642 zoom_factor :float = max ((1.0 , float (SETTINGS ['zoom_factor' ])))
47- scaling_method :str = SETTINGS ['scaling_method' ]
4843 top_threshold :float = float (HYPERPARAMETER ['top_threshold' ])
4944 bottom_threshold :float = float (HYPERPARAMETER ['bottom_threshold' ])
50- equirectangular_height :int = 720
51- equirectangular_width :int = 1240
52- equirectangular_fov :int = 100
45+ preview_scaling :float = 1.0
5346
5447
5548class FunscriptGenerator (QtCore .QThread ):
@@ -71,11 +64,6 @@ def __init__(self,
7164 # XXX destroyWindow(...) sems not to delete the trackbar. Workaround: we give the window each time a unique name
7265 self .window_name = "Funscript Generator ({})" .format (datetime .now ().strftime ("%H:%M:%S" ))
7366
74- # scale config for the video
75- with open (VIDEO_SCALING_CONFIG_FILE , 'r' ) as config_file :
76- self .scale = json .load (config_file )
77- self .scale = {int (k ) : float (v ) for k ,v in self .scale .items ()}
78-
7967 self .keypress_queue = Queue (maxsize = 32 )
8068 self .stopped = False
8169 self .perspective_params = {}
@@ -98,37 +86,8 @@ def __init__(self,
9886 #: processing event with current processed frame number
9987 processStatus = QtCore .pyqtSignal (int )
10088
101- __logger = logging .getLogger (__name__ )
102-
103- def get_scaling (self , frame_width : int , frame_height : int ) -> float :
104- """ Get the scaling parameter for current video
105-
106- Args:
107- frame_width (int): frame width of current video
108- frame_height (int): frame height of current video
89+ logger = logging .getLogger (__name__ )
10990
110- Returns:
111- float: scaling parameter from scaling config
112- """
113- if self .params .scaling_method == 'auto' :
114- scale = []
115- try :
116- for monitor in get_monitors ():
117- scale .append ( min ((monitor .width / (frame_width * 1.1 ), monitor .height / (frame_height * 1.1 ) )) )
118- except : pass
119-
120- if len (scale ) == 0 :
121- self .__logger .error ("Monitor info not found, please switch to scaling_method: 'config'" )
122- scale = 1.0
123- else :
124- # asume we use the largest monitor for scipting
125- scale = max (scale )
126- else :
127- # assume scaling_method is 'config' (fallback)
128- scale = max ([0.05 , min ([8.0 ]+ [self .scale [k ] for k in self .scale .keys () if k < frame_width ])])
129-
130- self .__logger .info ("Use scaling of %f for preview window and tracking" , scale )
131- return scale
13291
13392 def drawBox (self , img : np .ndarray , bbox : tuple ) -> np .ndarray :
13493 """ Draw an tracking box on the image/frame
@@ -256,7 +215,7 @@ def min_max_selector(self,
256215 (self .x_text_start , 50 ), cv2 .FONT_HERSHEY_SIMPLEX , 0.7 , (0 ,0 ,255 ), 2 )
257216 cv2 .putText (preview , "Set {} to {}" .format ('Max' , trackbarValueMax ),
258217 (image_min .shape [1 ] + self .x_text_start , 50 ), cv2 .FONT_HERSHEY_SIMPLEX , 0.7 , (0 ,0 ,255 ), 2 )
259- cv2 .imshow (self .window_name , preview )
218+ cv2 .imshow (self .window_name , self . preview_scaling ( preview ) )
260219 if self .was_space_pressed () or cv2 .waitKey (25 ) == ord (' ' ): break
261220 trackbarValueMin = cv2 .getTrackbarPos ("Min" , self .window_name )
262221 trackbarValueMax = cv2 .getTrackbarPos ("Max" , self .window_name )
@@ -332,17 +291,7 @@ def scale_score(self, status: str, direction : str = 'y') -> None:
332291 cap .release ()
333292
334293 if successMin and successMax :
335- if self .params .use_equirectangular :
336- dim = (int (self .params .equirectangular_width * self .params .equirectangular_scaling ), \
337- int (self .params .equirectangular_height * self .params .equirectangular_scaling ))
338- imgMin = cv2 .resize (imgMin , dim )
339- imgMax = cv2 .resize (imgMax , dim )
340- else :
341- scale = self .get_scaling (width , height )
342- imgMin = cv2 .resize (imgMin , None , fx = scale , fy = scale )
343- imgMax = cv2 .resize (imgMax , None , fx = scale , fy = scale )
344-
345- # Assume we have VR 3D Side by Side
294+ # TODO: Assume we have VR 3D Side by Side
346295 imgMin = imgMin [:, :int (imgMin .shape [1 ]/ 2 )]
347296 imgMax = imgMax [:, :int (imgMax .shape [1 ]/ 2 )]
348297
@@ -354,7 +303,7 @@ def scale_score(self, status: str, direction : str = 'y') -> None:
354303 title_max = ("Top" if direction != "x" else "Right" )
355304 )
356305 else :
357- self .__logger .warning ("Determine min and max failed" )
306+ self .logger .warning ("Determine min and max failed" )
358307 desired_min = 0
359308 desired_max = 99
360309
@@ -423,19 +372,40 @@ def delete_last_tracking_predictions(self, num :int) -> None:
423372 if self .params .track_men : del self .bboxes ['Men' ][i ]
424373
425374
426- def get_perspective_roi (self , image :np .ndarray ) -> None :
427- """ Get the perspective ROI parameters form user input
375+ def preview_scaling (self , preview_image :np .ndarray ) -> np .ndarray :
376+ """ Scale image for preview
377+
378+ Args:
379+ preview_image (np.ndarray): opencv image
380+
381+ Returns:
382+ np.ndarray: scaled opencv image
383+ """
384+ """
385+ frame_height, frame_width = preview_image.shape[:2]
386+ scale = []
387+ try:
388+ for monitor in get_monitors():
389+ scale.append( min((monitor.width / (frame_width*1.1), monitor.height / (frame_height*1.1) )) )
390+ except: pass
391+
392+ if len(scale) == 0:
393+ self.logger.error("Monitor resolution info not found")
394+ scale = 1.0
395+ else:
396+ # asume we use the largest monitor for scipting
397+ scale = max(scale)
398+ """
399+ return cv2 .resize (preview_image , None , fx = self .params .preview_scaling , fy = self .params .preview_scaling )
400+
401+
402+ def get_projection_parameter (self , image :np .ndarray ) -> None :
403+ """ Get the projection ROI parameters form user input
428404
429405 Args:
430406 image (np.ndarray): opencv vr 180 or 360 image
431407 """
432- perspective = {
433- 'FOV' : self .params .equirectangular_fov ,
434- 'THETA' : - 90 ,
435- 'PHI' : - 45 ,
436- 'height' : int (self .params .equirectangular_height * self .params .equirectangular_scaling ),
437- 'width' : int (self .params .equirectangular_width * self .params .equirectangular_scaling )
438- }
408+ parameter = FFmpegStreamParameter ()
439409
440410 # NOTE: improve processing speed to make this menu more responsive
441411 if image .shape [0 ] > 6000 or image .shape [1 ] > 6000 :
@@ -448,42 +418,37 @@ def get_perspective_roi(self, image :np.ndarray) -> None:
448418 while not selected :
449419 if parameter_changed :
450420 parameter_changed = False
451- preview = EquirectangularVideoStream .get_perspective (
452- image ,
453- perspective ['FOV' ],
454- perspective ['THETA' ],
455- perspective ['PHI' ],
456- perspective ['height' ],
457- perspective ['width' ]
458- )
421+ preview = FFmpegStream .get_projection (image , parameter )
459422
460423 preview = self .drawText (preview , "Press 'q' to use current selected region of interest)" ,
461424 y = 50 , color = (255 , 0 , 0 ))
462425 preview = self .drawText (preview , "Use 'w', 's' to move up/down to the region of interest" ,
463426 y = 75 , color = (0 , 255 , 0 ))
464427
465- cv2 .imshow (self .window_name , preview )
428+ cv2 .imshow (self .window_name , self .preview_scaling (preview ))
429+
466430 while self .keypress_queue .qsize () > 0 :
467431 pressed_key = '{0}' .format (self .keypress_queue .get ())
468432 if pressed_key == "'q'" :
469433 selected = True
470434 break
471435 elif pressed_key == "'w'" :
472- perspective [ 'PHI' ] = min ((80 , perspective [ 'PHI' ] + 5 ))
436+ parameter . phi = min ((80 , parameter . phi + 5 ))
473437 parameter_changed = True
474438 elif pressed_key == "'s'" :
475- perspective [ 'PHI' ] = max ((- 80 , perspective [ 'PHI' ] - 5 ))
439+ parameter . phi = max ((- 80 , parameter . phi - 5 ))
476440 parameter_changed = True
477441
478442 if cv2 .waitKey (1 ) in [ord ('q' )]: break
479443
480444 try :
481445 background = np .full (preview .shape , 0 , dtype = np .uint8 )
482446 loading_screen = self .drawText (background , "Please wait ..." )
483- cv2 .imshow (self .window_name , loading_screen )
447+ cv2 .imshow (self .window_name , self .preview_scaling (loading_screen ))
448+ cv2 .waitKey (1 )
484449 except : pass
485450
486- self . perspective_params = perspective
451+ return parameter
487452
488453
489454 def get_bbox (self , image : np .ndarray , txt : str ) -> tuple :
@@ -498,27 +463,34 @@ def get_bbox(self, image: np.ndarray, txt: str) -> tuple:
498463 """
499464 image = self .drawText (image , "Press 'space' or 'enter' to continue (sometimes not very responsive)" ,
500465 y = 75 , color = (255 , 0 , 0 ))
501- # cv2.namedWindow(self.window_name)
502- # cv2.createTrackbar("Scale", self.window_name, 1, 10, lambda x: None)
503466
504467 if self .params .use_zoom :
505468 while True :
506469 zoom_bbox = cv2 .selectROI (self .window_name , self .drawText (image , "Zoom selected area" ), False )
507470 if zoom_bbox is None or len (zoom_bbox ) == 0 : continue
508471 if zoom_bbox [2 ] < 75 or zoom_bbox [3 ] < 75 :
509- self .__logger .error ("The selected zoom area is to small" )
472+ self .logger .error ("The selected zoom area is to small" )
510473 continue
511474 break
512475
513476 image = image [zoom_bbox [1 ]:zoom_bbox [1 ]+ zoom_bbox [3 ], zoom_bbox [0 ]:zoom_bbox [0 ]+ zoom_bbox [2 ]]
514477 image = cv2 .resize (image , None , fx = self .params .zoom_factor , fy = self .params .zoom_factor )
515478
479+ image = self .drawText (image , txt )
480+ image = self .preview_scaling (image )
516481 while True :
517- bbox = cv2 .selectROI (self .window_name , self . drawText ( image , txt ) , False )
482+ bbox = cv2 .selectROI (self .window_name , image , False )
518483 if bbox is None or len (bbox ) == 0 : continue
519484 if bbox [0 ] == 0 or bbox [1 ] == 0 or bbox [2 ] < 9 or bbox [3 ] < 9 : continue
520485 break
521486
487+ # revert the preview scaling
488+ bbox = (round (bbox [0 ]/ self .params .preview_scaling ),
489+ round (bbox [1 ]/ self .params .preview_scaling ),
490+ round (bbox [2 ]/ self .params .preview_scaling ),
491+ round (bbox [3 ]/ self .params .preview_scaling )
492+ )
493+
522494 if self .params .use_zoom :
523495 bbox = (round (bbox [0 ]/ self .params .zoom_factor )+ zoom_bbox [0 ],
524496 round (bbox [1 ]/ self .params .zoom_factor )+ zoom_bbox [1 ],
@@ -548,33 +520,23 @@ def tracking(self) -> str:
548520 Returns:
549521 str: a process status message e.g. 'end of video reached'
550522 """
551- if self .params .use_equirectangular :
552- first_frame = self .get_first_frame ()
553- self .get_perspective_roi (first_frame )
554-
555- video = EquirectangularVideoStream (
556- self .params .video_path ,
557- self .perspective_params ['FOV' ],
558- self .perspective_params ['THETA' ],
559- self .perspective_params ['PHI' ],
560- self .perspective_params ['height' ],
561- self .perspective_params ['width' ],
562- self .params .start_frame
563- )
564- else :
565- video = FileVideoStream (
566- video_path = self .params .video_path ,
567- scale_determiner = self .get_scaling ,
568- start_frame = self .params .start_frame )
523+ first_frame = FFmpegStream .get_frame (self .params .video_path , self .params .start_frame )
524+ projection_parameter = self .get_projection_parameter (first_frame )
525+
526+ video = FFmpegStream (
527+ video_path = self .params .video_path ,
528+ parameter = projection_parameter ,
529+ start_frame = self .params .start_frame
530+ )
569531
570532 first_frame = video .read ()
571533 bboxWoman = self .get_bbox (first_frame , "Select Woman Feature" )
572- trackerWoman = StaticVideoTracker (first_frame , bboxWoman , limit_searchspace = not self . params . use_equirectangular )
534+ trackerWoman = StaticVideoTracker (first_frame , bboxWoman , limit_searchspace = False ) # TODO limit_searchspace for flat videos
573535 self .bboxes ['Woman' ].append (bboxWoman )
574536
575537 if self .params .track_men :
576538 bboxMen = self .get_bbox (self .drawBox (first_frame , bboxWoman ), "Select Men Feature" )
577- trackerMen = StaticVideoTracker (first_frame , bboxMen , limit_searchspace = not self . params . use_equirectangular )
539+ trackerMen = StaticVideoTracker (first_frame , bboxMen , limit_searchspace = False ) # TODO limit_searchspace for flat videos
578540 self .bboxes ['Men' ].append (bboxMen )
579541
580542 if self .params .max_playback_fps > (self .params .skip_frames + 1 ):
@@ -619,7 +581,7 @@ def tracking(self) -> str:
619581 last_frame = self .drawFPS (last_frame )
620582 cv2 .putText (last_frame , "Press 'q' if the tracking point shifts or a video cut occured" ,
621583 (self .x_text_start , 75 ), cv2 .FONT_HERSHEY_SIMPLEX , 0.7 , (255 ,0 ,0 ), 2 )
622- cv2 .imshow (self .window_name , last_frame )
584+ cv2 .imshow (self .window_name , self . preview_scaling ( last_frame ) )
623585
624586 if self .was_key_pressed ('q' ) or cv2 .waitKey (1 ) == ord ('q' ):
625587 status = 'Tracking stopped by user'
@@ -647,7 +609,7 @@ def tracking(self) -> str:
647609
648610
649611 video .stop ()
650- self .__logger .info (status )
612+ self .logger .info (status )
651613 self .calculate_score ()
652614 return status
653615
0 commit comments