Seite 1 von 1

Frag:e: Warum funktioniert dieser Code nicht? (Discord Bot + ChatGPT + Midjourney)

Verfasst: Donnerstag 13. April 2023, 00:18
von hamann.jonas
Guten Tag, ich habe nicht so viel Ahnung mit Python und bastle nun schon seit 21 Uhr hier rum. Bis jetzt habe ich aber immer Niederlagen gehabt. Ich will einen Discord Bot erstellen, der für mich ChatGPT 3.5 turbo nach prompts fragt, diese dann in die Textdatei prompts.txt legt. Als nächstes werden die promts ausgelesen und an Midjourney weitergegeben (Discord). Die erstellten Bilder von midjourney sollen dann hochskaliert werden, und dann auf meinen PC gedownloadet werden.

Es wäre echt nett, wenn mir jemand helfen würde, weil sonst verzweifle ich hier noch wirklich :lol:
Information: Ist nur für eigene Anwendung.(Interessehalber)

Code: Alles auswählen

import discord
import openai
import asyncio
import os



# OpenAI-API-Schlüssel
openai.api_key = "AI_SCHLÜSSEL"

# Discord-Bot-Token
TOKEN = "DISCORD_BOT_TOKEN"

intents = discord.Intents.default()
intents.members = True # Optional, falls Sie beabsichtigen, auf die Mitgliederliste des Servers zuzugreifen
# Discord-Client-Objekt
client = discord.Client(intents=intents)

# Pfad zum Speichern der Bilder
IMAGE_PATH = "DATEIPFAD"


# Funktion zum Speichern der Prompts in eine Datei
async def save_prompts(prompts):
    with open("prompts.txt", "w+") as f:
        f.write("\n\n".join(prompts))


# Funktion zum Generieren von Prompts und Herunterladen von Bildern
async def generate_prompts():
    while True:
        prompts = []
        for i in range(10):
            prompt = "generate a detailed prompt suitable for midjourney. picture is for stock."
            response = openai.Completion.create(
                engine="gpt-3.5-turbo",
                prompt=prompt,
                max_tokens=1024,
                n=1,
                stop=None,
                temperature=0.5,
            )
            prompt_text = response.choices[0].text.strip()
            prompts.append(prompt_text)

            # An Midjourney senden, um Bild zu generieren
            response = openai.Completion.create(
                engine="davinci",
                prompt=prompt_text,
                max_tokens=1024,
                n=1,
                stop=None,
                temperature=0.5,
            )
            image_url = response.choices[0].text.strip()

            # Bild herunterladen
            filename = "image_{}.png".format(i + 1)
            filepath = os.path.join(IMAGE_PATH, filename)
            os.system("wget {} -O {}".format(image_url, filepath))

            prompts.append(image_url)

        await save_prompts(prompts)


# Discord-Bot-Event-Handler für das "ready"-Event
@client.event
async def on_ready():
    print("Bot is ready")


# Discord-Bot-Event-Handler für das "message"-Event
@client.event
async def on_message(message):
    if message.author == client.user:
        return

    if message.content.startswith("*start"):
        # Starte den Prozess zum Generieren von Prompts und Herunterladen von Bildern
        await generate_prompts()
        await message.channel.send("Prompts werden generiert und Bilder heruntergeladen.")

    elif message.content.startswith("*stop"):
        # Stoppe den Prozess zum Generieren von Prompts und Herunterladen von Bildern
        await message.channel.send("Bot wird gestoppt.")
        exit()


# Discord-Bot-Client starten
client.run(TOKEN)

Re: Frag:e: Warum funktioniert dieser Code nicht? (Discord Bot + ChatGPT + Midjourney)

Verfasst: Donnerstag 13. April 2023, 07:58
von Sirius3
Und wo ist jetzt das konkrete Problem? Was funktioniert nicht so, wie Du Dir das wünschst?

"w+" ist der falsche Dateimodus um Text-Dateien zu schreiben. os.system benutzt man schon lange nicht mehr, subprocess.run wäre das richtige, aber eigentlich möchte man requests benutzen, um Dateien herunter zu laden.

Re: Frag:e: Warum funktioniert dieser Code nicht? (Discord Bot + ChatGPT + Midjourney)

