#include "gentmpthread.h"
#include "cfg.h"
#include "globaldefine.h"
#include "tools.h"
#include "program/eaclock.h"
#include "program/eaudio.h"
#include "program/edclock.h"
#include "program/eenviron.h"
#include "program/egif.h"
#include "program/etext.h"
#include "program/etimer.h"
#include "program/evideo.h"
#include <QBuffer>
#include <QJsonArray>
#include <QProcess>
#include <QMessageBox>

GenTmpThread::GenTmpThread(ProgItem *progItem, const QString &prog_name, const QString &zip_file, const QString &password, QObject *parent) : QThread(parent), mProgItem(progItem), prog_name(prog_name), zip_file(zip_file), password(password) {
    connect(this, &QThread::finished, this, &QThread::deleteLater);
    connect(this, &GenTmpThread::onErr, this, [](QString err) {
        QMessageBox::warning(gMainWin, "GenTmpThread Error", err);
    });
}

void GenTmpThread::run() {
    auto srcDir = programsDir() + "/" + prog_name;
    dstDir = srcDir + "_tmp";
    //清空目录
    emit sProgress(tr("Preparing ..."), 0);
    QDir progsDir(programsDir());
    progsDir.remove(prog_name + "_tmp.zip");
    QDir dstQDir(dstDir);
    if(! dstQDir.exists() || dstQDir.removeRecursively()) {
        int iReTryCount = 0;
        while(!progsDir.mkdir(prog_name + "_tmp")) {
            QThread::msleep(250);
            iReTryCount++;
            if(iReTryCount > 4) break;
        }
    }

    QFile jsonFile(srcDir+"/pro.json");
    if(! jsonFile.open(QIODevice::ReadOnly)) {
        onErr("Can't open "+srcDir+"/pro.json");
        return;
    }
    auto data = jsonFile.readAll();
    jsonFile.close();
    QJsonParseError error;
    auto proJson = QJsonDocument::fromJson(data, &error);
    if(error.error != QJsonParseError::NoError) {
        onErr("Parse "+srcDir+"/pro.json Error: "+error.errorString());
        return;
    }

    //扫描节目, 返回多个节目数组
    emit sProgress(tr("Scan program ..."), 30);
    QStringList pageNames = QDir(srcDir).entryList(QDir::Dirs | QDir::NoDotAndDotDot);
    //查询 order 属性, 将最上层的放在转换后 layers 的最前面
    //一个 page.json 对应节目任务中的一个 items 里的 program
    QList<QJsonDocument> pageJsons;
    foreach(QString pageName, pageNames) {
        QFile jsonFile(srcDir+"/"+pageName+"/page.json");
        if(jsonFile.open(QIODevice::ReadOnly)) {
            auto data = jsonFile.readAll();
            jsonFile.close();
            auto pageJson = QJsonDocument::fromJson(data, &error);
            if(error.error == QJsonParseError::NoError) pageJsons.append(pageJson);
        }
    }
    std::sort(pageJsons.begin(), pageJsons.end(), [](const QJsonDocument &a, const QJsonDocument &b) {
        return a["order"].toInt() < b["order"].toInt();
    });

    QJsonArray items;
    foreach(QJsonDocument pageJson, pageJsons) {
        srcPageDir = srcDir + "/" + pageJson["name"].toString();
        items.append(cvtPage(pageJson, proJson));
    }

    emit sProgress(tr("Create json ..."), 60);
    QJsonObject json;
    json["_type"] = "PlayXixunTask";
    json["id"] = QUuid::createUuid().toString(QUuid::WithoutBraces);
    json["preDownloadURL"] = "http://192.168.8.202:23412/file/download?id=";
    json["notificationURL"] = "http://192.168.8.202:23412/test";
    json["task"] = QJsonObject{
        {"_id", QUuid::createUuid().toString(QUuid::WithoutBraces)},
        {"name", prog_name},
        {"cmdId", QUuid::createUuid().toString(QUuid::WithoutBraces)},
        {"items", items}
    };
    QFile program(dstDir + "/program");
    if(program.open(QFile::WriteOnly)) {
        program.write(QJsonDocument(json).toJson());
        program.close();
    }

    //如果是usb更新则生成压缩包，网络发送则不需要
    if(! zip_file.isEmpty()) {
        QStringList args{"a", zip_file, dstDir+"/*"};
        if(! password.isEmpty()) args << "-p"+password;
        QProcess::execute("7z.exe", args);
    }
}

//此处需要把幻灯片中的元素按层顺序排序，再放入layers中，每个元素对一个layer。ewindow中的多个顺序元素为一个层上的时间轴上的素材
QJsonObject GenTmpThread::cvtPage(const QJsonDocument &pageJson, const QJsonDocument &proJson) {
    auto audios = pageJson["audios"].toArray();
    auto sourceRepeat = pageJson["loop"].toBool();
    QJsonArray sources;
    int start = 0;
    foreach(QJsonValue audio, audios) {
        auto dur = audio["dur"].toInt();
        if(dur==0) continue;
        auto name = audio["name"].toString();
        if(name.isEmpty()) continue;
        QString file = audio["dir"].toString()+"/"+name;
        QFileInfo srcInfo(file);
        if(! srcInfo.isFile()) continue;
        QString id = Tools::fileMd5(file);
        if(id.isEmpty()) continue;
        QFile::copy(file, dstDir+"/"+id);
        QJsonObject source;
        source.insert("_type", "Audio");
        source["id"] = id;
        source["md5"] = id;
        source["name"] = name;
        source["size"] = srcInfo.size();
        source["fileExt"] = srcInfo.suffix().toLower();
        source["timeSpan"] = dur;
        source["playTime"] = start;
        source["vol"] = audio["vol"].toInt();
        source["enabled"] = true;
        source["left"] = -1;
        source["top"] = -1;
        source["width"] = 1;
        source["height"] = 1;
        sources.append(source);
        start += dur;
    }
    QJsonArray layers;
    if(! sources.isEmpty()) layers.append(QJsonObject{{"repeat", sourceRepeat}, {"sources", sources}});

    auto elements = pageJson["elements"].toArray();
    foreach(auto ele, elements) {
        QString type = ele["elementType"].toString();
        auto geometry = ele["geometry"];
        QJsonArray sources;
        if(type=="Window") {
            auto elements = ele["elements"].toArray();
            auto left = geometry["x"];
            auto top = geometry["y"];
            auto width = geometry["w"];
            auto height = geometry["h"];
            QList<QJsonObject> eles;
            foreach(auto value, elements) eles.append(value.toObject());
            std::sort(eles.begin(), eles.end(), [](const QJsonObject &a, const QJsonObject &b) {
                return a["geometry"]["order"].toInt() < b["geometry"]["order"].toInt();
            });
            int playTime = 0;
            foreach(const auto ele, eles) {
                QJsonObject source = cvtEle(ele["elementType"].toString(), ele);
                if(source.isEmpty()) continue;
                source["left"] = left;
                source["top"] = top;
                source["width"] = width;
                source["height"] = height;
                source["playTime"] = playTime;
                playTime += source["timeSpan"].toInt();
                sources.append(source);
            }
        } else {
            QJsonObject source = cvtEle(type, ele.toObject());
            if(! source.isEmpty()) {
                if(mProgItem->mMaxWidth && (type=="Photo" || type=="Movie")) {
                    source["left"] = 0;
                    source["top"] = 0;
                    source["width"] = mProgItem->mMaxWidth;
                    source["height"] = mProgItem->mHeight * mProgItem->mSplitWidths.size();
                } else {
                    source["left"] = geometry["x"];
                    source["top"] = geometry["y"];
                    source["width"] = geometry["w"];
                    source["height"] = geometry["h"];
                }
                source["playTime"] = 0;
                sources.append(source);
            }
        }
        if(! sources.isEmpty()) {
            QJsonObject layer{{"repeat", sourceRepeat}, {"sources", sources}};
            auto border = ele["border"].toString();
            if(! border.isEmpty()) {
                QString bdSrc = ":res/borders/"+border;
                QString id = Tools::fileMd5(bdSrc);
                QFile::copy(bdSrc, dstDir+"/"+id);
                auto borderSize = ele["borderSize"];
                if(borderSize.isUndefined() || borderSize.isNull()){
                    QImage img(bdSrc);
                    borderSize = QJsonArray{img.width(), img.height()};
                }
                layer.insert("border", QJsonObject{
                    {"img", id},
                    {"eff", ele["borderEff"]},
                    {"speed", ele["borderSpeed"]},
                    {"img_size", borderSize},
                    {"geometry", QJsonArray{geometry["x"], geometry["y"], geometry["w"], geometry["h"]}}
                });
            }
            layers.append(layer);
        }
    }

    QJsonArray schedules, plans = pageJson["plans"].toArray();
    auto validDate = pageJson["validDate"];
    bool isValid = validDate["isValid"].toBool();
    if(plans.isEmpty()) {
        if(isValid) {
            QJsonObject schedule;
            schedule["dateType"] = "Range";
            schedule["startDate"] = validDate["start"];
            schedule["endDate"] = validDate["end"];
            schedule["timeType"] = "All";
            schedule["filterType"] = "None";
            schedule["monthFilter"] = QJsonArray();
            schedule["weekFilter"] = QJsonArray();
            schedules.append(schedule);
        }
    } else {
        foreach(QJsonValue plan, plans) {
            QJsonObject schedule;
            if(isValid) {
                schedule["dateType"] = "Range";
                schedule["startDate"] = validDate["start"];
                schedule["endDate"] = validDate["end"];
            } else schedule["dateType"] = "All";
            schedule["timeType"] = "Range";
            schedule["startTime"] = plan["start"];
            schedule["endTime"] = plan["end"];
            auto weekly = plan["weekly"];
            schedule["weekFilter"] = weekly;
            schedule["filterType"] = weekly.toArray().isEmpty() ? "None" : "Week";
            schedule["monthFilter"] = QJsonArray();
            schedules.append(schedule);
        }
    }
    return QJsonObject{
        {"_id", QUuid::createUuid().toString(QUuid::WithoutBraces)},
        {"priority", 0},
        {"version", 0},
        {"schedules", schedules},
        {"_program", QJsonObject{
            {"_id", QUuid::createUuid().toString(QUuid::WithoutBraces)},
            {"totalSize", 0},
            {"name", pageJson["name"].toString()},
            {"width", proJson["resolution"]["w"]},
            {"height", proJson["resolution"]["h"]},
            {"splitWidths", proJson["splitWidths"]},
            {"_company", "alahover"},
            {"_department", "alahover"},
            {"_role", "alahover"},
            {"_user", "alahover"},
            {"__v", 0},
            {"dateCreated", pageJson["last_edit"]},
            {"layers", layers}
        }},
        {"repeatTimes", pageJson["repeat"]}
    };
}

QJsonObject GenTmpThread::cvtEle(const QString &type, const QJsonObject &ele) {
    if(type=="Text") return convertText(ele);
    else if(type=="Photo") return convertPhoto(ele);
    else if(type=="Movie") return EVideo::genProg(ele, dstDir, mProgItem);
    else if(type=="Gif") return convertGif(ele);
    else if(type=="DClock") return convertDClock(ele);
    else if(type=="AClock") return convertAClock(ele);
    else if(type=="Temp") return EEnviron::genProg(ele, dstDir, srcPageDir);
    else if(type=="Web") return convertWeb(ele);
    else if(type=="Timer") return convertTimer(ele);
    else if(type=="Audio") return EAudio::genProg(ele, dstDir);
    return QJsonObject();
}

QJsonObject GenTmpThread::convertText(const QJsonObject &json) {
    EText::Data attr;
    EText::setElement(json, attr);
    QJsonObject source;
    auto type = mProgItem->mMaxWidth ? "SplitText" : "MultiPng";
    source["_type"] = type;
    source["name"] = type;
    source["id"] = res_id++;
    auto widget = json["widget"];
    QJsonArray srcFiles = widget["files"].toArray();
    source["iPicCount"] = srcFiles.size();
    if(attr.playMode==EText::Flip) {
        source["playMode"] = "Flip";
        source["timeSpan"] = attr.flip.pageDuration * srcFiles.size();
        if(attr.flip.effectDuration >= attr.flip.pageDuration) attr.flip.effectDuration = attr.flip.pageDuration / 2;
    } else if(attr.playMode==EText::Scroll) {
        source["playMode"] = "Scroll";
        source["timeSpan"] = attr.scroll.duration;
    } else if(attr.playMode==EText::Static) {
        source["playMode"] = "Static";
        source["timeSpan"] = attr.duration;
    }
    QJsonArray arrayPics;
    QString filePrefix = srcPageDir+"/"+widget["idDir"].toString()+"/";
    for(int i=0; i<srcFiles.size(); i++) {
        QString srcFile = filePrefix + srcFiles[i].toString();
        QFile srcQFile(srcFile);
        if(! srcQFile.exists()) continue;
        QJsonObject arrayPic;
        QString id = Tools::fileMd5(srcFile);
        srcQFile.copy(dstDir+"/"+id);
        arrayPic["id"] = id;
        arrayPic["mime"] = "image/png";
        if(attr.playMode==EText::Flip) {
            arrayPic["effect"] = attr.flip.effect;
            arrayPic["effectSpeed"] = attr.flip.effectDuration;
            arrayPic["picDuration"] = attr.flip.pageDuration;
        } else if(attr.playMode==EText::Scroll) {
            if(attr.scroll.effect==0) arrayPic["effect"] = "right to left";
            else if(attr.scroll.effect==1) arrayPic["effect"] = "bottom to top";
            else if(attr.scroll.effect==2) arrayPic["effect"] = "left to right";
            else if(attr.scroll.effect==3) arrayPic["effect"] = "top to bottom";
            arrayPic["effectSpeed"] = attr.scroll.effectSpeed;
            arrayPic["picDuration"] = 0;
        } else if(attr.playMode==EText::Static) {
            arrayPic["effect"] = "no";
            arrayPic["effectSpeed"] = 0;
            arrayPic["picDuration"] = attr.duration;
        }
        arrayPics.append(arrayPic);
    }
    source["arrayPics"] = arrayPics;
    return source;
}
//转换图片
QJsonObject GenTmpThread::convertPhoto(const QJsonObject &ele){
    auto widget = ele["widget"];
    auto name = widget["file"].toString();
    QString srcFile = widget["path"].toString() + "/" + name;
    QFileInfo srcInfo(srcFile);
    QJsonObject source;
    if(! srcInfo.isFile()) return source;
    if(mProgItem->mMaxWidth) {
        srcFile += "-square.png";
        auto md5 = Tools::fileMd5(srcFile);
        if(md5.isEmpty()) return source;
        QFile::copy(srcFile, dstDir+"/"+md5);
        source["id"] = md5;
        source["md5"] = md5;
        source["size"] = srcInfo.size();
        source["name"] = name;
        source["fileExt"] = "png";
        source["mime"] = "image/png";
    } else {
        QImage img(srcFile);
        auto geometry = ele["geometry"];
        int width = geometry["w"].toInt();
        int height = geometry["h"].toInt();
        if(img.width() > width*2 && img.height() > height*2) {
            QBuffer buf;
            img.scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation).save(&buf, "PNG");
            QCryptographicHash cryptoHash(QCryptographicHash::Md5);
            cryptoHash.addData(buf.data());
            auto md5 = QString::fromLatin1(cryptoHash.result().toHex());
            QFile file(dstDir+"/"+md5);
            if(! file.open(QFile::WriteOnly)) return source;
            file.write(buf.data());
            file.close();
            source["id"] = md5;
            source["md5"] = md5;
            source["size"] = buf.size();
            source["name"] = srcInfo.baseName()+".png";
            source["fileExt"] = "png";
            source["mime"] = "image/png";
        } else {
            auto md5 = Tools::fileMd5(srcFile);
            if(md5.isEmpty()) return source;
            QFile::copy(srcFile, dstDir+"/"+md5);
            source["id"] = md5;
            source["md5"] = md5;
            source["size"] = srcInfo.size();
            source["name"] = name;
            QString sufLower = srcInfo.suffix().toLower();
            source["fileExt"] = sufLower;
            source["mime"] = "image/"+(sufLower=="jpg" ? "jpeg" : sufLower);
        }
    }
    source["_type"] = "Image";
    auto play = ele["play"];
    source["timeSpan"] = play["playDuration"];
    source["enabled"] = true;
    source["enabledBy"] = "";
    source["oldFilePath"] = "";
    static const int effSize = 13;
    static const int effs[effSize] = {0,1,3,4,5,6,7,9,10,11,12,17,18};
    int enterStyle = play["enterStyle"].toInt();
    source["entryEffect"] = enterStyle < effSize ? effs[enterStyle] : 0;
    source["exitEffect"] = 0;
    source["entryEffectTimeSpan"] = play["enterDuration"];
    source["exitEffectTimeSpan"] = 0;
    return source;
}
//转换图片
QJsonObject GenTmpThread::convertGif(const QJsonObject &json) {
    auto widget = json["widget"];
    auto path = widget["path"].toString();
    auto name = widget["file"].toString();
    QString srcFile = path + "/" + name;
    QFileInfo srcInfo(srcFile);
    if(! srcInfo.isFile()) return QJsonObject();
    QString id = Tools::fileMd5(srcFile);
    if(id.isEmpty()) return QJsonObject();
    QFile::copy(srcFile, dstDir+"/"+id);
    QJsonObject oRes;
    oRes["_type"] = "Image";
    oRes["id"] = id;
    oRes["md5"] = id;
    oRes["name"] = name;
    oRes["size"] = srcInfo.size();
    QString suffix = srcInfo.suffix().toLower();
    oRes["fileExt"] = suffix;
    oRes["mime"] = "image/gif";
    auto play = json["play"];
    oRes["timeSpan"] = play["playDuration"].toInt() * play["playTimes"].toInt();
    oRes["enabled"] = true;
    oRes["entryEffect"] = "None";
    oRes["exitEffect"] = "None";
    oRes["entryEffectTimeSpan"] = 0;
    oRes["exitEffectTimeSpan"] = 0;
    return oRes;
}
QJsonObject GenTmpThread::convertDClock(const QJsonObject &json){
    QJsonObject oRes;
    oRes["_type"] = "DigitalClockNew";
    oRes["name"] = "DigitalClockNew";
    oRes["id"] = "";
    oRes["timeSpan"] = json["play"]["duration"];
    oRes["timezone"] = 8;
    auto widget = json["widget"];
    oRes["year"]   = widget["year"];
    oRes["month"]   = widget["month"];
    oRes["day"]   = widget["day"];
    oRes["hour"] = widget["hour"];
    oRes["min"] = widget["min"];
    oRes["sec"] = widget["sec"];
    oRes["weekly"] = widget["weekly"];
    oRes["fullYear"] = widget["fullYear"];
    oRes["hour12"] = widget["12Hour"];
    oRes["AmPm"] = widget["AmPm"];
    oRes["dateStyle"] = widget["dateStyle"];
    oRes["timeStyle"] = widget["timeStyle"];
    oRes["multiline"] = widget["multiline"];
    oRes["fontSize"] = widget["font"]["size"];
    oRes["entryEffect"] = "None";
    oRes["exitEffect"] = "None";
    oRes["entryEffectTimeSpan"] = 0;
    oRes["exitEffectTimeSpan"] = 0;

    auto fontVal = widget["font"];
    auto textColor = Tools::int2Color(fontVal["color"].toInt());
    QFont font(fontVal["family"].toString(), fontVal["size"].toInt());
    font.setBold(fontVal["bold"].toBool());
    font.setItalic(fontVal["italics"].toBool());
    font.setUnderline(fontVal["underline"].toBool());

    font.setStyleStrategy(gTextAntialiasing ? QFont::PreferAntialias : QFont::NoAntialias);
    QFontMetricsF metricF(font);
    oRes["spaceWidth"] = metricF.horizontalAdvance(" ");
    QFontMetrics metric(font);
    QColor color(textColor);
    QJsonArray imgs;
    for(auto str : str0_9) Tools::saveImg2(dstDir, metric, font, color, imgs, str, str);
    Tools::saveImg2(dstDir, metric, font, color, imgs, tr("MON"), "MON");
    Tools::saveImg2(dstDir, metric, font, color, imgs, tr("TUE"), "TUE");
    Tools::saveImg2(dstDir, metric, font, color, imgs, tr("WED"), "WED");
    Tools::saveImg2(dstDir, metric, font, color, imgs, tr("THU"), "THU");
    Tools::saveImg2(dstDir, metric, font, color, imgs, tr("FRI"), "FRI");
    Tools::saveImg2(dstDir, metric, font, color, imgs, tr("SAT"), "SAT");
    Tools::saveImg2(dstDir, metric, font, color, imgs, tr("SUN"), "SUN");
    Tools::saveImg2(dstDir, metric, font, color, imgs, tr("AM"), "AM");
    Tools::saveImg2(dstDir, metric, font, color, imgs, tr("PM"), "PM");
    Tools::saveImg2(dstDir, metric, font, color, imgs, "年", "YEAR");
    Tools::saveImg2(dstDir, metric, font, color, imgs, "月", "MONTH");
    Tools::saveImg2(dstDir, metric, font, color, imgs, "日", "DAY");
    Tools::saveImg2(dstDir, metric, font, color, imgs, ":", "maohao");
    Tools::saveImg2(dstDir, metric, font, color, imgs, "/", "xiegang");
    Tools::saveImg2(dstDir, metric, font, color, imgs, "-", "hengxian");
    oRes["iPicCount"] = imgs.size();
    oRes["arrayPics"] = imgs;
    return oRes;
}
QJsonObject GenTmpThread::convertAClock(const QJsonObject &json) {
    auto widget = json["widget"];
    QString srcFile = srcPageDir + "/" + widget["selfCreateDialName"].toString();
    QFile srcQFile(srcFile);
    if(! srcQFile.exists()) return QJsonObject();
    QString id = Tools::fileMd5(srcFile);
    srcQFile.copy(dstDir+"/"+id);
    QJsonObject oRes;
    oRes["_type"] = "AnalogClock";
    oRes["id"] = id;
    oRes["md5"] = id;
    oRes["mime"] = "image/png";
    oRes["name"] = "001";
    oRes["timeSpan"] = json["play"]["duration"];
    oRes["shade"] = 0;//表盘形状
    oRes["opacity"] = 1;//透明度
    oRes["showBg"] = false;//是否显示背景色
    oRes["bgColor"] = 0;
    oRes["showHourScale"] = false;//是否显示时针
    auto color = widget["hourMarkColor"];
    oRes["scaleHourColor"] = color.isString() ? color : Tools::int2Color(color.toInt()).name();
    color = widget["minMarkColor"];
    oRes["scaleMinColor"] = color.isString() ? color : Tools::int2Color(color.toInt()).name();
    color = widget["hourHandColor"];
    oRes["pinHourColor"] = color.isString() ? color : Tools::int2Color(color.toInt()).name();
    color = widget["minHandColor"];
    oRes["pinMinColor"] = color.isString() ? color : Tools::int2Color(color.toInt()).name();
    color = widget["secHandColor"];
    oRes["pinSecColor"] = color.isString() ? color : Tools::int2Color(color.toInt()).name();
    oRes["pinHourLen"] = widget["hhLen"].toInt();
    oRes["pinMinLen"] = widget["mhLen"].toInt();
    oRes["pinSecLen"] = widget["shLen"].toInt();
    oRes["pinHourWidth"] = widget["hhWidth"].toInt();
    oRes["pinMinWidth"] = widget["mhWidth"].toInt();
    oRes["pinSecWidth"] = widget["shWidth"].toInt();
    oRes["showMinScale"] = false;
    oRes["scaleStyle"] = 0;
    oRes["showScaleNum"] = false;
    oRes["pinStyle"] = 1;
    oRes["showSecond"] = widget["showSecHand"];
    //下同Video
    oRes["entryEffect"] = "None";
    oRes["exitEffect"] = "None";
    oRes["entryEffectTimeSpan"] = 0;
    oRes["exitEffectTimeSpan"] = 0 ;
    return oRes;
}

QJsonObject GenTmpThread::convertWeb(const QJsonObject &res) {
    QJsonObject dst;
    dst["_type"] = "WebURL";
    dst["id"] = "";
    dst["name"] = "WebURL";
    dst["url"] = res["url"];
    dst["timeSpan"] = res["duration"];
    return dst;
}
QJsonObject GenTmpThread::convertTimer(const QJsonObject &json) {
    QJsonObject oRes;
    oRes["_type"] = "Timer";
    oRes["name"] = "Timer";
    oRes["id"] = "";
    oRes["targetTime"] = json["targetTime"];
    oRes["isDown"] = json["isDown"];
    oRes["hasDay"] = json["hasDay"];
    oRes["hasHour"] = json["hasHour"];
    oRes["hasMin"] = json["hasMin"];
    oRes["hasSec"] = json["hasSec"];
    oRes["isMultiline"] = json["isMultiline"];
    auto text = json["text"].toString();
    oRes["text"] = text;
    QFont font(json["font"].toString(), json["fontSize"].toInt());
    font.setBold(json["fontBold"].toBool());
    font.setItalic(json["fontItalic"].toBool());
    font.setUnderline(json["fontUnderline"].toBool());
    oRes["font"] = font.family();
    oRes["fontSize"] = font.pixelSize();
    oRes["fontBold"] = font.bold();
    oRes["fontItalic"] = font.italic();
    oRes["fontUnderline"] = font.underline();
    auto textColor = json["textColor"].toString();
    oRes["textColor"] = textColor;
    oRes["backColor"] = json["backColor"];
    oRes["timeSpan"] = json["duration"];
    font.setStyleStrategy(gTextAntialiasing ? QFont::PreferAntialias : QFont::NoAntialias);
    QFontMetricsF metricF(font);
    oRes["spaceWidth"] = metricF.horizontalAdvance(" ");
    QFontMetrics metric(font);
    QColor color(textColor);
    QJsonObject imgs;
    for(auto str : str0_9) Tools::saveImg(dstDir, metric, font, color, imgs, str, str);
    Tools::saveImg(dstDir, metric, font, color, imgs, tr("day"), "day");
    Tools::saveImg(dstDir, metric, font, color, imgs, tr("hour"), "hour");
    Tools::saveImg(dstDir, metric, font, color, imgs, tr("min"), "min");
    Tools::saveImg(dstDir, metric, font, color, imgs, tr("sec"), "sec");
    if(! text.isEmpty()) {
        auto innerW = json["innerW"].toInt();
        auto innerH = json["innerH"].toInt();
        auto rect = metric.boundingRect(0, 0, innerW, innerH, Qt::AlignCenter | Qt::TextWordWrap, text);
        QImage img(qMin(rect.width(), innerW), qMin(rect.height(), innerH), QImage::Format_ARGB32);
        img.fill(Qt::transparent);
        {
            QPainter painter(&img);
            painter.setFont(font);
            painter.setPen(color);
            painter.drawText(QRectF(0, 0, img.width(), img.height()), text, QTextOption(Qt::AlignCenter));
        }
        QByteArray data;
        QBuffer buffer(&data);
        buffer.open(QIODevice::WriteOnly);
        if(img.save(&buffer, "PNG")) {
            QCryptographicHash cryptoHash(QCryptographicHash::Md5);
            cryptoHash.addData(data);
            auto md5 = QString::fromLatin1(cryptoHash.result().toHex());
            QFile file(dstDir+"/"+md5);
            if(file.open(QFile::WriteOnly)) {
                file.write(data);
                file.close();
                imgs.insert("text", md5);
            } else emit onErr("convertTimer file.open false");
        } else emit onErr("convertTimer img.save false");
    }
    oRes["imgs"] = imgs;
    return oRes;
}
