zou 0.19.14__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 (165) 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} +84 -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/flask.py +1 -0
  130. zou/app/utils/query.py +54 -6
  131. zou/app/utils/redis.py +11 -0
  132. zou/app/utils/saml.py +51 -0
  133. zou/app/utils/string.py +2 -0
  134. zou/app/utils/thumbnail.py +4 -2
  135. zou/cli.py +76 -18
  136. zou/debug.py +4 -2
  137. zou/event_stream.py +122 -165
  138. zou/job_settings.py +1 -0
  139. zou/migrations/env.py +0 -0
  140. zou/migrations/utils/base.py +6 -6
  141. zou/migrations/versions/1bb55759146f_add_table_studio.py +67 -0
  142. zou/migrations/versions/1fab8c420678_add_attachments_to_message_chats.py +56 -0
  143. zou/migrations/versions/23122f290ca2_add_entity_chat_models.py +149 -0
  144. zou/migrations/versions/32f134ff1201_add_is_shared_flag_to_filters.py +33 -0
  145. zou/migrations/versions/57222395f2be_add_statusautomation_import_last_revision.py +41 -0
  146. zou/migrations/versions/59a7445a966c_add_entity_is_shared.py +41 -0
  147. zou/migrations/versions/5b980f0dc365_add_comment_links.py +35 -0
  148. zou/migrations/versions/680c64565f9d_for_searchfiltergroup_is_shared.py +35 -0
  149. zou/migrations/versions/8e67c183bed7_add_preference_fields.py +71 -0
  150. zou/migrations/versions/92b40d79ad3f_allow_message_attachments.py +38 -0
  151. zou/migrations/versions/971dbf5a0faf_add_short_name_for_asset_type_entity_.py +33 -0
  152. zou/migrations/versions/9b85c14fa8a7_add_day_off_new_columns.py +68 -0
  153. zou/migrations/versions/9d3bb33c6fc6_add_department_keys_to_filter_models.py +73 -0
  154. zou/migrations/versions/a252a094e977_add_descriptions_for_entities_tasks_and_.py +40 -0
  155. zou/migrations/versions/be56dc0fb760_for_is_shared_disallow_nullable.py +102 -0
  156. zou/migrations/versions/ca28796a2a62_add_is_done_field_to_the_task_model.py +108 -0
  157. zou/migrations/versions/f344b867a911_for_description_of_entity_task_working_.py +75 -0
  158. zou/remote/config_payload.py +2 -1
  159. zou/utils/movie.py +14 -4
  160. {zou-0.19.14.dist-info → zou-0.20.11.dist-info}/METADATA +75 -69
  161. {zou-0.19.14.dist-info → zou-0.20.11.dist-info}/RECORD +164 -135
  162. {zou-0.19.14.dist-info → zou-0.20.11.dist-info}/WHEEL +1 -1
  163. {zou-0.19.14.dist-info → zou-0.20.11.dist-info}/LICENSE +0 -0
  164. {zou-0.19.14.dist-info → zou-0.20.11.dist-info}/entry_points.txt +0 -0
  165. {zou-0.19.14.dist-info → zou-0.20.11.dist-info}/top_level.txt +0 -0
@@ -659,7 +659,7 @@ class ProductionMetadataDescriptorsResource(Resource, ArgsMixin):
659
659
  200:
660
660
  description: All metadata descriptors
