feat: Add video count to person API and frontend for enhanced media management

This commit introduces a new `video_count` field in the `PersonWithFaces` interface and updates the API to return video counts alongside face counts. The frontend has been modified to display video counts in the people list and includes functionality for selecting and unmatching videos. Additionally, the layout has been enhanced to support resizing of the people panel, improving user experience when managing faces and videos. Documentation has been updated to reflect these changes.
This commit is contained in:
tanyar09
2025-12-02 16:03:15 -05:00
parent 9d40f9772e
commit e0e5aae2ff
11 changed files with 327 additions and 162 deletions
+23 -3
View File
@@ -50,16 +50,16 @@ def list_people_with_faces(
last_name: str | None = Query(None, description="Filter by last name or maiden name (case-insensitive)"),
db: Session = Depends(get_db),
) -> PeopleWithFacesListResponse:
"""List all people with face counts, sorted by last_name, first_name.
"""List all people with face counts and video counts, sorted by last_name, first_name.
Optionally filter by last_name or maiden_name if provided (case-insensitive search).
Returns all people, including those with zero faces.
Returns all people, including those with zero faces or videos.
"""
# Query people with face counts using LEFT OUTER JOIN to include people with no faces
query = (
db.query(
Person,
func.count(Face.id).label('face_count')
func.count(Face.id.distinct()).label('face_count')
)
.outerjoin(Face, Person.id == Face.person_id)
.group_by(Person.id)
@@ -75,6 +75,25 @@ def list_people_with_faces(
results = query.order_by(Person.last_name.asc(), Person.first_name.asc()).all()
# Get video counts separately for each person
person_ids = [person.id for person, _ in results]
video_counts = {}
if person_ids:
video_count_query = (
db.query(
PhotoPersonLinkage.person_id,
func.count(PhotoPersonLinkage.id).label('video_count')
)
.join(Photo, PhotoPersonLinkage.photo_id == Photo.id)
.filter(
PhotoPersonLinkage.person_id.in_(person_ids),
Photo.media_type == "video"
)
.group_by(PhotoPersonLinkage.person_id)
)
for person_id, video_count in video_count_query.all():
video_counts[person_id] = video_count
items = [
PersonWithFacesResponse(
id=person.id,
@@ -84,6 +103,7 @@ def list_people_with_faces(
maiden_name=person.maiden_name,
date_of_birth=person.date_of_birth,
face_count=face_count or 0, # Convert None to 0 for people with no faces
video_count=video_counts.get(person.id, 0), # Get video count or default to 0
)
for person, face_count in results
]
+1
View File
@@ -66,3 +66,4 @@ def update_role_permissions(
features = [RoleFeatureSchema(**feature) for feature in ROLE_FEATURES]
return RolePermissionsResponse(features=features, permissions=permissions)
+1
View File
@@ -335,3 +335,4 @@ def get_video_file(
response.headers["Cache-Control"] = "public, max-age=3600"
return response
+1
View File
@@ -66,6 +66,7 @@ class PersonWithFacesResponse(BaseModel):
maiden_name: Optional[str] = None
date_of_birth: Optional[date] = None
face_count: int
video_count: int
class PeopleWithFacesListResponse(BaseModel):
+1
View File
@@ -40,3 +40,4 @@ class RolePermissionsUpdateRequest(BaseModel):
def build_feature_list() -> list[RoleFeatureSchema]:
return [RoleFeatureSchema(**feature) for feature in ROLE_FEATURES]
+1
View File
@@ -88,3 +88,4 @@ class RemoveVideoPersonResponse(BaseModel):
removed: bool
message: str