From 71a44fffc989b572e2e62968fe31abfe82bb8f4e Mon Sep 17 00:00:00 2001 From: Excidos Date: Tue, 9 Jul 2024 19:41:06 +1000 Subject: [PATCH] Add files via upload --- ComfyUI-Documents/__init__.py | 44 ++++ .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 2925 bytes .../comfy_documents_utils.cpython-311.pyc | Bin 0 -> 1872 bytes .../document_nodes.cpython-311.pyc | Bin 0 -> 14260 bytes .../__pycache__/server.cpython-311.pyc | Bin 0 -> 3953 bytes .../__pycache__/utils.cpython-311.pyc | Bin 0 -> 2755 bytes ComfyUI-Documents/comfy_documents_utils.py | 21 ++ ComfyUI-Documents/document_nodes.py | 213 ++++++++++++++++++ ComfyUI-Documents/documents.core.js | 160 +++++++++++++ ComfyUI-Documents/install.py | 14 ++ ComfyUI-Documents/requirements.txt | 5 + ComfyUI-Documents/server.py | 55 +++++ ComfyUI-Documents/utils.py | 36 +++ ComfyUI-Documents/web/js/documents.core.js | 129 +++++++++++ 14 files changed, 677 insertions(+) create mode 100644 ComfyUI-Documents/__init__.py create mode 100644 ComfyUI-Documents/__pycache__/__init__.cpython-311.pyc create mode 100644 ComfyUI-Documents/__pycache__/comfy_documents_utils.cpython-311.pyc create mode 100644 ComfyUI-Documents/__pycache__/document_nodes.cpython-311.pyc create mode 100644 ComfyUI-Documents/__pycache__/server.cpython-311.pyc create mode 100644 ComfyUI-Documents/__pycache__/utils.cpython-311.pyc create mode 100644 ComfyUI-Documents/comfy_documents_utils.py create mode 100644 ComfyUI-Documents/document_nodes.py create mode 100644 ComfyUI-Documents/documents.core.js create mode 100644 ComfyUI-Documents/install.py create mode 100644 ComfyUI-Documents/requirements.txt create mode 100644 ComfyUI-Documents/server.py create mode 100644 ComfyUI-Documents/utils.py create mode 100644 ComfyUI-Documents/web/js/documents.core.js diff --git a/ComfyUI-Documents/__init__.py b/ComfyUI-Documents/__init__.py new file mode 100644 index 0000000..cde5af3 --- /dev/null +++ b/ComfyUI-Documents/__init__.py @@ -0,0 +1,44 @@ +import os +import subprocess +import sys +from .document_nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS +from .utils import folder_paths +from .server import WEB_DIRECTORY + +__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY'] + +def install_requirements(): + requirements_path = os.path.join(os.path.dirname(__file__), "requirements.txt") + try: + subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", requirements_path]) + print("Successfully installed requirements for ComfyUI-Documents") + except subprocess.CalledProcessError as e: + print(f"Error installing requirements: {e}") + +try: + import fitz # PyMuPDF +except ImportError: + print("Attempting to install required packages for ComfyUI-Documents...") + install_requirements() + import fitz + +# Ensure the input directory exists +input_dir = folder_paths.get_input_directory() +os.makedirs(input_dir, exist_ok=True) + +# Set up the web directory for the JavaScript file +javascript_file = os.path.join(WEB_DIRECTORY, "js", "documents.core.js") + +if os.path.exists(javascript_file): + print(f"documents.core.js found at {javascript_file}") +else: + print(f"Warning: documents.core.js not found at {javascript_file}") + +def get_javascript(): + if os.path.exists(javascript_file): + with open(javascript_file, "r", encoding="utf-8") as file: + return file.read() + else: + return "" + +__all__.extend(["get_javascript"]) \ No newline at end of file diff --git a/ComfyUI-Documents/__pycache__/__init__.cpython-311.pyc b/ComfyUI-Documents/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6595c7e5e0b4f3876778820ad38a50e556ae707f GIT binary patch literal 2925 zcmaJDO>Y~=b%s0SF25;Cu2m(DX~#+bKB9=?3MzEpxPaZcui&FT zDpjN^DUhna;KvB&q!Osg1=*$j6{Q+11Tk7gH1H`dgs6N66~e);pGYC19eWY6=rP2f z$H<3Rj4Ipky)WSD4woC=hRGKm3xQ{`8n9kQ8d*m-q-C^@!CGF6drHF=g#<{^+K4A* z2^#z|wCw}0@N@?j8cA=qHR@4=Zx*Cs8rhb>4xa8Pj1Acx@c4soHa5H~9(VT|Wn+Iq zpZJDl7-=zA8rxq=;}F50vC!7R!7q3;O|0Vo^+$0YO^y5@R2F{_k)856ql<5yom0w>Y17CS2y(Tz=_7c3EA_F3k00Q`m{D=oQMiS}U6G8ZeJunLDkX&0U;3 zbLp*%%Tp33;F^oN;WD5SJiVa+B_j4@$*m@{CqQVem+P%wE(XNtCvF2zay^^C;l<{E0fJg!*ry)xfZ4jLd;rq0dMu%A*&N>7KN%=d~UU zb$@6R582B%@qRV23^l)rZ^60C?@{a6tNbZtv60~Hy?U9mD$`6OZQd{&zUitX)yg$T zE^CHatWZda3R@Z5*EC90Yqd zWo>oHXeKokc6_?w5K$ivS<}m!Bhxb1idE()s=89HFjaLzM*W={*Tou~Q0ZM(x~`UB zagJnc0E69NrMg*sr@|b6jh8jkNuB{Iqf1>AcL`9wBZKR8E7HNt5cQKv{doEO%++je zI?K(gd%IBA%No_!4Yj6o*SjaUTB;kSUR5=nGQ(r`_k2}V%Uan~)pTvc8Qa^rI{0n= zbNHsnr|}h_CfXgJ_*njHYm04?RDt(>&>gQqY$64=7q7>5<_kQ3Pq+R4OEPfCxJOzK%JO*i|RL#;*}WVh9{fw=!u?PLE|(`y0<4Igqo zX9mID-Y^IBD5kzIrQs{#6xu{L(Uy0GdS>o>8*u+BEBW9mgWqs-!fwm>B{ImmuY0*+ zv<%mtxcK1s6KLoDMF;bSzizHf|Jn_-R?;cN%0W{BF8Uge0CECu0t_d> z`K1mb{^V|SZS@^ zkd;iGZ#cmo!%?cm>kK4@n<*X@D>@=jN+1zzkv)@X^mFtCX2{LVkIHYVI;~gOEPoYv zB4x(!0Yh9dZlhq+{k74Ep&RL-gEwW&f1&b`S$+$(vTV%o}6Bd~mux_63$Y3in z9YN9Qdl&4;@m3^lN7Bt;8-<&`JK=k|En=a`7MirtBoLhd8a?pgFYX`z;P{_5A5iP$ zd@Fg*PM&MVA4~o_zq&hnd-e~nw4|f9bW|+q9Nt?_=0V&FWdK@a#wIhC3y-@?edD2O zh28*Yk$IcUTV%dN{22DtLotMvyRq9bE0MC|M_TBpjgDF#7~*f}=Om53ejyu|c48Rt Fe*r+K%i#b3 literal 0 HcmV?d00001 diff --git a/ComfyUI-Documents/__pycache__/comfy_documents_utils.cpython-311.pyc b/ComfyUI-Documents/__pycache__/comfy_documents_utils.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a7a729d68eb807fbd30c7082d823ecbc431a0307 GIT binary patch literal 1872 zcmcgs&ube;6rNeFRE4e}y702`d6dYT!Ov38*4y zQ4MN>7QEcsjdFr2-4|CR6@Q45p%;98If$+@&ydcRZEBVj!&Ei9w>Wdl%$7@XoY)DY#k zV>xBRc~lcGo*-(WKW?7zFz|H_RvI7%qbjNf{6zJiPChut z8?VC|0bjm$Ny9bP#9FW#Y=+N(VZmROD*haicvWZ){TueQZ;TpW(D(pZ75rxdS@3dF z_Q>m9HQ@zdp(6aT0ksKFfJwT|dyMetSlG6TIt7BTV9{LD2$M3Fr5P%dY>zw8wnP2a zV8a6@u^@K>4b49cD=Yvun;&=8lP(csoQNF3`&{8+kgEXU1h=1ng2swYIMIX~P1KhT zV@W4A<;JG!?>-;7P=EU{dU1Pr_eR&;L1J*iQyXHp85gz|9*yt4@p$^-bhGl)>&}%~ z_sXmz&$;qk2L*;E4&#@$KYkWZJdG!ssuQ1b<5QdOY%Ffr?a0Lkm95I=y~e%WLMt)} zPdggl8To2t_qG!qccbI2==jUO<=B^r&6|yzEpg0ijbx}>O^H@4-N+Np-a(~V0CMw^ zJJpP@!t*6lEf=)~G66ey@7M)UodCwTjfU#puZ=FX{7*;3*uoujLQ5FW@P%U~$@uc_ Q^=4r2MhC(0T;)ao4@x3<9{>OV literal 0 HcmV?d00001 diff --git a/ComfyUI-Documents/__pycache__/document_nodes.cpython-311.pyc b/ComfyUI-Documents/__pycache__/document_nodes.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94556ff3f3b9af4c424e859ce345706734ee7a7a GIT binary patch literal 14260 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 QXcY#nf}oY^MTg}708G8Z9smFU literal 0 HcmV?d00001 diff --git a/ComfyUI-Documents/__pycache__/server.cpython-311.pyc b/ComfyUI-Documents/__pycache__/server.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..adbda59a33831a0960c998048edb1fa9493a2860 GIT binary patch literal 3953 zcma)8Z)_9E6`$Sp+UwZHo7lvV1Y(ka>+2;Eq4)vYKObezOTK9}SBE?Nn9rASB>Qu&q>S1IC4`(};*tO-?T zXJ_90d+*K6oA-O)_s=hz%$KjFMn4OD03>TE`7R<~D2lNCD?Xo0!suyYzmYIXA{=M-4`_yEega;~lI zE$6V?0lWe0vvZ(O#?y)RBgHXGUu7<$S&Bjz;l3zj({fu`W5JnYB+AZt9CadfcXe^8O;`FI*wnSgvCNf3N)p1;Qc4Vy2XL1*or;=JXB5++idR*Wp1ou`GKsW!ttE!B zjKfkUTzUraGOxp7=||ueZ89BMSy4@na3f1h2cb*{783#{Hf07bi)viTWM|a4AYpMz zmGPWO%OK2peU79oWiYW(OibJ4&WlP`&M3e`&Wah6oyC$Wn!cnWXX3a#=8j#R60@o# z!-OoBGOEeID~`HN7ZyL65kVt|kWdqt_zp~5jOm(^Gb%h_(_PWUbQ4`mPJo&=DN!NM zCR|Q0$4?)gJTcaN0;`j!_UN<1rLHE}5=mIo%MW)xLU$1}1ZD&@}Z z;a5&WnN&o4MZ`VXIn!Tvl6W7;ByyDnIM9E=H;*2PzO+KDNLWo0^xSD?Lj+dQ*qd)Un`t77QD~emyv71P2$~>%r#bk(Dz` zX9_#lM)lx;5gb@>Kl8V)j_Lk=hJW7%qTKIL>w)IwqXk9VKdc2}dLU*5VwyMh%-47; zy852(>o9yBWa6$uqUiPA9Jw)aYee_98QwO{+qS{LfHi6rKahDplA_wT6}7p!xmEYS zorn(@{%~R6+E27SeY(Hj@b_z6|K_?UxPfSPpjxbRK5bjycYSN0+#S3(2qa{k>o+(u zf8F1-m@D*sH-5kI?uC07G=G1E6^zzqP-Jt110|5XJIqC|9o7IXl2ggp{iT5}@05Z6G8yMML zmc5my)vaaJ^j1aX>f9gp+W@3)R0-PlS_xY&BfAdcpzj;P7#0Eh~~=e)ao23!u*yD&SG&jOrj4@6I_2H78x2nPZccKuF-O!GK*S3M~%g z7~Eew?+v?g+gAz=RctM68A{oMha7VS;gPy{dw1JI}<#>6Ph27oLvqW}~rCVEM|{gUc^ zN%cJc52R>-;6W-rgHt98uz=?*jKLvThl8XbQcPFkl0sNx@-$4RcvVtVr36VWq=$jW zYqCl0kXD_@0!#^*T))YtYu>J2QOGrW7|V?~$m*87^P1@bGW zr*)5L{!!gOYWPPrZgkz_Tm0G0<2R0feYha5y>mOQxAq&Y{kmts@C?ikKeL91ZVat< z>7I7O(++N#-TSxP{b4Yj%~Dx1;+;MayJ5)-MoI|`fu`! zd2s8#okhO&3E%OU?x97h3^sk@ZSM4HAhq-kLh9i{#Xb7ed7rpsz)6Sv0nC34+W)VUC|(fQx@D? zCI;3e$sem~wiMp5Gpdw&WnAQ*OEHr77g$bb)p2W?$w?WA$1+o*>BRDkDk_-x3@cE; z2M9&Xz_KUY43y~tJG5diE*`fcnz-rvAa*i7Ja#^I>b-O4FWyBjU>>rp=_PkGEekU# zaR`q>AJ`F6{s9GSnxcxxH*bAK zQ_A&K>u0T+uU)6Y1{L0*0~CNb6hAPC-c#v$3(?$HEuXb4ysJ|og9>fX+o`uU5ENwI vj!I7tY78v9R=i8z)$@P);Lq_t#MgeQ?;13A4L)FXe#qd5N?XAru)F^PNNr#~ literal 0 HcmV?d00001 diff --git a/ComfyUI-Documents/__pycache__/utils.cpython-311.pyc b/ComfyUI-Documents/__pycache__/utils.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f606254ba3b8e67f8da4e5f192b605efda4eb47 GIT binary patch literal 2755 zcmcImO=ufO6rSDHujP21SeAbjMd%qk zm?C|V>=r3RngzP0>dRFA(#iVB$zReR)P1-)}C+x7k1ji zbpn^G;krZz?C7-W<7z^bG+`#HPvaP4FNTm0`W!r)r-8eK^j)HBw%$s`zFksukS~j~N)Ng$R>hWsfNVLgsj_0Z zMOjm#Ny+kPQZzvY9zlr92}uxwjK#$g7!xawZI50)7a6$JJD};2AvGDFd+(AkD=VTp zi-j3g)1#9KsUnEPQdn1$f})BNt}uHqsIgR1Qgj?i>2d;}nwhhjrX*dUrU_t&6w_60 z?$)v0p>S#+4T2qhA4nGc<6%S1O^AmO zFA(4!f|unX{-R#=+F)8<>O`7P#QtN@v<9LbAdp5CMb)FaBvM}!ty^6+xYsI?UGdvM zvgi+g%ew!>n*W61?=}6sIq!x)@F27_{Q2-o+vAId|BUHBlk*n&*5zP91?O)^I>QUu|4C6g$KCz;vrI|FEV)v}UDxNOHpsR>nByMt7 z(JVu1>2+!gxmtv+`myoROGD$rm4We#W8>S*sds};n(>xfQf|nas!%sr?&u_@CtzJn zO(s=^^n=`vY%+=^IHN3ISysNN5TNY3S}$V(N8?i2d2fNJ10FmH1aipT{Al#yM8Vw+ zfAB+2-)LxDoVu?rsLL^=c$K;yy-SL4Y^m7wC(8@vmqGtsGk_M7S4APDg zLh3M$wrx*;xoo_n^#PBB0saU`iDMY1h`d?)D>--uQn;49BD_Ce3p!m)V+q-$(Zh6> JP%WYR{{y1kKCu7* literal 0 HcmV?d00001 diff --git a/ComfyUI-Documents/comfy_documents_utils.py b/ComfyUI-Documents/comfy_documents_utils.py new file mode 100644 index 0000000..60bdbc5 --- /dev/null +++ b/ComfyUI-Documents/comfy_documents_utils.py @@ -0,0 +1,21 @@ +import os +from typing import List + +def get_input_directory(): + return os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))), "input") + +def get_supported_extensions(): + return [".pdf", ".txt", ".doc", ".docx"] + +def get_filename_list() -> List[str]: + input_dir = get_input_directory() + extensions = get_supported_extensions() + + files = [] + for file in os.listdir(input_dir): + if os.path.isfile(os.path.join(input_dir, file)): + _, ext = os.path.splitext(file) + if ext.lower() in extensions: + files.append(file) + + return sorted(files) \ No newline at end of file diff --git a/ComfyUI-Documents/document_nodes.py b/ComfyUI-Documents/document_nodes.py new file mode 100644 index 0000000..9857e89 --- /dev/null +++ b/ComfyUI-Documents/document_nodes.py @@ -0,0 +1,213 @@ +import os +import fitz # PyMuPDF +import torch +from PIL import Image +import numpy as np +import folder_paths +from .utils import strip_path +from docx import Document + +class DocumentLoaderNode: + @classmethod + def INPUT_TYPES(s): + input_dir = folder_paths.get_input_directory() + files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f)) and f.lower().endswith((".pdf", ".txt", ".doc", ".docx"))] + return {"required": {"file_path": (sorted(files),)}} + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("parsed_text",) + FUNCTION = "load_document" + CATEGORY = "document_processing" + + def load_document(self, file_path): + full_path = folder_paths.get_annotated_filepath(strip_path(file_path)) + _, ext = os.path.splitext(full_path) + if ext.lower() == '.pdf': + return (self.parse_pdf(full_path),) + elif ext.lower() in ['.txt', '.doc', '.docx']: + return (self.parse_text(full_path),) + else: + raise ValueError(f"Unsupported file type: {ext}") + + def parse_pdf(self, file_path): + text = "" + with fitz.open(file_path) as doc: + for page in doc: + text += page.get_text() + return text + + def parse_text(self, file_path): + _, ext = os.path.splitext(file_path) + if ext.lower() == '.txt': + with open(file_path, 'r', encoding='utf-8') as file: + return file.read() + elif ext.lower() in ['.doc', '.docx']: + doc = Document(file_path) + return "\n".join([para.text for para in doc.paragraphs]) + + @classmethod + def IS_CHANGED(cls, file_path): + full_path = folder_paths.get_annotated_filepath(strip_path(file_path)) + return full_path + + @classmethod + def VALIDATE_INPUTS(cls, file_path): + full_path = folder_paths.get_annotated_filepath(strip_path(file_path)) + return os.path.isfile(full_path) + +class PDFToImageNode: + @classmethod + def INPUT_TYPES(s): + input_dir = folder_paths.get_input_directory() + files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f)) and f.lower().endswith(".pdf")] + return { + "required": { + "file_path": (sorted(files),), + "start_page": ("INT", {"default": 1, "min": 1, "max": 10000}), + "end_page": ("INT", {"default": 1, "min": 1, "max": 10000}), + "dpi": ("INT", {"default": 300, "min": 72, "max": 600}), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "pdf_to_images" + CATEGORY = "document_processing" + OUTPUT_IS_LIST = (True,) + + def pdf_to_images(self, file_path, start_page, end_page, dpi): + full_path = folder_paths.get_annotated_filepath(strip_path(file_path)) + doc = fitz.open(full_path) + num_pages = len(doc) + + start_page = max(1, min(start_page, num_pages)) + end_page = max(start_page, min(end_page, num_pages)) + + images = [] + for page_num in range(start_page - 1, end_page): + page = doc.load_page(page_num) + pix = page.get_pixmap(matrix=fitz.Matrix(dpi/72, dpi/72)) + img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples) + img_np = np.array(img).astype(np.float32) / 255.0 + img_tensor = torch.from_numpy(img_np)[None,] + images.append(img_tensor) + + doc.close() + return (images,) + + @classmethod + def IS_CHANGED(cls, file_path, start_page, end_page, dpi): + full_path = folder_paths.get_annotated_filepath(strip_path(file_path)) + return full_path + + @classmethod + def VALIDATE_INPUTS(cls, file_path, start_page, end_page, dpi): + full_path = folder_paths.get_annotated_filepath(strip_path(file_path)) + return os.path.isfile(full_path) + +class PDFPageSplitterNode: + @classmethod + def INPUT_TYPES(s): + input_dir = folder_paths.get_input_directory() + files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f)) and f.lower().endswith(".pdf")] + return { + "required": { + "file_path": (sorted(files),), + "page_numbers": ("STRING", {"default": "1,2,3"}), + "dpi": ("INT", {"default": 300, "min": 72, "max": 600}), + } + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("selected_pages",) + FUNCTION = "pdf_to_selected_images" + CATEGORY = "document_processing" + OUTPUT_NODE = True + OUTPUT_IS_LIST = (True,) + + def pdf_to_selected_images(self, file_path, page_numbers, dpi): + full_path = folder_paths.get_annotated_filepath(strip_path(file_path)) + doc = fitz.open(full_path) + num_pages = len(doc) + + # Parse page numbers from input string + try: + selected_pages = [int(p.strip()) for p in page_numbers.split(',') if p.strip()] + selected_pages = [p for p in selected_pages if 1 <= p <= num_pages] + except ValueError: + raise ValueError("Invalid page numbers. Please provide comma-separated integers.") + + images = [] + for page_num in selected_pages: + page = doc.load_page(page_num - 1) # PDF pages are 0-indexed + pix = page.get_pixmap(matrix=fitz.Matrix(dpi/72, dpi/72)) + img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples) + img_np = np.array(img).astype(np.float32) / 255.0 + img_tensor = torch.from_numpy(img_np)[None,] + images.append(img_tensor) + + doc.close() + return (images,) + + @classmethod + def IS_CHANGED(cls, file_path, page_numbers, dpi): + full_path = folder_paths.get_annotated_filepath(strip_path(file_path)) + return full_path + + @classmethod + def VALIDATE_INPUTS(cls, file_path, page_numbers, dpi): + full_path = folder_paths.get_annotated_filepath(strip_path(file_path)) + if not os.path.isfile(full_path): + return False + try: + [int(p.strip()) for p in page_numbers.split(',') if p.strip()] + except ValueError: + return False + return True + +class ImageSelectorNode: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "images": ("IMAGE",), + "indexes": ("STRING", {"default": "0"}), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "select_images" + CATEGORY = "image_processing" + OUTPUT_NODE = True + OUTPUT_IS_LIST = (True,) + + def select_images(self, images, indexes): + try: + index_list = [int(idx.strip()) for idx in indexes.split(',')] + except ValueError: + raise ValueError("Invalid indexes. Please provide comma-separated integers.") + + selected_images = [] + for idx in index_list: + if idx < 0 or idx >= len(images): + raise ValueError(f"Index {idx} is out of range. Available images: {len(images)}") + selected_images.append(images[idx]) + + return (selected_images,) + + @classmethod + def IS_CHANGED(cls, images, indexes): + return indexes + +NODE_CLASS_MAPPINGS = { + "DocumentLoader": DocumentLoaderNode, + "PDFToImage": PDFToImageNode, + "PDFPageSplitter": PDFPageSplitterNode, + "ImageSelector": ImageSelectorNode +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "DocumentLoader": "Document Loader", + "PDFToImage": "PDF to Image (Multi-Page)", + "PDFPageSplitter": "PDF Page Splitter", + "ImageSelector": "Image Selector" +} \ No newline at end of file diff --git a/ComfyUI-Documents/documents.core.js b/ComfyUI-Documents/documents.core.js new file mode 100644 index 0000000..e7b5406 --- /dev/null +++ b/ComfyUI-Documents/documents.core.js @@ -0,0 +1,160 @@ +import { app } from '../../../scripts/app.js'; +import { api } from '../../../scripts/api.js'; + +function chainCallback(object, property, callback) { + if (object == undefined) { + console.error("Tried to add callback to non-existent object"); + return; + } + if (property in object) { + const callback_orig = object[property]; + object[property] = function () { + const r = callback_orig.apply(this, arguments); + callback.apply(this, arguments); + return r; + }; + } else { + object[property] = callback; + } +} + +function searchBox(event, [x, y], node) { + if (this.prompt) { + return; + } + this.prompt = true; + + let pathWidget = this; + let dialog = document.createElement("div"); + dialog.className = "litegraph litesearchbox graphdialog rounded"; + dialog.innerHTML = 'File Path
'; + dialog.close = () => { + dialog.remove(); + }; + document.body.append(dialog); + if (app.canvas.ds.scale > 1) { + dialog.style.transform = "scale(" + app.canvas.ds.scale + ")"; + } + var input = dialog.querySelector(".value"); + var options_element = dialog.querySelector(".helper"); + input.value = pathWidget.value || ""; + + var timeout = null; + let last_path = null; + let extensions = pathWidget.options?.extensions; + + input.addEventListener("keydown", (e) => { + dialog.is_modified = true; + if (e.keyCode == 27) { + dialog.close(); + } else if (e.keyCode == 13 && e.target.localName != "textarea") { + if (pathWidget.value) { + pathWidget.value = input.value; + if (pathWidget.callback) { + pathWidget.callback(pathWidget.value); + } + } + dialog.close(); + } else { + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(updateOptions, 100); + return; + } + this.prompt = false; + e.preventDefault(); + e.stopPropagation(); + }); + + var button = dialog.querySelector("button"); + button.addEventListener("click", (e) => { + if (pathWidget.value) { + pathWidget.value = input.value; + if (pathWidget.callback) { + pathWidget.callback(pathWidget.value); + } + } + node.graph.setDirtyCanvas(true); + dialog.close(); + this.prompt = false; + }); + + var rect = app.canvas.canvas.getBoundingClientRect(); + var offsetx = -20; + var offsety = -20; + if (rect) { + offsetx -= rect.left; + offsety -= rect.top; + } + + if (event) { + dialog.style.left = event.clientX + offsetx + "px"; + dialog.style.top = event.clientY + offsety + "px"; + } else { + dialog.style.left = canvas.width * 0.5 + offsetx + "px"; + dialog.style.top = canvas.height * 0.5 + offsety + "px"; + } + + async function updateOptions() { + timeout = null; + let params = { path: input.value }; + if (extensions) { + params.extensions = extensions; + } + let optionsURL = api.apiURL('getpath?' + new URLSearchParams(params)); + try { + let resp = await fetch(optionsURL); + let options = await resp.json(); + options_element.innerHTML = ''; + for (let option of options) { + let el = document.createElement("div"); + el.innerText = option; + el.className = "litegraph lite-search-item"; + if (option.endsWith('/')) { + el.className += " is-dir"; + el.addEventListener("click", (e) => { + input.value = option; + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(updateOptions, 100); + }); + } else { + el.addEventListener("click", (e) => { + if (pathWidget.value) { + pathWidget.value = option; + if (pathWidget.callback) { + pathWidget.callback(pathWidget.value); + } + } + dialog.close(); + pathWidget.prompt = false; + }); + } + options_element.appendChild(el); + } + } catch (e) { + console.error("Error fetching options:", e); + } + } + + setTimeout(async function () { + input.focus(); + await updateOptions(); + }, 100); + + return dialog; +} + +app.registerExtension({ + name: "ComfyUI-Documents", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData?.name == "DocumentLoader" || nodeData?.name == "PDFToImage") { + chainCallback(nodeType.prototype, "onNodeCreated", function () { + const pathWidget = this.widgets.find((w) => w.name === "document"); + pathWidget.mouse = searchBox; + }); + } + } +}); \ No newline at end of file diff --git a/ComfyUI-Documents/install.py b/ComfyUI-Documents/install.py new file mode 100644 index 0000000..c7c3778 --- /dev/null +++ b/ComfyUI-Documents/install.py @@ -0,0 +1,14 @@ +import subprocess +import sys +import os + +def install_requirements(): + requirements_path = os.path.join(os.path.dirname(__file__), "requirements.txt") + try: + subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", requirements_path]) + print("Successfully installed requirements for ComfyUI-Documents") + except subprocess.CalledProcessError as e: + print(f"Error installing requirements: {e}") + +if __name__ == "__main__": + install_requirements() \ No newline at end of file diff --git a/ComfyUI-Documents/requirements.txt b/ComfyUI-Documents/requirements.txt new file mode 100644 index 0000000..61d1cdb --- /dev/null +++ b/ComfyUI-Documents/requirements.txt @@ -0,0 +1,5 @@ +PyMuPDF==1.23.8 +python-docx==1.1.0 +pandas==2.1.4 +Markdown==3.5.1 +PyYAML==6.0.1 \ No newline at end of file diff --git a/ComfyUI-Documents/server.py b/ComfyUI-Documents/server.py new file mode 100644 index 0000000..197297e --- /dev/null +++ b/ComfyUI-Documents/server.py @@ -0,0 +1,55 @@ +import server +import os +from aiohttp import web +from .utils import is_safe_path, strip_path +from .utils import folder_paths # Import folder_paths from utils + +routes = server.PromptServer.instance.routes + +@routes.post("/upload/document") +async def upload_document(request): + try: + data = await request.post() + file = data['document'] + filename = file.filename + content = file.file.read() + + input_dir = folder_paths.get_input_directory() + file_path = os.path.join(input_dir, filename) + + if not is_safe_path(file_path): + return web.Response(status=403, text="Invalid file path") + + with open(file_path, 'wb') as f: + f.write(content) + + return web.json_response({"name": filename}) + except Exception as e: + print(f"Error in upload_document: {str(e)}") + return web.Response(status=500, text=str(e)) + +@routes.get("/getpath") +async def get_path(request): + query = request.rel_url.query + if "path" not in query: + return web.Response(status=400, text="Missing 'path' parameter") + path = os.path.abspath(strip_path(query["path"])) + + if not os.path.exists(path) or not is_safe_path(path): + return web.json_response([]) + + valid_extensions = query.get("extensions", "").split(",") + valid_items = [] + for item in os.scandir(path): + try: + if item.is_dir(): + valid_items.append(item.name + "/") + continue + if not valid_extensions or item.name.split(".")[-1].lower() in valid_extensions: + valid_items.append(item.name) + except OSError: + pass + + return web.json_response(valid_items) + +WEB_DIRECTORY = os.path.join(os.path.dirname(__file__), "web") \ No newline at end of file diff --git a/ComfyUI-Documents/utils.py b/ComfyUI-Documents/utils.py new file mode 100644 index 0000000..04245d2 --- /dev/null +++ b/ComfyUI-Documents/utils.py @@ -0,0 +1,36 @@ +import os + +class folder_paths: + @classmethod + def get_input_directory(cls): + # Update the path to the correct location of the 'input' directory + return os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))), "input") + + @classmethod + def get_annotated_filepath(cls, filename): + input_dir = cls.get_input_directory() + file_path = os.path.join(input_dir, filename) + return file_path, input_dir + + @classmethod + def exists_annotated_filepath(cls, filename): + file_path, _ = cls.get_annotated_filepath(filename) + return os.path.exists(file_path) + +def strip_path(path): + path = path.strip() + if path.startswith("\""): + path = path[1:] + if path.endswith("\""): + path = path[:-1] + return path + +def is_safe_path(path): + if "STRICT_PATHS" not in os.environ: + return True + basedir = os.path.abspath('.') + try: + common_path = os.path.commonpath([basedir, path]) + except: + return False + return common_path == basedir \ No newline at end of file diff --git a/ComfyUI-Documents/web/js/documents.core.js b/ComfyUI-Documents/web/js/documents.core.js new file mode 100644 index 0000000..341e66d --- /dev/null +++ b/ComfyUI-Documents/web/js/documents.core.js @@ -0,0 +1,129 @@ +import { app } from '../../../scripts/app.js'; +import { api } from '../../../scripts/api.js'; + +function chainCallback(object, property, callback) { + if (object == undefined) { + console.error("Tried to add callback to non-existent object"); + return; + } + if (property in object) { + const callback_orig = object[property]; + object[property] = function () { + const r = callback_orig.apply(this, arguments); + callback.apply(this, arguments); + return r; + }; + } else { + object[property] = callback; + } +} + +function createFileUploadWidget(node, inputName, inputData, app) { + const fileInput = document.createElement("input"); + Object.assign(fileInput, { + type: "file", + accept: ".pdf,.txt,.doc,.docx", + style: "display: none", + onchange: async () => { + if (fileInput.files.length) { + await uploadFile(fileInput.files[0], true); + } + }, + }); + document.body.append(fileInput); + + const uploadWidget = node.addWidget("button", inputName, "document", () => { + fileInput.click(); + }); + uploadWidget.label = "Choose file to upload"; + uploadWidget.serialize = false; + + async function uploadFile(file, updateNode) { + try { + const body = new FormData(); + body.append("document", file); + const resp = await api.fetchApi("/upload/document", { + method: "POST", + body, + }); + if (resp.status === 200) { + const data = await resp.json(); + let path = data.name; + if (data.subfolder) path = data.subfolder + "/" + path; + + let documentWidget = null; + for (let i = 0; i < node.widgets.length; i++) { + if (node.widgets[i].name === "file_path") { + documentWidget = node.widgets[i]; + break; + } + } + + if (documentWidget && !documentWidget.options.values.includes(path)) { + documentWidget.options.values.push(path); + } + if (updateNode && documentWidget) { + documentWidget.value = path; + } + } else { + alert(resp.status + " - " + resp.statusText); + } + } catch (error) { + alert(error); + } + } + + node.onDragOver = function (e) { + if (e.dataTransfer && e.dataTransfer.items) { + const document = [...e.dataTransfer.items].find((f) => f.kind === "file" && + (f.type === "application/pdf" || f.type === "text/plain" || + f.type === "application/msword" || f.type === "application/vnd.openxmlformats-officedocument.wordprocessingml.document")); + return !!document; + } + return false; + }; + + node.onDragDrop = function (e) { + let handled = false; + for (const file of e.dataTransfer.files) { + if (file.type === "application/pdf" || file.type === "text/plain" || + file.type === "application/msword" || file.type === "application/vnd.openxmlformats-officedocument.wordprocessingml.document") { + uploadFile(file, !handled); + handled = true; + } + } + return handled; + }; + + return { widget: uploadWidget }; +} + +app.registerExtension({ + name: "ComfyUI-Documents", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData?.name == "DocumentLoader" || nodeData?.name == "PDFToImage") { + chainCallback(nodeType.prototype, "onNodeCreated", function () { + let documentWidget = null; + for (let i = 0; i < this.widgets.length; i++) { + if (this.widgets[i].name === "file_path") { + documentWidget = this.widgets[i]; + break; + } + } + + if (documentWidget) { + documentWidget.options.values = []; + api.fetchApi('/getpath?path=input&extensions=pdf,txt,doc,docx') + .then(response => response.json()) + .then(files => { + documentWidget.options.values = files; + if (files.length > 0) { + documentWidget.value = files[0]; + } + }); + } + createFileUploadWidget(this, "upload_document", {}, app); + }); + } + } +}); \ No newline at end of file