Initial commit: Video metadata registration service

- Add FastAPI server for video metadata registration
- PostgreSQL database models for videos, video_streams, audio_streams, subtitle_streams
- Batch registration script for .probe.json files
- RESTful API endpoints for CRUD operations
- Search functionality by title, artist, codec, resolution
This commit is contained in:
accusys
2026-03-11 00:30:31 +08:00
commit b4aa7b96d3
16 changed files with 709 additions and 0 deletions

1
app/models/__init__.py Normal file
View File

@@ -0,0 +1 @@
# models package

103
app/models/schemas.py Normal file
View File

@@ -0,0 +1,103 @@
from datetime import datetime
from uuid import UUID
from typing import Optional, List
from pydantic import BaseModel, ConfigDict
class VideoStreamSchema(BaseModel):
model_config = ConfigDict(from_attributes=True)
stream_index: Optional[int] = None
codec_name: Optional[str] = None
codec_long_name: Optional[str] = None
profile: Optional[str] = None
level: Optional[int] = None
width: Optional[int] = None
height: Optional[int] = None
coded_width: Optional[int] = None
coded_height: Optional[int] = None
aspect_ratio: Optional[str] = None
pix_fmt: Optional[str] = None
field_order: Optional[str] = None
frame_rate: Optional[str] = None
start_time: Optional[float] = None
duration: Optional[float] = None
bit_rate: Optional[int] = None
nb_frames: Optional[int] = None
color_range: Optional[str] = None
color_space: Optional[str] = None
has_b_frames: Optional[int] = None
sample_aspect_ratio: Optional[str] = None
class AudioStreamSchema(BaseModel):
model_config = ConfigDict(from_attributes=True)
stream_index: Optional[int] = None
codec_name: Optional[str] = None
codec_long_name: Optional[str] = None
profile: Optional[str] = None
channels: Optional[int] = None
channel_layout: Optional[str] = None
sample_rate: Optional[int] = None
sample_fmt: Optional[str] = None
bit_rate: Optional[int] = None
duration: Optional[float] = None
language: Optional[str] = None
class SubtitleStreamSchema(BaseModel):
model_config = ConfigDict(from_attributes=True)
stream_index: Optional[int] = None
codec_name: Optional[str] = None
language: Optional[str] = None
class VideoSchema(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: UUID
file_path: str
file_name: str
file_extension: Optional[str] = None
file_size: Optional[int] = None
format_name: Optional[str] = None
format_long_name: Optional[str] = None
duration: Optional[float] = None
bit_rate: Optional[int] = None
nb_streams: Optional[int] = None
start_time: Optional[float] = None
title: Optional[str] = None
artist: Optional[str] = None
description: Optional[str] = None
probed_at: Optional[datetime] = None
registered_at: Optional[datetime] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
video_streams: List[VideoStreamSchema] = []
audio_streams: List[AudioStreamSchema] = []
subtitle_streams: List[SubtitleStreamSchema] = []
class VideoListResponse(BaseModel):
total: int
videos: List[VideoSchema]
class VideoSearchQuery(BaseModel):
title: Optional[str] = None
artist: Optional[str] = None
codec_name: Optional[str] = None
min_width: Optional[int] = None
max_width: Optional[int] = None
min_height: Optional[int] = None
max_height: Optional[int] = None
format_name: Optional[str] = None
skip: int = 0
limit: int = 20
class RegisterRequest(BaseModel):
probe_json_path: str
absolute_file_path: str

116
app/models/video.py Normal file
View File

@@ -0,0 +1,116 @@
import uuid
from datetime import datetime
from sqlalchemy import (
Column,
String,
Integer,
BigInteger,
Float,
Text,
ForeignKey,
DateTime,
)
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from app.database import Base
class Video(Base):
__tablename__ = "videos"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
file_path = Column(String(512), unique=True, nullable=False)
file_name = Column(String(255), nullable=False)
file_extension = Column(String(10))
file_size = Column(BigInteger)
format_name = Column(String(50))
format_long_name = Column(String(100))
duration = Column(Float)
bit_rate = Column(BigInteger)
nb_streams = Column(Integer)
start_time = Column(Float, default=0)
title = Column(String(255))
artist = Column(String(255))
description = Column(Text)
probed_at = Column(DateTime)
registered_at = Column(DateTime, default=datetime.utcnow)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
video_streams = relationship(
"VideoStream", back_populates="video", cascade="all, delete-orphan"
)
audio_streams = relationship(
"AudioStream", back_populates="video", cascade="all, delete-orphan"
)
subtitle_streams = relationship(
"SubtitleStream", back_populates="video", cascade="all, delete-orphan"
)
class VideoStream(Base):
__tablename__ = "video_streams"
id = Column(Integer, primary_key=True, autoincrement=True)
video_id = Column(
UUID(as_uuid=True), ForeignKey("videos.id", ondelete="CASCADE"), nullable=False
)
stream_index = Column(Integer)
codec_name = Column(String(30))
codec_long_name = Column(String(100))
profile = Column(String(30))
level = Column(Integer)
width = Column(Integer)
height = Column(Integer)
coded_width = Column(Integer)
coded_height = Column(Integer)
aspect_ratio = Column(String(20))
pix_fmt = Column(String(30))
field_order = Column(String(20))
frame_rate = Column(String(20))
start_time = Column(Float)
duration = Column(Float)
bit_rate = Column(BigInteger)
nb_frames = Column(BigInteger)
color_range = Column(String(10))
color_space = Column(String(20))
has_b_frames = Column(Integer)
sample_aspect_ratio = Column(String(20))
video = relationship("Video", back_populates="video_streams")
class AudioStream(Base):
__tablename__ = "audio_streams"
id = Column(Integer, primary_key=True, autoincrement=True)
video_id = Column(
UUID(as_uuid=True), ForeignKey("videos.id", ondelete="CASCADE"), nullable=False
)
stream_index = Column(Integer)
codec_name = Column(String(30))
codec_long_name = Column(String(100))
profile = Column(String(30))
channels = Column(Integer)
channel_layout = Column(String(30))
sample_rate = Column(Integer)
sample_fmt = Column(String(20))
bit_rate = Column(BigInteger)
duration = Column(Float)
language = Column(String(10))
video = relationship("Video", back_populates="audio_streams")
class SubtitleStream(Base):
__tablename__ = "subtitle_streams"
id = Column(Integer, primary_key=True, autoincrement=True)
video_id = Column(
UUID(as_uuid=True), ForeignKey("videos.id", ondelete="CASCADE"), nullable=False
)
stream_index = Column(Integer)
codec_name = Column(String(30))
language = Column(String(10))
video = relationship("Video", back_populates="subtitle_streams")