endoreg-db 0.8.6.1__py3-none-any.whl → 0.8.8.0__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 (360) hide show
  1. endoreg_db/authz/auth.py +74 -0
  2. endoreg_db/authz/backends.py +168 -0
  3. endoreg_db/authz/management/commands/list_routes.py +18 -0
  4. endoreg_db/authz/middleware.py +83 -0
  5. endoreg_db/authz/permissions.py +127 -0
  6. endoreg_db/authz/policy.py +218 -0
  7. endoreg_db/authz/views_auth.py +66 -0
  8. endoreg_db/config/env.py +13 -8
  9. endoreg_db/data/__init__.py +8 -31
  10. endoreg_db/data/_examples/disease.yaml +55 -0
  11. endoreg_db/data/_examples/disease_classification.yaml +13 -0
  12. endoreg_db/data/_examples/disease_classification_choice.yaml +62 -0
  13. endoreg_db/data/_examples/event.yaml +64 -0
  14. endoreg_db/data/_examples/examination.yaml +72 -0
  15. endoreg_db/data/_examples/finding/anatomy_colon.yaml +128 -0
  16. endoreg_db/data/_examples/finding/colonoscopy.yaml +40 -0
  17. endoreg_db/data/_examples/finding/colonoscopy_bowel_prep.yaml +56 -0
  18. endoreg_db/data/_examples/finding/complication.yaml +16 -0
  19. endoreg_db/data/_examples/finding/data.yaml +105 -0
  20. endoreg_db/data/_examples/finding/examination_setting.yaml +16 -0
  21. endoreg_db/data/_examples/finding/medication_related.yaml +18 -0
  22. endoreg_db/data/_examples/finding/outcome.yaml +12 -0
  23. endoreg_db/data/_examples/finding_classification/colonoscopy_bowel_preparation.yaml +68 -0
  24. endoreg_db/data/_examples/finding_classification/colonoscopy_jnet.yaml +22 -0
  25. endoreg_db/data/_examples/finding_classification/colonoscopy_kudo.yaml +25 -0
  26. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_circularity.yaml +20 -0
  27. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_planarity.yaml +24 -0
  28. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_size.yaml +68 -0
  29. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_surface.yaml +20 -0
  30. endoreg_db/data/_examples/finding_classification/colonoscopy_location.yaml +80 -0
  31. endoreg_db/data/_examples/finding_classification/colonoscopy_lst.yaml +21 -0
  32. endoreg_db/data/_examples/finding_classification/colonoscopy_nice.yaml +20 -0
  33. endoreg_db/data/_examples/finding_classification/colonoscopy_paris.yaml +26 -0
  34. endoreg_db/data/_examples/finding_classification/colonoscopy_sano.yaml +22 -0
  35. endoreg_db/data/_examples/finding_classification/colonoscopy_summary.yaml +53 -0
  36. endoreg_db/data/_examples/finding_classification/complication_generic.yaml +25 -0
  37. endoreg_db/data/_examples/finding_classification/examination_setting_generic.yaml +40 -0
  38. endoreg_db/data/_examples/finding_classification/histology_colo.yaml +51 -0
  39. endoreg_db/data/_examples/finding_classification/intervention_required.yaml +26 -0
  40. endoreg_db/data/_examples/finding_classification/medication_related.yaml +23 -0
  41. endoreg_db/data/_examples/finding_classification/visualized.yaml +33 -0
  42. endoreg_db/data/_examples/finding_classification_choice/bowel_preparation.yaml +78 -0
  43. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_circularity_default.yaml +32 -0
  44. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_jnet.yaml +15 -0
  45. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_kudo.yaml +23 -0
  46. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_lst.yaml +15 -0
  47. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_nice.yaml +17 -0
  48. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_paris.yaml +57 -0
  49. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_planarity_default.yaml +49 -0
  50. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_sano.yaml +14 -0
  51. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_surface_intact_default.yaml +36 -0
  52. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_location.yaml +229 -0
  53. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_not_complete_reason.yaml +19 -0
  54. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_size.yaml +82 -0
  55. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_summary_worst_finding.yaml +15 -0
  56. endoreg_db/data/_examples/finding_classification_choice/complication_generic_types.yaml +15 -0
  57. endoreg_db/data/_examples/finding_classification_choice/examination_setting_generic_types.yaml +15 -0
  58. endoreg_db/data/_examples/finding_classification_choice/histology.yaml +24 -0
  59. endoreg_db/data/_examples/finding_classification_choice/histology_polyp.yaml +20 -0
  60. endoreg_db/data/_examples/finding_classification_choice/outcome.yaml +19 -0
  61. endoreg_db/data/_examples/finding_classification_choice/yes_no_na.yaml +11 -0
  62. endoreg_db/data/_examples/finding_classification_type/colonoscopy_basic.yaml +48 -0
  63. endoreg_db/data/_examples/finding_intervention/endoscopy.yaml +43 -0
  64. endoreg_db/data/_examples/finding_intervention/endoscopy_colonoscopy.yaml +168 -0
  65. endoreg_db/data/_examples/finding_intervention/endoscopy_egd.yaml +128 -0
  66. endoreg_db/data/_examples/finding_intervention/endoscopy_ercp.yaml +32 -0
  67. endoreg_db/data/_examples/finding_intervention/endoscopy_eus_lower.yaml +9 -0
  68. endoreg_db/data/_examples/finding_intervention/endoscopy_eus_upper.yaml +36 -0
  69. endoreg_db/data/_examples/finding_intervention_type/endoscopy.yaml +15 -0
  70. endoreg_db/data/_examples/finding_type/data.yaml +43 -0
  71. endoreg_db/data/_examples/requirement/age.yaml +26 -0
  72. endoreg_db/data/_examples/requirement/colonoscopy_baseline_austria.yaml +45 -0
  73. endoreg_db/data/_examples/requirement/disease_cardiovascular.yaml +79 -0
  74. endoreg_db/data/_examples/requirement/disease_classification_choice_cardiovascular.yaml +41 -0
  75. endoreg_db/data/_examples/requirement/disease_hepatology.yaml +12 -0
  76. endoreg_db/data/_examples/requirement/disease_misc.yaml +12 -0
  77. endoreg_db/data/_examples/requirement/disease_renal.yaml +96 -0
  78. endoreg_db/data/_examples/requirement/endoscopy_bleeding_risk.yaml +59 -0
  79. endoreg_db/data/_examples/requirement/event_cardiology.yaml +251 -0
  80. endoreg_db/data/_examples/requirement/event_requirements.yaml +145 -0
  81. endoreg_db/data/_examples/requirement/finding_colon_polyp.yaml +50 -0
  82. endoreg_db/data/_examples/requirement/gender.yaml +25 -0
  83. endoreg_db/data/_examples/requirement/lab_value.yaml +441 -0
  84. endoreg_db/data/_examples/requirement/medication.yaml +93 -0
  85. endoreg_db/data/_examples/requirement_operator/age.yaml +13 -0
  86. endoreg_db/data/_examples/requirement_operator/lab_operators.yaml +129 -0
  87. endoreg_db/data/_examples/requirement_operator/model_operators.yaml +96 -0
  88. endoreg_db/data/_examples/requirement_set/01_endoscopy_generic.yaml +48 -0
  89. endoreg_db/data/_examples/requirement_set/colonoscopy_austria_screening.yaml +57 -0
  90. endoreg_db/data/_examples/yaml_examples.xlsx +0 -0
  91. endoreg_db/data/ai_model_meta/default_multilabel_classification.yaml +4 -3
  92. endoreg_db/data/event_classification/data.yaml +4 -0
  93. endoreg_db/data/event_classification_choice/data.yaml +9 -0
  94. endoreg_db/data/finding_classification/colonoscopy_bowel_preparation.yaml +43 -70
  95. endoreg_db/data/finding_classification/colonoscopy_lesion_size.yaml +22 -52
  96. endoreg_db/data/finding_classification/colonoscopy_location.yaml +31 -62
  97. endoreg_db/data/finding_classification/histology_colo.yaml +28 -36
  98. endoreg_db/data/requirement/colon_polyp_intervention.yaml +49 -0
  99. endoreg_db/data/requirement/coloreg_colon_polyp.yaml +49 -0
  100. endoreg_db/data/requirement_set/01_endoscopy_generic.yaml +31 -12
  101. endoreg_db/data/requirement_set/01_laboratory.yaml +13 -0
  102. endoreg_db/data/requirement_set/02_endoscopy_bleeding_risk.yaml +46 -0
  103. endoreg_db/data/requirement_set/90_coloreg.yaml +178 -0
  104. endoreg_db/data/requirement_set/_old_ +109 -0
  105. endoreg_db/data/requirement_set_type/data.yaml +21 -0
  106. endoreg_db/data/setup_config.yaml +4 -4
  107. endoreg_db/data/tag/requirement_set_tags.yaml +21 -0
  108. endoreg_db/exceptions.py +5 -2
  109. endoreg_db/helpers/data_loader.py +1 -1
  110. endoreg_db/management/commands/create_model_meta_from_huggingface.py +21 -10
  111. endoreg_db/management/commands/create_multilabel_model_meta.py +299 -129
  112. endoreg_db/management/commands/import_video.py +9 -10
  113. endoreg_db/management/commands/import_video_with_classification.py +1 -1
  114. endoreg_db/management/commands/init_default_ai_model.py +1 -1
  115. endoreg_db/management/commands/list_routes.py +18 -0
  116. endoreg_db/management/commands/load_center_data.py +12 -12
  117. endoreg_db/management/commands/load_requirement_data.py +60 -31
  118. endoreg_db/management/commands/load_requirement_set_tags.py +95 -0
  119. endoreg_db/management/commands/setup_endoreg_db.py +3 -3
  120. endoreg_db/management/commands/storage_management.py +271 -203
  121. endoreg_db/migrations/0001_initial.py +1799 -1300
  122. endoreg_db/migrations/0002_requirementset_depends_on.py +18 -0
  123. endoreg_db/migrations/_old/0001_initial.py +1857 -0
  124. endoreg_db/migrations/_old/0004_employee_city_employee_post_code_employee_street_and_more.py +68 -0
  125. endoreg_db/migrations/_old/0004_remove_casetemplate_rules_and_more.py +77 -0
  126. endoreg_db/migrations/_old/0005_merge_20251111_1003.py +14 -0
  127. endoreg_db/migrations/_old/0006_sensitivemeta_anonymized_text_and_more.py +68 -0
  128. endoreg_db/migrations/_old/0007_remove_rule_attribute_dtype_remove_rule_rule_type_and_more.py +89 -0
  129. endoreg_db/migrations/_old/0008_remove_event_event_classification_and_more.py +27 -0
  130. endoreg_db/migrations/_old/0009_alter_modelmeta_options_and_more.py +21 -0
  131. endoreg_db/models/__init__.py +78 -123
  132. endoreg_db/models/administration/__init__.py +21 -42
  133. endoreg_db/models/administration/ai/active_model.py +2 -2
  134. endoreg_db/models/administration/ai/ai_model.py +7 -6
  135. endoreg_db/models/administration/case/__init__.py +1 -15
  136. endoreg_db/models/administration/case/case.py +3 -3
  137. endoreg_db/models/administration/case/case_template/__init__.py +2 -14
  138. endoreg_db/models/administration/case/case_template/case_template.py +2 -124
  139. endoreg_db/models/administration/case/case_template/case_template_rule.py +2 -268
  140. endoreg_db/models/administration/case/case_template/case_template_rule_value.py +2 -85
  141. endoreg_db/models/administration/case/case_template/case_template_type.py +2 -25
  142. endoreg_db/models/administration/center/center.py +33 -19
  143. endoreg_db/models/administration/center/center_product.py +12 -9
  144. endoreg_db/models/administration/center/center_resource.py +25 -19
  145. endoreg_db/models/administration/center/center_shift.py +21 -17
  146. endoreg_db/models/administration/center/center_waste.py +16 -8
  147. endoreg_db/models/administration/person/__init__.py +2 -0
  148. endoreg_db/models/administration/person/employee/employee.py +10 -5
  149. endoreg_db/models/administration/person/employee/employee_qualification.py +9 -4
  150. endoreg_db/models/administration/person/employee/employee_type.py +12 -6
  151. endoreg_db/models/administration/person/examiner/examiner.py +13 -11
  152. endoreg_db/models/administration/person/patient/__init__.py +2 -0
  153. endoreg_db/models/administration/person/patient/patient.py +103 -100
  154. endoreg_db/models/administration/person/patient/patient_external_id.py +37 -0
  155. endoreg_db/models/administration/person/person.py +4 -0
  156. endoreg_db/models/administration/person/profession/__init__.py +8 -4
  157. endoreg_db/models/administration/person/user/portal_user_information.py +11 -7
  158. endoreg_db/models/administration/product/product.py +20 -15
  159. endoreg_db/models/administration/product/product_material.py +17 -18
  160. endoreg_db/models/administration/product/product_weight.py +12 -8
  161. endoreg_db/models/administration/product/reference_product.py +23 -55
  162. endoreg_db/models/administration/qualification/qualification.py +7 -3
  163. endoreg_db/models/administration/qualification/qualification_type.py +7 -3
  164. endoreg_db/models/administration/shift/scheduled_days.py +8 -5
  165. endoreg_db/models/administration/shift/shift.py +16 -12
  166. endoreg_db/models/administration/shift/shift_type.py +23 -31
  167. endoreg_db/models/label/__init__.py +7 -8
  168. endoreg_db/models/label/annotation/image_classification.py +10 -9
  169. endoreg_db/models/label/annotation/video_segmentation_annotation.py +8 -5
  170. endoreg_db/models/label/label.py +15 -15
  171. endoreg_db/models/label/label_set.py +19 -6
  172. endoreg_db/models/label/label_type.py +1 -1
  173. endoreg_db/models/label/label_video_segment/_create_from_video.py +5 -8
  174. endoreg_db/models/label/label_video_segment/label_video_segment.py +76 -102
  175. endoreg_db/models/label/video_segmentation_label.py +4 -0
  176. endoreg_db/models/label/video_segmentation_labelset.py +4 -3
  177. endoreg_db/models/media/frame/frame.py +22 -22
  178. endoreg_db/models/media/pdf/raw_pdf.py +110 -182
  179. endoreg_db/models/media/pdf/report_file.py +25 -29
  180. endoreg_db/models/media/pdf/report_reader/report_reader_config.py +30 -46
  181. endoreg_db/models/media/pdf/report_reader/report_reader_flag.py +23 -7
  182. endoreg_db/models/media/video/__init__.py +1 -0
  183. endoreg_db/models/media/video/create_from_file.py +48 -56
  184. endoreg_db/models/media/video/pipe_2.py +8 -9
  185. endoreg_db/models/media/video/video_file.py +150 -108
  186. endoreg_db/models/media/video/video_file_ai.py +288 -74
  187. endoreg_db/models/media/video/video_file_anonymize.py +38 -38
  188. endoreg_db/models/media/video/video_file_frames/__init__.py +3 -1
  189. endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +6 -8
  190. endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +7 -9
  191. endoreg_db/models/media/video/video_file_frames/_delete_frames.py +9 -8
  192. endoreg_db/models/media/video/video_file_frames/_extract_frames.py +38 -45
  193. endoreg_db/models/media/video/video_file_frames/_get_frame.py +6 -8
  194. endoreg_db/models/media/video/video_file_frames/_get_frame_number.py +4 -18
  195. endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +4 -3
  196. endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +7 -6
  197. endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +6 -8
  198. endoreg_db/models/media/video/video_file_frames/_get_frames.py +6 -8
  199. endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +15 -25
  200. endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +26 -23
  201. endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +23 -14
  202. endoreg_db/models/media/video/video_file_io.py +109 -62
  203. endoreg_db/models/media/video/video_file_meta/get_crop_template.py +3 -3
  204. endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +5 -3
  205. endoreg_db/models/media/video/video_file_meta/get_fps.py +37 -34
  206. endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +19 -25
  207. endoreg_db/models/media/video/video_file_meta/text_meta.py +41 -38
  208. endoreg_db/models/media/video/video_file_meta/video_meta.py +14 -7
  209. endoreg_db/models/media/video/video_file_segments.py +24 -17
  210. endoreg_db/models/media/video/video_metadata.py +19 -35
  211. endoreg_db/models/media/video/video_processing.py +96 -95
  212. endoreg_db/models/medical/contraindication/__init__.py +13 -3
  213. endoreg_db/models/medical/disease.py +22 -16
  214. endoreg_db/models/medical/event.py +31 -18
  215. endoreg_db/models/medical/examination/__init__.py +13 -6
  216. endoreg_db/models/medical/examination/examination.py +17 -18
  217. endoreg_db/models/medical/examination/examination_indication.py +26 -25
  218. endoreg_db/models/medical/examination/examination_time.py +16 -6
  219. endoreg_db/models/medical/examination/examination_time_type.py +9 -6
  220. endoreg_db/models/medical/examination/examination_type.py +3 -4
  221. endoreg_db/models/medical/finding/finding.py +38 -39
  222. endoreg_db/models/medical/finding/finding_classification.py +37 -48
  223. endoreg_db/models/medical/finding/finding_intervention.py +27 -22
  224. endoreg_db/models/medical/finding/finding_type.py +13 -12
  225. endoreg_db/models/medical/hardware/endoscope.py +20 -26
  226. endoreg_db/models/medical/hardware/endoscopy_processor.py +2 -2
  227. endoreg_db/models/medical/laboratory/lab_value.py +62 -91
  228. endoreg_db/models/medical/medication/medication.py +22 -10
  229. endoreg_db/models/medical/medication/medication_indication.py +29 -3
  230. endoreg_db/models/medical/medication/medication_indication_type.py +25 -14
  231. endoreg_db/models/medical/medication/medication_intake_time.py +31 -19
  232. endoreg_db/models/medical/medication/medication_schedule.py +27 -16
  233. endoreg_db/models/medical/organ/__init__.py +15 -12
  234. endoreg_db/models/medical/patient/medication_examples.py +1 -5
  235. endoreg_db/models/medical/patient/patient_disease.py +20 -23
  236. endoreg_db/models/medical/patient/patient_event.py +19 -22
  237. endoreg_db/models/medical/patient/patient_examination.py +48 -54
  238. endoreg_db/models/medical/patient/patient_examination_indication.py +16 -14
  239. endoreg_db/models/medical/patient/patient_finding.py +122 -139
  240. endoreg_db/models/medical/patient/patient_finding_classification.py +44 -49
  241. endoreg_db/models/medical/patient/patient_finding_intervention.py +8 -19
  242. endoreg_db/models/medical/patient/patient_lab_sample.py +28 -23
  243. endoreg_db/models/medical/patient/patient_lab_value.py +82 -89
  244. endoreg_db/models/medical/patient/patient_medication.py +27 -38
  245. endoreg_db/models/medical/patient/patient_medication_schedule.py +28 -36
  246. endoreg_db/models/medical/risk/risk.py +7 -6
  247. endoreg_db/models/medical/risk/risk_type.py +8 -5
  248. endoreg_db/models/metadata/model_meta.py +60 -29
  249. endoreg_db/models/metadata/model_meta_logic.py +125 -18
  250. endoreg_db/models/metadata/pdf_meta.py +19 -24
  251. endoreg_db/models/metadata/sensitive_meta.py +102 -85
  252. endoreg_db/models/metadata/sensitive_meta_logic.py +192 -173
  253. endoreg_db/models/metadata/video_meta.py +51 -31
  254. endoreg_db/models/metadata/video_prediction_logic.py +16 -23
  255. endoreg_db/models/metadata/video_prediction_meta.py +29 -33
  256. endoreg_db/models/other/distribution/date_value_distribution.py +89 -29
  257. endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +21 -5
  258. endoreg_db/models/other/distribution/numeric_value_distribution.py +114 -53
  259. endoreg_db/models/other/distribution/single_categorical_value_distribution.py +4 -3
  260. endoreg_db/models/other/emission/emission_factor.py +18 -8
  261. endoreg_db/models/other/gender.py +10 -5
  262. endoreg_db/models/other/information_source.py +25 -25
  263. endoreg_db/models/other/material.py +9 -5
  264. endoreg_db/models/other/resource.py +6 -4
  265. endoreg_db/models/other/tag.py +10 -5
  266. endoreg_db/models/other/transport_route.py +13 -8
  267. endoreg_db/models/other/unit.py +10 -6
  268. endoreg_db/models/other/waste.py +6 -5
  269. endoreg_db/models/requirement/requirement.py +580 -272
  270. endoreg_db/models/requirement/requirement_error.py +85 -0
  271. endoreg_db/models/requirement/requirement_evaluation/evaluate_with_dependencies.py +268 -0
  272. endoreg_db/models/requirement/requirement_evaluation/operator_evaluation_models.py +3 -6
  273. endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +90 -64
  274. endoreg_db/models/requirement/requirement_operator.py +36 -33
  275. endoreg_db/models/requirement/requirement_set.py +74 -57
  276. endoreg_db/models/state/__init__.py +4 -4
  277. endoreg_db/models/state/abstract.py +2 -2
  278. endoreg_db/models/state/anonymization.py +12 -0
  279. endoreg_db/models/state/audit_ledger.py +46 -47
  280. endoreg_db/models/state/label_video_segment.py +9 -0
  281. endoreg_db/models/state/raw_pdf.py +40 -46
  282. endoreg_db/models/state/sensitive_meta.py +6 -2
  283. endoreg_db/models/state/video.py +58 -53
  284. endoreg_db/models/upload_job.py +32 -55
  285. endoreg_db/models/utils.py +1 -2
  286. endoreg_db/root_urls.py +21 -2
  287. endoreg_db/serializers/__init__.py +0 -2
  288. endoreg_db/serializers/anonymization.py +18 -10
  289. endoreg_db/serializers/meta/report_meta.py +1 -1
  290. endoreg_db/serializers/meta/sensitive_meta_detail.py +63 -118
  291. endoreg_db/serializers/misc/file_overview.py +11 -99
  292. endoreg_db/serializers/requirements/requirement_sets.py +92 -22
  293. endoreg_db/serializers/video/segmentation.py +2 -1
  294. endoreg_db/serializers/video/video_processing_history.py +20 -5
  295. endoreg_db/services/anonymization.py +75 -73
  296. endoreg_db/services/lookup_service.py +37 -24
  297. endoreg_db/services/pdf_import.py +166 -68
  298. endoreg_db/services/storage_aware_video_processor.py +140 -114
  299. endoreg_db/services/video_import.py +193 -283
  300. endoreg_db/urls/__init__.py +7 -20
  301. endoreg_db/urls/media.py +108 -67
  302. endoreg_db/urls/root_urls.py +29 -0
  303. endoreg_db/utils/__init__.py +15 -5
  304. endoreg_db/utils/ai/multilabel_classification_net.py +116 -20
  305. endoreg_db/utils/case_generator/__init__.py +3 -0
  306. endoreg_db/utils/dataloader.py +88 -16
  307. endoreg_db/utils/defaults/set_default_center.py +32 -0
  308. endoreg_db/utils/names.py +22 -16
  309. endoreg_db/utils/permissions.py +2 -1
  310. endoreg_db/utils/pipelines/process_video_dir.py +1 -1
  311. endoreg_db/utils/requirement_operator_logic/model_evaluators.py +414 -127
  312. endoreg_db/utils/setup_config.py +8 -5
  313. endoreg_db/utils/storage.py +115 -0
  314. endoreg_db/utils/validate_endo_roi.py +8 -2
  315. endoreg_db/utils/video/ffmpeg_wrapper.py +184 -188
  316. endoreg_db/views/__init__.py +0 -10
  317. endoreg_db/views/anonymization/media_management.py +198 -163
  318. endoreg_db/views/anonymization/overview.py +4 -1
  319. endoreg_db/views/anonymization/validate.py +174 -40
  320. endoreg_db/views/media/__init__.py +2 -0
  321. endoreg_db/views/media/pdf_media.py +131 -152
  322. endoreg_db/views/media/sensitive_metadata.py +46 -6
  323. endoreg_db/views/media/video_media.py +89 -82
  324. endoreg_db/views/media/video_segments.py +2 -3
  325. endoreg_db/views/meta/sensitive_meta_detail.py +0 -63
  326. endoreg_db/views/patient/patient.py +5 -4
  327. endoreg_db/views/pdf/pdf_stream.py +20 -21
  328. endoreg_db/views/pdf/reimport.py +11 -32
  329. endoreg_db/views/requirement/evaluate.py +188 -187
  330. endoreg_db/views/requirement/lookup.py +17 -3
  331. endoreg_db/views/requirement/requirement_utils.py +89 -0
  332. endoreg_db/views/video/__init__.py +0 -2
  333. endoreg_db/views/video/correction.py +2 -2
  334. {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/METADATA +7 -3
  335. {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/RECORD +341 -245
  336. endoreg_db/models/administration/permissions/__init__.py +0 -44
  337. endoreg_db/models/media/video/video_file_frames.py +0 -0
  338. endoreg_db/models/metadata/frame_ocr_result.py +0 -0
  339. endoreg_db/models/rule/__init__.py +0 -13
  340. endoreg_db/models/rule/rule.py +0 -27
  341. endoreg_db/models/rule/rule_applicator.py +0 -224
  342. endoreg_db/models/rule/rule_attribute_dtype.py +0 -17
  343. endoreg_db/models/rule/rule_type.py +0 -20
  344. endoreg_db/models/rule/ruleset.py +0 -17
  345. endoreg_db/serializers/video/video_metadata.py +0 -105
  346. endoreg_db/urls/report.py +0 -48
  347. endoreg_db/urls/video.py +0 -61
  348. endoreg_db/utils/case_generator/case_generator.py +0 -159
  349. endoreg_db/utils/case_generator/utils.py +0 -30
  350. endoreg_db/views/report/__init__.py +0 -9
  351. endoreg_db/views/report/report_list.py +0 -112
  352. endoreg_db/views/report/report_with_secure_url.py +0 -28
  353. endoreg_db/views/report/start_examination.py +0 -7
  354. endoreg_db/views.py +0 -0
  355. /endoreg_db/data/{requirement_set → _examples/requirement_set}/endoscopy_bleeding_risk.yaml +0 -0
  356. /endoreg_db/migrations/{0002_add_video_correction_models.py → _old/0002_add_video_correction_models.py} +0 -0
  357. /endoreg_db/migrations/{0003_add_center_display_name.py → _old/0003_add_center_display_name.py} +0 -0
  358. /endoreg_db/{models/media/video/refactor_plan.md → views/pdf/pdf_stream_views.py} +0 -0
  359. {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/WHEEL +0 -0
  360. {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,13 +1,14 @@
1
- import os
2
- import subprocess
3
1
  import json
4
2
  import logging
3
+ import os
4
+ import shutil
5
+ import subprocess
5
6
  from functools import lru_cache
6
7
  from pathlib import Path
7
- from typing import List, Dict, Optional, Tuple
8
+ from typing import Dict, List, Optional, Tuple
9
+
8
10
  import cv2
9
11
  from tqdm import tqdm
10
- import shutil
11
12
 
12
13
  logger = logging.getLogger("ffmpeg_wrapper")
13
14
 
@@ -28,13 +29,9 @@ def _resolve_ffmpeg_executable() -> Optional[str]:
28
29
 
29
30
  # 2) Django settings overrides (if Django is configured)
30
31
  try:
31
- from django.conf import settings # type: ignore
32
+ from django.conf import settings
32
33
 
33
- env_candidates.extend(
34
- getattr(settings, attr)
35
- for attr in ("FFMPEG_EXECUTABLE", "FFMPEG_BINARY", "FFMPEG_PATH")
36
- if hasattr(settings, attr)
37
- )
34
+ env_candidates.extend(getattr(settings, attr) for attr in ("FFMPEG_EXECUTABLE", "FFMPEG_BINARY", "FFMPEG_PATH") if hasattr(settings, attr))
38
35
  except Exception:
39
36
  # Django might not be configured for every consumer
40
37
  pass
@@ -76,35 +73,27 @@ def _resolve_ffmpeg_executable() -> Optional[str]:
76
73
 
77
74
  return None
78
75
 
76
+
79
77
  def _detect_nvenc_support() -> bool:
80
78
  """
81
79
  Detect if NVIDIA NVENC hardware acceleration is available.
82
-
80
+
83
81
  Returns:
84
82
  True if NVENC is available, False otherwise
85
83
  """
86
84
  try:
87
85
  # Test NVENC availability with a minimal command (minimum size for NVENC)
88
- cmd = [
89
- 'ffmpeg', '-f', 'lavfi', '-i', 'testsrc=duration=1:size=256x256:rate=1',
90
- '-c:v', 'h264_nvenc', '-preset', 'p1', '-f', 'null', '-'
91
- ]
92
-
93
- result = subprocess.run(
94
- cmd,
95
- capture_output=True,
96
- text=True,
97
- timeout=15,
98
- check=False
99
- )
100
-
86
+ cmd = ["ffmpeg", "-f", "lavfi", "-i", "testsrc=duration=1:size=256x256:rate=1", "-c:v", "h264_nvenc", "-preset", "p1", "-f", "null", "-"]
87
+
88
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=15, check=False)
89
+
101
90
  if result.returncode == 0:
102
91
  logger.debug("NVENC h264 encoding test successful")
103
92
  return True
104
93
  else:
105
94
  logger.debug(f"NVENC test failed: {result.stderr}")
106
95
  return False
107
-
96
+
108
97
  except (subprocess.TimeoutExpired, FileNotFoundError) as e:
109
98
  logger.debug(f"NVENC detection failed: {e}")
110
99
  return False
@@ -112,129 +101,132 @@ def _detect_nvenc_support() -> bool:
112
101
  logger.warning(f"Unexpected error during NVENC detection: {e}")
113
102
  return False
114
103
 
104
+
115
105
  def _get_preferred_encoder() -> Dict[str, str]:
116
106
  """
117
107
  Get the preferred video encoder configuration based on available hardware.
118
-
108
+
119
109
  Returns:
120
110
  Dictionary with encoder configuration
121
111
  """
122
112
  global _nvenc_available, _preferred_encoder
123
-
113
+
124
114
  if _nvenc_available is None:
125
115
  _nvenc_available = _detect_nvenc_support()
126
-
116
+
127
117
  if _preferred_encoder is None:
128
118
  if _nvenc_available:
129
119
  _preferred_encoder = {
130
- 'name': 'h264_nvenc',
131
- 'preset_param': '-preset',
132
- 'preset_value': 'p4', # Medium quality/speed for NVENC
133
- 'quality_param': '-cq',
134
- 'quality_value': '20', # NVENC CQ mode
135
- 'type': 'nvenc',
136
- 'fallback_preset': 'p1' # Fastest NVENC preset for fallback
120
+ "name": "h264_nvenc",
121
+ "preset_param": "-preset",
122
+ "preset_value": "p4", # Medium quality/speed for NVENC
123
+ "quality_param": "-cq",
124
+ "quality_value": "20", # NVENC CQ mode
125
+ "type": "nvenc",
126
+ "fallback_preset": "p1", # Fastest NVENC preset for fallback
137
127
  }
138
128
  logger.info("Hardware acceleration: NVENC available")
139
129
  else:
140
130
  _preferred_encoder = {
141
- 'name': 'libx264',
142
- 'preset_param': '-preset',
143
- 'preset_value': 'medium', # CPU preset
144
- 'quality_param': '-crf',
145
- 'quality_value': '23', # CPU CRF mode
146
- 'type': 'cpu',
147
- 'fallback_preset': 'ultrafast' # Fastest CPU preset for fallback
131
+ "name": "libx264",
132
+ "preset_param": "-preset",
133
+ "preset_value": "medium", # CPU preset
134
+ "quality_param": "-crf",
135
+ "quality_value": "23", # CPU CRF mode
136
+ "type": "cpu",
137
+ "fallback_preset": "ultrafast", # Fastest CPU preset for fallback
148
138
  }
149
139
  logger.info("Hardware acceleration: NVENC not available, using CPU")
150
-
140
+
151
141
  return _preferred_encoder
152
142
 
153
- def _build_encoder_args(quality_mode: str = 'balanced',
154
- fallback: bool = False,
155
- custom_crf: Optional[int] = None) -> Tuple[List[str], str]:
143
+
144
+ def _build_encoder_args(quality_mode: str = "balanced", fallback: bool = False, custom_crf: Optional[int] = None) -> Tuple[List[str], str]:
156
145
  """
157
146
  Build encoder command arguments based on available hardware and quality requirements.
158
-
147
+
159
148
  Args:
160
149
  quality_mode: 'fast', 'balanced', or 'quality'
161
150
  fallback: Whether to use fallback settings for compatibility
162
151
  custom_crf: Override quality setting (for backward compatibility)
163
-
152
+
164
153
  Returns:
165
154
  Tuple of (encoder_args, encoder_type)
166
155
  """
167
156
  encoder = _get_preferred_encoder()
168
-
169
- if encoder['type'] == 'nvenc':
157
+
158
+ if encoder["type"] == "nvenc":
170
159
  # NVIDIA NVENC configuration
171
160
  if fallback:
172
- preset = encoder['fallback_preset'] # p1 - fastest
173
- quality = '28' # Lower quality for speed
174
- elif quality_mode == 'fast':
175
- preset = 'p2' # Faster preset
176
- quality = '25'
177
- elif quality_mode == 'quality':
178
- preset = 'p6' # Higher quality preset
179
- quality = '18'
161
+ preset = encoder["fallback_preset"] # p1 - fastest
162
+ quality = "28" # Lower quality for speed
163
+ elif quality_mode == "fast":
164
+ preset = "p2" # Faster preset
165
+ quality = "25"
166
+ elif quality_mode == "quality":
167
+ preset = "p6" # Higher quality preset
168
+ quality = "18"
180
169
  else: # balanced
181
- preset = encoder['preset_value'] # p4
182
- quality = encoder['quality_value'] # 20
183
-
170
+ preset = encoder["preset_value"] # p4
171
+ quality = encoder["quality_value"] # 20
172
+
184
173
  # Override with custom CRF if provided (for backward compatibility)
185
174
  if custom_crf is not None:
186
175
  quality = str(custom_crf)
187
-
176
+
188
177
  return [
189
- '-c:v', encoder['name'],
190
- encoder['preset_param'], preset,
191
- encoder['quality_param'], quality,
192
- '-gpu', '0', # Use first GPU
193
- '-rc', 'vbr', # Variable bitrate
194
- '-profile:v', 'high'
195
- ], encoder['type']
178
+ "-c:v",
179
+ encoder["name"],
180
+ encoder["preset_param"],
181
+ preset,
182
+ encoder["quality_param"],
183
+ quality,
184
+ "-gpu",
185
+ "0", # Use first GPU
186
+ "-rc",
187
+ "vbr", # Variable bitrate
188
+ "-profile:v",
189
+ "high",
190
+ ], encoder["type"]
196
191
  else:
197
192
  # CPU libx264 configuration
198
193
  if fallback:
199
- preset = encoder['fallback_preset'] # ultrafast
200
- quality = '28' # Lower quality for speed
201
- elif quality_mode == 'fast':
202
- preset = 'faster'
203
- quality = '20'
204
- elif quality_mode == 'quality':
205
- preset = 'slow'
206
- quality = '18'
194
+ preset = encoder["fallback_preset"] # ultrafast
195
+ quality = "28" # Lower quality for speed
196
+ elif quality_mode == "fast":
197
+ preset = "faster"
198
+ quality = "20"
199
+ elif quality_mode == "quality":
200
+ preset = "slow"
201
+ quality = "18"
207
202
  else: # balanced
208
- preset = encoder['preset_value'] # medium
209
- quality = encoder['quality_value'] # 23
210
-
203
+ preset = encoder["preset_value"] # medium
204
+ quality = encoder["quality_value"] # 23
205
+
211
206
  # Override with custom CRF if provided (for backward compatibility)
212
207
  if custom_crf is not None:
213
208
  quality = str(custom_crf)
214
-
215
- return [
216
- '-c:v', encoder['name'],
217
- encoder['preset_param'], preset,
218
- encoder['quality_param'], quality,
219
- '-profile:v', 'high'
220
- ], encoder['type']
209
+
210
+ return ["-c:v", encoder["name"], encoder["preset_param"], preset, encoder["quality_param"], quality, "-profile:v", "high"], encoder["type"]
211
+
221
212
 
222
213
  def is_ffmpeg_available() -> bool:
223
214
  """
224
215
  Checks whether the FFmpeg executable is available in the system's PATH.
225
-
216
+
226
217
  Returns:
227
218
  True if FFmpeg is found in the PATH; otherwise, False.
228
219
  """
229
220
  return _resolve_ffmpeg_executable() is not None
230
221
 
222
+
231
223
  def check_ffmpeg_availability():
232
224
  """
233
225
  Verifies that FFmpeg is installed and available in the system's PATH.
234
-
226
+
235
227
  Raises:
236
228
  FileNotFoundError: If FFmpeg is not found.
237
-
229
+
238
230
  Returns:
239
231
  True if FFmpeg is available.
240
232
  """
@@ -245,10 +237,11 @@ def check_ffmpeg_availability():
245
237
  # logger.info("FFmpeg is available.") # Caller can log if needed
246
238
  return True
247
239
 
240
+
248
241
  def get_stream_info(file_path: Path) -> Optional[Dict]:
249
242
  """
250
243
  Retrieves video stream information from a file using ffprobe.
251
-
244
+
252
245
  Runs ffprobe to extract stream metadata in JSON format from the specified video file. Returns a dictionary with stream information, or None if the file does not exist or if an error occurs during execution or parsing.
253
246
  """
254
247
  if not file_path.exists():
@@ -257,8 +250,10 @@ def get_stream_info(file_path: Path) -> Optional[Dict]:
257
250
 
258
251
  command = [
259
252
  "ffprobe",
260
- "-v", "quiet",
261
- "-print_format", "json",
253
+ "-v",
254
+ "quiet",
255
+ "-print_format",
256
+ "json",
262
257
  "-show_streams",
263
258
  str(file_path),
264
259
  ]
@@ -276,7 +271,7 @@ def get_stream_info(file_path: Path) -> Optional[Dict]:
276
271
  return None
277
272
 
278
273
 
279
- def assemble_video_from_frames( # Renamed from assemble_video
274
+ def assemble_video_from_frames( # Renamed from assemble_video
280
275
  frame_paths: List[Path],
281
276
  output_path: Path,
282
277
  fps: float,
@@ -302,7 +297,7 @@ def assemble_video_from_frames( # Renamed from assemble_video
302
297
  logger.error("Error reading first frame to determine dimensions: %s", e, exc_info=True)
303
298
  return None
304
299
 
305
- fourcc = cv2.VideoWriter_fourcc(*"mp4v") # type: ignore
300
+ fourcc = cv2.VideoWriter_fourcc(*"mp4v")
306
301
  output_path.parent.mkdir(parents=True, exist_ok=True)
307
302
  video_writer = cv2.VideoWriter(str(output_path), fourcc, fps, (width, height))
308
303
 
@@ -343,7 +338,7 @@ def transcode_video(
343
338
  ) -> Optional[Path]:
344
339
  """
345
340
  Transcodes a video file using FFmpeg with automatic hardware acceleration.
346
-
341
+
347
342
  Args:
348
343
  input_path: Source video file path
349
344
  output_path: Output video file path
@@ -355,7 +350,7 @@ def transcode_video(
355
350
  extra_args: Additional FFmpeg arguments
356
351
  quality_mode: Quality mode ('fast', 'balanced', 'quality')
357
352
  force_cpu: Force CPU encoding even if NVENC is available
358
-
353
+
359
354
  Returns:
360
355
  Path to transcoded video or None if failed
361
356
  """
@@ -371,8 +366,8 @@ def transcode_video(
371
366
  # Force CPU encoding
372
367
  encoder_args, encoder_type = _build_encoder_args(quality_mode, fallback=False, custom_crf=crf)
373
368
  # Override to use CPU encoder
374
- encoder_args[1] = 'libx264' # Replace encoder name
375
- encoder_args[3] = 'medium' if preset == "auto" else preset # Replace preset
369
+ encoder_args[1] = "libx264" # Replace encoder name
370
+ encoder_args[3] = "medium" if preset == "auto" else preset # Replace preset
376
371
  if crf is not None:
377
372
  encoder_args[5] = str(crf) # Replace quality value
378
373
  else:
@@ -381,28 +376,33 @@ def transcode_video(
381
376
  else:
382
377
  # Manual codec/preset specification (backward compatibility)
383
378
  encoder_args = [
384
- '-c:v', codec,
385
- '-preset', preset,
386
- '-crf' if codec == 'libx264' else '-cq', str(crf if crf is not None else 23),
379
+ "-c:v",
380
+ codec,
381
+ "-preset",
382
+ preset,
383
+ "-crf" if codec == "libx264" else "-cq",
384
+ str(crf if crf is not None else 23),
387
385
  ]
388
- encoder_type = 'nvenc' if 'nvenc' in codec else 'cpu'
386
+ encoder_type = "nvenc" if "nvenc" in codec else "cpu"
389
387
 
390
388
  # Build complete command
391
389
  command = [
392
390
  "ffmpeg",
393
- "-i", str(input_path),
391
+ "-i",
392
+ str(input_path),
394
393
  *encoder_args,
395
- "-c:a", audio_codec,
396
- "-b:a", audio_bitrate,
394
+ "-c:a",
395
+ audio_codec,
396
+ "-b:a",
397
+ audio_bitrate,
397
398
  "-y", # Overwrite output file if it exists
398
399
  ]
399
-
400
+
400
401
  if extra_args:
401
402
  command.extend(extra_args)
402
403
  command.append(str(output_path))
403
404
 
404
- logger.info("Starting transcoding: %s -> %s (using %s)",
405
- input_path.name, output_path.name, encoder_type)
405
+ logger.info("Starting transcoding: %s -> %s (using %s)", input_path.name, output_path.name, encoder_type)
406
406
  logger.debug("FFmpeg command: %s", " ".join(command))
407
407
 
408
408
  try:
@@ -420,18 +420,14 @@ def transcode_video(
420
420
  logger.info("Transcoding finished successfully: %s", output_path)
421
421
  return output_path
422
422
  else:
423
- logger.error("FFmpeg transcoding failed for %s with return code %d.",
424
- input_path.name, process.returncode)
423
+ logger.error("FFmpeg transcoding failed for %s with return code %d.", input_path.name, process.returncode)
425
424
  logger.error("FFmpeg stderr:\n%s", stderr_output)
426
-
425
+
427
426
  # Try fallback to CPU if NVENC failed
428
- if encoder_type == 'nvenc' and not force_cpu:
427
+ if encoder_type == "nvenc" and not force_cpu:
429
428
  logger.warning("NVENC transcoding failed, trying CPU fallback...")
430
- return _transcode_video_fallback(
431
- input_path, output_path, audio_codec, audio_bitrate,
432
- extra_args, quality_mode, crf
433
- )
434
-
429
+ return _transcode_video_fallback(input_path, output_path, audio_codec, audio_bitrate, extra_args, quality_mode, crf)
430
+
435
431
  # Clean up potentially corrupted output file
436
432
  if output_path.exists():
437
433
  try:
@@ -447,18 +443,13 @@ def transcode_video(
447
443
  logger.error("Error during transcoding of %s: %s", input_path.name, e, exc_info=True)
448
444
  return None
449
445
 
446
+
450
447
  def _transcode_video_fallback(
451
- input_path: Path,
452
- output_path: Path,
453
- audio_codec: str,
454
- audio_bitrate: str,
455
- extra_args: Optional[List[str]],
456
- quality_mode: str,
457
- custom_crf: Optional[int]
448
+ input_path: Path, output_path: Path, audio_codec: str, audio_bitrate: str, extra_args: Optional[List[str]], quality_mode: str, custom_crf: Optional[int]
458
449
  ) -> Optional[Path]:
459
450
  """
460
451
  Fallback transcoding using CPU encoding.
461
-
452
+
462
453
  Args:
463
454
  input_path: Source video file path
464
455
  output_path: Output video file path
@@ -467,7 +458,7 @@ def _transcode_video_fallback(
467
458
  extra_args: Additional FFmpeg arguments
468
459
  quality_mode: Quality mode
469
460
  custom_crf: Custom CRF value
470
-
461
+
471
462
  Returns:
472
463
  Path to transcoded video or None if failed
473
464
  """
@@ -475,24 +466,27 @@ def _transcode_video_fallback(
475
466
  # Build CPU encoder arguments
476
467
  encoder_args, _ = _build_encoder_args(quality_mode, fallback=True, custom_crf=custom_crf)
477
468
  # Force CPU encoder
478
- encoder_args[1] = 'libx264'
479
-
469
+ encoder_args[1] = "libx264"
470
+
480
471
  command = [
481
472
  "ffmpeg",
482
- "-i", str(input_path),
473
+ "-i",
474
+ str(input_path),
483
475
  *encoder_args,
484
- "-c:a", audio_codec,
485
- "-b:a", audio_bitrate,
476
+ "-c:a",
477
+ audio_codec,
478
+ "-b:a",
479
+ audio_bitrate,
486
480
  "-y",
487
481
  ]
488
-
482
+
489
483
  if extra_args:
490
484
  command.extend(extra_args)
491
485
  command.append(str(output_path))
492
486
 
493
487
  logger.info("CPU fallback transcoding: %s -> %s", input_path.name, output_path.name)
494
488
  logger.debug("Fallback FFmpeg command: %s", " ".join(command))
495
-
489
+
496
490
  process = subprocess.Popen(command, stderr=subprocess.PIPE, text=True, universal_newlines=True)
497
491
  stderr_output = ""
498
492
  if process.stderr:
@@ -508,7 +502,7 @@ def _transcode_video_fallback(
508
502
  logger.error("CPU fallback transcoding also failed for %s", input_path.name)
509
503
  logger.error("Fallback stderr:\n%s", stderr_output)
510
504
  return None
511
-
505
+
512
506
  except Exception as e:
513
507
  logger.error("Error during CPU fallback transcoding: %s", e, exc_info=True)
514
508
  return None
@@ -551,12 +545,13 @@ def _transcode_video_fallback(
551
545
  logger.error("Error during transcoding of %s: %s", input_path.name, e, exc_info=True)
552
546
  return None
553
547
 
548
+
554
549
  def transcode_videofile_if_required(
555
550
  input_path: Path,
556
551
  output_path: Path,
557
552
  required_codec: str = "h264",
558
- required_pixel_format: str = "yuv420p", # Changed default from yuvj420p
559
- **transcode_options # Pass other options to transcode_video
553
+ required_pixel_format: str = "yuv420p", # Changed default from yuvj420p
554
+ **transcode_options, # Pass other options to transcode_video
560
555
  ) -> Optional[Path]:
561
556
  """
562
557
  Checks if a video needs transcoding based on codec and pixel format,
@@ -578,7 +573,7 @@ def transcode_videofile_if_required(
578
573
  codec_name = video_stream.get("codec_name")
579
574
  pixel_format = video_stream.get("pix_fmt")
580
575
  # Check color range as well, default is usually 'tv' (limited)
581
- color_range = video_stream.get("color_range", "tv") # Default to tv if not specified
576
+ color_range = video_stream.get("color_range", "tv") # Default to tv if not specified
582
577
 
583
578
  needs_transcoding = False
584
579
  transcode_reason = []
@@ -597,44 +592,44 @@ def transcode_videofile_if_required(
597
592
  if needs_transcoding:
598
593
  logger.info("Transcoding %s to %s due to: %s", input_path.name, output_path.name, "; ".join(transcode_reason))
599
594
  # Ensure codec and pixel format are set in options if not already present
600
- transcode_options.setdefault('codec', 'libx264' if required_codec == 'h264' else required_codec)
601
- transcode_options.setdefault('extra_args', [])
595
+ transcode_options.setdefault("codec", "libx264" if required_codec == "h264" else required_codec)
596
+ transcode_options.setdefault("extra_args", [])
602
597
 
603
598
  # Ensure pixel format and color range are correctly set in extra_args
604
- extra_args = transcode_options['extra_args']
605
- if '-pix_fmt' not in extra_args:
606
- extra_args.extend(['-pix_fmt', required_pixel_format])
599
+ extra_args = transcode_options["extra_args"]
600
+ if "-pix_fmt" not in extra_args:
601
+ extra_args.extend(["-pix_fmt", required_pixel_format])
607
602
  else:
608
603
  # If pix_fmt is already set, ensure it's the required one
609
604
  try:
610
- pix_fmt_index = extra_args.index('-pix_fmt')
605
+ pix_fmt_index = extra_args.index("-pix_fmt")
611
606
  if extra_args[pix_fmt_index + 1] != required_pixel_format:
612
607
  logger.warning("Overriding existing -pix_fmt '%s' with '%s'", extra_args[pix_fmt_index + 1], required_pixel_format)
613
608
  extra_args[pix_fmt_index + 1] = required_pixel_format
614
609
  except (ValueError, IndexError):
615
- # Should not happen if '-pix_fmt' is in extra_args, but handle defensively
616
- logger.error("Error processing existing -pix_fmt argument. Appending required format.")
617
- extra_args.extend(['-pix_fmt', required_pixel_format])
610
+ # Should not happen if '-pix_fmt' is in extra_args, but handle defensively
611
+ logger.error("Error processing existing -pix_fmt argument. Appending required format.")
612
+ extra_args.extend(["-pix_fmt", required_pixel_format])
618
613
 
619
-
620
- if '-color_range' not in extra_args:
621
- # Add color range 'pc' (which corresponds to 2 or 'jpeg') for yuv420p
622
- extra_args.extend(['-color_range', 'pc'])
614
+ if "-color_range" not in extra_args:
615
+ # Add color range 'pc' (which corresponds to 2 or 'jpeg') for yuv420p
616
+ extra_args.extend(["-color_range", "pc"])
623
617
  else:
624
618
  # If color_range is already set, ensure it's 'pc'
625
- try:
626
- color_range_index = extra_args.index('-color_range')
627
- if extra_args[color_range_index + 1] != 'pc':
619
+ try:
620
+ color_range_index = extra_args.index("-color_range")
621
+ if extra_args[color_range_index + 1] != "pc":
628
622
  logger.warning("Overriding existing -color_range '%s' with 'pc'", extra_args[color_range_index + 1])
629
- extra_args[color_range_index + 1] = 'pc'
630
- except (ValueError, IndexError):
631
- logger.error("Error processing existing -color_range argument. Appending 'pc'.")
632
- extra_args.extend(['-color_range', 'pc'])
633
-
623
+ extra_args[color_range_index + 1] = "pc"
624
+ except (ValueError, IndexError):
625
+ logger.error("Error processing existing -color_range argument. Appending 'pc'.")
626
+ extra_args.extend(["-color_range", "pc"])
634
627
 
635
628
  return transcode_video(input_path, output_path, **transcode_options)
636
629
  else:
637
- logger.info("Video %s already meets requirements (%s, %s, color_range=pc). No transcoding needed.", input_path.name, required_codec, required_pixel_format)
630
+ logger.info(
631
+ "Video %s already meets requirements (%s, %s, color_range=pc). No transcoding needed.", input_path.name, required_codec, required_pixel_format
632
+ )
638
633
  # If no transcoding is needed, should we copy/link or just return the original path?
639
634
  # For simplicity, let's assume the caller handles the file location.
640
635
  # If the output_path is different, we might need to copy.
@@ -648,15 +643,10 @@ def transcode_videofile_if_required(
648
643
  except Exception as e:
649
644
  logger.error("Failed to copy %s to %s: %s", input_path.name, output_path.name, e)
650
645
  return None
651
- return input_path # Return original path if no copy needed
646
+ return input_path # Return original path if no copy needed
652
647
 
653
- def extract_frames(
654
- video_path: Path,
655
- output_dir: Path,
656
- quality: int,
657
- ext: str = "jpg",
658
- fps: Optional[float] = None
659
- ) -> List[Path]:
648
+
649
+ def extract_frames(video_path: Path, output_dir: Path, quality: int, ext: str = "jpg", fps: Optional[float] = None) -> List[Path]:
660
650
  """
661
651
  Extracts frames from a video file using FFmpeg.
662
652
 
@@ -681,9 +671,11 @@ def extract_frames(
681
671
  output_pattern = output_dir / f"frame_%07d.{ext}"
682
672
 
683
673
  cmd = [
684
- ffmpeg_executable, # Use the found executable path
685
- "-i", str(video_path),
686
- "-qscale:v", str(quality), # Video quality scale
674
+ ffmpeg_executable, # Use the found executable path
675
+ "-i",
676
+ str(video_path),
677
+ "-qscale:v",
678
+ str(quality), # Video quality scale
687
679
  ]
688
680
 
689
681
  if fps is not None:
@@ -713,27 +705,27 @@ def extract_frames(
713
705
  logger.error("An unexpected error occurred during FFmpeg execution: %s", e, exc_info=True)
714
706
  return []
715
707
 
716
-
717
708
  # Collect paths of extracted frames
718
709
  extracted_files = sorted(output_dir.glob(f"frame_*.{ext}"))
719
710
  return extracted_files
720
711
 
712
+
721
713
  def extract_frame_range(
722
714
  video_path: Path,
723
715
  output_dir: Path,
724
716
  start_frame: int,
725
- end_frame: int, # Exclusive end frame number
717
+ end_frame: int, # Exclusive end frame number
726
718
  quality: int,
727
719
  ext: str = "jpg",
728
720
  ) -> List[Path]:
729
721
  """
730
722
  Extracts a specific range of frames from a video using FFmpeg.
731
-
723
+
732
724
  Frames from start_frame (inclusive) to end_frame (exclusive) are saved as images
733
725
  in the output directory, following the naming pattern 'frame_%07d.ext'. The
734
726
  function ensures only the requested frames are returned, and cleans up partial
735
727
  results on failure.
736
-
728
+
737
729
  Args:
738
730
  video_path: Path to the input video file.
739
731
  output_dir: Directory where extracted frames will be saved.
@@ -741,10 +733,10 @@ def extract_frame_range(
741
733
  end_frame: Index at which to stop extraction (exclusive, 0-based).
742
734
  quality: JPEG quality factor (1-31, lower is better).
743
735
  ext: File extension for output images (e.g., 'jpg', 'png').
744
-
736
+
745
737
  Returns:
746
738
  List of Paths to the extracted frame image files within the specified range.
747
-
739
+
748
740
  Raises:
749
741
  FileNotFoundError: If the FFmpeg executable is not found.
750
742
  ValueError: If start_frame is greater than or equal to end_frame.
@@ -767,15 +759,19 @@ def extract_frame_range(
767
759
  # Use select filter for precise frame range extraction
768
760
  # 'select' uses 0-based indexing 'n'
769
761
  # We want frames where start_frame <= n < end_frame
770
- select_filter = f"select='between(n,{start_frame},{end_frame-1})'"
762
+ select_filter = f"select='between(n,{start_frame},{end_frame - 1})'"
771
763
 
772
764
  cmd = [
773
765
  ffmpeg_executable,
774
- "-i", str(video_path),
775
- "-vf", select_filter,
776
- "-vsync", "vfr", # Variable frame rate sync to handle selected frames
777
- "-qscale:v", str(quality),
778
- "-copyts", # Attempt to copy timestamps if needed, might not be accurate with select
766
+ "-i",
767
+ str(video_path),
768
+ "-vf",
769
+ select_filter,
770
+ "-vsync",
771
+ "vfr", # Variable frame rate sync to handle selected frames
772
+ "-qscale:v",
773
+ str(quality),
774
+ "-copyts", # Attempt to copy timestamps if needed, might not be accurate with select
779
775
  str(output_pattern),
780
776
  ]
781
777
 
@@ -819,17 +815,17 @@ def extract_frame_range(
819
815
  # This might happen if ffmpeg fails silently for some frames or if the video ends early.
820
816
  logger.warning("Expected frame file %s not found after extraction.", frame_file)
821
817
 
822
-
823
818
  logger.info("Found %d extracted frame files in range [%d, %d) for video %s.", len(extracted_files), start_frame, end_frame, video_path.name)
824
819
  return extracted_files
825
820
 
821
+
826
822
  __all__ = [
827
- "is_ffmpeg_available", # ADDED
828
- "check_ffmpeg_availability", # ADDED
823
+ "is_ffmpeg_available", # ADDED
824
+ "check_ffmpeg_availability", # ADDED
829
825
  "get_stream_info",
830
- "assemble_video_from_frames", # Updated name
826
+ "assemble_video_from_frames", # Updated name
831
827
  "transcode_video",
832
828
  "transcode_videofile_if_required",
833
829
  "extract_frames",
834
- "extract_frame_range", # Add new function to __all__
830
+ "extract_frame_range", # Add new function to __all__
835
831
  ]