Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""
Blender script to process frames from a directory.

Usage:
blender --background --python main.py -- --frames_dir path --fps 30
"""

import sys
import argparse
from pathlib import Path

# Supported image file extensions
SUPPORTED_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.exr', '.tiff', '.tif'}


def parse_arguments():
"""
Parse command-line arguments.

When running with Blender, arguments after '--' are passed to the script.
"""
# Find the separator '--' in sys.argv
if '--' in sys.argv:
# Get arguments after the '--' separator
script_args = sys.argv[sys.argv.index('--') + 1:]
else:
# No separator found, use all arguments after script name
script_args = sys.argv[1:]

parser = argparse.ArgumentParser(
description='Process frames in Blender',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__
)
parser.add_argument(
'--frames_dir',
type=str,
required=True,
help='Directory containing frames to process'
)
parser.add_argument(
'--fps',
type=int,
default=30,
help='Frames per second (default: 30)'
)

args = parser.parse_args(script_args)
return args


def process_frames(frames_dir, fps):
"""
Process frames from the specified directory.

Args:
frames_dir: Path to directory containing frames
fps: Frames per second for rendering
"""
frames_path = Path(frames_dir)

if not frames_path.exists():
raise FileNotFoundError(f"Frames directory not found: {frames_dir}")

if not frames_path.is_dir():
raise NotADirectoryError(f"Path is not a directory: {frames_dir}")

print(f"Processing frames from: {frames_path.absolute()}")
print(f"FPS: {fps}")

# Import Blender API (only available when running within Blender)
try:
import bpy

# Set render FPS
bpy.context.scene.render.fps = fps
print(f"Blender scene FPS set to: {fps}")

# Get list of frame files
frame_files = sorted([
f for f in frames_path.iterdir()
if f.is_file() and f.suffix.lower() in SUPPORTED_EXTENSIONS
])

if not frame_files:
print(f"Warning: No image files found in {frames_dir}")
return

print(f"Found {len(frame_files)} frame files")

# Process frames
for idx, frame_file in enumerate(frame_files, start=1):
print(f"Processing frame {idx}/{len(frame_files)}: {frame_file.name}")
# Add custom processing logic here

print("Frame processing complete!")

except ImportError:
print("Warning: Blender Python API (bpy) not available.")
print("This script should be run with: blender --background --python main.py -- --frames_dir path --fps 30")
print(f"\nDry run mode:")
print(f" Frames directory: {frames_path.absolute()}")
print(f" FPS: {fps}")

# List available frames even without Blender (filter by supported extensions)
frame_files = [f for f in frames_path.iterdir() if f.is_file() and f.suffix.lower() in SUPPORTED_EXTENSIONS]
if frame_files:
print(f" Found {len(frame_files)} image files in directory")
print(f" Supported formats: {', '.join(sorted(SUPPORTED_EXTENSIONS))}")


def main():
"""Main entry point."""
try:
args = parse_arguments()
process_frames(args.frames_dir, args.fps)
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)


if __name__ == '__main__':
main()
126 changes: 124 additions & 2 deletions src/main/lib/trpc/routers/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ import { z } from "zod"
import { router, publicProcedure } from "../index"
import { getDatabase, projects } from "../../db"
import { eq, desc } from "drizzle-orm"
import { dialog, BrowserWindow } from "electron"
import { basename } from "path"
import { dialog, BrowserWindow, app } from "electron"
import { basename, join } from "path"
import { exec } from "node:child_process"
import { promisify } from "node:util"
import { existsSync } from "node:fs"
import { mkdir } from "node:fs/promises"
import { getGitRemoteInfo } from "../../git"
import { trackProjectOpened } from "../../analytics"

const execAsync = promisify(exec)

export const projectsRouter = router({
/**
* List all projects
Expand Down Expand Up @@ -219,4 +225,120 @@ export const projectsRouter = router({
.returning()
.get()
}),

/**
* Clone a GitHub repo and create a project
*/
cloneFromGitHub: publicProcedure
.input(z.object({ repoUrl: z.string() }))
.mutation(async ({ input }) => {
const { repoUrl } = input

// Parse the URL to extract owner/repo
let owner: string | null = null
let repo: string | null = null

// Match HTTPS format: https://github.com/owner/repo
const httpsMatch = repoUrl.match(
/https?:\/\/github\.com\/([^/]+)\/([^/]+)/,
)
if (httpsMatch) {
owner = httpsMatch[1] || null
repo = httpsMatch[2]?.replace(/\.git$/, "") || null
}

// Match SSH format: git@github.com:owner/repo
const sshMatch = repoUrl.match(/git@github\.com:([^/]+)\/(.+)/)
if (sshMatch) {
owner = sshMatch[1] || null
repo = sshMatch[2]?.replace(/\.git$/, "") || null
}

// Match short format: owner/repo
const shortMatch = repoUrl.match(/^([^/]+)\/([^/]+)$/)
if (shortMatch) {
owner = shortMatch[1] || null
repo = shortMatch[2]?.replace(/\.git$/, "") || null
}

if (!owner || !repo) {
throw new Error("Invalid GitHub URL or repo format")
}

// Clone to userData/repos/{owner}/{repo}
const userDataPath = app.getPath("userData")
const reposDir = join(userDataPath, "repos", owner)
const clonePath = join(reposDir, repo)

// Check if already cloned
if (existsSync(clonePath)) {
// Project might already exist in DB
const db = getDatabase()
const existing = db
.select()
.from(projects)
.where(eq(projects.path, clonePath))
.get()

if (existing) {
trackProjectOpened({
id: existing.id,
hasGitRemote: !!existing.gitRemoteUrl,
})
return existing
}

// Create project for existing clone
const gitInfo = await getGitRemoteInfo(clonePath)
const newProject = db
.insert(projects)
.values({
name: repo,
path: clonePath,
gitRemoteUrl: gitInfo.remoteUrl,
gitProvider: gitInfo.provider,
gitOwner: gitInfo.owner,
gitRepo: gitInfo.repo,
})
.returning()
.get()

trackProjectOpened({
id: newProject!.id,
hasGitRemote: !!gitInfo.remoteUrl,
})
return newProject
}

// Create repos directory
await mkdir(reposDir, { recursive: true })

// Clone the repo
const cloneUrl = `https://github.com/${owner}/${repo}.git`
await execAsync(`git clone "${cloneUrl}" "${clonePath}"`)

// Get git info and create project
const db = getDatabase()
const gitInfo = await getGitRemoteInfo(clonePath)

const newProject = db
.insert(projects)
.values({
name: repo,
path: clonePath,
gitRemoteUrl: gitInfo.remoteUrl,
gitProvider: gitInfo.provider,
gitOwner: gitInfo.owner,
gitRepo: gitInfo.repo,
})
.returning()
.get()

trackProjectOpened({
id: newProject!.id,
hasGitRemote: !!gitInfo.remoteUrl,
})

return newProject
}),
})
Loading