0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Stable Diffusion Web UI で作成された PNG ファイルのメタデータ(パラメータ)を C++ で読む

Last updated at Posted at 2023-05-06

Stable Diffusion Web UI をセットアップしたい方は (Google Colab なき今は) Paperspace を使うのが得策かと・・・(こちらの記事もお読みください)

また、Web UI の簡単な使い方についてはこちらもどうぞ・・・(Google Colabo を無料で使い続けるのは避けるべきかとは思いますが)


Qt が便利なのでとりあえず C++ といいつつ Qt 依存になってしまいました(Qt5 および Qt6 で動作します)

このプログラム全体では、コンパイルされた Exe に PNG のパスを渡すと Stable Diffusion Web UI で作成された際のメタデータを表示します。PNG のチャンクデータを順番に読んで "tEXt" または "iTXt" チャンクを探します。
(日本語等が入っていると "iTXt" チャンクになります)

QString getPngInfo(const QString &fileName) に Stable Diffusion Web UI で作成された PNG ファイルのパスを渡すと、呪文(prompt, negatie prompt)とか各種設定情報が文字列として返ってきます。
(その文字列の解析は、今のところ皆さんにおまかせします)

↑作ってみました:
QString getPngInfoJson(const QString &fileName) を呼ぶと各種設定情報を解析してJSONとして返します。
もし、バグがあったら直したいので、不具合のあった PNG ファイルを添付して javacommons@gmail.com まで電子メール送っていただければ対応します(google drive に置くとかでもいいです)。その際は、この記事にコメントで同時にご連絡ください。よろしくお願いします。

他の言語版も作りたいと思います。(次は C# 辺り!)

これを使って、なんか凄いソフト作ってくれや!
それでゃ!

pnginfo.cpp
#include <QtCore>

QString getPngInfo(const QString &fileName)
{
    QString result = "";
    //QFileInfo fi(fileName);
    //qDebug() << fi.size();
    QByteArray signature = "\x89PNG\r\n\x1a\n";
    //qDebug() << signature.size();
    int crcSize = 4;
    QFile file(fileName);
    if (!file.open(QIODevice::ReadOnly)) return "";
    QByteArray realSig = file.read(signature.size());
    if (realSig != signature) return "";
    for (;;)
    {
        QByteArray bytes = file.read(8);
        //qDebug() << "bytes.size()" << bytes.size();
        if (bytes.size() != 8) break;
        QBuffer buf(&bytes);
        if (!buf.open(QBuffer::ReadOnly))
        {
            break;
        }
        QByteArray lengthBytes = buf.read(4);
        quint32 length;
        QDataStream stream(lengthBytes);
        stream.setByteOrder(QDataStream::BigEndian);
        stream >> length;
        QByteArray chunkType = buf.read(4);
        if (chunkType == "tEXt")
        {
            qDebug() << chunkType << length;
            QByteArray s = file.read(length);
            //qDebug() << s;
            if (s.startsWith("parameters"))
            {
                s = s.mid(10);
                if (s.size() > 0 && s.at(0) == 0)
                {
                    while (s.size() > 0 && s.at(0) == 0) s = s.mid(1);
                    result = QString::fromLatin1(s);
                    break;
                }
            }
        }
        else if (chunkType == "iTXt")
        {
            qDebug() << chunkType << length;
            QByteArray s = file.read(length);
            //qDebug() << s;
            if (s.startsWith("parameters"))
            {
                s = s.mid(10);
                if (s.size() > 0 && s.at(0) == 0)
                {
                    while (s.size() > 0 && s.at(0) == 0) s = s.mid(1);
                    result = QString::fromUtf8(s);
                    break;
                }
            }
        }
        else
        {
            qDebug() << chunkType << length;
            file.seek(file.pos()+length);
        }
        buf.close();
        file.seek(file.pos()+crcSize);
        //qDebug() << file.pos();
    }
    file.close();
    return result;
}

QString getPngInfoJson(const QString &fileName)
{
    QJsonObject o;
    QString infoString = getPngInfo(fileName);
    QStringList lines = infoString.split("\n");
    int phase = 0;
    QString prompt;
    QString negPrompt;
    foreach(QString line, lines)
    {
        //qDebug() << line;
        QRegularExpression re("(?<pair>[A-Z][a-zA-Z0-9 ]*: [^,]+)(, )?");
        if (phase == 0)
        {
            if (line.startsWith("Negative prompt: "))
            {
                negPrompt = line.mid(17);
                phase = 1;
                continue;
            }
            QRegularExpressionMatch m = re.match(line);
            if (m.hasMatch())
            {
                phase = 2;
                goto phase2;
            }
            prompt += "\n" + line;
            continue;
        }
        if (phase == 1)
        {
            QRegularExpressionMatch m = re.match(line);
            if (!m.hasMatch())
            {
                negPrompt += "\n" + line;
                continue;
            }
            phase = 2;
        }
phase2:
        QRegularExpressionMatchIterator i = re.globalMatch(line);
        QStringList words;
        while (i.hasNext()) {
            QRegularExpressionMatch match = i.next();
            QString word = match.captured("pair");
            words << word;
        }
        if (words.size() > 0)
        {
            foreach(QString word, words)
            {
                QRegularExpression rx1("(?<name>[A-Z][a-zA-Z0-9 ]*): (?<value>[^,]+)(, )?");
                QRegularExpressionMatch m = rx1.match(word);
                if (m.hasMatch())
                {
                    o[m.captured("name")] = m.captured("value");
                }
            }
        }
    }
    o["Prompt"] = prompt.trimmed();
    o["Negative prompt"] = negPrompt.trimmed();
    QJsonDocument result(o);
    return QString::fromUtf8(result.toJson());
}

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    qDebug() << app.arguments();
    if (app.arguments().size() != 2) return 1;
    QString result = getPngInfo(app.arguments()[1]);
    qDebug().noquote() << result;
    QString json = getPngInfoJson(app.arguments()[1]);
    qDebug().noquote() << json;
    return 0;
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?