diff --git a/bots/prompt_layer/__init__.py b/bots/prompt_layer/__init__.py new file mode 100644 index 0000000..dfa7e3a --- /dev/null +++ b/bots/prompt_layer/__init__.py @@ -0,0 +1,43 @@ +""" +bots/prompt_layer/__init__.py +Unified entry point for all prompt composition. + +V3.0 scope: video + search + tts categories +V3.1+: expand to all categories +""" +from .base import ComposedPrompt +from .video_prompt import KlingPromptFormatter, VeoPromptFormatter +from .search_query import StockSearchQueryComposer + + +def compose(category: str, input_data: dict, engine: str) -> 'ComposedPrompt': + """ + Unified entry point for all prompt composition. + + category: 'video' | 'search' | 'tts' | 'image' | 'writing' | 'caption' + input_data: category-specific dict + engine: target engine name + + V3.0 scope: video + search only + V3.1+: expand to all categories + """ + composer = _get_composer(category, engine) + return composer.compose(input_data, engine) + + +def _get_composer(category: str, engine: str): + """Return appropriate composer for category+engine combination.""" + if category == 'video': + if engine in ('kling_free', 'kling_pro'): + return KlingPromptFormatter() + else: + return VeoPromptFormatter() + elif category == 'search': + return StockSearchQueryComposer() + else: + # Fallback: return a passthrough composer for unsupported categories + from .base import PassthroughComposer + return PassthroughComposer() + + +__all__ = ['compose', 'ComposedPrompt'] diff --git a/bots/prompt_layer/base.py b/bots/prompt_layer/base.py new file mode 100644 index 0000000..dab636d --- /dev/null +++ b/bots/prompt_layer/base.py @@ -0,0 +1,41 @@ +""" +bots/prompt_layer/base.py +Base types for the prompt layer. +""" +from dataclasses import dataclass, field +from typing import Optional + +@dataclass +class ComposedPrompt: + """ + Unified prompt container returned by all composers. + + Fields used varies by engine: + - Kling: positive + negative + - Veo: positive (structured) + - Search: queries list + - TTS: processed_text + """ + positive: str = '' + negative: str = '' + queries: list[str] = field(default_factory=list) + processed_text: str = '' + metadata: dict = field(default_factory=dict) + + def __bool__(self) -> bool: + return bool(self.positive or self.queries or self.processed_text) + + +class BaseComposer: + """Abstract base for all composers.""" + def compose(self, input_data: dict, engine: str) -> ComposedPrompt: + raise NotImplementedError + + +class PassthroughComposer(BaseComposer): + """Returns input as-is for unsupported categories.""" + def compose(self, input_data: dict, engine: str) -> ComposedPrompt: + return ComposedPrompt( + positive=input_data.get('text', ''), + metadata={'passthrough': True, 'engine': engine} + ) diff --git a/bots/prompt_layer/search_query.py b/bots/prompt_layer/search_query.py new file mode 100644 index 0000000..4281809 --- /dev/null +++ b/bots/prompt_layer/search_query.py @@ -0,0 +1,55 @@ +""" +bots/prompt_layer/search_query.py +Compose stock video/image search queries from Korean concepts. +""" +from .base import BaseComposer, ComposedPrompt +from .visual_vocabulary import CONCEPT_TO_VISUAL, VISUAL_STYLE_MODIFIERS +import re + + +class StockSearchQueryComposer(BaseComposer): + """ + Korean concept -> English visual search terms. + Used to search Pexels/Pixabay/Unsplash for stock footage. + """ + + def compose(self, input_data: dict, engine: str = 'pexels') -> ComposedPrompt: + """ + input_data: { + 'sentence': str, # Korean sentence to find visuals for + 'platform': str, # 'pexels' | 'pixabay' | 'kling' | 'veo' + 'count': int, # number of search queries to return (default 3) + } + Returns ComposedPrompt with queries list + """ + sentence = input_data.get('sentence', '') + count = input_data.get('count', 3) + + queries = self._sentence_to_queries(sentence, count) + + return ComposedPrompt( + queries=queries, + metadata={'sentence': sentence, 'engine': engine} + ) + + def _sentence_to_queries(self, sentence: str, count: int) -> list[str]: + """Extract Korean concepts from sentence and map to visual search terms.""" + # Find matching concepts from vocabulary + matched_visuals = [] + for concept, visuals in CONCEPT_TO_VISUAL.items(): + if concept in sentence: + matched_visuals.extend(visuals) + + # If no matches, use generic professional stock footage terms + if not matched_visuals: + matched_visuals = ['professional business', 'modern lifestyle', 'technology future'] + + # Return up to count unique queries + seen = set() + unique = [] + for v in matched_visuals: + if v not in seen: + seen.add(v) + unique.append(v) + + return unique[:count] diff --git a/bots/prompt_layer/video_prompt.py b/bots/prompt_layer/video_prompt.py new file mode 100644 index 0000000..1b45c88 --- /dev/null +++ b/bots/prompt_layer/video_prompt.py @@ -0,0 +1,85 @@ +""" +bots/prompt_layer/video_prompt.py +Format prompts for video generation engines (Kling, Veo). +""" +from .base import BaseComposer, ComposedPrompt +from .visual_vocabulary import VISUAL_STYLE_MODIFIERS, NEGATIVE_TERMS + + +class KlingPromptFormatter(BaseComposer): + """ + Format prompts for Kling AI video generation. + Kling works best with: scene description + movement + mood + negative prompt. + """ + + def compose(self, input_data: dict, engine: str = 'kling_free') -> ComposedPrompt: + """ + input_data: { + 'scenes': list[dict], # [{text, type, image_prompt}, ...] + 'corner': str, # content corner/category + 'duration': float, # target duration in seconds + } + """ + scenes = input_data.get('scenes', []) + corner = input_data.get('corner', '') + + # Build positive prompt from scenes + scene_texts = [] + for scene in scenes: + prompt = scene.get('image_prompt') or scene.get('text', '') + if prompt: + scene_texts.append(self._enhance_for_kling(prompt, corner)) + + positive = '. '.join(scene_texts[:3]) # Max 3 scenes per prompt + if not positive: + positive = f'cinematic short video about {corner or "technology"}' + + # Kling negative prompt + negative = ', '.join(NEGATIVE_TERMS + ['text overlay', 'subtitles', 'watermark']) + + # Add beat markers for Kling + positive = f'{positive}. Camera: smooth movement, vertical 9:16 format. Style: cinematic, vibrant.' + + return ComposedPrompt( + positive=positive, + negative=negative, + metadata={'engine': engine, 'corner': corner} + ) + + def _enhance_for_kling(self, text: str, corner: str) -> str: + """Add cinematic enhancement to prompt.""" + modifiers = ', '.join(VISUAL_STYLE_MODIFIERS[:3]) + return f'{text}, {modifiers}' + + +class VeoPromptFormatter(BaseComposer): + """ + Format prompts for Google Veo video generation. + Veo works best with structured ingredient list format. + """ + + def compose(self, input_data: dict, engine: str = 'veo3') -> ComposedPrompt: + """ + input_data: same as KlingPromptFormatter + """ + scenes = input_data.get('scenes', []) + corner = input_data.get('corner', '') + + scene_texts = [ + scene.get('image_prompt') or scene.get('text', '') + for scene in scenes if scene.get('image_prompt') or scene.get('text') + ] + + # Veo structured format: Subject + Action + Setting + Style + subject = scene_texts[0] if scene_texts else f'{corner or "technology"} concept' + positive = ( + f'Subject: {subject}. ' + f'Format: vertical 9:16 portrait video. ' + f'Style: cinematic, {", ".join(VISUAL_STYLE_MODIFIERS[:2])}. ' + f'Camera: smooth pan or zoom. Duration: short clip.' + ) + + return ComposedPrompt( + positive=positive, + metadata={'engine': engine, 'corner': corner, 'format': 'veo_structured'} + ) diff --git a/bots/prompt_layer/visual_vocabulary.py b/bots/prompt_layer/visual_vocabulary.py new file mode 100644 index 0000000..4c23408 --- /dev/null +++ b/bots/prompt_layer/visual_vocabulary.py @@ -0,0 +1,145 @@ +""" +bots/prompt_layer/visual_vocabulary.py +Shared Korean -> English visual concept dictionary. +Used by search_query.py and video_prompt.py for concept mapping. +""" + +CONCEPT_TO_VISUAL = { + # Technology + 'AI': ['artificial intelligence screen', 'digital interface', 'neural network visualization'], + '인공지능': ['robot brain', 'digital mind', 'AI hologram'], + '자동화': ['gears mechanism', 'conveyor belt', 'robot arm factory'], + '코딩': ['computer code screen', 'programmer keyboard', 'dark terminal code'], + '데이터': ['data visualization', 'bar chart analytics', 'network nodes'], + '알고리즘': ['flowchart diagram', 'binary code', 'decision tree'], + '앱': ['smartphone screen', 'mobile app interface', 'app store'], + '소프트웨어': ['software development', 'code editor', 'programming laptop'], + # Finance/Money + '돈': ['money cash bills', 'coins pile', 'dollar bills'], + '수익': ['profit growth chart', 'rising arrow money', 'income cash'], + '투자': ['stock market chart', 'investment portfolio', 'financial growth'], + '절약': ['piggy bank savings', 'money jar coins', 'budget planning'], + '부자': ['luxury lifestyle', 'wealthy business person', 'success achievement'], + '무료': ['gift present box', 'unlocked padlock', 'free tag label'], + '할인': ['sale discount tag', 'percent off sign', 'price reduction'], + # Business + '비즈니스': ['business meeting', 'office workspace', 'professional handshake'], + '창업': ['startup launch rocket', 'entrepreneur office', 'business idea lightbulb'], + '마케팅': ['marketing strategy board', 'social media icons', 'advertising billboard'], + '브랜드': ['brand logo design', 'brand identity', 'premium label'], + '고객': ['customer service smile', 'client meeting', 'happy customer'], + '성공': ['success achievement trophy', 'winner podium', 'goal celebration'], + '실패': ['failure mistake frustrated', 'broken plan', 'problem obstacle'], + # Health/Lifestyle + '건강': ['healthy lifestyle', 'fitness exercise', 'fresh vegetables'], + '다이어트': ['diet food salad', 'weight loss scale', 'healthy eating'], + '운동': ['gym workout exercise', 'running sport', 'fitness training'], + '수면': ['peaceful sleep bedroom', 'sleeping person night', 'rest relaxation'], + '스트레스': ['stress anxiety person', 'overwhelmed work', 'headache pressure'], + '행복': ['happy smiling person', 'joy celebration', 'positive energy'], + # Education + '공부': ['studying books desk', 'student learning', 'open textbook'], + '독서': ['reading book cozy', 'bookshelf library', 'person reading'], + '교육': ['classroom teaching', 'education school', 'learning knowledge'], + '자격증': ['certificate diploma award', 'achievement credential', 'professional certification'], + # Social/Communication + '소통': ['communication talking', 'conversation speech bubble', 'people talking'], + '관계': ['relationship people together', 'friendship bond', 'social connection'], + '가족': ['family together happy', 'family portrait', 'home family'], + '친구': ['friends together laughing', 'friendship bond', 'social gathering'], + # Environment/Nature + '자연': ['nature landscape scenic', 'green forest trees', 'outdoor beauty'], + '환경': ['environment ecology', 'green earth planet', 'sustainability'], + '도시': ['city skyline urban', 'modern architecture', 'downtown cityscape'], + '여행': ['travel adventure journey', 'wanderlust explore', 'tourism destination'], + # Time/Productivity + '시간': ['clock time management', 'hourglass countdown', 'calendar schedule'], + '생산성': ['productivity work desk', 'efficient workflow', 'organized workspace'], + '습관': ['habit routine daily', 'calendar habit tracker', 'consistent practice'], + '목표': ['goal target arrow', 'achievement milestone', 'success roadmap'], + # Food + '음식': ['food meal delicious', 'restaurant dining', 'cooking kitchen'], + '커피': ['coffee cup cafe', 'espresso morning', 'coffee shop cozy'], + '요리': ['cooking chef kitchen', 'recipe preparation', 'homemade food'], + # Digital/Social Media + '유튜브': ['youtube play button', 'video content creator', 'streaming platform'], + '틱톡': ['social media video', 'short video content', 'viral content'], + '인스타그램': ['instagram photo aesthetic', 'social media post', 'influencer lifestyle'], + '콘텐츠': ['content creation studio', 'digital content', 'creative media'], + # Generic actions + '시작': ['starting launch beginning', 'new start fresh', 'launch rocket'], + '변화': ['change transformation', 'before after contrast', 'evolution progress'], + '성장': ['growth plant sprouting', 'growth chart rising', 'development progress'], + '문제': ['problem solving puzzle', 'challenge obstacle', 'issue question mark'], + '해결': ['solution lightbulb', 'problem solved checkmark', 'resolution answer'], + '비교': ['comparison side by side', 'versus contrast', 'pros cons balance'], + '순위': ['ranking top list', 'leaderboard winners', 'chart comparison'], + '방법': ['how-to guide steps', 'tutorial instruction', 'method process'], + '팁': ['tips tricks advice', 'helpful hints', 'pro tip star'], + '비밀': ['secret reveal hidden', 'mystery unlock', 'insider knowledge'], + '진실': ['truth reveal facts', 'reality check', 'honest disclosure'], + '놀라운': ['surprising amazing wow', 'unexpected revelation', 'shocking discovery'], + # Numbers/Stats + '1위': ['number one winner', 'first place gold', 'top ranked best'], + '100%': ['one hundred percent complete', 'full capacity', 'perfect score'], + # Korean culture + '한국': ['korea seoul cityscape', 'korean culture', 'hanbok traditional'], + '직장': ['office workplace corporate', 'work desk professional', 'business office'], + '취업': ['job interview hiring', 'employment opportunity', 'career success'], + '부동산': ['real estate property', 'house home investment', 'property market'], + # Abstract concepts + '가능성': ['possibility open door', 'opportunity horizon', 'potential unlimited'], + '미래': ['future technology vision', 'futuristic landscape', 'innovation tomorrow'], + '트렌드': ['trend arrow upward', 'trending popular', 'hot topic social'], +} + +# Quality/style modifiers to append to video/image prompts +VISUAL_STYLE_MODIFIERS = [ + 'cinematic', + '4k', + 'professional', + 'high quality', + 'vibrant colors', + 'sharp focus', + 'natural lighting', + 'smooth motion', +] + +# Terms to avoid in video generation prompts +NEGATIVE_TERMS = [ + 'blurry', + 'low quality', + 'watermark', + 'text overlay', + 'distorted', + 'pixelated', + 'grainy', + 'overexposed', + 'underexposed', + 'shaky camera', +] + + +if __name__ == '__main__': + import sys + + if '--test' in sys.argv: + print('=== visual_vocabulary 테스트 시작 ===') + print(f'총 개념 수: {len(CONCEPT_TO_VISUAL)}') + print(f'스타일 수식어 수: {len(VISUAL_STYLE_MODIFIERS)}') + print(f'네거티브 용어 수: {len(NEGATIVE_TERMS)}') + print() + + # Test a few lookups + test_concepts = ['AI', '미래', '성공', '건강', '코딩'] + for concept in test_concepts: + visuals = CONCEPT_TO_VISUAL.get(concept, []) + print(f' [{concept}] -> {visuals}') + + print() + print(f'스타일 수식어: {VISUAL_STYLE_MODIFIERS}') + print(f'네거티브 용어: {NEGATIVE_TERMS}') + print() + print('=== 테스트 완료 ===') + else: + print('사용법: python -m bots.prompt_layer.visual_vocabulary --test')