Advertisement
Guest User

Untitled

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