add covers generators

This commit is contained in:
pk
2026-03-10 10:07:32 +01:00
parent 7eb9648ead
commit 5c72e54df8
5 changed files with 237 additions and 26 deletions
+5 -3
View File
@@ -10,6 +10,7 @@ from .nodes.api.AQ_Gemini import (
AQ_Parse_JSON_to_acestep
)
from .nodes.api.AQ_SendImageToAPI import AQ_SendImageToAPI
from .nodes.api.AQ_Cover import AQ_CoverPrompt
from .nodes.clip.AQ_CLIPSetLastLayer import AQ_CLIPSetLastLayer
from .nodes.numbers.AQ_Numbers import AQ_Increment, AQ_Random
@@ -43,7 +44,8 @@ NODE_CLASS_MAPPINGS = {
"AQ_BatchAverageImage": AQ_BatchAverageImage,
"AQ_ColorMatchImage": AQ_ColorMatchImage,
"AQ_TextEncodeQwenImageEdit": AQ_TextEncodeQwenImageEdit,
"AQ_TextEncodeQwenImageEditPlus": AQ_TextEncodeQwenImageEditPlus
"AQ_TextEncodeQwenImageEditPlus": AQ_TextEncodeQwenImageEditPlus,
"AQ_CoverPrompt": AQ_CoverPrompt,
}
NODE_DISPLAY_NAME_MAPPINGS = {
@@ -69,8 +71,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"AQ_BatchAverageImage": "AQ_BatchAverageImage",
"AQ_ColorMatchImage": "AQ_ColorMatchImage",
"AQ_TextEncodeQwenImageEdit": "AQ_TextEncodeQwenImageEdit",
"AQ_TextEncodeQwenImageEditPlus": "AQ_TextEncodeQwenImageEditPlus"
"AQ_TextEncodeQwenImageEditPlus": "AQ_TextEncodeQwenImageEditPlus",
"AQ_CoverPrompt": "AQ_CoverPrompt",
}
+66
View File
@@ -0,0 +1,66 @@
import os
class AQ_CoverPrompt:
ASPECT_RATIO_NOTES = {
"16:9 YouTube": "optimized for 16:9 widescreen YouTube thumbnail format (1280x720)",
"1:1 Square": "optimized for 1:1 square format (1080x1080)",
"4:5 Portrait": "optimized for 4:5 portrait format (1080x1350)",
}
NEGATIVE_PROMPT = (
"blur, blurry, out of focus, low resolution, low quality, pixelated, "
"watermark, logo, signature, text errors, misspelled text, distorted letters, "
"distorted anatomy, disfigured, bad proportions, extra limbs, missing limbs, "
"bad lighting, flat lighting, overexposed, underexposed, washed out, "
"generic stock photo, clip art, cartoon (unless style selected), "
"oversaturated, noise, grain, compression artifacts"
)
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"cover_prompt": ("STRING", {"multiline": True, "default": ""}),
"title": ("STRING", {"multiline": False, "default": ""}),
"tagline": ("STRING", {"multiline": False, "default": ""}),
"headline": ("STRING", {"multiline": False, "default": ""}),
"tags": ("STRING", {"multiline": False, "default": ""}),
"keyscale": ("STRING", {"multiline": False, "default": "C major"}),
"style": (["cinematic photo", "digital illustration", "oil painting", "neon art", "minimalist", "watercolor", "3D render"], {"default": "cinematic photo"}),
"aspect_ratio": (["16:9 YouTube", "1:1 Square", "4:5 Portrait"], {"default": "16:9 YouTube"}),
},
"optional": {
"template_override": ("STRING", {"multiline": True, "default": ""}),
}
}
RETURN_TYPES = ("STRING", "STRING")
RETURN_NAMES = ("prompt", "negative_prompt")
FUNCTION = "compose"
CATEGORY = "Aquasite/LLM"
def compose(self, cover_prompt, title, tagline, headline, tags, keyscale, style, aspect_ratio, template_override=""):
if template_override and template_override.strip():
template = template_override
else:
template_path = os.path.join(
os.path.dirname(__file__), "..", "..", "prompts", "cover.md"
)
with open(template_path, "r", encoding="utf-8") as f:
template = f.read()
aspect_ratio_note = self.ASPECT_RATIO_NOTES.get(aspect_ratio, aspect_ratio)
prompt = template.format(
style=style,
aspect_ratio_note=aspect_ratio_note,
coverImage=cover_prompt,
title=title,
tagline=tagline,
headline=headline,
tags=tags,
keyscale=keyscale,
)
return (prompt, self.NEGATIVE_PROMPT)
+81 -23
View File
@@ -320,6 +320,9 @@ class AQ_Gemini_acstep15:
"COMBO",
"STRING",
"STRING",
"STRING",
"STRING",
"STRING",
)
RETURN_NAMES = (
"response_json",
@@ -332,6 +335,9 @@ class AQ_Gemini_acstep15:
"language",
"keyscale",
"coverImage",
"title",
"tagline",
"headline",
"instruction",
)
FUNCTION = "generate"
@@ -344,7 +350,7 @@ class AQ_Gemini_acstep15:
"- Uses Google Gemini API."
)
if not gemini_api_key:
return ("", "", "", seed, 120, 120.0, "4", "en", "C major", "", instruction)
return ("", "", "", seed, 120, 120.0, "4", "en", "C major", "", "", "", "", instruction)
json_schema = {
"type": "object",
@@ -358,7 +364,10 @@ class AQ_Gemini_acstep15:
"durationSeconds",
"timesignature",
"language",
"coverImage"
"coverImage",
"title",
"tagline",
"headline"
],
"properties": {
"description": {"type": "string"},
@@ -370,7 +379,10 @@ class AQ_Gemini_acstep15:
"durationSeconds": {"type": "number"},
"timesignature": {"type": "string"},
"language": {"type": "string"},
"coverImage": {"type": "string", "description": "Prompt for generating an album cover image for this song."}
"coverImage": {"type": "string", "description": "150-250 word rich art-direction prompt for an album cover image."},
"title": {"type": "string", "description": "Song title, 1-6 words."},
"tagline": {"type": "string", "description": "Catchy subtitle, 5-12 words, for YouTube thumbnail."},
"headline": {"type": "string", "description": "Bold thumbnail primary text, 3-8 words, under 30 chars."}
}
}
@@ -473,6 +485,9 @@ class AQ_Gemini_acstep15:
language = str(json_response.get("language", "en"))
keyscale = str(json_response.get("keyscale", "C major"))
coverImage = str(json_response.get("coverImage", ""))
title = str(json_response.get("title", ""))
tagline = str(json_response.get("tagline", ""))
headline = str(json_response.get("headline", ""))
response_json = json.dumps({
"description": json_response.get("description", ""),
@@ -484,10 +499,13 @@ class AQ_Gemini_acstep15:
"durationSeconds": duration,
"timesignature": timesignature,
"language": language,
"coverImage": coverImage
"coverImage": coverImage,
"title": title,
"tagline": tagline,
"headline": headline
})
return (response_json, tags, lyrics, seed_value, bpm, duration, timesignature, language, keyscale, coverImage, instruction)
return (response_json, tags, lyrics, seed_value, bpm, duration, timesignature, language, keyscale, coverImage, title, tagline, headline, instruction)
except Exception as e:
print(f"Error in Gemini API: {str(e)}")
raise e
@@ -592,6 +610,9 @@ class AQ_OpenAI_acstep15:
"COMBO",
"STRING",
"STRING",
"STRING",
"STRING",
"STRING",
)
RETURN_NAMES = (
"response_json",
@@ -604,6 +625,9 @@ class AQ_OpenAI_acstep15:
"language",
"keyscale",
"coverImage",
"title",
"tagline",
"headline",
"instruction",
)
FUNCTION = "generate"
@@ -618,10 +642,10 @@ class AQ_OpenAI_acstep15:
"- verbosity: none|low|medium|high (none omits).\n"
"- reasoning_effort: none|minimal|low|medium|high (none omits).\n"
"- response_json contains the raw JSON output (or error JSON).\n"
"- tags/lyrics/bpm/duration/timesignature/language/keyscale/coverImage are parsed from JSON."
"- tags/lyrics/bpm/duration/timesignature/language/keyscale/coverImage/title/tagline/headline are parsed from JSON."
)
if not openai_api_key:
return ("", "", "", seed, 120, 120.0, "4", "en", "C major", "", instruction)
return ("", "", "", seed, 120, 120.0, "4", "en", "C major", "", "", "", "", instruction)
schema = {
"type": "object",
@@ -666,7 +690,19 @@ class AQ_OpenAI_acstep15:
},
"coverImage": {
"type": "string",
"description": "Artistic prompt for generating an album cover visual for this song."
"description": "150-250 word rich art-direction prompt for an album cover image."
},
"title": {
"type": "string",
"description": "Song title, 1-6 words."
},
"tagline": {
"type": "string",
"description": "Catchy subtitle, 5-12 words, for YouTube thumbnail."
},
"headline": {
"type": "string",
"description": "Bold thumbnail primary text, 3-8 words, under 30 chars."
}
},
"required": [
@@ -678,7 +714,10 @@ class AQ_OpenAI_acstep15:
"durationSeconds",
"timesignature",
"language",
"coverImage"
"coverImage",
"title",
"tagline",
"headline"
],
"additionalProperties": False
}
@@ -790,6 +829,9 @@ class AQ_OpenAI_acstep15:
language = str(json_response.get("language", "en"))
keyscale = str(json_response.get("keyscale", "C major"))
coverImage = str(json_response.get("coverImage", ""))
title = str(json_response.get("title", ""))
tagline = str(json_response.get("tagline", ""))
headline = str(json_response.get("headline", ""))
response_json = json.dumps({
"tags": tags,
@@ -800,10 +842,13 @@ class AQ_OpenAI_acstep15:
"durationSeconds": duration,
"timesignature": timesignature,
"language": language,
"coverImage": coverImage
"coverImage": coverImage,
"title": title,
"tagline": tagline,
"headline": headline
})
return (response_json, tags, lyrics, seed_value, bpm, duration, timesignature, language, keyscale, coverImage, instruction)
return (response_json, tags, lyrics, seed_value, bpm, duration, timesignature, language, keyscale, coverImage, title, tagline, headline, instruction)
except Exception as e:
print(f"Error in OpenAI API: {str(e)}")
raise e
@@ -851,10 +896,10 @@ class AQ_OpenAI_Compatible_acstep15(AQ_OpenAI_acstep15):
"- Allows custom API endpoint."
)
if not openai_api_key:
return ("", "", "", seed, 120, 120.0, "4", "en", "C major", "", instruction)
return ("", "", "", seed, 120, 120.0, "4", "en", "C major", "", "", "", "", instruction)
model_name = custom_model if model == "custom" and custom_model else model
messages = []
if system_message:
messages.append({"role": "system", "content": system_message})
@@ -895,9 +940,12 @@ class AQ_OpenAI_Compatible_acstep15(AQ_OpenAI_acstep15):
"durationSeconds": {"type": "integer", "minimum": 1},
"timesignature": {"type": "string", "enum": ["2", "3", "4", "6"]},
"language": {"type": "string", "pattern": "^[a-z]{2}$", "minLength": 2, "maxLength": 2},
"coverImage": {"type": "string", "description": "Artistic prompt for generating an album cover visual for this song."}
"coverImage": {"type": "string", "description": "150-250 word rich art-direction prompt for an album cover image."},
"title": {"type": "string", "description": "Song title, 1-6 words."},
"tagline": {"type": "string", "description": "Catchy subtitle, 5-12 words, for YouTube thumbnail."},
"headline": {"type": "string", "description": "Bold thumbnail primary text, 3-8 words, under 30 chars."}
},
"required": ["tags", "lyrics", "seed", "bpm", "keyscale", "durationSeconds", "timesignature", "language", "coverImage"],
"required": ["tags", "lyrics", "seed", "bpm", "keyscale", "durationSeconds", "timesignature", "language", "coverImage", "title", "tagline", "headline"],
"additionalProperties": False
}
@@ -958,6 +1006,9 @@ class AQ_OpenAI_Compatible_acstep15(AQ_OpenAI_acstep15):
language = str(json_response.get("language", "en"))
keyscale = str(json_response.get("keyscale", "C major"))
coverImage = str(json_response.get("coverImage", ""))
title = str(json_response.get("title", ""))
tagline = str(json_response.get("tagline", ""))
headline = str(json_response.get("headline", ""))
response_json = json.dumps({
"tags": tags,
@@ -968,10 +1019,13 @@ class AQ_OpenAI_Compatible_acstep15(AQ_OpenAI_acstep15):
"durationSeconds": duration,
"timesignature": timesignature,
"language": language,
"coverImage": coverImage
"coverImage": coverImage,
"title": title,
"tagline": tagline,
"headline": headline
})
return (response_json, tags, lyrics, seed_value, bpm, duration, timesignature, language, keyscale, coverImage, instruction)
return (response_json, tags, lyrics, seed_value, bpm, duration, timesignature, language, keyscale, coverImage, title, tagline, headline, instruction)
except Exception as e:
print(f"Error in API: {str(e)}")
raise e
@@ -996,6 +1050,9 @@ class AQ_Parse_JSON_to_acestep:
"COMBO",
"COMBO",
"STRING",
"STRING",
"STRING",
"STRING",
)
RETURN_NAMES = (
"response_json",
@@ -1008,6 +1065,9 @@ class AQ_Parse_JSON_to_acestep:
"language",
"keyscale",
"coverImage",
"title",
"tagline",
"headline",
)
FUNCTION = "parse"
CATEGORY = "Aquasite/LLM"
@@ -1039,15 +1099,13 @@ class AQ_Parse_JSON_to_acestep:
language = str(data.get("language", "en"))
keyscale = str(data.get("keyscale", "C major"))
coverImage = str(data.get("coverImage", ""))
# Ensure it's a valid COMBO value for ComfyUI if needed,
# but usually RETURN_TYPES "COMBO" means it returns one of the values.
# We'll just return the strings as they are.
title = str(data.get("title", ""))
tagline = str(data.get("tagline", ""))
headline = str(data.get("headline", ""))
return (json_text, tags, lyrics, seed, bpm, duration, timesignature, language, keyscale, coverImage)
return (json_text, tags, lyrics, seed, bpm, duration, timesignature, language, keyscale, coverImage, title, tagline, headline)
except Exception as e:
print(f"Error parsing JSON in AQ_Parse_JSON_to_acestep: {str(e)}")
# Fail the node if parsing fails
raise e
def _coerce_int(self, value, default=0):
+37
View File
@@ -0,0 +1,37 @@
Create a stunning album/single cover image with the following specifications:
**Style:** {style}
**Format:** {aspect_ratio_note}
---
**Art Direction:**
{coverImage}
---
**Genre & Mood Context:**
Music style: {tags}
Key & scale: {keyscale}
Let the color palette, lighting, and visual atmosphere reflect this musical character.
---
**Typography — integrate the following text into the image design:**
- **Title** (bold, highly legible, prominent placement): {title}
- **Tagline** (smaller subtitle beneath or near the title): {tagline}
- **Headline** (largest, most dominant text element — this is the first thing the viewer reads): {headline}
Text must be sharp, high-contrast, and readable at thumbnail size (even at 320px wide).
Use a typeface and color that complement the visual style. Avoid script fonts that are hard to read at small sizes.
---
**Technical Requirements:**
- Sharp focus, high resolution feel
- High contrast — readable as a small YouTube thumbnail
- No watermarks, logos, or signatures
- No blurry or low-resolution textures
- No generic stock-photo feel
- Composition should work at 16:9, 1:1, and 4:5 crops if possible
+48
View File
@@ -0,0 +1,48 @@
# Song Generation System Prompt
You are a creative music production assistant. Given a user's song concept, generate a complete, cohesive set of music metadata fields. All fields must be thematically consistent with each other.
## Required Output Fields
Return ONLY valid JSON with the following fields:
- **description** — brief summary of the song concept (1-3 sentences)
- **tags** — style prompt: genre, instruments, mood, tempo, production style (comma-separated keywords or descriptive phrase)
- **lyrics** — complete song lyrics with structure tags: `[intro]`, `[verse]`, `[chorus]`, `[bridge]`, `[outro]`, `[instrumental]`; use `\n` for line breaks
- **seed** — integer seed for reproducibility
- **bpm** — tempo in beats per minute (integer, 1–300)
- **keyscale** — musical key and scale, e.g. "C major", "A minor"
- **durationSeconds** — target duration in seconds (integer, 30–600)
- **timesignature** — beats per measure: "2", "3", "4", or "6"
- **language** — ISO 639-1 two-letter code of primary lyric language (e.g. "en", "es", "fr")
- **coverImage** — 150–250 word rich art-direction prompt for an album cover image; describe composition, foreground/background elements, color palette, lighting setup, visual style (photography, illustration, painting, etc.), mood/atmosphere, and any symbolic imagery. Be specific and evocative.
- **title** — the song's name, 1–6 words, memorable and genre-appropriate
- **tagline** — catchy subtitle, 5–12 words, suitable for a YouTube thumbnail subtitle line
- **headline** — bold primary text for a YouTube thumbnail, 3–8 words, ideally under 30 characters, all-caps acceptable; this is the dominant visual text
## Consistency Guidelines
- `title`, `tagline`, and `headline` must reflect the same emotional core as the lyrics and tags
- `coverImage` must reference the song's mood, setting, and genre implied by `tags`
- `keyscale` should match the emotional tone: major for uplifting/happy, minor for emotional/dark
- `bpm` should align with the energy described in `tags`
## Example (structure only)
```json
{
"description": "...",
"tags": "...",
"lyrics": "[verse]\n...\n[chorus]\n...",
"seed": 42,
"bpm": 120,
"keyscale": "G major",
"durationSeconds": 180,
"timesignature": "4",
"language": "en",
"coverImage": "Wide cinematic shot of a lone figure standing on a rain-soaked city rooftop at dusk...",
"title": "Neon Rain",
"tagline": "When the city lights blur your tears away",
"headline": "NEON RAIN"
}
```