661
661
  """
662
- user_service.check_manager_project_access(project_id)
662
+ user_service.check_project_access(project_id)
663
663
  for_client = permissions.has_client_permissions()
664
664
  return projects_service.get_metadata_descriptors(
665
665
  project_id, for_client
@@ -78,12 +78,24 @@ class SearchResource(Resource, ArgsMixin):
78
78
  query, limit=limit, offset=offset
79
79
  )
80
80
  if "assets" in index_names:
81
- results["assets"] = index_service.search_assets(
82
- query, project_ids, limit=limit, offset=offset
83
- )
81
+ if (
82
+ len(project_ids) == 0
83
+ and not permissions.has_admin_permissions()
84
+ ):
85
+ results["assets"] = []
86
+ else:
87
+ results["assets"] = index_service.search_assets(
88
+ query, project_ids, limit=limit, offset=offset
89
+ )
84
90
  if "shots" in index_names:
85
- results["shots"] = index_service.search_shots(
86
- query, project_ids, limit=limit, offset=offset
87
- )
91
+ if (
92
+ len(project_ids) == 0
93
+ and not permissions.has_admin_permissions()
94
+ ):
95
+ results["shots"] = []
96
+ else:
97
+ results["shots"] = index_service.search_shots(
98
+ query, project_ids, limit=limit, offset=offset
99
+ )
88
100
 
89
101
  return results
@@ -42,6 +42,7 @@ from zou.app.blueprints.shots.resources import (
42
42
  EpisodeAssetTasksResource,
43
43
  SequenceShotTasksResource,
44
44
  ProjectQuotasResource,
45
+ SetShotsFramesResource,
45
46
  )
46
47
 
47
48
  routes = [
@@ -94,6 +95,10 @@ routes = [
94
95
  "/data/projects/<project_id>/quotas/<task_type_id>",
95
96
  ProjectQuotasResource,
96
97
  ),
98
+ (
99
+ "/actions/projects/<project_id>/task-types/<task_type_id>/set-shot-nb-frames",
100
+ SetShotsFramesResource,
101
+ ),
97
102
  ]
98
103
 
99
104
 
@@ -18,6 +18,9 @@ from zou.app.services import (
18
18
 
19
19
  from zou.app.mixin import ArgsMixin
20
20
  from zou.app.utils import fields, query, permissions
21
+ from zou.app.services.exception import (
22
+ WrongParameterException,
23
+ )
21
24
 
22
25
 
23
26
  class ShotResource(Resource, ArgsMixin):
@@ -47,6 +50,52 @@ class ShotResource(Resource, ArgsMixin):
47
50
  user_service.check_entity_access(shot["id"])
48
51
  return shot
49
52
 
53
+ @jwt_required()
54
+ def put(self, shot_id):
55
+ """
56
+ Update given shot.
57
+ ---
58
+ tags:
59
+ - Shots
60
+ parameters:
61
+ - in: path
62
+ name: shot_id
63
+ required: True
64
+ type: string
65
+ format: UUID
66
+ x-example: a24a6ea4-ce75-4665-a070-57453082c25
67
+ - in: body
68
+ name: data
69
+ required: True
70
+ type: object
71
+ responses:
72
+ 200:
73
+ description: Update given shot
74
+ """
75
+ shot = shots_service.get_shot(shot_id)
76
+ user_service.check_manager_project_access(shot["project_id"])
77
+ data = request.json
78
+ if data is None:
79
+ raise WrongParameterException(
80
+ "Data are empty. Please verify that you sent JSON data and"
81
+ " that you set the right headers."
82
+ )
83
+ for field in [
84
+ "id",
85
+ "created_at",
86
+ "updated_at",
87
+ "instance_casting",
88
+ "project_id",
89
+ "entities_in",
90
+ "entities_out",
91
+ "type",
92
+ "shotgun_id",
93
+ "created_by",
94
+ ]:
95
+ data.pop(field, None)
96
+
97
+ return shots_service.update_shot(shot_id, data)
98
+
50
99
  @jwt_required()
51
100
  def delete(self, shot_id):
52
101
  """
@@ -1506,19 +1555,100 @@ class ProjectQuotasResource(Resource, ArgsMixin):
1506
1555
  type: string
1507
1556
  format: UUID
1508
1557
  x-example: a24a6ea4-ce75-4665-a070-57453082c25
1558
+ - in: query
1559
+ name: count_mode
1560
+ required: True
1561
+ type: string
1562
+ enum: [weighted, weigtheddone, feedback, done]
1563
+ x-example: weighted
1564
+ - in: query
1565
+ name: studio_id
1566
+ required: False
1567
+ type: string
1568
+ format: UUID
1569
+ x-example: a24a6ea4-ce75-4665-a070-57453082c25
1509
1570
  responses:
1510
1571
  200:
1511
1572
  description: Quotas statistics for shots
