Guest User

Untitled

a guest
Dec 7th, 2024
60
0
129 days
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 12.90 KB | Source Code | 0 0
  1. import json
  2. import os
  3. import subprocess
  4. import tkinter as tk
  5. from tkinter import filedialog, messagebox
  6. from moviepy.video.io.VideoFileClip import VideoFileClip
  7. from moviepy.video.compositing.concatenate import concatenate_videoclips
  8.  
  9.  
  10.  
  11. selected_index = None
  12.  
  13. def process_file(input_file_path, start_threshold, end_threshold):
  14.     """
  15.    Processes a .funscript file to retain 'actions' within a range and offset 'at' values.
  16.    Returns the filtered and adjusted actions.
  17.    """
  18.     # Read the .funscript file (handling as JSON)
  19.     with open(input_file_path, 'r') as file:
  20.         data = json.load(file)
  21.  
  22.     # Filter and offset the "actions"
  23.     filtered_actions = [
  24.         {'at': action['at'] - start_threshold, 'pos': action['pos']}
  25.         for action in data['actions']
  26.         if start_threshold <= action['at'] <= end_threshold
  27.     ]
  28.     return filtered_actions
  29.  
  30.  
  31. def cut_video(video_file_path, start_ms, end_ms):
  32.     """
  33.    Cuts a portion of the video based on start and end milliseconds using FFmpeg.
  34.    """
  35.     global selected_index
  36.     start_s = start_ms / 1000  # Convert milliseconds to seconds
  37.     end_s = end_ms / 1000  # Convert milliseconds to seconds
  38.     output_file = f"cut{start_ms}_{os.path.basename(video_file_path)}"
  39.  
  40.     # Construct the FFmpeg command
  41.     ffmpeg_command = [
  42.         'ffmpeg',
  43.         '-i', video_file_path,          # Input file
  44.         '-ss', str(start_s),            # Start time (seconds)
  45.         '-to', str(end_s),              # End time (seconds)
  46.         '-c:v', 'copy',                 # Copy video stream (no re-encoding)
  47.         '-c:a', 'copy',                 # Copy audio stream (no re-encoding)
  48.         output_file                     # Output file name
  49.     ]
  50.    
  51.     try:
  52.         # Run the FFmpeg command
  53.         subprocess.run(ffmpeg_command, check=True)
  54.         print(f"Video cut successfully: {output_file}")
  55.  
  56.        
  57.         clip = VideoFileClip(output_file)
  58.         duration_ms = clip.duration * 1000  # Convert seconds to milliseconds
  59.  
  60.         return VideoFileClip(output_file), duration_ms
  61.     except subprocess.CalledProcessError as e:
  62.         print(f"Error cutting video {video_file_path}: {e}")
  63.         return None
  64.  
  65.  
  66. def combine_videos(video_clips, output_file):
  67.     """
  68.    Combines multiple VideoFileClip objects into one video.
  69.    """
  70.     final_clip = concatenate_videoclips(video_clips, method="compose")
  71.     final_clip.write_videofile(output_file, codec="libx264")
  72.     print(f"Combined video saved to {output_file}")
  73.  
  74.  
  75. def browse_files():
  76.     """
  77.    Opens a file dialog to select a .funscript file and add it to the listbox.
  78.    """
  79.     funscript_file = filedialog.askopenfilename(filetypes=[("Funscript files", "*.funscript")])
  80.     if funscript_file:
  81.         # Add .funscript file to the listbox
  82.         json_files_listbox.insert(tk.END, funscript_file)
  83.         # Initialize thresholds as empty for new files
  84.         file_thresholds[json_files_listbox.size() - 1] = {'start': 0, 'end': 0}  # Index-based tracking
  85.         update_threshold_fields(json_files_listbox.size() - 1)
  86.  
  87.  
  88. def update_threshold_fields(index):
  89.     """
  90.    Updates the start and end threshold fields when a .funscript file is selected.
  91.    """
  92.     start_entry.delete(0, tk.END)
  93.     end_entry.delete(0, tk.END)
  94.  
  95.     # Get the current thresholds for the selected file
  96.     if index in file_thresholds:
  97.         start_entry.insert(0, milliseconds_to_time(file_thresholds[index]['start']))
  98.         end_entry.insert(0, milliseconds_to_time(file_thresholds[index]['end']))
  99.  
  100. def on_select_file(event):
  101.     """
  102.    Handles .funscript file selection from the listbox, updates the threshold fields.
  103.    """
  104.     global selected_index
  105.     selection = json_files_listbox.curselection()
  106.     if selection:
  107.         selected_index = selection[0]  # Get the first selected index
  108.         update_threshold_fields(selected_index)
  109.  
  110.  
  111. def remove_selected():
  112.     """
  113.    Removes selected .funscript files from the listbox and corresponding threshold entries.
  114.    """
  115.     selected_indices = json_files_listbox.curselection()
  116.     for index in reversed(selected_indices):
  117.         selected_file = json_files_listbox.get(index)
  118.         json_files_listbox.delete(index)
  119.         del file_thresholds[selected_file]
  120.     update_threshold_fields('')  # Reset fields
  121.  
  122.  
  123. def process_all_files():
  124.     """
  125.    Processes all selected files and generates combined output based on the selected checkboxes.
  126.    """
  127.     try:
  128.         all_actions = []
  129.         video_clips = []
  130.         offset_list = []
  131.         for i in range(json_files_listbox.size()):
  132.             # Get the file path and thresholds for this instance
  133.             funscript_file = json_files_listbox.get(i)
  134.             start_threshold = file_thresholds[i]['start']
  135.             end_threshold = file_thresholds[i]['end']
  136.  
  137.  
  138.  
  139.             # Process the .funscript file
  140.             filtered_actions = process_file(funscript_file, start_threshold, end_threshold)
  141.             all_actions.append(filtered_actions)
  142.  
  143.             # Handle corresponding video file
  144.             video_file = funscript_file.replace('.funscript', '.mp4')
  145.             if os.path.exists(video_file) and (combine_scripts_and_cut_videos_var.get() or combine_scripts_and_videos_var.get()):
  146.                 video_clip, vid_length = cut_video(video_file, start_threshold, end_threshold)
  147.                 video_clips.append(video_clip)
  148.                 offset_list.append(vid_length)
  149.  
  150.         # Combine actions if "Combine Scripts Only" checkbox is selected
  151.         if combine_scripts_var.get():
  152.             combined_actions = combine_actions(all_actions, offset_list)
  153.             combined_file_path = "Combined_Actions.funscript"
  154.             with open(combined_file_path, 'w') as file:
  155.                 json.dump({'actions': combined_actions}, file, indent=4)
  156.  
  157.         # Combine actions and videos if needed
  158.         if combine_scripts_and_videos_var.get():
  159.             combined_actions = combine_actions(all_actions, offset_list)
  160.             combined_file_path = "Combined_Actions.funscript"
  161.             with open(combined_file_path, 'w') as file:
  162.                 json.dump({'actions': combined_actions}, file, indent=4)
  163.  
  164.             combined_video_path = "Combined_Video.mp4"
  165.             if video_clips:
  166.                 combine_videos(video_clips, combined_video_path)
  167.  
  168.         messagebox.showinfo("Success", "Files processed and saved successfully!")
  169.  
  170.     except Exception as e:
  171.         messagebox.showerror("Error", f"An error occurred: {str(e)}")
  172.  
  173.  
  174. def combine_actions(file_actions_list, offset_list):
  175.     """
  176.    Combines multiple 'actions' lists, offsetting the 'at' values for continuity.
  177.    """
  178.     combined_actions = []
  179.     current_offset = 0
  180.     file_num = 0
  181.  
  182.     for actions in file_actions_list:
  183.         for action in actions:
  184.             combined_actions.append({
  185.                 'at': action['at'] + current_offset,
  186.                 'pos': action['pos']
  187.             })
  188.         if actions:
  189.             #current_offset += actions[-1]['at']  # Update offset to ensure continuity
  190.             current_offset += offset_list[file_num]
  191.             file_num += 1
  192.  
  193.  
  194.     return combined_actions
  195.  
  196. # Function to manage checkbox states
  197. def manage_checkboxes(selected_var):
  198.     """
  199.    Disable other checkboxes when one checkbox is selected.
  200.    Enable all checkboxes if none are selected.
  201.    """
  202.     if selected_var.get():
  203.         # Disable other checkboxes
  204.         if selected_var == combine_scripts_var:
  205.             combine_scripts_and_cut_videos_checkbox.config(state="disabled")
  206.             combine_scripts_and_videos_checkbox.config(state="disabled")
  207.         elif selected_var == combine_scripts_and_cut_videos_var:
  208.             combine_scripts_checkbox.config(state="disabled")
  209.             combine_scripts_and_videos_checkbox.config(state="disabled")
  210.         elif selected_var == combine_scripts_and_videos_var:
  211.             combine_scripts_checkbox.config(state="disabled")
  212.             combine_scripts_and_cut_videos_checkbox.config(state="disabled")
  213.     else:
  214.         # Enable all checkboxes if none are selected
  215.         combine_scripts_checkbox.config(state="normal")
  216.         combine_scripts_and_cut_videos_checkbox.config(state="normal")
  217.         combine_scripts_and_videos_checkbox.config(state="normal")
  218.  
  219. # Save button to save the current thresholds
  220. def save_thresholds():
  221.     """
  222.    Save the updated thresholds for the selected file.
  223.    """
  224.     global selected_index
  225.     if selected_index is not None:
  226.         try:
  227.             start_threshold = int(convert_to_milliseconds(start_entry.get()))
  228.             end_threshold = int(convert_to_milliseconds(end_entry.get()))
  229.             file_thresholds[selected_index] = {'start': start_threshold, 'end': end_threshold}
  230.             print(f"Saved thresholds for file at index {selected_index}: Start - {start_threshold} ms, End - {end_threshold} ms")
  231.         except ValueError:
  232.             messagebox.showerror("Invalid Input", "Input must be in the format hh:mm:ss:milliseconds")
  233.     else:
  234.         messagebox.showerror("No Selection", "Please select a file from the listbox.")
  235.  
  236.  
  237.  
  238. def convert_to_milliseconds(time_str):
  239.     # Split the input string into components
  240.     parts = time_str.split(':')
  241.    
  242.     # Ensure there are exactly 4 parts (hh, mm, ss, milliseconds)
  243.     if len(parts) != 4:
  244.         raise ValueError("Input must be in the format hh:mm:ss:milliseconds")
  245.    
  246.     # Extract hours, minutes, seconds, and milliseconds
  247.     hours = int(parts[0])
  248.     minutes = int(parts[1])
  249.     seconds = int(parts[2])
  250.     milliseconds = int(parts[3])
  251.    
  252.     # Convert all parts to milliseconds
  253.     total_milliseconds = (
  254.         (hours * 3600 * 1000) +  # Convert hours to milliseconds
  255.         (minutes * 60 * 1000) +  # Convert minutes to milliseconds
  256.         (seconds * 1000) +       # Convert seconds to milliseconds
  257.         milliseconds              # Already in milliseconds
  258.     )
  259.    
  260.     return total_milliseconds
  261.  
  262. def milliseconds_to_time(ms):
  263.     """
  264.    Converts milliseconds to time in the format 'hh:mm:ss:milliseconds'.
  265.    
  266.    :param ms: Time in milliseconds
  267.    :return: Formatted time as a string
  268.    """
  269.     hours = ms // (1000 * 60 * 60)
  270.     minutes = (ms % (1000 * 60 * 60)) // (1000 * 60)
  271.     seconds = (ms % (1000 * 60)) // 1000
  272.     milliseconds = ms % 1000
  273.     return f"{hours:02}:{minutes:02}:{seconds:02}:{milliseconds:03}"
  274.  
  275.  
  276.  
  277. # Initialize the main window
  278. window = tk.Tk()
  279. window.title("Funscript Compilation Maker")
  280. window.geometry("400x600")
  281.  
  282. # Create a frame to hold the content
  283. frame = tk.Frame(window)
  284.  
  285. # JSON files listbox
  286. json_files_listbox = tk.Listbox(frame, width=50, height=10)
  287. json_files_listbox.pack(pady=20)
  288.  
  289. # Bind selection event for listbox
  290. json_files_listbox.bind('<<ListboxSelect>>', on_select_file)
  291.  
  292. # Browse button
  293. browse_button = tk.Button(frame, text="Browse Files", command=browse_files)
  294. browse_button.pack(pady=5)
  295.  
  296. # Remove selected button
  297. remove_button = tk.Button(frame, text="Remove Selected", command=remove_selected)
  298. remove_button.pack(pady=5)
  299.  
  300. # Thresholds frame for start and end points
  301. thresholds_frame = tk.Frame(frame)
  302. thresholds_frame.pack(pady=10)
  303.  
  304. # Labels and entries for start and end threshold inputs
  305. tk.Label(thresholds_frame, text="Start Time:").grid(row=0, column=0, padx=5, pady=5)
  306. start_entry = tk.Entry(thresholds_frame)
  307. start_entry.grid(row=0, column=1, padx=5, pady=5)
  308.  
  309. tk.Label(thresholds_frame, text="End Time:").grid(row=1, column=0, padx=5, pady=5)
  310. end_entry = tk.Entry(thresholds_frame)
  311. end_entry.grid(row=1, column=1, padx=5, pady=5)
  312.  
  313. # Initialize the dictionary for file thresholds
  314. file_thresholds = {}
  315.  
  316. # Save button UI
  317. save_button = tk.Button(frame, text="Save Time", command=save_thresholds)
  318. save_button.pack(pady=10)
  319.  
  320. # Checkboxes for different functionalities with commands to manage state
  321. combine_scripts_var = tk.BooleanVar()
  322. combine_scripts_and_cut_videos_var = tk.BooleanVar()
  323. combine_scripts_and_videos_var = tk.BooleanVar()
  324.  
  325. combine_scripts_checkbox = tk.Checkbutton(
  326.     frame, text="Combine Scripts Only", variable=combine_scripts_var,
  327.     command=lambda: manage_checkboxes(combine_scripts_var)
  328. )
  329. combine_scripts_checkbox.pack(pady=5)
  330.  
  331. combine_scripts_and_cut_videos_checkbox = tk.Checkbutton(
  332.     frame, text="Combine Scripts and Cut Videos", variable=combine_scripts_and_cut_videos_var,
  333.     command=lambda: manage_checkboxes(combine_scripts_and_cut_videos_var)
  334. )
  335. combine_scripts_and_cut_videos_checkbox.pack(pady=5)
  336.  
  337. combine_scripts_and_videos_checkbox = tk.Checkbutton(
  338.     frame, text="Combine Scripts and Videos", variable=combine_scripts_and_videos_var,
  339.     command=lambda: manage_checkboxes(combine_scripts_and_videos_var)
  340. )
  341. combine_scripts_and_videos_checkbox.pack(pady=5)
  342.  
  343. # Process button
  344. process_button = tk.Button(frame, text="Create Funscript", command=process_all_files)
  345. process_button.pack(pady=20)
  346.  
  347. # Add the frame to the window
  348. frame.pack()
  349.  
  350. # Start the GUI loop
  351. window.mainloop()
Advertisement
Add Comment
Please, Sign In to add comment