1 from ldt.ldt_utils.models import Project, Content, Segment |
|
2 from django.db.models import F, Q |
|
3 from piston.handler import BaseHandler |
|
4 from piston.utils import rc, require_extended |
|
5 from ldt.ldt_utils.utils import LdtAnnotation |
|
6 from ldt.ldt_utils.stat import add_annotation_to_stat |
|
7 from ldt.security import protect_models, unprotect_models |
|
8 from ldt.ldt_utils.segmentserializer import SegmentSerializer |
|
9 import logging #@UnresolvedImport |
|
10 |
|
11 |
|
12 class ProjectHandler(BaseHandler): |
|
13 allowed_methods = ('GET', 'PUT',) |
|
14 model = Project |
|
15 |
|
16 def read(self, request, project_id): |
|
17 """ |
|
18 returns a single project |
|
19 """ |
|
20 return Project.objects.get(ldt_id=project_id) |
|
21 |
|
22 @require_extended |
|
23 def update(self, request, project_id): |
|
24 #return rc.ALL_OK |
|
25 logging.debug("request " + repr(request)) |
|
26 data = request.data |
|
27 ldt_str = data["ldt"] |
|
28 |
|
29 logging.debug("request data" + repr(ldt_str)) |
|
30 |
|
31 if not ldt_str: |
|
32 return rc.BAD_REQUEST |
|
33 |
|
34 project = Project.objects.get(ldt_id=project_id) |
|
35 |
|
36 project.ldt = ldt_str |
|
37 |
|
38 unprotect_models() |
|
39 project.save() |
|
40 protect_models() |
|
41 |
|
42 return rc.ALL_OK |
|
43 |
|
44 |
|
45 class AnnotationHandler(BaseHandler): |
|
46 allowed_methods = ('PUT',) |
|
47 |
|
48 @require_extended |
|
49 def update(self, request, project_id): |
|
50 """ |
|
51 This method is called when a PUT request is sent to http://<plateform_location>/api/ldt/projects/<project_id>.<format>. |
|
52 <project_id> is the ldt_id field of the project. If <projet_id> does not match any project on the platform, a 410 ("Gone") |
|
53 error will be returned. |
|
54 <format> is the format of the data sent back by the server. It can be 'json', 'yaml', 'xml' or 'pickle'. |
|
55 |
|
56 If the request contains a content-type header whose value is set to "application/json" and a valid utf-8 encoded JSON file, |
|
57 the following conditions will be checked before the annotations are added : |
|
58 If the submitted file is not valid or refers to a media that is not contained in the project, a 500 ("Bad Request") |
|
59 error will be returned. If the "type" field of an annotation matches an already existing cutting, it will be added to that |
|
60 cutting. Otherwise, a new cutting will be added (as well as a new ensemble if needed). New cuttings are added to the view |
|
61 "View at the last recording" if it exists, or to the view "Init view" else. If none of those views exist, the server will |
|
62 not add the cutting to a view. Several annotations can be added at the same time if the submitted file contains multiple |
|
63 annotations. The server returns the file submitted if all annotations have been added successfully, and adds to this file |
|
64 IDs of created annotations to the file with a 200("OK") error code. |
|
65 |
|
66 If no content-type header is set, the file submitted must be a valid XML file and will replace entirely the ldt field |
|
67 of the project without any verifications. |
|
68 |
|
69 Example : |
|
70 |
|
71 Remark : The file below contain the minimum necessary fields and attributes for the handler to work. If one field or attribute is |
|
72 missing (e.g. author, or date) during submission, an error will occur. |
|
73 |
|
74 A platform is reachable at http://localhost/. It contains a project with ID a0593b58-f258-11df-80e1-00145ea4a2be. This project has |
|
75 a content milosforman_amadeus, which has a cutting Salieri inside the view "View at the last recording". The following JSON file exists in the current directory : |
|
76 |
|
77 Example of ajax call with 2 differents annotations : |
|
78 $('#mon_ajax').click(function(e) { |
|
79 var url = "{% url project_api project_id='c8448f21-272d-11e1-876b-c8bcc896c290' emitter_format='.json' %}"; // Don't forget the "." before "json" ! |
|
80 |
|
81 var monjson = '{\ |
|
82 "annotations": [\ |
|
83 {\ |
|
84 "type": "c_07BA1284-5F24-71A8-1EE2-423EED999B8A",\ |
|
85 "type_title": "New cutting name if necessary",\ |
|
86 "media": "briandepalma_scarfacedepalma",\ |
|
87 "begin": 1600000,\ |
|
88 "end": 2100000,\ |
|
89 "content": {\ |
|
90 "data": "new scar annot"\ |
|
91 "audio": {\ |
|
92 "mimetype": "audio/mp3",\ |
|
93 "src": "mic",\ |
|
94 "href": "rtmp://media.iri.centrepompidou.fr/ddc_micro_record/r_20120606190143793"\ |
|
95 }\ |
|
96 },\ |
|
97 "tags": [ "json","dude" ]\ |
|
98 }\ |
|
99 ],\ |
|
100 "meta": {\ |
|
101 "creator": "John Doe",\ |
|
102 "created": "2011-09-10T09:12:58"\ |
|
103 }\ |
|
104 }'; |
|
105 var monjson2 = '{\ |
|
106 "annotations": [\ |
|
107 {\ |
|
108 "type": "c_07BA1284-5F24-71A8-1EE2-423EED999B8A",\ |
|
109 "type_title": "New cutting name if necessary",\ |
|
110 "media": "briandepalma_scarfacedepalma",\ |
|
111 "begin": 2400000,\ |
|
112 "end": 3000000,\ |
|
113 "content": {\ |
|
114 "data": "ntm iam 2"\ |
|
115 },\ |
|
116 "tags": [ "jak" ]\ |
|
117 }\ |
|
118 ],\ |
|
119 "meta": {\ |
|
120 "creator": "John Doe",\ |
|
121 "created": "2011-09-10T09:12:58"\ |
|
122 }\ |
|
123 }'; |
|
124 |
|
125 $.ajax({ |
|
126 url: url, |
|
127 type: 'PUT', |
|
128 contentType: 'application/json', |
|
129 data: monjson, |
|
130 // bug with jquery >= 1.5, "json" adds a callback so we don't specify dataType |
|
131 //dataType: 'json', |
|
132 success: function(json, textStatus, XMLHttpRequest) { |
|
133 alert("success = " + json); |
|
134 }, |
|
135 error: function(jqXHR, textStatus, errorThrown) { |
|
136 alert("ERROR = " + jqXHR.responseText + ", " + errorThrown); |
|
137 } |
|
138 }); |
|
139 }); |
|
140 |
|
141 If we send a PUT request with curl : |
|
142 $curl -X PUT http://localhost/api/ldt/projects/a0593b58-f258-11df-80e1-00145ea4a2be.json -d @example.JSON -H "content-type:application/json" |
|
143 A new cutting titled "New cutting name" will be created with the first annotation inside, and the annotation "Annotation about Salieri" |
|
144 will be added to the Salieri cutting. The returned file is : |
|
145 |
|
146 { |
|
147 "annotations": [ |
|
148 { |
|
149 "id": "6d8baf01-ffb1-11e0-810c-001485352c9a", |
|
150 "type": "id_annot_type", |
|
151 "type_title": "New cutting name", |
|
152 "media": "milosforman_amadeus", |
|
153 "begin": 50000, |
|
154 "end": 900000, |
|
155 "content": { |
|
156 "data": "new annotation" |
|
157 }, |
|
158 "tags": [ "json" ] |
|
159 }, |
|
160 { |
|
161 "id": "6d8baf00-ffb1-11e0-8097-001485352c9b", |
|
162 "type": "another_id_annot_type", |
|
163 "type_title": "Salieri", |
|
164 "media": "milosforman_amadeus", |
|
165 "begin": 700000, |
|
166 "end": 1200000, |
|
167 "content": { |
|
168 "data": "Annotation about Salieri" |
|
169 }, |
|
170 "tags": [ "xml", "test", "blop" ] |
|
171 } |
|
172 ], |
|
173 |
|
174 "meta": { |
|
175 "creator": "John Doe", |
|
176 "created": "2011-09-10T09:12:58" |
|
177 } |
|
178 } |
|
179 |
|
180 """ |
|
181 #return rc.ALL_OK |
|
182 try: |
|
183 project = Project.objects.get(ldt_id=project_id) |
|
184 except Project.DoesNotExist: |
|
185 return rc.NOT_HERE |
|
186 |
|
187 adder = LdtAnnotation(project) |
|
188 logging.debug("request json " + repr(request.data)) |
|
189 |
|
190 unprotect_models() # Allows anonymous user to modify models in this request only |
|
191 |
|
192 meta = request.data['meta'] |
|
193 author = meta['creator'] |
|
194 date = meta['created'] |
|
195 new_annotations = request.data['annotations'] |
|
196 |
|
197 for a in new_annotations: |
|
198 dur = str(a['end'] - a['begin']) |
|
199 begin = str(a['begin']) |
|
200 # We test if the annotation has audio node |
|
201 audio_src = "" |
|
202 audio_href = "" |
|
203 if a['content'].has_key('audio') : |
|
204 if a['content']['audio'].has_key('src') : |
|
205 audio_src = a['content']['audio']['src'] |
|
206 if a['content']['audio'].has_key('href') : |
|
207 audio_href = a['content']['audio']['href'] |
|
208 type_id, new_id = adder.add(a['media'], a['type'], a['type_title'], a['content']['data'], '', a['tags'], begin, dur, author, date, None, "2194379", audio_src, audio_href) |
|
209 if not new_id: |
|
210 protect_models() |
|
211 return rc.BAD_REQUEST |
|
212 |
|
213 |
|
214 content = project.contents.get(iri_id=a['media']) |
|
215 add_annotation_to_stat(content, a['begin'], a['end']) |
|
216 |
|
217 # We update the ids |
|
218 a['type'] = type_id |
|
219 a['id'] = new_id |
|
220 if not a['content'].has_key('audio') : |
|
221 a['content']['audio'] = {'src':audio_src, 'href':audio_href} |
|
222 |
|
223 # We save if there were added annotation |
|
224 if len(new_annotations)>0 : |
|
225 adder.save() |
|
226 |
|
227 protect_models() |
|
228 |
|
229 return request.data |
|
230 |
|
231 |
|
232 |
|
233 class ContentHandler(BaseHandler): |
|
234 allowed_methods = ('GET', 'PUT') |
|
235 model = Content |
|
236 exclude = ( |
|
237 ("media_obj"), |
|
238 ) |
|
239 |
|
240 def read(self, request, iri_id): |
|
241 """ |
|
242 returns a single content |
|
243 """ |
|
244 return Content.objects.get(iri_id=iri_id) |
|
245 |
|
246 @require_extended |
|
247 def update(self, request, iri_id): |
|
248 """ |
|
249 Receives a json exactly like AnnotationHandler, but without any project indicated. |
|
250 We get or set the current content front project, and add the annotation |
|
251 """ |
|
252 try: |
|
253 content = Content.objects.get(iri_id=iri_id) |
|
254 except Content.DoesNotExist: |
|
255 return rc.NOT_HERE |
|
256 proj = content.get_or_create_front_project() |
|
257 ah = AnnotationHandler() |
|
258 updated_data = ah.update(request, proj.ldt_id) |
|
259 |
|
260 return updated_data |
|
261 |
|
262 |
|
263 class SegmentHandler(BaseHandler): |
|
264 allowed_methods = ('GET', ) |
|
265 model = Segment |
|
266 exclude = ( |
|
267 ("project_obj"), |
|
268 ("content"), |
|
269 ) |
|
270 |
|
271 def read(self, request, iri_id, begin, end): |
|
272 """ |
|
273 returns segments about content iri_id between timecodes begin and end |
|
274 """ |
|
275 begin = int(begin) |
|
276 end = int(end) |
|
277 |
|
278 content = Content.objects.filter(iri_id=iri_id) |
|
279 if not content: |
|
280 return rc.NOT_FOUND |
|
281 content = content[0] |
|
282 |
|
283 segments = Segment.objects.filter(content=content).filter( |
|
284 Q(start_ts__gte=begin, start_ts__lte=end) | # segment starts between begin and end |
|
285 Q(start_ts__gte=begin-F('duration'), start_ts__lte=end-F('duration')) |# segment ends between begin and end |
|
286 Q(start_ts__lte=begin, start_ts__gte=end-F('duration')) # period [begin:end] is included in the segment |
|
287 ) |
|
288 |
|
289 a = SegmentSerializer(content, segments) |
|
290 return a.serialize_to_cinelab() |
|
291 |
|