Funscript Compilation Maker

I created an application to make a new script using sections from existing scripts, basically a script compilation maker tool. You can combine any number of scripts by defining the start and end time of the segments you want, the result is a new script that combines the segments into one file. There is also an option to create the combined video that will be in sync with the generated script.

You can get the download on GitHub

Update 1
  • Used code from @hentaiprodigy69 to convert time to milliseconds so now the time format can be entered for the thresholds.
  • UI is more smooth now, selected file in the list box is persistent until a different file is selected.
  • Multiple sections from the same script are working as intended now.

Setup:

  • You will need to download FFMPEG to do any of the video processing.

  • For just generating a funscript file and doing no video processing, all you need to do is have the script files ready.

  • If you want to do the video processing as well you will need to have the video and the script in the same folder with the files having the same name, just as you would do if you’re trying to use the script normally.

  • To start the user interface you will have to run the python script

How to run the Python Script
  1. Download and install the latest version of Python here

  2. To verify enter the following line into your command line/terminal

    python --version

  3. Download the files from GitHub.

  4. Install pip, this will allow you to install dependencies.

    python -m ensurepip --upgrade

  5. Navigate to the folder where the Python script is located:

    cd path/to/your/script/folder

  6. Install dependencies using requirements.txt

    pip install -r requirements.txt

  7. Run the python script

    python script.py

Note: Some systems use python3 in the terminal so if step 2 doesn’t work try replacing “python” with “python3”

Step by Step Instructions:

  1. Using the ‘Browse Files’ button select the scripts you want to pull from. The order in the list box is the order the final combined script and video will be in.

    • If you want to include several sections from the same script you will have to browse for the file as many times as you want to use it. For example, if you want 3 sections from the same script, the script needs to be in the box 3 times.
  2. Set the start time and end time of the section of each script you want to use.

    • To do this you select the script in the box, set the start and end time values and click ‘Save Time.’

    • Time must be in hh:mm:ss:milliseconds format, for example 01:19:22:033.

  1. Select the method of script generation you want.

    • Combine Funscripts Only: No video processing, will just generate combined_actions.funscript.

    • Combine Funscripts and Cut Videos: Will generate the combined_actions.funscript and the cut videos, which are just the segments of the videos defined by the start and end times entered.

    • Combine Funscripts and Videos: Will generate the combine_actions.funscript, the cut videos, and a combined_video.mp4, which is the full video in sync with the combined script.

  2. Select ‘Generate Funscript’.

    • The time it takes will depend heavily on the script generation method. Just generating the script is near instant, but the video processing will take longer. Video processing can take very long and depends on several factors such as encoding, length, quality and your computers capabilities.

Some things to note:

  • It’s possible the transition from one video to the next on the script won’t be smooth, there’s no editing being done it is just starting the next section of the script after the previous section is done. So it really depends on the start and end points location. The transition here in my sample is decent, but the scripts you use may not transition well.

    image

  • The UI is pretty basic, I created it using a python library so it might not be the smoothest experience, but it functions well and there’s no glaring issues.

  • I’ve tested this a lot with small clips from 2 or 3 different videos. I don’t anticipate problems with having many clips but anything is possible. But I don’t really know the lengths this can go, my computer isn’t great and a 3 minute video takes about 1.5 hours to process. Probably would have been quicker to use lower quality videos but I used whatever I already had on hand.

  • After processing, the script file will be named Combined_Actions.funscript and the video file will be named Combined_Videos.mp4 so you will have to change the names to matching to use the script.

  • I didn’t originally set out to make this something I’d share so it might not be the most efficient or user friendly, I’ve seen some threads over time about similar things so I figured someone else might be able to make use of this.

  • For the sample, I took 3 scripts I made and used the tool to cut out 1 minute long clips.
    If you want to check out the full scripts here are the links (FYI they are paid scripts)



  • Feel free to leave feedback or make suggestions. I’m not really looking to add features or more functionality, but I will try to fix bugs or refine what is already there.
5 Likes

Seems like a really useful tool!
Next step is to include a player window (maybe even a script viewer) with controls to aid in selecting the timings.

1 Like

I can see how that would be useful but other things exist that you can get that information from so I think it would just be unnecessary bloat for this project.

For us folks who don’t do python, getting this to run is Greek to me. You have instructions for dummies?

Remixing/splicing free scripts you didn’t make and uploading them is allowed according to the site wide rules, but I’m worried people will start complaining if this becomes a widely used thing. Granted, I don’t post scripts I make so it doesn’t affect me, but still makes me worry.

I tried to make my instructions as simple as I could but I think your best bet is finding a youtube tutorial that you can follow along with. If that doesn’t work you can DM me for python specific stuff. Fortunately with this once you get the interface up you can do everything there.

I did have a thought about this as I was posting. Speaking for myself I wouldn’t mind if my scripts were used, obviously I can’t speak for everyone. If it comes to being a problem I think this community is good at kind of policing itself. I could also maybe find a way to add something to the produced script that shows it was made from this.

