Add default font, add package fs, prepare to add more sherlocked nodes

This commit is contained in:
doctorpangloss 2025-10-23 12:50:34 -07:00
parent 72bb572181
commit d707efe53c
11 changed files with 351 additions and 0 deletions

View File

@ -14,8 +14,11 @@ import os
import shutil
import warnings
import fsspec
from .. import options
from ..app import logger
from ..component_model import package_filesystem
os.environ['TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL'] = '1'
os.environ["OPENCV_IO_ENABLE_OPENEXR"] = "1"
@ -155,8 +158,15 @@ def _configure_logging():
logging_level = args.logging_level
logger.setup_logger(logging_level)
def _register_fsspec_fs():
fsspec.register_implementation(
package_filesystem.PkgResourcesFileSystem.protocol,
package_filesystem.PkgResourcesFileSystem,
)
_configure_logging()
_fix_pytorch_240()
_register_fsspec_fs()
tracer = _create_tracer()
__all__ = ["args", "tracer"]

View File

@ -0,0 +1,117 @@
import importlib.resources
from fsspec.spec import AbstractFileSystem
from fsspec.registry import register_implementation
class PkgResourcesFileSystem(AbstractFileSystem):
"""
An fsspec filesystem for reading Python package resources.
Paths are expected in the format:
pkg://<package.name>/path/to/resource.txt
"""
protocol = "pkg"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._traversables = {}
def _get_traversable(self, package_name):
"""Get or cache the root Traversable for a package."""
if package_name not in self._traversables:
try:
# Get the Traversable object for the root of the package
self._traversables[package_name] = importlib.resources.files(package_name)
except ModuleNotFoundError as e:
raise FileNotFoundError(f"Package '{package_name}' not found.") from e
return self._traversables[package_name]
def _resolve_path(self, path):
"""Split a pkg:// path into package name and resource path."""
# Remove protocol and leading slashes
path_no_proto = self._strip_protocol(path).lstrip('/')
if not path_no_proto:
raise ValueError("Path must include a package name.")
parts = path_no_proto.split('/', 1)
package_name = parts[0]
resource_path = parts[1] if len(parts) > 1 else ""
root = self._get_traversable(package_name)
# Resolve the final resource Traversable
if resource_path:
resource = root.joinpath(resource_path)
else:
resource = root
return resource
def _open(
self,
path,
mode="rb",
**kwargs,
):
"""Open a file for reading."""
if "w" in mode or "a" in mode or "x" in mode:
raise NotImplementedError("Only read mode is supported.")
try:
resource = self._resolve_path(path)
if not resource.is_file():
raise FileNotFoundError(f"Path is not a file: {path}")
return resource.open("rb")
except (ModuleNotFoundError, FileNotFoundError):
raise FileNotFoundError(f"Resource not found: {path}")
except Exception as e:
raise IOError(f"Failed to open resource {path}: {e}") from e
def ls(self, path, detail=False, **kwargs):
"""List contents of a package directory."""
try:
resource = self._resolve_path(path)
if not resource.is_dir():
# If it's a file, 'ls' should return info on that file
return [self.info(path)] if detail else [path]
items = []
for item in resource.iterdir():
item_path = f"{path.rstrip('/')}/{item.name}"
if detail:
items.append(self.info(item_path))
else:
items.append(item_path)
return items
except (ModuleNotFoundError, FileNotFoundError):
raise FileNotFoundError(f"Resource path not found: {path}")
def info(self, path, **kwargs):
"""Get info about a resource."""
try:
resource = self._resolve_path(path)
resource_type = "directory" if resource.is_dir() else "file"
size = None
if resource_type == 'file':
# This is inefficient but demonstrates the principle
try:
with resource.open('rb') as f:
size = len(f.read())
except Exception:
size = None # Could fail for some reason
return {
"name": path,
"type": resource_type,
"size": size,
}
except (ModuleNotFoundError, FileNotFoundError):
raise FileNotFoundError(f"Resource not found: {path}")
# Register the filesystem with fsspec
register_implementation(PkgResourcesFileSystem.protocol, PkgResourcesFileSystem)

107
comfy/fonts/OFL.md Normal file
View File