1512
1573
  """
1513
1574
  projects_service.get_project(project_id)
1514
1575
  user_service.check_project_access(project_id)
1515
- detail_level = self.get_text_parameter("detail")
1516
- weighted = self.get_bool_parameter("weighted", default="true")
1576
+ args = self.get_args(
1577
+ [
1578
+ ("count_mode", "weighted", False, str),
1579
+ ("studio_id", None, False, str),
1580
+ ]
1581
+ )
1582
+ count_mode = args["count_mode"]
1583
+ studio_id = args["studio_id"]
1584
+
1585
+ if count_mode not in ["weighted", "weighteddone", "feedback", "done"]:
1586
+ raise WrongParameterException(
1587
+ "count_mode must be equal to weighted, weigtheddone, feedback"
1588
+ ", or done"
1589
+ )
1590
+
1591
+ feedback = "done" not in count_mode
1592
+ weighted = "weighted" in count_mode
1593
+
1517
1594
  if weighted:
1518
1595
  return shots_service.get_weighted_quotas(
1519
- project_id, task_type_id, detail_level
1596
+ project_id,
1597
+ task_type_id,
1598
+ feedback=feedback,
1599
+ studio_id=studio_id,
1520
1600
  )
1521
1601
  else:
1522
1602
  return shots_service.get_raw_quotas(
1523
- project_id, task_type_id, detail_level
1603
+ project_id,
1604
+ task_type_id,
1605
+ feedback=feedback,
1606
+ studio_id=studio_id,
1524
1607
  )
1608
+
1609
+
1610
+ class SetShotsFramesResource(Resource, ArgsMixin):
1611
+ @jwt_required()
1612
+ def post(self, project_id, task_type_id):
1613
+ """
1614
+ Set frames for given shots.
1615
+ ---
1616
+ tags:
1617
+ - Shots
1618
+ parameters:
1619
+ - in: formData
1620
+ name: shots
1621
+ required: True
1622
+ type: array
1623
+ items:
1624
+ type: object
1625
+ properties:
1626
+ shot_id:
1627
+ type: string
1628
+ format: UUID
1629
+ x-example: a24a6ea4-ce75-4665-a070-57453082c25
1630
+ nb_frames:
1631
+ type: integer
1632
+ x-example: 24
1633
+ responses:
1634
+ 200:
1635
+ description: Frames set for given shots
1636
+ """
1637
+ user_service.check_manager_project_access(project_id)
1638
+ if not fields.is_valid_id(task_type_id) or not fields.is_valid_id(
1639
+ project_id
1640
+ ):
1641
+ raise WrongParameterException("Invalid project or task type id")
1642
+
1643
+ episode_id = self.get_episode_id()
1644
+ if not episode_id in ["", None] and not fields.is_valid_id(episode_id):
1645
+ raise WrongParameterException("Invalid episode id")
1646
+
1647
+ if episode_id == "":
1648
+ episode_id = None
1649
+
1650
+ return shots_service.set_frames_from_task_type_preview_files(
1651
+ project_id,
1652
+ task_type_id,
1653
+ episode_id=episode_id,
1654
+ )
@@ -80,9 +80,9 @@ from zou.app.blueprints.source.kitsu import (
80
80
  ImportKitsuTasksResource,
81
81
  )
82
82
 
83
- from zou.app.blueprints.source.edl import (
84
- EDLImportResource,
85
- EDLImportEpisodeResource,
83
+ from zou.app.blueprints.source.otio import (
84
+ OTIOImportResource,
85
+ OTIOImportEpisodeResource,
86
86
  )
87
87
 
88
88
  routes = [
@@ -133,10 +133,10 @@ routes = [
133
133
  "/import/csv/projects/<project_id>/episodes/<episode_id>/task-types/<task_type_id>/estimations",
134
134
  TaskTypeEstimationsEpisodeCsvImportResource,
135
135
  ),
136
- ("/import/edl/projects/<project_id>", EDLImportResource),
136
+ ("/import/otio/projects/<project_id>", OTIOImportResource),
137
137
  (
138
- "/import/edl/projects/<project_id>/episodes/<episode_id>",
139
- EDLImportEpisodeResource,
138
+ "/import/otio/projects/<project_id>/episodes/<episode_id>",
139
+ OTIOImportEpisodeResource,
140
140
  ),
141
141
  ("/import/kitsu/comments", ImportKitsuCommentsResource),
142
142
  ("/import/kitsu/entities", ImportKitsuEntitiesResource),
@@ -16,7 +16,7 @@ from zou.app.services import (
16
16
  )
17
17
  from zou.app.models.entity import Entity
18
18
  from zou.app.services.exception import WrongParameterException
19
- from zou.app.utils import events, cache
19
+ from zou.app.utils import events, cache, string
20
20
 
21
21
 
22
22
  class AssetsCsvImportResource(BaseCsvProjectImportResource):
@@ -209,9 +209,16 @@ class AssetsCsvImportResource(BaseCsvProjectImportResource):
209
209
  else:
210
210
  asset_new_values["data"] = entity.data.copy()
211
211
 
212
- for name, field_name in self.descriptor_fields.items():
212
+ for name, descriptor in self.descriptor_fields.items():
213
213
  if name in row:
214
- asset_new_values["data"][field_name] = row[name]
214
+ if descriptor["data_type"] == "boolean":
215
+ asset_new_values["data"][descriptor["field_name"]] = (
216
+ "true" if string.strtobool(row[name]) else "false"
217
+ )
218
+ else:
219
+ asset_new_values["data"][descriptor["field_name"]] = row[
220
+ name
221
+ ]
215
222
 
216
223
  ready_for = row.get("Ready for", None)
217
224
  if ready_for is not None:
@@ -131,5 +131,5 @@ class BaseCsvProjectImportResource(BaseCsvImportResource, ArgsMixin):
131
131
  descriptors = projects_service.get_metadata_descriptors(project_id)
132
132
  for descriptor in descriptors:
133
133
  if descriptor["entity_type"] == entity_type:
134
- descriptor_map[descriptor["name"]] = descriptor["field_name"]
134
+ descriptor_map[descriptor["name"]] = descriptor
135
135
  return descriptor_map
@@ -21,7 +21,7 @@ from zou.app.services.tasks_service import (
21
21
  )
22
22
  from zou.app.services.comments_service import create_comment
23
23
  from zou.app.services.exception import WrongParameterException
24
- from zou.app.utils import events
24
+ from zou.app.utils import events, string
25
25
 
26
26
 
27
27
  class EditsCsvImportResource(BaseCsvProjectImportResource):
@@ -192,9 +192,16 @@ class EditsCsvImportResource(BaseCsvProjectImportResource):
192
192
  else:
193
193
  edit_new_values["data"] = entity.data.copy()
194
194
 
195
- for name, field_name in self.descriptor_fields.items():
195
+ for name, descriptor in self.descriptor_fields.items():
196
196
  if name in row:
197
- edit_new_values["data"][field_name] = row[name]
197
+ if descriptor["data_type"] == "boolean":
198
+ edit_new_values["data"][descriptor["field_name"]] = (
199
+ "true" if string.strtobool(row[name]) else "false"
200
+ )
201
+ else:
202
+ edit_new_values["data"][descriptor["field_name"]] = row[
203
+ name
204
+ ]
198
205
 
199
206
  tasks_update = self.get_tasks_update(row)
200
207
 
@@ -21,7 +21,7 @@ from zou.app.services.tasks_service import (
21
21
  )
22
22
  from zou.app.services.comments_service import create_comment
23
23
  from zou.app.services.exception import WrongParameterException
24
- from zou.app.utils import events
24
+ from zou.app.utils import events, string
25
25
 
26
26
 
27
27
  class ShotsCsvImportResource(BaseCsvProjectImportResource):
@@ -229,9 +229,16 @@ class ShotsCsvImportResource(BaseCsvProjectImportResource):
229
229
  if fps is not None:
230
230
  shot_new_values["data"]["fps"] = fps
231
231
 
232
- for name, field_name in self.descriptor_fields.items():
232
+ for name, descriptor in self.descriptor_fields.items():
233
233
  if name in row:
234
- shot_new_values["data"][field_name] = row[name]
234
+ if descriptor["data_type"] == "boolean":
235
+ shot_new_values["data"][descriptor["field_name"]] = (
236
+ "true" if string.strtobool(row[name]) else "false"
237
+ )
238
+ else:
239
+ shot_new_values["data"][descriptor["field_name"]] = row[
240
+ name
241
+ ]
235
242
 
236
243
  tasks_update = self.get_tasks_update(row)
237
244
 
@@ -1,12 +1,12 @@
1
1
  import os
2
- import uuid
2
+ import pathlib
3
3
  import opentimelineio as otio
4
4
  import re
5
5
 
6
6
  from string import Template
7
7
 
8
8
  from flask import request, current_app
9
- from flask_restful import Resource
9
+ from flask_restful import Resource, inputs
10
10
  from flask_jwt_extended import jwt_required
11
11
 
12
12
  from zou.app import config
@@ -40,25 +40,26 @@ mapping_substitutions_to_regex = {
40
40
  }
41
41
 
42
42
 
43
- class EDLBaseResource(Resource, ArgsMixin):
43
+ class OTIOBaseResource(Resource, ArgsMixin):
44
+ @jwt_required()
44
45
  def post(self, project_id, episode_id=None):
45
46
  args = self.post_args()
46
47
  user_service.check_manager_project_access(project_id)
47
48
  uploaded_file = request.files["file"]
48
- file_name = "%s.edl" % uuid.uuid4()
49
+ file_name = uploaded_file.filename
49
50
  file_path = os.path.join(config.TMP_DIR, file_name)
50
51
  uploaded_file.save(file_path)
51
- self.prepare_import(
52
- project_id,
53
- episode_id,
54
- args["naming_convention"],
55
- args["match_case"],
56
- )
57
52
  try:
58
- result = self.run_import(project_id, file_path)
53
+ result = self.run_import(
54
+ file_path,
55
+ project_id,
56
+ episode_id,
57
+ args["naming_convention"],
58
+ args["match_case"],
59
+ )
59
60
  except Exception as e:
60
61
  current_app.logger.error(
61
- f"Import EDL failed: {type(e).__name__}: {str(e)}"
62
+ f"Import OTIO failed: {type(e).__name__}: {str(e)}"
62
63
  )
63
64
  return {
64
65
  "error": True,
@@ -120,25 +121,50 @@ class EDLBaseResource(Resource, ArgsMixin):
120
121
  key = key.lower()
121
122
  self.shot_map[key] = shot["id"]
122
123
 
123
- def run_import(self, project_id, file_path):
124
+ def run_import(
125
+ self,
126
+ file_path,
127
+ project_id,
128
+ episode_id,
129
+ naming_convention,
130
+ match_case,
131
+ ):
124
132
  result = {"updated_shots": [], "created_shots": []}
125
133
  try:
126
- with open(file_path, "r+", errors="replace") as edl_file:
127
- contents = edl_file.read()
128
- edl_file.seek(0)
129
- edl_file.write(contents)
130
-
131
- timeline = otio.adapters.read_from_file(
132
- file_path,
133
- rate=projects_service.get_project_fps(project_id),
134
- ignore_timecode_mismatch=True,
135
- )
134
+ kwargs = {}
135
+ extension = pathlib.Path(file_path).suffix
136
+ if extension == ".edl":
137
+ kwargs["rate"] = projects_service.get_project_fps(project_id)
138
+ kwargs["ignore_timecode_mismatch"] = True
139
+ timeline = otio.adapters.read_from_file(file_path, **kwargs)
136
140
  except Exception as e:
137
- raise Exception("Failed to parse EDL file: %s" % str(e))
141
+ raise Exception(f"Failed to parse OTIO file: {str(e)}")
142
+
143
+ self.prepare_import(
144
+ project_id,
145
+ episode_id,
146
+ naming_convention,
147
+ match_case,
148
+ )
138
149
  for video_track in timeline.video_tracks():
139
150
  for track in video_track:
140
151
  if isinstance(track, otio.schema.Clip):
141
- name, _ = os.path.splitext(track.name)
152
+ name = os.path.splitext(track.name)[0]
153
+ if not name:
154
+ if getattr(track.media_reference, "name_prefix", None):
155
+ name = track.media_reference.name_prefix
156
+ elif getattr(track.media_reference, "name", None):
157
+ name, _ = os.path.splitext(
158
+ track.media_reference.name
159
+ )[0]
160
+ elif getattr(
161
+ track.media_reference, "target_url", None
162
+ ):
163
+ name, _ = os.path.splitext(
164
+ os.path.basename(
165
+ track.media_reference.target_url
166
+ )
167
+ )[0]
142
168
  name_to_search = name if self.match_case else name.lower()
143
169
  if name_to_search in self.shot_map:
144
170
  shot_id = self.shot_map[name_to_search]
@@ -183,19 +209,31 @@ class EDLBaseResource(Resource, ArgsMixin):
183
209
 
184
210
  data = future_shot_values["data"] or {}
185
211
  try:
186
- data["frame_in"] = (
187
- track.trimmed_range_in_parent().start_time.to_frames()
188
- )
212
+ try:
213
+ data["frame_in"] = (
214
+ track.trimmed_range_in_parent().start_time.to_frames()
215
+ )
216
+ except:
217
+ data["frame_in"] = (
218
+ track.trimmed_range().start_time.to_frames()
219
+ )
189
220
  except Exception as e:
190
221
  current_app.logger.error(
191
222
  f"Parsing frame_in failed: {type(e).__name__}: {str(e)}"
192
223
  )
193
224
  try:
194
- data["frame_out"] = (
195
- track.trimmed_range_in_parent()
196
- .end_time_inclusive()
197
- .to_frames()
198
- )
225
+ try:
226
+ data["frame_out"] = (
227
+ track.trimmed_range_in_parent()
228
+ .end_time_inclusive()
229
+ .to_frames()
230
+ )
231
+ except:
232
+ data["frame_out"] = (
233
+ track.trimmed_range()
234
+ .end_time_inclusive()
235
+ .to_frames()
236
+ )
199
237
  except Exception as e:
200
238
  current_app.logger.error(
201
239
  f"Parsing frame_out failed: {type(e).__name__}: {str(e)}"
@@ -230,11 +268,13 @@ class EDLBaseResource(Resource, ArgsMixin):
230
268
  return result
231
269
 
232
270
 
233
- class EDLImportResource(EDLBaseResource):
271
+ class OTIOImportResource(OTIOBaseResource):
234
272
  @jwt_required()
235
273
  def post(self, **kwargs):
236
274
  """
237
- Import an EDL file to enter frame_in / frame_out / nb_frames.
275
+ Import an OTIO file to enter frame_in / frame_out / nb_frames (it can
276
+ be every adapter supported by OpenTimelineIO, for example: EDL, OTIO...
277
+ ).
238
278
  ---
239
279
  tags:
240
280
  - Import
@@ -255,7 +295,7 @@ class EDLImportResource(EDLBaseResource):
255
295
  201:
256
296
  description: .
257
297
  400:
258
- description: The .EDL file is not properly formatted.
298
+ description: The .otio file is not properly formatted.
259
299
  """
260
300
  return super().post(**kwargs)
261
301
 
@@ -268,15 +308,18 @@ class EDLImportResource(EDLBaseResource):
268
308
  False,
269
309
  str,
270
310
  ),
271
- ("match_case", True, False, bool),
311
+ ("match_case", True, False, inputs.boolean),
272
312
  ]
273
313
  )
274
314
 
275
315
 
276
- class EDLImportEpisodeResource(EDLBaseResource):
316
+ class OTIOImportEpisodeResource(OTIOBaseResource):
317
+ @jwt_required()
277
318
  def post(self, **kwargs):
278
319
  """
279
- Import an EDL file to enter frame_in / frame_out / nb_frames.
320
+ Import an OTIO file to enter frame_in / frame_out / nb_frames (it can
321
+ be every adapter supported by OpenTimelineIO, for example: edl, otio...
322
+ ).
280
323
  ---
