18 | "redbull gives you wings we give a comic"
19 | provide us your story we'll generate amazing art just like you
20 | see any comics
21 | See Examples from Navigation bar
22 |
161 | >
162 | );
163 | }
164 |
--------------------------------------------------------------------------------
/server/utils/Controller.js:
--------------------------------------------------------------------------------
1 | const { GoogleGenerativeAI } = require("@google/generative-ai");
2 | const fs = require("fs");
3 | const { createCanvas, loadImage } = require("canvas");
4 | require("dotenv").config();
5 |
6 | const genAI = new GoogleGenerativeAI(process.env.geminiAI);
7 |
8 | const engineId = "stable-diffusion-v1-6";
9 | const apiHost = process.env.API_HOST ?? "https://api.stability.ai";
10 | const apiKey = process.env.STABLE_DIFFUSION_KEY;
11 |
12 | const writeDialogue = async (person, Imgpath, speech, imageNumber) => {
13 | try {
14 | const image = await loadImage(Imgpath);
15 | const canvas = createCanvas(image.width, image.height);
16 | const ctx = canvas.getContext("2d");
17 |
18 | ctx.drawImage(image, 0, 0, image.width, image.height);
19 | ctx.fillStyle = "white";
20 | ctx.strokeRect(10, canvas.height - 50, canvas.width, 200);
21 | ctx.fillRect(10, canvas.height - 50, canvas.width, 190);
22 |
23 | const out = fs.createWriteStream(`./final/main/${imageNumber}.png`);
24 | const stream = canvas.createPNGStream();
25 | stream.pipe(out);
26 |
27 | out.on("finish", () => {
28 | ctx.font = "italic bold 12px serif";
29 | ctx.fillStyle = "black";
30 | ctx.strokeStyle = "red";
31 | ctx.lineWidth = 3;
32 | ctx.textAlign = "left";
33 | const textX = 10;
34 | const textY = canvas.height - 30;
35 | ctx.fillText(`${person}: ${speech}`, textX, textY);
36 |
37 | const out = fs.createWriteStream(`./final/main/${imageNumber}.png`);
38 | const stream = canvas.createPNGStream();
39 | stream.pipe(out);
40 |
41 | out.on("finish", () => {
42 | ctx.shadowColor = "#FFFFFF";
43 | ctx.shadowBlur = 50;
44 | ctx.lineJoin = "miter";
45 | ctx.lineCap = "butt";
46 | ctx.lineWidth = 4;
47 | ctx.strokeStyle = "black";
48 | ctx.strokeRect(0, 0, image.width, image.height);
49 |
50 | const out = fs.createWriteStream(`./final/main/${imageNumber}.png`);
51 | const stream = canvas.createPNGStream();
52 | stream.pipe(out);
53 |
54 | out.on("finish", () => {
55 | console.log("Image generated");
56 |
57 | return;
58 | });
59 | });
60 | });
61 | } catch (error) {
62 | console.error("An error occurred:", error);
63 | }
64 | };
65 |
66 | const generateImages = async (person, speech, features, diffusionKey) => {
67 | try {
68 | const response = await fetch(
69 | `${apiHost}/v1/generation/${engineId}/text-to-image`,
70 | {
71 | method: "POST",
72 | headers: {
73 | "Content-Type": "application/json",
74 | Accept: "application/json",
75 | Authorization: `Bearer ${apiKey}`,
76 | },
77 | body: JSON.stringify({
78 | text_prompts: [
79 | {
80 | text: `A single classic superhero comic panel. In the foreground stands ${person}, a ${features} style . They have a determined expression and a speech bubble pops out from above their head. Inside the speech bubble, write in bold, dynamic lettering: "${speech}".`,
81 | },
82 | ],
83 | cfg_scale: 30,
84 | height: 512,
85 | width: 512,
86 | steps: 30,
87 | samples: 1,
88 | seed: 992446758,
89 | style_preset: "comic-book",
90 | }),
91 | }
92 | );
93 | if (!response.ok) {
94 | throw new Error(`Non-200 response: ${await response.text()}`);
95 | }
96 |
97 | const responseJSON = await response.json();
98 | if (!fs.existsSync("./out/")) {
99 | fs.mkdirSync("./out/");
100 | }
101 | responseJSON.artifacts.forEach((image, index) => {
102 | let randomNum = Math.floor(Math.random() * 1000);
103 | let filePath = `./out/v1_txt2img_${randomNum}.png`;
104 | return new Promise((resolve) => {
105 | fs.writeFile(filePath, Buffer.from(image.base64, "base64"), (err) => {
106 | if (!err) {
107 | writeDialogue(person, filePath, speech, randomNum);
108 | }
109 | resolve(); // Resolve the promise after writing the dialogue
110 | });
111 | });
112 | });
113 | } catch (error) {
114 | console.log(error);
115 | return error;
116 | }
117 | };
118 |
119 | const generateComic = async (jsonOutput, customization, diffusionKey) => {
120 | for (const eachObj of jsonOutput) {
121 | await generateImages(eachObj.speaker, eachObj.dialogue, customization);
122 | }
123 | return "done";
124 | };
125 |
126 | const generateMapFromText = (text) => {
127 | let lines = text.split("\n");
128 | let jsonOutput = [];
129 |
130 | lines.forEach((line) => {
131 | if (line.trim() !== "") {
132 | let parts = line.split(":");
133 | let speaker = parts[0].trim().replace(/\*\*/g, "");
134 | let dialogue = parts.slice(1).join(":").trim();
135 | jsonOutput.push({ speaker: speaker, dialogue: dialogue });
136 | }
137 | });
138 |
139 | return JSON.stringify(jsonOutput, null, 2);
140 | };
141 |
142 | const textToComic = async (userText, customization, diffusionKey) => {
143 | return new Promise(async (resolve, reject) => {
144 | try {
145 | const model = genAI.getGenerativeModel({ model: "gemini-pro" });
146 | const prompt = `Convert the following boring text into a comic style conversation (make conversation smaller) between characters while retaining information . Try to keep the characters as people from the story. Keep a line break after each dialogue and don't include words like Scene 1, narration context and scenes etc. take the name of the character which is makred in double quote and not character number: \n\n\n ${userText}`;
147 | const result = await model.generateContent(prompt);
148 | const response = result.response.text();
149 | const jsonOutput = generateMapFromText(response);
150 | await generateComic(JSON.parse(jsonOutput), customization, diffusionKey);
151 | resolve();
152 | } catch (error) {
153 | reject(error);
154 | }
155 | });
156 | };
157 |
158 | module.exports = {
159 | textToComic,
160 | };
161 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Comic-cult
2 |
3 | ## redbull gives you wings 💸we give you comic 🖼️
4 |
5 | 
6 |
7 |
8 |
9 | ## ℹ️ Project description
10 |
11 | Transform your stories into captivating comic art with dialogue effortlessly! Our project harnesses the power of GemeiAI and stable diffusion for processing and generating stunning images and dialogues. Utilizing feature flags, you can seamlessly toggle environment variables on and off as needed. The final masterpiece is delivered straight to your inbox as a beautifully crafted comic PDF. Enjoy the magic of storytelling in a whole new visual dimension! 📧💬🖼️
12 |
13 |
14 |
15 | ## Demo 💻
16 |
17 | Visit https://palettegram.vercel.app to see the live demo!
18 |
19 | ## ⚒️ Features:
20 |
21 |
22 |
23 |
24 |
Feature Name
25 |
Feature Description
26 |
27 |
28 |
29 |
Dialogue Generation
30 |
With advanced dialogue generation capabilities, your characters come to life on the comic pages. Using sophisticated summary, gemeni generates natural and dynamic dialogues that complement the visuals, enhancing the overall storytelling experience.
31 |
32 |
33 |
34 |
Comic Art Generation
35 |
: Leveraging stability AI and DreamStudio API keys, Comic Art Generation process is infused with stability and creativity. Stability AI ensures consistent and reliable image processing, while DreamStudio API keys unlock a world of artistic possibilities, allowing us to create captivating comic art with ease.
36 |
37 |
38 |
39 |
Flagsmith Feature Flags Integration
40 |
I've implemented flagsmith feature flags into our project, providing you with ultimate control over its functionalities. Whether you need to toggle specific features on or off, our feature flags make it effortless to customize the comic generation process according to your preferences and requirements.
41 |
42 |
43 |
44 |
Email Delivery of Comic PDF:
45 |
Seamlessly integrating nodemailer into our workflow, I've streamlined the process of delivering your comic PDF straight to your inbox. Nodemailer provides a robust and reliable solution for sending emails programmatically, ensuring that your comic masterpiece reaches its destination efficiently and securely.
46 |
47 |
48 |
49 |
50 | ## 🤔 What challenges I ran into
51 |
52 |
53 |
54 |
Comic Art generation
55 |
I wanted to make something that is of fun and also gives me a lot of learning I used apis of openAi even utilized the langchain for some instances but wanted to take my level above, I wanted to play around with images that directed me of using any stable diffusion model (which I had no Idea how to use) I tried running model locally also used google collab however all these things made me more perplexed at the end I was in the need of some Stable diffusion API and key which dream-studio and Stability AI provided
56 |
57 |
58 |
Workflow/pipeline of making
59 |
Probably I worked for this part mostly such a pipeline was the backbone of this project, I was too confused and wanted to leave project for once however, I slowly worked of making a streamlined flow of generating dialogues (gemini helped) and then getting images(Stability API helped) based on that dialogue, adding dialogues onto images(Canvas helped), all these steps I wanted to be reliable and smooth which could provide happy user experience
60 |
61 |
62 |
Using promises
63 |
I have only heard and learned about promises and never utilized them in my any of the project until now, oh man they were life saver, they helped me in making and flow better and workable, I literally now want to use promises everywhere, I really got the understand of promises, async await, .then and catch
64 |
65 |
66 |
failure of express func and integration of nodemailer
67 |
I am using express for my backend to serve front of various routes I never thought that res.sendfile, res.download would be so pathetic to use, they are made to sendfile from backend to frontend and guess what they were giving errors for the same, almost at this point also I am thinking to use them but they were so un-useful but then nodemailer came, I had this thought in back of my mind that if express functions does not work I will try sending pdf to user mail and using nodemailer this was so useful that I never imagined basically in few minutes I did this
68 |
69 |
70 |
71 |
72 | ## 💻 Tech Stack
73 |
74 | - [Next.js](https://nextjs.org) - Next.js is an open-source web development framework.
75 | - [TypeScript](https://www.typescriptlang.org) - TypeScript is a free and open-source high-level programming language.
76 | - [Tailwind CSS](https://tailwindcss.com) - Tailwind CSS is a utility-first CSS framework for rapidly building modern websites without ever leaving your HTML.
77 | - [Daisy UI](https://tailwindcss.com) - It has inbuilt tailwind component which can be used
78 | - [Express](https://tailwindcss.com) - It is use to make server endpoint and serve front end as needed
79 | - [flagsmith](https://tailwindcss.com) - Manage feature flags across web, mobile, and server side applications
80 | - [Gemini AI](https://tailwindcss.com) - It has api which could be use ful to make prompt and get output
81 | - [Nodemailer](https://tailwindcss.com) - Used to send mail or attachment programmatically
82 |
83 | ## 🔨 Locally install
84 |
85 | To run this project, you will need to add the following environment variables to your .env file (use .env.example for reference)
86 |
87 | [Gemini AI](https://ai.google.dev/docs): `API_KEY`
88 |
89 | [Dream Studio](https://dreamstudio.ai/): `API_KEY`
90 |
91 | [Setup Flagsmit instance and get flagsmith API](https://docs.flagsmith.com/quickstart) : `API_Key`
92 |
93 | [Nodemailer](https://stackoverflow.com/questions/45478293/username-and-password-not-accepted-when-using-nodemailer) user : `your mail` nodemailer pass : `somepass`
94 |
95 | ### Run Locally
96 |
97 | ## Run Locally
98 |
99 | Clone the project
100 |
101 | ```bash
102 | git clone https://github.com/Girishbari/comic-cult
103 | ```
104 |
105 | Go to the project directory
106 |
107 | ```bash
108 | cd comic-cult
109 | ```
110 |
111 | Install dependencies
112 |
113 | ```bash
114 | npm install
115 | ```
116 |
117 | ## fill up the API keys
118 |
119 | Start the Front-end
120 |
121 | ```bash
122 | npm run dev
123 | ```
124 |
125 | Start the back-end
126 |
127 | ```bash
128 | npm run server
129 | ```
130 |
--------------------------------------------------------------------------------