Verfasst: Donnerstag 13. April 2023, 09:44
von hamann.jonas
So, ich habe nun nochmal rumgetüftelt. Ich verstehe jedoch nicht warum das ganze nicht funktioniert.
Ich will, dass der Discord Bot ChatGPT nach 10 prompts fragt, diese 10 promts dannn in die prompts.txt Datei abspeichert
als nächstes soll nacheinander ein prompt mit /imagine *prompt* an Midjourney gesendet werden. Bevor jedoch der nächste prompt gesendet wird, soll noch das bild in 4 Teile geteilt werden und hochskaliert werden.
Dann soll es gedownloadet werden.
Das passiert solange bis die 10 prompts aufgebraucht sind.
Die Textdatei wird gelöscht, und das Ganze Programm läuft nochmal durch.
Bis man *stop in den Discord Kanal schreibt.


Ich finde meine Fehler leider nicht.
Ich habe mir sehr viele Tutorials angesehen, aber ich weiß leider nicht weiter.
Sitze nun schon seit 3 Tagen an diesem Code.

HIer der code:

Code: Alles auswählen

from PIL import Image
import shutil
import discord
from discord.ext import commands#
from dotenv import load_dotenv
import openai
import asyncio
import os
import time
import pyautogui as pg


discord_token = "YOUR_DISCORD_TOKEN"
openai.api_key = "YOUR KEY"

# Using readlines()
prompt_file = open('prompts.txt', 'r')
prompts = prompt_file.readlines()

prompt_counter = 0

load_dotenv()
client = commands.Bot(command_prefix="*", intents=discord.Intents.all())

#Funktion zum Speichern der Promts in eine Datei
async def save_prompts(prompts):
    with open("prompts.txt", "w+") as f:
        f.write("/n/n".join(prompts))


#Endlosschleife, um ChatGPT kontinuierlich nach Promts zu Fragen
async def generate_prompts():
    prompts = []
    for i in range(10):
        prompt = input("give a detailed prompt for midjourney")
        response = openai.Completion.create(
            engine="gpt-3.5-turbo",
            prompt=prompt,
            max_tokens=1024,
            n=1,
            stop=None,
            temperature=0.5,
        )
        prompt_text = response.choices[0].text.strip()
        prompts.append(prompt_text)

    await save_promts(prompts)

def split_image(image_file):
    with Image.open(image_file) as im:
        # Get the width and height of the original image
        width, height = im.size
        # Calculate the middle points along the horizontal and vertical axes
        mid_x = width // 2
        mid_y = height // 2
        # Split the image into four equal parts
        top_left = im.crop((0, 0, mid_x, mid_y))
        top_right = im.crop((mid_x, 0, width, mid_y))
        bottom_left = im.crop((0, mid_y, mid_x, height))
        bottom_right = im.crop((mid_x, mid_y, width, height))

        return top_left, top_right, bottom_left, bottom_right

async def download_image(url, filename):
    response = requests.get(url)
    if response.status_code == 200:

        # Define the input and output folder paths
        input_folder = "input"
        output_folder = "output"

        # Check if the output folder exists, and create it if necessary
        if not os.path.exists(output_folder):
            os.makedirs(output_folder)
        # Check if the input folder exists, and create it if necessary
        if not os.path.exists(input_folder):
            os.makedirs(input_folder)

        with open(f"{directory}/{input_folder}/{filename}", "wb") as f:
            f.write(response.content)
        print(f"Image downloaded: {filename}")

        input_file = os.path.join(input_folder, filename)

        if "UPSCALED_" not in filename:
            file_prefix = os.path.splitext(filename)[0]
            # Split the image
            top_left, top_right, bottom_left, bottom_right = split_image(input_file)
            # Save the output images with dynamic names in the output folder
            top_left.save(os.path.join(output_folder, file_prefix + "_top_left.jpg"))
            top_right.save(os.path.join(output_folder, file_prefix + "_top_right.jpg"))
            bottom_left.save(os.path.join(output_folder, file_prefix + "_bottom_left.jpg"))
            bottom_right.save(os.path.join(output_folder, file_prefix + "_bottom_right.jpg"))

        else:
            os.rename(f"{directory}/{input_folder}/{filename}", f"{directory}/{output_folder}/{filename}")
        # Delete the input file
        os.remove(f"{directory}/{input_folder}/{filename}")


@client.event
async def on_ready():
    print("Bot connected")

@client.event
async def on_message(message):
    global prompt_counter

    msg = message.content
    print(message)

    for attachment in message.attachments:
        if "Upscaled by" in message.content:
            file_prefix = 'UPSCALED_'
        else:
            file_prefix = ''
        if attachment.filename.lower().endswith((".png", ".jpg", ".jpeg", ".gif")):
            await download_image(attachment.url, f"{file_prefix}{attachment.filename}")

    while prompt_counter < len(prompts):
        # Start Automation by typing "automation" in the discord channel
        if msg == 'automation':
            time.sleep(3)
            pg.press('tab')
            for i in range(1):
                time.sleep(3)
                pg.write('/imagine')
                time.sleep(5)
                pg.press('tab')
                pg.write(prompts[prompt_counter])
                time.sleep(3)
                pg.press('enter')
                time.sleep(5)
                prompt_counter += 1

        # continue Automation as soon Midjourney bot sends a message with attachment.
        for attachment in message.attachments:
            time.sleep(3)
            pg.write('/imagine')
            time.sleep(5)
            pg.press('tab')
            pg.write(prompts[prompt_counter])
            time.sleep(3)
            pg.press('enter')
            time.sleep(5)
            prompt_counter += 1

    # Stop Automation once all prompts are completed
    quit()

client.run(discord_token)

Re: Frag:e: Warum funktioniert dieser Code nicht? (Discord Bot + ChatGPT + Midjourney)

Verfasst: Donnerstag 13. April 2023, 10:20
von Sirius3
Aber welcher Teil des Codes funktioniert denn nicht?

Re: Frag:e: Warum funktioniert dieser Code nicht? (Discord Bot + ChatGPT + Midjourney)

Verfasst: Donnerstag 13. April 2023, 11:05
von __deets__
Um mal etwas Klarheit hier reinzubringen: niemand hier wird einen Discord-Server anlegen, einen Bot registrieren, und das alles nachvollziehen. Du musst schon konkrete Fehlermeldungen zeigen, oder Verhaltensweisen wie zB "da erwarte ich, dass er das schreibt, aber er schreibt nix, oder dies" oder was auch immer.

Es gibt aber diverse Fehler, die mindestens mal behoben werden muessen;

- du arbeitest grundsaetzlich mit relativen Pfaden. Das wird nicht gut gehen. Benutze absolute Pfade.
- /n ist ungewoehnlich, meinst du \n?
- time.sleep ist falsch, das Ding muss asyncio vernuenftig benutzen. Also await asyncio.sleep(x) in diesem Fall.

Kein Fehler, aber veraltet: os. Benutze pathlib.
Statt for in range(), benutze direkt

Code: Alles auswählen

for prompt in prompts
Das du mal prompts global benutzt, und auch lokal, ist auch ziemlich wahrscheinlich ein Fehler.

Re: Frag:e: Warum funktioniert dieser Code nicht? (Discord Bot + ChatGPT + Midjourney)

Verfasst: Samstag 20. Mai 2023, 13:27
von __blackjack__
@hamann.jonas: Da ist so vieles komisch bis falsch, dass man glatt vermuten könnte Du versuchst Dir von ChatGPT auch das Programm schreiben zu lassen. Das wird nicht wirklich funktionieren. Man muss dafür so viel Ahnung vom Programmieren haben, dass man es auch selbst machen könnte, denn sonst kann man nicht beurteilen ob das sinnvoll ist, was ChatGPT da ausspuckt, und kann auch die Fehler nicht erkennen und beheben die es da immer wieder einbaut.

Teilweise sind da auch total offensichtliche Fehler drin, die zu sehr eindeutigen Fehlermeldungen führen, was ja bedeutet, dass Du das überhaupt gar nicht hast laufen lassen, denn sonst hättest Du ja diese Fehler hier gepostet. Oder behoben.

`asyncio` und `shutil` werden importiert aber nicht verwendet. `asyncio` *sollte* verwendet werden, aber `shutil` kann weg.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht.

Dateien die man öffnet, sollte man auch wieder schliessen. Wo möglich verwendet man dazu die ``with``-Anweisung.

Beim öffnen von Textdateien sollte man immer explizit die Kodierung angeben.

`load_dotenv()` sollte man so früh wie möglich aufrufen. Wobei zumindest aus dem gezeigten Quelltext nicht ersichtlich wird ob das überhaupt gebraucht wird. Da könnte man einen Kommentar dran schreiben was das bewirken soll.

`save_prompts()` macht nichts asynchrones, da macht es keinen Sinn die Funktion als ``async`` zu definieren. Die Funktion wird auch nirgends aufgerufen. Was in `generate_prompts()` aufgerufen wird ist `save_promts()`, was es nicht gibt. Das wäre einer der Fehler die definitiv aufgefallen wären, wenn das mal gelaufen wäre.

Dateimodi mit "+" sind bei Textdateien eigentlich immer falsch. So auch in `save_promts()`.

Das `join()` in der Funktion sorgt für eine Leerzeile zwischen den Prompts, beim einlesen sieht der Code allerdings so aus, als sollte in jeder Zeile ein Prompt stehen und nicht nur in jeder zweiten. Zudem ist `join()` falsch, denn das setzt die Zeilenendezeichen ja nur *zwischen* die Prompts, schliesst aber den letzten *nicht* mit einem Zeilenendezeichen ab.

Falsche Kommentare sind schlimmer als keine Kommentare. Ein Kommentar sollen eventuelle Fragen mit dem Code klären, aber wenn dort falsche Informationen drin stehen, oder gar welche die dem Code direkt wiedersprechen, erreicht man genau das Gegenteil. Der Leser weiss dann nicht was falsch ist, der Code oder der Kommentar. Der Kommentar das in `generate_prompts()` eine Endlosschleife ist, stimmt nicht.

`generate_prompts()` macht ebenfalls nichts asynchrones, braucht also auch kein ``async``.

Die Funktion wird auch überhaupt nicht aufgerufen. Damit werden dann natürlich auch keine Prompts vom Benutzer abgefragt.

In `on_message()` wird `file_prefix` für jeden Anhang bestimmt, aber aufgrund des Nachrichteninhalts der ja für alle Anhänge unverändert ist. Das gehört also vor die Schleife.

Eine Schleife die immer genau einmal durchlaufen wird, ist offensichtlich keine Schleife.

Bei der Fernsteuerung des Browsers steht zweimal der gleiche Code in der Funktion. So etwas macht man als Programmierer nicht. Das würde man in eine eigene Funktion heraus ziehen.

Dabei braucht man dann auch das `asyncio`-Modul, denn in einer ``async``-Funktion verwendet man kein `time.sleep()` das die Async-Loop blockiert.

Ist das tatsächlich so gewollt das pro Anhang zu machen, ohne das irgendwelche Informationen aus dem Anhang selbst verwendet werden‽

`quit()` gibt es nicht. Das ist eine Funktion die wegen der interaktiven Python-Shell existiert, die hat in normalen Programmen nichts zu suchen. Es erscheint mir hier auch nicht wirklich sinnvoll das Programm nach *einer* empfangenen Nachricht abbrechen zu wollen.

In `download_image()`, das zumindest potentiell aufgerufen werden kann, wenn entsprechende Nachrichten eintreffen, sind wieder krass auffällige Fehler. Gleich in der ersten Zeile wird `requests` benutzt, was aber gar nicht importiert wird, und in der Funktion wird versucht ein undefiniertes `directory` zu verwenden.

Es werden Pfade mit Zeichenkettenoperationen erstellt obwohl im Programm auch `os.path.join()` verwendet wird.

In neuem Code sollte man auch nicht mehr die `os.path`-Funktionen verwenden, sondern `pathlib`.

Gespeichert wird die Bilddatei *mit* dem undefinierten `directory`, zum aufteilen des Bildes wird dann aber versucht ohne diesen zusätzlich Pfadpräfix die Datei zu öffnen. Wieder so ein anzeichen, dass dieser Code nie ausprobiert wurde, weil das dabei ja aufgefallen wäre. Ebenso das `os.remove()` natürlich nicht funktioniert wenn man vorher im ``else``-Zweig die Datei die entfernt werden soll umbenannt hat.

Auch `download_image()` ist wieder völlig unnötig als ``async`` definiert.

Ungetesteter Zwischenstand bei dem unbedingt die globalen Variablen verschwinden sollten:

Code: Alles auswählen

#!/usr/bin/env python3
import asyncio

import discord
import openai
import pyautogui
import requests
from discord.ext import commands
from dotenv import load_dotenv
from PIL import Image

DISCORD_TOKEN = "YOUR_DISCORD_TOKEN"
PROMPTS_FILENAME = "prompts.txt"
#
# TODO Whatever `directory` in `download_image()` was supposed to be.
#
IMAGES_PATH = Path(".")
#
# FIXME Remove global variables!!!
#
client = commands.Bot(command_prefix="*", intents=discord.Intents.all())
prompts = []
prompt_counter = 0


def save_prompts(prompts):
    with open("prompts.txt", "w", encoding="utf-8") as file:
        file.writelines(prompt + "/n" for prompt in prompts)


def generate_prompts():
    save_prompts(
        [
            openai.Completion.create(
                engine="gpt-3.5-turbo",
                prompt=input("give a detailed prompt for midjourney: "),
                max_tokens=1024,
                n=1,
                stop=None,
                temperature=0.5,
            )
            .choices[0]
            .text.strip()
            for _ in range(10)
        ]
    )


def split_image(file_path):
    with Image.open(file_path) as image:
        width, height = image.size
        center_x = width // 2
        center_y = height // 2
        return (
            image.crop((0, 0, center_x, center_y)),
            image.crop((center_x, 0, width, center_y)),
            image.crop((0, center_y, center_x, height)),
            image.crop((center_x, center_y, width, height)),
        )


def download_image(url, filename):
    response = requests.get(url, timeout=None)
    if response.status_code == 200:
        input_path = IMAGES_PATH / "input"
        output_path = IMAGES_PATH / "output"
        input_path.mkdir(parents=True, exist_ok=True)
        output_path.mkdir(parents=True, exist_ok=True)

        input_file_path = input_path / filename
        input_file_path.write_bytes(response.content)
        print(f"Image downloaded: {filename}")

        if input_file_path.name.startswith("UPSCALED_"):
            input_file_path.rename(output_path / input_file_path.name)
        else:
            name_prefix = input_file_path.stem
            for image, name_suffix in zip(
                split_image(input_file_path),
                ["top_left", "top_right", "bottom_left", "bottom_right"],
            ):
                image.save(output_path / f"{name_prefix}_{name_suffix}.jpg")

            input_file_path.unlink()


async def automate_browser(prompt):
    await asyncio.sleep(3)
    pyautogui.write("/imagine")
    await asyncio.sleep(5)
    pyautogui.press("tab")
    pyautogui.write(prompt)
    await asyncio.sleep(3)
    pyautogui.press("enter")
    await asyncio.sleep(5)


@client.event
async def on_ready():
    print("Bot connected")


@client.event
async def on_message(message):
    global prompt_counter

    print(message)
    #
    # TODO Instead of passing this information as part of the filename to
    # `download_image()` it would be cleaner as an explicit flag, passed as an
    # additional argument.
    #
    file_prefix = "UPSCALED_" if "Upscaled by" in message.content else ""
    for attachment in message.attachments:
        if attachment.filename.lower().endswith(
            (".png", ".jpg", ".jpeg", ".gif")
        ):
            download_image(
                attachment.url, f"{file_prefix}{attachment.filename}"
            )

    while prompt_counter < len(prompts):
        #
        # Start automation by typing "automation" in the discord channel.
        #
        if message.content == "automation":
            await asyncio.sleep(3)
            pyautogui.press("tab")
            automate_browser(prompts[prompt_counter])
            prompt_counter += 1
        #
        # Continue automation as soon Midjourney bot sends a message with
        # attachment.
        #
        for _ in message.attachments:
            automate_browser(prompts[prompt_counter])
            prompt_counter += 1


def main():
    global prompts

    load_dotenv()
    openai.api_key = "YOUR KEY"
    with open("prompts.txt", "r", encoding="utf-8") as file:
        prompts = list(file)

    client.run(DISCORD_TOKEN)


if __name__ == "__main__":
    main()
Was hauptsächlich falsch gemacht wurde: Anscheinend wurde keine Funktion mal getestet bevor sie verwendet wurde, weil dabei vieles sehr leicht aufgefallen wäre. Programme schreibt man nicht in einem grossen Stück runter, sondern entwickelt sie Funktion für Funktion, und testet jede Funktion ausgiebig, bevor man weiter macht. Und man macht erst weiter wenn die zuletzt geschriebene Funktion auch tatsächlich funktioniert.