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: 174 lines (+88/-12)
1 file modified
src/unity-scope-video-remote (+88/-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 Pending
Review via email: mp+121857@code.launchpad.net

This proposal supersedes a proposal from 2012-08-27.

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 : Posted in a previous version of this proposal

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.

Revision history for this message
Jim Hodapp (jhodapp) wrote : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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) : Posted in a previous version of this proposal
review: Needs Fixing
Revision history for this message
Paweł Stołowski (stolowski) wrote : Posted in a previous version of this proposal

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:51:17 +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,98 @@
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 release_date = None
310 duration = 0
311
312 if "release_date" in details:
313 try:
314 # v1 spec states release_date will be YYYY-MM-DD
315 release_date = datetime.strptime(details["release_date"], "%Y-%m-%d")
316 except ValueError:
317 print "Failed to parse release_date since it was not in format: YYYY-MM-DD"
318
319 if "duration" in details:
320 # Given in seconds, convert to minutes
321 duration = int(details["duration"]) / 60
322
323 if release_date != None and duration > 0:
324 subtitle = release_date.strftime("%Y") + ", " + str(duration) + " " + _("mins")
325
294 preview = Unity.MoviePreview.new(title, subtitle, desc, thumbnail_icon)326 preview = Unity.MoviePreview.new(title, subtitle, desc, thumbnail_icon)
295 play_video = Unity.PreviewAction.new("play", _("Play"), None)327 play_video = Unity.PreviewAction.new("play", _("Play"), None)
296 play_video.connect('activated', self.play_video)328 play_video.connect('activated', self.play_video)
297 preview.add_action(play_video)329 preview.add_action(play_video)
298 #TODO: insert proper uploader and upload date when available in videosearch metadata330
299 preview.add_info(Unity.InfoHint.new("uploaded-by", _("Uploaded by"), None, ""));331 # 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, ""));332 if source == "Amazon":
333 if "directors" in details:
334 directors = details["directors"]
335 preview.add_info(Unity.InfoHint.new("directors",
336 n_("Director", "Directors", len(directors)), None, ', '.join(directors)))
337 if "starring" in details:
338 cast = details["starring"]
339 if cast:
340 preview.add_info(Unity.InfoHint.new("cast",
341 n_("Cast", "Cast", len(cast)), None, ', '.join(cast)))
342 if "genres" in details:
343 genres = details["genres"]
344 if genres:
345 preview.add_info(Unity.InfoHint.new("genres",
346 n_("Genre", "Genres", len(genres)), None, ', '.join(genres)))
347 # TODO: Add Vimeo & YouTube details for v1 of JSON API
348 else:
349 if "uploaded_by" in details:
350 preview.add_info(Unity.InfoHint.new("uploaded-by", _("Uploaded by"),
351 None, details["uploaded_by"]))
352 if "date_uploaded" in details:
353 preview.add_info(Unity.InfoHint.new("uploaded-on", _("Uploaded on"),
354 None, details["date_uploaded"]))
355
301 return preview356 return preview
302357
358 def get_details(self, details_url):
359 """Get online video details for preview"""
360 details = []
361 if len(details_url) == 0:
362 print "Cannot get preview details, no details_url specified."
363 return details
364
365 if self._pending is not None:
366 self.session.cancel_message(self._pending,
367 Soup.KnownStatusCode.CANCELLED)
368
369 self._pending = Soup.Message.new("GET", details_url)
370 # Send a synchronous GET request for video metadata details
371 self.session.send_message(self._pending)
372 try:
373 details = json.loads(self._pending.response_body.data)
374 except ValueError:
375 print "Error: got garbage json from the server"
376
377 return details
378
303 def play_video (self, action, uri):379 def play_video (self, action, uri):
304 """Preview request - Play action handler"""380 """Preview request - Play action handler"""
305 return self.on_activate_uri (action, uri)381 return self.on_activate_uri (action, uri)

Subscribers

People subscribed via source and target branches

to all changes: