Skip to content

Instantly share code, notes, and snippets.

@MattiooFR
Created August 26, 2024 12:08
Show Gist options
  • Save MattiooFR/a952d36aa92dd5e0fae8ee063fbb9a59 to your computer and use it in GitHub Desktop.
Save MattiooFR/a952d36aa92dd5e0fae8ee063fbb9a59 to your computer and use it in GitHub Desktop.
Recreating mouse movements and clicks from focusee json file
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