import streamlit as st import spacy import random import re from gtts import gTTS from PIL import Image, ImageDraw, ImageFont import io import numpy as np import base64 # Load spaCy model for NLP nlp = spacy.load("en_core_web_sm") # Default word lists for storytelling classes default_word_lists = { "Characters": ["Hero", "Villain", "Protagonist", "Antagonist", "Wizard", "Knight"], "Settings": ["Forest", "City", "Castle", "Beach", "Mountain", "Space"], "Events": ["Adventure", "Romance", "Mystery", "Conflict", "Discovery", "Escape"], "Objects": ["Sword", "Magic potion", "Treasure chest", "Book", "Ring", "Spaceship"], "Dialogues": ["\"Hello, how are you?\"", "\"Let's go!\"", "\"Beware the danger!\"", "\"I love you.\""] } # Suit properties for narrative flavor suit_properties = { "Hearts": "emotional or romantic", "Diamonds": "wealthy or luxurious", "Clubs": "conflict or struggle", "Spades": "mysterious or dangerous" } # Sentence templates for story generation sentence_templates = { "Characters": "There was a {word} who was {property}.", "Settings": "The {word} was {property}, stretching far into the distance.", "Events": "Then, a {property} {word} unfolded.", "Objects": "A {property} {word} appeared before them.", "Dialogues": "One of them said, {word}, with a {property} tone." } # Function to process user input and augment word lists def augment_word_lists(user_input): doc = nlp(user_input) augmented_lists = {key: list(set(val)) for key, val in default_word_lists.items()} # Start with defaults # Extract elements from user input for ent in doc.ents: if ent.label_ == "PERSON": augmented_lists["Characters"].append(ent.text) elif ent.label_ in ["GPE", "LOC"]: augmented_lists["Settings"].append(ent.text) for token in doc: if token.pos_ == "VERB" and token.text not in augmented_lists["Events"]: augmented_lists["Events"].append(token.text) elif token.pos_ == "NOUN" and token.text not in augmented_lists["Characters"] + augmented_lists["Settings"]: augmented_lists["Objects"].append(token.text) dialogues = re.findall(r'"[^"]*"', user_input) augmented_lists["Dialogues"].extend(dialogues) return augmented_lists # Function to create a 52-card deck def create_deck(): suits = ["Hearts", "Diamonds", "Clubs", "Spades"] ranks = list(range(1, 14)) # 1-13 for each suit deck = [(suit, rank) for suit in suits for rank in ranks] random.shuffle(deck) return deck # Assign cards to classes based on position def assign_card_to_class(card_index): if 0 <= card_index < 10: return "Characters" elif 10 <= card_index < 20: return "Settings" elif 20 <= card_index < 30: return "Events" elif 30 <= card_index < 40: return "Objects" else: return "Dialogues" # Generate a card image with details def generate_card_image(suit, rank, story_class, word, property): img = Image.new("RGB", (200, 300), color="white") draw = ImageDraw.Draw(img) font = ImageFont.load_default() draw.text((10, 10), f"{rank} of {suit}", fill="black", font=font) draw.text((10, 50), f"Class: {story_class}", fill="black", font=font) draw.text((10, 90), f"Word: {word}", fill="black", font=font) draw.text((10, 130), f"Property: {property}", fill="black", font=font) buffer = io.BytesIO() img.save(buffer, format="PNG") return buffer.getvalue() # Generate story sentence based on card def generate_story_sentence(story_class, word, property): template = sentence_templates[story_class] return template.format(word=word, property=property) # Generate song lyrics from story def generate_song_lyrics(story_text): doc = nlp(story_text) key_elements = [token.text for token in doc if token.pos_ in ["NOUN", "VERB", "ADJ"]][:10] # Top 10 elements lyrics = " ".join(key_elements) # Simple concatenation for 60-second duration return lyrics # Main Streamlit app def main(): st.title("StoryForge: Card-Based Story Generator") # User input for story seed user_input = st.text_area("Paste your story seed here (large text accepted):", height=200) if "augmented_lists" not in st.session_state: st.session_state.augmented_lists = default_word_lists if "deck" not in st.session_state: st.session_state.deck = create_deck() if "story" not in st.session_state: st.session_state.story = [] if "drawn_cards" not in st.session_state: st.session_state.drawn_cards = 0 # Process input and augment word lists if st.button("Process Input"): if user_input: st.session_state.augmented_lists = augment_word_lists(user_input) st.session_state.deck = create_deck() # Reset deck st.session_state.story = [] st.session_state.drawn_cards = 0 st.success("Input processed! Ready to draw cards.") # Draw card button if st.button("Draw a Card") and st.session_state.drawn_cards < 52: card_index = st.session_state.drawn_cards suit, rank = st.session_state.deck[card_index] story_class = assign_card_to_class(card_index) word_list = st.session_state.augmented_lists[story_class] word = random.choice(word_list) property = suit_properties[suit] # Generate and display card image card_image = generate_card_image(suit, rank, story_class, word, property) st.image(card_image, caption=f"Card {card_index + 1}") # Generate and append story sentence sentence = generate_story_sentence(story_class, word, property) st.session_state.story.append(sentence) st.session_state.drawn_cards += 1 # Display current story st.write("### Current Story") st.write("\n".join(st.session_state.story)) # Generate song when deck is fully drawn if st.session_state.drawn_cards == 52: st.write("### Full Story Generated!") full_story = "\n".join(st.session_state.story) st.write(full_story) # Generate and play song lyrics = generate_song_lyrics(full_story) st.write("### Song Lyrics") st.write(lyrics) tts = gTTS(text=lyrics, lang="en") audio_file = "song.mp3" tts.save(audio_file) audio_bytes = open(audio_file, "rb").read() st.audio(audio_bytes, format="audio/mp3") # Basic p5.js integration for interactivity (placeholder) st.components.v1.html("""
""", height=120) if __name__ == "__main__": main()