Add file PyProject;
Support display of a single pubsub node item; Update document README; Modularize code;
This commit is contained in:
parent
37aa7e8f40
commit
5e495579c2
32 changed files with 2431 additions and 2059 deletions
181
jabbercard/utilities/graphics.py
Normal file
181
jabbercard/utilities/graphics.py
Normal file
|
@ -0,0 +1,181 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from jabbercard.config import Cache
|
||||
import glob
|
||||
import os
|
||||
import qrcode
|
||||
import random
|
||||
|
||||
try:
|
||||
import cv2
|
||||
except:
|
||||
print('OpenCV (cv2) is required for dynamic background.')
|
||||
|
||||
try:
|
||||
import numpy
|
||||
except:
|
||||
print('NumPy (numpy) is required for dynamic background.')
|
||||
|
||||
class Graphics:
|
||||
|
||||
def handle_photo(jid_bare, jid_vcard, link_href):
|
||||
filename = filepath = filetype = mimetype = selection = None
|
||||
directory_cache = Cache.get_directory()
|
||||
filecirca = os.path.join(directory_cache, 'photo', jid_bare, '.*')
|
||||
filepath_guess = glob.glob(filecirca)
|
||||
if filepath_guess:
|
||||
filepath = filepath_guess[0]
|
||||
filetype = filepath.split('.').pop()
|
||||
filename = '{}.{}'.format(jid_bare, filetype)
|
||||
elif jid_vcard:
|
||||
if jid_vcard['type']:
|
||||
mimetype = jid_vcard['type']
|
||||
if mimetype:
|
||||
filetype = mimetype.split('/')[1]
|
||||
if filetype == 'svg+xml': filetype = 'svg'
|
||||
filename = '{}.{}'.format(jid_bare, filetype)
|
||||
filepath = os.path.join(directory_cache, 'photo', filename)
|
||||
#img.save(filename)
|
||||
|
||||
# Write the decoded bytes to a file
|
||||
if 'bin' in jid_vcard:
|
||||
with open(filepath, 'wb') as file:
|
||||
file.write(jid_vcard['bin'])
|
||||
|
||||
if not filepath or not os.path.exists(filepath) or os.path.getsize(filepath) == 0:
|
||||
filename = 'default.svg'
|
||||
elif filetype == 'svg':
|
||||
selection = Graphics.extract_colours_from_vector(filepath)
|
||||
else:
|
||||
selection = Graphics.extract_colours_from_raster(filepath)
|
||||
|
||||
# QR code
|
||||
filepath_qrcode = os.path.join(directory_cache, 'qr', jid_bare, '.png')
|
||||
if not os.path.exists(filepath_qrcode) or os.path.getsize(filepath_qrcode) == 0:
|
||||
Graphics.generate_qr_code_graphics_from_string(link_href, jid_bare)
|
||||
|
||||
return filename, filepath, filetype, selection
|
||||
|
||||
|
||||
def extract_colours_from_raster(filepath):
|
||||
try:
|
||||
img = cv2.imread(filepath)
|
||||
#thresholded = cv2.inRange(img, (50, 100, 200), (50, 100, 200))
|
||||
thresholded = cv2.inRange(img, (90, 90, 90), (190, 190, 190))
|
||||
#thresholded = cv2.bitwise_not(thresholded)
|
||||
#thresholded = cv2.inRange(img, (0, 0, 0), (0, 0, 0))
|
||||
#res = img + cv2.cvtColor(thresholded, cv2.COLOR_GRAY2BGR)
|
||||
|
||||
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
||||
#result = numpy.clip(img, 90, 190)
|
||||
#result = numpy.clip(img, 50, 200)
|
||||
#result = numpy.clip(img, 100, 150)
|
||||
result = numpy.clip(img, 100, 200)
|
||||
res = cv2.cvtColor(result, cv2.COLOR_RGB2BGR)
|
||||
|
||||
"""
|
||||
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
||||
mask = numpy.all(numpy.logical_and(img >= 90, img <= 190), axis=2)
|
||||
result = numpy.where(mask[...,None], img, 255)
|
||||
res = cv2.cvtColor(result, cv2.COLOR_RGB2BGR)
|
||||
"""
|
||||
|
||||
"""
|
||||
# Thresholding for black:
|
||||
lower_black = numpy.array([0, 0, 0])
|
||||
upper_black = numpy.array([50, 50, 50]) # Adjust this value for the black range
|
||||
black_mask = cv2.inRange(img, lower_black, upper_black)
|
||||
|
||||
# Thresholding for white:
|
||||
lower_white = numpy.array([250, 250, 250])
|
||||
upper_white = numpy.array([255, 255, 255])
|
||||
white_mask = cv2.inRange(img, lower_white, upper_white)
|
||||
|
||||
# Combine the masks
|
||||
combined_mask = cv2.bitwise_or(black_mask, white_mask)
|
||||
|
||||
# Invert the combined mask
|
||||
inverted_mask = cv2.bitwise_not(combined_mask)
|
||||
|
||||
# Apply the mask to the original image
|
||||
res = cv2.bitwise_and(img, img, mask=inverted_mask)
|
||||
"""
|
||||
|
||||
selection = []
|
||||
|
||||
ix_1st = random.randint(1, len(res)-1)
|
||||
res_ix_1st = res[ix_1st]
|
||||
ix_ix_1st = random.randint(1, len(res_ix_1st)-1)
|
||||
res_ix_ix_1st = res_ix_1st[ix_ix_1st]
|
||||
selection.append(numpy.array(res_ix_ix_1st).tolist())
|
||||
|
||||
ix_2nd = random.randint(1, len(res)-1)
|
||||
res_ix_2nd = res[ix_2nd]
|
||||
ix_ix_2nd = random.randint(1, len(res_ix_2nd)-1)
|
||||
res_ix_ix_2nd = res_ix_2nd[ix_ix_2nd]
|
||||
selection.append(numpy.array(res_ix_ix_2nd).tolist())
|
||||
print(selection)
|
||||
|
||||
except Exception as e:
|
||||
selection = None
|
||||
exception = str(e)
|
||||
print(exception)
|
||||
|
||||
return selection
|
||||
|
||||
def extract_colours_from_vector(filepath):
|
||||
# Parse the SVG file
|
||||
tree = ET.parse(filepath)
|
||||
root = tree.getroot()
|
||||
|
||||
# Set to store unique colours
|
||||
colours_hex = set()
|
||||
colours_rgb = []
|
||||
|
||||
# SVG namespace
|
||||
namespace = {'svg': 'http://www.w3.org/2000/svg'}
|
||||
|
||||
# Find all possible elements
|
||||
for elem in root.findall('.//svg:circle', namespace) + \
|
||||
root.findall('.//svg:ellipse', namespace) + \
|
||||
root.findall('.//svg:line', namespace) + \
|
||||
root.findall('.//svg:path', namespace) + \
|
||||
root.findall('.//svg:polygon', namespace) + \
|
||||
root.findall('.//svg:rect', namespace) + \
|
||||
root.findall('.//svg:text', namespace):
|
||||
|
||||
fill = elem.get('fill')
|
||||
stroke = elem.get('stroke')
|
||||
|
||||
# Add colours to the set if they are not None or 'none'
|
||||
if fill and fill.startswith('#') and len(fill) > 4 and fill.lower() != 'none':
|
||||
colours_hex.add(fill)
|
||||
if stroke and stroke.startswith('#') and len(stroke) > 4 and stroke.lower() != 'none':
|
||||
colours_hex.add(stroke)
|
||||
|
||||
for colour in colours_hex:
|
||||
hex = colour.lstrip('#')
|
||||
rgb = list(int(hex[i:i+2], 16) for i in (0, 2, 4))
|
||||
rgb.reverse()
|
||||
colours_rgb.append(rgb)
|
||||
|
||||
selection = []
|
||||
if len(colours_rgb) > 1:
|
||||
for i in range(2):
|
||||
ix = random.randint(0, len(colours_rgb)-1)
|
||||
selection.append(colours_rgb[ix])
|
||||
del colours_rgb[ix]
|
||||
elif len(colours_rgb) == 1:
|
||||
selection = [colours_rgb[0], colours_rgb[0]]
|
||||
|
||||
return selection
|
||||
|
||||
def generate_qr_code_graphics_from_string(text, jid_bare):
|
||||
#qrcode_graphics = qrcode.make(text)
|
||||
qr = qrcode.QRCode(border=2, box_size=10)
|
||||
qr.add_data(text)
|
||||
qrcode_graphics = qr.make_image(fill_color='#333', back_color='#f2f2f2')
|
||||
directory_cache = Cache.get_directory()
|
||||
filename = os.path.join(directory_cache, 'qr', jid_bare + '.png')
|
||||
qrcode_graphics.save(filename)
|
30
jabbercard/utilities/toml.py
Normal file
30
jabbercard/utilities/toml.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import tomli_w
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
try:
|
||||
import tomllib
|
||||
except:
|
||||
import tomli as tomllib
|
||||
|
||||
class Toml:
|
||||
|
||||
def open_file_toml(filename: str) -> dict:
|
||||
with open(filename, mode="rb") as fn:
|
||||
data = tomllib.load(fn)
|
||||
return data
|
||||
|
||||
def save_to_toml(filename: str, data: dict) -> None:
|
||||
with open(filename, 'w') as fn:
|
||||
data_as_string = tomli_w.dumps(data)
|
||||
fn.write(data_as_string)
|
||||
|
||||
def open_file_xml(filename: str) -> ET.ElementTree:
|
||||
data = ET.parse(filename)
|
||||
return data
|
||||
|
||||
def save_to_file(filename: str, data: str) -> None:
|
||||
with open(filename, 'w') as fn:
|
||||
fn.write(data)
|
220
jabbercard/utilities/xml.py
Normal file
220
jabbercard/utilities/xml.py
Normal file
|
@ -0,0 +1,220 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
class Syndication:
|
||||
|
||||
# def extract_vcard_items(xml_data):
|
||||
# namespace = '{urn:ietf:params:xml:ns:vcard-4.0}'
|
||||
# title = xml_data.find(namespace + 'title')
|
||||
#
|
||||
# entry = {'fn' : content_text,
|
||||
# 'note' : link_href,
|
||||
# 'email' : published_text,
|
||||
# 'impp' : summary_text,
|
||||
# 'url' : tags}
|
||||
# return entry
|
||||
|
||||
def extract_vcard_items(xml_data):
|
||||
"""Extracts all items from a vCard XML ElementTree.
|
||||
|
||||
Args:
|
||||
xml_data (ElementTree): The vCard XML as an ElementTree object.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary where keys are item names and values are their text content.
|
||||
"""
|
||||
|
||||
items = {}
|
||||
for item in xml_data.iter():
|
||||
# Skip the root element (vcard)
|
||||
if item.tag == '{urn:ietf:params:xml:ns:vcard-4.0}vcard':
|
||||
continue
|
||||
|
||||
# Extract item name and text content
|
||||
item_name = item.tag.split('}')[1]
|
||||
|
||||
# Check for any direct text content or child elements
|
||||
item_text = []
|
||||
if item.text:
|
||||
item_text.append(item.text)
|
||||
for child in item:
|
||||
if child.text:
|
||||
item_text.append(child.text)
|
||||
|
||||
# Join text elements if multiple found
|
||||
if item_text:
|
||||
items[item_name] = ' '.join(item_text).strip() # Strip extra spaces
|
||||
else:
|
||||
items[item_name] = None
|
||||
|
||||
return items
|
||||
|
||||
def extract_vcard4_items(xml_data):
|
||||
namespace = '{urn:ietf:params:xml:ns:vcard-4.0}'
|
||||
vcard = {}
|
||||
|
||||
element_em = xml_data.find(namespace + 'email')
|
||||
element_fn = xml_data.find(namespace + 'fn')
|
||||
element_nn = xml_data.find(namespace + 'nickname')
|
||||
element_nt = xml_data.find(namespace + 'note')
|
||||
element_og = xml_data.find(namespace + 'org')
|
||||
element_im = xml_data.find(namespace + 'impp')
|
||||
element_ul = xml_data.find(namespace + 'url')
|
||||
|
||||
if isinstance(element_em, ET.Element):
|
||||
for i in element_em:
|
||||
text = i.text
|
||||
if text:
|
||||
email = text
|
||||
break
|
||||
else:
|
||||
email = ''
|
||||
else:
|
||||
email = ''
|
||||
if isinstance(element_fn, ET.Element):
|
||||
for i in element_fn:
|
||||
text = i.text
|
||||
if text:
|
||||
title = text
|
||||
break
|
||||
else:
|
||||
title = ''
|
||||
else:
|
||||
title = ''
|
||||
if isinstance(element_nn, ET.Element):
|
||||
for i in element_nn:
|
||||
text = i.text
|
||||
if text:
|
||||
alias = text
|
||||
break
|
||||
else:
|
||||
alias = ''
|
||||
else:
|
||||
alias = ''
|
||||
if isinstance(element_nt, ET.Element):
|
||||
for i in element_nt:
|
||||
text = i.text
|
||||
if text:
|
||||
note = text
|
||||
break
|
||||
else:
|
||||
note = ''
|
||||
else:
|
||||
note = ''
|
||||
if isinstance(element_og, ET.Element):
|
||||
for i in element_og:
|
||||
text = i.text
|
||||
if text:
|
||||
org = text
|
||||
break
|
||||
else:
|
||||
org = ''
|
||||
else:
|
||||
org = ''
|
||||
if isinstance(element_im, ET.Element):
|
||||
for i in element_im:
|
||||
text = i.text
|
||||
if text:
|
||||
impp = text
|
||||
break
|
||||
else:
|
||||
impp = ''
|
||||
else:
|
||||
impp = ''
|
||||
if isinstance(element_ul, ET.Element):
|
||||
for i in element_ul:
|
||||
text = i.text
|
||||
if text:
|
||||
url = text
|
||||
break
|
||||
else:
|
||||
url = ''
|
||||
else:
|
||||
url = ''
|
||||
|
||||
vcard['extras'] = {}
|
||||
for element in xml_data.findall(namespace + "group"):
|
||||
category = '?'
|
||||
for i in element.find(namespace + 'x-ablabel'):
|
||||
txt = i.text
|
||||
for i in element.find(namespace + 'url'):
|
||||
uri = i.text
|
||||
for i in element.find(namespace + 'url/' + namespace + 'parameters/' + namespace + 'type'):
|
||||
category = i.text
|
||||
if not category in vcard['extras']: vcard['extras'][category] = []
|
||||
vcard['extras'][category].append({'label' : txt, 'uri' : uri})
|
||||
|
||||
vcard['alias'] = alias
|
||||
vcard['email'] = email
|
||||
vcard['fn'] = title
|
||||
vcard['note'] = note
|
||||
vcard['org'] = org
|
||||
vcard['impp'] = impp
|
||||
vcard['url'] = url
|
||||
return vcard
|
||||
|
||||
|
||||
def extract_atom_items(xml_data, limit=False):
|
||||
# NOTE
|
||||
# `.//` was not needded when node item payload was passed directly.
|
||||
# Now that item is saved as xml, it is required to use `.//`.
|
||||
# Perhaps navigating a level down (i.e. to "child"), or removing the root from the file would solve this.
|
||||
#namespace = './/{http://www.w3.org/2005/Atom}'
|
||||
namespace = '{http://www.w3.org/2005/Atom}'
|
||||
title = xml_data.find(namespace + 'title')
|
||||
links = xml_data.find(namespace + 'link')
|
||||
if (not isinstance(title, ET.Element) and
|
||||
not isinstance(links, ET.Element)): return None
|
||||
title_text = '' if title == None else title.text
|
||||
link_href = ''
|
||||
if isinstance(links, ET.Element):
|
||||
for link in xml_data.findall(namespace + 'link'):
|
||||
link_href = link.attrib['href'] if 'href' in link.attrib else ''
|
||||
if link_href: break
|
||||
contents = xml_data.find(namespace + 'content')
|
||||
content_text = ''
|
||||
if isinstance(contents, ET.Element):
|
||||
for content in xml_data.findall(namespace + 'content'):
|
||||
content_text = content.text or ''
|
||||
if content_text: break
|
||||
summaries = xml_data.find(namespace + 'summary')
|
||||
summary_text = ''
|
||||
if isinstance(summaries, ET.Element):
|
||||
for summary in xml_data.findall(namespace + 'summary'):
|
||||
summary_text = summary.text or ''
|
||||
if summary_text: break
|
||||
published = xml_data.find(namespace + 'published')
|
||||
published_text = '' if published == None else published.text
|
||||
categories = xml_data.find(namespace + 'category')
|
||||
tags = []
|
||||
if isinstance(categories, ET.Element):
|
||||
for category in xml_data.findall(namespace + 'category'):
|
||||
if 'term' in category.attrib and category.attrib['term']:
|
||||
category_term = category.attrib['term']
|
||||
if len(category_term) < 20:
|
||||
tags.append(category_term)
|
||||
elif len(category_term) < 50:
|
||||
tags.append(category_term)
|
||||
if limit and len(tags) > 4: break
|
||||
|
||||
|
||||
identifier = xml_data.find(namespace + 'id')
|
||||
if identifier and identifier.attrib: print(identifier.attrib)
|
||||
identifier_text = '' if identifier == None else identifier.text
|
||||
|
||||
instances = '' # TODO Check the Blasta database for instances.
|
||||
|
||||
entry = {'content' : content_text,
|
||||
'href' : link_href,
|
||||
'published' : published_text,
|
||||
'summary' : summary_text,
|
||||
'tags' : tags,
|
||||
'title' : title_text,
|
||||
'updated' : published_text} # TODO "Updated" is missing
|
||||
return entry
|
||||
|
||||
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue