| ... | ... | @@ -0,0 +1,142 @@ |
| 1 | #!/usr/bin/env python3 |
| 2 | import subprocess |
| 3 | import os |
| 4 | import sys |
| 5 | import re |
| 6 | import glob |
| 7 | import pyperclip |
| 8 | import time |
| 9 | |
| 10 | def run_command(cmd, shell=False): |
| 11 | """Run a command and return its output, exit on failure""" |
| 12 | try: |
| 13 | if shell: |
| 14 | result = subprocess.run(cmd, shell=True, check=True, text=True, capture_output=True) |
| 15 | else: |
| 16 | result = subprocess.run(cmd, check=True, text=True, capture_output=True) |
| 17 | return result.stdout.strip() |
| 18 | except subprocess.CalledProcessError as e: |
| 19 | print(f"Error executing command: {cmd}") |
| 20 | print(f"Error message: {e.stderr}") |
| 21 | sys.exit(1) |
| 22 | except Exception as e: |
| 23 | print(f"Unexpected error running command: {e}") |
| 24 | sys.exit(1) |
| 25 | |
| 26 | def find_file(base_name): |
| 27 | """Find a file with the given base name in the specified directory structure""" |
| 28 | search_path = "/Volumes/Project/*/Film/**/*" |
| 29 | |
| 30 | try: |
| 31 | # Expand the glob pattern to find all matching files |
| 32 | matching_files = [] |
| 33 | for project_dir in glob.glob("/Volumes/Project/*/"): |
| 34 | for root, dirs, files in os.walk(os.path.join(project_dir, "Film")): |
| 35 | # Skip hidden directories |
| 36 | dirs[:] = [d for d in dirs if not d.startswith('.')] |
| 37 | for file in files: |
| 38 | if file == base_name and not file.startswith('.'): |
| 39 | matching_files.append(os.path.join(root, file)) |
| 40 | |
| 41 | if not matching_files: |
| 42 | print(f"Error: Could not find file '{base_name}' in {search_path}") |
| 43 | sys.exit(1) |
| 44 | elif len(matching_files) > 1: |
| 45 | print(f"Warning: Found multiple matches for '{base_name}'. Using the first one.") |
| 46 | |
| 47 | return matching_files[0] |
| 48 | except Exception as e: |
| 49 | print(f"Error searching for file: {e}") |
| 50 | sys.exit(1) |
| 51 | |
| 52 | def activate_app(app_name): |
| 53 | """Activate an application by name""" |
| 54 | try: |
| 55 | script = f'tell application "{app_name}" to activate' |
| 56 | subprocess.run(["osascript", "-e", script], check=True) |
| 57 | except Exception as e: |
| 58 | print(f"Error activating {app_name}: {e}") |
| 59 | sys.exit(1) |
| 60 | |
| 61 | def main(): |
| 62 | # Step 1: Run Apple Script to get frame and name from QuickTime Player |
| 63 | print("Step 1: Getting frame and name from QuickTime Player...") |
| 64 | applescript = ''' |
| 65 | tell application "QuickTime Player" to tell document 1 |
| 66 | set t to current time |
| 67 | step forward |
| 68 | set k to current time |
| 69 | set r to 1 / (k - t) |
| 70 | step backward |
| 71 | return "" & (round (r * t) rounding down) & ":" & name |
| 72 | end tell |
| 73 | ''' |
| 74 | |
| 75 | try: |
| 76 | result = subprocess.run(["osascript", "-e", applescript], |
| 77 | check=True, text=True, capture_output=True) |
| 78 | frame_and_name = result.stdout.strip() |
| 79 | |
| 80 | if not frame_and_name or ":" not in frame_and_name: |
| 81 | print("Error: AppleScript did not return expected output") |
| 82 | sys.exit(1) |
| 83 | |
| 84 | target_frame, name = frame_and_name.split(":", 1) |
| 85 | target_frame = int(target_frame) |
| 86 | |
| 87 | print(f"Target frame: {target_frame}") |
| 88 | print(f"File name: {name}") |
| 89 | except Exception as e: |
| 90 | print(f"Error running AppleScript: {e}") |
| 91 | sys.exit(1) |
| 92 | |
| 93 | # Step 2: Find the file on disk |
| 94 | print("\nStep 2: Finding file on disk...") |
| 95 | file_path = find_file(name) |
| 96 | print(f"Found file at: {file_path}") |
| 97 | |
| 98 | # Step 3: Run Fusion script to get current frame |
| 99 | print("\nStep 3: Getting current frame from Fusion...") |
| 100 | fusion_script_cmd = "'/Applications/Blackmagic Fusion 19/Fusion.app/Contents/Libraries/fuscript' -x 'print(\"[[\"..Fusion().CurrentComp.CurrentTime..\"]]\")'" |
| 101 | fusion_output = run_command(fusion_script_cmd, shell=True) |
| 102 | |
| 103 | # Extract the frame number from the output |
| 104 | match = re.search(r'\[\[(\d+)\]\]', fusion_output) |
| 105 | if not match: |
| 106 | print(f"Error: Could not parse frame number from Fusion output: {fusion_output}") |
| 107 | sys.exit(1) |
| 108 | |
| 109 | current_frame = int(match.group(1)) |
| 110 | print(f"Current frame: {current_frame}") |
| 111 | |
| 112 | # Step 4: Compute TRIM_IN and EXTEND_FIRST |
| 113 | print("\nStep 4: Computing TRIM_IN and EXTEND_FIRST...") |
| 114 | trim_in = 0 |
| 115 | extend_first = 0 |
| 116 | |
| 117 | if target_frame > current_frame: |
| 118 | trim_in = target_frame - current_frame |
| 119 | print(f"Target frame is AFTER current frame. Setting TRIM_IN to {trim_in}") |
| 120 | else: |
| 121 | extend_first = current_frame - target_frame |
| 122 | print(f"Target frame is BEFORE current frame. Setting EXTEND_FIRST to {extend_first}") |
| 123 | |
| 124 | # Step 5: Create the Fusion loader text and copy to clipboard |
| 125 | print("\nStep 5: Creating Fusion loader text and copying to clipboard...") |
| 126 | fusion_text = f'''{{Tools = ordered() {{Loader = Loader {{Clips = {{Clip {{ID = "Clip1",Filename = "{file_path}",FormatID = "QuickTimeMovies",Length = 9999999,Multiframe = true,TrimIn = {trim_in},TrimOut = 9999999,ExtendFirst = {extend_first},ExtendLast = 0,Loop = 1,AspectMode = 0,Depth = 0,TimeCode = 0,GlobalStart = 0,GlobalEnd = 9999999}}}},CtrlWZoom = false,Inputs = {{["Gamut.SLogVersion"] = Input {{ Value = FuID {{ "SLog2" }}, }}}},}}}},ActiveTool = "Loader"}}''' |
| 127 | |
| 128 | try: |
| 129 | pyperclip.copy(fusion_text) |
| 130 | print("Text copied to clipboard:") |
| 131 | print(fusion_text) |
| 132 | except Exception as e: |
| 133 | print(f"Error copying to clipboard: {e}") |
| 134 | sys.exit(1) |
| 135 | |
| 136 | # Finally, activate Fusion but don't paste |
| 137 | print("\nActivating Fusion...") |
| 138 | activate_app("Fusion") |
| 139 | print("Script completed successfully!") |
| 140 | |
| 141 | if __name__ == "__main__": |
| 142 | main() |