├── README.ja.md
├── README.md
├── misc
├── sss00.png
├── sss01-1.png
├── sss01-2.png
├── sss02-1.png
├── sss02-2.png
├── sss03-1.png
├── sss03-2.png
├── sss04-1.png
├── sss04-2.png
├── sss05-1.png
├── sss05-2.png
├── sss06-1.png
├── sss06-2.png
├── sss07-1.png
├── sss07-2.png
├── sss07-3.png
├── sss07-4.png
├── sss09.png
├── sss10.png
├── sss11.png
├── sss12.png
└── sss_top.png
└── scripts
├── eagle-pnginfo.py
├── eagleapi
├── api_application.py
├── api_folder.py
├── api_item.py
└── api_util.py
├── parser.py
└── tag_generator.py
/README.ja.md:
--------------------------------------------------------------------------------
1 | # Eagle-pnginfo
2 |
3 | 
4 |
5 | [English README](README.md)
6 |
7 | - [AUTOMATIC1111's Stable Diffusion Web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) 用の Extension です
8 | - WebUIで生成した画像を、生成情報(プロンプト・Generation Info)を含めて、お手元の PC で動いている [Eagle](https://jp.eagle.cool/) (画像管理ソフト) へ転送・登録します
9 |
10 | ## インストール方法
11 |
12 | - `Extensions` タブを開く
13 |
14 | - `Install from URL` にこのレポジトリの URL を入力
15 |
16 | - `Install` を実行
17 |
18 | - 別途、PCへ [Eagle]([https://jp.eagle.cool/](https://jp.eagle.cool/) をインストールしておく
19 |
20 | ## 使い方 / How to use
21 |
22 | - "設定" タブで、この Extension を有効にする
23 |
24 | - 別途、"Eagle" アプリを立ち上げておく
25 |
26 | - "AUTO1111" の Web UI を開き、いつも通り画像を生成する
27 |
28 | - 生成された画像は、自動的に Eagle アプリに登録されます
29 |
30 | ## 設定項目について
31 |
32 | | In "Setting" tab |  |
33 | | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
34 | | Send all image to Eagle | この Extension を有効にします |
35 | | Save Generation info as Annotation | PNGinfo に表示されるような、3行からなる生成情報を、Eagle の メモ欄に登録します |
36 | | Save positive prompt to Eagle as tags | プロンプトを Eagle の tag として登録します |
37 | | Save negative prompt to Eagle as | ネガティブプロンプトを Eagle の tag として登録します
None: 登録しません
tag: 登録します
n:tag 登録します。登録時、タグ名の頭に "n:" をつけ、通常のプロンプトの tag と判別できるようにします |
38 | | Use prompt parser when save prompt to eagle as tags | プロンプトを Eagle の tag として登録する場合に、 prompt_parser を利用して強調を除外します |
39 | | Additinal tag pattern | Generation info の3行目に表示されている項目について、タグ化する項目を選択できます

使用可能な設定は以下の通りです。
```Steps,Sampler,CFG scale,Seed,Face restoration,Size,Model hash,Model,Hypernet,Hypernet strength,Variation seed,Variation seed strength,Seed resize from,Denoising strength,Conditional mask weight,Eta,Clip skip,ENSD``` |
40 | | Outside Eagle server connection (url:port) | URL:Portを用いて、公開されている Eagle サーバへ画像を送信する設定です |
41 | | FolderID or FolderName on Eagle (option) | Eagle 上での画像保存先フォルダを、FolderID または Folder名で設定できます |
42 | | Allow to crete folder on Eagle, if specified foldername dont exists. | (option) 指定された名前のフォルダが存在しない場合に、新しくその名前のフォルダを作成することを許可する設定です |
43 |
44 | ## 設定サンプル
45 |
46 | | Settings | Result | Comment |
47 | | --------------------- | --------------------- | --------------------------------------------------------------------------------------------------- |
48 | |  | No output to Eagle | |
49 | |  |  | 画像を Eagle へ送ります |
50 | |  |  | 画像を Eagle へ送ります
Generation info 付き |
51 | |  |  | 画像を Eagle へ送ります
Generation info, tag, positive prompt 付き |
52 | |  |  | 画像を Eagle へ送ります
Generation info, tag, negative prompt 付き |
53 | |  |  | 画像を Eagle へ送ります
Generation info, tag, negative prompt 付き。< i.e.) n:bad anatomy |
54 | |  |  | 画像を Eagle へ送ります
Generation info, tag, positive prompt, negative prompt 付き。< i.e.) n:bad anatomy |
55 |
56 | ### 設定サンプル: フォルダへの保存(IDで指定)
57 |
58 | | Settings | Result | Comment |
59 | | --------------------- | --------------------- | ------------------------------------------------------------------------- |
60 | |  | | folderID を入れておくと、対象のフォルダに画像が格納されます |
61 | |  |  | "Eagle forlderID" を取得するには、Eagle UI で対象のフォルダを右クリックし、"リンクをコピー" を選択 |
62 | |  | | folderID の悪い例
コピーしたものをそのまま入力すると左画像のように長いパス名が入っていますが、必要になるのは右端の文字列のみです |
63 |
64 | ### 設定サンプル: フォルダへの保存(フォルダ名で指定)
65 |
66 | 
67 |
68 | - 画像を保存したいフォルダ名を入力してください
69 | - 上記のサンプルでは `eagle_inbox` を指定しています
70 |
71 | ### 設定サンプル: ネットワーク越しで Eagle Server へ保存
72 |
73 | 
74 |
75 | - Eagle サーバへアクセス出来るようにする必要があります
76 | - サーバアドレスを以下の形式で入力してください ```http://:```
77 | - 使用しない場合は、空白にしてください
78 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Eagle-pnginfo
2 |
3 | 
4 |
5 | [日本語 README はこちら](README.ja.md)
6 |
7 | - This is Extension for [AUTOMATIC1111's Stable Diffusion Web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui)
8 | - Send your creation image to [Eagle](https://jp.eagle.cool/) (image management software) with Generation info, tags.
9 |
10 | ## How to Install
11 |
12 | - Go to `Extensions` tab on your web UI
13 |
14 | - `Install from URL` with this repo URL
15 |
16 | - Install
17 |
18 | - Install [Eagle]([https://jp.eagle.cool/](https://jp.eagle.cool/))
19 |
20 | ## How to use
21 |
22 | - Enable this extension in "Setting"
23 |
24 | - Start "Eagle" application
25 |
26 | - Open "AUTO1111" and create image as usual.
27 |
28 | - images sent to "Eagle" automatically
29 |
30 | ## About Setting params
31 |
32 | | In "Setting" tab |  |
33 | | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
34 | | "Send all image to Eagle" | Enable this extension |
35 | | "Save Generation info as Annotation" | Save PNGinfo style text to "memo" on Eagle |
36 | | "Save positive prompt to Eagle as tags" | Save each prompt word as tag on Eagle |
37 | | "Save negative prompt to Eagle as" | Save each negative prompt word as tag on Eagle
None : disabled
tag : normal tag. i.e.) "bad anatomy"
n:tag : tag with "n:". i.e.)"n:bad annatomy" |
38 | | "Use prompt parser when save prompt to eagle as tags" | Use prompt parser when save prompts |
39 | | Additinal tag pattern | Add tags about Generation info params.

Usable word is listed,
```Steps,Sampler,CFG scale,Seed,Face restoration,Size,Model hash,Model,Hypernet,Hypernet strength,Variation seed,Variation seed strength,Seed resize from,Denoising strength,Conditional mask weight,Eta,Clip skip,ENSD``` |
40 | | Outside Eagle server connection (url:port) | (Default: http://localhost:41595)
URL:port as Eagle server address.
```http://:``` |
41 | | FolderID or FolderName on Eagle (option) | (option) Specify folder by ID on Eagle to input images |
42 | | Allow to crete folder on Eagle, if specified foldername dont exists. | (option) Allow create new folder with specified name, when folder with this name dont exists. |
43 |
44 | ## Setting sample
45 |
46 | | Settings | Result | Comment |
47 | | --------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------ |
48 | |  | No output to Eagle | |
49 | |  |  | Image sent to Eagle, only with filename. |
50 | |  |  | Image sent to Eagle with Generation info. |
51 | |  |  | Image sent to Eagle, with Generation info, tags from positive prompt |
52 | |  |  | Image sent to Eagle, with Generation info, tags from negative prompt |
53 | |  |  | Image sent to Eagle, with Generation info, tags from negative prompt decorated with "n:".
i.e.) n:bad anatomy |
54 | |  |  | Image sent to Eagle, with Generation info, tags from positive prompt and negative prompt decorated with "n:". |
55 |
56 | ### Setting sample: Save to folder (by ID)
57 |
58 | | Settings | Result | Comment |
59 | | --------------------- | --------------------- | -------------------------------------------------------------------------------------- |
60 | |  | | Input folderID |
61 | |  |  | .You can get "Eagle forlderID" on Eagle UI. Right click folder and select "copy link". |
62 | |  | | Bad sample of folderID.
Only right-end value required. |
63 |
64 | ### Setting sample: Save to folder (by Name)
65 |
66 | 
67 |
68 | - Input folder Name on Eagle
69 |
70 | - Example is `eagle_inbox`
71 |
72 | - Image will saved in this folder
73 |
74 | ### Setting sample: Save to (not local) Eagle server
75 |
76 | 
77 |
78 | - Run your Eagle server public, and note server IP and Port
79 | - Input address of your server as ```http://:```
80 |
--------------------------------------------------------------------------------
/misc/sss00.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss00.png
--------------------------------------------------------------------------------
/misc/sss01-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss01-1.png
--------------------------------------------------------------------------------
/misc/sss01-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss01-2.png
--------------------------------------------------------------------------------
/misc/sss02-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss02-1.png
--------------------------------------------------------------------------------
/misc/sss02-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss02-2.png
--------------------------------------------------------------------------------
/misc/sss03-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss03-1.png
--------------------------------------------------------------------------------
/misc/sss03-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss03-2.png
--------------------------------------------------------------------------------
/misc/sss04-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss04-1.png
--------------------------------------------------------------------------------
/misc/sss04-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss04-2.png
--------------------------------------------------------------------------------
/misc/sss05-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss05-1.png
--------------------------------------------------------------------------------
/misc/sss05-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss05-2.png
--------------------------------------------------------------------------------
/misc/sss06-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss06-1.png
--------------------------------------------------------------------------------
/misc/sss06-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss06-2.png
--------------------------------------------------------------------------------
/misc/sss07-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss07-1.png
--------------------------------------------------------------------------------
/misc/sss07-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss07-2.png
--------------------------------------------------------------------------------
/misc/sss07-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss07-3.png
--------------------------------------------------------------------------------
/misc/sss07-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss07-4.png
--------------------------------------------------------------------------------
/misc/sss09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss09.png
--------------------------------------------------------------------------------
/misc/sss10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss10.png
--------------------------------------------------------------------------------
/misc/sss11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss11.png
--------------------------------------------------------------------------------
/misc/sss12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss12.png
--------------------------------------------------------------------------------
/misc/sss_top.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bbc-mc/sdweb-eagle-pnginfo/15e67fd152c176e87eee14a52e720959a53fd5f6/misc/sss_top.png
--------------------------------------------------------------------------------
/scripts/eagle-pnginfo.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import gradio as gr
4 |
5 | from modules import paths, script_callbacks, shared
6 |
7 | from scripts.eagleapi import api_application
8 | from scripts.eagleapi import api_item
9 | from scripts.eagleapi import api_util
10 | from scripts.parser import Parser
11 | from scripts.tag_generator import TagGenerator
12 |
13 | DEBUG = False
14 | def dprint(str):
15 | if DEBUG:
16 | print(str)
17 |
18 | path_root = paths.script_path
19 |
20 | def on_ui_settings():
21 | # flg: Enable/Disable
22 | shared.opts.add_option("enable_eagle_integration", shared.OptionInfo(False, "Send all image to Eagle", section=("eagle_pnginfo", "Eagle Pnginfo")))
23 | # flg: save generation info to annotation
24 | shared.opts.add_option("save_generationinfo_to_eagle_as_annotation", shared.OptionInfo(False, "Save Generation info as Annotation", section=("eagle_pnginfo", "Eagle Pnginfo")))
25 | # flg: save positive prompt to tags
26 | shared.opts.add_option("save_positive_prompt_to_eagle_as_tags", shared.OptionInfo(False, "Save positive prompt to Eagle as tags", section=("eagle_pnginfo", "Eagle Pnginfo")))
27 | shared.opts.add_option("save_negative_prompt_to_eagle_as", shared.OptionInfo("n:tag", "Save negative prompt as", gr.Radio, {"choices": ["None", "tag", "n:tag"]}, section=("eagle_pnginfo", "Eagle Pnginfo")))
28 | shared.opts.add_option("use_prompt_parser_when_save_prompt_to_eagle_as_tags", shared.OptionInfo(False, "Use prompt parser when save prompt to eagle as tags", section=("eagle_pnginfo", "Eagle Pnginfo")))
29 | # txt: Additinal tags
30 | shared.opts.add_option("additional_tags_to_eagle", shared.OptionInfo("", "Additinal tag pattern", section=("eagle_pnginfo", "Eagle Pnginfo")))
31 | # txt: server_url
32 | shared.opts.add_option("outside_server_url_port", shared.OptionInfo("", "Outside Eagle server connection (url:port)", section=("eagle_pnginfo", "Eagle Pnginfo")))
33 | # specify Eagle folderID
34 | shared.opts.add_option("save_to_eagle_folderid", shared.OptionInfo("", "(option) FolderID or FolderName on Eagle", component_args=shared.hide_dirs, section=("eagle_pnginfo", "Eagle Pnginfo")))
35 | # specify Eagle folderID
36 | shared.opts.add_option("allow_to_create_folder_on_eagle", shared.OptionInfo(False, "(option) Allow to create folder on Eagle, if specified foldername dont exists.", section=("eagle_pnginfo", "Eagle Pnginfo")))
37 |
38 | # image saved callback
39 | def on_image_saved(params:script_callbacks.ImageSaveParams):
40 | if not shared.opts.enable_eagle_integration:
41 | dprint(f"DEBUG:on_image_saved: DISABLED")
42 | else:
43 | dprint(f"DEBUG:on_image_saved: ENABELD. enable_eagle_pnginfo is true.")
44 | # collect info
45 | fullfn = os.path.join(path_root, params.filename)
46 | info = params.pnginfo.get('parameters', None)
47 | filename = os.path.splitext(os.path.basename(fullfn))[0]
48 | #
49 | pos_prompt = params.p.prompt
50 | neg_prompt = params.p.negative_prompt
51 | #
52 | annotation = None
53 | tags = []
54 | if shared.opts.save_generationinfo_to_eagle_as_annotation:
55 | annotation = info
56 | if shared.opts.save_positive_prompt_to_eagle_as_tags:
57 | if len(pos_prompt.split(",")) > 0:
58 | tags += Parser.prompt_to_tags(pos_prompt)
59 | if shared.opts.save_negative_prompt_to_eagle_as == "tag":
60 | if len(neg_prompt.split(",")) > 0:
61 | tags += Parser.prompt_to_tags(neg_prompt)
62 | elif shared.opts.save_negative_prompt_to_eagle_as == "n:tag":
63 | if len(neg_prompt.split(",")) > 0:
64 | tags += [ f"n:{x}" for x in Parser.prompt_to_tags(neg_prompt) ]
65 | if shared.opts.additional_tags_to_eagle != "":
66 | gen = TagGenerator(p=params.p, image=params.image)
67 | _tags = gen.generate_from_p(shared.opts.additional_tags_to_eagle)
68 | if _tags and len(_tags) > 0:
69 | tags += _tags
70 |
71 | def _get_folderId(folder_name_or_id, allow_create_new_folder, server_url="http://localhost", port=41595):
72 | _ret = api_util.find_or_create_folder(folder_name_or_id, allow_create_new_folder, server_url, port)
73 | return _ret
74 |
75 | # send to Eagle
76 | if shared.opts.outside_server_url_port != "" and api_application.is_valid_url_port(shared.opts.outside_server_url_port):
77 | # send by URL
78 | dprint("DEBUG: try to send URL")
79 | item = api_item.EAGLE_ITEM_URL(
80 | url=fullfn,
81 | name=filename,
82 | tags=tags,
83 | annotation=annotation
84 | )
85 | server_url, port = api_util.get_url_port(shared.opts.outside_server_url_port)
86 | folderId = _get_folderId(shared.opts.save_to_eagle_folderid, shared.opts.allow_to_create_folder_on_eagle, server_url=server_url, port=port)
87 | _ret = api_item.add_from_URL_base64(
88 | item,
89 | folderId=folderId,
90 | server_url=server_url,
91 | port=port
92 | )
93 | else:
94 | # send to local
95 | dprint("DEBUG: try to send local")
96 | item = api_item.EAGLE_ITEM_PATH(
97 | filefullpath=fullfn,
98 | filename=filename,
99 | annotation=annotation,
100 | tags=tags
101 | )
102 | folderId = _get_folderId(shared.opts.save_to_eagle_folderid, shared.opts.allow_to_create_folder_on_eagle)
103 | _ret = api_item.add_from_path(
104 | item=item,
105 | folderId=folderId
106 | )
107 | dprint(f"DEBUG: {_ret}")
108 | dprint(f" content :{_ret.content}")
109 | dprint(f" status_code:{_ret.status_code}")
110 |
111 | # on_image_saved
112 | script_callbacks.on_image_saved(on_image_saved)
113 |
114 | #
115 | script_callbacks.on_ui_settings(on_ui_settings)
116 |
--------------------------------------------------------------------------------
/scripts/eagleapi/api_application.py:
--------------------------------------------------------------------------------
1 | # seealso: https://api.eagle.cool/application/info
2 | #
3 | import requests
4 |
5 | from . import api_util
6 |
7 | def info(server_url="http://localhost", port=41595, timeout_connect=3, timeout_read=10):
8 | """EAGLE API:/api/application/info
9 |
10 | Returns:
11 | Response: return of requests.post
12 | """
13 |
14 | API_URL = f"{server_url}:{port}/api/application/info"
15 |
16 | try:
17 | r_get = requests.get(API_URL, timeout=(timeout_connect, timeout_read))
18 | except requests.exceptions.Timeout as e:
19 | print("Error: api_application.info")
20 | print(e)
21 | return
22 |
23 | return r_get
24 |
25 | #
26 | # Support function
27 | #
28 | def is_alive(server_url="http://localhost", port=41595, timeout_connect=3, timeout_read=10):
29 | if not port or type(port) != int or port == "":
30 | port=41595
31 | try:
32 | r_get = info(server_url, port, timeout_connect, timeout_read)
33 | except Exception as e:
34 | print("Error: api_application.is_alive")
35 | print(e)
36 | return False
37 | try:
38 | r_get.raise_for_status()
39 | return True
40 | except:
41 | return False
42 |
43 | def is_valid_url_port(server_url_port="", timeout_connect=3, timeout_read=3):
44 | if not server_url_port or server_url_port == "":
45 | return False
46 | server_url, port = api_util.get_url_port(server_url_port)
47 | if not server_url or not port:
48 | return False
49 | if not is_alive(server_url=server_url, port=port, timeout_connect=timeout_connect, timeout_read=timeout_read):
50 | return False
51 | return True
52 |
--------------------------------------------------------------------------------
/scripts/eagleapi/api_folder.py:
--------------------------------------------------------------------------------
1 | # see also https://api.eagle.cool/folder/list
2 | #
3 | import requests
4 | import sys
5 |
6 | from . import api_util
7 |
8 | def create(newfoldername, server_url="http://localhost", port=41595, allow_duplicate_name=True, timeout_connect=3, timeout_read=10):
9 | """EAGLE API:/api/folder/list
10 |
11 | Method: POST
12 |
13 | Returns:
14 | list(response dict): return list of response.json()
15 | """
16 | API_URL = f"{server_url}:{port}/api/folder/create"
17 |
18 | def _init_data(newfoldername):
19 | _data = {}
20 | if newfoldername and newfoldername != "":
21 | _data.update({"folderName": newfoldername})
22 | return _data
23 | data = _init_data(newfoldername)
24 |
25 | # check duplicate if needed
26 | if not allow_duplicate_name:
27 | r_post = list()
28 | _ret = api_util.findFolderByName(r_post, newfoldername)
29 | if _ret != None or len(_ret) > 0:
30 | print(f"ERROR: create folder with same name is forbidden by option. [eagleapi.folder.create] foldername=\"{newfoldername}\"", file=sys.stderr)
31 | return
32 |
33 | r_post = requests.post(API_URL, json=data, timeout=(timeout_connect, timeout_read))
34 | return r_post
35 |
36 |
37 | def rename(folderId, newName, server_url="http://localhost", port=41595, timeout_connect=3, timeout_read=10):
38 | """EAGLE API:/api/folder/rename
39 |
40 | Method: POST
41 |
42 | Returns:
43 | list(response dict): return list of response.json()
44 | """
45 | data = {
46 | "folderId": folderId,
47 | "newName": newName
48 | }
49 | API_URL = f"{server_url}:{port}/api/folder/rename"
50 | r_post = requests.post(API_URL, json=data, timeout=(timeout_connect, timeout_read))
51 | return r_post
52 |
53 |
54 | def list(server_url="http://localhost", port=41595, timeout_connect=3, timeout_read=10):
55 | """EAGLE API:/api/folder/list
56 |
57 | Method: GET
58 |
59 | Returns:
60 | Response: return of requests.post
61 | """
62 |
63 | API_URL = f"{server_url}:{port}/api/folder/list"
64 |
65 | r_get = requests.get(API_URL, timeout=(timeout_connect, timeout_read))
66 |
67 | return r_get
68 |
--------------------------------------------------------------------------------
/scripts/eagleapi/api_item.py:
--------------------------------------------------------------------------------
1 | # seealso: https://api.eagle.cool/item/add-from-path
2 | # seealso: https://api.eagle.cool/item/add-from-paths
3 | #
4 | import requests
5 | import os
6 |
7 | import base64
8 |
9 | DEBUG = False
10 | def dprint(str):
11 | if DEBUG:
12 | print(str)
13 |
14 | class EAGLE_ITEM_PATH:
15 | def __init__(self, filefullpath, filename="", website="", tags:list=[], annotation=""):
16 | """Data container for addFromPath, addFromPaths
17 |
18 | Args:
19 | filefullpath : Required, full path of the local files.
20 | filename : (option), name of image to be added.
21 | website : (option), address of the source of the image.
22 | tags (list) : (option), tags for the image.
23 | annotation : (option), annotation for the image.
24 | """
25 | self.filefullpath = filefullpath
26 | self.filename = filename
27 | self.website = website
28 | self.tags = tags
29 | self.annotation = annotation
30 |
31 | def output_data(self):
32 | """
33 | output data in json format for POST
34 | """
35 | _data = {
36 | "path": self.filefullpath,
37 | "name": os.path.splitext(os.path.basename(self.filefullpath))[0] if (self.filename == None or self.filename == "") else self.filename
38 | }
39 | if self.website and self.website != "":
40 | _data.update({"website": self.website})
41 | if self.tags and len(self.tags) > 0:
42 | _data.update({"tags": self.tags})
43 | if self.annotation and self.annotation != "":
44 | _data.update({"annotation": self.annotation})
45 | return _data
46 |
47 |
48 | class EAGLE_ITEM_URL:
49 | def __init__(self, url, name, website="", tags=[], annotation="", modificationTime="", folderId="", headers={}):
50 | """Data container for addFromURL, addFromURLs
51 | url : Required, the URL of the image to be added. Supports http, https, base64
52 | name : Required, The name of the image to be added.
53 | website : The Address of the source of the image
54 | tags: Tags for the image.
55 | annotation: The annotation for the image.
56 | modificationTime: The creation date of the image. The parameter can be used to alter the image's sorting order in Eagle.
57 | headers: Optional, customize the HTTP headers properties, this could be used to circumvent the security of certain websites.
58 |
59 | folderId: If this parameter is defined, the image will be added to the corresponding folder.
60 | """
61 | self.url = url
62 | self.name = name
63 | self.website = website
64 | self.tags = tags
65 | self.annotation = annotation
66 | self.modificationTime = modificationTime
67 | self.folderId = folderId
68 | self.headers = headers
69 |
70 | def convert_file_to_base64url(self, filepath=None):
71 | if (not filepath or filepath == ""):
72 | if self.url and self.url != "":
73 | filepath = self.url
74 | else:
75 | print("Error convert_file_to_base64url: invalid filepath")
76 | return filepath
77 | else:
78 | self.url = filepath
79 | if not os.path.exists(filepath):
80 | print("Error convert_file_to_base64url: file not found.")
81 | return filepath
82 | try:
83 | with open(filepath, "rb") as file:
84 | enc_file = base64.urlsafe_b64encode(file.read())
85 | self.url = f"data:image/png;base64, {enc_file.decode('utf-8')}"
86 | except Exception as e:
87 | print("Error convert_file_to_base64url: eocode failed")
88 | print(e)
89 | return filepath
90 |
91 | return self.url
92 |
93 | def output_data(self):
94 | """
95 | output data in json format for POST
96 | """
97 | _data = {
98 | "url": self.url,
99 | "name": self.name
100 | }
101 | # add optional data
102 | if self.website and self.website != "":
103 | _data.update({"website": self.website})
104 | if self.tags and len(self.tags) > 0:
105 | _data.update({"tags": self.tags})
106 | if self.annotation and self.annotation != "":
107 | _data.update({"annotation": self.annotation})
108 | if self.modificationTime and self.modificationTime != "":
109 | _data.update({"modificationTime": self.modificationTime})
110 | if self.folderId and self.folderId != "":
111 | _data.update({"folderId": self.folderId})
112 | if self.headers and len(self.headers) > 0:
113 | _data.update({"headers": self.headers})
114 | return _data
115 |
116 |
117 | def add_from_URL(item:EAGLE_ITEM_URL, folderId=None, server_url="http://localhost", port=41595):
118 | API_URL = f"{server_url}:{port}/api/item/addFromURL"
119 | _data = item.output_data()
120 | if folderId and folderId != "":
121 | _data.update({"folderId": folderId})
122 | r_post = requests.post(API_URL, json=_data)
123 | return r_post
124 |
125 |
126 | def add_from_URL_base64(item:EAGLE_ITEM_URL, folderId=None, server_url="http://localhost", port=41595):
127 | API_URL = f"{server_url}:{port}/api/item/addFromURL"
128 | item.url = item.convert_file_to_base64url()
129 | _data = item.output_data()
130 | if folderId and folderId != "":
131 | _data.update({"folderId": folderId})
132 | r_post = requests.post(API_URL, json=_data)
133 | return r_post
134 |
135 |
136 | def add_from_path(item:EAGLE_ITEM_PATH, folderId=None, server_url="http://localhost", port=41595):
137 | API_URL = f"{server_url}:{port}/api/item/addFromPath"
138 | _data = item.output_data()
139 | if folderId and folderId != "":
140 | _data.update({"folderId": folderId})
141 | r_post = requests.post(API_URL, json=_data)
142 | return r_post
143 |
144 |
145 | def add_from_paths(files, folderId=None, server_url="http://localhost", port=41595, step=None):
146 | """EAGLE API:/api/item/addFromPaths
147 |
148 | Method: POST
149 |
150 | Args:
151 | path: Required, the path of the local files.
152 | name: Required, the name of images to be added.
153 | website: The Address of the source of the images.
154 | annotation: The annotation for the images.
155 | tags: Tags for the images.
156 | folderId: If this parameter is defined, the image will be added to the corresponding folder.
157 | step: interval image num of doing POST. Defaults is None (disabled)
158 |
159 | Returns:
160 | Response: return of requests.posts
161 | """
162 | API_URL = f"{server_url}:{port}/api/item/addFromPaths"
163 |
164 | if step:
165 | step = int(step)
166 |
167 | def _init_data():
168 | _data = {"items": []}
169 | if folderId and folderId != "":
170 | _data.update({"folderId": folderId})
171 | return _data
172 |
173 | r_posts = []
174 | data = _init_data()
175 | for _index, _item in enumerate(files):
176 | _item:EAGLE_ITEM_PATH = _item
177 | _data = _item.output_data()
178 | if _data:
179 | data["items"].append(_data)
180 | if step and step > 0:
181 | if ((_index + 1) - ((_index + 1) // step) * step) == 0:
182 | _ret = requests.post(API_URL, json=data)
183 | try:
184 | r_posts.append(_ret.json())
185 | except:
186 | r_posts.append(_ret)
187 | data = _init_data()
188 | if (len(data["items"]) > 0) or (not step or step <= 0):
189 | _ret = requests.post(API_URL, json=data)
190 | try:
191 | r_posts.append(_ret.json())
192 | except:
193 | r_posts.append(_ret)
194 |
195 | return [ x for x in r_posts if x != "" ]
196 |
--------------------------------------------------------------------------------
/scripts/eagleapi/api_util.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import ipaddress
3 | from urllib.parse import urlparse
4 |
5 | from . import api_folder
6 |
7 | def get_url_port(server_url_port=""):
8 | if not server_url_port or server_url_port == "":
9 | return None, None
10 | o = urlparse(server_url_port)
11 | _url = f"http://{o.hostname}"
12 | if o.hostname != "localhost":
13 | _ip = ipaddress.ip_address(o.hostname)
14 | if _ip.version == 6:
15 | _url = f"http://[{o.hostname}]"
16 | port = o.port
17 | return _url, port
18 |
19 | # util for /api/folder/list
20 | def findFolderByID(r_posts, target_id):
21 | return findFolderByName(r_posts, target_id, findByID=True)
22 |
23 | def findFolderByName(r_posts, target_name, findByID=False):
24 | _ret = []
25 | if not target_name or target_name == "" or not r_posts:
26 | return None
27 | _all_folder = getAllFolder(r_posts)
28 | for _data in _all_folder:
29 | if (findByID and _data.get("id", "") == target_name) or (_data.get("name", "") == target_name):
30 | _ret = _data
31 | break
32 | return _ret
33 |
34 | def getAllFolder(r_posts):
35 | """ get dict of {"folderId": _data, ..."""
36 | def dig_folder(data, dig_count, dig_limit=10):
37 | dig_count+=1
38 | if(dig_count>dig_limit):
39 | return []
40 | _ret = [data]
41 | if "children" in data and len(data["children"]) > 0:
42 | for _child in data["children"]:
43 | _ret += dig_folder(_child, dig_count)
44 | return _ret
45 | _ret = []
46 | if not r_posts:
47 | return None
48 | _posts = r_posts.json()
49 | if not _posts or "status" not in _posts or _posts["status"] != "success":
50 | return None
51 | if "data" in _posts and len(_posts["data"]) > 0:
52 | for _data in _posts["data"]:
53 | _ret += dig_folder(_data, 0)
54 | return _ret
55 |
56 | #
57 | # Support functions
58 | #
59 |
60 | def print_response(_res:requests.Response):
61 | print(f"status code : {_res.status_code}")
62 | print(f"headers : {_res.headers}")
63 | print(f"text : {_res.text}")
64 | print(f"encoding : {_res.encoding}")
65 | print(f"cookies : {_res.cookies}")
66 |
67 | print(f"content : {_res.content}")
68 | print(f"content decode: {_res.content.decode(encoding=_res.apparent_encoding)}")
69 |
70 | def get_json_from_response(_res:requests.Response):
71 | try:
72 | _result = _res.json()
73 | return _result
74 | except:
75 | return _res
76 |
77 | def find_or_create_folder(folder_name_or_id, allow_create_new_folder=False, server_url="http://localhost", port=41595, timeout_connect=3, timeout_read=10):
78 | """
79 | Find or Create folder on Eagle, by folderId or FolderName
80 |
81 | Args:
82 | folder_name_or_id (str):
83 | allow_create_new_folder (bool, optional): if True, create new folder on Eagle. Defaults to False.
84 | server_url (str, optional): Defaults to "http://localhost".
85 | port (int, optional): Defaults to 41595.
86 | timeout_connect (int, optional): Defaults to 3.
87 | timeout_read (int, optional): Defaults to 10.
88 | Return:
89 | folderId or ""
90 |
91 | """
92 | _eagle_folderid = ""
93 | if folder_name_or_id and folder_name_or_id !="":
94 | _ret_folder_list = api_folder.list(server_url=server_url, port=port, timeout_connect=timeout_connect, timeout_read=timeout_read)
95 |
96 | # serach by name
97 | _ret = findFolderByName(_ret_folder_list, folder_name_or_id)
98 | if _ret and len(_ret) > 0:
99 | _eagle_folderid = _ret.get("id", "")
100 | # serach by ID
101 | if _eagle_folderid == "":
102 | _ret = findFolderByID(_ret_folder_list, folder_name_or_id)
103 | if _ret and len(_ret) > 0:
104 | _eagle_folderid = _ret.get("id", "")
105 | if _eagle_folderid == "":
106 | if allow_create_new_folder: # allow new
107 | _r_get = api_folder.create(folder_name_or_id, server_url=server_url, port=port, timeout_connect=timeout_connect, timeout_read=timeout_read)
108 | try:
109 | _eagle_folderid = _r_get.json().get("data").get("id")
110 | except:
111 | _eagle_folderid = ""
112 | return _eagle_folderid
113 |
--------------------------------------------------------------------------------
/scripts/parser.py:
--------------------------------------------------------------------------------
1 | from modules import prompt_parser, shared
2 |
3 | class Parser:
4 | def prompt_to_tags(prompt):
5 | use_prompt_parser = shared.opts.use_prompt_parser_when_save_prompt_to_eagle_as_tags
6 |
7 | p = prompt
8 | if use_prompt_parser:
9 | p = ','.join(map(lambda x: x[0].strip(), prompt_parser.parse_prompt_attention(p)))
10 |
11 | return [ x.strip() for x in p.split(",") if x.strip() != "" ]
12 |
--------------------------------------------------------------------------------
/scripts/tag_generator.py:
--------------------------------------------------------------------------------
1 | from modules import shared
2 |
3 |
4 | class TagGenerator():
5 | # @seealso modules.images FilenameGenerator replacements
6 | # @seealso modules.processing create_infotext generation_params
7 | replacements ={
8 | "Steps": lambda self: self.p.steps,
9 | "Sampler": lambda self: self.p.sampler_name,
10 | "CFG scale": lambda self: self.p.cfg_scale,
11 | "Seed": lambda self: self.p.seed if self.p.seed is not None else '',
12 | "Face restoration": lambda self: (shared.opts.face_restoration_model if self.p.restore_faces else None),
13 | "Size": lambda self: f"{self.p.width}x{self.p.height}",
14 | "Model hash": lambda self: getattr(self.p, 'sd_model_hash', None if not shared.opts.add_model_hash_to_info or not shared.sd_model.sd_model_hash else shared.sd_model.sd_model_hash),
15 | "Model": lambda self: (None if not shared.opts.add_model_name_to_info or not shared.sd_model.sd_checkpoint_info.model_name else shared.sd_model.sd_checkpoint_info.model_name.replace(',', '').replace(':', '')),
16 | "Hypernet": lambda self: (None if shared.loaded_hypernetwork is None else shared.loaded_hypernetwork.name),
17 | "Hypernet strength": lambda self: (None if shared.loaded_hypernetwork is None or shared.opts.sd_hypernetwork_strength >= 1 else shared.opts.sd_hypernetwork_strength),
18 | "Variation seed": lambda self: (None if self.p.subseed_strength == 0 else self.p.seed),
19 | "Variation seed strength": lambda self: (None if self.p.subseed_strength == 0 else self.p.subseed_strength),
20 | "Seed resize from": lambda self: (None if self.p.seed_resize_from_w == 0 or self.p.seed_resize_from_h == 0 else f"{self.p.seed_resize_from_w}x{self.p.seed_resize_from_h}"),
21 | "Denoising strength": lambda self: getattr(self.p, 'denoising_strength', None),
22 | "Conditional mask weight": lambda self: getattr(self.p, "inpainting_mask_weight", shared.opts.inpainting_mask_weight) if self.p.is_using_inpainting_conditioning else None,
23 | "Eta": lambda self: (None if self.p.sampler is None or self.p.sampler.eta == self.p.sampler.default_eta else self.p.sampler.eta),
24 | "Clip skip": lambda self: None if getattr(self.p, 'clip_skip', shared.opts.CLIP_stop_at_last_layers) <= 1 else getattr(self.p, 'clip_skip', shared.opts.CLIP_stop_at_last_layers),
25 | "ENSD": lambda self: None if shared.opts.eta_noise_seed_delta == 0 else shared.opts.eta_noise_seed_delta
26 | }
27 |
28 | def __init__(self, p=None, image=None):
29 | self.p = p
30 | self.image = image
31 |
32 | def generate_from_geninfo(self, tags_to_eagle, geninfo):
33 | geninfo = geninfo.split("\n")
34 | if len(geninfo) == 3:
35 | geninfo = geninfo[2]
36 | else:
37 | return []
38 | # generate lists from geninfo. i.e) "Steps: 30, CFG scale: 7.5" -> ["Steps: 30", "CFG scale: 7.5"]
39 | geninfo_params = [ x.strip() for x in geninfo.split(",") if x.strip() != "" ]
40 | geninfo_dict = {} # {"Steps": "30", "CFG scale": "7.5"}
41 | for item in geninfo_params:
42 | geninfo_dict.update({item.split(":")[0]: item.split(":")[1].strip()})
43 | tag_list = [ x.strip() for x in tags_to_eagle.split(",") if x.strip() != "" ]
44 | _tags = [ f"{x}: {geninfo_dict.get(x)}" for x in geninfo_dict.keys() if x in tag_list ]
45 | return _tags
46 |
47 | def generate_from_p(self, tags_to_eagle):
48 | tag_list = [ x.strip() for x in tags_to_eagle.split(",") if x.strip() != "" ]
49 |
50 | tags = []
51 | for _tag in tag_list:
52 | if not _tag or _tag == "":
53 | continue
54 | func = self.replacements.get(_tag)
55 | if func:
56 | try:
57 | _tag_data = func(self)
58 | except Exception as e:
59 | print(e)
60 | _tag_data = ""
61 | if _tag_data:
62 | tags += [f"{_tag}: {_tag_data}"]
63 | tags = [ x for x in tags if x.strip() != "" ]
64 | return tags
65 |
--------------------------------------------------------------------------------