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
@@ -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,26 +40,26 @@ mapping_substitutions_to_regex = {
40
40
  }
41
41
 
42
42
 
43
- class EDLBaseResource(Resource, ArgsMixin):
43
+ class OTIOBaseResource(Resource, ArgsMixin):
44
44
  @jwt_required()
45
45
  def post(self, project_id, episode_id=None):
46
46
  args = self.post_args()
47
47
  user_service.check_manager_project_access(project_id)
48
48
  uploaded_file = request.files["file"]
49
- file_name = "%s.edl" % uuid.uuid4()
49
+ file_name = uploaded_file.filename
50
50
  file_path = os.path.join(config.TMP_DIR, file_name)
51
51
  uploaded_file.save(file_path)
52
- self.prepare_import(
53
- project_id,
54
- episode_id,
55
- args["naming_convention"],
56
- args["match_case"],
57
- )
58
52
  try:
59
- 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
+ )
60
60
  except Exception as e:
61
61
  current_app.logger.error(
62
- f"Import EDL failed: {type(e).__name__}: {str(e)}"
62
+ f"Import OTIO failed: {type(e).__name__}: {str(e)}"
63
63
  )
64
64
  return {
65
65
  "error": True,
@@ -121,25 +121,50 @@ class EDLBaseResource(Resource, ArgsMixin):
121
121
  key = key.lower()
122
122
  self.shot_map[key] = shot["id"]
123
123
 
124
- 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
+ ):
125
132
  result = {"updated_shots": [], "created_shots": []}
126
133
  try:
127
- with open(file_path, "r+", errors="replace") as edl_file:
128
- contents = edl_file.read()
129
- edl_file.seek(0)
130
- edl_file.write(contents)
131
-
132
- timeline = otio.adapters.read_from_file(
133
- file_path,
134
- rate=projects_service.get_project_fps(project_id),
135
- ignore_timecode_mismatch=True,
136
- )
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)
137
140
  except Exception as e:
138
- 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
+ )
139
149
  for video_track in timeline.video_tracks():
140
150
  for track in video_track:
141
151
  if isinstance(track, otio.schema.Clip):
142
- 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]
143
168
  name_to_search = name if self.match_case else name.lower()
144
169
  if name_to_search in self.shot_map:
145
170
  shot_id = self.shot_map[name_to_search]
@@ -184,19 +209,31 @@ class EDLBaseResource(Resource, ArgsMixin):
184
209
 
185
210
  data = future_shot_values["data"] or {}
186
211
  try:
187
- data["frame_in"] = (
188
- track.trimmed_range_in_parent().start_time.to_frames()
189
- )
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
+ )
190
220
  except Exception as e:
191
221
  current_app.logger.error(
192
222
  f"Parsing frame_in failed: {type(e).__name__}: {str(e)}"
193
223
  )
194
224
  try:
195
- data["frame_out"] = (
196
- track.trimmed_range_in_parent()
197
- .end_time_inclusive()
198
- .to_frames()
199
- )
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
+ )
200
237
  except Exception as e:
201
238
  current_app.logger.error(
202
239
  f"Parsing frame_out failed: {type(e).__name__}: {str(e)}"
@@ -231,11 +268,13 @@ class EDLBaseResource(Resource, ArgsMixin):
231
268
  return result
232
269
 
233
270
 
234
- class EDLImportResource(EDLBaseResource):
271
+ class OTIOImportResource(OTIOBaseResource):
235
272
  @jwt_required()
236
273
  def post(self, **kwargs):
237
274
  """
238
- 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
+ ).
239
278
  ---
240
279
  tags:
241
280
  - Import
@@ -256,7 +295,7 @@ class EDLImportResource(EDLBaseResource):
256
295
  201:
257
296
  description: .
258
297
  400:
259
- description: The .EDL file is not properly formatted.
298
+ description: The .otio file is not properly formatted.
260
299
  """
261
300
  return super().post(**kwargs)
262
301
 
@@ -269,16 +308,18 @@ class EDLImportResource(EDLBaseResource):
269
308
  False,
270
309
  str,
271
310
  ),
272
- ("match_case", True, False, bool),
311
+ ("match_case", True, False, inputs.boolean),
273
312
  ]
274
313
  )
275
314
 
276
315
 
277
- class EDLImportEpisodeResource(EDLBaseResource):
316
+ class OTIOImportEpisodeResource(OTIOBaseResource):
278
317
  @jwt_required()
279
318
  def post(self, **kwargs):
280
319
  """
281
- 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
+ ).
282
323
  ---
283
324
  tags:
284
325
  - Import
@@ -305,7 +346,7 @@ class EDLImportEpisodeResource(EDLBaseResource):
305
346
  201:
306
347
  description: .
307
348
  400:
308
- description: The .EDL file is not properly formatted.
349
+ description: The .otio file is not properly formatted.
309
350
  """
310
351
  return super().post(**kwargs)
311
352
 
@@ -318,6 +359,6 @@ class EDLImportEpisodeResource(EDLBaseResource):
318
359
  False,
319
360
  str,
320
361
  ),
321
- ("match_case", True, False, bool),
362
+ ("match_case", True, False, inputs.boolean),
322
363
  ]
323
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),