Gotta add this to the Github. I tried installing moviepy but it didn’t have one of the modules.

Oop you’re right sorry about that it’s on there now

1 Like

Cool, I managed to get it to work.

Definitely gotta be careful when clicking around or hitting other keys. It seems like it works from my simple test. I like how you can cut down a video to the action parts, but man it’d be great if you guys learned how to just parse the time from hours, minutes, seconds, milliseconds.

Add in this function:

def convert_to_milliseconds(time_str):
    # Split the input string into components
    parts = time_str.split(':')
    
    # Ensure there are exactly 4 parts (hh, mm, ss, milliseconds)
    if len(parts) != 4:
        raise ValueError("Input must be in the format hh:mm:ss:milliseconds")
    
    # Extract hours, minutes, seconds, and milliseconds
    hours = int(parts[0])
    minutes = int(parts[1])
    seconds = int(parts[2])
    milliseconds = int(parts[3])
    
    # Convert all parts to milliseconds
    total_milliseconds = (
        (hours * 3600 * 1000) +  # Convert hours to milliseconds
        (minutes * 60 * 1000) +  # Convert minutes to milliseconds
        (seconds * 1000) +       # Convert seconds to milliseconds
        milliseconds              # Already in milliseconds
    )
    
    return total_milliseconds

And then in the function save_thresholds():
You can change the line with the start_threshold and end_threshold to call the conversion

...
        start_threshold = int(convert_to_milliseconds(start_entry.get()))
        end_threshold = int(convert_to_milliseconds(end_entry.get()))

Also, for some reason you have that function in there twice, so the bottom one gets referenced.

And now you can just type in a time formatted like this:
00:30:45:500

You have to put the hours there as 0, but this is still better than converting to milliseconds lol

For people who don’t know how to run this, I can give a quick guide…

  1. Download and Install Python for Windows

  2. Open command prompt and type in these commands:
    pip install moviepy==1.0.3
    pip install tkinter

  3. Download the file from github and rename it top FunscriptCompilationGUI.pyw

  4. Now you can just double-click on the file and it will open up the interface.

Tips: Don’t double-click, don’t hit the Tab key, and don’t use “Shift+left-arrow” to highlight any text you’re trying to edit, or it will mess up the tool, and you have to click on the script again and then start writing the time all over again.

2 Likes

I’m not finding this working as intended. If you import the script a second time, the timing is wiped out and set back to 0. And if you have the same funscript in there twice, if you apply the Start and End time to one instance of the script, it’s applied to all of them.

Cool thanks for letting me know, that is actually something I thought of last second so I didn’t get to put it through many tests I will look into that.

And thanks for the time conversion, this is of course better than having to convert it manually.

1 Like

Made some fixes and improved usability:

  • Used code from hentaiprodigy69 to convert time to milliseconds so now the time format can be entered for the thresholds.
  • UI is more smooth now, selected file in the list box is persistent until a different file is selected.
  • Multiple sections from the same script are working as intended now.
1 Like

Seems like it’s better working now.
I guess my only suggestion is that if you’re only cutting clips from multiple parts of the same video, it would be great to just crop and append the videos, rather than doing the whole encoding process which takes a lot longer (and diminishes the quality).

I also tried making it as two clips (2nd checkbox), combining those on the side using mmg, and then using the Combined_Actions.funscript that generates (either this generated from the 2nd or 3rd checkbox) and the timing is off, but I’m still waiting for the encoded version of Combined_Video to generate.

You mean when you just generate the clips on this but combine them through a different method, the script this generates is out of sync on that video?

Well, I tried with Combined_Video.mp4 and Combined_Actions.funscript also, from the 3rd checkbox, and it’s also not in sync.
It’s the same as if I edit the two videos together from the 2nd checkbox.

Correct visual frame is on the right.


The timing is about 16 seconds off, having the action around 12:12 when it appears visually at 12:28.

And where I manually re-combined the two video clips generated from this option:
image
The vid length is different but the timing is similarly off.

One issue is this 2nd option never actually created the trimmed .funscript. It just gives me the two video clips. The 1st option or the 3rd option will generate the Combined_actions.funscript, which I think are the same file (not sure).
However, by taking the two clips and combining them myself and using with the combined script, or by using the output from the 3rd option, both video results are the same, the timing being off.

I haven’t looked at the programming logic in detail to find the bug in how the scripts are incorrectly compiled together.

It’s probably an issue with appending the two scripts together, because individually it seems to be clipping it fine.

HOW TO FIX:
I’m gonna take a hunch. I think the issue is right here.

I think you might be setting the current_offset as the last point in the first script.
The problem is that the last point in the script might be the length of the video.
So… look here:


My last point is at 8:41.851, but the video is 9:00 long. So the offset will be off by 18 seconds, putting the points 18 seconds earlier in the video.
You have to pass the offset that’s the first “End Time” variable that you passed when you created the funscript, which would be “00:09:00:000” so that script2 is properly offset.

Yeah that’s where I was thinking the logic was messed up. I have to see what I changed that broke it cause the sync was working before the last update I made. I’ll take a look at this tonight and try to refine it.

As for the videos being combined through different methods, I can’t really control that I’m sure there’s dozens of ways to combine videos like that. I might remove the just clips option cause there’s not really a purpose of having just the clips if combining them through different means would be too unpredictable with the script that is generated.

1 Like

“We Will Watch Your Career With Great Interest”

-Senator Palpatine

1 Like

Yeah, I was mainly doing it on the side since I was impatient while the re-encode was going on, and I found that things were out of sync.
I’d mainly be interested in being able to do a single video where I remove the non-action parts, and save the best parts of a video. Might be able to save some space here and there, and save time over manually cropping and moving around the .funscript timings.

For compilations of different videos, it’d also be pretty cool. Might start collecting more short videos for that purpose.

When they ask me why my Python is so good during an interview and I just say “personal projects”

1 Like

All right, here’s another code fix. Here’s how I fixed the offset issue.

TLDR go to the end and see the new code I made.

1st Attempt: Fixing the Offset by using the provided start and end durations:

I added an offset_list that I’m passing to the combine funscripts function. It’s essentially the length of the video.

def process_all_files():
    """
    Processes all selected files and generates combined output based on the selected checkboxes.
    """
    try:
        all_actions = []
        video_clips = []
        offset_list = []
        for i in range(json_files_listbox.size()):
            # Get the file path and thresholds for this instance
            funscript_file = json_files_listbox.get(i)
            start_threshold = file_thresholds[i]['start']
            end_threshold = file_thresholds[i]['end']

            offset_list.append(end_threshold-start_threshold)


            # Process the .funscript file
            filtered_actions = process_file(funscript_file, start_threshold, end_threshold)
            all_actions.append(filtered_actions)

            # Handle corresponding video file
            video_file = funscript_file.replace('.funscript', '.mp4')
            if os.path.exists(video_file) and (combine_scripts_and_cut_videos_var.get() or combine_scripts_and_videos_var.get()):
                video_clip = cut_video(video_file, start_threshold, end_threshold)
                video_clips.append(video_clip)

        # Combine actions if "Combine Scripts Only" checkbox is selected
        if combine_scripts_var.get():
            combined_actions = combine_actions(all_actions, offset_list)
            combined_file_path = "Combined_Actions.funscript"
            with open(combined_file_path, 'w') as file:
                json.dump({'actions': combined_actions}, file, indent=4)

        # Combine actions and videos if needed
        if combine_scripts_and_videos_var.get():
            combined_actions = combine_actions(all_actions, offset_list)
            combined_file_path = "Combined_Actions.funscript"
            with open(combined_file_path, 'w') as file:
                json.dump({'actions': combined_actions}, file, indent=4)

            combined_video_path = "Combined_Video.mp4"
            if video_clips:
                combine_videos(video_clips, combined_video_path)

        messagebox.showinfo("Success", "Files processed and saved successfully!")

    except Exception as e:
        messagebox.showerror("Error", f"An error occurred: {str(e)}")

Then in the combine_actions function, we will add the offset onto the current offset, which basically just adds the length of each successive video clip onto the last offset, so that the points start in sync. I admit I didn’t look at any of the json parser stuff you did, but it seems like when you create a vid clip and create the clipped funscript, you’re correctly properly setting back the actions to start at a position of 0, so now we just apply the offset of the video length up to the current clip, and we’re good to go.

def combine_actions(file_actions_list, offset_list):
    """
    Combines multiple 'actions' lists, offsetting the 'at' values for continuity.
    """
    combined_actions = []
    current_offset = 0
    file_num = 0

    for actions in file_actions_list:
        for action in actions:
            combined_actions.append({
                'at': action['at'] + current_offset,
                'pos': action['pos']
            })
        if actions:
            #current_offset += actions[-1]['at']  # Update offset to ensure continuity
            current_offset += offset_list[file_num]
            file_num += 1


    return combined_actions

Now, the only problem is that when I combine clips, I get extra milliseconds of video.
I did 3x 20 second clips and I get 1:00.473, so the more clips you append, the more off it becomes, unless we can avoid this part.
image
My fix works for a perfect world where the new video ends up being 1:00.000.

Checking the cut video sizes, sometimes it’s under 20 seconds, sometimes it’s over…

The vid I tested this on is 23.976 fps.
On a 50.0 fps video, I get a similar issue with combining. Extra ms added.

I guess the only way is to fix this is to make the clips first, get their runtimes from the output, and then make the funscript.

2nd Attempt: Final Fix
Ok, I went and actually did that, grabbing the offset from the output file instead, and now it’s pretty much perfect. It stays in sync within a frame even with multiple clips.
Here’s the code:

That’s awesome you’re a legend, I got called into work this weekend so I haven’t had any time to look at this. I’ll update the source code at some point tonight. Thanks!

1 Like