endoreg-db 0.8.1__py3-none-any.whl → 0.8.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of endoreg-db might be problematic. Click here for more details.

Files changed (48) hide show
  1. endoreg_db/helpers/download_segmentation_model.py +31 -0
  2. endoreg_db/migrations/0003_add_center_display_name.py +30 -0
  3. endoreg_db/models/administration/center/center.py +7 -1
  4. endoreg_db/models/media/pdf/raw_pdf.py +31 -26
  5. endoreg_db/models/media/video/create_from_file.py +26 -4
  6. endoreg_db/models/media/video/pipe_1.py +13 -1
  7. endoreg_db/models/media/video/video_file.py +36 -13
  8. endoreg_db/models/media/video/video_file_anonymize.py +2 -1
  9. endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +12 -0
  10. endoreg_db/models/media/video/video_file_io.py +4 -2
  11. endoreg_db/models/metadata/video_meta.py +2 -2
  12. endoreg_db/serializers/anonymization.py +3 -0
  13. endoreg_db/services/pdf_import.py +131 -45
  14. endoreg_db/services/video_import.py +427 -128
  15. endoreg_db/urls/__init__.py +0 -2
  16. endoreg_db/urls/media.py +201 -4
  17. endoreg_db/urls/report.py +0 -30
  18. endoreg_db/urls/sensitive_meta.py +0 -36
  19. endoreg_db/urls/video.py +30 -88
  20. endoreg_db/utils/paths.py +2 -10
  21. endoreg_db/utils/video/ffmpeg_wrapper.py +67 -4
  22. endoreg_db/views/anonymization/validate.py +76 -32
  23. endoreg_db/views/media/__init__.py +38 -2
  24. endoreg_db/views/media/pdf_media.py +1 -1
  25. endoreg_db/views/media/segments.py +71 -0
  26. endoreg_db/views/media/sensitive_metadata.py +314 -0
  27. endoreg_db/views/media/video_segments.py +596 -0
  28. endoreg_db/views/pdf/reimport.py +18 -8
  29. endoreg_db/views/video/__init__.py +0 -8
  30. endoreg_db/views/video/correction.py +34 -32
  31. endoreg_db/views/video/reimport.py +15 -12
  32. endoreg_db/views/video/video_stream.py +168 -50
  33. {endoreg_db-0.8.1.dist-info → endoreg_db-0.8.2.1.dist-info}/METADATA +2 -2
  34. {endoreg_db-0.8.1.dist-info → endoreg_db-0.8.2.1.dist-info}/RECORD +47 -43
  35. endoreg_db/views/video/media/__init__.py +0 -23
  36. /endoreg_db/{urls/pdf.py → config/__init__.py} +0 -0
  37. /endoreg_db/views/video/{media/task_status.py → task_status.py} +0 -0
  38. /endoreg_db/views/video/{media/video_analyze.py → video_analyze.py} +0 -0
  39. /endoreg_db/views/video/{media/video_apply_mask.py → video_apply_mask.py} +0 -0
  40. /endoreg_db/views/video/{media/video_correction.py → video_correction.py} +0 -0
  41. /endoreg_db/views/video/{media/video_download_processed.py → video_download_processed.py} +0 -0
  42. /endoreg_db/views/video/{media/video_media.py → video_media.py} +0 -0
  43. /endoreg_db/views/video/{media/video_meta.py → video_meta.py} +0 -0
  44. /endoreg_db/views/video/{media/video_processing_history.py → video_processing_history.py} +0 -0
  45. /endoreg_db/views/video/{media/video_remove_frames.py → video_remove_frames.py} +0 -0
  46. /endoreg_db/views/video/{media/video_reprocess.py → video_reprocess.py} +0 -0
  47. {endoreg_db-0.8.1.dist-info → endoreg_db-0.8.2.1.dist-info}/WHEEL +0 -0
  48. {endoreg_db-0.8.1.dist-info → endoreg_db-0.8.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,596 @@
1
+ """
2
+ Modern Media Framework - Video Segments Views
3
+ Migrated from legacy label_video_segment views (October 14, 2025)
4
+
5
+ Provides RESTful endpoints for video segment management:
6
+ - Collection: GET/POST /api/media/videos/segments/
7
+ - Detail: GET/PATCH/DELETE /api/media/videos/<pk>/segments/<segment_id>/
8
+ - Video-specific: GET/POST /api/media/videos/<pk>/segments/
9
+ """
10
+
11
+ from endoreg_db.models import Label, LabelVideoSegment, VideoFile
12
+ from endoreg_db.serializers.label_video_segment.label_video_segment import LabelVideoSegmentSerializer
13
+
14
+ from django.db import transaction
15
+ from django.db.models import Count
16
+ from django.shortcuts import get_object_or_404
17
+ from rest_framework import status
18
+ from rest_framework.decorators import api_view, permission_classes
19
+ from rest_framework.response import Response
20
+
21
+ from endoreg_db.utils.permissions import EnvironmentAwarePermission
22
+
23
+ import logging
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ @api_view(['GET'])
28
+ @permission_classes([EnvironmentAwarePermission])
29
+ def video_segments_stats(request):
30
+ """
31
+ Statistics endpoint for video segments.
32
+
33
+ GET /api/media/videos/segments/stats/
34
+ Returns aggregated statistics about video segments.
35
+ """
36
+ try:
37
+ # Get all segments queryset
38
+ segments = LabelVideoSegment.objects.all()
39
+
40
+ # Calculate statistics
41
+ total_segments = segments.count()
42
+
43
+ # Segments by status (assuming status field exists)
44
+ status_counts = segments.values('status').annotate(count=Count('id'))
45
+
46
+ # Segments by label
47
+ label_counts = segments.values('label__name').annotate(count=Count('id'))
48
+
49
+ # Videos with segments
50
+ videos_with_segments = segments.values('video_id').distinct().count()
51
+
52
+ stats = {
53
+ 'total_segments': total_segments,
54
+ 'videos_with_segments': videos_with_segments,
55
+ 'by_status': {item['status']: item['count'] for item in status_counts if item['status']},
56
+ 'by_label': {item['label__name']: item['count'] for item in label_counts if item['label__name']},
57
+ }
58
+
59
+ return Response(stats, status=status.HTTP_200_OK)
60
+
61
+ except Exception as e:
62
+ logger.error(f"Error fetching video segment stats: {e}")
63
+ return Response(
64
+ {'error': 'Failed to fetch segment statistics'},
65
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
66
+ )
67
+
68
+
69
+ @api_view(['GET', 'POST'])
70
+ @permission_classes([EnvironmentAwarePermission])
71
+ def video_segments_collection(request):
72
+ """
73
+ Collection endpoint for all video segments across all videos.
74
+
75
+ GET /api/media/videos/segments/
76
+ - Lists all segments, optionally filtered by video_id and/or label_id
77
+ - Query params: video_id, label_id
78
+
79
+ POST /api/media/videos/segments/
80
+ - Creates a new video segment
81
+ - Requires: video_id, label_id, start_frame_number, end_frame_number
82
+
83
+ Modern replacement for: /api/video-segments/
84
+ """
85
+ if request.method == 'POST':
86
+ logger.info(f"Creating new video segment with data: {request.data}")
87
+
88
+ with transaction.atomic():
89
+ serializer = LabelVideoSegmentSerializer(data=request.data)
90
+ if serializer.is_valid():
91
+ try:
92
+ segment = serializer.save()
93
+ logger.info(f"Successfully created video segment {segment.pk}")
94
+ return Response(
95
+ LabelVideoSegmentSerializer(segment).data,
96
+ status=status.HTTP_201_CREATED
97
+ )
98
+ except Exception as e:
99
+ logger.error(f"Error creating video segment: {str(e)}")
100
+ return Response(
101
+ {'error': f'Failed to create segment: {str(e)}'},
102
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
103
+ )
104
+ else:
105
+ logger.warning(f"Invalid data for video segment creation: {serializer.errors}")
106
+ return Response(
107
+ {'error': 'Invalid data', 'details': serializer.errors},
108
+ status=status.HTTP_400_BAD_REQUEST
109
+ )
110
+
111
+ elif request.method == 'GET':
112
+ # Optional filtering by video_id
113
+ video_id = request.GET.get('video_id')
114
+ label_id = request.GET.get('label_id')
115
+
116
+ queryset = LabelVideoSegment.objects.all()
117
+
118
+ if video_id:
119
+ try:
120
+ video = VideoFile.objects.get(id=video_id)
121
+ queryset = queryset.filter(video_file=video)
122
+ except VideoFile.DoesNotExist:
123
+ return Response(
124
+ {'error': f'Video with id {video_id} not found'},
125
+ status=status.HTTP_404_NOT_FOUND
126
+ )
127
+
128
+ if label_id:
129
+ try:
130
+ label = Label.objects.get(id=label_id)
131
+ queryset = queryset.filter(label=label)
132
+ except Label.DoesNotExist:
133
+ return Response(
134
+ {'error': f'Label with id {label_id} not found'},
135
+ status=status.HTTP_404_NOT_FOUND
136
+ )
137
+
138
+ # Order by video and start time for consistent results
139
+ segments = queryset.order_by('video_file__id', 'start_frame_number')
140
+ serializer = LabelVideoSegmentSerializer(segments, many=True)
141
+ return Response(serializer.data)
142
+
143
+
144
+ @api_view(['GET', 'POST'])
145
+ @permission_classes([EnvironmentAwarePermission])
146
+ def video_segments_by_video(request, pk):
147
+ """
148
+ Video-specific segments endpoint.
149
+
150
+ GET /api/media/videos/<pk>/segments/
151
+ - Lists all segments for a specific video
152
+ - Query params: label (label name filter)
153
+ - Note: This was already implemented in segments.py as video_segments_by_pk
154
+
155
+ POST /api/media/videos/<pk>/segments/
156
+ - Creates a new segment for this video
157
+ - Automatically sets video_id to pk
158
+ - Requires: label_id, start_frame_number, end_frame_number
159
+
160
+ Modern replacement for: /api/video-segments/?video_id=<pk>
161
+ """
162
+ # Verify video exists
163
+ video = get_object_or_404(VideoFile, id=pk)
164
+
165
+ if request.method == 'GET':
166
+ # This duplicates video_segments_by_pk functionality
167
+ # We keep both for compatibility during migration
168
+ label_name = request.GET.get('label')
169
+
170
+ queryset = LabelVideoSegment.objects.filter(video_file=video)
171
+
172
+ if label_name:
173
+ try:
174
+ label = Label.objects.get(name=label_name)
175
+ queryset = queryset.filter(label=label)
176
+ except Label.DoesNotExist:
177
+ return Response(
178
+ {'error': f'Label "{label_name}" not found'},
179
+ status=status.HTTP_404_NOT_FOUND
180
+ )
181
+
182
+ segments = queryset.order_by('start_frame_number')
183
+ serializer = LabelVideoSegmentSerializer(segments, many=True)
184
+ return Response(serializer.data)
185
+
186
+ elif request.method == 'POST':
187
+ logger.info(f"Creating new segment for video {pk} with data: {request.data}")
188
+
189
+ # Automatically set video_id to pk
190
+ data = request.data.copy()
191
+ data['video_id'] = pk
192
+
193
+ with transaction.atomic():
194
+ serializer = LabelVideoSegmentSerializer(data=data)
195
+ if serializer.is_valid():
196
+ try:
197
+ segment = serializer.save()
198
+ logger.info(f"Successfully created segment {segment.pk} for video {pk}")
199
+ return Response(
200
+ LabelVideoSegmentSerializer(segment).data,
201
+ status=status.HTTP_201_CREATED
202
+ )
203
+ except Exception as e:
204
+ logger.error(f"Error creating segment for video {pk}: {str(e)}")
205
+ return Response(
206
+ {'error': f'Failed to create segment: {str(e)}'},
207
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
208
+ )
209
+ else:
210
+ logger.warning(f"Invalid data for segment creation: {serializer.errors}")
211
+ return Response(
212
+ {'error': 'Invalid data', 'details': serializer.errors},
213
+ status=status.HTTP_400_BAD_REQUEST
214
+ )
215
+
216
+
217
+ @api_view(['GET', 'PATCH', 'DELETE'])
218
+ @permission_classes([EnvironmentAwarePermission])
219
+ def video_segment_detail(request, pk, segment_id):
220
+ """
221
+ Detail endpoint for a specific video segment.
222
+
223
+ GET /api/media/videos/<pk>/segments/<segment_id>/
224
+ - Returns segment details
225
+
226
+ PATCH /api/media/videos/<pk>/segments/<segment_id>/
227
+ - Updates segment (partial update)
228
+
229
+ DELETE /api/media/videos/<pk>/segments/<segment_id>/
230
+ - Deletes segment
231
+
232
+ Modern replacement for: /api/video-segments/<segment_id>/
233
+ """
234
+ # Verify video exists
235
+ video = get_object_or_404(VideoFile, id=pk)
236
+
237
+ # Get segment and verify it belongs to this video
238
+ segment = get_object_or_404(
239
+ LabelVideoSegment,
240
+ id=segment_id,
241
+ video_file=video
242
+ )
243
+
244
+ if request.method == 'GET':
245
+ serializer = LabelVideoSegmentSerializer(segment)
246
+ return Response(serializer.data)
247
+
248
+ elif request.method == 'PATCH':
249
+ logger.info(f"Updating segment {segment_id} for video {pk} with data: {request.data}")
250
+
251
+ with transaction.atomic():
252
+ serializer = LabelVideoSegmentSerializer(
253
+ segment,
254
+ data=request.data,
255
+ partial=True
256
+ )
257
+ if serializer.is_valid():
258
+ try:
259
+ segment = serializer.save()
260
+ logger.info(f"Successfully updated segment {segment_id}")
261
+ return Response(LabelVideoSegmentSerializer(segment).data)
262
+ except Exception as e:
263
+ logger.error(f"Error updating segment {segment_id}: {str(e)}")
264
+ return Response(
265
+ {'error': f'Failed to update segment: {str(e)}'},
266
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
267
+ )
268
+ else:
269
+ logger.warning(f"Invalid data for segment update: {serializer.errors}")
270
+ return Response(
271
+ {'error': 'Invalid data', 'details': serializer.errors},
272
+ status=status.HTTP_400_BAD_REQUEST
273
+ )
274
+
275
+ elif request.method == 'DELETE':
276
+ logger.info(f"Deleting segment {segment_id} from video {pk}")
277
+ try:
278
+ with transaction.atomic():
279
+ segment.delete()
280
+ logger.info(f"Successfully deleted segment {segment_id}")
281
+ return Response(
282
+ {'message': f'Segment {segment_id} deleted successfully'},
283
+ status=status.HTTP_204_NO_CONTENT
284
+ )
285
+ except Exception as e:
286
+ logger.error(f"Error deleting segment {segment_id}: {str(e)}")
287
+ return Response(
288
+ {'error': f'Failed to delete segment: {str(e)}'},
289
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
290
+ )
291
+
292
+
293
+ # ============================================================================
294
+ # VIDEO SEGMENT VALIDATION ENDPOINTS (Modern Framework)
295
+ # Migrated from /api/label-video-segment/*/validate/ (October 14, 2025)
296
+ # ============================================================================
297
+
298
+ @api_view(['POST'])
299
+ @permission_classes([EnvironmentAwarePermission])
300
+ def video_segment_validate(request, pk: int, segment_id: int):
301
+ """
302
+ Validate a single video segment.
303
+
304
+ POST /api/media/videos/<pk>/segments/<segment_id>/validate/
305
+
306
+ Validates a single LabelVideoSegment and marks it as verified.
307
+ Used to confirm user-reviewed segment annotations.
308
+
309
+ Request Body (optional):
310
+ {
311
+ "is_validated": true, // optional, default true
312
+ "notes": "..." // optional, validation notes
313
+ }
314
+
315
+ Response:
316
+ {
317
+ "message": "Segment validated successfully",
318
+ "segment_id": 123,
319
+ "is_validated": true,
320
+ "label": "polyp",
321
+ "video_id": 456,
322
+ "start_frame": 100,
323
+ "end_frame": 200
324
+ }
325
+ """
326
+ # Verify video exists
327
+ video = get_object_or_404(VideoFile, pk=pk)
328
+
329
+ # Get segment and verify it belongs to this video
330
+ segment = get_object_or_404(
331
+ LabelVideoSegment.objects.select_related('state', 'video_file', 'label'),
332
+ pk=segment_id,
333
+ video_file=video
334
+ )
335
+
336
+ try:
337
+ # Validation status from request (default: True)
338
+ is_validated = request.data.get('is_validated', True)
339
+ notes = request.data.get('notes', '')
340
+
341
+ # Get or create state object
342
+ if not hasattr(segment, 'state') or segment.state is None:
343
+ return Response({
344
+ "error": "Segment has no state object. Cannot validate."
345
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
346
+
347
+ # Update state
348
+ with transaction.atomic():
349
+ segment.state.is_validated = is_validated
350
+ if notes and hasattr(segment.state, 'validation_notes'):
351
+ segment.state.validation_notes = notes
352
+ segment.state.save()
353
+
354
+ logger.info(f"Validated segment {segment_id} in video {pk}: {is_validated}")
355
+
356
+ return Response({
357
+ "message": f"Segment {segment_id} validation status updated",
358
+ "segment_id": segment_id,
359
+ "is_validated": is_validated,
360
+ "label": segment.label.name if segment.label else None,
361
+ "video_id": video.id,
362
+ "start_frame": segment.start_frame_number,
363
+ "end_frame": segment.end_frame_number
364
+ }, status=status.HTTP_200_OK)
365
+
366
+ except Exception as e:
367
+ logger.error(f"Error validating segment {segment_id} in video {pk}: {e}")
368
+ return Response({
369
+ "error": f"Validation failed: {str(e)}"
370
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
371
+
372
+
373
+ @api_view(['POST'])
374
+ @permission_classes([EnvironmentAwarePermission])
375
+ def video_segments_validate_bulk(request, pk: int):
376
+ """
377
+ Validate multiple video segments at once.
378
+
379
+ POST /api/media/videos/<pk>/segments/validate-bulk/
380
+
381
+ Validates multiple LabelVideoSegments simultaneously.
382
+ Useful for batch validation after review.
383
+
384
+ Request Body:
385
+ {
386
+ "segment_ids": [1, 2, 3, ...],
387
+ "is_validated": true, // optional, default true
388
+ "notes": "..." // optional, applies to all segments
389
+ }
390
+
391
+ Response:
392
+ {
393
+ "message": "Bulk validation completed. 3 segments updated.",
394
+ "updated_count": 3,
395
+ "requested_count": 3,
396
+ "is_validated": true,
397
+ "failed_ids": [] // only present if some failed
398
+ }
399
+ """
400
+ # Verify video exists
401
+ video = get_object_or_404(VideoFile, pk=pk)
402
+
403
+ segment_ids = request.data.get('segment_ids', [])
404
+ is_validated = request.data.get('is_validated', True)
405
+ notes = request.data.get('notes', '')
406
+
407
+ if not segment_ids:
408
+ return Response({
409
+ "error": "segment_ids is required"
410
+ }, status=status.HTTP_400_BAD_REQUEST)
411
+
412
+ try:
413
+ # Get all segments for this video only
414
+ segments = LabelVideoSegment.objects.filter(
415
+ pk__in=segment_ids,
416
+ video_file=video
417
+ ).select_related('state')
418
+
419
+ if not segments.exists():
420
+ return Response({
421
+ "error": "No segments found with provided IDs for this video"
422
+ }, status=status.HTTP_404_NOT_FOUND)
423
+
424
+ updated_count = 0
425
+ failed_ids = []
426
+
427
+ with transaction.atomic():
428
+ for segment in segments:
429
+ try:
430
+ if segment.state:
431
+ segment.state.is_validated = is_validated
432
+ if notes and hasattr(segment.state, 'validation_notes'):
433
+ segment.state.validation_notes = notes
434
+ segment.state.save()
435
+ updated_count += 1
436
+ else:
437
+ failed_ids.append(segment.id)
438
+ except Exception as e:
439
+ logger.error(f"Error validating segment {segment.id}: {e}")
440
+ failed_ids.append(segment.id)
441
+
442
+ logger.info(f"Bulk validated {updated_count} segments in video {pk}")
443
+
444
+ response_data = {
445
+ "message": f"Bulk validation completed. {updated_count} segments updated.",
446
+ "updated_count": updated_count,
447
+ "requested_count": len(segment_ids),
448
+ "is_validated": is_validated,
449
+ "video_id": pk
450
+ }
451
+
452
+ if failed_ids:
453
+ response_data["failed_ids"] = failed_ids
454
+ response_data["warning"] = f"{len(failed_ids)} segments could not be validated"
455
+
456
+ return Response(response_data, status=status.HTTP_200_OK)
457
+
458
+ except Exception as e:
459
+ logger.error(f"Error in bulk validation for video {pk}: {e}")
460
+ return Response({
461
+ "error": f"Bulk validation failed: {str(e)}"
462
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
463
+
464
+
465
+ @api_view(['GET', 'POST'])
466
+ @permission_classes([EnvironmentAwarePermission])
467
+ def video_segments_validation_status(request, pk: int):
468
+ """
469
+ Get or update validation status for all segments of a video.
470
+
471
+ GET /api/media/videos/<pk>/segments/validation-status/
472
+ Returns validation statistics for all segments.
473
+
474
+ POST /api/media/videos/<pk>/segments/validation-status/
475
+ Marks all segments (or filtered by label) as validated.
476
+
477
+ Query Parameters (GET):
478
+ - label_name: filter by label (optional)
479
+
480
+ Request Body (POST, optional):
481
+ {
482
+ "label_name": "...", // optional, only validate segments with this label
483
+ "notes": "..." // optional
484
+ }
485
+
486
+ Response (GET):
487
+ {
488
+ "video_id": 123,
489
+ "total_segments": 10,
490
+ "validated_count": 7,
491
+ "unvalidated_count": 3,
492
+ "validation_complete": false,
493
+ "by_label": {...}
494
+ }
495
+
496
+ Response (POST):
497
+ {
498
+ "message": "Video segment validation completed",
499
+ "video_id": 123,
500
+ "total_segments": 10,
501
+ "updated_count": 10,
502
+ "failed_count": 0
503
+ }
504
+ """
505
+ # Verify video exists
506
+ video = get_object_or_404(VideoFile, pk=pk)
507
+
508
+ if request.method == 'GET':
509
+ # Get validation status
510
+ label_name = request.query_params.get('label_name')
511
+
512
+ segments_query = LabelVideoSegment.objects.filter(
513
+ video_file=video
514
+ ).select_related('state', 'label')
515
+
516
+ if label_name:
517
+ segments_query = segments_query.filter(label__name=label_name)
518
+
519
+ segments = segments_query.all()
520
+ total_count = segments.count()
521
+
522
+ # Count validated segments
523
+ validated_count = sum(
524
+ 1 for s in segments
525
+ if s.state and s.state.is_validated
526
+ )
527
+
528
+ # By label breakdown
529
+ by_label = {}
530
+ for segment in segments:
531
+ label = segment.label.name if segment.label else 'unknown'
532
+ if label not in by_label:
533
+ by_label[label] = {'total': 0, 'validated': 0}
534
+ by_label[label]['total'] += 1
535
+ if segment.state and segment.state.is_validated:
536
+ by_label[label]['validated'] += 1
537
+
538
+ return Response({
539
+ "video_id": pk,
540
+ "total_segments": total_count,
541
+ "validated_count": validated_count,
542
+ "unvalidated_count": total_count - validated_count,
543
+ "validation_complete": validated_count == total_count and total_count > 0,
544
+ "by_label": by_label,
545
+ "label_filter": label_name
546
+ }, status=status.HTTP_200_OK)
547
+
548
+ elif request.method == 'POST':
549
+ # Mark all segments as validated
550
+ label_name = request.data.get('label_name')
551
+ notes = request.data.get('notes', '')
552
+
553
+ segments_query = LabelVideoSegment.objects.filter(
554
+ video_file=video
555
+ ).select_related('state', 'label')
556
+
557
+ if label_name:
558
+ segments_query = segments_query.filter(label__name=label_name)
559
+
560
+ segments = segments_query.all()
561
+
562
+ if not segments.exists():
563
+ return Response({
564
+ "message": "No segments found to validate",
565
+ "video_id": pk,
566
+ "updated_count": 0
567
+ }, status=status.HTTP_200_OK)
568
+
569
+ updated_count = 0
570
+ failed_count = 0
571
+
572
+ with transaction.atomic():
573
+ for segment in segments:
574
+ try:
575
+ if segment.state:
576
+ segment.state.is_validated = True
577
+ if notes and hasattr(segment.state, 'validation_notes'):
578
+ segment.state.validation_notes = notes
579
+ segment.state.save()
580
+ updated_count += 1
581
+ else:
582
+ failed_count += 1
583
+ except Exception as e:
584
+ logger.error(f"Error validating segment {segment.id}: {e}")
585
+ failed_count += 1
586
+
587
+ logger.info(f"Completed validation for {updated_count} segments in video {pk}")
588
+
589
+ return Response({
590
+ "message": f"Video segment validation completed for video {pk}",
591
+ "video_id": pk,
592
+ "total_segments": len(segments),
593
+ "updated_count": updated_count,
594
+ "failed_count": failed_count,
595
+ "label_filter": label_name
596
+ }, status=status.HTTP_200_OK)
@@ -18,15 +18,21 @@ class PdfReimportView(APIView):
18
18
  super().__init__(**kwargs)
19
19
  self.pdf_service = PdfImportService()
20
20
 
21
- def post(self, request, pdf_id):
21
+ def post(self, request, pk):
22
22
  """
23
23
  Re-import a pdf file to regenerate SensitiveMeta and other metadata.
24
24
  Instead of creating a new pdf, this updates the existing one.
25
+
26
+ Args:
27
+ request: HTTP request object
28
+ pk: PDF primary key (ID)
25
29
  """
30
+ pdf_id = pk # Align with media framework naming convention
31
+
26
32
  # Validate pdf_id parameter
27
33
  if not pdf_id or not isinstance(pdf_id, int):
28
34
  return Response(
29
- {"error": "Invalid pdf ID provided."},
35
+ {"error": "Invalid PDF ID provided."},
30
36
  status=status.HTTP_400_BAD_REQUEST
31
37
  )
32
38
 
@@ -94,7 +100,11 @@ class PdfReimportView(APIView):
94
100
 
95
101
  logger.info(f"Starting anonymization using VideoImportService for {pdf.uuid}")
96
102
  self.pdf_service.import_and_anonymize(
97
- pdf_file_obj=pdf,
103
+ file_path=raw_file_path,
104
+ center_name=pdf.center.name,
105
+ processor_name=pdf.processor.name if pdf.processor else "Unknown",
106
+ save_video=True,
107
+ delete_source=False
98
108
  )
99
109
 
100
110
  logger.info(f"VideoImportService anonymization completed for {pdf.uuid}")
@@ -115,17 +125,17 @@ class PdfReimportView(APIView):
115
125
  logger.exception(f"VideoImportService anonymization failed for pdf {pdf.uuid}: {e}")
116
126
  logger.warning("Continuing without anonymization due to error")
117
127
 
118
- # Set anonymization status to "done" even without frame cleaning
119
- pdf
128
+ # Refresh from database to get final state
129
+ pdf.refresh_from_db()
120
130
 
121
131
  return Response({
122
- "message": "Video re-import completed successfully.",
132
+ "message": "PDF re-import completed successfully.",
123
133
  "pdf_id": pdf_id,
124
134
  "uuid": str(pdf.uuid),
125
135
  "sensitive_meta_created": pdf.sensitive_meta is not None,
126
- "sensitive_meta_id": .sensitive_meta.id if pdf.sensitive_meta else None,
136
+ "sensitive_meta_id": pdf.sensitive_meta.id if pdf.sensitive_meta else None,
127
137
  "updated_in_place": True,
128
- "status": "done" # ⭐ Add explicit done status
138
+ "status": "done"
129
139
  }, status=status.HTTP_200_OK)
130
140
 
131
141
  except Exception as e:
@@ -7,16 +7,8 @@ from .correction import (
7
7
  VideoReprocessView,
8
8
  )
9
9
 
10
- # Phase 1.2: Media Management Views ✅ IMPLEMENTED
11
10
  from ..media.video_media import VideoMediaView
12
11
 
13
- # TODO Phase 1.2+: Future views
14
- # from .media import (
15
- # VideoCorrectionView,
16
- # TaskStatusView,
17
- # VideoDownloadProcessedView,
18
- # )
19
-
20
12
  from .reimport import (
21
13
  VideoReimportView
22
14
  )