281
324
  tags:
282
325
  - Import
@@ -303,7 +346,7 @@ class EDLImportEpisodeResource(EDLBaseResource):
303
346
  201:
304
347
  description: .
305
348
  400:
306
- description: The .EDL file is not properly formatted.
349
+ description: The .otio file is not properly formatted.
307
350
  """
308
351
  return super().post(**kwargs)
309
352
 
@@ -316,6 +359,6 @@ class EDLImportEpisodeResource(EDLBaseResource):
316
359
  False,
317
360
  str,
318
361
  ),
319
- ("match_case", True, False, bool),
362
+ ("match_case", True, False, inputs.boolean),
320
363
  ]
321
364
  )
@@ -35,11 +35,13 @@ from zou.app.blueprints.tasks.resources import (
35
35
  SetTaskMainPreviewResource,
36
36
  PersonsTasksDatesResource,
37
37
  CreateConceptTasksResource,
38
+ OpenTasksStatsResource,
38
39
  )
39
40
 
40
41
 
41
42
  routes = [
42
43
  ("/data/tasks/open-tasks", OpenTasksResource),
44
+ ("/data/tasks/open-tasks/stats", OpenTasksStatsResource),
43
45
  ("/data/tasks/<task_id>/comments", TaskCommentsResource),
44
46
  ("/data/tasks/<task_id>/comments/<comment_id>", TaskCommentResource),
45
47
  ("/data/tasks/<task_id>/previews", TaskPreviewsResource),
@@ -92,8 +94,7 @@ routes = [
92
94
  AddPreviewResource,
93
95
  ),
94
96
  (
95
- "/actions/tasks/<task_id>/comments/<comment_id>/preview-files/"
96
- "<preview_file_id>",
97
+ "/actions/tasks/<task_id>/comments/<comment_id>/preview-files/<preview_file_id>",
97
98
  AddExtraPreviewResource,
98
99
  ),
99
100
  ("/actions/tasks/<task_id>/to-review", ToReviewResource),