Intro
I have created a python script, that can script cock hero videos, if they have a nice visual beat meter. I have used the script to create
https://discuss.eroscripts.com/t/cock-hero-seductive-reasoning/34245
https://discuss.eroscripts.com/t/cock-hero-freedom/34246
It takes about 5 minutes of actual work, to script a video.
Code
# Setup
import cv2
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from sklearn import svm
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from tqdm import tqdm
import os
%matplotlib inline
#Version 1.1
class BeatScripter:
def __init__(self, filename):
self.filename = filename
self.file_path = f'D:\\ax156\Launch\Scripts\CH todo\{self.filename}.mp4'
self.file_path_out = f'D:\\ax156\Launch\Scripts\CH todo\{self.filename}.funscript'
self.box_top_corner = (0,0)
self.box_size = (50,50)
self.frames_list = []
self.beats_trained_list = []
self.model = KNeighborsClassifier(n_neighbors=5)
self.predicted_beats = []
def load_frames_list(self, start_times, number_of_frames = 100):
for start_time in start_times:
self.frames_list.append(self.load_frames(start_time * 1000, number_of_frames))
print(f"{number_of_frames} frames loaded from {start_time} seconds" )
print(f"dimension (height, length, colors) {self.frames_list[0][0][1].shape}")
def load_frames(self, start_time_in_msec, number_of_frames):
if not os.path.isfile(self.file_path):
print(f'Video file not found! {self.file_path}')
print(f'make sure self.file_path points at the correct folder')
print(f'make sure filename is the correct filename (without ".mp4")')
raise Exception(f'Video file not found! {self.file_path}')
cap = cv2.VideoCapture(self.file_path, cv2.CAP_PROP_POS_MSEC)
frames = []
i = 0
while True:
read, frame = cap.read()
if not read:
break
time = cap.get(cv2.CAP_PROP_POS_MSEC)
if time >= start_time_in_msec:
frames.append((i, frame, time))
if len(frames) >= number_of_frames:
break
i += 1
cap.release()
return frames
def show_frame(self, frame_set, frame_number, y_sub=None):
if y_sub is None:
self.do_show_frame(self.frames_list[frame_set][frame_number][1], x = None, y = None)
else:
self.do_show_frame(self.frames_list[frame_set][frame_number][1][y_sub:, :, :], x = None, y = None)
def show_frame_with_box(self, frame_set, frame_number, y_sub=None):
if y_sub is None:
self.do_show_frame(self.frames_list[frame_set][frame_number][1],
x = (self.box_top_corner[0], self.box_top_corner[0] + self.box_size[0]),
y = (self.box_top_corner[1], self.box_top_corner[1] + self.box_size[1]))
else:
self.do_show_frame(self.frames_list[frame_set][frame_number][1][y_sub:, :, :],
x = (self.box_top_corner[0], self.box_top_corner[0] + self.box_size[0]),
y = (self.box_top_corner[1]-y_sub, self.box_top_corner[1] + self.box_size[1] - y_sub))
def anotate(self, num_frames = 100, y_sub=None):
print("Space: not beat")
print("b: beat")
for frames in self.frames_list:
beats_trained = self.beat_marker(frames[0:num_frames], y_sub=y_sub)
self.beats_trained_list.append(beats_trained)
def get_sub_frame(self, frame):
return frame[self.box_top_corner[1]:self.box_top_corner[1] + self.box_size[1],
self.box_top_corner[0]:self.box_top_corner[0] + self.box_size[0], :]
def train(self):
beats_train = []
frames_train = []
for b in self.beats_trained_list:
beats_train = beats_train + b
for f in self.frames_list:
frames_train = frames_train + f
X = []
y = []
for i, beat in beats_train:
for j, frame, time in frames_train:
if i == j:
X.append(self.get_sub_frame(frame).flatten())
y.append(int(beat))
self.model.fit(X, y)
def do_show_frame(self, frame, x = None, y = None):
red = (0, 0, 255)
frame_cp = frame.copy()
if x is not None:
cv2.rectangle(frame_cp, (x[0], y[0]), (x[1], y[1]), red, 2)
cv2.imshow("frame", frame_cp)
cv2.waitKey(0)
cv2.destroyAllWindows()
def beat_marker(self, frames, y_sub):
red = (0, 0, 255)
beats = []
for i, frame, time in frames:
frame_cp = frame.copy()
if y_sub is None:
x = (self.box_top_corner[0], self.box_top_corner[0] + self.box_size[0])
y = (self.box_top_corner[1], self.box_top_corner[1] + self.box_size[1])
cv2.rectangle(frame_cp, (x[0], y[0]), (x[1], y[1]), red, 2)
cv2.imshow("frame", frame_cp)
else:
x = (self.box_top_corner[0], self.box_top_corner[0] + self.box_size[0])
y = (self.box_top_corner[1]-y_sub, self.box_top_corner[1] + self.box_size[1] - y_sub)
cv2.rectangle(frame_cp, (x[0], y[0]), (x[1], y[1]), red, 2)
cv2.imshow("frame", frame_cp[y_sub:, :, :])
while True:
k = cv2.waitKey(0)
if k == 32: # wait for ESC key to exit
beats.append((i, False))
cv2.destroyAllWindows()
break
elif k == ord('b'): # wait for 's' key to save and exit
beats.append((i, True))
cv2.destroyAllWindows()
break
return beats
def predict_beats(self, number=None):
cap = cv2.VideoCapture(self.file_path, cv2.CAP_PROP_POS_MSEC)
if number is None:
number = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
beats = []
for i in tqdm(range(number)):
_, frame = cap.read()
try:
X_pred = [self.get_sub_frame(frame).flatten()]
beats.append((self.model.predict(X_pred)[0] == 1,
i,
cap.get(cv2.CAP_PROP_POS_MSEC)))
except:
break
cap.release()
self.predicted_beats = beats
def export_funscript(self):
initial = '{"version": "1.0","inverted": false,"range": 90,"actions": ['
end = ']}'
middle = ''
pos = 0
last_i = 0
for beat, i, time in self.predicted_beats:
if beat and (i-last_i) > 4:
next_step = '{"pos": ' + str(pos) + ', "at": ' + str(round(time)) + '}'
middle = ','.join([middle, next_step])
pos = 100 - pos
last_i = i
middle = middle[1:]
text_file = open(self.file_path_out, "w")
text_file.write(initial + middle + end)
text_file.close()
#Script
beat_scripter = BeatScripter("chhappyface")
beat_scripter.load_frames_list([0, 1.5*60, 5*60, 9*60])
# beat_scripter.show_frame(1, 0, y_sub=500)
beat_scripter.show_frame(1, 0)
beat_scripter.box_top_corner = (625, 669)
beat_scripter.box_size=(32, 50)
# beat_scripter.show_frame_with_box(1 ,0, y_sub=500)
beat_scripter.show_frame_with_box(1 ,0)
# beat_scripter.anotate(y_sub=500)
beat_scripter.anotate()
beat_scripter.train()
beat_scripter.predict_beats(15000)
# beat_scripter.predict_beats()
beat_scripter.export_funscript()
Tutorial
- Install and open jupyter https://jupyter.org/
Maybe you need to install the following libraries
scikit-learn Installing scikit-learn — scikit-learn 1.1.2 documentation
cv2 opencv-python · PyPI
matplotlib matplotlib.org/stable/users/installing.html
pandas Installation — pandas 1.4.3 documentation
tqdm tqdm · PyPI - Create a new notebook and insert the code. The setup shold be in its own cell. And when you come to
#Script
an empty line, means the code should be divided in cells.
- To enter a cell: Enter
- To exit a cell: Escape
- To create a new cell below, when outside: b
- To create a new cell above, when outside: a
- To run a cell: ctrl + Enter
- Change the paths
self.file_path
andself.file_path_out
to the directory of the video. (keep\{self.filename}.mp4
) - Run the setup cell
-
beat_scripter = BeatScripter("CH Freedom")
Write the name of the file insted of “CH Freedom” -
beat_scripter.load_frames_list([0, 1.5*60, 5*60, 9*60])
loads 100 frames from the video at the beginning, and at the 1.5, 5 and 9 minute marks -
beat_scripter.show_frame(1, 0)
Opens a new window showing the first frame at the 1.5 minute mark. You can close the window by hitting enter. This frame should show the beat meter. If it does not, change the numbers. The first number corresponds to the above timestamps (starting at 0) 0 is 0 minutes, 1 is 1.5 minutes, 2 is 5 minutes and 3 is 9 minutes. The second number is the number of the frame in the series, also staring at 0. - If the the resolution is so high, that you have problems having it on your screen, you can add for example “, y_sub=500” to your calls
beat_scripter.box_top_corner = (625, 669)
beat_scripter.box_size=(32, 50)
# beat_scripter.show_frame_with_box(1 ,0, y_sub=500)
beat_scripter.show_frame_with_box(1 ,0)
Now we need to tell the script where the beat meter is located. This code shows a frame with a red box. The box needs to cover the small area, where the beats “activate”. This is where the script will look for beats.
To move the box change the values in beat_scripter.box_top_corner = (625, 669)
(x pixels from the top left corner, y pixels from the top left corner)
To change the size of the box change the numbers in beat_scripter.box_size=(32, 50)
Change the numbers and run the cell, until you are satisfied.
10. beat_scripter.anotate()
Now you need to tell the script what a beat and what a not-beat looks like.
The code will open a new window showing the first frame of the video. If the frame is a beat press “b” if not press “space”. Then it will show the next frame, until alle the frames we loaded in step 6 have been shown. It is okay to press b two-three times in a row, when it is almost a beat, and when it is a beat, and when it just was a beat. It will help the script.
11. beat_scripter.train()
This will let the script analyze what you have just told it.
12. beat_scripter.predict_beats(15000)
This will analyze the video and find the beats. The number is the number of frames, to analyze. This takes some time, so it is best just to do part of the video, until it finds the beats as it should.
If the script is ok, then remove the number, and run again.
13. beat_scripter.export_funscript()
Exports the funscript in the video directory
What to do if the funscript is not ok?
You can fine tune the placement of the red box.
If there is a part of the video, where the script is bad, then load som frames from that part (add the minute mark to beat_scripter.load_frames_list([0, 1.5*60, 5*60, 9*60])
)
If this doesn’t work, it is probably better to look for another video, some videos are just dificult.
Final notes
I hope you can use the script, and share the funscripts you create. The script is quite basic, so you are very welcome to improve it, and upload your version of it.
It would for example be nice to create the red box with the mouse instead of changing the numbers. The algorithm used for detecting beats (k-nearest neighbours) could also be improved.