Compare commits
3 Commits
26bd158c47
...
c0a3901951
| Author | SHA1 | Date | |
|---|---|---|---|
| c0a3901951 | |||
| 318741d8ca | |||
| 022baa407f |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -17,7 +17,8 @@ env/
|
|||||||
*.pot
|
*.pot
|
||||||
*.pyc
|
*.pyc
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
media/
|
media/*
|
||||||
|
!media/.gitkeep
|
||||||
|
|
||||||
# Node
|
# Node
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ def system_status():
|
|||||||
def worker_status():
|
def worker_status():
|
||||||
"""Worker status from gRPC."""
|
"""Worker status from gRPC."""
|
||||||
try:
|
try:
|
||||||
from mpr.grpc.client import get_client
|
from grpc.client import get_client
|
||||||
|
|
||||||
client = get_client()
|
client = get_client()
|
||||||
status = client.get_worker_status()
|
status = client.get_worker_status()
|
||||||
|
|||||||
@@ -26,3 +26,6 @@ MPR_EXECUTOR=local
|
|||||||
GRPC_HOST=grpc
|
GRPC_HOST=grpc
|
||||||
GRPC_PORT=50051
|
GRPC_PORT=50051
|
||||||
GRPC_MAX_WORKERS=10
|
GRPC_MAX_WORKERS=10
|
||||||
|
|
||||||
|
# Vite
|
||||||
|
VITE_ALLOWED_HOSTS=your-domain.local
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ..
|
context: ..
|
||||||
dockerfile: ctrl/Dockerfile
|
dockerfile: ctrl/Dockerfile
|
||||||
command: python -m mpr.grpc.server
|
command: python -m grpc.server
|
||||||
ports:
|
ports:
|
||||||
- "50052:50051"
|
- "50052:50051"
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -29,18 +29,18 @@ python -m modelgen from-schema \
|
|||||||
# Protobuf for gRPC
|
# Protobuf for gRPC
|
||||||
python -m modelgen from-schema \
|
python -m modelgen from-schema \
|
||||||
--schema schema/models \
|
--schema schema/models \
|
||||||
--output mpr/grpc/protos/worker.proto \
|
--output grpc/protos/worker.proto \
|
||||||
--targets proto
|
--targets proto
|
||||||
|
|
||||||
# Generate gRPC stubs from proto
|
# Generate gRPC stubs from proto
|
||||||
echo "Generating gRPC stubs..."
|
echo "Generating gRPC stubs..."
|
||||||
python -m grpc_tools.protoc \
|
python -m grpc_tools.protoc \
|
||||||
-I mpr/grpc/protos \
|
-I grpc/protos \
|
||||||
--python_out=mpr/grpc \
|
--python_out=grpc \
|
||||||
--grpc_python_out=mpr/grpc \
|
--grpc_python_out=grpc \
|
||||||
mpr/grpc/protos/worker.proto
|
grpc/protos/worker.proto
|
||||||
|
|
||||||
# Fix relative import in generated grpc stub
|
# Fix relative import in generated grpc stub
|
||||||
sed -i 's/^import worker_pb2/from . import worker_pb2/' mpr/grpc/worker_pb2_grpc.py
|
sed -i 's/^import worker_pb2/from . import worker_pb2/' grpc/worker_pb2_grpc.py
|
||||||
|
|
||||||
echo "Done!"
|
echo "Done!"
|
||||||
|
|||||||
0
media/.gitkeep
Normal file
0
media/.gitkeep
Normal file
@@ -1,7 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
Django ORM Models - GENERATED FILE
|
Django ORM Models - GENERATED FILE
|
||||||
|
|
||||||
Do not edit directly. Regenerate using modelgen.
|
Do not edit directly. Modify schema/models/*.py and run:
|
||||||
|
python schema/generate.py --django
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
@@ -12,6 +13,7 @@ class AssetStatus(models.TextChoices):
|
|||||||
READY = "ready", "Ready"
|
READY = "ready", "Ready"
|
||||||
ERROR = "error", "Error"
|
ERROR = "error", "Error"
|
||||||
|
|
||||||
|
|
||||||
class JobStatus(models.TextChoices):
|
class JobStatus(models.TextChoices):
|
||||||
PENDING = "pending", "Pending"
|
PENDING = "pending", "Pending"
|
||||||
PROCESSING = "processing", "Processing"
|
PROCESSING = "processing", "Processing"
|
||||||
@@ -19,13 +21,14 @@ class JobStatus(models.TextChoices):
|
|||||||
FAILED = "failed", "Failed"
|
FAILED = "failed", "Failed"
|
||||||
CANCELLED = "cancelled", "Cancelled"
|
CANCELLED = "cancelled", "Cancelled"
|
||||||
|
|
||||||
|
|
||||||
class MediaAsset(models.Model):
|
class MediaAsset(models.Model):
|
||||||
"""A video/audio file registered in the system."""
|
"""A video/audio file registered in the system."""
|
||||||
|
|
||||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
filename = models.CharField(max_length=500)
|
filename = models.CharField(max_length=500)
|
||||||
file_path = models.CharField(max_length=1000)
|
file_path = models.CharField(max_length=1000)
|
||||||
status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING)
|
status = models.CharField(max_length=20, choices=AssetStatus.choices, default=AssetStatus.PENDING)
|
||||||
error_message = models.TextField(blank=True, default='')
|
error_message = models.TextField(blank=True, default='')
|
||||||
file_size = models.BigIntegerField(null=True, blank=True)
|
file_size = models.BigIntegerField(null=True, blank=True)
|
||||||
duration = models.FloatField(null=True, blank=True, default=None)
|
duration = models.FloatField(null=True, blank=True, default=None)
|
||||||
@@ -89,7 +92,7 @@ class TranscodeJob(models.Model):
|
|||||||
output_filename = models.CharField(max_length=500)
|
output_filename = models.CharField(max_length=500)
|
||||||
output_path = models.CharField(max_length=1000, null=True, blank=True)
|
output_path = models.CharField(max_length=1000, null=True, blank=True)
|
||||||
output_asset_id = models.UUIDField(null=True, blank=True)
|
output_asset_id = models.UUIDField(null=True, blank=True)
|
||||||
status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING)
|
status = models.CharField(max_length=20, choices=JobStatus.choices, default=JobStatus.PENDING)
|
||||||
progress = models.FloatField(default=0.0)
|
progress = models.FloatField(default=0.0)
|
||||||
current_frame = models.IntegerField(null=True, blank=True, default=None)
|
current_frame = models.IntegerField(null=True, blank=True, default=None)
|
||||||
current_time = models.FloatField(null=True, blank=True, default=None)
|
current_time = models.FloatField(null=True, blank=True, default=None)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ DJANGO_TYPES: dict[Any, str] = {
|
|||||||
"list": "models.JSONField(default=list, blank=True)",
|
"list": "models.JSONField(default=list, blank=True)",
|
||||||
"text": "models.TextField(blank=True, default='')",
|
"text": "models.TextField(blank=True, default='')",
|
||||||
"bigint": "models.BigIntegerField({opts})",
|
"bigint": "models.BigIntegerField({opts})",
|
||||||
"enum": "models.CharField(max_length=20, choices=Status.choices{opts})",
|
"enum": "models.CharField(max_length=20, choices={enum_name}.choices{opts})",
|
||||||
}
|
}
|
||||||
|
|
||||||
DJANGO_SPECIAL: dict[str, str] = {
|
DJANGO_SPECIAL: dict[str, str] = {
|
||||||
@@ -69,9 +69,11 @@ TS_RESOLVERS: dict[Any, Callable[[Any], str]] = {
|
|||||||
"UUID": lambda _: "string",
|
"UUID": lambda _: "string",
|
||||||
"datetime": lambda _: "string",
|
"datetime": lambda _: "string",
|
||||||
"dict": lambda _: "Record<string, unknown>",
|
"dict": lambda _: "Record<string, unknown>",
|
||||||
"list": lambda base: f"{TS_RESOLVERS.get(get_args(base)[0], lambda _: 'string')(None)}[]"
|
"list": lambda base: (
|
||||||
if get_args(base)
|
f"{TS_RESOLVERS.get(get_args(base)[0], lambda _: 'string')(None)}[]"
|
||||||
else "string[]",
|
if get_args(base)
|
||||||
|
else "string[]"
|
||||||
|
),
|
||||||
"enum": lambda base: base.__name__,
|
"enum": lambda base: base.__name__,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,9 +82,11 @@ PROTO_RESOLVERS: dict[Any, Callable[[Any], str]] = {
|
|||||||
int: lambda _: "int32",
|
int: lambda _: "int32",
|
||||||
float: lambda _: "float",
|
float: lambda _: "float",
|
||||||
bool: lambda _: "bool",
|
bool: lambda _: "bool",
|
||||||
"list": lambda base: f"repeated {PROTO_RESOLVERS.get(get_args(base)[0], lambda _: 'string')(None)}"
|
"list": lambda base: (
|
||||||
if get_args(base)
|
f"repeated {PROTO_RESOLVERS.get(get_args(base)[0], lambda _: 'string')(None)}"
|
||||||
else "repeated string",
|
if get_args(base)
|
||||||
|
else "repeated string"
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -172,13 +176,14 @@ def resolve_django_type(name: str, type_hint: Any, default: Any) -> str:
|
|||||||
|
|
||||||
# Enum
|
# Enum
|
||||||
if isinstance(base, type) and issubclass(base, Enum):
|
if isinstance(base, type) and issubclass(base, Enum):
|
||||||
|
enum_name = base.__name__
|
||||||
extra = []
|
extra = []
|
||||||
if optional:
|
if optional:
|
||||||
extra.append("null=True, blank=True")
|
extra.append("null=True, blank=True")
|
||||||
if default is not dc.MISSING and isinstance(default, Enum):
|
if default is not dc.MISSING and isinstance(default, Enum):
|
||||||
extra.append(f"default=Status.{default.name}")
|
extra.append(f"default={enum_name}.{default.name}")
|
||||||
return DJANGO_TYPES["enum"].format(
|
return DJANGO_TYPES["enum"].format(
|
||||||
opts=", " + ", ".join(extra) if extra else ""
|
enum_name=enum_name, opts=", " + ", ".join(extra) if extra else ""
|
||||||
)
|
)
|
||||||
|
|
||||||
# Text fields
|
# Text fields
|
||||||
@@ -216,6 +221,15 @@ def resolve_django_type(name: str, type_hint: Any, default: Any) -> str:
|
|||||||
return DJANGO_TYPES[str].format(max_length=255, opts=", " + opts if opts else "")
|
return DJANGO_TYPES[str].format(max_length=255, opts=", " + opts if opts else "")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_django_enum(enum_cls: type) -> list[str]:
|
||||||
|
"""Generate Django TextChoices enum."""
|
||||||
|
lines = [f"class {enum_cls.__name__}(models.TextChoices):"]
|
||||||
|
for member in enum_cls:
|
||||||
|
label = member.name.replace("_", " ").title()
|
||||||
|
lines.append(f' {member.name} = "{member.value}", "{label}"')
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
def generate_django_model(cls: type) -> list[str]:
|
def generate_django_model(cls: type) -> list[str]:
|
||||||
"""Generate Django model lines from dataclass."""
|
"""Generate Django model lines from dataclass."""
|
||||||
lines = [
|
lines = [
|
||||||
@@ -227,17 +241,6 @@ def generate_django_model(cls: type) -> list[str]:
|
|||||||
hints = get_type_hints(cls)
|
hints = get_type_hints(cls)
|
||||||
fields = {f.name: f for f in dc.fields(cls)}
|
fields = {f.name: f for f in dc.fields(cls)}
|
||||||
|
|
||||||
# Add Status inner class for enum fields
|
|
||||||
for type_hint in hints.values():
|
|
||||||
base, _ = unwrap_optional(type_hint)
|
|
||||||
if isinstance(base, type) and issubclass(base, Enum):
|
|
||||||
lines.append(" class Status(models.TextChoices):")
|
|
||||||
for member in base:
|
|
||||||
label = member.name.replace("_", " ").title()
|
|
||||||
lines.append(f' {member.name} = "{member.value}", "{label}"')
|
|
||||||
lines.append("")
|
|
||||||
break
|
|
||||||
|
|
||||||
# Fields
|
# Fields
|
||||||
for name, type_hint in hints.items():
|
for name, type_hint in hints.items():
|
||||||
if name.startswith("_"):
|
if name.startswith("_"):
|
||||||
@@ -283,7 +286,13 @@ def generate_django() -> str:
|
|||||||
"",
|
"",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Generate enums first
|
||||||
body = []
|
body = []
|
||||||
|
for enum_cls in ENUMS:
|
||||||
|
body.extend(generate_django_enum(enum_cls))
|
||||||
|
body.extend(["", ""])
|
||||||
|
|
||||||
|
# Generate models
|
||||||
for cls in DATACLASSES:
|
for cls in DATACLASSES:
|
||||||
body.extend(generate_django_model(cls))
|
body.extend(generate_django_model(cls))
|
||||||
body.extend(["", ""])
|
body.extend(["", ""])
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
host: "0.0.0.0",
|
host: "0.0.0.0",
|
||||||
port: 5173,
|
port: 5173,
|
||||||
|
allowedHosts: process.env.VITE_ALLOWED_HOSTS?.split(",") || [],
|
||||||
proxy: {
|
proxy: {
|
||||||
"/api": {
|
"/api": {
|
||||||
target: "http://fastapi:8702",
|
target: "http://fastapi:8702",
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ from typing import Any, Dict, Optional
|
|||||||
|
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
|
||||||
|
from grpc.server import update_job_progress
|
||||||
from worker.executor import get_executor
|
from worker.executor import get_executor
|
||||||
from worker_grpc.server import update_job_progress
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user