aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--tagit/apps/port-schema.nt35
-rw-r--r--tagit/apps/port_data.py79
-rw-r--r--tagit/assets/icons/kivy/no_preview.pngbin0 -> 4012 bytes
-rw-r--r--tagit/utils/__init__.py1
-rw-r--r--tagit/utils/namespaces.py4
-rw-r--r--tagit/utils/rmatcher.py53
-rw-r--r--tagit/widgets/browser.py59
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
new file mode 100644
index 0000000..3049768
--- /dev/null
+++ b/tagit/assets/icons/kivy/no_preview.png
Binary files differ
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):