zou 0.19.15__py3-none-any.whl → 0.20.11__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.
Files changed (164) hide show
  1. zou/__init__.py +1 -1
  2. zou/app/__init__.py +10 -2
  3. zou/app/api.py +2 -0
  4. zou/app/blueprints/assets/__init__.py +22 -0
  5. zou/app/blueprints/assets/resources.py +241 -4
  6. zou/app/blueprints/auth/__init__.py +4 -0
  7. zou/app/blueprints/auth/resources.py +154 -22
  8. zou/app/blueprints/breakdown/resources.py +4 -4
  9. zou/app/blueprints/chats/__init__.py +22 -0
  10. zou/app/blueprints/chats/resources.py +199 -0
  11. zou/app/blueprints/comments/resources.py +36 -19
  12. zou/app/blueprints/crud/__init__.py +12 -0
  13. zou/app/blueprints/crud/attachment_file.py +14 -5
  14. zou/app/blueprints/crud/base.py +29 -28
  15. zou/app/blueprints/crud/chat.py +13 -0
  16. zou/app/blueprints/crud/chat_message.py +13 -0
  17. zou/app/blueprints/crud/comments.py +85 -29
  18. zou/app/blueprints/crud/custom_action.py +1 -1
  19. zou/app/blueprints/crud/day_off.py +47 -9
  20. zou/app/blueprints/crud/department.py +1 -25
  21. zou/app/blueprints/crud/entity.py +46 -5
  22. zou/app/blueprints/crud/entity_type.py +13 -1
  23. zou/app/blueprints/crud/event.py +1 -1
  24. zou/app/blueprints/crud/file_status.py +1 -1
  25. zou/app/blueprints/crud/metadata_descriptor.py +24 -10
  26. zou/app/blueprints/crud/organisation.py +22 -5
  27. zou/app/blueprints/crud/output_file.py +1 -1
  28. zou/app/blueprints/crud/output_type.py +1 -1
  29. zou/app/blueprints/crud/person.py +32 -24
  30. zou/app/blueprints/crud/playlist.py +1 -1
  31. zou/app/blueprints/crud/preview_background_file.py +6 -7
  32. zou/app/blueprints/crud/preview_file.py +1 -1
  33. zou/app/blueprints/crud/project.py +14 -6
  34. zou/app/blueprints/crud/project_status.py +1 -1
  35. zou/app/blueprints/crud/schedule_item.py +4 -2
  36. zou/app/blueprints/crud/software.py +1 -1
  37. zou/app/blueprints/crud/status_automation.py +1 -1
  38. zou/app/blueprints/crud/studio.py +33 -0
  39. zou/app/blueprints/crud/task.py +47 -3
  40. zou/app/blueprints/crud/task_status.py +1 -1
  41. zou/app/blueprints/crud/task_type.py +4 -4
  42. zou/app/blueprints/crud/working_file.py +4 -8
  43. zou/app/blueprints/events/resources.py +13 -12
  44. zou/app/blueprints/export/csv/assets.py +15 -6
  45. zou/app/blueprints/export/csv/edits.py +15 -5
  46. zou/app/blueprints/export/csv/playlists.py +1 -1
  47. zou/app/blueprints/export/csv/shots.py +15 -5
  48. zou/app/blueprints/export/csv/time_spents.py +1 -1
  49. zou/app/blueprints/files/resources.py +22 -23
  50. zou/app/blueprints/index/resources.py +38 -29
  51. zou/app/blueprints/news/resources.py +25 -11
  52. zou/app/blueprints/persons/__init__.py +5 -2
  53. zou/app/blueprints/persons/resources.py +126 -120
  54. zou/app/blueprints/previews/__init__.py +18 -8
  55. zou/app/blueprints/previews/resources.py +569 -328
  56. zou/app/blueprints/projects/resources.py +1 -1
  57. zou/app/blueprints/search/resources.py +18 -6
  58. zou/app/blueprints/shots/__init__.py +5 -0
  59. zou/app/blueprints/shots/resources.py +134 -4
  60. zou/app/blueprints/source/__init__.py +6 -6
  61. zou/app/blueprints/source/csv/assets.py +10 -3
  62. zou/app/blueprints/source/csv/base.py +1 -1
  63. zou/app/blueprints/source/csv/edits.py +10 -3
  64. zou/app/blueprints/source/csv/shots.py +10 -3
  65. zou/app/blueprints/source/{edl.py → otio.py} +82 -41
  66. zou/app/blueprints/tasks/__init__.py +3 -2
  67. zou/app/blueprints/tasks/resources.py +83 -52
  68. zou/app/blueprints/user/__init__.py +9 -0
  69. zou/app/blueprints/user/resources.py +170 -12
  70. zou/app/config.py +10 -0
  71. zou/app/mixin.py +6 -5
  72. zou/app/models/attachment_file.py +10 -4
  73. zou/app/models/base.py +18 -13
  74. zou/app/models/build_job.py +7 -4
  75. zou/app/models/chat.py +44 -0
  76. zou/app/models/chat_message.py +37 -0
  77. zou/app/models/comment.py +1 -0
  78. zou/app/models/day_off.py +3 -0
  79. zou/app/models/entity.py +4 -6
  80. zou/app/models/entity_type.py +2 -0
  81. zou/app/models/organisation.py +14 -15
  82. zou/app/models/person.py +6 -1
  83. zou/app/models/project.py +3 -0
  84. zou/app/models/search_filter.py +11 -0
  85. zou/app/models/search_filter_group.py +10 -0
  86. zou/app/models/serializer.py +17 -17
  87. zou/app/models/status_automation.py +2 -0
  88. zou/app/models/studio.py +13 -0
  89. zou/app/models/subscription.py +2 -2
  90. zou/app/models/task.py +6 -1
  91. zou/app/models/task_status.py +1 -0
  92. zou/app/models/task_type.py +1 -0
  93. zou/app/models/working_file.py +1 -1
  94. zou/app/services/assets_service.py +101 -14
  95. zou/app/services/auth_service.py +17 -44
  96. zou/app/services/breakdown_service.py +37 -5
  97. zou/app/services/chats_service.py +279 -0
  98. zou/app/services/comments_service.py +110 -65
  99. zou/app/services/concepts_service.py +4 -12
  100. zou/app/services/deletion_service.py +43 -30
  101. zou/app/services/edits_service.py +5 -11
  102. zou/app/services/emails_service.py +4 -4
  103. zou/app/services/entities_service.py +17 -2
  104. zou/app/services/events_service.py +12 -4
  105. zou/app/services/exception.py +5 -5
  106. zou/app/services/names_service.py +7 -2
  107. zou/app/services/news_service.py +17 -9
  108. zou/app/services/persons_service.py +38 -21
  109. zou/app/services/playlists_service.py +8 -7
  110. zou/app/services/preview_files_service.py +137 -10
  111. zou/app/services/projects_service.py +5 -14
  112. zou/app/services/shots_service.py +221 -49
  113. zou/app/services/sync_service.py +46 -42
  114. zou/app/services/tasks_service.py +185 -46
  115. zou/app/services/time_spents_service.py +67 -20
  116. zou/app/services/user_service.py +350 -107
  117. zou/app/stores/auth_tokens_store.py +2 -1
  118. zou/app/stores/file_store.py +18 -0
  119. zou/app/stores/publisher_store.py +7 -7
  120. zou/app/stores/queue_store.py +1 -0
  121. zou/app/swagger.py +36 -20
  122. zou/app/utils/cache.py +2 -0
  123. zou/app/utils/commands.py +104 -7
  124. zou/app/utils/csv_utils.py +1 -4
  125. zou/app/utils/date_helpers.py +33 -17
  126. zou/app/utils/dbhelpers.py +14 -1
  127. zou/app/utils/emails.py +2 -2
  128. zou/app/utils/fido.py +22 -0
  129. zou/app/utils/query.py +54 -6
  130. zou/app/utils/redis.py +11 -0
  131. zou/app/utils/saml.py +51 -0
  132. zou/app/utils/string.py +2 -0
  133. zou/app/utils/thumbnail.py +4 -2
  134. zou/cli.py +76 -18
  135. zou/debug.py +4 -2
  136. zou/event_stream.py +122 -165
  137. zou/job_settings.py +1 -0
  138. zou/migrations/env.py +0 -0
  139. zou/migrations/utils/base.py +6 -6
  140. zou/migrations/versions/1bb55759146f_add_table_studio.py +67 -0
  141. zou/migrations/versions/1fab8c420678_add_attachments_to_message_chats.py +56 -0
  142. zou/migrations/versions/23122f290ca2_add_entity_chat_models.py +149 -0
  143. zou/migrations/versions/32f134ff1201_add_is_shared_flag_to_filters.py +33 -0
  144. zou/migrations/versions/57222395f2be_add_statusautomation_import_last_revision.py +41 -0
  145. zou/migrations/versions/59a7445a966c_add_entity_is_shared.py +41 -0
  146. zou/migrations/versions/5b980f0dc365_add_comment_links.py +35 -0
  147. zou/migrations/versions/680c64565f9d_for_searchfiltergroup_is_shared.py +35 -0
  148. zou/migrations/versions/8e67c183bed7_add_preference_fields.py +71 -0
  149. zou/migrations/versions/92b40d79ad3f_allow_message_attachments.py +38 -0
  150. zou/migrations/versions/971dbf5a0faf_add_short_name_for_asset_type_entity_.py +33 -0
  151. zou/migrations/versions/9b85c14fa8a7_add_day_off_new_columns.py +68 -0
  152. zou/migrations/versions/9d3bb33c6fc6_add_department_keys_to_filter_models.py +73 -0
  153. zou/migrations/versions/a252a094e977_add_descriptions_for_entities_tasks_and_.py +40 -0
  154. zou/migrations/versions/be56dc0fb760_for_is_shared_disallow_nullable.py +102 -0
  155. zou/migrations/versions/ca28796a2a62_add_is_done_field_to_the_task_model.py +108 -0
  156. zou/migrations/versions/f344b867a911_for_description_of_entity_task_working_.py +75 -0
  157. zou/remote/config_payload.py +2 -1
  158. zou/utils/movie.py +14 -4
  159. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/METADATA +75 -69
  160. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/RECORD +163 -134
  161. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/WHEEL +1 -1
  162. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/LICENSE +0 -0
  163. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/entry_points.txt +0 -0
  164. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/top_level.txt +0 -0
