Coverage for /home/admin/Documents/AI/applications/lexigram-dev/lexigram/experimental/ai/lexigram-ai-rag/src/lexigram/ai/rag/multimodal/types/video.py: 81%

59 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-25 07:19 +0800

1"""Video-related document types.""" 

2 

3from __future__ import annotations 

4 

5from dataclasses import dataclass 

6from pathlib import Path 

7from typing import TYPE_CHECKING, Any 

8 

9from lexigram.domain import DomainModel 

10from lexigram.validation import Field, field_validator 

11 

12if TYPE_CHECKING: 

13 from datetime import datetime 

14 

15 from lexigram.ai.rag.multimodal.types.audio import AudioDocument 

16 from lexigram.ai.rag.multimodal.types.enums import VideoFormat 

17 from lexigram.ai.rag.multimodal.types.image import ImageDocument 

18 

19 

20@dataclass(init=False) 

21class VideoMetadata(DomainModel): 

22 """Metadata for video documents.""" 

23 

24 title: str | None = None 

25 description: str | None = None 

26 author: str | None = None 

27 created_at: datetime | None = None 

28 modified_at: datetime | None = None 

29 tags: list[str] = Field(default_factory=list) 

30 chapters: list[dict[str, Any]] = Field(default_factory=list) 

31 codec: str | None = None 

32 bitrate: int | None = None 

33 

34 model_config = { 

35 "json_schema_extra": { 

36 "example": { 

37 "title": "Product Demo", 

38 "tags": ["demo", "product"], 

39 "chapters": [{"time": 0, "title": "Introduction"}], 

40 }, 

41 }, 

42 } 

43 

44 

45@dataclass(init=False) 

46class VideoDocument(DomainModel): 

47 """Document representing video content.""" 

48 

49 content: bytes | str 

50 format: VideoFormat 

51 duration: float = Field(gt=0) 

52 fps: float = Field(gt=0) 

53 width: int = Field(gt=0) 

54 height: int = Field(gt=0) 

55 metadata: VideoMetadata = Field(default_factory=VideoMetadata) 

56 frames: list[ImageDocument] = Field(default_factory=list) 

57 audio_track: AudioDocument | None = None 

58 transcript: str | None = None 

59 embedding: list[float] | None = None 

60 file_path: Path | None = None 

61 

62 @field_validator("content") 

63 @classmethod 

64 def validate_content(cls, v: bytes | str) -> bytes | str: 

65 if isinstance(v, str): 

66 path = Path(v) 

67 if not path.exists(): 

68 raise ValueError(f"File path does not exist: {v}") 

69 return v 

70 

71 @property 

72 def aspect_ratio(self) -> float: 

73 return self.width / self.height 

74 

75 @property 

76 def total_frames(self) -> int: 

77 return int(self.duration * self.fps) 

78 

79 @property 

80 def has_audio(self) -> bool: 

81 return self.audio_track is not None 

82 

83 @property 

84 def has_transcript(self) -> bool: 

85 return self.transcript is not None and len(self.transcript) > 0 

86 

87 @property 

88 def has_frames(self) -> bool: 

89 return len(self.frames) > 0 

90 

91 @property 

92 def has_embedding(self) -> bool: 

93 return self.embedding is not None and len(self.embedding) > 0 

94 

95 model_config = { 

96 "arbitrary_types_allowed": True, 

97 "json_schema_extra": { 

98 "example": { 

99 "content": "/path/to/video.mp4", 

100 "format": "mp4", 

101 "duration": 300.0, 

102 "fps": 30.0, 

103 "width": 1920, 

104 "height": 1080, 

105 "metadata": {"title": "Tutorial Video"}, 

106 }, 

107 }, 

108 }