Merge lp:~jhodapp/unity-lens-video/remote-videos-scope-trunk into lp:unity-lens-video/remote-videos-scope-trunk

Proposed by Jim Hodapp
Status: Superseded
Proposed branch: lp:~jhodapp/unity-lens-video/remote-videos-scope-trunk
Merge into: lp:unity-lens-video/remote-videos-scope-trunk
Diff against target: 169 lines (+83/-12)
1 file modified
src/unity-scope-video-remote (+83/-12)
To merge this branch: bzr merge lp:~jhodapp/unity-lens-video/remote-videos-scope-trunk
Reviewer Review Type Date Requested Status
Paweł Stołowski (community) Needs Fixing
Review via email: mp+121458@code.launchpad.net

This proposal has been superseded by a proposal from 2012-08-29.

Description of the change

Added details to the Amazon video preview.

To post a comment you must log in.
Revision history for this message
Jim Hodapp (jhodapp) wrote :

One caveat: the SERVER is currently set to the staging URL. This will need to be changed when the JSON API v0 extended is pushed into production.

59. By Jim Hodapp

Merged Saviq's changes.

60. By Jim Hodapp

Renable the main videosearch API server URL instead of the staging one.

61. By Jim Hodapp

Remove staging server URL.

Revision history for this message
Jim Hodapp (jhodapp) wrote :

Change SERVER to videosearch.staging.ubuntu.com to test out the video details for the preview.

Revision history for this message
Paweł Stołowski (stolowski) wrote :

Looking good, just minor comments:

46 # Clear results details map
47 - self.details_map = {}

The comment above is not needed anymore.

128 + duration = int(details["duration"]) / 60
129 + # TODO: Extend VideoPreview class and set property for showing video length

Extending MoviePreview shouldn't be needed; looking at the mockups, video length should be displayed next to year as preview subtitle; in fact year property is not really used by Unity, so you should populate subtitle with both year and duration; don't forget about units, e.g. "2012, 88 mins".

Revision history for this message
Paweł Stołowski (stolowski) :
review: Needs Fixing
62. By Jim Hodapp

Display video release year and length in minutes as the subtitle.

Revision history for this message
Paweł Stołowski (stolowski) wrote :

109 + subtitle = release_date.strftime("%Y") + ", " + str(duration) + " mins"

This will crash if release_date or duration are unset (i.e. not in details). Also str(duration) + " mins" should be i18n friendly.

review: Needs Fixing
63. By Jim Hodapp

Initialize release_date and duration, and internationalize 'mins'.

64. By Jim Hodapp

More flexible duration internationalization. Also allow year or duration to be shown independently of each other being present.

65. By Jim Hodapp

Hide the rating widget by default since the JSON API doesn't yet support rating data in details.

66. By Jim Hodapp

Make the fetch of details data more robust. Signify when the remote server could not be connected to.

67. By Jim Hodapp

No longer need the variable 'uri' in on_preview_uri().

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/unity-scope-video-remote'
--- src/unity-scope-video-remote 2012-08-14 12:50:14 +0000
+++ src/unity-scope-video-remote 2012-08-29 13:04:18 +0000
@@ -22,6 +22,7 @@
22import sys22import sys
23from urllib import urlencode23from urllib import urlencode
24import time24import time
25from datetime import datetime
25import gettext26import gettext
2627
27#pylint: disable=E061128#pylint: disable=E0611
@@ -49,6 +50,7 @@
49gettext.bindtextdomain(APP_NAME, LOCAL_PATH)50gettext.bindtextdomain(APP_NAME, LOCAL_PATH)
50gettext.textdomain(APP_NAME)51gettext.textdomain(APP_NAME)
51_ = gettext.gettext52_ = gettext.gettext
53n_ = gettext.ngettext
5254
53BUS_NAME = "net.launchpad.scope.RemoteVideos"55BUS_NAME = "net.launchpad.scope.RemoteVideos"
54SERVER = "http://videosearch.ubuntu.com/v0"56SERVER = "http://videosearch.ubuntu.com/v0"
@@ -89,14 +91,14 @@
89 self.query_list_of_sources()91 self.query_list_of_sources()
90 self.update_recommendations()92 self.update_recommendations()
91 self.scope.export()93 self.scope.export()
92 self.details_map = {} #holds details not passed via lens-meta://, needed by preview
9394
94 def update_recommendations(self):95 def update_recommendations(self):
95 """Query the server for 'recommendations'.96 """Query the server for 'recommendations'.
9697
97 In v0, that means simply do an empty search.98 In v0 extended, that means simply do an empty search with Amazon
99 as the source since it's the only paid provider at the moment.
98 """100 """
99 msg = Soup.Message.new("GET", SERVER + "/search")101 msg = Soup.Message.new("GET", SERVER + "/search?q=&sources=Amazon")
100 self.session.queue_message(msg, self._recs_cb, None)102 self.session.queue_message(msg, self._recs_cb, None)
101103
102 def _recs_cb(self, session, msg, user_data):104 def _recs_cb(self, session, msg, user_data):
@@ -175,11 +177,10 @@
175 the server, update the model and notify the lens when we are done."""177 the server, update the model and notify the lens when we are done."""
176 search_string = search.props.search_string.strip()178 search_string = search.props.search_string.strip()
177 # note search_string is a utf-8 encoded string, now:179 # note search_string is a utf-8 encoded string, now:
178 print "Search changed to %r" % search_string180 print "Remote scope search changed to %r" % search_string
179 model = search.props.results_model181 model = search.props.results_model
180 model.clear()182 model.clear()
181 # Clear results details map183 # Clear results details map
182 self.details_map = {}
183 # Create a list of activated sources184 # Create a list of activated sources
184 sources = [source for source in self.sources_list185 sources = [source for source in self.sources_list
185 if self.source_activated(source)]186 if self.source_activated(source)]
@@ -207,13 +208,17 @@
207 title = i['title'].encode('utf-8')208 title = i['title'].encode('utf-8')
208 icon = i['img'].encode('utf-8')209 icon = i['img'].encode('utf-8')
209 comment = i['source'].encode('utf-8')210 comment = i['source'].encode('utf-8')
210 self.details_map[uri] = (comment) #TODO: more to come211 details_url = ""
212 if "details" in i:
213 details_url = i['details'].encode('utf-8')
214
211 if uri.startswith('http'):215 if uri.startswith('http'):
212 # Instead of an uri, we pass a string of uri + metadata216 # Instead of an uri, we pass a string of uri + metadata
213 model.append(uri+'lens-meta://'+title+'lens-meta://'+icon,217 model.append(uri+'lens-meta://'+title+'lens-meta://'+icon+'lens-meta://'+details_url,
214 icon, 2, "text/html", str(title), str(comment), str(uri))218 icon, 2, "text/html", str(title), str(comment), str(uri))
215 except (KeyError, TypeError, AttributeError):219 except (KeyError, TypeError, AttributeError):
216 print "Malformed result, skipping."220 print "Malformed result, skipping."
221
217 model.flush_revision_queue()222 model.flush_revision_queue()
218 if search:223 if search:
219 search.finished()224 search.finished()
@@ -279,27 +284,93 @@
279284
280 def on_preview_uri (self, scope, rawuri):285 def on_preview_uri (self, scope, rawuri):
281 """Preview request handler"""286 """Preview request handler"""
287 details_url = rawuri.split('lens-meta://')[3]
288 details = self.get_details(details_url)
289
282 uri = rawuri.split('lens-meta://')[0]290 uri = rawuri.split('lens-meta://')[0]
283 title = rawuri.split('lens-meta://')[1]291 title = rawuri.split('lens-meta://')[1]
284 thumbnail_uri = rawuri.split('lens-meta://')[2]292 thumbnail_uri = rawuri.split('lens-meta://')[2]
285 subtitle = ''293 subtitle = ''
286 desc = ''294 desc = ''
287 if self.details_map.has_key (uri):295 source = ''
288 desc = self.details_map[uri][0]296 if isinstance(details, dict):
297 uri = details['browser_url']
298 title = details['title']
299 thumbnail_uri = details['image']
300 desc = details['description']
301 source = details['source']
302
289 try:303 try:
290 thumbnail_file = Gio.File.new_for_uri (thumbnail_uri)304 thumbnail_file = Gio.File.new_for_uri (thumbnail_uri)
291 thumbnail_icon = Gio.FileIcon.new(thumbnail_file)305 thumbnail_icon = Gio.FileIcon.new(thumbnail_file)
292 except:306 except:
293 thumbnail_icon = None307 thumbnail_icon = None
308
309 if "release_date" in details:
310 try:
311 # v1 spec states release_date will be YYYY-MM-DD
312 release_date = datetime.strptime(details["release_date"], "%Y-%m-%d")
313 except ValueError:
314 print "Failed to parse release_date since it was not in format: YYYY-MM-DD"
315 if "duration" in details:
316 # Given in seconds, convert to minutes
317 duration = int(details["duration"]) / 60
318
319 subtitle = release_date.strftime("%Y") + ", " + str(duration) + " mins"
320
294 preview = Unity.MoviePreview.new(title, subtitle, desc, thumbnail_icon)321 preview = Unity.MoviePreview.new(title, subtitle, desc, thumbnail_icon)
295 play_video = Unity.PreviewAction.new("play", _("Play"), None)322 play_video = Unity.PreviewAction.new("play", _("Play"), None)
296 play_video.connect('activated', self.play_video)323 play_video.connect('activated', self.play_video)
297 preview.add_action(play_video)324 preview.add_action(play_video)
298 #TODO: insert proper uploader and upload date when available in videosearch metadata325
299 preview.add_info(Unity.InfoHint.new("uploaded-by", _("Uploaded by"), None, ""));326 # TODO: For details of future source types, factor out common detail key/value pairs
300 preview.add_info(Unity.InfoHint.new("uploaded-on", _("Uploaded on"), None, ""));327 if source == "Amazon":
328 if "directors" in details:
329 directors = details["directors"]
330 preview.add_info(Unity.InfoHint.new("directors",
331 n_("Director", "Directors", len(directors)), None, ', '.join(directors)))
332 if "starring" in details:
333 cast = details["starring"]
334 if cast:
335 preview.add_info(Unity.InfoHint.new("cast",
336 n_("Cast", "Cast", len(cast)), None, ', '.join(cast)))
337 if "genres" in details:
338 genres = details["genres"]
339 if genres:
340 preview.add_info(Unity.InfoHint.new("genres",
341 n_("Genre", "Genres", len(genres)), None, ', '.join(genres)))
342 # TODO: Add Vimeo & YouTube details for v1 of JSON API
343 else:
344 if "uploaded_by" in details:
345 preview.add_info(Unity.InfoHint.new("uploaded-by", _("Uploaded by"),
346 None, details["uploaded_by"]))
347 if "date_uploaded" in details:
348 preview.add_info(Unity.InfoHint.new("uploaded-on", _("Uploaded on"),
349 None, details["date_uploaded"]))
350
301 return preview351 return preview
302352
353 def get_details(self, details_url):
354 """Get online video details for preview"""
355 details = []
356 if len(details_url) == 0:
357 print "Cannot get preview details, no details_url specified."
358 return details
359
360 if self._pending is not None:
361 self.session.cancel_message(self._pending,
362 Soup.KnownStatusCode.CANCELLED)
363
364 self._pending = Soup.Message.new("GET", details_url)
365 # Send a synchronous GET request for video metadata details
366 self.session.send_message(self._pending)
367 try:
368 details = json.loads(self._pending.response_body.data)
369 except ValueError:
370 print "Error: got garbage json from the server"
371
372 return details
373
303 def play_video (self, action, uri):374 def play_video (self, action, uri):
304 """Preview request - Play action handler"""375 """Preview request - Play action handler"""
305 return self.on_activate_uri (action, uri)376 return self.on_activate_uri (action, uri)

Subscribers

People subscribed via source and target branches

to all changes: