<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <th:block th:include="include :: header('科大讯飞声音克隆')" />
</head>
<body class="gray-bg">
<div class="container-div">
    <div class="row">
        <div class="col-sm-12 search-collapse">
            <pre><strong>科大讯飞声音克隆注意事项：</strong>
1. 请先在“科大讯飞TTS配置”中配置 app-id、api-key、api-secret，本页面直接复用该配置。
2. 先点击“加载训练文本”，按选中的文本内容录音，录音内容必须与训练文本一致。
3. 建议上传 24kHz 及以上、单声道、16bit 的 wav 或 mp3 文件，时长尽量控制在 40 秒内。
4. 训练提交后一般需要等待几十秒，成功后会返回音色ID，可直接在页面里试听。</pre>
        </div>

        <div class="col-sm-12 search-collapse">
            <form id="trainForm">
                <div class="select-list">
                    <ul>
                        <li>
                            <label th:text="#{xfvoiceclone.form.voice-name}"></label>
                            <input type="text" name="voice_name" id="voice_name" />
                        </li>
                        <li>
                            <label th:text="#{xfvoiceclone.form.task-name}"></label>
                            <input type="text" name="task_name" id="task_name" />
                        </li>
                        <li>
                            <label th:text="#{xfvoiceclone.form.resource-name}"></label>
                            <input type="text" name="resource_name" id="resource_name" />
                        </li>
                        <li>
                            <label th:text="#{xfvoiceclone.form.language}"></label>
                            <select name="language" id="language">
                                <option value="zh">中文</option>
                                <option value="en">英文</option>
                                <option value="jp">日语</option>
                                <option value="ko">韩语</option>
                                <option value="ru">俄语</option>
                            </select>
                        </li>
                        <li>
                            <label th:text="#{xfvoiceclone.form.engine-version}"></label>
                            <select name="engine_version" id="engine_version">
                                <option value="">标准版 x5_clone</option>
                                <option value="omni_v1">多风格版 x6_clone</option>
                            </select>
                        </li>
                        <li>
                            <label th:text="#{xfvoiceclone.form.sex}"></label>
                            <select name="sex" id="sex">
                                <option value="1">男</option>
                                <option value="2">女</option>
                            </select>
                        </li>
                        <li>
                            <label th:text="#{xfvoiceclone.form.age-group}"></label>
                            <select name="age_group" id="age_group">
                                <option value="1">儿童</option>
                                <option value="2" selected>青年</option>
                                <option value="3">中年</option>
                                <option value="4">中老年</option>
                            </select>
                        </li>
                        <li>
                            <label th:text="#{xfvoiceclone.form.denoise}"></label>
                            <select name="denoise" id="denoise">
                                <option value="0">关闭</option>
                                <option value="1">开启</option>
                            </select>
                        </li>
                        <li>
                            <label th:text="#{xfvoiceclone.form.mos-ratio}"></label>
                            <input type="number" step="0.1" min="0" max="5" name="mos_ratio" id="mos_ratio" value="0.0" />
                        </li>
                        <li style="width: 100%">
                            <label th:text="#{xfvoiceclone.form.train-text}"></label>
                            <select name="text_seg_id" id="text_seg_id" style="width: 480px;"></select>
                            <a class="btn btn-success btn-xs" href="javascript:void(0)" onclick="$.operate.loadTrainTexts()">
                                <i class="fa fa-refresh"></i> &nbsp;<span th:text="#{xfvoiceclone.form.load-train-texts}"></span>
                            </a>
                        </li>
                        <li style="width: 100%">
                            <label th:text="#{xfvoiceclone.form.train-content}"></label>
                            <textarea id="seg_text" cols="80" rows="4" readonly="readonly"></textarea>
                        </li>
                        <li>
                            <a class="btn btn-info btn-xs" href="javascript:void(0)" onclick="$.operate.importFile()">
                                <i class="fa fa-upload"></i> &nbsp;<span th:text="#{xfvoiceclone.form.choose-audio-file}"></span>
                            </a>
                        </li>
                    </ul>
                </div>
            </form>
        </div>

        <div class="col-sm-12 search-collapse">
            <form id="resultForm">
                <div class="select-list">
                    <ul>
                        <li>
                            <label th:text="#{xfvoiceclone.form.task-id}"></label>
                            <input type="text" name="taskId" id="taskId" />
                            <a class="btn btn-primary btn-xs" href="javascript:void(0)" onclick="$.operate.queryTrainResult()">
                                <i class="fa fa-search"></i> &nbsp;<span th:text="#{xfvoiceclone.form.query-train-result}"></span>
                            </a>
                        </li>
                        <li>
                            <label th:text="#{xfvoiceclone.form.asset-id}"></label>
                            <input type="text" name="assetId" id="assetId" />
                        </li>
                        <li>
                            <label th:text="#{xfvoiceclone.form.train-status}"></label>
                            <input type="text" id="trainStatusText" readonly="readonly" />
                        </li>
                    </ul>
                </div>
            </form>
        </div>

        <div class="col-sm-12 search-collapse" id="tts_area" style="display: none">
            <form id="ttsForm">
                <div class="select-list">
                    <ul>
                        <li style="width: 100%">
                            <label th:text="#{xfvoiceclone.form.test-text}"></label>
                            <textarea placeholder="请输入试听文本" name="tts_text" id="tts_text" cols="80" rows="4"></textarea>
                        </li>
                        <li>
                            <a class="btn btn-primary btn-rounded btn-sm" href="javascript:void(0)" onclick="$.operate.ttsTest()">
                                <i class="fa fa-volume-up"></i> &nbsp;<span th:text="#{btn.ok}"></span>
                            </a>
                        </li>
                        <li style="width: 100%">
                            <audio id="test_player" controls="controls" style="width: 480px; display: none"></audio>
                        </li>
                    </ul>
                </div>
            </form>
        </div>
    </div>
</div>

<th:block th:include="include :: footer" />
<script th:inline="javascript">
    var prefix = ctx + "aicall/xfvoiceclone";
    var trainTextMap = {};

    $(function() {
        $.operate.loadTrainTexts();
        $("#text_seg_id").on("change", function() {
            var segId = $(this).val();
            $("#seg_text").val(trainTextMap[segId] || "");
        });
    });

    $.operate.loadTrainTexts = function() {
        $.get(prefix + "/getTrainTexts", function(respJson) {
            if (respJson.code !== 0) {
                $.modal.msgError(respJson.msg || i18n('xfvoiceclone.form.load-train-texts-failed'));
                return;
            }
            var select = $("#text_seg_id");
            select.empty();
            trainTextMap = {};
            var rows = respJson.data || [];
            for (var i = 0; i < rows.length; i++) {
                var row = rows[i];
                var segId = row.segId + "";
                var segText = row.segText || "";
                trainTextMap[segId] = segText;
                select.append('<option value="' + segId + '">' + segId + ' - ' + segText + '</option>');
            }
            if (rows.length > 0) {
                select.val(rows[0].segId + "");
                $("#seg_text").val(rows[0].segText || "");
            } else {
                $("#seg_text").val("");
            }
        }).fail(function() {
            $.modal.msgError(i18n('xfvoiceclone.form.load-train-texts-failed'));
        });
    };

    $.operate.importFile = function() {
        var voiceName = $("#voice_name").val().trim();
        if (voiceName === "") {
            $.modal.msgError(i18n('xfvoiceclone.form.voice-name-required'));
            $("#voice_name")[0].focus();
            return;
        }

        var textSegId = $("#text_seg_id").val();
        if (!textSegId) {
            $.modal.msgError(i18n('xfvoiceclone.form.train-text-required'));
            return;
        }

        var input = document.createElement('input');
        input.type = 'file';
        input.accept = '.wav,.mp3,.m4a,.pcm';
        input.onchange = function(event) {
            var file = event.target.files[0];
            if (!file) {
                return;
            }
            var formData = new FormData();
            formData.append('file', file);
            formData.append('voice_name', voiceName);
            formData.append('task_name', $("#task_name").val().trim());
            formData.append('resource_name', $("#resource_name").val().trim());
            formData.append('language', $("#language").val());
            formData.append('engine_version', $("#engine_version").val());
            formData.append('sex', $("#sex").val());
            formData.append('age_group', $("#age_group").val());
            formData.append('denoise', $("#denoise").val());
            formData.append('mos_ratio', $("#mos_ratio").val().trim());
            formData.append('text_seg_id', textSegId);

            $.modal.msg(i18n('xfvoiceclone.form.training-tips'));
            $.ajax({
                url: prefix + "/uploadAndTrain",
                type: "POST",
                data: formData,
                processData: false,
                contentType: false,
                success: function(respJson) {
                    if (respJson.code !== 0) {
                        $.modal.msgError(respJson.msg || i18n('xfvoiceclone.form.upload-failed-tips'));
                        return;
                    }
                    $.modal.msgSuccess(respJson.msg || i18n('xfvoiceclone.form.upload-success-tips'));
                    var data = respJson.data || {};
                    if (data.taskId) {
                        $("#taskId").val(data.taskId);
                    }
                    if (data.assetId) {
                        $("#assetId").val(data.assetId);
                    }
                    $("#trainStatusText").val($.operate.renderTrainStatus(data.trainStatus, data.failedDesc));
                    $("#tts_area").css("display", "block");
                },
                error: function() {
                    $.modal.msgError(i18n('xfvoiceclone.form.upload-failed-tips'));
                }
            });
        };
        input.click();
    };

    $.operate.queryTrainResult = function() {
        var taskId = $("#taskId").val().trim();
        if (taskId === "") {
            $.modal.msgError(i18n('xfvoiceclone.form.task-id-required'));
            $("#taskId")[0].focus();
            return;
        }
        $.post(prefix + "/queryTrainResult", {taskId: taskId}, function(respJson) {
            if (respJson.code !== 0) {
                $.modal.msgError(respJson.msg || i18n('xfvoiceclone.form.query-train-result-failed'));
                return;
            }
            var data = respJson.data || {};
            if (data.assetId) {
                $("#assetId").val(data.assetId);
                $("#tts_area").css("display", "block");
            }
            $("#trainStatusText").val($.operate.renderTrainStatus(data.trainStatus, data.failedDesc));
            $.modal.msgSuccess(respJson.msg || i18n('xfvoiceclone.form.query-train-result-success'));
        }).fail(function() {
            $.modal.msgError(i18n('xfvoiceclone.form.query-train-result-failed'));
        });
    };

    $.operate.renderTrainStatus = function(status, failedDesc) {
        if (status === 1) {
            return i18n('xfvoiceclone.status.success');
        }
        if (status === -1) {
            return i18n('xfvoiceclone.status.training');
        }
        if (status === 2) {
            return i18n('xfvoiceclone.status.pending');
        }
        if (status === 0) {
            return i18n('xfvoiceclone.status.failed') + (failedDesc ? ': ' + failedDesc : '');
        }
        return '';
    };

    $.operate.ttsTest = function() {
        if ($.operate.ttsTest.processing === true) {
            $.modal.msg(i18n('xfvoiceclone.form.tts-processing'));
            return;
        }

        var assetId = $("#assetId").val().trim();
        if (assetId === "") {
            $.modal.msgError(i18n('xfvoiceclone.form.asset-id-required'));
            $("#assetId")[0].focus();
            return;
        }

        var text = $("#tts_text").val().trim();
        if (text === "") {
            $.modal.msgError(i18n('xfvoiceclone.form.test-text-required'));
            $("#tts_text")[0].focus();
            return;
        }

        $.operate.ttsTest.processing = true;
        var formData = new FormData();
        formData.append('assetId', assetId);
        formData.append('text', text);
        formData.append('engine_version', $("#engine_version").val());
        $.ajax({
            url: prefix + "/ttsTest",
            type: "POST",
            data: formData,
            processData: false,
            contentType: false,
            success: function(respJson) {
                if (respJson.code !== 0) {
                    $.modal.msgError(respJson.msg || i18n('xfvoiceclone.admin.tts-test.failed-tips'));
                    return;
                }
                var data = respJson.data || {};
                var player = $("#test_player");
                var audioSrc = 'data:' + (data.audioMime || 'audio/mp3') + ';base64,' + data.audioBase64;
                player.css("display", "block");
                player.attr("src", audioSrc);
                player[0].play();
                $.modal.msgSuccess(respJson.msg || i18n('xfvoiceclone.admin.tts-test.success-tips'));
            },
            complete: function() {
                $.operate.ttsTest.processing = false;
            },
            error: function() {
                $.operate.ttsTest.processing = false;
                $.modal.msgError(i18n('xfvoiceclone.admin.tts-test.failed-tips'));
            }
        });
    };
</script>
</body>
</html>
