let path = require('path') let fspromises = require('fs').promises import { Tools } from '../tools' import { DEFINE_STRING } from '../../define/define_string' import { PublicMethod } from '../Public/publicMethod' import { define } from '../../define/define' import { get, has, isEmpty } from 'lodash' import { errorMessage, successMessage } from '../Public/generalTools' import { ServiceBase } from '../../define/db/service/serviceBase' import { GetDoubaoErrorResponse, GetKimiErrorResponse, GetOpenAISuccessResponse, GetRixApiErrorResponse } from '../../define/response/openAIResponse' import axios from 'axios' import { ValidateJson } from '../../define/Tools/validate' import { RetryWithBackoff } from '../../define/Tools/common' const { v4: uuidv4 } = require('uuid') // 引入UUID库来生成唯一标识符 let tools = new Tools() export class Writing extends ServiceBase { pm: PublicMethod constructor(global) { super() this.pm = new PublicMethod(global) axios.defaults.baseURL = define.lms } /** * AI 请求发送 * @param {*} setting * @param {*} aiData * @param {*} word * @returns */ async AIRequest(setting, aiData, word): Promise { // 开始请求AI let axiosRes = await axios.post('/lms/Forward/ForwardWord', { promptTypeId: setting.gptType, promptId: setting.gptData, gptUrl: aiData.gpt_url + '/v1/chat/completions', model: aiData.model, machineId: global.machineId, apiKey: aiData.api_key, word: word }) // 判断返回的状态,如果是失败的话直接返回错误信息 if (axiosRes.status != 200) { throw new Error('请求失败') } let dataRes = axiosRes.data if (dataRes.code == 1) { // 获取成功 // 解析返回的数据 return GetOpenAISuccessResponse(dataRes.data); } else { // 系统报错 if (dataRes.code == 5000) { throw new Error('系统错误,错误信息如下:' + dataRes.message) } else { // 处理不同类型的错误消息 if (setting.gptAI == 'laiapi') { throw new Error(GetRixApiErrorResponse(dataRes.data)) } else if (setting.gptAI == 'kimi') { throw new Error(GetKimiErrorResponse(dataRes.data)) } else if (setting.gptAI == 'doubao') { throw new Error(GetDoubaoErrorResponse(dataRes.data)) } else { throw new Error(dataRes.data) } } } } /** * 流式请求接口 * @param setting * @param aiData * @param word */ async AIRequestStream(setting, aiData, word, oldData: string) { let body = { promptTypeId: setting.gptType, promptId: setting.gptData, gptUrl: aiData.gpt_url, model: aiData.model, machineId: global.machineId, apiKey: aiData.api_key, word: word } var myHeaders = new Headers(); myHeaders.append("User-Agent", "Apifox/1.0.0 (https://apifox.com)"); myHeaders.append("Content-Type", "application/json"); var requestOptions = { method: 'POST', headers: myHeaders, body: JSON.stringify(body), }; let resData = '\n\n'; return new Promise((resolve, reject) => { fetch(define.lms + "/lms/Forward/ForwardWordStream", requestOptions) .then(response => { if (!response.body) { throw new Error('ReadableStream not yet supported in this browser.'); } const reader = response.body.getReader(); return new ReadableStream({ start(controller) { function push() { reader.read().then(({ done, value }) => { if (done) { controller.close(); resolve(resData) return; } // 假设服务器发送的是文本数据 const text = new TextDecoder().decode(value); resData += text // 将数据返回前端 global.newWindow[0].win.webContents.send(DEFINE_STRING.GPT.GPT_STREAM_RETURN, oldData + resData) controller.enqueue(value); // 可选:将数据块放入流中 push(); }).catch(err => { controller.error(err); reject(err) }); } push(); } }); }) .catch(error => { reject(error) }); }) } async SplitWord(word: string, wordCount: number) { let word_list = word.split('\n') let new_word = [] let tmp_str = '' let result = [] for (let i = 0; i < word_list.length; i++) { const element = word_list[i]; if (tmp_str.length + element.length > wordCount) { result.push({ index: i, word: new_word.join('\n') }) new_word = [] tmp_str = "" new_word.push(element); } else { tmp_str += ',' + element new_word.push(element); } } result.push({ index: word_list.length, word: new_word.join('\n') }) return result } /** * 文案处理,GPT进行推理 * @param {*} setting * @param {*} word * @returns */ async ActionStart(setting, word) { try { await this.InitService() // console.log(setting, word) if (isEmpty(setting.gptType)) { throw new Error('请选择GPT类型') } if (isEmpty(setting.gptData)) { throw new Error('请选择GPT预设') } if (isEmpty(setting.gptAI)) { throw new Error('请选择使用的AI') } // 判断对应的AI的设置是不是有 let aiSetting = this.softService.GetSoftWarePropertyData('aiSetting') if (isEmpty(aiSetting)) { throw new Error('请先设置AI设置') } let tryP = ValidateJson(aiSetting) if (!tryP) { throw new Error('AI设置的数据格式不正确') } aiSetting = JSON.parse(aiSetting) // 判断是不是有对应的AI设置 let aiData = get(aiSetting, setting.gptAI) for (const aid in aiData) { if (isEmpty(aid)) { throw new Error('请先设置AI设置') } } if (isEmpty(word)) { throw new Error('请先设置文案') } let result = '' if (setting.isSplit) { // 这边拆分文案 let spiltWord = await this.SplitWord(word, setting.splitNumber) for (let i = 0; i < spiltWord.length; i++) { const element = spiltWord[i]; if (setting.isStream) { result += await RetryWithBackoff(async () => { return await this.AIRequestStream(setting, aiData, element.word, result) }, 3, 1000) + '\n' } else { result = await RetryWithBackoff(async () => { return await this.AIRequest(setting, aiData, word) }, 3, 1000) } } } else { if (setting.isStream) { result += await RetryWithBackoff(async () => { return await this.AIRequestStream(setting, aiData, word, '') }, 3, 1000) + '\r\n\n' } else { result = await RetryWithBackoff(async () => { return await this.AIRequest(setting, aiData, word) }, 3, 1000) } } return successMessage(result, "执行文案相关任务成功", 'Writing_ActionStart'); } catch (error) { return errorMessage( '执行文案相关任务失败,失败信息如下:' + error.toString(), 'Writing_ActionStart' ) } } //#region 下面是文案的处理相关 /** * 将文案信息写入到本地的文案文件中 * @param {*} value */ async SaveWordTxt(value) { try { let word_path = path.join(global.config.project_path, '文案.txt') await tools.writeArrayToFile(value, word_path) return { code: 1, message: '保存成功' } } catch (error) { throw new Error(error) } } /** * 将分镜的时间信息添加道配置文件中 * @param {*} value 是一个数组,0 :写入的数据 1:写入的属性 2:是否需要解析 */ async SaveCopywritingInformation(value) { try { return await this.pm.SaveConfigJsonProperty(value) } catch (error) { return { code: 0, message: error.toString() } } } /** * 获取Config.json文件中指定的属性 * @param {Array} value 传入的值 0 : 需要获取的属性 1: 返回的默认值 * @returns */ async GetConfigJson(value) { try { return await this.pm.GetConfigJson(value, false) } catch (error) { return { code: 0, message: error.toString() } } } /** * 获取当前项目下面的文案 */ async GetProjectWord() { try { // 先判断当前的项目文件下面是不是又配置文件。没有才读取文案 let srt_config_path = path.join(global.config.project_path, 'scripts/config.json') let isExist = await tools.checkExists(srt_config_path) let data = null let isImformation = false if (isExist) { let config_1 = JSON.parse(await fspromises.readFile(srt_config_path)) isImformation = has(config_1, 'srt_time_information') if (isImformation) { data = JSON.parse(await fspromises.readFile(srt_config_path)).srt_time_information } } if (!isExist || !isImformation) { let word_path = path.join(global.config.project_path, '文案.txt') let isExistWord = await tools.checkExists(word_path) if (!isExistWord) { return { code: 0, message: '没有文案文件' } } let data = await fspromises.readFile(word_path, { encoding: 'utf-8' }) let lines = data.split(/\r?\n/) // 打印或返回这个数组 // console.log(lines); // 判断是不是有洗稿后的文件 let new_srt_path = path.join(global.config.project_path, 'new_word.txt') let isExistAfterGPTWord = await tools.checkExists(new_srt_path) let after_data = null if (isExistAfterGPTWord) { after_data = (await fspromises.readFile(new_srt_path, { encoding: 'utf-8' })).split( /\r?\n/ ) } // 判断抽帧文件是不是存在 // 返回图片信息 let old_image_path_list = await tools.getFilesWithExtensions( path.join(global.config.project_path, 'tmp/input_crop'), '.png' ) let res = [] let lastId = '' // 处理数据 for (let i = 0; i < lines.length; i++) { const line = lines[i] let id = uuidv4() let after_gpt = null if (after_data != null) { after_gpt = after_data[i] } let img_path = null if (old_image_path_list != null) { img_path = old_image_path_list[i] } let obj = { no: i + 1, id: id, lastId: lastId, word: line, old_image: img_path, after_gpt: after_gpt, start_time: null, end_time: null, timeLimit: null, subValue: [] } res.push(obj) lastId = id } return { code: 1, type: 0, data: res } } else { let data = JSON.parse(await fspromises.readFile(srt_config_path)).srt_time_information return { code: 1, type: 1, data: data } } } catch (error) { throw new Error(error) } } /** * 搭导入srt。然后加载时间轴。完全匹配失败的将会还是会导入然后手动手动切换 * @param {文案洗稿界面信息} textData */ async ImportSrtAndGetTime(data) { let textData = data[0] let init_num = textData.length let srt_path = data[1] let current_text = '' let iii = 0 let sss = '' try { if (!srt_path) { // 获取项目下面的所有的srt let srtfiles = await tools.getFilesWithExtensions(global.config.project_path, '.srt') if (srtfiles.length <= 0) { throw new Error('没有SRT文件') } srt_path = srtfiles[0] } let srt_data = (await fspromises.readFile(srt_path, 'utf-8')).toString('utf-8') const entries = srt_data.replace(/\r\n/g, '\n').split('\n\n') let data = entries .map((entry) => { const lines = entry.split('\n') if (lines.length >= 3) { const times = lines[1] const text = lines.slice(2).join(' ') const [start, end] = times.split(' --> ').map((time) => { const [hours, minutes, seconds] = time.split(':') const [sec, millis] = seconds.split(',') return ( (parseInt(hours) * 3600 + parseInt(minutes) * 60 + parseInt(sec)) * 1000 + parseInt(millis) ) }) return { start, end, text, id: uuidv4() } } }) .filter((entry) => entry) // 开始匹配(洗稿后的) let srt_list = [] let srt_obj = null let text_count = 0 let tmp_str = '' for (let i = 0; i < data.length;) { iii = i sss = data[i].after_gpt let srt_value = data[i].text current_text = `字幕: “${srt_value}” 和文案第${text_count + 1} 行数据 “${textData[text_count].after_gpt }” 数据不匹配(检查一下上下文)` let start_time = data[i].start let end_time = data[i].end let obj = { start_time, end_time, srt_value, id: data[i].id } // 判断当前字幕是不是在当前句 // 不能用简单的包含,而是将数据进行去除特殊符号拼接后判断是不是相同 tmp_str += srt_value if ( tools .removePunctuationIncludingEllipsis(textData[text_count].after_gpt) .startsWith(tools.removePunctuationIncludingEllipsis(tmp_str)) ) { if (srt_obj == null) { srt_obj = {} srt_obj.id = uuidv4() srt_obj.start_time = start_time srt_obj.value = srt_value srt_obj.subValue = [obj] } else { srt_obj.value = srt_obj.value + srt_value srt_obj.subValue = [...srt_obj.subValue, obj] } textData[text_count].start_time = srt_obj.start_time textData[text_count].subValue = srt_obj.subValue srt_list.push(obj) i++ } else { // 判断下一句文件是不是以当当前巨开头。是的话继续。不是的话。直接返回后面的所有信息 if ( tools .removePunctuationIncludingEllipsis(textData[text_count + 1].after_gpt) .startsWith(tools.removePunctuationIncludingEllipsis(srt_value)) ) { textData[text_count].end_time = srt_list[srt_list.length - 1].end_time text_count++ srt_obj = null tmp_str = '' } else { // 将下面的数据直接 添加到textData后面。 // 修改当前行数据的结束事件为 if (srt_list.length > 0) { textData[text_count].end_time = srt_list[srt_list.length - 1].end_time text_count++ } // 将后面的数据直接添加 let lastId = textData[textData.length - 1].id for (let j = i; j < data.length; j++) { // 直接修改原有数据 if (text_count < init_num) { textData[text_count].subValue = [ { start_time: data[j].start, end_time: data[j].end, id: data[j].id, srt_value: data[j].text } ] textData[text_count].start_time = data[j].start textData[text_count].end_time = data[j].end text_count++ } else { let id = uuidv4() // 添加 let obj = { no: j + 1, id: id, word: null, lastId: lastId, old_image: path.normalize(define.zhanwei_image), after_gpt: null, start_time: data[j].start, end_time: data[j].end, subValue: [ { start_time: data[j].start, end_time: data[j].end, id: data[j].id, srt_value: data[j].text } ] } lastId = id textData.push(obj) } } global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, { code: 0, message: current_text }) return { code: 1, data: textData } } } } // 最后对齐 textData[textData.length - 1].end_time = srt_list[srt_list.length - 1].end_time // 返回数据 return { code: 1, data: textData } } catch (error) { // console.log(iii) // console.log(sss) return { code: 0, message: error.toString() } } } //#endregion }