diff --git a/README.md b/README.md index 5e5026d..b80c27d 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,9 @@ You can colorize nodes via their `title` or `body`. This can be done to the node ## UPDATES +**2024/01/30** @1.2.62: +* `QUEUE` nodes can do URL loading of images + **2024/01/08** @1.2.61: * living in the stupidest timeline where case-sensitivty matters for URLS in chrome... * fixed stylesheet reference in help diff --git a/core/utility/batch.py b/core/utility/batch.py index 8f36ff2..13d8d16 100644 --- a/core/utility/batch.py +++ b/core/utility/batch.py @@ -257,36 +257,36 @@ class QueueBaseNode(JOVBaseNode): continue data = [line] + if not line.lower().startswith("http"): + # ;*.png;*.gif;*.jpg + base_path_str, tail = os.path.split(line) + filters = [p.strip() for p in tail.split(';')] - # ;*.png;*.gif;*.jpg - base_path_str, tail = os.path.split(line) - filters = [p.strip() for p in tail.split(';')] + base_path = Path(base_path_str) + if base_path.is_absolute(): + search_dir = base_path if base_path.is_dir() else base_path.parent + else: + search_dir = (ROOT / base_path).resolve() - base_path = Path(base_path_str) - if base_path.is_absolute(): - search_dir = base_path if base_path.is_dir() else base_path.parent - else: - search_dir = (ROOT / base_path).resolve() - - # Check if the base directory exists - if search_dir.exists(): - if search_dir.is_dir(): - new_data = [] - filters = filters if len(filters) > 0 and isinstance(filters[0], str) else IMAGE_FORMATS - for pattern in filters: - found = glob.glob(str(search_dir / pattern), recursive=recurse) - new_data.extend([str(Path(f).resolve()) for f in found if Path(f).is_file()]) - if len(new_data): - data = new_data - elif search_dir.is_file(): - path = str(search_dir.resolve()) - if path.lower().endswith('.txt'): - with open(path, 'r', encoding='utf-8') as f: - data = f.read().split('\n') - else: - data = [path] - elif len(results := glob.glob(str(search_dir))) > 0: - data = [x.replace('\\', '/') for x in results] + # Check if the base directory exists + if search_dir.exists(): + if search_dir.is_dir(): + new_data = [] + filters = filters if len(filters) > 0 and isinstance(filters[0], str) else IMAGE_FORMATS + for pattern in filters: + found = glob.glob(str(search_dir / pattern), recursive=recurse) + new_data.extend([str(Path(f).resolve()) for f in found if Path(f).is_file()]) + if len(new_data): + data = new_data + elif search_dir.is_file(): + path = str(search_dir.resolve()) + if path.lower().endswith('.txt'): + with open(path, 'r', encoding='utf-8') as f: + data = f.read().split('\n') + else: + data = [path] + elif len(results := glob.glob(str(search_dir))) > 0: + data = [x.replace('\\', '/') for x in results] if len(data): ret = [] @@ -303,8 +303,6 @@ class QueueBaseNode(JOVBaseNode): if (val := self.__last_q_value.get(q_data, None)) is not None: return val if isinstance(q_data, (str,)): - if not os.path.isfile(q_data): - return q_data _, ext = os.path.splitext(q_data) if ext in IMAGE_FORMATS: data = image_load(q_data)[0] diff --git a/pyproject.toml b/pyproject.toml index c5c835c..8f6faca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "jovimetrix" description = "Integrates Webcam, MIDI, Spout and GLSL shader support. Animation via tick. Parameter manipulation with wave generator. Math operations with Unary and Binary support. Value conversion for all major types (int, string, list, dict, Image, Mask). Shape mask generation, image stacking and channel ops, batch splitting, merging and randomizing, load images and video from anywhere, dynamic bus routing with a single node, export support for GIPHY, save output anywhere! flatten, crop, transform; check colorblindness, make stereogram or stereoscopic images, or liner interpolate values and more." -version = "1.2.61" +version = "1.2.62" license = { file = "LICENSE" } dependencies = [ "aenum", diff --git a/sup/image/__init__.py b/sup/image/__init__.py index f1295f2..c8b9972 100644 --- a/sup/image/__init__.py +++ b/sup/image/__init__.py @@ -10,6 +10,7 @@ import io from io import BytesIO import math import base64 +import requests from enum import Enum from typing import List, Tuple, Union @@ -365,13 +366,12 @@ def image_lerp(imageA: TYPE_IMAGE, imageB:TYPE_IMAGE, mask:TYPE_IMAGE=None, return np.clip(imageA, 0, 255) def image_load(url: str) -> Tuple[TYPE_IMAGE, TYPE_IMAGE]: - try: - img = cv2.imread(url, cv2.IMREAD_UNCHANGED) - if img is None: - raise ValueError(f"{url} could not be loaded.") - + if url.lower().startswith("http"): + response = requests.get(url, stream=True) + response.raise_for_status() + img_array = np.asarray(bytearray(response.content), dtype=np.uint8) + img = cv2.imdecode(img_array, cv2.IMREAD_UNCHANGED) img = image_normalize(img) - # logger.debug(f"load image {url}: {img.ndim} {img.shape}") if img.ndim == 3: if img.shape[2] == 4: img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGRA) @@ -379,18 +379,33 @@ def image_load(url: str) -> Tuple[TYPE_IMAGE, TYPE_IMAGE]: img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) elif img.ndim < 3: img = np.expand_dims(img, -1) - - except Exception: - logger.debug(f"load image fallback to PIL {url}") + else: try: - img = Image.open(url) - img = ImageOps.exif_transpose(img) - img = np.array(img) - if img.dtype != np.uint8: - img = np.clip(np.array(img * 255), 0, 255).astype(dtype=np.uint8) - except Exception as e: - logger.error(str(e)) - raise Exception(f"Error loading image: {e}") + img = cv2.imread(url, cv2.IMREAD_UNCHANGED) + if img is None: + raise ValueError(f"{url} could not be loaded.") + + img = image_normalize(img) + # logger.debug(f"load image {url}: {img.ndim} {img.shape}") + if img.ndim == 3: + if img.shape[2] == 4: + img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGRA) + else: + img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) + elif img.ndim < 3: + img = np.expand_dims(img, -1) + + except Exception: + logger.debug(f"load image fallback to PIL {url}") + try: + img = Image.open(url) + img = ImageOps.exif_transpose(img) + img = np.array(img) + if img.dtype != np.uint8: + img = np.clip(np.array(img * 255), 0, 255).astype(dtype=np.uint8) + except Exception as e: + # logger.error(str(e)) + raise Exception(f"Error loading image: {e}") if img is None: raise Exception(f"No file found at {url}")