endoreg-db 0.8.9.2__py3-none-any.whl → 0.8.9.10__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 (450) hide show
  1. endoreg_db/admin.py +10 -5
  2. endoreg_db/apps.py +4 -7
  3. endoreg_db/authz/auth.py +1 -0
  4. endoreg_db/authz/backends.py +1 -1
  5. endoreg_db/authz/management/commands/list_routes.py +2 -0
  6. endoreg_db/authz/middleware.py +8 -7
  7. endoreg_db/authz/permissions.py +21 -10
  8. endoreg_db/authz/policy.py +14 -19
  9. endoreg_db/authz/views_auth.py +14 -10
  10. endoreg_db/codemods/rename_datetime_fields.py +8 -1
  11. endoreg_db/exceptions.py +5 -2
  12. endoreg_db/forms/__init__.py +0 -1
  13. endoreg_db/forms/examination_form.py +4 -3
  14. endoreg_db/forms/patient_finding_intervention_form.py +30 -8
  15. endoreg_db/forms/patient_form.py +9 -13
  16. endoreg_db/forms/questionnaires/__init__.py +1 -1
  17. endoreg_db/forms/settings/__init__.py +4 -1
  18. endoreg_db/forms/unit.py +2 -1
  19. endoreg_db/helpers/count_db.py +17 -14
  20. endoreg_db/helpers/default_objects.py +2 -1
  21. endoreg_db/helpers/download_segmentation_model.py +4 -3
  22. endoreg_db/helpers/interact.py +0 -5
  23. endoreg_db/helpers/test_video_helper.py +33 -25
  24. endoreg_db/import_files/__init__.py +1 -1
  25. endoreg_db/import_files/context/__init__.py +1 -1
  26. endoreg_db/import_files/context/default_sensitive_meta.py +11 -9
  27. endoreg_db/import_files/context/ensure_center.py +4 -4
  28. endoreg_db/import_files/context/file_lock.py +3 -3
  29. endoreg_db/import_files/context/import_context.py +11 -12
  30. endoreg_db/import_files/context/validate_directories.py +1 -0
  31. endoreg_db/import_files/file_storage/create_report_file.py +57 -34
  32. endoreg_db/import_files/file_storage/create_video_file.py +64 -35
  33. endoreg_db/import_files/file_storage/sensitive_meta_storage.py +5 -2
  34. endoreg_db/import_files/file_storage/state_management.py +89 -122
  35. endoreg_db/import_files/file_storage/storage.py +5 -1
  36. endoreg_db/import_files/processing/report_processing/report_anonymization.py +24 -19
  37. endoreg_db/import_files/processing/sensitive_meta_adapter.py +3 -3
  38. endoreg_db/import_files/processing/video_processing/video_anonymization.py +18 -18
  39. endoreg_db/import_files/pseudonymization/k_anonymity.py +8 -9
  40. endoreg_db/import_files/pseudonymization/k_pseudonymity.py +16 -5
  41. endoreg_db/import_files/report_import_service.py +36 -30
  42. endoreg_db/import_files/video_import_service.py +27 -23
  43. endoreg_db/logger_conf.py +56 -40
  44. endoreg_db/management/__init__.py +1 -1
  45. endoreg_db/management/commands/__init__.py +1 -1
  46. endoreg_db/management/commands/check_auth.py +45 -38
  47. endoreg_db/management/commands/create_model_meta_from_huggingface.py +53 -2
  48. endoreg_db/management/commands/create_multilabel_model_meta.py +54 -19
  49. endoreg_db/management/commands/fix_missing_patient_data.py +105 -71
  50. endoreg_db/management/commands/fix_video_paths.py +75 -54
  51. endoreg_db/management/commands/import_report.py +1 -3
  52. endoreg_db/management/commands/list_routes.py +2 -0
  53. endoreg_db/management/commands/load_ai_model_data.py +8 -2
  54. endoreg_db/management/commands/load_ai_model_label_data.py +0 -1
  55. endoreg_db/management/commands/load_center_data.py +3 -3
  56. endoreg_db/management/commands/load_distribution_data.py +35 -38
  57. endoreg_db/management/commands/load_endoscope_data.py +0 -3
  58. endoreg_db/management/commands/load_examination_data.py +20 -4
  59. endoreg_db/management/commands/load_finding_data.py +18 -3
  60. endoreg_db/management/commands/load_gender_data.py +17 -24
  61. endoreg_db/management/commands/load_green_endoscopy_wuerzburg_data.py +95 -85
  62. endoreg_db/management/commands/load_information_source.py +0 -3
  63. endoreg_db/management/commands/load_lab_value_data.py +14 -3
  64. endoreg_db/management/commands/load_legacy_data.py +303 -0
  65. endoreg_db/management/commands/load_name_data.py +1 -2
  66. endoreg_db/management/commands/load_pdf_type_data.py +4 -8
  67. endoreg_db/management/commands/load_profession_data.py +0 -1
  68. endoreg_db/management/commands/load_report_reader_flag_data.py +0 -4
  69. endoreg_db/management/commands/load_requirement_data.py +6 -2
  70. endoreg_db/management/commands/load_unit_data.py +0 -4
  71. endoreg_db/management/commands/load_user_groups.py +5 -7
  72. endoreg_db/management/commands/model_input.py +169 -0
  73. endoreg_db/management/commands/register_ai_model.py +22 -16
  74. endoreg_db/management/commands/setup_endoreg_db.py +110 -32
  75. endoreg_db/management/commands/storage_management.py +14 -8
  76. endoreg_db/management/commands/summarize_db_content.py +154 -63
  77. endoreg_db/management/commands/train_image_multilabel_model.py +144 -0
  78. endoreg_db/management/commands/validate_video_files.py +82 -50
  79. endoreg_db/management/commands/video_validation.py +4 -6
  80. endoreg_db/migrations/0001_initial.py +112 -63
  81. endoreg_db/models/__init__.py +8 -0
  82. endoreg_db/models/administration/ai/active_model.py +5 -5
  83. endoreg_db/models/administration/ai/ai_model.py +41 -18
  84. endoreg_db/models/administration/ai/model_type.py +1 -0
  85. endoreg_db/models/administration/case/case.py +22 -22
  86. endoreg_db/models/administration/center/__init__.py +5 -5
  87. endoreg_db/models/administration/center/center.py +6 -2
  88. endoreg_db/models/administration/center/center_resource.py +18 -4
  89. endoreg_db/models/administration/center/center_shift.py +3 -1
  90. endoreg_db/models/administration/center/center_waste.py +6 -2
  91. endoreg_db/models/administration/person/__init__.py +1 -1
  92. endoreg_db/models/administration/person/employee/__init__.py +1 -1
  93. endoreg_db/models/administration/person/employee/employee_type.py +3 -1
  94. endoreg_db/models/administration/person/examiner/__init__.py +1 -1
  95. endoreg_db/models/administration/person/examiner/examiner.py +10 -2
  96. endoreg_db/models/administration/person/names/first_name.py +6 -4
  97. endoreg_db/models/administration/person/names/last_name.py +4 -3
  98. endoreg_db/models/administration/person/patient/__init__.py +1 -1
  99. endoreg_db/models/administration/person/patient/patient.py +0 -1
  100. endoreg_db/models/administration/person/patient/patient_external_id.py +0 -1
  101. endoreg_db/models/administration/person/person.py +1 -1
  102. endoreg_db/models/administration/product/__init__.py +7 -6
  103. endoreg_db/models/administration/product/product.py +6 -2
  104. endoreg_db/models/administration/product/product_group.py +9 -7
  105. endoreg_db/models/administration/product/product_material.py +9 -2
  106. endoreg_db/models/administration/product/reference_product.py +64 -15
  107. endoreg_db/models/administration/qualification/qualification.py +3 -1
  108. endoreg_db/models/administration/shift/shift.py +3 -1
  109. endoreg_db/models/administration/shift/shift_type.py +12 -4
  110. endoreg_db/models/aidataset/__init__.py +5 -0
  111. endoreg_db/models/aidataset/aidataset.py +193 -0
  112. endoreg_db/models/label/__init__.py +1 -1
  113. endoreg_db/models/label/label.py +10 -2
  114. endoreg_db/models/label/label_set.py +3 -1
  115. endoreg_db/models/label/label_video_segment/_create_from_video.py +6 -2
  116. endoreg_db/models/label/label_video_segment/label_video_segment.py +148 -44
  117. endoreg_db/models/media/__init__.py +12 -5
  118. endoreg_db/models/media/frame/__init__.py +1 -1
  119. endoreg_db/models/media/frame/frame.py +34 -8
  120. endoreg_db/models/media/pdf/__init__.py +2 -1
  121. endoreg_db/models/media/pdf/raw_pdf.py +11 -4
  122. endoreg_db/models/media/pdf/report_file.py +6 -2
  123. endoreg_db/models/media/pdf/report_reader/__init__.py +3 -3
  124. endoreg_db/models/media/pdf/report_reader/report_reader_flag.py +15 -5
  125. endoreg_db/models/media/video/create_from_file.py +20 -41
  126. endoreg_db/models/media/video/pipe_1.py +75 -30
  127. endoreg_db/models/media/video/pipe_2.py +37 -12
  128. endoreg_db/models/media/video/video_file.py +36 -24
  129. endoreg_db/models/media/video/video_file_ai.py +235 -70
  130. endoreg_db/models/media/video/video_file_anonymize.py +240 -65
  131. endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +6 -1
  132. endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +3 -1
  133. endoreg_db/models/media/video/video_file_frames/_delete_frames.py +30 -9
  134. endoreg_db/models/media/video/video_file_frames/_extract_frames.py +95 -29
  135. endoreg_db/models/media/video/video_file_frames/_get_frame.py +13 -3
  136. endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +4 -1
  137. endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +15 -3
  138. endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +15 -3
  139. endoreg_db/models/media/video/video_file_frames/_get_frames.py +7 -2
  140. endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +109 -23
  141. endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +111 -27
  142. endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +46 -13
  143. endoreg_db/models/media/video/video_file_io.py +85 -33
  144. endoreg_db/models/media/video/video_file_meta/__init__.py +6 -6
  145. endoreg_db/models/media/video/video_file_meta/get_crop_template.py +17 -4
  146. endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +28 -7
  147. endoreg_db/models/media/video/video_file_meta/get_fps.py +46 -13
  148. endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +81 -20
  149. endoreg_db/models/media/video/video_file_meta/text_meta.py +61 -20
  150. endoreg_db/models/media/video/video_file_meta/video_meta.py +40 -12
  151. endoreg_db/models/media/video/video_file_segments.py +118 -27
  152. endoreg_db/models/media/video/video_metadata.py +25 -6
  153. endoreg_db/models/media/video/video_processing.py +54 -15
  154. endoreg_db/models/medical/__init__.py +3 -13
  155. endoreg_db/models/medical/contraindication/__init__.py +3 -1
  156. endoreg_db/models/medical/disease.py +18 -6
  157. endoreg_db/models/medical/event.py +6 -2
  158. endoreg_db/models/medical/examination/__init__.py +5 -1
  159. endoreg_db/models/medical/examination/examination.py +22 -6
  160. endoreg_db/models/medical/examination/examination_indication.py +23 -7
  161. endoreg_db/models/medical/examination/examination_time.py +6 -2
  162. endoreg_db/models/medical/finding/__init__.py +3 -1
  163. endoreg_db/models/medical/finding/finding.py +37 -12
  164. endoreg_db/models/medical/finding/finding_classification.py +27 -8
  165. endoreg_db/models/medical/finding/finding_intervention.py +19 -6
  166. endoreg_db/models/medical/finding/finding_type.py +3 -1
  167. endoreg_db/models/medical/hardware/__init__.py +1 -1
  168. endoreg_db/models/medical/hardware/endoscope.py +14 -2
  169. endoreg_db/models/medical/laboratory/__init__.py +1 -1
  170. endoreg_db/models/medical/laboratory/lab_value.py +139 -39
  171. endoreg_db/models/medical/medication/__init__.py +7 -3
  172. endoreg_db/models/medical/medication/medication.py +3 -1
  173. endoreg_db/models/medical/medication/medication_indication.py +3 -1
  174. endoreg_db/models/medical/medication/medication_indication_type.py +11 -3
  175. endoreg_db/models/medical/medication/medication_intake_time.py +3 -1
  176. endoreg_db/models/medical/medication/medication_schedule.py +3 -1
  177. endoreg_db/models/medical/patient/__init__.py +2 -10
  178. endoreg_db/models/medical/patient/medication_examples.py +3 -14
  179. endoreg_db/models/medical/patient/patient_disease.py +17 -5
  180. endoreg_db/models/medical/patient/patient_event.py +12 -4
  181. endoreg_db/models/medical/patient/patient_examination.py +52 -15
  182. endoreg_db/models/medical/patient/patient_examination_indication.py +15 -4
  183. endoreg_db/models/medical/patient/patient_finding.py +105 -29
  184. endoreg_db/models/medical/patient/patient_finding_classification.py +41 -12
  185. endoreg_db/models/medical/patient/patient_finding_intervention.py +11 -3
  186. endoreg_db/models/medical/patient/patient_lab_sample.py +6 -2
  187. endoreg_db/models/medical/patient/patient_lab_value.py +42 -10
  188. endoreg_db/models/medical/patient/patient_medication.py +25 -7
  189. endoreg_db/models/medical/patient/patient_medication_schedule.py +34 -10
  190. endoreg_db/models/metadata/model_meta.py +40 -12
  191. endoreg_db/models/metadata/model_meta_logic.py +51 -16
  192. endoreg_db/models/metadata/sensitive_meta.py +65 -28
  193. endoreg_db/models/metadata/sensitive_meta_logic.py +28 -26
  194. endoreg_db/models/metadata/video_meta.py +146 -39
  195. endoreg_db/models/metadata/video_prediction_logic.py +70 -21
  196. endoreg_db/models/metadata/video_prediction_meta.py +80 -27
  197. endoreg_db/models/operation_log.py +63 -0
  198. endoreg_db/models/other/__init__.py +10 -10
  199. endoreg_db/models/other/distribution/__init__.py +9 -7
  200. endoreg_db/models/other/distribution/base_value_distribution.py +3 -1
  201. endoreg_db/models/other/distribution/date_value_distribution.py +19 -5
  202. endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +3 -1
  203. endoreg_db/models/other/distribution/numeric_value_distribution.py +34 -9
  204. endoreg_db/models/other/emission/__init__.py +1 -1
  205. endoreg_db/models/other/emission/emission_factor.py +9 -3
  206. endoreg_db/models/other/information_source.py +15 -5
  207. endoreg_db/models/other/material.py +3 -1
  208. endoreg_db/models/other/transport_route.py +3 -1
  209. endoreg_db/models/other/unit.py +6 -2
  210. endoreg_db/models/report/report.py +0 -1
  211. endoreg_db/models/requirement/requirement.py +84 -27
  212. endoreg_db/models/requirement/requirement_error.py +5 -6
  213. endoreg_db/models/requirement/requirement_evaluation/__init__.py +1 -1
  214. endoreg_db/models/requirement/requirement_evaluation/evaluate_with_dependencies.py +8 -8
  215. endoreg_db/models/requirement/requirement_evaluation/get_values.py +3 -3
  216. endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +24 -8
  217. endoreg_db/models/requirement/requirement_operator.py +28 -8
  218. endoreg_db/models/requirement/requirement_set.py +34 -11
  219. endoreg_db/models/state/__init__.py +1 -0
  220. endoreg_db/models/state/audit_ledger.py +9 -2
  221. endoreg_db/models/{media → state}/processing_history/__init__.py +1 -3
  222. endoreg_db/models/state/processing_history/processing_history.py +136 -0
  223. endoreg_db/models/state/raw_pdf.py +0 -1
  224. endoreg_db/models/state/video.py +2 -4
  225. endoreg_db/models/utils.py +4 -2
  226. endoreg_db/queries/__init__.py +2 -6
  227. endoreg_db/queries/annotations/__init__.py +1 -3
  228. endoreg_db/queries/annotations/legacy.py +37 -26
  229. endoreg_db/root_urls.py +3 -4
  230. endoreg_db/schemas/examination_evaluation.py +3 -0
  231. endoreg_db/serializers/Frames_NICE_and_PARIS_classifications.py +249 -163
  232. endoreg_db/serializers/__init__.py +2 -8
  233. endoreg_db/serializers/administration/__init__.py +1 -2
  234. endoreg_db/serializers/administration/ai/__init__.py +0 -1
  235. endoreg_db/serializers/administration/ai/active_model.py +3 -1
  236. endoreg_db/serializers/administration/ai/ai_model.py +5 -3
  237. endoreg_db/serializers/administration/ai/model_type.py +3 -1
  238. endoreg_db/serializers/administration/center.py +7 -2
  239. endoreg_db/serializers/administration/gender.py +4 -2
  240. endoreg_db/serializers/anonymization.py +13 -13
  241. endoreg_db/serializers/evaluation/examination_evaluation.py +0 -1
  242. endoreg_db/serializers/examination/__init__.py +1 -1
  243. endoreg_db/serializers/examination/base.py +12 -13
  244. endoreg_db/serializers/examination/dropdown.py +6 -7
  245. endoreg_db/serializers/examination_serializer.py +3 -6
  246. endoreg_db/serializers/finding/__init__.py +1 -1
  247. endoreg_db/serializers/finding/finding.py +14 -7
  248. endoreg_db/serializers/finding_classification/__init__.py +3 -3
  249. endoreg_db/serializers/finding_classification/choice.py +3 -3
  250. endoreg_db/serializers/finding_classification/classification.py +2 -4
  251. endoreg_db/serializers/label_video_segment/__init__.py +5 -3
  252. endoreg_db/serializers/{label → label_video_segment}/image_classification_annotation.py +5 -5
  253. endoreg_db/serializers/label_video_segment/label/__init__.py +6 -0
  254. endoreg_db/serializers/{label → label_video_segment/label}/label.py +1 -1
  255. endoreg_db/serializers/label_video_segment/label_video_segment.py +338 -228
  256. endoreg_db/serializers/meta/__init__.py +1 -2
  257. endoreg_db/serializers/meta/sensitive_meta_detail.py +28 -13
  258. endoreg_db/serializers/meta/sensitive_meta_update.py +51 -46
  259. endoreg_db/serializers/meta/sensitive_meta_verification.py +19 -16
  260. endoreg_db/serializers/misc/__init__.py +2 -2
  261. endoreg_db/serializers/misc/file_overview.py +11 -7
  262. endoreg_db/serializers/misc/stats.py +10 -8
  263. endoreg_db/serializers/misc/translatable_field_mix_in.py +6 -6
  264. endoreg_db/serializers/misc/upload_job.py +32 -29
  265. endoreg_db/serializers/patient/__init__.py +2 -1
  266. endoreg_db/serializers/patient/patient.py +32 -15
  267. endoreg_db/serializers/patient/patient_dropdown.py +11 -3
  268. endoreg_db/serializers/patient_examination/__init__.py +1 -1
  269. endoreg_db/serializers/patient_examination/patient_examination.py +67 -40
  270. endoreg_db/serializers/patient_finding/__init__.py +1 -1
  271. endoreg_db/serializers/patient_finding/patient_finding.py +2 -1
  272. endoreg_db/serializers/patient_finding/patient_finding_classification.py +17 -9
  273. endoreg_db/serializers/patient_finding/patient_finding_detail.py +26 -17
  274. endoreg_db/serializers/patient_finding/patient_finding_intervention.py +7 -5
  275. endoreg_db/serializers/patient_finding/patient_finding_list.py +10 -11
  276. endoreg_db/serializers/patient_finding/patient_finding_write.py +36 -27
  277. endoreg_db/serializers/pdf/__init__.py +1 -3
  278. endoreg_db/serializers/requirements/requirement_schema.py +1 -6
  279. endoreg_db/serializers/sensitive_meta_serializer.py +100 -81
  280. endoreg_db/serializers/video/__init__.py +2 -2
  281. endoreg_db/serializers/video/{segmentation.py → video_file.py} +66 -47
  282. endoreg_db/serializers/video/video_file_brief.py +6 -2
  283. endoreg_db/serializers/video/video_file_detail.py +36 -23
  284. endoreg_db/serializers/video/video_file_list.py +4 -2
  285. endoreg_db/serializers/video/video_processing_history.py +54 -50
  286. endoreg_db/services/__init__.py +1 -1
  287. endoreg_db/services/anonymization.py +2 -2
  288. endoreg_db/services/examination_evaluation.py +40 -17
  289. endoreg_db/services/model_meta_from_hf.py +76 -0
  290. endoreg_db/services/polling_coordinator.py +101 -70
  291. endoreg_db/services/pseudonym_service.py +27 -22
  292. endoreg_db/services/report_import.py +6 -3
  293. endoreg_db/services/segment_sync.py +75 -59
  294. endoreg_db/services/video_import.py +6 -7
  295. endoreg_db/urls/__init__.py +2 -2
  296. endoreg_db/urls/ai.py +7 -25
  297. endoreg_db/urls/anonymization.py +61 -15
  298. endoreg_db/urls/auth.py +4 -4
  299. endoreg_db/urls/classification.py +4 -9
  300. endoreg_db/urls/examination.py +27 -18
  301. endoreg_db/urls/media.py +27 -34
  302. endoreg_db/urls/patient.py +11 -7
  303. endoreg_db/urls/requirements.py +3 -1
  304. endoreg_db/urls/root_urls.py +2 -3
  305. endoreg_db/urls/stats.py +24 -16
  306. endoreg_db/urls/upload.py +3 -11
  307. endoreg_db/utils/__init__.py +14 -15
  308. endoreg_db/utils/ai/__init__.py +1 -1
  309. endoreg_db/utils/ai/data_loader_for_model_input.py +262 -0
  310. endoreg_db/utils/ai/data_loader_for_model_training.py +262 -0
  311. endoreg_db/utils/ai/get.py +2 -1
  312. endoreg_db/utils/ai/inference_dataset.py +14 -15
  313. endoreg_db/utils/ai/model_training/config.py +117 -0
  314. endoreg_db/utils/ai/model_training/dataset.py +74 -0
  315. endoreg_db/utils/ai/model_training/losses.py +68 -0
  316. endoreg_db/utils/ai/model_training/metrics.py +78 -0
  317. endoreg_db/utils/ai/model_training/model_backbones.py +155 -0
  318. endoreg_db/utils/ai/model_training/model_gastronet_resnet.py +118 -0
  319. endoreg_db/utils/ai/model_training/trainer_gastronet_multilabel.py +771 -0
  320. endoreg_db/utils/ai/multilabel_classification_net.py +21 -6
  321. endoreg_db/utils/ai/predict.py +4 -4
  322. endoreg_db/utils/ai/preprocess.py +19 -11
  323. endoreg_db/utils/calc_duration_seconds.py +4 -4
  324. endoreg_db/utils/case_generator/lab_sample_factory.py +3 -4
  325. endoreg_db/utils/check_video_files.py +74 -47
  326. endoreg_db/utils/cropping.py +10 -9
  327. endoreg_db/utils/dataloader.py +11 -3
  328. endoreg_db/utils/dates.py +3 -4
  329. endoreg_db/utils/defaults/set_default_center.py +7 -6
  330. endoreg_db/utils/env.py +6 -2
  331. endoreg_db/utils/extract_specific_frames.py +24 -9
  332. endoreg_db/utils/file_operations.py +30 -18
  333. endoreg_db/utils/fix_video_path_direct.py +57 -41
  334. endoreg_db/utils/frame_anonymization_utils.py +157 -157
  335. endoreg_db/utils/hashs.py +3 -18
  336. endoreg_db/utils/links/requirement_link.py +96 -52
  337. endoreg_db/utils/ocr.py +30 -25
  338. endoreg_db/utils/operation_log.py +61 -0
  339. endoreg_db/utils/parse_and_generate_yaml.py +12 -13
  340. endoreg_db/utils/paths.py +6 -6
  341. endoreg_db/utils/permissions.py +40 -24
  342. endoreg_db/utils/pipelines/process_video_dir.py +50 -26
  343. endoreg_db/utils/product/sum_emissions.py +5 -3
  344. endoreg_db/utils/product/sum_weights.py +4 -2
  345. endoreg_db/utils/pydantic_models/__init__.py +3 -4
  346. endoreg_db/utils/requirement_operator_logic/_old/lab_value_operators.py +207 -107
  347. endoreg_db/utils/requirement_operator_logic/_old/model_evaluators.py +252 -65
  348. endoreg_db/utils/requirement_operator_logic/new_operator_logic.py +27 -10
  349. endoreg_db/utils/setup_config.py +21 -5
  350. endoreg_db/utils/storage.py +3 -1
  351. endoreg_db/utils/translation.py +19 -15
  352. endoreg_db/utils/uuid.py +1 -0
  353. endoreg_db/utils/validate_endo_roi.py +12 -4
  354. endoreg_db/utils/validate_subcategory_dict.py +26 -24
  355. endoreg_db/utils/validate_video_detailed.py +207 -149
  356. endoreg_db/utils/video/__init__.py +7 -3
  357. endoreg_db/utils/video/extract_frames.py +30 -18
  358. endoreg_db/utils/video/names.py +11 -6
  359. endoreg_db/utils/video/streaming_processor.py +175 -101
  360. endoreg_db/utils/video/video_splitter.py +30 -19
  361. endoreg_db/views/Frames_NICE_and_PARIS_classifications_views.py +59 -50
  362. endoreg_db/views/__init__.py +0 -20
  363. endoreg_db/views/anonymization/__init__.py +6 -2
  364. endoreg_db/views/anonymization/media_management.py +2 -6
  365. endoreg_db/views/anonymization/overview.py +34 -1
  366. endoreg_db/views/anonymization/validate.py +79 -18
  367. endoreg_db/views/auth/__init__.py +1 -1
  368. endoreg_db/views/auth/keycloak.py +16 -14
  369. endoreg_db/views/examination/__init__.py +12 -15
  370. endoreg_db/views/examination/examination.py +5 -5
  371. endoreg_db/views/examination/examination_manifest_cache.py +5 -5
  372. endoreg_db/views/examination/get_finding_classification_choices.py +8 -5
  373. endoreg_db/views/examination/get_finding_classifications.py +9 -7
  374. endoreg_db/views/examination/get_findings.py +8 -10
  375. endoreg_db/views/examination/get_instruments.py +3 -2
  376. endoreg_db/views/examination/get_interventions.py +1 -1
  377. endoreg_db/views/finding/__init__.py +2 -2
  378. endoreg_db/views/finding/finding.py +58 -54
  379. endoreg_db/views/finding/get_classifications.py +1 -1
  380. endoreg_db/views/finding/get_interventions.py +1 -1
  381. endoreg_db/views/finding_classification/__init__.py +5 -5
  382. endoreg_db/views/finding_classification/finding_classification.py +5 -6
  383. endoreg_db/views/finding_classification/get_classification_choices.py +3 -4
  384. endoreg_db/views/media/__init__.py +13 -13
  385. endoreg_db/views/media/pdf_media.py +9 -9
  386. endoreg_db/views/media/sensitive_metadata.py +10 -7
  387. endoreg_db/views/media/video_media.py +4 -4
  388. endoreg_db/views/meta/__init__.py +1 -1
  389. endoreg_db/views/meta/sensitive_meta_list.py +20 -22
  390. endoreg_db/views/meta/sensitive_meta_verification.py +14 -11
  391. endoreg_db/views/misc/__init__.py +6 -34
  392. endoreg_db/views/misc/center.py +2 -1
  393. endoreg_db/views/misc/csrf.py +2 -1
  394. endoreg_db/views/misc/gender.py +2 -1
  395. endoreg_db/views/misc/stats.py +141 -106
  396. endoreg_db/views/patient/__init__.py +1 -3
  397. endoreg_db/views/patient/patient.py +141 -99
  398. endoreg_db/views/patient_examination/__init__.py +5 -5
  399. endoreg_db/views/patient_examination/patient_examination.py +43 -42
  400. endoreg_db/views/patient_examination/patient_examination_create.py +10 -15
  401. endoreg_db/views/patient_examination/patient_examination_detail.py +12 -15
  402. endoreg_db/views/patient_examination/patient_examination_list.py +21 -17
  403. endoreg_db/views/patient_examination/video.py +114 -80
  404. endoreg_db/views/patient_finding/__init__.py +1 -1
  405. endoreg_db/views/patient_finding/patient_finding.py +17 -10
  406. endoreg_db/views/patient_finding/patient_finding_optimized.py +127 -95
  407. endoreg_db/views/patient_finding_classification/__init__.py +1 -1
  408. endoreg_db/views/patient_finding_classification/pfc_create.py +35 -27
  409. endoreg_db/views/report/reimport.py +1 -1
  410. endoreg_db/views/report/report_stream.py +5 -8
  411. endoreg_db/views/requirement/__init__.py +2 -1
  412. endoreg_db/views/requirement/evaluate.py +7 -9
  413. endoreg_db/views/requirement/lookup.py +2 -3
  414. endoreg_db/views/requirement/lookup_store.py +0 -1
  415. endoreg_db/views/requirement/requirement_utils.py +2 -4
  416. endoreg_db/views/stats/__init__.py +4 -4
  417. endoreg_db/views/stats/stats_views.py +152 -115
  418. endoreg_db/views/video/__init__.py +18 -27
  419. endoreg_db/views/{ai → video/ai}/__init__.py +2 -2
  420. endoreg_db/views/{ai → video/ai}/label.py +20 -16
  421. endoreg_db/views/video/correction.py +5 -6
  422. endoreg_db/views/video/reimport.py +134 -99
  423. endoreg_db/views/video/segments_crud.py +134 -44
  424. endoreg_db/views/video/video_apply_mask.py +13 -12
  425. endoreg_db/views/video/video_correction.py +2 -1
  426. endoreg_db/views/video/video_download_processed.py +15 -15
  427. endoreg_db/views/video/video_meta_stats.py +7 -6
  428. endoreg_db/views/video/video_processing_history.py +3 -2
  429. endoreg_db/views/video/video_remove_frames.py +13 -12
  430. endoreg_db/views/video/video_stream.py +110 -82
  431. {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/METADATA +9 -3
  432. {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/RECORD +434 -431
  433. endoreg_db/management/commands/import_fallback_video.py +0 -203
  434. endoreg_db/management/commands/import_video.py +0 -422
  435. endoreg_db/management/commands/import_video_with_classification.py +0 -367
  436. endoreg_db/models/media/processing_history/processing_history.py +0 -96
  437. endoreg_db/serializers/label/__init__.py +0 -7
  438. endoreg_db/serializers/label_video_segment/_lvs_create.py +0 -149
  439. endoreg_db/serializers/label_video_segment/_lvs_update.py +0 -138
  440. endoreg_db/serializers/label_video_segment/_lvs_validate.py +0 -149
  441. endoreg_db/serializers/label_video_segment/label_video_segment_annotation.py +0 -99
  442. endoreg_db/serializers/label_video_segment/label_video_segment_update.py +0 -163
  443. endoreg_db/services/__old/pdf_import.py +0 -1487
  444. endoreg_db/services/__old/video_import.py +0 -1306
  445. endoreg_db/tasks/upload_tasks.py +0 -216
  446. endoreg_db/tasks/video_ingest.py +0 -161
  447. endoreg_db/tasks/video_processing_tasks.py +0 -327
  448. endoreg_db/views/misc/translation.py +0 -182
  449. {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/WHEEL +0 -0
  450. {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/licenses/LICENSE +0 -0
@@ -1,17 +1,18 @@
1
1
  import os
2
- import shutil
3
2
  from pathlib import Path
4
- from icecream import ic
5
- import subprocess
3
+ from typing import TYPE_CHECKING, List, Optional
4
+
6
5
  from django.db import transaction
6
+ from icecream import ic
7
7
  from tqdm import tqdm
8
- from typing import TYPE_CHECKING, Union, List, Optional
9
8
 
10
9
  if TYPE_CHECKING:
11
10
  from ...models.media import VideoFile
12
11
 
13
- from django.core.files import File
14
12
  import io
13
+
14
+ from django.core.files import File
15
+
15
16
  from .ffmpeg_wrapper import extract_frames as ffmpeg_extract_frames
16
17
 
17
18
 
@@ -28,27 +29,33 @@ def prepare_bulk_frames(frame_paths: List[Path]):
28
29
  yield frame_number, file_obj
29
30
 
30
31
 
31
- def extract_frames(video_path: Path, output_dir: Path, quality: int, ext: str = "jpg", fps: Optional[float] = None) -> List[Path]:
32
+ def extract_frames(
33
+ video_path: Path,
34
+ output_dir: Path,
35
+ quality: int,
36
+ ext: str = "jpg",
37
+ fps: Optional[float] = None,
38
+ ) -> List[Path]:
32
39
  """Extracts frames from a video file using ffmpeg_wrapper."""
33
40
  # Ensure output directory exists
34
41
  output_dir.mkdir(parents=True, exist_ok=True)
35
42
  return ffmpeg_extract_frames(video_path, output_dir, quality, ext, fps)
36
43
 
37
44
 
38
- def initialize_frame_objects(
39
- video: "VideoFile", extracted_paths: List[Path]
40
- ):
45
+ def initialize_frame_objects(video: "VideoFile", extracted_paths: List[Path]):
41
46
  """
42
47
  Initialize frame objects for the extracted frames and update state.
43
48
  """
44
49
  state = video.get_or_create_state()
45
50
  # Check state before proceeding
46
51
  if state.frames_initialized:
47
- ic(f"Frames already initialized for video {video.uuid}, skipping.")
52
+ ic(f"Frames already initialized for video {video.video_hash}, skipping.")
48
53
  return
49
54
 
50
55
  if not extracted_paths:
51
- ic(f"No extracted paths provided for video {video.uuid}, cannot initialize frames.")
56
+ ic(
57
+ f"No extracted paths provided for video {video.video_hash}, cannot initialize frames."
58
+ )
52
59
  return
53
60
 
54
61
  video.frame_count = len(extracted_paths)
@@ -58,13 +65,19 @@ def initialize_frame_objects(
58
65
  # Prepare frame data (relative paths for storage)
59
66
  frame_dir = video.get_frame_dir_path()
60
67
  if not frame_dir:
61
- raise ValueError(f"Frame directory not set for video {video.uuid}")
68
+ raise ValueError(f"Frame directory not set for video {video.video_hash}")
62
69
 
63
- storage_base_path = Path(video._meta.get_field('raw_file').storage.location) # Get storage root
70
+ storage_base_path = Path(
71
+ video._meta.get_field("raw_file").storage.location
72
+ ) # Get storage root
64
73
 
65
74
  for i, path in tqdm(enumerate(extracted_paths, start=1)):
66
- frame_number = int(path.stem.split("_")[1]) - 1 # Assuming frame_0000001.jpg is frame_number 0
67
- relative_path = path.relative_to(storage_base_path).as_posix() # Path relative to MEDIA_ROOT
75
+ frame_number = (
76
+ int(path.stem.split("_")[1]) - 1
77
+ ) # Assuming frame_0000001.jpg is frame_number 0
78
+ relative_path = path.relative_to(
79
+ storage_base_path
80
+ ).as_posix() # Path relative to MEDIA_ROOT
68
81
 
69
82
  # Create Frame instance (without saving yet)
70
83
  frame_obj_instance = video.create_frame_object(
@@ -83,6 +96,5 @@ def initialize_frame_objects(
83
96
 
84
97
  # Update state and save VideoFile (to save frame_count)
85
98
  state.frames_initialized = True
86
- state.save(update_fields=['frames_initialized'])
87
- video.save(update_fields=['frame_count']) # Save frame_count on VideoFile
88
-
99
+ state.save(update_fields=["frames_initialized"])
100
+ video.save(update_fields=["frame_count"]) # Save frame_count on VideoFile
@@ -1,6 +1,9 @@
1
1
  from typing import Optional
2
2
 
3
- def get_video_key(examination_alias:str, content:str, is_anonymous:bool=False) -> str:
3
+
4
+ def get_video_key(
5
+ examination_alias: str, content: str, is_anonymous: bool = False
6
+ ) -> str:
4
7
  """
5
8
  Generates a video key based on the examination alias, content, and anonymity status.
6
9
  """
@@ -8,8 +11,9 @@ def get_video_key(examination_alias:str, content:str, is_anonymous:bool=False) -
8
11
  return f"{examination_alias}-{content}-anonymous"
9
12
  else:
10
13
  return f"{examination_alias}-{content}-non_anonymous"
11
-
12
- def identify_video_key(video_key:str) -> str:
14
+
15
+
16
+ def identify_video_key(video_key: str) -> str:
13
17
  """
14
18
  Identifies the video key based on the provided string.
15
19
  """
@@ -22,7 +26,9 @@ def identify_video_key(video_key:str) -> str:
22
26
 
23
27
 
24
28
  def get_video_key_regex_by_examination_alias(
25
- examination_alias:Optional[str]=None, content:Optional[str]=None, is_anonymous:Optional[bool]=None
29
+ examination_alias: Optional[str] = None,
30
+ content: Optional[str] = None,
31
+ is_anonymous: Optional[bool] = None,
26
32
  ):
27
33
  """
28
34
  Generates a regex pattern to match video keys based on examination alias, content, and anonymity status.
@@ -37,6 +43,5 @@ def get_video_key_regex_by_examination_alias(
37
43
  pattern += "anonymous" if is_anonymous else "non_anonymous"
38
44
  else:
39
45
  pattern += "(anonymous|non_anonymous)"
40
-
41
- return f"^{pattern}$"
42
46
 
47
+ return f"^{pattern}$"
@@ -9,93 +9,132 @@ from ...exceptions import InsufficientStorageError, VideoProcessingError
9
9
 
10
10
  logger = logging.getLogger(__name__)
11
11
 
12
+
12
13
  class StreamingVideoProcessor:
13
14
  """
14
15
  Streaming video processor for memory-efficient video anonymization.
15
16
  Processes videos in chunks to reduce memory usage and improve performance.
16
17
  """
17
-
18
+
18
19
  def __init__(self, chunk_duration: int = 30, temp_dir: Optional[Path] = None):
19
20
  """
20
21
  Initialize the streaming processor.
21
-
22
+
22
23
  Args:
23
24
  chunk_duration: Duration of each chunk in seconds
24
25
  temp_dir: Temporary directory for processing chunks
25
26
  """
26
27
  self.chunk_duration = chunk_duration
27
- self.temp_dir = Path(temp_dir) if temp_dir else Path(tempfile.gettempdir()) / 'video_streaming'
28
+ self.temp_dir = (
29
+ Path(temp_dir)
30
+ if temp_dir
31
+ else Path(tempfile.gettempdir()) / "video_streaming"
32
+ )
28
33
  self.temp_dir.mkdir(parents=True, exist_ok=True)
29
-
34
+
30
35
  def check_ffmpeg_available(self) -> bool:
31
36
  """Check if FFmpeg is available in the system."""
32
37
  try:
33
- subprocess.run(['ffmpeg', '-version'],
34
- capture_output=True, check=True, timeout=10)
38
+ subprocess.run(
39
+ ["ffmpeg", "-version"], capture_output=True, check=True, timeout=10
40
+ )
35
41
  return True
36
- except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired):
42
+ except (
43
+ subprocess.CalledProcessError,
44
+ FileNotFoundError,
45
+ subprocess.TimeoutExpired,
46
+ ):
37
47
  return False
38
-
48
+
39
49
  def get_video_duration(self, video_path: Path) -> float:
40
50
  """Get video duration in seconds using FFprobe."""
41
51
  try:
42
52
  cmd = [
43
- 'ffprobe', '-v', 'quiet', '-show_entries', 'format=duration',
44
- '-of', 'csv=p=0', str(video_path)
53
+ "ffprobe",
54
+ "-v",
55
+ "quiet",
56
+ "-show_entries",
57
+ "format=duration",
58
+ "-of",
59
+ "csv=p=0",
60
+ str(video_path),
45
61
  ]
46
- result = subprocess.run(cmd, capture_output=True, text=True, check=True, timeout=30)
62
+ result = subprocess.run(
63
+ cmd, capture_output=True, text=True, check=True, timeout=30
64
+ )
47
65
  return float(result.stdout.strip())
48
- except (subprocess.CalledProcessError, ValueError, subprocess.TimeoutExpired) as e:
66
+ except (
67
+ subprocess.CalledProcessError,
68
+ ValueError,
69
+ subprocess.TimeoutExpired,
70
+ ) as e:
49
71
  logger.error(f"Failed to get video duration for {video_path}: {e}")
50
72
  raise VideoProcessingError(f"Could not determine video duration: {e}")
51
-
52
- def split_video_chunks(self, video_path: Path) -> Iterator[Tuple[Path, float, float]]:
73
+
74
+ def split_video_chunks(
75
+ self, video_path: Path
76
+ ) -> Iterator[Tuple[Path, float, float]]:
53
77
  """
54
78
  Split video into chunks for streaming processing.
55
-
79
+
56
80
  Args:
57
81
  video_path: Path to the input video
58
-
82
+
59
83
  Yields:
60
84
  Tuple of (chunk_path, start_time, end_time)
61
85
  """
62
86
  if not self.check_ffmpeg_available():
63
87
  raise VideoProcessingError("FFmpeg not available for video processing")
64
-
88
+
65
89
  try:
66
90
  total_duration = self.get_video_duration(video_path)
67
- logger.info(f"Video duration: {total_duration:.2f}s, splitting into {self.chunk_duration}s chunks")
68
-
91
+ logger.info(
92
+ f"Video duration: {total_duration:.2f}s, splitting into {self.chunk_duration}s chunks"
93
+ )
94
+
69
95
  chunk_count = 0
70
96
  for start_time in range(0, int(total_duration), self.chunk_duration):
71
97
  end_time = min(start_time + self.chunk_duration, total_duration)
72
-
98
+
73
99
  # Create chunk filename
74
- chunk_filename = f"chunk_{chunk_count:04d}_{start_time}_{int(end_time)}.mp4"
100
+ chunk_filename = (
101
+ f"chunk_{chunk_count:04d}_{start_time}_{int(end_time)}.mp4"
102
+ )
75
103
  chunk_path = self.temp_dir / chunk_filename
76
-
104
+
77
105
  # Extract chunk using FFmpeg
78
106
  cmd = [
79
- 'ffmpeg', '-y', # Overwrite output files
80
- '-ss', str(start_time), # Start time
81
- '-i', str(video_path), # Input file
82
- '-t', str(end_time - start_time), # Duration
83
- '-c', 'copy', # Copy streams without re-encoding for speed
84
- '-avoid_negative_ts', 'make_zero', # Handle timestamp issues
85
- str(chunk_path)
107
+ "ffmpeg",
108
+ "-y", # Overwrite output files
109
+ "-ss",
110
+ str(start_time), # Start time
111
+ "-i",
112
+ str(video_path), # Input file
113
+ "-t",
114
+ str(end_time - start_time), # Duration
115
+ "-c",
116
+ "copy", # Copy streams without re-encoding for speed
117
+ "-avoid_negative_ts",
118
+ "make_zero", # Handle timestamp issues
119
+ str(chunk_path),
86
120
  ]
87
-
121
+
88
122
  try:
89
- logger.debug(f"Creating chunk {chunk_count}: {start_time}s-{end_time}s")
90
- result = subprocess.run(cmd, capture_output=True, text=True,
91
- check=True, timeout=300) # 5 minute timeout per chunk
92
-
123
+ logger.debug(
124
+ f"Creating chunk {chunk_count}: {start_time}s-{end_time}s"
125
+ )
126
+ result = subprocess.run(
127
+ cmd, capture_output=True, text=True, check=True, timeout=300
128
+ ) # 5 minute timeout per chunk
129
+
93
130
  if chunk_path.exists() and chunk_path.stat().st_size > 0:
94
131
  yield chunk_path, start_time, end_time
95
132
  chunk_count += 1
96
133
  else:
97
- logger.warning(f"Chunk {chunk_count} was not created or is empty")
98
-
134
+ logger.warning(
135
+ f"Chunk {chunk_count} was not created or is empty"
136
+ )
137
+
99
138
  except subprocess.CalledProcessError as e:
100
139
  logger.error(f"FFmpeg failed for chunk {chunk_count}: {e.stderr}")
101
140
  # Skip this chunk but continue with others
@@ -103,102 +142,119 @@ class StreamingVideoProcessor:
103
142
  except subprocess.TimeoutExpired:
104
143
  logger.error(f"FFmpeg timeout for chunk {chunk_count}")
105
144
  continue
106
-
145
+
107
146
  except Exception as e:
108
147
  logger.error(f"Error in video chunking: {e}")
109
148
  raise VideoProcessingError(f"Video chunking failed: {e}")
110
-
111
- def process_chunk_anonymization(self, chunk_path: Path, anonymizer_func, **kwargs) -> Path:
149
+
150
+ def process_chunk_anonymization(
151
+ self, chunk_path: Path, anonymizer_func, **kwargs
152
+ ) -> Path:
112
153
  """
113
154
  Process a single chunk with the anonymization function.
114
-
155
+
115
156
  Args:
116
157
  chunk_path: Path to the video chunk
117
158
  anonymizer_func: Function to anonymize the chunk
118
159
  **kwargs: Additional arguments for the anonymizer
119
-
160
+
120
161
  Returns:
121
162
  Path to the anonymized chunk
122
163
  """
123
164
  try:
124
- output_path = chunk_path.with_suffix('.anonymized.mp4')
125
-
165
+ output_path = chunk_path.with_suffix(".anonymized.mp4")
166
+
126
167
  # Call the anonymization function
127
168
  result = anonymizer_func(chunk_path, output_path, **kwargs)
128
-
169
+
129
170
  if isinstance(result, Path):
130
171
  return result
131
172
  elif result is True and output_path.exists():
132
173
  return output_path
133
174
  else:
134
- raise VideoProcessingError(f"Anonymization failed for chunk {chunk_path}")
135
-
175
+ raise VideoProcessingError(
176
+ f"Anonymization failed for chunk {chunk_path}"
177
+ )
178
+
136
179
  except Exception as e:
137
180
  logger.error(f"Chunk anonymization failed for {chunk_path}: {e}")
138
181
  raise VideoProcessingError(f"Chunk processing failed: {e}")
139
-
182
+
140
183
  def merge_chunks(self, chunk_paths: list[Path], output_path: Path) -> Path:
141
184
  """
142
185
  Merge anonymized chunks back into a single video.
143
-
186
+
144
187
  Args:
145
188
  chunk_paths: List of paths to anonymized chunks
146
189
  output_path: Path for the final merged video
147
-
190
+
148
191
  Returns:
149
192
  Path to the merged video
150
193
  """
151
194
  if not chunk_paths:
152
195
  raise VideoProcessingError("No chunks to merge")
153
-
196
+
154
197
  try:
155
198
  # Check storage space before merging
156
- total_chunk_size = sum(chunk.stat().st_size for chunk in chunk_paths if chunk.exists())
199
+ total_chunk_size = sum(
200
+ chunk.stat().st_size for chunk in chunk_paths if chunk.exists()
201
+ )
157
202
  free_space = shutil.disk_usage(output_path.parent).free
158
-
203
+
159
204
  if free_space < total_chunk_size * 1.2: # 20% safety margin
160
205
  raise InsufficientStorageError(
161
206
  f"Insufficient space for merging. Required: {total_chunk_size * 1.2 / 1e9:.1f} GB, "
162
207
  f"Available: {free_space / 1e9:.1f} GB",
163
208
  required_space=int(total_chunk_size * 1.2),
164
- available_space=free_space
209
+ available_space=free_space,
165
210
  )
166
-
211
+
167
212
  # Create concat file for FFmpeg
168
213
  concat_file = self.temp_dir / f"concat_{output_path.stem}.txt"
169
-
170
- with open(concat_file, 'w') as f:
214
+
215
+ with open(concat_file, "w") as f:
171
216
  for chunk_path in chunk_paths:
172
217
  if chunk_path.exists():
173
218
  # Use relative paths in concat file for better portability
174
219
  f.write(f"file '{chunk_path.name}'\n")
175
-
220
+
176
221
  # Merge using FFmpeg concat demuxer
177
222
  cmd = [
178
- 'ffmpeg', '-y', # Overwrite output
179
- '-f', 'concat', # Use concat demuxer
180
- '-safe', '0', # Allow unsafe file paths
181
- '-i', str(concat_file), # Input concat file
182
- '-c', 'copy', # Copy streams without re-encoding
183
- str(output_path)
223
+ "ffmpeg",
224
+ "-y", # Overwrite output
225
+ "-f",
226
+ "concat", # Use concat demuxer
227
+ "-safe",
228
+ "0", # Allow unsafe file paths
229
+ "-i",
230
+ str(concat_file), # Input concat file
231
+ "-c",
232
+ "copy", # Copy streams without re-encoding
233
+ str(output_path),
184
234
  ]
185
-
235
+
186
236
  logger.info(f"Merging {len(chunk_paths)} chunks into {output_path}")
187
-
237
+
188
238
  # Change working directory to temp_dir for relative paths
189
- result = subprocess.run(cmd, cwd=str(self.temp_dir),
190
- capture_output=True, text=True, check=True, timeout=600)
191
-
239
+ result = subprocess.run(
240
+ cmd,
241
+ cwd=str(self.temp_dir),
242
+ capture_output=True,
243
+ text=True,
244
+ check=True,
245
+ timeout=600,
246
+ )
247
+
192
248
  if not output_path.exists() or output_path.stat().st_size == 0:
193
249
  raise VideoProcessingError("Merged video was not created or is empty")
194
-
250
+
195
251
  logger.info(f"Successfully merged video: {output_path}")
196
-
252
+
197
253
  # Clean up concat file
198
254
  concat_file.unlink(missing_ok=True)
199
-
255
+
200
256
  return output_path
201
-
257
+
202
258
  except subprocess.CalledProcessError as e:
203
259
  logger.error(f"FFmpeg merge failed: {e.stderr}")
204
260
  raise VideoProcessingError(f"Video merging failed: {e.stderr}")
@@ -208,80 +264,98 @@ class StreamingVideoProcessor:
208
264
  except Exception as e:
209
265
  logger.error(f"Unexpected error during merge: {e}")
210
266
  raise VideoProcessingError(f"Video merging failed: {e}")
211
-
212
- def process_video_streaming(self, input_path: Path, output_path: Path,
213
- anonymizer_func, progress_callback=None, **kwargs) -> Path:
267
+
268
+ def process_video_streaming(
269
+ self,
270
+ input_path: Path,
271
+ output_path: Path,
272
+ anonymizer_func,
273
+ progress_callback=None,
274
+ **kwargs,
275
+ ) -> Path:
214
276
  """
215
277
  Process a video using streaming approach for memory efficiency.
216
-
278
+
217
279
  Args:
218
280
  input_path: Path to input video
219
281
  output_path: Path for output video
220
282
  anonymizer_func: Function to anonymize video chunks
221
283
  progress_callback: Optional callback for progress updates
222
284
  **kwargs: Additional arguments for anonymizer
223
-
285
+
224
286
  Returns:
225
287
  Path to the processed video
226
288
  """
227
289
  processed_chunks = []
228
290
  total_chunks = 0
229
-
291
+
230
292
  try:
231
- logger.info(f"Starting streaming video processing: {input_path} -> {output_path}")
232
-
293
+ logger.info(
294
+ f"Starting streaming video processing: {input_path} -> {output_path}"
295
+ )
296
+
233
297
  # First pass: count total chunks for progress tracking
234
298
  chunk_list = list(self.split_video_chunks(input_path))
235
299
  total_chunks = len(chunk_list)
236
-
300
+
237
301
  if total_chunks == 0:
238
- raise VideoProcessingError("No chunks were created from the input video")
239
-
302
+ raise VideoProcessingError(
303
+ "No chunks were created from the input video"
304
+ )
305
+
240
306
  logger.info(f"Processing {total_chunks} chunks")
241
-
307
+
242
308
  # Process each chunk
243
309
  for i, (chunk_path, start_time, end_time) in enumerate(chunk_list):
244
310
  try:
245
- logger.debug(f"Processing chunk {i+1}/{total_chunks}: {chunk_path}")
246
-
311
+ logger.debug(
312
+ f"Processing chunk {i + 1}/{total_chunks}: {chunk_path}"
313
+ )
314
+
247
315
  # Process the chunk
248
316
  processed_chunk = self.process_chunk_anonymization(
249
317
  chunk_path, anonymizer_func, **kwargs
250
318
  )
251
319
  processed_chunks.append(processed_chunk)
252
-
320
+
253
321
  # Update progress
254
322
  if progress_callback:
255
- progress = int((i + 1) / total_chunks * 80) # Reserve 20% for merging
256
- progress_callback(progress, f"Processed chunk {i+1}/{total_chunks}")
257
-
323
+ progress = int(
324
+ (i + 1) / total_chunks * 80
325
+ ) # Reserve 20% for merging
326
+ progress_callback(
327
+ progress, f"Processed chunk {i + 1}/{total_chunks}"
328
+ )
329
+
258
330
  # Clean up original chunk to save space
259
331
  chunk_path.unlink(missing_ok=True)
260
-
332
+
261
333
  except Exception as e:
262
334
  logger.error(f"Failed to process chunk {i}: {e}")
263
335
  # Clean up failed chunk
264
336
  chunk_path.unlink(missing_ok=True)
265
337
  # Continue with other chunks
266
338
  continue
267
-
339
+
268
340
  if not processed_chunks:
269
341
  raise VideoProcessingError("No chunks were successfully processed")
270
-
342
+
271
343
  # Update progress for merging phase
272
344
  if progress_callback:
273
- progress_callback(80, f"Merging {len(processed_chunks)} processed chunks...")
274
-
345
+ progress_callback(
346
+ 80, f"Merging {len(processed_chunks)} processed chunks..."
347
+ )
348
+
275
349
  # Merge processed chunks
276
350
  final_output = self.merge_chunks(processed_chunks, output_path)
277
-
351
+
278
352
  # Final progress update
279
353
  if progress_callback:
280
354
  progress_callback(100, "Video processing completed")
281
-
355
+
282
356
  logger.info(f"Streaming video processing completed: {final_output}")
283
357
  return final_output
284
-
358
+
285
359
  except (InsufficientStorageError, VideoProcessingError):
286
360
  # Re-raise these specific errors as-is
287
361
  raise
@@ -291,7 +365,7 @@ class StreamingVideoProcessor:
291
365
  finally:
292
366
  # Clean up all temporary chunks
293
367
  self.cleanup_chunks(processed_chunks)
294
-
368
+
295
369
  def cleanup_chunks(self, chunk_paths: list[Path]) -> None:
296
370
  """Clean up temporary chunk files."""
297
371
  for chunk_path in chunk_paths:
@@ -301,7 +375,7 @@ class StreamingVideoProcessor:
301
375
  logger.debug(f"Cleaned up chunk: {chunk_path}")
302
376
  except Exception as e:
303
377
  logger.warning(f"Failed to clean up chunk {chunk_path}: {e}")
304
-
378
+
305
379
  def cleanup_temp_dir(self) -> None:
306
380
  """Clean up the entire temporary directory."""
307
381
  try:
@@ -309,4 +383,4 @@ class StreamingVideoProcessor:
309
383
  shutil.rmtree(self.temp_dir)
310
384
  logger.debug(f"Cleaned up temp directory: {self.temp_dir}")
311
385
  except Exception as e:
312
- logger.warning(f"Failed to clean up temp directory {self.temp_dir}: {e}")
386
+ logger.warning(f"Failed to clean up temp directory {self.temp_dir}: {e}")