diff options
-rw-r--r-- | tagit/apps/port-schema.nt | 35 | ||||
-rw-r--r-- | tagit/apps/port_data.py | 79 | ||||
-rw-r--r-- | tagit/assets/icons/kivy/no_preview.png | bin | 0 -> 4012 bytes | |||
-rw-r--r-- | tagit/utils/__init__.py | 1 | ||||
-rw-r--r-- | tagit/utils/namespaces.py | 4 | ||||
-rw-r--r-- | tagit/utils/rmatcher.py | 53 | ||||
-rw-r--r-- | tagit/widgets/browser.py | 59 |
7 files changed, 204 insertions, 27 deletions
diff --git a/tagit/apps/port-schema.nt b/tagit/apps/port-schema.nt index 4f9a37c..2b354e5 100644 --- a/tagit/apps/port-schema.nt +++ b/tagit/apps/port-schema.nt @@ -9,14 +9,18 @@ prefix bsfs: <http://bsfs.ai/schema/> prefix bse: <http://bsfs.ai/schema/Entity#> prefix bst: <http://bsfs.ai/schema/Tag#> prefix bsg: <http://bsfs.ai/schema/Group#> +prefix bsp: <http://bsfs.ai/schema/Preview#> # essential nodes bsfs:Entity rdfs:subClassOf bsfs:Node . +bsfs:Preview rdfs:subClassOf bsfs:Node . bsfs:File rdfs:subClassOf bsfs:Entity . bsfs:Tag rdfs:subClassOf bsfs:Node . bsfs:Group rdfs:subClassOf bsfs:Node . # common definitions +bsfs:Blob rdfs:subClassOf bsfs:Literal . +bsfs:URI rdfs:subClassOf bsfs:Literal . bsfs:Number rdfs:subClassOf bsfs:Literal . bsfs:Time rdfs:subClassOf bsfs:Literal . xsd:string rdfs:subClassOf bsfs:Literal . @@ -41,10 +45,9 @@ bse:mime rdfs:subClassOf bsfs:Predicate ; rdfs:range xsd:string ; bsfs:unique "true"^^xsd:boolean . -bse:orientation rdfs:subClassOf bsfs:Predicate ; # FIXME: shouldn't this be part of the preview? - rdfs:domain bsfs:File ; - rdfs:range xsd:integer ; - bsfs:unique "true"^^xsd:boolean . +bse:preview rdfs:subClassOf bsfs:Predicate ; + rdfs:domain bsfs:Entity ; + rdfs:range bsfs:Preview . bse:tag rdfs:subClassOf bsfs:Predicate ; rdfs:domain bsfs:File ; @@ -78,3 +81,27 @@ bse:latitude rdfs:subClassOf bsfs:Predicate ; rdfs:range xsd:integer ; bsfs:unique "true"^^xsd:boolean . + +## preview nodes + +bsp:width rdfs:subClassOf bsfs:Predicate ; + rdfs:domain bsfs:Preview ; + rdfs:range xsd:integer ; + bsfs:unique "true"^^xsd:boolean . + +bsp:height rdfs:subClassOf bsfs:Predicate ; + rdfs:domain bsfs:Preview ; + rdfs:range xsd:integer ; + bsfs:unique "true"^^xsd:boolean . + +bsp:orientation rdfs:subClassOf bsfs:Predicate ; + rdfs:domain bsfs:Preview ; + rdfs:range xsd:integer ; + bsfs:unique "true"^^xsd:boolean . + +bsp:asset rdfs:subClassOf bsfs:Predicate ; + rdfs:domain bsfs:Preview ; + rdfs:range xsd:string ; # FIXME: mb/port + bsfs:unique "true"^^xsd:boolean . + + diff --git a/tagit/apps/port_data.py b/tagit/apps/port_data.py index 1d9f4e6..56fb522 100644 --- a/tagit/apps/port_data.py +++ b/tagit/apps/port_data.py @@ -45,4 +45,83 @@ def add_port_data(store): n1.set(ns.bse.group, grp) n3.set(ns.bse.group, grp) + # previews + base = os.path.join(os.path.dirname(__file__), 'port-data') + n0.set(ns.bse.preview, + store.node(ns.bsfs.Preview, URI('http://example.com/me/preview#ent01_w100_h100')) \ + .set(ns.bsp.width, 100) \ + .set(ns.bsp.height, 100) \ + .set(ns.bsp.orientation, 1) \ + .set(ns.bsp.asset, str(os.path.join(base, 'ent01_w100_h100.jpg'))) + ) + n0.set(ns.bse.preview, + store.node(ns.bsfs.Preview, str('http://example.com/me/preview#ent01_w400_h200')) \ + .set(ns.bsp.width, 200) \ + .set(ns.bsp.height, 400) \ + .set(ns.bsp.orientation, 1) \ + .set(ns.bsp.asset, str(os.path.join(base, 'ent01_w400_h200.jpg'))) + ) + n0.set(ns.bse.preview, + store.node(ns.bsfs.Preview, URI('http://example.com/me/preview#ent01_w400_h400')) \ + .set(ns.bsp.width, 400) \ + .set(ns.bsp.height, 400) \ + .set(ns.bsp.orientation, 1) \ + .set(ns.bsp.asset, str(os.path.join(base, 'ent01_w400_h400.jpg'))) + ) + n1.set(ns.bse.preview, + store.node(ns.bsfs.Preview, URI('http://example.com/me/preview#ent02_w100_h100')) \ + .set(ns.bsp.width, 100) \ + .set(ns.bsp.height, 100) \ + .set(ns.bsp.orientation, 1) \ + .set(ns.bsp.asset, str(os.path.join(base, 'ent02_w100_h100.jpg'))) + ) + n1.set(ns.bse.preview, + store.node(ns.bsfs.Preview, URI('http://example.com/me/preview#ent02_w400_h200')) \ + .set(ns.bsp.width, 200) \ + .set(ns.bsp.height, 400) \ + .set(ns.bsp.orientation, 1) \ + .set(ns.bsp.asset, str(os.path.join(base, 'ent02_w400_h200.jpg'))) + ) + n2.set(ns.bse.preview, + store.node(ns.bsfs.Preview, URI('http://example.com/me/preview#ent03_w100_h100')) \ + .set(ns.bsp.width, 100) \ + .set(ns.bsp.height, 100) \ + .set(ns.bsp.orientation, 1) \ + .set(ns.bsp.asset, str(os.path.join(base, 'ent03_w100_h100.jpg'))) + ) + n2.set(ns.bse.preview, + store.node(ns.bsfs.Preview, URI('http://example.com/me/preview#ent03_w400_h200')) \ + .set(ns.bsp.width, 200) \ + .set(ns.bsp.height, 400) \ + .set(ns.bsp.orientation, 1) \ + .set(ns.bsp.asset, str(os.path.join(base, 'ent03_w400_h200.jpg'))) + ) + n3.set(ns.bse.preview, + store.node(ns.bsfs.Preview, URI('http://example.com/me/preview#ent04_w100_h100')) \ + .set(ns.bsp.width, 100) \ + .set(ns.bsp.height, 100) \ + .set(ns.bsp.orientation, 1) \ + .set(ns.bsp.asset, str(os.path.join(base, 'ent04_w100_h100.png'))) + ) + n3.set(ns.bse.preview, + store.node(ns.bsfs.Preview, URI('http://example.com/me/preview#ent04_w400_h200')) \ + .set(ns.bsp.width, 200) \ + .set(ns.bsp.height, 400) \ + .set(ns.bsp.orientation, 1) \ + .set(ns.bsp.asset, str(os.path.join(base, 'ent04_w400_h200.png'))) + ) + n4.set(ns.bse.preview, + store.node(ns.bsfs.Preview, URI('http://example.com/me/preview#ent05_w100_h100')) \ + .set(ns.bsp.width, 100) \ + .set(ns.bsp.height, 100) \ + .set(ns.bsp.orientation, 1) \ + .set(ns.bsp.asset, str(os.path.join(base, 'ent05_w100_h100.jpg'))) + ) + n4.set(ns.bse.preview, + store.node(ns.bsfs.Preview, URI('http://example.com/me/preview#ent05_w400_h200')) \ + .set(ns.bsp.width, 200) \ + .set(ns.bsp.height, 400) \ + .set(ns.bsp.orientation, 1) \ + .set(ns.bsp.asset, str(os.path.join(base, 'ent05_w400_h200.jpg'))) + ) diff --git a/tagit/assets/icons/kivy/no_preview.png b/tagit/assets/icons/kivy/no_preview.png Binary files differnew file mode 100644 index 0000000..3049768 --- /dev/null +++ b/tagit/assets/icons/kivy/no_preview.png diff --git a/tagit/utils/__init__.py b/tagit/utils/__init__.py index 16dcd4d..daa9eab 100644 --- a/tagit/utils/__init__.py +++ b/tagit/utils/__init__.py @@ -10,6 +10,7 @@ import typing # inner-module imports from . import bsfs from . import namespaces as ns +from . import rmatcher from . import time as ttime from .frame import Frame from .shared import * # FIXME: port properly diff --git a/tagit/utils/namespaces.py b/tagit/utils/namespaces.py index 5446922..dbdb853 100644 --- a/tagit/utils/namespaces.py +++ b/tagit/utils/namespaces.py @@ -19,8 +19,9 @@ bsm = _bsfs.Namespace('http://bsfs.ai/schema/Meta') # auxiliary bsfs namespaces bse = _bsfs.Namespace('http://bsfs.ai/schema/Entity') -bst = _bsfs.Namespace('http://bsfs.ai/schema/Tag') bsg = _bsfs.Namespace('http://bsfs.ai/schema/Group') +bsp = _bsfs.Namespace('http://bsfs.ai/schema/Preview') +bst = _bsfs.Namespace('http://bsfs.ai/schema/Tag') # export __all__: typing.Sequence[str] = ( @@ -28,6 +29,7 @@ __all__: typing.Sequence[str] = ( 'bsfs', 'bsg', 'bsm', + 'bsp', 'bst', 'xsd', ) diff --git a/tagit/utils/rmatcher.py b/tagit/utils/rmatcher.py new file mode 100644 index 0000000..b5bb802 --- /dev/null +++ b/tagit/utils/rmatcher.py @@ -0,0 +1,53 @@ +""" + +Part of the tagit module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# exports +__all__ = ( + 'by_area', + 'by_area_min', + ) + + +## code ## + +def by_area(target, candidates): + """Pick the item from *candidates* whose area is most similar to *target*.""" + target_area = target[0] * target[1] + scores = [ + (key, abs(target_area - res[0] * res[1])) + for key, res in candidates + ] + best_key, best_score = min(scores, key=lambda key_score: key_score[1]) + return best_key + + +def by_area_min(target, candidates): + """Pick the item from *candidates* whose area is at least that of *target*.""" + # rank the candidates by area difference + # a positive score means that the candidate is larger than the target. + target_area = target[0] * target[1] + scores = [(key, res[0] * res[1] - target_area) for key, res in candidates] + + # identify the two items with + # a) the smallest positive score (kmin), or + # b) the largest negative score (kmax) + kmin, kmax = None, None + cmin, cmax = float('inf'), float('-inf') + for key, score in scores: + if score >= 0 and score < cmin: + kmin, cmin = key, score + elif score < 0 and score > cmax: + kmax, cmax = key, score + + # prefer positive over negative scores + if cmin < float('inf'): + return kmin + if cmax > float('-inf'): + return kmax + # no viable resolution found + raise IndexError('list contains no valid element') + +## EOF ## diff --git a/tagit/widgets/browser.py b/tagit/widgets/browser.py index 0cc65f6..4a254ee 100644 --- a/tagit/widgets/browser.py +++ b/tagit/widgets/browser.py @@ -26,7 +26,7 @@ import kivy.properties as kp # tagit imports from tagit import config from tagit.external.setproperty import SetProperty -from tagit.utils import Frame, Resolution, Struct, clamp, ns, ttime +from tagit.utils import Frame, Resolution, Struct, clamp, ns, ttime, rmatcher from tagit.utils.bsfs import ast # inner-module imports @@ -394,26 +394,39 @@ class Browser(GridLayout, StorageAwareMixin, ConfigAwareMixin): for _ in range(self.page_size - len(items)): next(childs).clear() - # load previews for items - # FIXME: Only relevant items, not all of them - #thumbs = items.preview(default=open(resource_find('no_preview.png'), 'rb')) # FIXME: mb/port - #resolution = self._cell_resolution() # FIXME: mb/port - #for ent, thumb, child in zip(reversed(items), reversed(thumbs), childs): # FIXME: mb/port + if len(items) == 0: # FIXME: mb/port + return + + # fetch previews + node_preview = reduce(operator.add, items).get(ns.bse.preview, node=True) + previews = {p for previews in node_preview.values() for p in previews} + previews = reduce(operator.add, previews) + # fetch preview resolutions + res_preview = previews.get(ns.bsp.width, ns.bsp.height, node=True) + # get target resolution + resolution = self._cell_resolution() + # get default preview + default = resource_find('no_preview.png') + # select a preview for each item for ent, child in zip(reversed(items), childs): - # FIXME: default/no preview handling - #thumb = best_resolution_match(thumb, resolution) # FIXME: mb/port - #child.update(ent, thumb, f'{ent.guid}x{resolution}') # FIXME: mb/port - thumb = open(resource_find('no_preview.png'), 'rb') - child.update(ent, thumb, f'{ent}x{1234}') - - # load previews for items - #resolution = self._cell_resolution() - #for obj, child in zip(reversed(items), childs): - # try: - # thumb = obj.get('preview').best_match(resolution) - # except PredicateNotSet: - # thumb = open(resource_find('no_preview.png'), 'rb') - # child.update(obj, thumb, f'{obj.guid}x{resolution}') + try: + # get previews and their resolution for this ent + options = [] + for preview in node_preview[ent]: + # unpack resolution + res = res_preview[preview] + width = res.get(ns.bsp.width, 0) + height = res.get(ns.bsp.height, 0) + options.append((preview, Resolution(width, height))) + # select the best fitting preview + chosen = rmatcher.by_area_min(resolution, options) + # open the preview file, default if no asset is available + thumb = open(chosen.get(ns.bsp.asset, default=default), 'rb') # FIXME: mb/port: asset storage + except IndexError: + # no viable resolution found + thumb = open(default, 'rb') + # update the image in the child widget + child.update(ent, thumb, f'{ent}x{resolution}') #def _preload_all(self): # # prefer loading from start to end @@ -517,7 +530,8 @@ class BrowserItem(RelativeLayout): class BrowserImage(BrowserItem): def update(self, obj, buffer, source): super(BrowserImage, self).update(obj) - self.preview.load_image(buffer, source, obj.orientation(default=1)) + self.preview.load_image(buffer, source, 1) + #self.preview.load_image(buffer, source, obj.orientation(default=1)) # FIXME: mb/port self.preview.set_size(self.size) @@ -534,7 +548,8 @@ class BrowserDescription(BrowserItem): def update(self, obj, buffer, source): super(BrowserDescription, self).update(obj) - self.preview.load_image(buffer, source, obj.orientation(default=1)) + self.preview.load_image(buffer, source, 1) + #self.preview.load_image(buffer, source, obj.orientation(default=1)) # FIXME: mb/port self.preview.set_size((self.height, self.height)) def clear(self): |