@@ -4,6 +4,7 @@ import orjson as json
4
4
  import os
5
5
  import zlib
6
6
 
7
+ from flask import current_app
7
8
  from operator import itemgetter
8
9
  from pathlib import Path
9
10
  from shutil import copyfile
@@ -781,13 +782,13 @@ def remove_build_job(playlist, build_job_id):
781
782
  movie_file_path = get_playlist_movie_file_path(job.serialize())
782
783
  if os.path.exists(movie_file_path):
783
784
  os.remove(movie_file_path)
784
- # stop removing files for now
785
- # try:
786
- # file_store.remove_movie("playlists", build_job_id)
787
- # except Exception:
788
- # current_app.logger.error(
789
- # "Playlist file can't be deleted: %s" % build_job_id
790
- # )
785
+ if config.REMOVE_FILES:
786
+ try:
787
+ file_store.remove_movie("playlists", build_job_id)
788
+ except Exception:
789
+ current_app.logger.error(
790
+ "Playlist file can't be deleted: %s" % build_job_id
791
+ )
791
792
  job.delete()
792
793
  events.emit(
793
794
  "build-job:delete",
@@ -4,6 +4,7 @@ import re
4
4
  import time
5
5
 
6
6
  import ffmpeg
7
+ import shutil
7
8
 
8
9
  from sqlalchemy.orm import aliased
9
10
  from sqlalchemy.orm.exc import ObjectDeletedError
@@ -33,7 +34,7 @@ from zou.app.utils import (
33
34
  thumbnail as thumbnail_utils,
34
35
  )
35
36
  from zou.app.services.exception import (
36
- ArgumentsException,
37
+ WrongParameterException,
37
38
  PreviewFileNotFoundException,
38
39
  ProjectNotFoundException,
39
40
  EpisodeNotFoundException,
@@ -88,7 +89,7 @@ def _is_valid_partial_resolution(resolution):
88
89
  return resolution is not None and bool(re.match(r"x\d{3,4}", resolution))
89
90
 
90
91
 
91
- def get_preview_file_fps(project):
92
+ def get_preview_file_fps(project, entity=None):
92
93
  """
93
94
  Return fps set at project level or default fps if the dimensions are not
94
95
  set.
@@ -96,6 +97,12 @@ def get_preview_file_fps(project):
96
97
  fps = "25.00"
97
98
  if project.get("fps", None) is not None:
98
99
  fps = project["fps"].replace(",", ".")
100
+
101
+ if entity is not None:
102
+ entity_data = entity.get("data", {}) or {}
103
+ if entity_data.get("fps", None):
104
+ fps = str(entity_data["fps"]).replace(",", ".")
105
+
99
106
  return "%.3f" % float(fps)
100
107
 
101
108
 
@@ -162,7 +169,10 @@ def set_preview_file_as_ready(preview_file_id):
162
169
 
163
170
 
164
171
  def prepare_and_store_movie(
165
- preview_file_id, uploaded_movie_path, normalize=True
172
+ preview_file_id,
173
+ uploaded_movie_path,
174
+ normalize=True,
175
+ add_source_to_file_store=True,
166
176
  ):
167
177
  """
168
178
  Prepare movie preview, normalize the movie as a .mp4, build the thumbnails
@@ -171,6 +181,10 @@ def prepare_and_store_movie(
171
181
  from zou.app import app as current_app
172
182
 
173
183
  with current_app.app_context():
184
+ if add_source_to_file_store:
185
+ file_store.add_movie(
186
+ "source", preview_file_id, uploaded_movie_path
187
+ )
174
188
  preview_file_raw = files_service.get_preview_file_raw(preview_file_id)
175
189
  normalized_movie_low_path = None
176
190
  try:
@@ -189,7 +203,7 @@ def prepare_and_store_movie(
189
203
  preview_file = set_preview_file_as_broken(preview_file_id)
190
204
  return preview_file
191
205
 
192
- fps = get_preview_file_fps(project)
206
+ fps = get_preview_file_fps(project, entity)
193
207
  (width, height) = get_preview_file_dimensions(project, entity)
194
208
 
195
209
  if normalize:
@@ -199,9 +213,6 @@ def prepare_and_store_movie(
199
213
  config.ENABLE_JOB_QUEUE_REMOTE
200
214
  and len(config.JOB_QUEUE_NOMAD_NORMALIZE_JOB) > 0
201
215
  ):
202
- file_store.add_movie(
203
- "source", preview_file_id, uploaded_movie_path
204
- )
205
216
  result = _run_remote_normalize_movie(
206
217
  current_app, preview_file_id, fps, width, height
207
218
  )
@@ -556,7 +567,7 @@ def get_running_preview_files():
556
567
  result = preview_file.serialize()
557
568
  result["project_id"] = fields.serialize_value(project_id)
558
569
  result["task_type_id"] = fields.serialize_value(task_type_id)
559
- (result["full_entity_name"], _) = names_service.get_full_entity_name(
570
+ result["full_entity_name"], _, _ = names_service.get_full_entity_name(
560
571
  entity_id
561
572
  )
562
573
  results.append(result)
@@ -614,7 +625,9 @@ def extract_frame_from_preview_file(preview_file, frame_number):
614
625
  else:
615
626
  raise PreviewFileNotFoundException
616
627
 
617
- fps = get_preview_file_fps(project)
628
+ fps = get_preview_file_fps(
629
+ project, get_entity_from_preview_file(preview_file["id"])
630
+ )
618
631
  extracted_frame_path = movie.extract_frame_from_movie(
619
632
  preview_file_path, frame_number, fps
620
633
  )
@@ -645,7 +658,7 @@ def extract_tile_from_preview_file(preview_file):
645
658
  extracted_tile_path = movie.generate_tile(preview_file_path)
646
659
  return extracted_tile_path
647
660
  else:
648
- return ArgumentsException("Preview file is not a movie")
661
+ raise WrongParameterException("Preview file is not a movie")
649
662
 
650
663
 
651
664
  def reset_movie_files_metadata():
@@ -940,3 +953,117 @@ def _reset_preview_file_metadata(
940
953
  print(
941
954
  f"Failed to store information for preview file {preview_file.id}: {e}.",
942
955
  )
956
+
957
+
958
+ def copy_preview_file_on_storage(
959
+ get_path_func,
960
+ exists_func,
961
+ copy_func,
962
+ prefix,
963
+ original_preview_file_id,
964
+ preview_file_to_update_id,
965
+ ):
966
+ if config.FS_BACKEND == "local":
967
+ file_path = get_path_func(prefix, original_preview_file_id)
968
+ other_file_path = get_path_func(prefix, preview_file_to_update_id)
969
+ if os.path.exists(file_path):
970
+ os.makedirs(os.path.dirname(other_file_path), exist_ok=True)
971
+ shutil.copyfile(file_path, other_file_path)
972
+ elif exists_func(prefix, original_preview_file_id):
973
+ copy_func(
974
+ prefix, original_preview_file_id, prefix, preview_file_to_update_id
975
+ )
976
+
977
+
978
+ def copy_preview_file_in_another_one(
979
+ original_preview_file_id, preview_file_to_update_id
980
+ ):
981
+ """
982
+ Copy preview file data/files from one preview file to another one.
983
+ """
984
+ original_preview_file = files_service.get_preview_file(
985
+ original_preview_file_id
986
+ )
987
+ is_movie = original_preview_file["extension"] == "mp4"
988
+ is_picture = original_preview_file["extension"] == "png"
989
+
990
+ if is_movie:
991
+ prefixes = ["previews", "lowdef"]
992
+ for prefix in prefixes:
993
+ copy_preview_file_on_storage(
994
+ file_store.get_local_movie_path,
995
+ file_store.exists_movie,
996
+ file_store.copy_movie,
997
+ prefix,
998
+ original_preview_file_id,
999
+ preview_file_to_update_id,
1000
+ )
1001
+
1002
+ if is_movie or is_picture:
1003
+ prefixes = [
1004
+ "previews",
1005
+ "original",
1006
+ "thumbnails",
1007
+ "thumbnails-square",
1008
+ ]
1009
+ if is_movie:
1010
+ prefixes.append("tiles")
1011
+
1012
+ for prefix in prefixes:
1013
+ copy_preview_file_on_storage(
1014
+ file_store.get_local_picture_path,
1015
+ file_store.exists_picture,
1016
+ file_store.copy_picture,
1017
+ prefix,
1018
+ original_preview_file_id,
1019
+ preview_file_to_update_id,
1020
+ )
1021
+ else:
1022
+ copy_preview_file_on_storage(
1023
+ file_store.get_local_file_path,
1024
+ file_store.exists_file,
1025
+ file_store.copy_file,
1026
+ "previews",
1027
+ original_preview_file_id,
1028
+ preview_file_to_update_id,
1029
+ )
1030
+
1031
+ preview_file_to_update = update_preview_file(
1032
+ preview_file_to_update_id,
1033
+ {
1034
+ "extension": original_preview_file["extension"],
1035
+ "original_name": original_preview_file["original_name"],
1036
+ "status": original_preview_file["status"],
1037
+ "file_size": original_preview_file["file_size"],
1038
+ "width": original_preview_file["width"],
1039
+ "height": original_preview_file["height"],
1040
+ "duration": original_preview_file["duration"],
1041
+ },
1042
+ )
1043
+ tasks_service.update_preview_file_info(preview_file_to_update)
1044
+ comment = tasks_service.get_comment_by_preview_file_id(
1045
+ preview_file_to_update_id
1046
+ )
1047
+ task = tasks_service.get_task(preview_file_to_update["task_id"])
1048
+ comment_id = None
1049
+ if comment is not None:
1050
+ comment_id = comment["id"]
1051
+ events.emit(
1052
+ "comment:update",
1053
+ {"comment_id": comment_id},
1054
+ project_id=task["project_id"],
1055
+ )
1056
+ events.emit(
1057
+ "preview-file:add-file",
1058
+ {
1059
+ "comment_id": comment_id,
1060
+ "task_id": preview_file_to_update["task_id"],
1061
+ "preview_file_id": preview_file_to_update["id"],
1062
+ "revision": preview_file_to_update["revision"],
1063
+ "extension": preview_file_to_update["extension"],
1064
+ "status": preview_file_to_update["status"],
1065
+ },
1066
+ project_id=task["project_id"],
1067
+ )
1068
+
1069
+ return preview_file_to_update
@@ -38,7 +38,7 @@ from sqlalchemy import or_
38
38
 
39
39
  def clear_project_cache(project_id):
40
40
  cache.cache.delete_memoized(get_project, project_id)
41
- cache.cache.delete_memoized(get_project_with_relations, project_id)
41
+ cache.cache.delete_memoized(get_project, project_id, True)
42
42
  cache.cache.delete_memoized(get_project_by_name)
43
43
  cache.cache.delete_memoized(open_projects)
44
44
 
@@ -259,21 +259,12 @@ def get_project_raw(project_id):
259
259
 
260
260
 
261
261
  @cache.memoize_function(240)
262
- def get_project(project_id):
262
+ def get_project(project_id, relations=False):
263
263
  """
264
264
  Get project matching given id, as a dict. Raises an exception if project is
265
265
  not found.
266
266
  """
267
- return get_project_raw(project_id).serialize()
268
-
269
-
270
- @cache.memoize_function(240)
271
- def get_project_with_relations(project_id):
272
- """
273
- Get project matching given id, as a dict. Raises an exception if project is
274
- not found.
275
- """
276
- return get_project_raw(project_id).serialize(relations=True)
267
+ return get_project_raw(project_id).serialize(relations=relations)
277
268
 
278
269
 
279
270
  @cache.memoize_function(120)
@@ -487,7 +478,7 @@ def add_metadata_descriptor(
487
478
  field_name=slugify.slugify(name, separator="_"),
488
479
  )
489
480
  except Exception:
490
- raise WrongParameterException
481
+ raise WrongParameterException("Metadata descriptor already exists.")
491
482
  events.emit(
492
483
  "metadata-descriptor:new",
493
484
  {"metadata_descriptor_id": str(descriptor.id)},
@@ -687,7 +678,7 @@ def get_project_fps(project_id):
687
678
  Return fps set at project level or default fps if it not set.
688
679
  """
689
680
  project = get_project(project_id)
690
- return float(project["fps"] or "24.00")
681
+ return float(project["fps"] or "25.00")
691
682
 
692
683
 
693
684
  def get_task_type_priority_map(project_id, for_entity="Asset"):