diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..e7cb624
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,21 @@
+# Pixelle-Video Environment Configuration
+# Copy this file to .env and customize as needed
+
+# ============================================================================
+# Port Configuration
+# ============================================================================
+
+# FastAPI Backend Port
+API_PORT=8000
+
+# Next.js Editor Port
+EDITOR_PORT=3000
+
+# Streamlit Web UI Port
+WEB_PORT=8501
+
+# ============================================================================
+# Other Configuration
+# ============================================================================
+
+# Add other environment variables here as needed
diff --git a/api/routers/editor.py b/api/routers/editor.py
index d80059f..9022ead 100644
--- a/api/routers/editor.py
+++ b/api/routers/editor.py
@@ -47,24 +47,31 @@ from api.schemas.editor import (
from fastapi import BackgroundTasks
import asyncio
import uuid as uuid_module
+import os
router = APIRouter(prefix="/editor", tags=["Editor"])
# Export task storage
_export_tasks: dict = {}
+# Get API port from environment
+API_PORT = os.getenv("API_PORT", "8000")
-def _path_to_url(file_path: str, base_url: str = "http://localhost:8000") -> str:
+
+def _path_to_url(file_path: str, base_url: str = None) -> str:
"""Convert local file path to URL accessible through API"""
if not file_path:
return None
-
+
+ if base_url is None:
+ base_url = f"http://localhost:{API_PORT}"
+
import os
from pathlib import Path
-
+
# Normalize path separators
file_path = file_path.replace("\\", "/")
-
+
# Extract relative path from output directory
parts = file_path.split("/")
try:
@@ -73,7 +80,7 @@ def _path_to_url(file_path: str, base_url: str = "http://localhost:8000") -> str
relative_path = "/".join(relative_parts)
except ValueError:
relative_path = Path(file_path).name
-
+
return f"{base_url}/api/files/{relative_path}"
@@ -951,8 +958,9 @@ async def export_video(
for frame in sorted_frames:
path = frame.get("video_segment_path", "")
if path.startswith("http"):
- # Extract path from URL
- path = path.replace("http://localhost:8000/api/files/", "output/")
+ # Extract path from URL (format: http://localhost:{port}/api/files/{relative_path})
+ if "/api/files/" in path:
+ path = "output/" + path.split("/api/files/")[-1]
video_segments.append(path)
_export_tasks[task_id]["progress"] = 0.3
diff --git a/api/routers/quality.py b/api/routers/quality.py
index b144128..ee83d83 100644
--- a/api/routers/quality.py
+++ b/api/routers/quality.py
@@ -349,8 +349,9 @@ async def extract_style(
# Convert URL to file path if needed
actual_path = image_path
if image_path.startswith("http"):
- # Extract path from URL like http://localhost:8000/api/files/...
- actual_path = image_path.replace("http://localhost:8000/api/files/", "output/")
+ # Extract path from URL (format: http://localhost:{port}/api/files/{relative_path})
+ if "/api/files/" in image_path:
+ actual_path = "output/" + image_path.split("/api/files/")[-1]
# Check if file exists
import os
diff --git a/dev.sh b/dev.sh
index 55c83b3..028b32f 100755
--- a/dev.sh
+++ b/dev.sh
@@ -54,7 +54,7 @@ print_banner() {
start_api() {
echo -e "${GREEN}🚀 Starting FastAPI Backend...${NC}"
- uv run python api/app.py --port $API_PORT --reload &
+ API_PORT=$API_PORT uv run python api/app.py --port $API_PORT --reload &
echo $! > "$PID_DIR/api.pid"
echo -e " ${GREEN}✓${NC} API running at: ${YELLOW}http://localhost:$API_PORT${NC}"
echo -e " ${GREEN}✓${NC} API Docs at: ${YELLOW}http://localhost:$API_PORT/docs${NC}"
@@ -63,7 +63,7 @@ start_api() {
start_editor() {
echo -e "${GREEN}🎬 Starting Next.js Editor...${NC}"
cd "$PROJECT_ROOT/frontend"
- PORT=$EDITOR_PORT npm run dev &
+ API_PORT=$API_PORT PORT=$EDITOR_PORT npm run dev &
echo $! > "$PID_DIR/editor.pid"
cd "$PROJECT_ROOT"
echo -e " ${GREEN}✓${NC} Editor running at: ${YELLOW}http://localhost:$EDITOR_PORT${NC}"
@@ -71,7 +71,7 @@ start_editor() {
start_web() {
echo -e "${GREEN}🌐 Starting Streamlit Web UI...${NC}"
- uv run streamlit run web/app.py --server.port $WEB_PORT &
+ API_PORT=$API_PORT EDITOR_PORT=$EDITOR_PORT uv run streamlit run web/app.py --server.port $WEB_PORT &
echo $! > "$PID_DIR/web.pid"
echo -e " ${GREEN}✓${NC} Web UI running at: ${YELLOW}http://localhost:$WEB_PORT${NC}"
}
diff --git a/frontend/next.config.ts b/frontend/next.config.ts
index 77b8708..bb744a0 100644
--- a/frontend/next.config.ts
+++ b/frontend/next.config.ts
@@ -2,10 +2,11 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = {
async rewrites() {
+ const apiPort = process.env.API_PORT || '8000';
return [
{
source: '/api/:path*',
- destination: 'http://localhost:8000/api/:path*',
+ destination: `http://localhost:${apiPort}/api/:path*`,
},
]
},
diff --git a/web/components/output_preview.py b/web/components/output_preview.py
index d4a0fc5..5221040 100644
--- a/web/components/output_preview.py
+++ b/web/components/output_preview.py
@@ -26,6 +26,10 @@ from web.utils.async_helpers import run_async
from pixelle_video.models.progress import ProgressEvent
from pixelle_video.config import config_manager
+# Get ports from environment
+API_PORT = os.getenv("API_PORT", "8000")
+EDITOR_PORT = os.getenv("EDITOR_PORT", "3000")
+
def render_output_preview(pixelle_video, video_params):
"""Render output preview section (right column)"""
@@ -135,7 +139,7 @@ def render_single_output(pixelle_video, video_params):
# Submit to async API
response = requests.post(
- "http://localhost:8000/api/video/generate/async",
+ f"http://localhost:{API_PORT}/api/video/generate/async",
json=api_payload,
timeout=30
)
@@ -309,7 +313,7 @@ def render_single_output(pixelle_video, video_params):
pass
if task_id:
- editor_url = f"http://localhost:3000/editor?storyboard_id={task_id}"
+ editor_url = f"http://localhost:{EDITOR_PORT}/editor?storyboard_id={task_id}"
st.markdown(
f'''
diff --git a/web/pages/2_📚_History.py b/web/pages/2_📚_History.py
index f619b65..30e32f8 100644
--- a/web/pages/2_📚_History.py
+++ b/web/pages/2_📚_History.py
@@ -33,6 +33,9 @@ from web.components.header import render_header
from web.i18n import tr
from web.utils.async_helpers import run_async
+# Get ports from environment
+EDITOR_PORT = os.getenv("EDITOR_PORT", "3000")
+
# Page config
st.set_page_config(
page_title="History - Pixelle-Video",
@@ -363,7 +366,7 @@ def render_task_detail_modal(task_id: str, pixelle_video):
)
# Open in Editor button
- editor_url = f"http://localhost:3000/editor?storyboard_id={task_id}"
+ editor_url = f"http://localhost:{EDITOR_PORT}/editor?storyboard_id={task_id}"
st.markdown(
f'''
diff --git a/web/pages/3_📋_Tasks.py b/web/pages/3_📋_Tasks.py
index 956970b..3eaad08 100644
--- a/web/pages/3_📋_Tasks.py
+++ b/web/pages/3_📋_Tasks.py
@@ -22,6 +22,7 @@ Features:
import streamlit as st
import requests
import time
+import os
from datetime import datetime
from web.i18n import tr, get_language
@@ -33,8 +34,12 @@ st.set_page_config(
layout="wide",
)
+# Get ports from environment
+API_PORT = os.getenv("API_PORT", "8000")
+EDITOR_PORT = os.getenv("EDITOR_PORT", "3000")
+
# API endpoint
-API_BASE = "http://localhost:8000/api"
+API_BASE = f"http://localhost:{API_PORT}/api"
def get_all_tasks():
@@ -183,7 +188,7 @@ def render_task_card(task):
with col_a:
st.success("✨ 视频生成成功")
with col_b:
- editor_url = f"http://localhost:3000/editor?storyboard_id={task_id}"
+ editor_url = f"http://localhost:{EDITOR_PORT}/editor?storyboard_id={task_id}"
st.markdown(
f'''