@ -0,0 +1,107 @@
Copyright 2022-2024 The Tiny5 Project Authors (https://github.com/Gissio/font_tiny5)
This Font Software is licensed under the SIL Open Font License, Version 1.1 . This license is copied below, and is also available with a FAQ at: https://openfontlicense.org
&nbsp;
\----------------------------------------------------------------------
#### SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
\----------------------------------------------------------------------
&nbsp;
PREAMBLE
-----------
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
-----------
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
-----------
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
-----------
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
-----------
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

0
comfy/fonts/__init__.py Normal file
View File

View File

@ -112,6 +112,7 @@ dependencies = [
# doesn't support linux correctly yet
"stringzilla<4.2.0",
"requests_cache",
"universal_pathlib",
]
[build-system]

View File

View File

@ -0,0 +1 @@
OK

View File

@ -0,0 +1 @@
OK

View File

@ -0,0 +1,114 @@
import pytest
import fsspec
from comfy.component_model import package_filesystem
import os
# Ensure the filesystem is registered once for all tests
@pytest.fixture(scope="module", autouse=True)
def setup_package_filesystem():
if "pkg" not in fsspec.available_protocols():
fsspec.register_implementation(
package_filesystem.PkgResourcesFileSystem.protocol,
package_filesystem.PkgResourcesFileSystem,
)
# Yield to allow tests to run, then teardown if necessary (though not needed here)
yield
@pytest.fixture
def pkg_fs():
return fsspec.filesystem("pkg")
def test_open_file_in_package(pkg_fs):
"""Test opening a file directly within a package."""
with pkg_fs.open("pkg://tests.unit.fsspec_tests.files/b.txt", "rb") as f:
content = f.read()
assert content == b"OK"
def test_open_file_in_text_mode(pkg_fs):
"""Test opening a file in text mode."""
with pkg_fs.open("pkg://tests.unit.fsspec_tests.files/b.txt", "r") as f:
content = f.read()
assert content == "OK"
def test_open_file_in_subdir(pkg_fs):
"""Test opening a file in a subdirectory of a package."""
with pkg_fs.open("pkg://tests.unit.fsspec_tests.files/subdir/a.txt", "rb") as f:
content = f.read()
assert content == b"OK"
def test_file_not_found(pkg_fs):
"""Test that opening a non-existent file raises FileNotFoundError."""
with pytest.raises(FileNotFoundError):
pkg_fs.open("pkg://tests.unit.fsspec_tests.files/nonexistent.txt")
def test_package_not_found(pkg_fs):
"""Test that using a non-existent package raises FileNotFoundError."""
with pytest.raises(FileNotFoundError):
pkg_fs.open("pkg://non.existent.package/resource.txt")
def test_ls_package_root(pkg_fs):
"""Test listing the contents of a package."""
contents = pkg_fs.ls("pkg://tests.unit.fsspec_tests.files", detail=False)
expected_items = {
"pkg://tests.unit.fsspec_tests.files/b.txt",
"pkg://tests.unit.fsspec_tests.files/subdir",
"pkg://tests.unit.fsspec_tests.files/__init__.py",
}
# Use a subset assertion to be resilient to __pycache__
normalized_contents = {os.path.normpath(p.split('@')[0]) for p in contents}
normalized_expected = {os.path.normpath(p) for p in expected_items}
assert normalized_expected.issubset(normalized_contents)
def test_ls_subdir(pkg_fs):
"""Test listing the contents of a subdirectory."""
contents = pkg_fs.ls("pkg://tests.unit.fsspec_tests.files/subdir", detail=False)
normalized_contents = [os.path.normpath(p.split('@')[0]) for p in contents]
assert os.path.normpath("pkg://tests.unit.fsspec_tests.files/subdir/a.txt") in normalized_contents
def test_info_file(pkg_fs):
"""Test getting info for a file."""
info = pkg_fs.info("pkg://tests.unit.fsspec_tests.files/b.txt")
assert info["type"] == "file"
assert info["name"] == "pkg://tests.unit.fsspec_tests.files/b.txt"
assert info["size"] == 2
def test_info_directory(pkg_fs):
"""Test getting info for a directory."""
info = pkg_fs.info("pkg://tests.unit.fsspec_tests.files/subdir")
assert info["type"] == "directory"
assert info["name"] == "pkg://tests.unit.fsspec_tests.files/subdir"
# Directories typically don't have a size in this context, or it might be 0
assert "size" in info # Ensure size key exists
assert info["size"] is None or info["size"] == 0
def test_load_font_with_upath(pkg_fs):
"""Test that a font can be loaded from the pkg filesystem using UPath."""
from upath import UPath
from PIL import ImageFont, features
# This test requires Pillow with FreeType support
if not features.check("freetype2"):
pytest.skip("Pillow FreeType support not available")
# UPath will use the registered fsspec filesystem for "pkg"
font_path = UPath("pkg://comfy.fonts/Tiny5-Regular.ttf")
# ImageFont.truetype can take a file-like object.
# UPath.open() provides one using the underlying fsspec filesystem.
with font_path.open("rb") as f:
font = ImageFont.truetype(f, 10)
assert font is not None
assert isinstance(font, ImageFont.FreeTypeFont)