Created
August 26, 2024 12:08
-
-
Save MattiooFR/a952d36aa92dd5e0fae8ee063fbb9a59 to your computer and use it in GitHub Desktop.
Recreating mouse movements and clicks from focusee json file
This file contains hidden or 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
import cv2 | |
import json | |
import numpy as np | |
import os | |
import argparse | |
from tqdm import tqdm | |
import time | |
def load_json(filename): | |
with open(filename, 'r') as f: | |
return json.load(f) | |
def load_cursor_images(cursor_folder, cursors_info): | |
cursor_images = {} | |
for cursor in cursors_info: | |
cursor_id = cursor['id'] | |
img_path = os.path.join(cursor_folder, f"{cursor_id}.png") | |
if os.path.exists(img_path): | |
img = cv2.imread(img_path, cv2.IMREAD_UNCHANGED) | |
if cursor_id == 'arrow': | |
img = cv2.resize(img, (int(img.shape[1]*0.7), int(img.shape[0]*0.7)), interpolation=cv2.INTER_AREA) | |
cursor_images[cursor_id] = (img, (int(cursor['hotSpot']['x']*0.7), int(cursor['hotSpot']['y']*0.7))) | |
elif cursor_id == 'iBeam': | |
img = cv2.resize(img, (int(img.shape[1]*0.25), int(img.shape[0]*0.25)), interpolation=cv2.INTER_AREA) | |
cursor_images[cursor_id] = (img, (int(cursor['hotSpot']['x']*0.25), int(cursor['hotSpot']['y']*0.25))) | |
else: | |
img = cv2.resize(img, (img.shape[1]*2, img.shape[0]*2), interpolation=cv2.INTER_CUBIC) | |
cursor_images[cursor_id] = (img, (cursor['hotSpot']['x']*2, cursor['hotSpot']['y']*2)) | |
return cursor_images | |
def draw_cursor(frame, cursor_img, hot_spot, x, y, scale=1.0): | |
h, w = cursor_img.shape[:2] | |
new_h, new_w = int(h * scale), int(w * scale) | |
resized_cursor = cv2.resize(cursor_img, (new_w, new_h), interpolation=cv2.INTER_AREA) | |
x = int(x - hot_spot[0] * scale) | |
y = int(y - hot_spot[1] * scale) | |
if x >= 0 and y >= 0 and x + new_w <= frame.shape[1] and y + new_h <= frame.shape[0]: | |
if resized_cursor.shape[2] == 4: # Image with alpha channel | |
alpha_s = resized_cursor[:, :, 3] / 255.0 | |
alpha_l = 1.0 - alpha_s | |
for c in range(0, 3): | |
frame[y:y+new_h, x:x+new_w, c] = (alpha_s * resized_cursor[:, :, c] + | |
alpha_l * frame[y:y+new_h, x:x+new_w, c]) | |
else: | |
frame[y:y+new_h, x:x+new_w] = resized_cursor | |
def ease_cubic(t): | |
return t * t * (3 - 2 * t) | |
def interpolate_position(pos1, pos2, t): | |
eased_t = ease_cubic(t) | |
return (pos1[0] + eased_t * (pos2[0] - pos1[0]), pos1[1] + eased_t * (pos2[1] - pos1[1])) | |
def process_video(video_path, mousemoves, mouseclicks, cursor_images, output_path, time_advance, process_start_time): | |
cap = cv2.VideoCapture(video_path) | |
fps = cap.get(cv2.CAP_PROP_FPS) | |
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) | |
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) | |
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) | |
original_width, original_height = 1728, 1117 | |
scale_x = width / original_width | |
scale_y = height / original_height | |
fourcc = cv2.VideoWriter_fourcc(*'avc1') | |
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) | |
out.set(cv2.VIDEOWRITER_PROP_QUALITY, 100) | |
# Pré-traitement des mouvements et clics de souris | |
mousemoves = [m for m in mousemoves if m['processTimeMs'] >= process_start_time] | |
mouseclicks = [c for c in mouseclicks if c['processTimeMs'] >= process_start_time] | |
move_index = 0 | |
click_index = 0 | |
current_cursor = 'arrow' | |
current_x, current_y = mousemoves[0]['x'] * scale_x, mousemoves[0]['y'] * scale_y | |
is_clicking = False | |
click_duration = 5 # Durée du clic en frames | |
click_frame_count = 0 | |
start_time = time.time() | |
with tqdm(total=total_frames, unit='frame') as pbar: | |
for frame_count in range(total_frames): | |
ret, frame = cap.read() | |
if not ret: | |
break | |
current_time = frame_count / fps * 1000 + process_start_time + time_advance | |
# Interpolation du mouvement | |
while move_index < len(mousemoves) - 1 and mousemoves[move_index + 1]['processTimeMs'] <= current_time: | |
move_index += 1 | |
if move_index < len(mousemoves) - 1: | |
next_move = mousemoves[move_index + 1] | |
t = (current_time - mousemoves[move_index]['processTimeMs']) / (next_move['processTimeMs'] - mousemoves[move_index]['processTimeMs']) | |
current_x, current_y = interpolate_position( | |
(mousemoves[move_index]['x'] * scale_x, mousemoves[move_index]['y'] * scale_y), | |
(next_move['x'] * scale_x, next_move['y'] * scale_y), | |
t | |
) | |
current_cursor = mousemoves[move_index]['cursorId'] | |
while click_index < len(mouseclicks) and mouseclicks[click_index]['processTimeMs'] <= current_time: | |
is_clicking = True | |
click_frame_count = 0 | |
click_index += 1 | |
if current_cursor in cursor_images: | |
cursor_img, hot_spot = cursor_images[current_cursor] | |
if is_clicking and click_frame_count < click_duration: | |
draw_cursor(frame, cursor_img, hot_spot, current_x, current_y, scale=0.8) | |
click_frame_count += 1 | |
if click_frame_count >= click_duration: | |
is_clicking = False | |
else: | |
draw_cursor(frame, cursor_img, hot_spot, current_x, current_y) | |
out.write(frame) | |
pbar.update(1) | |
elapsed_time = time.time() - start_time | |
frames_remaining = total_frames - frame_count - 1 | |
if frame_count > 0: | |
time_per_frame = elapsed_time / (frame_count + 1) | |
estimated_time_remaining = frames_remaining * time_per_frame | |
pbar.set_postfix({ | |
'Elapsed': f'{elapsed_time:.2f}s', | |
'Remaining': f'{estimated_time_remaining:.2f}s' | |
}) | |
cap.release() | |
out.release() | |
cv2.destroyAllWindows() | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser(description="Process video with mouse movements and clicks") | |
parser.add_argument("--time-advance", type=int, default=0, help="Time in milliseconds to advance all events") | |
args = parser.parse_args() | |
mousemoves = load_json('mousemoves.json') | |
mouseclicks = load_json('mouseclicks.json') | |
cursors_info = load_json('cursors.json') | |
metadata = load_json('metadata.json') | |
cursor_images = load_cursor_images('cursors', cursors_info) | |
process_start_time = metadata['sessions'][0]['processTimeStartMs'] | |
process_video('screen_record.mp4', mousemoves, mouseclicks, cursor_images, 'output_video_with_cursor.mp4', args.time_advance, process_start_time) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment