示例#1
0
文件: search.py 项目: drewp/photo
    def render_random(self, ctx, data):

        for randRow in randomSet(self.graph, 3):
            print 'randRow', randRow
            current = randRow['uri']
            bindings = {"pic": current}
            tags = [[
                T.a(href=['/set?tag=', row['tag']])[row['tag']], ' '
            ] for row in self.graph.queryd("""SELECT DISTINCT ?tag WHERE {
                             ?pic scot:hasTag [
                               rdfs:label ?tag ]
                           }""",
                                           initBindings=bindings)]
            depicts = [[T.a(href=localSite(row['uri']))[row['label']], ' ']
                       for row in self.graph.queryd("""
                         SELECT DISTINCT ?uri ?label WHERE {
                           ?pic foaf:depicts ?uri .
                           ?uri rdfs:label ?label .
                         }""",
                                                    initBindings=bindings)]
            # todo: description and tags would be good too, and some
            # other service should be rendering this whole section
            yield T.div(class_="randPick")[T.a(href=[
                '/set?',
                urllib.urlencode(dict(date=randRow['date'], current=current))
            ])[T.img(src=[localSite(current), '?size=medium']), ], T.div[tags],
                                           T.div[depicts],
                                           T.div[randRow['filename'].
                                                 replace('/my/pic/', '')], ]
示例#2
0
文件: edit.py 项目: drewp/photo
    def render_pics(self, ctx, data):
        rows = []
        d = URIRef(ctx.arg('dir'))
        for i, (pic, filename) in enumerate(sorted(picsInDirectory(self.graph, d))[:]):
            img = T.img(src=[localSite(pic), '?size=thumb'],
                        # look these up in the graph
                        width=75, height=56,
                        onclick='javascript:photo.showLarge("%s")' %
                        (localSite(pic) + "?size=large"))


            tableRow = T.table(class_="picRow")[T.tr[
                T.td[T.a(href=localSite(pic))[filename]],
                T.td[img],
                T.td[
                T.div["Depicts: ", T.input(type="text", class_="tags")],
                T.div["Comment: ", T.input(type="text")],
                ],
                ]]

            toCopy = "protoSectionSplitter"
            if i ==0:
                toCopy = "protoSectionBreak"
                

            rows.append([T.raw('<script type="text/javascript">document.write(document.getElementById("%s").innerHTML);</script>' % toCopy),
                         tableRow])
            
        return rows
示例#3
0
文件: search.py 项目: drewp/photo
 def render_random(self, ctx, data):
     
     for randRow in randomSet(self.graph, 3):
         print 'randRow', randRow
         current = randRow['uri']
         bindings = {"pic" : current}
         tags = [[T.a(href=['/set?tag=', row['tag']])[row['tag']], ' ']
                 for row in self.graph.queryd(
                     """SELECT DISTINCT ?tag WHERE {
                          ?pic scot:hasTag [
                            rdfs:label ?tag ]
                        }""", initBindings=bindings)]
         depicts = [[T.a(href=localSite(row['uri']))[row['label']], ' ']
                    for row in self.graph.queryd("""
                      SELECT DISTINCT ?uri ?label WHERE {
                        ?pic foaf:depicts ?uri .
                        ?uri rdfs:label ?label .
                      }""", initBindings=bindings)]
         # todo: description and tags would be good too, and some
         # other service should be rendering this whole section
         yield T.div(class_="randPick")[
             T.a(href=['/set?',
                       urllib.urlencode(dict(date=randRow['date'],
                                             current=current))])[
                 T.img(src=[localSite(current), '?size=medium']),
                 ],
             T.div[tags],
             T.div[depicts],
             T.div[randRow['filename'].replace('/my/pic/','')],
             ]
示例#4
0
文件: oneimage.py 项目: drewp/photo
def GET_links(request):
    """images and other things related to this one"""

    img = URIRef(request.args['uri'][0])
    links = {}

    def relQuery(rel):
        rows = graph.queryd("""
           SELECT DISTINCT ?d ?label WHERE {
             ?img ?rel ?d .
             OPTIONAL { ?d rdfs:label ?label }
           }""",
                            initBindings={
                                Variable("rel"): rel,
                                Variable("img"): img
                            })
        for r in rows:
            if 'label' not in r:
                r['label'] = r['d']
            yield r

    def setUrl(**params):
        params['current'] = img
        return ('/set?' + urllib.urlencode(params))

    for row in relQuery(FOAF.depicts):
        try:
            links.setdefault('depicting', []).append({
                'uri': localSite(row['d']),
                'label': row['label']
            })
        except ValueError, e:
            log.warn("error in FOAF.depicts: %s %s" % (vars(), e))
            pass
示例#5
0
    def GET(self):
        uri = URIRef(web.input()['uri'])
        r = MediaResource(graph, uri)
        size = r.getSize(sizes["screen"])

        try:
            created = photoCreated(graph, uri)
            prettyDate = created.date().isoformat()
        except ValueError:
            prettyDate = "(unknown date)"
            
        tmpl = loader.load("sharesingle.html")
        stream = tmpl.generate(
            title="photo",
            prettyDate=prettyDate,
            bestJqueryLink=networking.jqueryLink(
                web.ctx.environ.get('HTTP_X_FORWARDED_FOR', '')),
            featuredImg=Markup('<img src="%s" width="%s" height="%s"/>' %
                               (localSite(uri)+"?size=screen",
                                size[0], size[1])),
            loginWidget=Markup(networking.getLoginBarSync(
                web.ctx.environ.get('HTTP_COOKIE', ''))),
            actionsAllowed=[],
            otherSizeLinks=[],
            link="l",
            allowedToWriteMeta=False,
            pageJson="",
            )
        return (''.join(serializer(stream))).encode('utf8')
示例#6
0
文件: story.py 项目: drewp/photo
def sizeAttrs_by_http(foafUser, uri, sizeName):
    innerUri = localSite(uri) + '/size'
    site = restkit.Resource('http://bang:8086/')
    # restkit.get would hang in this twisted process
    return json.loads(site.get(path=innerUri, size=sizeName,
                                  headers={'x-foaf-agent' : foafUser}
                                  ).body_string())
示例#7
0
def GET_links(request):
    """images and other things related to this one"""

    img = URIRef(request.args['uri'][0])
    links = {}

    def relQuery(rel):
        rows = graph.queryd("""
           SELECT DISTINCT ?d ?label WHERE {
             ?img ?rel ?d .
             OPTIONAL { ?d rdfs:label ?label }
           }""", initBindings={Variable("rel") : rel,
                               Variable("img") : img})
        for r in rows:
            if 'label' not in r:
                r['label'] = r['d']
            yield r

    def setUrl(**params):
        params['current'] = img
        return ('/set?' + urllib.urlencode(params))

    for row in relQuery(FOAF.depicts):
        try:
            links.setdefault('depicting', []).append(
                {'uri' : localSite(row['d']), 'label' : row['label']})
        except ValueError, e:
            log.warn("error in FOAF.depicts: %s %s" % (vars(), e))
            pass
示例#8
0
文件: story.py 项目: drewp/photo
def sizeAttrs_by_http(foafUser, uri, sizeName):
    innerUri = localSite(uri) + '/size'
    site = restkit.Resource('http://bang:8086/')
    # restkit.get would hang in this twisted process
    return json.loads(
        site.get(path=innerUri,
                 size=sizeName,
                 headers={
                     'x-foaf-agent': foafUser
                 }).body_string())
示例#9
0
 def photosInSet(self):
     desc = self.desc
     return [dict(thumb=dict(
         link=desc.otherImageUrl(p),
         uri=p,
         cls=(("current" if p == desc.currentPhoto() else "not-current")
              + (" video" if desc.isVideo(p) else '')),
         src="%s?size=thumb" % localSite(p),
         isVideo=desc.isVideo(p)))
             for p in desc.photos()]
示例#10
0
文件: imageSet.py 项目: drewp/photo
 def photosInSet(self):
     desc = self.desc
     return [
         dict(thumb=dict(link=desc.otherImageUrl(p),
                         uri=p,
                         cls=(("current" if p ==
                               desc.currentPhoto() else "not-current") +
                              (" video" if desc.isVideo(p) else '')),
                         src="%s?size=thumb" % localSite(p),
                         isVideo=desc.isVideo(p))) for p in desc.photos()
     ]
示例#11
0
文件: imageSet.py 项目: drewp/photo
 def nextImagePreload(self):
     _, nextImg = self.prevNext()
     if nextImg is None:
         return ''
     # must always return a string, for json encoding below
     preloadSize = "large"
     if nextImg.lower().endswith(videoExtensions):
         # sloppy; this should use the presence of "a pho:Video" in the graph.
         # also, i'm still stuffing this in an img tag, which may
         # or may not trigger a convert or load
         preloadSize = "video2"
     return localSite(nextImg) + "?size=" + preloadSize
示例#12
0
 def nextImagePreload(self):
     _, nextImg = self.prevNext()
     if nextImg is None:
         return ''
     # must always return a string, for json encoding below
     preloadSize = "large"
     if nextImg.lower().endswith(videoExtensions):
         # sloppy; this should use the presence of "a pho:Video" in the graph.
         # also, i'm still stuffing this in an img tag, which may
         # or may not trigger a convert or load
         preloadSize = "video2"
     return localSite(nextImg) + "?size=" + preloadSize
示例#13
0
文件: search.py 项目: drewp/photo
 def render_newestDirs(self, ctx, data):
     # todo- should use rdf and work over all dirs
     top = '/my/pic/digicam'
     times = []
     for fn in os.listdir(top):
         fn = os.path.join(top, fn) + '/'
         if not os.path.isdir(fn):
             continue
         times.append((os.path.getmtime(fn), fn))
     times.sort(reverse=True)
     for t, dirname in times[:10]:
         # todo: escaping
         yield T.div[T.a(href=[localSite('/set?dir='), photoUri(dirname)])[dirname]]
示例#14
0
文件: edit.py 项目: drewp/photo
    def render_table(self, ctx, data):
        if not ctx.arg('dir'):
            return []
        
        d = URIRef(ctx.arg('dir')) # "http://photo.bigasterisk.com/digicam/dl-2008-09-25")
        rows = []
        for i, (pic, filename) in enumerate(sorted(
            picsInDirectory(self.graph, d))[:]):
            img = T.img(src=[localSite(pic), '?size=thumb'],
                        onclick='javascript:photo.showLarge("%s")' %
                        (localSite(pic) + "?size=large"))

            picCols = [''] * self.brickColumns
            for fill in range(i, self.brickColumns):
                picCols[fill] = T.td
            picCols[i % self.brickColumns] = T.td(rowspan=self.brickColumns)[img]
            
            rows.append(T.tr(subj=pic)[
                T.td[T.a(href=localSite(pic))[filename]],
                picCols,
                ])

        return rows
示例#15
0
文件: search.py 项目: drewp/photo
 def render_newestDirs(self, ctx, data):
     # todo- should use rdf and work over all dirs
     top = '/my/pic/digicam'
     times = []
     for fn in os.listdir(top):
         fn = os.path.join(top, fn) + '/'
         if not os.path.isdir(fn):
             continue
         times.append((os.path.getmtime(fn), fn))
     times.sort(reverse=True)
     for t, dirname in times[:10]:
         # todo: escaping
         yield T.div[T.a(href=[localSite('/set?dir='),
                               photoUri(dirname)])[dirname]]
示例#16
0
    def otherSizeLinks(self):
        if self.desc.currentPhoto() is None:
            return []

        isVideo = self.desc.isVideo(self.desc.currentPhoto())

        if isVideo:
            avail = ['video2', 'full']
        else:
            avail = ['medium', 'large', 'screen', 'full']
            
        return [
            dict(href="%s?size=%s" % (localSite(self.desc.currentPhoto()), s),
                 label={'video2' : '320 (webm)',
                        'full' : ('Original size and format' if isVideo else
                                'Original'),
                        }.get(s, str(sizes[s])))
            for s in avail]
示例#17
0
文件: imageSet.py 项目: drewp/photo
    def otherSizeLinks(self):
        if self.desc.currentPhoto() is None:
            return []

        isVideo = self.desc.isVideo(self.desc.currentPhoto())

        if isVideo:
            avail = ['video2', 'full']
        else:
            avail = ['medium', 'large', 'screen', 'full']

        return [
            dict(href="%s?size=%s" % (localSite(self.desc.currentPhoto()), s),
                 label={
                     'video2':
                     '320 (webm)',
                     'full':
                     ('Original size and format' if isVideo else 'Original'),
                 }.get(s, str(sizes[s]))) for s in avail
        ]
示例#18
0
文件: imageSet.py 项目: drewp/photo
    def featured(self):
        current = self.desc.currentPhoto()
        if current is None:
            return ''
        currentLocal = localSite(current)
        _, nextUri = self.prevNext()

        feat = MediaResource(self.graph, current)

        if feat.isVideo():
            progress = feat.videoProgress()
            if progress is Done:
                w, h = feat.getSize(Video2)
                return dict(video=dict(sources=[
                    dict(src=currentLocal + "?size=video2", type='video/webm'),
                    dict(src=currentLocal + "?size=video2&type=mp4",
                         type='video/mp4')
                ],
                                       width=600,
                                       height=600 / w * h))
            else:
                return dict(videoNotReady=dict(progress=progress,
                                               failed=isinstance(
                                                   progress, FailedStatus)))
        else:
            try:
                size = feat.getSize(sizes["large"])
            except (ValueError, IOError) as e:
                log.warn('current=%r', current)
                import traceback
                traceback.print_exc()
                size = (0, 0)
            marg = (602 - 2 - size[0]) // 2
            return dict(image=dict(
                nextClick=self.desc.otherImageUrl(nextUri),
                src=currentLocal + "?size=large",
                w=size[0],
                h=size[1],
                marg=marg,
                alt=self.graph.label(current),
            ))
示例#19
0
    def featured(self):
        current = self.desc.currentPhoto()
        if current is None:
            return ''
        currentLocal = localSite(current)
        _, nextUri = self.prevNext()

        feat = MediaResource(self.graph, current)

        if feat.isVideo():
            progress = feat.videoProgress()
            if progress is Done:
                w, h = feat.getSize(Video2)
                return dict(
                    video=dict(
                        sources=[
                            dict(src=currentLocal+"?size=video2", type='video/webm'),
                            dict(src=currentLocal+"?size=video2&type=mp4", type='video/mp4')
                        ],
                        width=600,
                        height=600 / w * h))
            else:
                return dict(videoNotReady=dict(
                    progress=progress,
                    failed=isinstance(progress, FailedStatus)))
        else:
            try:
                size = feat.getSize(sizes["large"])
            except (ValueError, IOError) as e:
                log.warn('current=%r', current)
                import traceback;traceback.print_exc()
                size = (0,0)
            marg = (602 - 2 - size[0]) // 2
            return dict(image=dict(
                nextClick=self.desc.otherImageUrl(nextUri),
                src=currentLocal+"?size=large",
                w=size[0], h=size[1],
                marg=marg,
                alt=self.graph.label(current),
                ))
示例#20
0
文件: search.py 项目: drewp/photo
    def render_topics(self, ctx, data):
        graph = self.graph
        byClass = {}
        for row in self.graph.queryd("""
          SELECT DISTINCT ?topic ?cls WHERE {
            ?img a foaf:Image ;
              foaf:depicts ?topic .
            ?topic a ?cls .
          }"""):
            byClass.setdefault(row['cls'], set()).add(row['topic'])

        for cls, topics in byClass.items():
            yield T.h2[graph.label(cls, default=cls)]
            rows = []
            for topic in topics:
                try:
                    localUrl = localSite(topic)
                except ValueError, e:
                    log.warn("skipping topic %r: %s" % (topic, e))
                    continue
                lab = topicLabel(graph, topic)
                rows.append((lab.lower(), T.div[T.a(href=localUrl)[lab]]))
            rows.sort()
            yield [r[1] for r in rows]
示例#21
0
文件: search.py 项目: drewp/photo
    def render_topics(self, ctx, data):
        graph = self.graph
        byClass = {}
        for row in self.graph.queryd("""
          SELECT DISTINCT ?topic ?cls WHERE {
            ?img a foaf:Image ;
              foaf:depicts ?topic .
            ?topic a ?cls .
          }"""):
            byClass.setdefault(row['cls'], set()).add(row['topic'])

        for cls, topics in byClass.items():
            yield T.h2[graph.label(cls, default=cls)]
            rows = []
            for topic in topics:
                try:
                    localUrl = localSite(topic)
                except ValueError, e:
                    log.warn("skipping topic %r: %s" % (topic, e))
                    continue
                lab = topicLabel(graph, topic)
                rows.append((lab.lower(), T.div[T.a(href=localUrl)[lab]]))
            rows.sort()
            yield [r[1] for r in rows]
