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:
1
app/models/__init__.py
Normal file
1
app/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# models package
|
||||
103
app/models/schemas.py
Normal file
103
app/models/schemas.py
Normal 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
116
app/models/video.py
Normal 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")
|
||||
Reference in New Issue
Block a user