示例#22
0
文件: imageSet.py 项目: drewp/photo
class View(TemplateSpec):
    template_name = "imageSet"

    def __init__(self, graph, desc, params, cookie, agent, openidProxyHeader,
                 forwardedFor):
        TemplateSpec.__init__(self)
        self.graph = graph
        self.desc = desc
        self.params, self.cookie, self.agent = params, cookie, agent
        self.openidProxyHeader = openidProxyHeader
        self.forwardedFor = forwardedFor

    def title(self):
        # why not setLabel?
        return self.desc.determineLabel(self.desc.graph, self.desc.topicDict)

    def bestJqueryLink(self):
        return networking.jqueryLink(self.forwardedFor)

    def setLabel(self):
        return self.desc.label()

    def storyModeUrl(self):
        # only works on topic?edit=1 urls, not stuff like
        # set?tag=foo. error message is poor.

        # need something on desc
        try:
            return self.desc.storyModeUrl()
        except ValueError:
            return None

    def intro(self):
        intro = self.graph.value(self.desc.topicDict['topic'], PHO['intro'])
        if intro is not None:
            intro = intro.replace(r'\n', '\n')  # rdflib parse bug?
            return {'html': intro}
        else:
            return ''

    def currentLabel(self):
        if self.desc.currentPhoto() is None:
            return ''
        return self.graph.label(
            self.desc.currentPhoto(),
            # keep the title line taking up space
            # so the image doesn't bounce around
            # when there's no title
            default=None)

    def prevNextDateButtons(self):
        showingDate = self.params['date']
        if showingDate is None:
            if self.desc.currentPhoto() is None:
                return None

            try:
                showingDate = photoDate(self.graph, self.desc.currentPhoto())
            except ValueError:
                return None

        dtd = parse_date(showingDate)
        try:
            prev = dict(prevDate=date_isoformat(
                nextDateWithPics(self.graph, dtd, -datetime.timedelta(
                    days=1))))
        except ValueError:
            prev = None

        try:
            next = dict(nextDate=date_isoformat(
                nextDateWithPics(self.graph, dtd, datetime.timedelta(days=1))))
        except ValueError:
            next = None

        return dict(prev=prev, next=next)

    def stepButtons(self):
        p, n = self.prevNext()
        return dict(p=self.desc.otherImageUrl(p), n=self.desc.otherImageUrl(n))

    def featured(self):
        current = self.desc.currentPhoto()
        if current is None:
            return ''
        currentLocal = localSite(current)
        _, nextUri = self.prevNext()

        feat = MediaResource(self.graph, current)

        if feat.isVideo():
            progress = feat.videoProgress()
            if progress is Done:
                w, h = feat.getSize(Video2)
                return dict(video=dict(sources=[
                    dict(src=currentLocal + "?size=video2", type='video/webm'),
                    dict(src=currentLocal + "?size=video2&type=mp4",
                         type='video/mp4')
                ],
                                       width=600,
                                       height=600 / w * h))
            else:
                return dict(videoNotReady=dict(progress=progress,
                                               failed=isinstance(
                                                   progress, FailedStatus)))
        else:
            try:
                size = feat.getSize(sizes["large"])
            except (ValueError, IOError) as e:
                log.warn('current=%r', current)
                import traceback
                traceback.print_exc()
                size = (0, 0)
            marg = (602 - 2 - size[0]) // 2
            return dict(image=dict(
                nextClick=self.desc.otherImageUrl(nextUri),
                src=currentLocal + "?size=large",
                w=size[0],
                h=size[1],
                marg=marg,
                alt=self.graph.label(current),
            ))

    def loginWidget(self):
        return networking.getLoginBarSync(self.cookie)

    def actionsAllowed(self):
        """should the actions section be displayed"""
        return self.agent is not None and self.agent in auth.superagents

    @print_timing
    def aclWidget(self):
        if not self.desc.currentPhoto():
            return ''
        import access
        reload(access)
        return access.accessControlWidget(self.graph, self.agent,
                                          self.desc.currentPhoto())

    @print_timing
    def setAclWidget(self):
        """
        access for the whole displayed set
        """
        try:
            setUri = self.desc.canonicalSetUri()
        except NoSetUri:
            return ''
        import access
        reload(access)
        return access.accessControlWidget(self.graph, self.agent, setUri)

    def uploadButton(self):
        if self.desc.currentPhoto() is None:
            return ''
        copy = self.graph.value(self.desc.currentPhoto(), PHO.flickrCopy)
        if copy is not None:
            return dict(copied=dict(uri=copy))

        if self.openidProxyHeader is not None and URIRef(
                self.openidProxyHeader) in auth.superusers:
            return dict(mayCopy=dict(show=True))

        return None

    @print_timing
    def publicShareButton(self):
        if self.desc.currentPhoto() is None:
            return None

        # not absoluteSite() here, since i didn't want to make
        # separate shortener entries for test sites and the real one
        target = self.desc.currentPhoto() + "/single"
        short = hasShortUrlSync(target)
        if short:
            if access.viewable(self.graph, self.desc.currentPhoto(),
                               FOAF.Agent):

                return dict(hasLink=dict(short=short))
        return dict(makeLink=dict(show=True))

    def facts(self, p=None):
        try:
            js = serviceCallSync(self.agent, 'facts', p
                                 or self.desc.currentPhoto())
        except ValueError:
            return {}
        return json.loads(js)

    def otherSizeLinks(self):
        if self.desc.currentPhoto() is None:
            return []

        isVideo = self.desc.isVideo(self.desc.currentPhoto())

        if isVideo:
            avail = ['video2', 'full']
        else:
            avail = ['medium', 'large', 'screen', 'full']

        return [
            dict(href="%s?size=%s" % (localSite(self.desc.currentPhoto()), s),
                 label={
                     'video2':
                     '320 (webm)',
                     'full':
                     ('Original size and format' if isVideo else 'Original'),
                 }.get(s, str(sizes[s]))) for s in avail
        ]

    def localPath(self):
        cur = self.desc.currentPhoto()
        if cur is None:
            return '(none)'
        return MediaResource(self.graph, cur).sourcePath()

    def link(self):
        current = self.desc.currentPhoto()
        if current is None:
            return None
        if self.graph.contains((current, RDF.type, PHO.Video)):
            return dict(video=dict(uri=current + '?size=video2'))
        # should put sizes in this link(s)
        return dict(image=dict(uri=current + '?size=large'))

    def allowedToWriteMeta(self):
        return (self.agent is not None
                and tagging.allowedToWrite(self.graph, self.agent))

    def currentImgJson(self):
        p = self.desc.currentPhoto()
        obj = {'uri': p} if p else None
        return json.dumps(obj)

    def starLinkAll(self):
        if self.params['star'] is None:
            return ''
        else:
            return dict(href=self.desc.altUrl(star='all'))

    def starLinkOnly(self):
        if self.params['star'] == 'only':
            return ''
        else:
            return dict(href=self.desc.altUrl(star='only'))

    def recentLinks(self):
        raise NotImplementedError
        choices = []
        for opt in [10, 50, 100]:
            href = self.desc.altUrl(recent=str(opt))
            choices.append(T.a(href=href)[opt])
        choices.append(T.a(href=self.desc.altUrl(recent=''))['all'])
        return [
            'show only the ', [[c, ' '] for c in choices],
            ' most recent of these'
        ]

    @print_timing
    def photosInSet(self):
        desc = self.desc
        return [
            dict(thumb=dict(link=desc.otherImageUrl(p),
                            uri=p,
                            cls=(("current" if p ==
                                  desc.currentPhoto() else "not-current") +
                                 (" video" if desc.isVideo(p) else '')),
                            src="%s?size=thumb" % localSite(p),
                            isVideo=desc.isVideo(p))) for p in desc.photos()
        ]

    def photosInSetPlus(self):
        """for use by other tools who want to draw some photos
        """
        out = []
        for p in self.desc.photos():
            r = MediaResource(self.graph, p)
            try:
                s = r.getSize(sizes["thumb"])
                thumbSize = {"thumbSize": dict(w=s[0], h=s[1])}
            except (ValueError, IOError, subprocess.CalledProcessError):
                thumbSize = {}
            out.append(
                dict(link=absoluteSite(self.desc.otherImageUrl(p)),
                     uri=p,
                     facts=self.facts(p),
                     thumb="%s?size=thumb" % p,
                     screen="%s?size=screen" % p,
                     isVideo=self.desc.isVideo(p)))
            out[-1].update(thumbSize)
        return out

    def zipUrl(self):
        return "%s?archive=zip" % self.desc.topic

    def zipSizeWarning(self):
        mb = 17.3 / 9 * len(self.desc.photos())
        secs = mb * 1024 / 40
        return "(file will be roughly %d MB and take %d mins to download)" % (
            mb, secs / 60)

    @print_timing
    def pageJson(self):
        prev, next = self.prevNext()

        return dict(
            picInfo=json.dumps(self.picInfoJson()),
            prev=json.dumps(self.desc.otherImageUrl(prev)),
            next=json.dumps(self.desc.otherImageUrl(next)),
            preloadImg=json.dumps(self.nextImagePreload()),
            tagHotkeys=json.dumps(auth.tagHotkeys),
        )

    @print_timing
    def picInfoJson(self):
        # vars for the javascript side to use
        current = self.desc.currentPhoto()
        if current is None:
            return {}

        try:
            js = serviceCallSync(self.agent, 'tags', current)
            tags = json.loads(js)
        except restkit.RequestFailed, e:
            tags = {"error": str(e)}

        return dict(relCurrentPhotoUri=localSite(current),
                    currentPhotoUri=current,
                    tags=tags)