diff --git a/package.json b/package.json index ce59fc4..cb5e970 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "laitool", - "version": "3.4.1", + "version": "3.4.2", "description": "An AI tool for image processing, video processing, and other functions.", "main": "./out/main/index.js", "author": "laitool.cn", @@ -89,6 +89,7 @@ "resources/image/zhanwei.png", "resources/scripts/model/**", "resources/scripts/Lai.exe", + "resources/scripts/xiangbei_jianying_main.exe", "resources/scripts/discordScript.js", "resources/tmp/**", "resources/icon.ico" diff --git a/resources/scripts/Lai.py b/resources/scripts/Lai.py index b0c85ef..313f15a 100644 --- a/resources/scripts/Lai.py +++ b/resources/scripts/Lai.py @@ -121,6 +121,10 @@ elif sys.argv[1] == "-ka": shotSplit.get_fram(sys.argv[2], sys.argv[3], sys.argv[4]) pass +elif sys.argv[1] == "-df": + shotSplit.get_fram(sys.argv[2], sys.argv[3], sys.argv[4]) + pass + # # 智能分镜。字幕识别 # elif sys.argv[1] == "-a": # print("开始算法分镜:" + sys.argv[2] + " -- 输出文件夹:" + sys.argv[3]) diff --git a/resources/scripts/db/book.realm.lock b/resources/scripts/db/book.realm.lock index 2624449..f551e0b 100644 Binary files a/resources/scripts/db/book.realm.lock and b/resources/scripts/db/book.realm.lock differ diff --git a/resources/scripts/db/software.realm.lock b/resources/scripts/db/software.realm.lock index eb12843..ec442dc 100644 Binary files a/resources/scripts/db/software.realm.lock and b/resources/scripts/db/software.realm.lock differ diff --git a/resources/scripts/xiangbei_jianying_main.exe b/resources/scripts/xiangbei_jianying_main.exe new file mode 100644 index 0000000..1fd7924 Binary files /dev/null and b/resources/scripts/xiangbei_jianying_main.exe differ diff --git a/resources/tmp/config/clip_setting.json b/resources/tmp/config/clip_setting.json index fe5b380..a8eebe0 100644 --- a/resources/tmp/config/clip_setting.json +++ b/resources/tmp/config/clip_setting.json @@ -1 +1,1145 @@ -{"text_style":[{"name":"无","id":"0","style":[],"font_size":7,"fonts":"新青年体","style_name":"黄字黑边","clip":{"alpha":1,"flip":{"horizontal":false,"vertical":false},"rotation":0,"scale":{"x":1,"y":1},"transform":{"x":0,"y":-0.8333333333333334}},"ratio":"4:3"},{"name":"随机","id":"1","style":[],"font_size":7,"fonts":"新青年体","style_name":"黄字黑边","clip":{"alpha":1,"flip":{"horizontal":false,"vertical":false},"rotation":0,"scale":{"x":1,"y":1},"transform":{"x":0,"y":-0.8333333333333334}},"ratio":"4:3"},{"name":"test3","id":"8d5b03e5-552a-485f-852f-b3df99e3fc25","style":[{"fill":{"content":{"solid":{"color":[1,0.870588,0]}}},"font":{"path":"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.1.0.11009/Resources/Font/新青年体.ttf","id":"6740435892441190919"},"strokes":[{"content":{"solid":{"color":[0,0,0]}},"width":0.08}],"size":7,"range":[0,4]}],"font_size":7,"fonts":"新青年体","style_name":"黄字黑边","clip":{"alpha":1,"flip":{"horizontal":false,"vertical":false},"rotation":0,"scale":{"x":1,"y":1},"transform":{"x":0,"y":-0.8333333333333334}},"ratio":"4:3"},{"name":"金陵体_黑字白边","id":"2074d84c-7ac1-455d-a1ef-4d4453bb0ef7","style":[{"bold":true,"fill":{"content":{"render_type":"solid","solid":{"alpha":1,"color":[0,0,0]}}},"font":{"id":"7086699209738424840","path":"C:/Users/27698/AppData/Local/JianyingPro/User Data/Cache/effect/1698067/599831579b8aa5b5f0608d5e7d4ec5ce/FZCuJinLJW.ttf"},"range":[0,4],"size":7,"strokes":[{"alpha":1,"content":{"render_type":"solid","solid":{"alpha":1,"color":[1,1,1]}},"width":0.08}],"useLetterColor":true}],"font_size":7,"fonts":"金陵体","style_name":"","clip":{"alpha":1,"flip":{"horizontal":false,"vertical":false},"rotation":0,"scale":{"x":1,"y":1},"transform":{"x":0,"y":-0.8333333333333334}},"ratio":"4:3"}],"background_music_setting":[],"friendly_reminder_setting":[{"id":"0","name":"无","material_animations":{},"texts":{},"tracks":{},"text_value":"无"},{"id":"1","name":"随机","material_animations":{},"texts":{},"tracks":{},"text_value":"下面的数据随机生成(不包含这条啊)"},{"id":"f4862857-1db6-428f-88e5-53473609fa18","name":"虚拟内容 请勿模仿","material_animations":{"animations":[],"id":"B1AFE531-0139-4beb-A950-23AFFA5B889C","type":"sticker_animation"},"texts":{"add_type":0,"alignment":1,"background_alpha":1,"background_color":"","background_height":0.14,"background_horizontal_offset":0,"background_round_radius":0,"background_style":0,"background_vertical_offset":0,"background_width":0.14,"bold_width":0,"border_alpha":1,"border_color":"","border_width":0.08,"caption_template_info":{"category_id":"","category_name":"","effect_id":"","resource_id":"","resource_name":""},"check_flag":7,"combo_info":{"text_templates":[]},"content":"{\"styles\":[{\"fill\":{\"alpha\":1.0,\"content\":{\"render_type\":\"solid\",\"solid\":{\"alpha\":1.0,\"color\":[1.0,1.0,1.0]}}},\"font\":{\"id\":\"\",\"path\":\"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf\"},\"range\":[0,9],\"size\":15.0}],\"text\":\"虚拟内容\\n请勿模仿\"}","fixed_height":-1,"fixed_width":-1,"font_category_id":"","font_category_name":"","font_id":"","font_name":"","font_path":"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf","font_resource_id":"","font_size":15,"font_source_platform":0,"font_team_id":"","font_title":"none","font_url":"","fonts":[],"force_apply_line_max_width":false,"global_alpha":1,"group_id":"","has_shadow":false,"id":"83F79AF1-A749-4aa8-B059-F11108F70A6E","initial_scale":1,"is_rich_text":false,"italic_degree":0,"ktv_color":"","language":"","layer_weight":1,"letter_spacing":0,"line_feed":1,"line_max_width":0.82,"line_spacing":0.02,"name":"","original_size":[],"preset_category":"","preset_category_id":"","preset_has_set_alignment":false,"preset_id":"","preset_index":0,"preset_name":"","recognize_task_id":"","recognize_type":0,"relevance_segment":[],"shadow_alpha":0.8,"shadow_angle":-45,"shadow_color":"","shadow_distance":8,"shadow_point":{"x":1.0182337649086284,"y":-1.0182337649086284},"shadow_smoothing":1,"shape_clip_x":false,"shape_clip_y":false,"style_name":"","sub_type":0,"subtitle_keywords":null,"text_alpha":1,"text_color":"#ffffff","text_curve":null,"text_preset_resource_id":"","text_size":30,"text_to_audio_ids":[],"tts_auto_update":false,"type":"text","typesetting":0,"underline":false,"underline_offset":0.22,"underline_width":0.05,"use_effect_default_color":true,"words":{"end_time":[],"start_time":[],"text":[]}},"tracks":{"attribute":0,"flag":0,"id":"CC71B371-480D-424f-8980-546C68EAEB6F","is_default_name":true,"name":"","segments":[{"cartoon":false,"clip":{"alpha":1,"flip":{"horizontal":false,"vertical":false},"rotation":0,"scale":{"x":0.39049561306183955,"y":0.39049561306183955},"transform":{"x":0.8192161820480404,"y":0.8473861720067454}},"common_keyframes":[],"enable_adjust":false,"enable_color_curves":true,"enable_color_match_adjust":false,"enable_color_wheels":true,"enable_lut":false,"enable_smart_color_adjust":false,"extra_material_refs":["B1AFE531-0139-4beb-A950-23AFFA5B889C"],"group_id":"","hdr_settings":null,"id":"5AC3F57F-92E5-4249-AA7F-F3D7D84F3759","intensifies_audio":false,"is_placeholder":false,"is_tone_modify":false,"keyframe_refs":[],"last_nonzero_volume":1,"material_id":"83F79AF1-A749-4aa8-B059-F11108F70A6E","render_index":14000,"responsive_layout":{"enable":false,"horizontal_pos_layout":0,"size_layout":0,"target_follow":"","vertical_pos_layout":0},"reverse":false,"source_timerange":null,"speed":1,"target_timerange":{"duration":3000000,"start":0},"template_id":"","template_scene":"default","track_attribute":0,"track_render_index":0,"uniform_scale":{"on":true,"value":1},"visible":true,"volume":1}],"type":"text"},"text_value":"虚拟内容\n请勿模仿"},{"id":"78f7cbcf-bbe5-4f08-9b8c-747d2e582ab8","name":"虚拟小说内容 请勿模仿","material_animations":{"animations":[],"id":"B0B9928C-5394-4af8-83B1-FB124F86C3CF","type":"sticker_animation"},"texts":{"add_type":0,"alignment":1,"background_alpha":1,"background_color":"","background_height":0.14,"background_horizontal_offset":0,"background_round_radius":0,"background_style":0,"background_vertical_offset":0,"background_width":0.14,"bold_width":0,"border_alpha":1,"border_color":"","border_width":0.08,"caption_template_info":{"category_id":"","category_name":"","effect_id":"","resource_id":"","resource_name":""},"check_flag":7,"combo_info":{"text_templates":[]},"content":"{\"styles\":[{\"fill\":{\"alpha\":1.0,\"content\":{\"render_type\":\"solid\",\"solid\":{\"alpha\":1.0,\"color\":[1.0,1.0,1.0]}}},\"font\":{\"id\":\"\",\"path\":\"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf\"},\"range\":[0,11],\"size\":15.0}],\"text\":\"虚拟小说内容 请勿模仿\"}","fixed_height":-1,"fixed_width":-1,"font_category_id":"","font_category_name":"","font_id":"","font_name":"","font_path":"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf","font_resource_id":"","font_size":15,"font_source_platform":0,"font_team_id":"","font_title":"none","font_url":"","fonts":[],"force_apply_line_max_width":false,"global_alpha":1,"group_id":"","has_shadow":false,"id":"3D8C3AC4-2926-4b69-B1A7-ED0031B5EE47","initial_scale":1,"is_rich_text":false,"italic_degree":0,"ktv_color":"","language":"","layer_weight":1,"letter_spacing":0,"line_feed":1,"line_max_width":0.82,"line_spacing":0.02,"name":"","original_size":[],"preset_category":"","preset_category_id":"","preset_has_set_alignment":false,"preset_id":"","preset_index":0,"preset_name":"","recognize_task_id":"","recognize_type":0,"relevance_segment":[],"shadow_alpha":0.8,"shadow_angle":-45,"shadow_color":"","shadow_distance":8,"shadow_point":{"x":1.0182337649086284,"y":-1.0182337649086284},"shadow_smoothing":1,"shape_clip_x":false,"shape_clip_y":false,"style_name":"","sub_type":0,"subtitle_keywords":null,"text_alpha":1,"text_color":"#ffffff","text_curve":null,"text_preset_resource_id":"","text_size":30,"text_to_audio_ids":[],"tts_auto_update":false,"type":"text","typesetting":0,"underline":false,"underline_offset":0.22,"underline_width":0.05,"use_effect_default_color":true,"words":{"end_time":[],"start_time":[],"text":[]}},"tracks":{"attribute":0,"flag":0,"id":"C6D7DD2A-8431-4fee-93D2-7E503C847BD7","is_default_name":true,"name":"","segments":[{"cartoon":false,"clip":{"alpha":1,"flip":{"horizontal":false,"vertical":false},"rotation":0,"scale":{"x":0.3341015354334309,"y":0.3341015354334309},"transform":{"x":0.663716814159292,"y":0.918212478920742}},"common_keyframes":[],"enable_adjust":false,"enable_color_curves":true,"enable_color_match_adjust":false,"enable_color_wheels":true,"enable_lut":false,"enable_smart_color_adjust":false,"extra_material_refs":["B0B9928C-5394-4af8-83B1-FB124F86C3CF"],"group_id":"","hdr_settings":null,"id":"A40D04ED-C36A-45bf-97D8-D574EBC74B04","intensifies_audio":false,"is_placeholder":false,"is_tone_modify":false,"keyframe_refs":[],"last_nonzero_volume":1,"material_id":"3D8C3AC4-2926-4b69-B1A7-ED0031B5EE47","render_index":14002,"responsive_layout":{"enable":false,"horizontal_pos_layout":0,"size_layout":0,"target_follow":"","vertical_pos_layout":0},"reverse":false,"source_timerange":null,"speed":1,"target_timerange":{"duration":3000000,"start":0},"template_id":"","template_scene":"default","track_attribute":0,"track_render_index":0,"uniform_scale":{"on":true,"value":1},"visible":true,"volume":1}],"type":"text"},"text_value":"虚拟小说内容 请勿模仿"},{"id":"b093fac1-06c5-4293-8fd8-76cf07e2aa42","name":"故事纯属虚构,请勿模仿","material_animations":{"animations":[],"id":"AD6528B5-8A30-4856-959A-9DDD508A7774","type":"sticker_animation"},"texts":{"add_type":0,"alignment":1,"background_alpha":1,"background_color":"","background_height":0.14,"background_horizontal_offset":0,"background_round_radius":0,"background_style":0,"background_vertical_offset":0,"background_width":0.14,"bold_width":0,"border_alpha":1,"border_color":"","border_width":0.08,"caption_template_info":{"category_id":"","category_name":"","effect_id":"","resource_id":"","resource_name":""},"check_flag":7,"combo_info":{"text_templates":[]},"content":"{\"styles\":[{\"fill\":{\"alpha\":1.0,\"content\":{\"render_type\":\"solid\",\"solid\":{\"alpha\":1.0,\"color\":[1.0,1.0,1.0]}}},\"font\":{\"id\":\"6740435892441190919\",\"path\":\"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/新青年体.ttf\"},\"range\":[0,11],\"size\":15.0}],\"text\":\"故事纯属虚构,请勿模仿\"}","fixed_height":-1,"fixed_width":-1,"font_category_id":"","font_category_name":"","font_id":"","font_name":"","font_path":"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/新青年体.ttf","font_resource_id":"6740435892441190919","font_size":15,"font_source_platform":0,"font_team_id":"","font_title":"none","font_url":"","fonts":[],"force_apply_line_max_width":false,"global_alpha":1,"group_id":"","has_shadow":false,"id":"54E1F74E-FC64-4795-94D3-B29B5071CDAD","initial_scale":1,"is_rich_text":false,"italic_degree":0,"ktv_color":"","language":"","layer_weight":1,"letter_spacing":0,"line_feed":1,"line_max_width":0.82,"line_spacing":0.02,"name":"","original_size":[],"preset_category":"","preset_category_id":"","preset_has_set_alignment":false,"preset_id":"","preset_index":0,"preset_name":"","recognize_task_id":"","recognize_type":0,"relevance_segment":[],"shadow_alpha":0.8,"shadow_angle":-45,"shadow_color":"","shadow_distance":8,"shadow_point":{"x":1.0182337649086284,"y":-1.0182337649086284},"shadow_smoothing":1,"shape_clip_x":false,"shape_clip_y":false,"style_name":"","sub_type":0,"subtitle_keywords":null,"text_alpha":1,"text_color":"#ffffff","text_curve":null,"text_preset_resource_id":"","text_size":30,"text_to_audio_ids":[],"tts_auto_update":false,"type":"text","typesetting":0,"underline":false,"underline_offset":0.22,"underline_width":0.05,"use_effect_default_color":true,"words":{"end_time":[],"start_time":[],"text":[]}},"tracks":{"attribute":0,"flag":0,"id":"783E0C53-B36E-4a7b-A880-F05E758FD2DB","is_default_name":true,"name":"","segments":[{"cartoon":false,"clip":{"alpha":1,"flip":{"horizontal":false,"vertical":false},"rotation":0,"scale":{"x":0.2860491929811744,"y":0.2860491929811744},"transform":{"x":0.7124542124542124,"y":0.9087947882736158}},"common_keyframes":[],"enable_adjust":false,"enable_color_curves":true,"enable_color_match_adjust":false,"enable_color_wheels":true,"enable_lut":false,"enable_smart_color_adjust":false,"extra_material_refs":["AD6528B5-8A30-4856-959A-9DDD508A7774"],"group_id":"","hdr_settings":null,"id":"A5557348-B228-4222-BDA7-2798283A0F68","intensifies_audio":false,"is_placeholder":false,"is_tone_modify":false,"keyframe_refs":[],"last_nonzero_volume":1,"material_id":"54E1F74E-FC64-4795-94D3-B29B5071CDAD","render_index":14002,"responsive_layout":{"enable":false,"horizontal_pos_layout":0,"size_layout":0,"target_follow":"","vertical_pos_layout":0},"reverse":false,"source_timerange":null,"speed":1,"target_timerange":{"duration":3000000,"start":0},"template_id":"","template_scene":"default","track_attribute":0,"track_render_index":0,"uniform_scale":{"on":true,"value":1},"visible":true,"volume":1}],"type":"text"},"text_value":"故事纯属虚构,请勿模仿"},{"id":"f0d739ad-2ef5-443f-a598-b58c6cab0a58","name":"AI生成画面 无不良引导","material_animations":{"animations":[],"id":"CFA0E8D8-1B2B-4b3a-8CF5-64A1A252D953","type":"sticker_animation"},"texts":{"add_type":0,"alignment":1,"background_alpha":1,"background_color":"","background_height":0.14,"background_horizontal_offset":0,"background_round_radius":0,"background_style":0,"background_vertical_offset":0,"background_width":0.14,"bold_width":0,"border_alpha":1,"border_color":"","border_width":0.08,"caption_template_info":{"category_id":"","category_name":"","effect_id":"","resource_id":"","resource_name":""},"check_flag":7,"combo_info":{"text_templates":[]},"content":"{\"styles\":[{\"fill\":{\"alpha\":1.0,\"content\":{\"render_type\":\"solid\",\"solid\":{\"alpha\":1.0,\"color\":[1.0,1.0,1.0]}}},\"font\":{\"id\":\"\",\"path\":\"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf\"},\"range\":[0,12],\"size\":15.0,\"useLetterColor\":true}],\"text\":\"AI生成画面\\n无不良引导\"}","fixed_height":-1,"fixed_width":-1,"font_category_id":"","font_category_name":"","font_id":"","font_name":"","font_path":"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf","font_resource_id":"","font_size":15,"font_source_platform":0,"font_team_id":"","font_title":"none","font_url":"","fonts":[],"force_apply_line_max_width":false,"global_alpha":1,"group_id":"","has_shadow":false,"id":"1AE8EF5E-6380-4130-B164-709E2160BFD0","initial_scale":1,"is_rich_text":false,"italic_degree":0,"ktv_color":"","language":"","layer_weight":1,"letter_spacing":0,"line_feed":1,"line_max_width":0.82,"line_spacing":0.02,"name":"","original_size":[],"preset_category":"","preset_category_id":"","preset_has_set_alignment":false,"preset_id":"","preset_index":0,"preset_name":"","recognize_task_id":"","recognize_type":0,"relevance_segment":[],"shadow_alpha":0.8,"shadow_angle":-45,"shadow_color":"","shadow_distance":8,"shadow_point":{"x":1.0182337649086284,"y":-1.0182337649086284},"shadow_smoothing":1,"shape_clip_x":false,"shape_clip_y":false,"style_name":"","sub_type":0,"subtitle_keywords":null,"text_alpha":1,"text_color":"#ffffff","text_curve":null,"text_preset_resource_id":"","text_size":30,"text_to_audio_ids":[],"tts_auto_update":false,"type":"text","typesetting":0,"underline":false,"underline_offset":0.22,"underline_width":0.05,"use_effect_default_color":false,"words":{"end_time":[],"start_time":[],"text":[]}},"tracks":{"attribute":0,"flag":0,"id":"B468156C-B4B6-4949-91E3-FBDB56839C48","is_default_name":true,"name":"","segments":[{"cartoon":false,"clip":{"alpha":1,"flip":{"horizontal":false,"vertical":false},"rotation":0,"scale":{"x":0.40831670575608614,"y":0.40831670575608614},"transform":{"x":0.8086015433073044,"y":0.8607584675000406}},"common_keyframes":[],"enable_adjust":false,"enable_color_curves":true,"enable_color_match_adjust":false,"enable_color_wheels":true,"enable_lut":false,"enable_smart_color_adjust":false,"extra_material_refs":["CFA0E8D8-1B2B-4b3a-8CF5-64A1A252D953"],"group_id":"","hdr_settings":null,"id":"ACEDF876-5665-4c6c-AC88-95B3AE49352E","intensifies_audio":false,"is_placeholder":false,"is_tone_modify":false,"keyframe_refs":[],"last_nonzero_volume":1,"material_id":"1AE8EF5E-6380-4130-B164-709E2160BFD0","render_index":14000,"responsive_layout":{"enable":false,"horizontal_pos_layout":0,"size_layout":0,"target_follow":"","vertical_pos_layout":0},"reverse":false,"source_timerange":null,"speed":1,"target_timerange":{"duration":3000000,"start":0},"template_id":"","template_scene":"default","track_attribute":0,"track_render_index":0,"uniform_scale":{"on":true,"value":1},"visible":true,"volume":1}],"type":"text"},"text_value":"AI生成画面\n无不良引导"},{"id":"b422ce35-d439-46d7-9b51-e0d21e72e99d","name":"小说内容 请勿模仿","material_animations":{"animations":[],"id":"96F35048-8837-43de-8E83-4F3C3683FBEC","type":"sticker_animation"},"texts":{"add_type":0,"alignment":1,"background_alpha":1,"background_color":"","background_height":0.14,"background_horizontal_offset":0,"background_round_radius":0,"background_style":0,"background_vertical_offset":0,"background_width":0.14,"bold_width":0,"border_alpha":1,"border_color":"","border_width":0.08,"caption_template_info":{"category_id":"","category_name":"","effect_id":"","resource_id":"","resource_name":""},"check_flag":7,"combo_info":{"text_templates":[]},"content":"{\"styles\":[{\"fill\":{\"alpha\":1.0,\"content\":{\"render_type\":\"solid\",\"solid\":{\"alpha\":1.0,\"color\":[1.0,1.0,1.0]}}},\"font\":{\"id\":\"\",\"path\":\"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf\"},\"range\":[0,9],\"size\":15.0,\"useLetterColor\":true}],\"text\":\"小说内容\\n请勿模仿\"}","fixed_height":-1,"fixed_width":-1,"font_category_id":"","font_category_name":"","font_id":"","font_name":"","font_path":"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf","font_resource_id":"","font_size":15,"font_source_platform":0,"font_team_id":"","font_title":"none","font_url":"","fonts":[],"force_apply_line_max_width":false,"global_alpha":1,"group_id":"","has_shadow":false,"id":"92C492B0-944F-48e5-8C99-40FAB82B9648","initial_scale":1,"is_rich_text":false,"italic_degree":0,"ktv_color":"","language":"","layer_weight":1,"letter_spacing":0,"line_feed":1,"line_max_width":0.82,"line_spacing":0.02,"name":"","original_size":[],"preset_category":"","preset_category_id":"","preset_has_set_alignment":false,"preset_id":"","preset_index":0,"preset_name":"","recognize_task_id":"","recognize_type":0,"relevance_segment":[],"shadow_alpha":0.8,"shadow_angle":-45,"shadow_color":"","shadow_distance":8,"shadow_point":{"x":1.0182337649086284,"y":-1.0182337649086284},"shadow_smoothing":1,"shape_clip_x":false,"shape_clip_y":false,"style_name":"","sub_type":0,"subtitle_keywords":null,"text_alpha":1,"text_color":"#ffffff","text_curve":null,"text_preset_resource_id":"","text_size":30,"text_to_audio_ids":[],"tts_auto_update":false,"type":"text","typesetting":0,"underline":false,"underline_offset":0.22,"underline_width":0.05,"use_effect_default_color":false,"words":{"end_time":[],"start_time":[],"text":[]}},"tracks":{"attribute":0,"flag":0,"id":"DAB33387-452D-4597-9A10-932E1FC1B47E","is_default_name":true,"name":"","segments":[{"cartoon":false,"clip":{"alpha":1,"flip":{"horizontal":false,"vertical":false},"rotation":0,"scale":{"x":0.40831670575608614,"y":0.40831670575608614},"transform":{"x":0.8238636363636365,"y":0.8535353535353536}},"common_keyframes":[],"enable_adjust":false,"enable_color_curves":true,"enable_color_match_adjust":false,"enable_color_wheels":true,"enable_lut":false,"enable_smart_color_adjust":false,"extra_material_refs":["96F35048-8837-43de-8E83-4F3C3683FBEC"],"group_id":"","hdr_settings":null,"id":"D31C287B-9E51-475a-8843-37BFC7C07CF9","intensifies_audio":false,"is_placeholder":false,"is_tone_modify":false,"keyframe_refs":[],"last_nonzero_volume":1,"material_id":"92C492B0-944F-48e5-8C99-40FAB82B9648","render_index":14006,"responsive_layout":{"enable":false,"horizontal_pos_layout":0,"size_layout":0,"target_follow":"","vertical_pos_layout":0},"reverse":false,"source_timerange":null,"speed":1,"target_timerange":{"duration":3000000,"start":0},"template_id":"","template_scene":"default","track_attribute":0,"track_render_index":0,"uniform_scale":{"on":true,"value":1},"visible":true,"volume":1}],"type":"text"},"text_value":"小说内容\n请勿模仿"}],"key_frame":{"key_frame":"KFTypePositionY","isFixedSpeed":true,"key_frame_time":4,"up_down_key_frame":{"default_scale":133,"start_position":275,"end_position":275},"left_right_key_frame":{"default_scale":133,"start_position":275,"end_position":275},"scale_key_frame":{"default_scale":100,"start_position":210,"end_position":133}},"write_setting":{"split_char":"。,“”‘’!?【】「」《》()…—;,''\"\"!?[]<>()-:;╰*°▽°*╯′,ノ﹏<o‵゚Д゚,ノ,へ ̄工╬▔皿","merge_count":5,"merge_char":",","end_char":"。"}} \ No newline at end of file +{ + "text_style": [ + { + "name": "无", + "id": "0", + "style": [], + "font_size": 7, + "fonts": "新青年体", + "style_name": "黄字黑边", + "clip": { + "alpha": 1, + "flip": { + "horizontal": false, + "vertical": false + }, + "rotation": 0, + "scale": { + "x": 1, + "y": 1 + }, + "transform": { + "x": 0, + "y": -0.8333333333333334 + } + }, + "ratio": "4:3" + }, + { + "name": "随机", + "id": "1", + "style": [], + "font_size": 7, + "fonts": "新青年体", + "style_name": "黄字黑边", + "clip": { + "alpha": 1, + "flip": { + "horizontal": false, + "vertical": false + }, + "rotation": 0, + "scale": { + "x": 1, + "y": 1 + }, + "transform": { + "x": 0, + "y": -0.8333333333333334 + } + }, + "ratio": "4:3" + }, + { + "name": "test3", + "id": "8d5b03e5-552a-485f-852f-b3df99e3fc25", + "style": [ + { + "fill": { + "content": { + "solid": { + "color": [ + 1, + 0.870588, + 0 + ] + } + } + }, + "font": { + "path": "C:/Users/27698/AppData/Local/JianyingPro/Apps/5.1.0.11009/Resources/Font/新青年体.ttf", + "id": "6740435892441190919" + }, + "strokes": [ + { + "content": { + "solid": { + "color": [ + 0, + 0, + 0 + ] + } + }, + "width": 0.08 + } + ], + "size": 7, + "range": [ + 0, + 4 + ] + } + ], + "font_size": 7, + "fonts": "新青年体", + "style_name": "黄字黑边", + "clip": { + "alpha": 1, + "flip": { + "horizontal": false, + "vertical": false + }, + "rotation": 0, + "scale": { + "x": 1, + "y": 1 + }, + "transform": { + "x": 0, + "y": -0.8333333333333334 + } + }, + "ratio": "4:3" + }, + { + "name": "金陵体_黑字白边", + "id": "2074d84c-7ac1-455d-a1ef-4d4453bb0ef7", + "style": [ + { + "bold": true, + "fill": { + "content": { + "render_type": "solid", + "solid": { + "alpha": 1, + "color": [ + 0, + 0, + 0 + ] + } + } + }, + "font": { + "id": "7086699209738424840", + "path": "C:/Users/27698/AppData/Local/JianyingPro/User Data/Cache/effect/1698067/599831579b8aa5b5f0608d5e7d4ec5ce/FZCuJinLJW.ttf" + }, + "range": [ + 0, + 4 + ], + "size": 7, + "strokes": [ + { + "alpha": 1, + "content": { + "render_type": "solid", + "solid": { + "alpha": 1, + "color": [ + 1, + 1, + 1 + ] + } + }, + "width": 0.08 + } + ], + "useLetterColor": true + } + ], + "font_size": 7, + "fonts": "金陵体", + "style_name": "", + "clip": { + "alpha": 1, + "flip": { + "horizontal": false, + "vertical": false + }, + "rotation": 0, + "scale": { + "x": 1, + "y": 1 + }, + "transform": { + "x": 0, + "y": -0.8333333333333334 + } + }, + "ratio": "4:3" + } + ], + "background_music_setting": [], + "friendly_reminder_setting": [ + { + "id": "0", + "name": "无", + "material_animations": {}, + "texts": {}, + "tracks": {}, + "text_value": "无" + }, + { + "id": "1", + "name": "随机", + "material_animations": {}, + "texts": {}, + "tracks": {}, + "text_value": "下面的数据随机生成(不包含这条啊)" + }, + { + "id": "f4862857-1db6-428f-88e5-53473609fa18", + "name": "虚拟内容 请勿模仿", + "material_animations": { + "animations": [], + "id": "B1AFE531-0139-4beb-A950-23AFFA5B889C", + "type": "sticker_animation" + }, + "texts": { + "add_type": 0, + "alignment": 1, + "background_alpha": 1, + "background_color": "", + "background_height": 0.14, + "background_horizontal_offset": 0, + "background_round_radius": 0, + "background_style": 0, + "background_vertical_offset": 0, + "background_width": 0.14, + "bold_width": 0, + "border_alpha": 1, + "border_color": "", + "border_width": 0.08, + "caption_template_info": { + "category_id": "", + "category_name": "", + "effect_id": "", + "resource_id": "", + "resource_name": "" + }, + "check_flag": 7, + "combo_info": { + "text_templates": [] + }, + "content": "{\"styles\":[{\"fill\":{\"alpha\":1.0,\"content\":{\"render_type\":\"solid\",\"solid\":{\"alpha\":1.0,\"color\":[1.0,1.0,1.0]}}},\"font\":{\"id\":\"\",\"path\":\"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf\"},\"range\":[0,9],\"size\":15.0}],\"text\":\"虚拟内容\\n请勿模仿\"}", + "fixed_height": -1, + "fixed_width": -1, + "font_category_id": "", + "font_category_name": "", + "font_id": "", + "font_name": "", + "font_path": "C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf", + "font_resource_id": "", + "font_size": 15, + "font_source_platform": 0, + "font_team_id": "", + "font_title": "none", + "font_url": "", + "fonts": [], + "force_apply_line_max_width": false, + "global_alpha": 1, + "group_id": "", + "has_shadow": false, + "id": "83F79AF1-A749-4aa8-B059-F11108F70A6E", + "initial_scale": 1, + "is_rich_text": false, + "italic_degree": 0, + "ktv_color": "", + "language": "", + "layer_weight": 1, + "letter_spacing": 0, + "line_feed": 1, + "line_max_width": 0.82, + "line_spacing": 0.02, + "name": "", + "original_size": [], + "preset_category": "", + "preset_category_id": "", + "preset_has_set_alignment": false, + "preset_id": "", + "preset_index": 0, + "preset_name": "", + "recognize_task_id": "", + "recognize_type": 0, + "relevance_segment": [], + "shadow_alpha": 0.8, + "shadow_angle": -45, + "shadow_color": "", + "shadow_distance": 8, + "shadow_point": { + "x": 1.0182337649086284, + "y": -1.0182337649086284 + }, + "shadow_smoothing": 1, + "shape_clip_x": false, + "shape_clip_y": false, + "style_name": "", + "sub_type": 0, + "subtitle_keywords": null, + "text_alpha": 1, + "text_color": "#ffffff", + "text_curve": null, + "text_preset_resource_id": "", + "text_size": 30, + "text_to_audio_ids": [], + "tts_auto_update": false, + "type": "text", + "typesetting": 0, + "underline": false, + "underline_offset": 0.22, + "underline_width": 0.05, + "use_effect_default_color": true, + "words": { + "end_time": [], + "start_time": [], + "text": [] + } + }, + "tracks": { + "attribute": 0, + "flag": 0, + "id": "CC71B371-480D-424f-8980-546C68EAEB6F", + "is_default_name": true, + "name": "", + "segments": [ + { + "cartoon": false, + "clip": { + "alpha": 1, + "flip": { + "horizontal": false, + "vertical": false + }, + "rotation": 0, + "scale": { + "x": 0.39049561306183955, + "y": 0.39049561306183955 + }, + "transform": { + "x": 0.8192161820480404, + "y": 0.8473861720067454 + } + }, + "common_keyframes": [], + "enable_adjust": false, + "enable_color_curves": true, + "enable_color_match_adjust": false, + "enable_color_wheels": true, + "enable_lut": false, + "enable_smart_color_adjust": false, + "extra_material_refs": [ + "B1AFE531-0139-4beb-A950-23AFFA5B889C" + ], + "group_id": "", + "hdr_settings": null, + "id": "5AC3F57F-92E5-4249-AA7F-F3D7D84F3759", + "intensifies_audio": false, + "is_placeholder": false, + "is_tone_modify": false, + "keyframe_refs": [], + "last_nonzero_volume": 1, + "material_id": "83F79AF1-A749-4aa8-B059-F11108F70A6E", + "render_index": 14000, + "responsive_layout": { + "enable": false, + "horizontal_pos_layout": 0, + "size_layout": 0, + "target_follow": "", + "vertical_pos_layout": 0 + }, + "reverse": false, + "source_timerange": null, + "speed": 1, + "target_timerange": { + "duration": 3000000, + "start": 0 + }, + "template_id": "", + "template_scene": "default", + "track_attribute": 0, + "track_render_index": 0, + "uniform_scale": { + "on": true, + "value": 1 + }, + "visible": true, + "volume": 1 + } + ], + "type": "text" + }, + "text_value": "虚拟内容\n请勿模仿" + }, + { + "id": "78f7cbcf-bbe5-4f08-9b8c-747d2e582ab8", + "name": "虚拟小说内容 请勿模仿", + "material_animations": { + "animations": [], + "id": "B0B9928C-5394-4af8-83B1-FB124F86C3CF", + "type": "sticker_animation" + }, + "texts": { + "add_type": 0, + "alignment": 1, + "background_alpha": 1, + "background_color": "", + "background_height": 0.14, + "background_horizontal_offset": 0, + "background_round_radius": 0, + "background_style": 0, + "background_vertical_offset": 0, + "background_width": 0.14, + "bold_width": 0, + "border_alpha": 1, + "border_color": "", + "border_width": 0.08, + "caption_template_info": { + "category_id": "", + "category_name": "", + "effect_id": "", + "resource_id": "", + "resource_name": "" + }, + "check_flag": 7, + "combo_info": { + "text_templates": [] + }, + "content": "{\"styles\":[{\"fill\":{\"alpha\":1.0,\"content\":{\"render_type\":\"solid\",\"solid\":{\"alpha\":1.0,\"color\":[1.0,1.0,1.0]}}},\"font\":{\"id\":\"\",\"path\":\"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf\"},\"range\":[0,11],\"size\":15.0}],\"text\":\"虚拟小说内容 请勿模仿\"}", + "fixed_height": -1, + "fixed_width": -1, + "font_category_id": "", + "font_category_name": "", + "font_id": "", + "font_name": "", + "font_path": "C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf", + "font_resource_id": "", + "font_size": 15, + "font_source_platform": 0, + "font_team_id": "", + "font_title": "none", + "font_url": "", + "fonts": [], + "force_apply_line_max_width": false, + "global_alpha": 1, + "group_id": "", + "has_shadow": false, + "id": "3D8C3AC4-2926-4b69-B1A7-ED0031B5EE47", + "initial_scale": 1, + "is_rich_text": false, + "italic_degree": 0, + "ktv_color": "", + "language": "", + "layer_weight": 1, + "letter_spacing": 0, + "line_feed": 1, + "line_max_width": 0.82, + "line_spacing": 0.02, + "name": "", + "original_size": [], + "preset_category": "", + "preset_category_id": "", + "preset_has_set_alignment": false, + "preset_id": "", + "preset_index": 0, + "preset_name": "", + "recognize_task_id": "", + "recognize_type": 0, + "relevance_segment": [], + "shadow_alpha": 0.8, + "shadow_angle": -45, + "shadow_color": "", + "shadow_distance": 8, + "shadow_point": { + "x": 1.0182337649086284, + "y": -1.0182337649086284 + }, + "shadow_smoothing": 1, + "shape_clip_x": false, + "shape_clip_y": false, + "style_name": "", + "sub_type": 0, + "subtitle_keywords": null, + "text_alpha": 1, + "text_color": "#ffffff", + "text_curve": null, + "text_preset_resource_id": "", + "text_size": 30, + "text_to_audio_ids": [], + "tts_auto_update": false, + "type": "text", + "typesetting": 0, + "underline": false, + "underline_offset": 0.22, + "underline_width": 0.05, + "use_effect_default_color": true, + "words": { + "end_time": [], + "start_time": [], + "text": [] + } + }, + "tracks": { + "attribute": 0, + "flag": 0, + "id": "C6D7DD2A-8431-4fee-93D2-7E503C847BD7", + "is_default_name": true, + "name": "", + "segments": [ + { + "cartoon": false, + "clip": { + "alpha": 1, + "flip": { + "horizontal": false, + "vertical": false + }, + "rotation": 0, + "scale": { + "x": 0.3341015354334309, + "y": 0.3341015354334309 + }, + "transform": { + "x": 0.663716814159292, + "y": 0.918212478920742 + } + }, + "common_keyframes": [], + "enable_adjust": false, + "enable_color_curves": true, + "enable_color_match_adjust": false, + "enable_color_wheels": true, + "enable_lut": false, + "enable_smart_color_adjust": false, + "extra_material_refs": [ + "B0B9928C-5394-4af8-83B1-FB124F86C3CF" + ], + "group_id": "", + "hdr_settings": null, + "id": "A40D04ED-C36A-45bf-97D8-D574EBC74B04", + "intensifies_audio": false, + "is_placeholder": false, + "is_tone_modify": false, + "keyframe_refs": [], + "last_nonzero_volume": 1, + "material_id": "3D8C3AC4-2926-4b69-B1A7-ED0031B5EE47", + "render_index": 14002, + "responsive_layout": { + "enable": false, + "horizontal_pos_layout": 0, + "size_layout": 0, + "target_follow": "", + "vertical_pos_layout": 0 + }, + "reverse": false, + "source_timerange": null, + "speed": 1, + "target_timerange": { + "duration": 3000000, + "start": 0 + }, + "template_id": "", + "template_scene": "default", + "track_attribute": 0, + "track_render_index": 0, + "uniform_scale": { + "on": true, + "value": 1 + }, + "visible": true, + "volume": 1 + } + ], + "type": "text" + }, + "text_value": "虚拟小说内容 请勿模仿" + }, + { + "id": "b093fac1-06c5-4293-8fd8-76cf07e2aa42", + "name": "故事纯属虚构,请勿模仿", + "material_animations": { + "animations": [], + "id": "AD6528B5-8A30-4856-959A-9DDD508A7774", + "type": "sticker_animation" + }, + "texts": { + "add_type": 0, + "alignment": 1, + "background_alpha": 1, + "background_color": "", + "background_height": 0.14, + "background_horizontal_offset": 0, + "background_round_radius": 0, + "background_style": 0, + "background_vertical_offset": 0, + "background_width": 0.14, + "bold_width": 0, + "border_alpha": 1, + "border_color": "", + "border_width": 0.08, + "caption_template_info": { + "category_id": "", + "category_name": "", + "effect_id": "", + "resource_id": "", + "resource_name": "" + }, + "check_flag": 7, + "combo_info": { + "text_templates": [] + }, + "content": "{\"styles\":[{\"fill\":{\"alpha\":1.0,\"content\":{\"render_type\":\"solid\",\"solid\":{\"alpha\":1.0,\"color\":[1.0,1.0,1.0]}}},\"font\":{\"id\":\"6740435892441190919\",\"path\":\"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/新青年体.ttf\"},\"range\":[0,11],\"size\":15.0}],\"text\":\"故事纯属虚构,请勿模仿\"}", + "fixed_height": -1, + "fixed_width": -1, + "font_category_id": "", + "font_category_name": "", + "font_id": "", + "font_name": "", + "font_path": "C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/新青年体.ttf", + "font_resource_id": "6740435892441190919", + "font_size": 15, + "font_source_platform": 0, + "font_team_id": "", + "font_title": "none", + "font_url": "", + "fonts": [], + "force_apply_line_max_width": false, + "global_alpha": 1, + "group_id": "", + "has_shadow": false, + "id": "54E1F74E-FC64-4795-94D3-B29B5071CDAD", + "initial_scale": 1, + "is_rich_text": false, + "italic_degree": 0, + "ktv_color": "", + "language": "", + "layer_weight": 1, + "letter_spacing": 0, + "line_feed": 1, + "line_max_width": 0.82, + "line_spacing": 0.02, + "name": "", + "original_size": [], + "preset_category": "", + "preset_category_id": "", + "preset_has_set_alignment": false, + "preset_id": "", + "preset_index": 0, + "preset_name": "", + "recognize_task_id": "", + "recognize_type": 0, + "relevance_segment": [], + "shadow_alpha": 0.8, + "shadow_angle": -45, + "shadow_color": "", + "shadow_distance": 8, + "shadow_point": { + "x": 1.0182337649086284, + "y": -1.0182337649086284 + }, + "shadow_smoothing": 1, + "shape_clip_x": false, + "shape_clip_y": false, + "style_name": "", + "sub_type": 0, + "subtitle_keywords": null, + "text_alpha": 1, + "text_color": "#ffffff", + "text_curve": null, + "text_preset_resource_id": "", + "text_size": 30, + "text_to_audio_ids": [], + "tts_auto_update": false, + "type": "text", + "typesetting": 0, + "underline": false, + "underline_offset": 0.22, + "underline_width": 0.05, + "use_effect_default_color": true, + "words": { + "end_time": [], + "start_time": [], + "text": [] + } + }, + "tracks": { + "attribute": 0, + "flag": 0, + "id": "783E0C53-B36E-4a7b-A880-F05E758FD2DB", + "is_default_name": true, + "name": "", + "segments": [ + { + "cartoon": false, + "clip": { + "alpha": 1, + "flip": { + "horizontal": false, + "vertical": false + }, + "rotation": 0, + "scale": { + "x": 0.2860491929811744, + "y": 0.2860491929811744 + }, + "transform": { + "x": 0.7124542124542124, + "y": 0.9087947882736158 + } + }, + "common_keyframes": [], + "enable_adjust": false, + "enable_color_curves": true, + "enable_color_match_adjust": false, + "enable_color_wheels": true, + "enable_lut": false, + "enable_smart_color_adjust": false, + "extra_material_refs": [ + "AD6528B5-8A30-4856-959A-9DDD508A7774" + ], + "group_id": "", + "hdr_settings": null, + "id": "A5557348-B228-4222-BDA7-2798283A0F68", + "intensifies_audio": false, + "is_placeholder": false, + "is_tone_modify": false, + "keyframe_refs": [], + "last_nonzero_volume": 1, + "material_id": "54E1F74E-FC64-4795-94D3-B29B5071CDAD", + "render_index": 14002, + "responsive_layout": { + "enable": false, + "horizontal_pos_layout": 0, + "size_layout": 0, + "target_follow": "", + "vertical_pos_layout": 0 + }, + "reverse": false, + "source_timerange": null, + "speed": 1, + "target_timerange": { + "duration": 3000000, + "start": 0 + }, + "template_id": "", + "template_scene": "default", + "track_attribute": 0, + "track_render_index": 0, + "uniform_scale": { + "on": true, + "value": 1 + }, + "visible": true, + "volume": 1 + } + ], + "type": "text" + }, + "text_value": "故事纯属虚构,请勿模仿" + }, + { + "id": "f0d739ad-2ef5-443f-a598-b58c6cab0a58", + "name": "AI生成画面 无不良引导", + "material_animations": { + "animations": [], + "id": "CFA0E8D8-1B2B-4b3a-8CF5-64A1A252D953", + "type": "sticker_animation" + }, + "texts": { + "add_type": 0, + "alignment": 1, + "background_alpha": 1, + "background_color": "", + "background_height": 0.14, + "background_horizontal_offset": 0, + "background_round_radius": 0, + "background_style": 0, + "background_vertical_offset": 0, + "background_width": 0.14, + "bold_width": 0, + "border_alpha": 1, + "border_color": "", + "border_width": 0.08, + "caption_template_info": { + "category_id": "", + "category_name": "", + "effect_id": "", + "resource_id": "", + "resource_name": "" + }, + "check_flag": 7, + "combo_info": { + "text_templates": [] + }, + "content": "{\"styles\":[{\"fill\":{\"alpha\":1.0,\"content\":{\"render_type\":\"solid\",\"solid\":{\"alpha\":1.0,\"color\":[1.0,1.0,1.0]}}},\"font\":{\"id\":\"\",\"path\":\"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf\"},\"range\":[0,12],\"size\":15.0,\"useLetterColor\":true}],\"text\":\"AI生成画面\\n无不良引导\"}", + "fixed_height": -1, + "fixed_width": -1, + "font_category_id": "", + "font_category_name": "", + "font_id": "", + "font_name": "", + "font_path": "C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf", + "font_resource_id": "", + "font_size": 15, + "font_source_platform": 0, + "font_team_id": "", + "font_title": "none", + "font_url": "", + "fonts": [], + "force_apply_line_max_width": false, + "global_alpha": 1, + "group_id": "", + "has_shadow": false, + "id": "1AE8EF5E-6380-4130-B164-709E2160BFD0", + "initial_scale": 1, + "is_rich_text": false, + "italic_degree": 0, + "ktv_color": "", + "language": "", + "layer_weight": 1, + "letter_spacing": 0, + "line_feed": 1, + "line_max_width": 0.82, + "line_spacing": 0.02, + "name": "", + "original_size": [], + "preset_category": "", + "preset_category_id": "", + "preset_has_set_alignment": false, + "preset_id": "", + "preset_index": 0, + "preset_name": "", + "recognize_task_id": "", + "recognize_type": 0, + "relevance_segment": [], + "shadow_alpha": 0.8, + "shadow_angle": -45, + "shadow_color": "", + "shadow_distance": 8, + "shadow_point": { + "x": 1.0182337649086284, + "y": -1.0182337649086284 + }, + "shadow_smoothing": 1, + "shape_clip_x": false, + "shape_clip_y": false, + "style_name": "", + "sub_type": 0, + "subtitle_keywords": null, + "text_alpha": 1, + "text_color": "#ffffff", + "text_curve": null, + "text_preset_resource_id": "", + "text_size": 30, + "text_to_audio_ids": [], + "tts_auto_update": false, + "type": "text", + "typesetting": 0, + "underline": false, + "underline_offset": 0.22, + "underline_width": 0.05, + "use_effect_default_color": false, + "words": { + "end_time": [], + "start_time": [], + "text": [] + } + }, + "tracks": { + "attribute": 0, + "flag": 0, + "id": "B468156C-B4B6-4949-91E3-FBDB56839C48", + "is_default_name": true, + "name": "", + "segments": [ + { + "cartoon": false, + "clip": { + "alpha": 1, + "flip": { + "horizontal": false, + "vertical": false + }, + "rotation": 0, + "scale": { + "x": 0.40831670575608614, + "y": 0.40831670575608614 + }, + "transform": { + "x": 0.8086015433073044, + "y": 0.8607584675000406 + } + }, + "common_keyframes": [], + "enable_adjust": false, + "enable_color_curves": true, + "enable_color_match_adjust": false, + "enable_color_wheels": true, + "enable_lut": false, + "enable_smart_color_adjust": false, + "extra_material_refs": [ + "CFA0E8D8-1B2B-4b3a-8CF5-64A1A252D953" + ], + "group_id": "", + "hdr_settings": null, + "id": "ACEDF876-5665-4c6c-AC88-95B3AE49352E", + "intensifies_audio": false, + "is_placeholder": false, + "is_tone_modify": false, + "keyframe_refs": [], + "last_nonzero_volume": 1, + "material_id": "1AE8EF5E-6380-4130-B164-709E2160BFD0", + "render_index": 14000, + "responsive_layout": { + "enable": false, + "horizontal_pos_layout": 0, + "size_layout": 0, + "target_follow": "", + "vertical_pos_layout": 0 + }, + "reverse": false, + "source_timerange": null, + "speed": 1, + "target_timerange": { + "duration": 3000000, + "start": 0 + }, + "template_id": "", + "template_scene": "default", + "track_attribute": 0, + "track_render_index": 0, + "uniform_scale": { + "on": true, + "value": 1 + }, + "visible": true, + "volume": 1 + } + ], + "type": "text" + }, + "text_value": "AI生成画面\n无不良引导" + }, + { + "id": "b422ce35-d439-46d7-9b51-e0d21e72e99d", + "name": "小说内容 请勿模仿", + "material_animations": { + "animations": [], + "id": "96F35048-8837-43de-8E83-4F3C3683FBEC", + "type": "sticker_animation" + }, + "texts": { + "add_type": 0, + "alignment": 1, + "background_alpha": 1, + "background_color": "", + "background_height": 0.14, + "background_horizontal_offset": 0, + "background_round_radius": 0, + "background_style": 0, + "background_vertical_offset": 0, + "background_width": 0.14, + "bold_width": 0, + "border_alpha": 1, + "border_color": "", + "border_width": 0.08, + "caption_template_info": { + "category_id": "", + "category_name": "", + "effect_id": "", + "resource_id": "", + "resource_name": "" + }, + "check_flag": 7, + "combo_info": { + "text_templates": [] + }, + "content": "{\"styles\":[{\"fill\":{\"alpha\":1.0,\"content\":{\"render_type\":\"solid\",\"solid\":{\"alpha\":1.0,\"color\":[1.0,1.0,1.0]}}},\"font\":{\"id\":\"\",\"path\":\"C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf\"},\"range\":[0,9],\"size\":15.0,\"useLetterColor\":true}],\"text\":\"小说内容\\n请勿模仿\"}", + "fixed_height": -1, + "fixed_width": -1, + "font_category_id": "", + "font_category_name": "", + "font_id": "", + "font_name": "", + "font_path": "C:/Users/27698/AppData/Local/JianyingPro/Apps/5.3.0.11154/Resources/Font/SystemFont/zh-hans.ttf", + "font_resource_id": "", + "font_size": 15, + "font_source_platform": 0, + "font_team_id": "", + "font_title": "none", + "font_url": "", + "fonts": [], + "force_apply_line_max_width": false, + "global_alpha": 1, + "group_id": "", + "has_shadow": false, + "id": "92C492B0-944F-48e5-8C99-40FAB82B9648", + "initial_scale": 1, + "is_rich_text": false, + "italic_degree": 0, + "ktv_color": "", + "language": "", + "layer_weight": 1, + "letter_spacing": 0, + "line_feed": 1, + "line_max_width": 0.82, + "line_spacing": 0.02, + "name": "", + "original_size": [], + "preset_category": "", + "preset_category_id": "", + "preset_has_set_alignment": false, + "preset_id": "", + "preset_index": 0, + "preset_name": "", + "recognize_task_id": "", + "recognize_type": 0, + "relevance_segment": [], + "shadow_alpha": 0.8, + "shadow_angle": -45, + "shadow_color": "", + "shadow_distance": 8, + "shadow_point": { + "x": 1.0182337649086284, + "y": -1.0182337649086284 + }, + "shadow_smoothing": 1, + "shape_clip_x": false, + "shape_clip_y": false, + "style_name": "", + "sub_type": 0, + "subtitle_keywords": null, + "text_alpha": 1, + "text_color": "#ffffff", + "text_curve": null, + "text_preset_resource_id": "", + "text_size": 30, + "text_to_audio_ids": [], + "tts_auto_update": false, + "type": "text", + "typesetting": 0, + "underline": false, + "underline_offset": 0.22, + "underline_width": 0.05, + "use_effect_default_color": false, + "words": { + "end_time": [], + "start_time": [], + "text": [] + } + }, + "tracks": { + "attribute": 0, + "flag": 0, + "id": "DAB33387-452D-4597-9A10-932E1FC1B47E", + "is_default_name": true, + "name": "", + "segments": [ + { + "cartoon": false, + "clip": { + "alpha": 1, + "flip": { + "horizontal": false, + "vertical": false + }, + "rotation": 0, + "scale": { + "x": 0.40831670575608614, + "y": 0.40831670575608614 + }, + "transform": { + "x": 0.8238636363636365, + "y": 0.8535353535353536 + } + }, + "common_keyframes": [], + "enable_adjust": false, + "enable_color_curves": true, + "enable_color_match_adjust": false, + "enable_color_wheels": true, + "enable_lut": false, + "enable_smart_color_adjust": false, + "extra_material_refs": [ + "96F35048-8837-43de-8E83-4F3C3683FBEC" + ], + "group_id": "", + "hdr_settings": null, + "id": "D31C287B-9E51-475a-8843-37BFC7C07CF9", + "intensifies_audio": false, + "is_placeholder": false, + "is_tone_modify": false, + "keyframe_refs": [], + "last_nonzero_volume": 1, + "material_id": "92C492B0-944F-48e5-8C99-40FAB82B9648", + "render_index": 14006, + "responsive_layout": { + "enable": false, + "horizontal_pos_layout": 0, + "size_layout": 0, + "target_follow": "", + "vertical_pos_layout": 0 + }, + "reverse": false, + "source_timerange": null, + "speed": 1, + "target_timerange": { + "duration": 3000000, + "start": 0 + }, + "template_id": "", + "template_scene": "default", + "track_attribute": 0, + "track_render_index": 0, + "uniform_scale": { + "on": true, + "value": 1 + }, + "visible": true, + "volume": 1 + } + ], + "type": "text" + }, + "text_value": "小说内容\n请勿模仿" + } + ], + "key_frame": { + "key_frame": "KFTypePositionY", + "isFixedSpeed": true, + "key_frame_time": 4, + "up_down_key_frame": { + "default_scale": 133, + "start_position": 275, + "end_position": 275 + }, + "left_right_key_frame": { + "default_scale": 133, + "start_position": 275, + "end_position": 275 + }, + "scale_key_frame": { + "default_scale": 100, + "start_position": 210, + "end_position": 133 + } + }, + "write_setting": { + "split_char": "。,“”‘’!?【】「」《》()…—;,''\"\"!?[]<>()-:;╰*°▽°*╯′,ノ﹏<o‵゚Д゚,ノ,へ ̄工╬▔皿", + "merge_count": 5, + "merge_char": ",", + "end_char": "。" + } +} \ No newline at end of file diff --git a/src/define/api/apiUrlDefine.ts b/src/define/api/apiUrlDefine.ts index e0711b6..c11cd4c 100644 --- a/src/define/api/apiUrlDefine.ts +++ b/src/define/api/apiUrlDefine.ts @@ -6,6 +6,7 @@ let apiUrl = [ isPackage: false, mj_url: { imagine: 'https://api.laitool.cc/mj/submit/imagine', + video: 'https://api.laitool.cc/mj/submit/video', describe: 'https://api.laitool.cc/mj/submit/describe', update_file: 'https://api.laitool.cc/mj/submit/upload-discord-images', once_get_task: 'https://api.laitool.cc/mj/task/${id}/fetch' @@ -22,6 +23,7 @@ let apiUrl = [ isPackage: false, mj_url: { imagine: 'https://laitool.net/mj/submit/imagine', + video: 'https://laitool.net/mj/submit/video', describe: 'https://laitool.net/mj/submit/describe', update_file: 'https://laitool.net/mj/submit/upload-discord-images', once_get_task: 'https://laitool.net/mj/task/${id}/fetch' diff --git a/src/define/data/softawareData.ts b/src/define/data/softawareData.ts index 4c399e5..a8bf8ae 100644 --- a/src/define/data/softawareData.ts +++ b/src/define/data/softawareData.ts @@ -1,10 +1,24 @@ export const SoftwareData = { - "version": "V3.4.1", - "date": "2025-07-08", + "version": "V3.4.2", + "date": "2025-08-08", "notes": [ - "1. 适配 MJ V7 版本的 oref 参数", - "2. 恢复超级单证中文版推理模式", - "3. 出图进度添加本地图片文件是否存在的判断", - "4. 新增 推理模式 Laitool提示词专家-全能优化版" + "1. 新增图/文转视频菜单界面,专注实现图/文转视频(目前只集成了 MJ VIDEO)", + " • 全新的界面排列,小说列表和批次任务更加分明", + " • 添加转视频进度,在主界面即可看到转视频的比例", + " • 单独的界面去处理图转视频,避免表格数据过多繁琐", + " • 新增分页显示,界面加载更快,也可切换不分页", + " • 单独操作面板,参数修改处理更加清晰,支持多种模式显示", + " • 批量设置转视频配置,可以批量修改分类", + " • 友好的选择视频界面", + "2. 重写软件导出剪映,修复若干草稿导出问题", + " • 修复导出剪映文案和图片对齐问题,解决时长越长越明显的对不上问题", + " • 修复导出草稿关键帧部分问题", + " • 导出的文案通过分镜自动导入,不再需要手动选择SRT", + "3. 美化生成草稿界面弹窗,优化部分逻辑", + " • 删除选择SRT文件,SRT根据聚合推文中导入的SRT自动生成草稿", + " • 只需选择配音文件即可,配音文件和导入的SRT请自行对应", + " • 背景音乐不在内部设置,自行选择文件夹或者是MP3、WAV文件", + " • 背景音乐选择文件夹则读取文件夹,随机获取一个", + " • 背景音乐选择指定的音乐文件则使用选择的" ] } diff --git a/src/define/db/model/Book/BookBackTaskListModel.ts b/src/define/db/model/Book/BookBackTaskListModel.ts index 1bf6036..2659995 100644 --- a/src/define/db/model/Book/BookBackTaskListModel.ts +++ b/src/define/db/model/Book/BookBackTaskListModel.ts @@ -16,6 +16,8 @@ export class BookBackTaskList extends Realm.Object { startTime: number endTime: number messageName?: string + taskId?: string // 任务ID,可能是视频生成任务的ID + taskMessage?: string // 任务消息,可能是视频生成任务的消息 static schema: ObjectSchema = { name: 'BookBackTaskList', @@ -33,7 +35,9 @@ export class BookBackTaskList extends Realm.Object { updateTime: 'date', startTime: 'int', endTime: 'int', - messageName: 'string?' + messageName: 'string?', + taskId: 'string?', // 任务ID,可能是视频生成任务的ID + taskMessage: 'string?', // 任务消息,可能是视频生成任务的消息 }, primaryKey: 'id' } diff --git a/src/define/db/service/Book/bookBackTaskListService.ts b/src/define/db/service/Book/bookBackTaskListService.ts index 32f38cd..5e9df41 100644 --- a/src/define/db/service/Book/bookBackTaskListService.ts +++ b/src/define/db/service/Book/bookBackTaskListService.ts @@ -260,6 +260,32 @@ export class BookBackTaskListService extends BaseRealmService { } } + + /** + * 更新后台任务的数据 + * @param taskId 任务ID + * @param backTaskParam 需要更新的任务数据部分字段 + */ + UpdateBackTaskData(taskId: string, backTaskParam: Partial): void { + this.transaction(() => { + // 根据ID获取后台任务 + let backTask = this.realm.objectForPrimaryKey('BookBackTaskList', taskId) + // 检查任务是否存在 + if (backTask == null) { + throw new Error('更新后台任务数据失败,未找到对应的任务') + } + // 遍历需要更新的字段 + for (const key in backTaskParam) { + // 跳过ID字段,防止主键被修改 + if (key == "id") { + continue; + } + // 更新对应字段的值 + backTask[key] = backTaskParam[key] + } + }) + } + /** * 删除满足条件的数据,包含 id、bookId、bookTaskId * 上面的条件,至少要有一个 diff --git a/src/define/db/service/Book/bookBasic.ts b/src/define/db/service/Book/bookBasic.ts index 4f53472..1bfcff0 100644 --- a/src/define/db/service/Book/bookBasic.ts +++ b/src/define/db/service/Book/bookBasic.ts @@ -281,6 +281,15 @@ const migration = (oldRealm: Realm, newRealm: Realm) => { } } + if (oldRealm.schemaVersion < 44) { + const oldBookTask = oldRealm.objects('BookBackTaskList') + const newBookTask = newRealm.objects('BookBackTaskList') + for (let i = 0; i < oldBookTask.length; i++) { + newBookTask[i].taskId = undefined; + newBookTask[i].taskMessage = undefined; + } + } + } export class BaseRealmService extends BaseService { @@ -323,7 +332,7 @@ export class BaseRealmService extends BaseService { VideoMessage ], path: this.dbpath, - schemaVersion: 43, + schemaVersion: 44, migration: migration } this.realm = await Realm.open(config) diff --git a/src/define/db/service/Book/bookService.ts b/src/define/db/service/Book/bookService.ts index 5871925..16ff93c 100644 --- a/src/define/db/service/Book/bookService.ts +++ b/src/define/db/service/Book/bookService.ts @@ -12,6 +12,7 @@ import { FfmpegOptions } from '../../../../main/Service/ffmpegOptions.js' import { version } from '../../../../../package.json' import { Book } from '../../../../model/book/book.js' import { GeneralResponse } from '../../../../model/generalResponse.js' +import { ImageToVideoModels } from '@/define/enum/video.js' export class BookService extends BaseRealmService { static instance: BookService | null = null @@ -210,6 +211,8 @@ export class BookService extends BaseRealmService { throw new Error('未知的小说类型') } + let videoCategory = ImageToVideoModels.MJ_VIDEO; + this.realm.write(() => { book.version = version this.realm.create('Book', book) @@ -233,6 +236,7 @@ export class BookService extends BaseRealmService { createTime: new Date(), version: version, imageCategory: imageCategory, + videoCategory: videoCategory, openVideoGenerate: false } diff --git a/src/define/db/service/Book/bookTaskDetailService.ts b/src/define/db/service/Book/bookTaskDetailService.ts index 7191156..f1cdd43 100644 --- a/src/define/db/service/Book/bookTaskDetailService.ts +++ b/src/define/db/service/Book/bookTaskDetailService.ts @@ -11,6 +11,7 @@ const { v4: uuidv4 } = require('uuid') import { Book } from "../../../../model/book/book" import { GeneralResponse } from '../../../../model/generalResponse.js' import { BookTaskDetail } from '@/model/book/bookTaskDetail' +import { ValidateJson } from '@/define/Tools/validate' let dbPath = path.resolve(define.db_path, 'book.realm') @@ -73,8 +74,21 @@ export class BookTaskDetailService extends BaseRealmService { subImagePath: (item.subImagePath as string[])?.map((subImage) => { return JoinPath(define.project_path, subImage) }), - subVideoPath: (item.subVideoPath as string[])?.map((subVideo) => { - return JoinPath(define.project_path, subVideo) + subVideoPath: (item.subVideoPath as string[]).map((subVideo) => subVideo.toString()), + subVideoPathObject: (item.subVideoPath as string[])?.map((subVideo) => { + if (isEmpty(subVideo)) { + return {}; + } else { + if (!ValidateJson(subVideo)) { + return {}; + } else { + let obj = JSON.parse(subVideo); + if (!isEmpty(obj.localPath)) { + obj.localPath = JoinPath(define.project_path, obj.localPath) + '?t=' + new Date().getTime(); + } + return obj + } + } }), characterTags: item.characterTags ? item.characterTags.map((tag) => tag) : null, sceneTags: item.sceneTags ? item.sceneTags.map((tag) => tag) : null, @@ -257,6 +271,7 @@ export class BookTaskDetailService extends BaseRealmService { */ UpdateBookTaskDetailVideoMessage(bookTaskDetailId: string, videoMessage: BookTaskDetail.VideoMessage): void { this.transaction(() => { + console.log("开始更新小说分镜的视频消息", bookTaskDetailId, videoMessage) let bookTaskDetail = this.realm.objectForPrimaryKey('BookTaskDetail', bookTaskDetailId) let videoMessageRes = this.realm.objectForPrimaryKey('VideoMessage', bookTaskDetailId) if (bookTaskDetail.videoMessage == null) { diff --git a/src/define/define_string/bookDefineString.ts b/src/define/define_string/bookDefineString.ts index 615464e..0a3119e 100644 --- a/src/define/define_string/bookDefineString.ts +++ b/src/define/define_string/bookDefineString.ts @@ -193,14 +193,20 @@ const BOOK = { /** 修改小说分镜的VideoMessage */ UPDATE_BOOK_TASK_DETAIL_VIDEO_MESSAGE: "UPDATE_BOOK_TASK_DETAIL_VIDEO_MESSAGE", + /** 重新下载视频任务 */ + RELOAD_VIDEO_TASK_INFO: "RELOAD_VIDEO_TASK_INFO", + /** Runway图转视频返回前端数据任务 */ RUNWAY_IMAGE_TO_VIDEO_RETURN: "RUNWAY_IMAGE_TO_VIDEO_RETURN", + /** MJ VIDEO 图转视频返回前端数据任务 */ + MJ_VIDEO_TO_VIDEO_RETURN: "MJ_VIDEO_TO_VIDEO_RETURN", + /** 获取指定的条件的图转视频的数据,包含字批次 */ GET_VIDEO_BOOK_INFO_LIST: "GET_VIDEO_BOOK_INFO_LIST", /** 获取小说图片和视频生成进度 */ - GET_BOOK_IMAGE_AND_VIDEO_PROGRESS : "GET_BOOK_IMAGE_AND_VIDEO_PROGRESS" + GET_BOOK_IMAGE_AND_VIDEO_PROGRESS: "GET_BOOK_IMAGE_AND_VIDEO_PROGRESS" //#endregion diff --git a/src/define/define_string/systemDefineString.ts b/src/define/define_string/systemDefineString.ts index d2d5c9c..e414568 100644 --- a/src/define/define_string/systemDefineString.ts +++ b/src/define/define_string/systemDefineString.ts @@ -17,4 +17,7 @@ export const SYSTEM = { /** 选择多个指定文件后缀的文件 */ SELECT_MULTIPLE_FILE: "SELECT_MULTIPLE_FILE", + + /** 选择文件夹或指定后缀的文件 */ + SELECT_FOLDER_OR_FILE: "SELECT_FOLDER_OR_FILE", } \ No newline at end of file diff --git a/src/define/enum/bookEnum.ts b/src/define/enum/bookEnum.ts index 30b5d40..7755d29 100644 --- a/src/define/enum/bookEnum.ts +++ b/src/define/enum/bookEnum.ts @@ -100,6 +100,10 @@ export enum BookBackTaskType { LUMA_VIDEO = 'luma_video', // kling 生成视频 KLING_VIDEO = 'kling_video', + // MJ Video + MJ_VIDEO = 'mj_video', + // MJ VIDEO EXTEND 视频拓展 + MJ_VIDEO_EXTEND = 'mj_video_extend' } diff --git a/src/define/enum/option.ts b/src/define/enum/option.ts index 9065cf5..cc40b90 100644 --- a/src/define/enum/option.ts +++ b/src/define/enum/option.ts @@ -5,9 +5,10 @@ export enum OptionType { STRING = 'string', NUMBER = 'number', BOOLEAN = 'boolean', - JOSN = 'json' + JSON = 'json' } + export enum OptionKeyName { //#region 文案处理 @@ -79,7 +80,17 @@ export enum OptionKeyName { /** * ComfyUI 工作流设置 */ - ComfyUI_WorkFlowSetting = "ComfyUI_WorkFlowSetting" + ComfyUI_WorkFlowSetting = "ComfyUI_WorkFlowSetting", + + //#endregion + + //#region Image To Video + + /** 是否显示右侧的Image To Video 操作面板 */ + ImageToVideo_ShowRightPanel = 'ImageToVideo_ShowRightPanel', + + /** 是否显示分页 */ + ImageToVideo_ShowPagination = 'ImageToVideo_ShowPagination', //#endregion } \ No newline at end of file diff --git a/src/define/enum/softwareEnum.ts b/src/define/enum/softwareEnum.ts index e8497d5..b773904 100644 --- a/src/define/enum/softwareEnum.ts +++ b/src/define/enum/softwareEnum.ts @@ -69,6 +69,8 @@ export enum ResponseMessageType { RUNWAY_VIDEO = "RUNWAY_VIDEO",// Runway生成视频 LUMA_VIDEO = "LUMA_VIDEO",// Luma生成视频 KLING_VIDEO = "KLING_VIDEO",// Kling生成视频 + MJ_VIDEO = "MJ_VIDEO",// MJ生成视频 + MJ_VIDEO_EXTEND = "MJ_VIDEO_EXTEND",// MJ生成视频拓展 VIDEO_SUCESS = "VIDEO_SUCESS" //视频生成成功 } diff --git a/src/define/enum/video.ts b/src/define/enum/video.ts index 66bcd60..fb34ad0 100644 --- a/src/define/enum/video.ts +++ b/src/define/enum/video.ts @@ -1,6 +1,8 @@ //#region 图转视频类型 +import { BookBackTaskType } from "./bookEnum"; + /** 图片转视频的方式 */ export enum ImageToVideoModels { /** runway 生成视频 */ @@ -12,7 +14,27 @@ export enum ImageToVideoModels { /** Pika 生成视频 */ PIKA = "PIKA", /** MJ 图转视频 */ - MJ_VIDEO = "MJ_VIDEO" + MJ_VIDEO = "MJ_VIDEO", + /** MJ 视频拓展 */ + MJ_VIDEO_EXTEND = "MJ_VIDEO_EXTEND" +} + + +export const MappingTaskTypeToVideoModel = (type: BookBackTaskType | string) => { + switch (type) { + case BookBackTaskType.LUMA_VIDEO: + return ImageToVideoModels.LUMA; + case BookBackTaskType.RUNWAY_VIDEO: + return ImageToVideoModels.RUNWAY; + case BookBackTaskType.KLING_VIDEO: + return ImageToVideoModels.KLING; + case BookBackTaskType.MJ_VIDEO: + return ImageToVideoModels.MJ_VIDEO; + case BookBackTaskType.MJ_VIDEO_EXTEND: + return ImageToVideoModels.MJ_VIDEO_EXTEND; + default: + return "UNKNOWN" + } } /** @@ -48,11 +70,11 @@ export const GetImageToVideoModelsLabel = (model: ImageToVideoModels | string) = */ export const GetImageToVideoModelsOptions = () => { return [ + { label: GetImageToVideoModelsLabel(ImageToVideoModels.MJ_VIDEO), value: ImageToVideoModels.MJ_VIDEO }, { label: GetImageToVideoModelsLabel(ImageToVideoModels.RUNWAY), value: ImageToVideoModels.RUNWAY }, { label: GetImageToVideoModelsLabel(ImageToVideoModels.LUMA), value: ImageToVideoModels.LUMA }, { label: GetImageToVideoModelsLabel(ImageToVideoModels.KLING), value: ImageToVideoModels.KLING }, { label: GetImageToVideoModelsLabel(ImageToVideoModels.PIKA), value: ImageToVideoModels.PIKA }, - { label: GetImageToVideoModelsLabel(ImageToVideoModels.MJ_VIDEO), value: ImageToVideoModels.MJ_VIDEO } ] } @@ -126,4 +148,62 @@ export enum KlingMode { PRO = "pro" } -//#endregion \ No newline at end of file +//#endregion + +//#region MJ Video + +/** + * 对视频任务进行操作。不为空时,index、taskId必填 + */ +export enum MJVideoAction { + Extend = "extend", +} + +/** + * 首帧图片,扩展时可为空 + */ +export enum MJVideoImageType { + Base64 = "base64", + Url = "url", +} + +/** + * MJ Video的动作幅度 + */ +export enum MJVideoMotion { + High = "high", + Low = "low", +} +/** + * 获取MJ视频动作幅度的标签 + * + * @param model MJ视频动作幅度枚举值或字符串 + * @returns 返回对应的中英文标签 + */ +export function GetMJVideoMotionLabel(model: MJVideoMotion | string) { + switch (model) { + case MJVideoMotion.High: + return "高 (High)"; + case MJVideoMotion.Low: + return "低 (Low)"; + default: + return "无效" + } +} + +/** + * 获取MJ视频动作幅度的选项列表 + * + * @returns 返回包含标签和值的选项数组,用于下拉选择框等UI组件 + */ +export function GetMJVideoMotionOptions() { + return [ + { + label: GetMJVideoMotionLabel(MJVideoMotion.Low), value: MJVideoMotion.Low + }, { + label: GetMJVideoMotionLabel(MJVideoMotion.High), value: MJVideoMotion.High + } + ] +} + +//#endregion diff --git a/src/main/IPCEvent/bookIpc.ts b/src/main/IPCEvent/bookIpc.ts index fe20323..25d453e 100644 --- a/src/main/IPCEvent/bookIpc.ts +++ b/src/main/IPCEvent/bookIpc.ts @@ -371,6 +371,9 @@ export function BookIpc() { /** 修改小说详细分镜的Videomessage */ ipcMain.handle(DEFINE_STRING.BOOK.UPDATE_BOOK_TASK_DETAIL_VIDEO_MESSAGE, async (event, bookTaskDetailId, videoMessage) => await videoGlobal.UpdateBookTaskDetailVideoMessage(bookTaskDetailId, videoMessage)) + /** 重新下载视频任务 */ + ipcMain.handle(DEFINE_STRING.BOOK.RELOAD_VIDEO_TASK_INFO, async (_, bookTaskDetailId) => await bookImageTextToVideoIndex.ReloadVideoTaskInfo(bookTaskDetailId)) + /** 获取指定的条件的图转视频的数据,包含子批次 */ ipcMain.handle(DEFINE_STRING.BOOK.GET_VIDEO_BOOK_INFO_LIST, async (event, condition: BookVideo.BookVideoInfoListQuertCondition) => await bookImageTextToVideoIndex.GetVideoBookInfoList(condition)) diff --git a/src/main/IPCEvent/systemIpc.ts b/src/main/IPCEvent/systemIpc.ts index 4af500a..dca4469 100644 --- a/src/main/IPCEvent/systemIpc.ts +++ b/src/main/IPCEvent/systemIpc.ts @@ -34,5 +34,8 @@ function SystemIpc() { /** 选择多个指定文件后缀的文件 */ ipcMain.handle(DEFINE_STRING.SYSTEM.SELECT_MULTIPLE_FILE, async (event, value: string[]) => await electronInterface.SelectMultipleFile(value)) + + /** 选择文件夹或指定后缀的文件 */ + ipcMain.handle(DEFINE_STRING.SYSTEM.SELECT_FOLDER_OR_FILE, async (event, value?: string[]) => await electronInterface.SelectFolderOrFile(value)) } export { SystemIpc } diff --git a/src/main/Service/Book/BookImageTextToVideo/bookImageTextToVideoCategory.ts b/src/main/Service/Book/BookImageTextToVideo/bookImageTextToVideoCategory.ts new file mode 100644 index 0000000..19e3882 --- /dev/null +++ b/src/main/Service/Book/BookImageTextToVideo/bookImageTextToVideoCategory.ts @@ -0,0 +1,64 @@ +import { errorMessage, successMessage } from "@/main/Public/generalTools"; +import { BookBasicHandle } from "../bookBasicHandle"; +import { ImageToVideoModels } from "@/define/enum/video"; +import { MJVideoService } from "../../video/mjVideo"; +import { isEmpty } from "lodash"; +import { GeneralResponse } from "@/model/generalResponse"; + + +export class BookImageTextToVideoCategory extends BookBasicHandle { + mjVideoService: MJVideoService + constructor() { + super(); + this.mjVideoService = new MJVideoService(); + } + + /** + * 重新加载视频任务信息 + * + * 根据小说分镜的ID重新获取视频任务的信息。该方法会检查小说分镜数据是否存在, + * 视频消息数据是否存在,视频任务ID是否存在,然后根据视频类型调用相应的服务 + * 重新加载视频任务。 + * + * @param bookTaskDetailId - 小说分镜的ID + * @returns 成功时返回任务信息,失败时返回错误信息 + * @throws 如果重新加载过程中发生错误 + */ + async ReloadVideoTaskInfo(bookTaskDetailId: string) { + try { + await this.InitBookBasicHandle() + let bookTaskDetail = this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetailId); + if (bookTaskDetail == null) { + return errorMessage('没有找到对应的小说分镜数据,请先添加小说分镜', 'BookImageTextToVideoCategory_ReloadVideoTaskInfo'); + } + + let videoMessage = bookTaskDetail.videoMessage; + if (videoMessage == null) { + return errorMessage('没有找到对应的小说分镜的视频消息数据,请先添加视频消息', 'BookImageTextToVideoCategory_ReloadVideoTaskInfo'); + } + + if (isEmpty(videoMessage.taskId)) { + return errorMessage('没有找到对应的小说分镜的视频任务ID,请先添加视频任务', 'BookImageTextToVideoCategory_ReloadVideoTaskInfo'); + } + + let res: GeneralResponse.ErrorItem | GeneralResponse.SuccessItem; + switch (videoMessage.videoType) { + case ImageToVideoModels.MJ_VIDEO: + res = await this.mjVideoService.ReloadMJVideoTask(bookTaskDetail, videoMessage.taskId); + break; + default: + return errorMessage('不支持的视频类型,请检查视频类型', 'BookImageTextToVideoCategory_ReloadVideoTaskInfo'); + } + + // 检查返回结果 + if (res.code != 1) { + return errorMessage(res.message, 'BookImageTextToVideoCategory_ReloadVideoTaskInfo'); + } + return successMessage(res.data, res.message, 'BookImageTextToVideoCategory_ReloadVideoTaskInfo'); + + } catch (error) { + return errorMessage('重新下载视频任务失败,错误信息:' + error.message, 'BookImageTextToVideoCategory_ReloadVideoTaskInfo'); + } + } + +} \ No newline at end of file diff --git a/src/main/Service/Book/BookImageTextToVideo/bookImageTextToVideoIndex.ts b/src/main/Service/Book/BookImageTextToVideo/bookImageTextToVideoIndex.ts index 19a400e..e7bdbe9 100644 --- a/src/main/Service/Book/BookImageTextToVideo/bookImageTextToVideoIndex.ts +++ b/src/main/Service/Book/BookImageTextToVideo/bookImageTextToVideoIndex.ts @@ -1,12 +1,15 @@ import { BookImageTextToVideoInfo } from "./bookImageTextToVideoInfo"; +import { BookImageTextToVideoCategory } from "./bookImageTextToVideoCategory"; export class BookImageTextToVideoIndex { bookImageTextToVideoInfo: BookImageTextToVideoInfo; + bookImageTextToVideoCategory: BookImageTextToVideoCategory constructor() { this.bookImageTextToVideoInfo = new BookImageTextToVideoInfo(); + this.bookImageTextToVideoCategory = new BookImageTextToVideoCategory(); } //#region Info @@ -14,9 +17,16 @@ export class BookImageTextToVideoIndex { /** 获取用于视频生成的小说信息列表 根据查询条件返回小说数据,如果指定了bookTaskId,则返回对应小说的单个任务数据 否则返回所有启用了视频生成功能的小说及其任务数据 */ GetVideoBookInfoList = async (condition: BookVideo.BookVideoInfoListQuertCondition) => await this.bookImageTextToVideoInfo.GetVideoBookInfoList(condition) - + + /** 获取小说图片和视频生成的进度信息 根据提供的参数查询指定小说或小说任务的图片和视频生成进度 */ GetBookImageAndVideoProgress = async (bookId?: string, bookTaskId?: string) => await this.bookImageTextToVideoInfo.GetBookImageAndVideoProgress(bookId, bookTaskId); //#endregion + //#region Category + + ReloadVideoTaskInfo = async (bookTaskDetailId: string) => await this.bookImageTextToVideoCategory.ReloadVideoTaskInfo(bookTaskDetailId); + + //#endregion + } \ No newline at end of file diff --git a/src/main/Service/Book/BookImageTextToVideo/bookImageTextToVideoInfo.ts b/src/main/Service/Book/BookImageTextToVideo/bookImageTextToVideoInfo.ts index 3c8b0c9..7ac7719 100644 --- a/src/main/Service/Book/BookImageTextToVideo/bookImageTextToVideoInfo.ts +++ b/src/main/Service/Book/BookImageTextToVideo/bookImageTextToVideoInfo.ts @@ -234,7 +234,7 @@ export class BookImageTextToVideoInfo extends BookBasicHandle { imageProgress += 1; } // 检查视频信息 - if (!isEmpty(bookTaskDetail.videoPath) && await CheckFileOrDirExist(bookTaskDetail.videoPath)) { + if (!isEmpty(bookTaskDetail.generateVideoPath) && await CheckFileOrDirExist(bookTaskDetail.generateVideoPath)) { videoProgress += 1; } } diff --git a/src/main/Service/Book/ReverseBook.ts b/src/main/Service/Book/ReverseBook.ts index 9e335c5..930065a 100644 --- a/src/main/Service/Book/ReverseBook.ts +++ b/src/main/Service/Book/ReverseBook.ts @@ -135,7 +135,6 @@ export class ReverseBook { ...item, outImagePath: isEmpty(item.outImagePath) ? item.outImagePath : item.outImagePath + '?t=' + new Date().getTime(), subImagePath: item.subImagePath && item.subImagePath.length > 0 ? item.subImagePath.map(it => it + '?t=' + new Date().getTime()) : item.subImagePath, - subVideoPath: item.subVideoPath && item.subVideoPath.length > 0 ? item.subVideoPath.map(it => it + '?t=' + new Date().getTime()) : item.subVideoPath, } }) diff --git a/src/main/Service/Book/bookBasicHandle.ts b/src/main/Service/Book/bookBasicHandle.ts index 609237a..54ccb5d 100644 --- a/src/main/Service/Book/bookBasicHandle.ts +++ b/src/main/Service/Book/bookBasicHandle.ts @@ -2,13 +2,14 @@ import { BookService } from "@/define/db/service/Book/bookService" import { BookTaskDetailService } from "@/define/db/service/Book/bookTaskDetailService" import { BookTaskService } from "@/define/db/service/Book/bookTaskService" import { OptionRealmService } from "@/define/db/service/SoftWare/optionRealmService" +import { BookBackTaskListService } from "@/define/db/service/Book/bookBackTaskListService" export class BookBasicHandle { bookTaskDetailService!: BookTaskDetailService bookTaskService!: BookTaskService optionRealmService!: OptionRealmService bookService!: BookService - + bookBackTaskListService!: BookBackTaskListService constructor() { // 初始化 @@ -28,6 +29,9 @@ export class BookBasicHandle { if (!this.bookService) { this.bookService = await BookService.getInstance() } + if (!this.bookBackTaskListService) { + this.bookBackTaskListService = await BookBackTaskListService.getInstance() + } } async transaction(callback: (realm: any) => void) { diff --git a/src/main/Service/Book/bookTask.ts b/src/main/Service/Book/bookTask.ts index 4a05364..4d137f9 100644 --- a/src/main/Service/Book/bookTask.ts +++ b/src/main/Service/Book/bookTask.ts @@ -11,6 +11,7 @@ import { BookServiceBasic } from "../ServiceBasic/bookServiceBasic"; import { ValidateJson } from "../../../define/Tools/validate"; import fs from 'fs' import { TimeStringToMilliseconds } from "../../../define/Tools/time"; +import { ImageToVideoModels } from "@/define/enum/video"; /** * 小说批次相关的操作 @@ -123,6 +124,7 @@ export class BookTask { let name = 'output_' + no.toString().padStart(5, '0'); let imageFolder = path.join(define.project_path, `${bookTask.bookId}/tmp/${name}`); let imageCategory = global.config.defaultImageMode ?? BookImageCategory.MJ; + let videoCategory = ImageToVideoModels.MJ_VIDEO; let book = await this.bookServiceBasic.GetBookDataById(bookTask.bookId) if (!isEmpty(bookTask.imageCategory)) { imageCategory = bookTask.imageCategory; @@ -136,6 +138,10 @@ export class BookTask { } } + if(!isEmpty(bookTask.videoCategory)){ + videoCategory = bookTask.videoCategory; + } + let newBookTask = { id: uuidv4(), bookId: bookTask.bookId, @@ -157,6 +163,7 @@ export class BookTask { prefixPrompt: addNewBookTask.prefixPrompt ??= undefined, suffixPrompt: addNewBookTask.suffixPrompt ?? undefined, imageCategory: imageCategory, + videoCategory : videoCategory, subImageFolder: [], draftSrtStyle: undefined, backgroundMusic: bookTask.backgroundMusic ??= undefined, diff --git a/src/main/Service/Book/bookVideo.ts b/src/main/Service/Book/bookVideo.ts index b8a5dbc..0c0081e 100644 --- a/src/main/Service/Book/bookVideo.ts +++ b/src/main/Service/Book/bookVideo.ts @@ -18,6 +18,7 @@ import util from 'util'; import { spawn, exec } from 'child_process'; import { SendMessageToRenderer } from "../globalService"; import { TaskModal } from "@/model/task"; +import compressing from "compressing"; const execAsync = util.promisify(exec); export class BookVideo { @@ -25,12 +26,68 @@ export class BookVideo { bookServiceBasic: BookServiceBasic jianyingService: JianyingService bookSetting: BookSetting + constructor() { this.setting = new Setting(global) this.bookServiceBasic = new BookServiceBasic() this.jianyingService = new JianyingService() } + /** + * 安全执行外部脚本的方法 + * @param scriptPath 脚本路径 + * @param configPath 配置文件路径 + * @returns Promise<{stdout: string, stderr: string}> + */ + private async executeScript(scriptPath: string, configPath: string): Promise<{ stdout: string, stderr: string }> { + return new Promise((resolve, reject) => { + // 设置环境变量 + const env = { + ...process.env, + PYTHONIOENCODING: 'utf-8', + PYTHONLEGACYWINDOWSSTDIO: 'utf-8', + LANG: 'zh_CN.UTF-8', + PYTHONUTF8: '1' + }; + + // 使用spawn方式执行,更好地控制进程 + const child = spawn(scriptPath, [configPath.replaceAll("\\", '/')], { + env: env, + cwd: path.dirname(scriptPath), + stdio: ['pipe', 'pipe', 'pipe'] + }); + + let stdout = ''; + let stderr = ''; + + child.stdout?.on('data', (data) => { + stdout += data.toString('utf8'); + }); + + child.stderr?.on('data', (data) => { + stderr += data.toString('utf8'); + }); + + child.on('close', (code) => { + if (code === 0) { + resolve({ stdout, stderr }); + } else { + reject(new Error(`脚本执行失败,退出代码: ${code}, 错误信息: ${stderr}`)); + } + }); + + child.on('error', (error) => { + reject(new Error(`无法启动脚本: ${error.message}`)); + }); + + // 设置超时 + setTimeout(() => { + child.kill(); + reject(new Error('脚本执行超时')); + }, 300000); // 5分钟超时 + }); + } + //#region 引用主小说相关数据 /** @@ -100,7 +157,10 @@ export class BookVideo { * @param book 小说数据 * @param bookTask 对应的小说任务数据 */ - private async GenerateConfigFile(book: Book.SelectBook, bookTask: Book.SelectBookTask): Promise { + private async GenerateConfigFile(book: Book.SelectBook, bookTask: Book.SelectBookTask): Promise<{ + draftName: string; + configJsonPath: string; + }> { try { // 先修改通用设置 let saveProjectRes = await this.setting.ModifySampleSetting(JSON.stringify({ @@ -111,7 +171,6 @@ export class BookVideo { throw new Error("修改通用设置失败") } // 开始生成配置文件 - let configPath = path.join(book.bookFolderPath, `scripts/${bookTask.name}_config.json`); await CheckFolderExistsOrCreate(path.dirname(configPath)); @@ -120,17 +179,71 @@ export class BookVideo { bookTaskId: bookTask.id }); + let musicPath: string | undefined = undefined; + // 处理背景音乐 + if (!isEmpty(bookTask.backgroundMusic)) { + + // 判断文件或者是文件夹是不是存在 + if (!CheckFileOrDirExist(bookTask.backgroundMusic)) { + throw new Error("背景音乐文件夹或文件不存在,请检查"); + } + + // 判断背景音乐是文件夹还是文件 文件的话 就直接赋值 文件夹的话 随机一个文件 + let isFolder = await fs.promises.stat(bookTask.backgroundMusic).then(stat => stat.isDirectory()).catch(() => false); + if (!isFolder) { + musicPath = bookTask.backgroundMusic; + } else { + let files = await GetFilesWithExtensions(bookTask.backgroundMusic, [".mp3", ".wav"]); + if (files.length <= 0) { + throw new Error("背景音乐文件夹下面未存在有效的音频文件"); + } else { + const randomIndex = Math.floor(Math.random() * files.length); + musicPath = files[randomIndex]; + } + + } + } + + // 处理草稿文件 + let draft_name = `${book.name}_${bookTask.name}`; + let draft_path = path.join(global.config.draft_path, draft_name); + await fs.promises.rm(draft_path, { recursive: true, force: true }); + await compressing.zip.uncompress(define.draft_temp_path, path.join(global.config.draft_path, draft_name)); + let draftPath = path.join(draft_path, "draft_content.json"); + + // 处理关键帧数据 + let key_frame_setting_str = await fs.promises.readFile(define.clip_setting, 'utf-8'); + if (!ValidateJson(key_frame_setting_str)) { + throw new Error("关键帧配置文件格式错误,请检查"); + } + let key_frame_setting = JSON.parse(key_frame_setting_str) + let key_frame = key_frame_setting.key_frame; + + // 判断关键帧配置是不是存在。不存在直接结束 + if (key_frame == null) { + throw new Error("没有找到关键帧配置,请检查"); + } + let newKeyFrame = { + ...key_frame, + is_fixed_speed: key_frame.isFixedSpeed ? key_frame.isFixedSpeed : false, + } + let configData = { srt_time_information: [], video_config: { srt_path: bookTask.srtPath, audio_path: bookTask.audioPath, draft_srt_style: bookTask.draftSrtStyle ? bookTask.draftSrtStyle : "0", - background_music: bookTask.backgroundMusic, + background_music: musicPath, friendly_reminder: bookTask.friendlyReminder ? bookTask.friendlyReminder : "0", + draft_content_json_path: draftPath, + key_frame_info: newKeyFrame, } } + // 用于判断是不是开启视频合成,是不是给视频路径 + let openVideo = bookTask.openVideoGenerate ?? false; + for (let i = 0; i < bookTaskDetail.length; i++) { const element = bookTaskDetail[i]; let frameData = { @@ -150,6 +263,7 @@ export class BookVideo { prompt_json: '', name: element.name + '.png', outImagePath: element.outImagePath, + generateVideoPath: openVideo ? element.generateVideoPath : null, subImagePath: element.subImagePath, scene_tags: [], imageLock: element.imageLock, @@ -158,9 +272,14 @@ export class BookVideo { configData.srt_time_information.push(frameData) } // 完毕,将数据写出 - await fs.promises.writeFile(configPath, JSON.stringify(configData)); + await fs.promises.writeFile(configPath, JSON.stringify(configData), 'utf-8'); + let configJsonPath = path.join(book.bookFolderPath, 'scripts/config.json'); // 复制一个到config.json中 - await CopyFileOrFolder(configPath, path.join(book.bookFolderPath, 'scripts/config.json')); + await CopyFileOrFolder(configPath, configJsonPath); + return { + draftName: draft_name, + configJsonPath: configJsonPath + } } catch (error) { throw error } @@ -227,40 +346,84 @@ export class BookVideo { element.imageFolder, draft_name); result.push(draft_name); } else { - await this.GenerateConfigFile(book, element); - // 数据处理完毕,开始输出 - let clipDraft = new ClipDraft(global, [element.name, { - srt_path: operateBookType == OperateBookType.ASSIGNBOOKTASK ? book.srtPath : element.srtPath, - audio_path: operateBookType == OperateBookType.ASSIGNBOOKTASK ? book.audioPath : element.audioPath, - draft_srt_style: operateBookType == OperateBookType.ASSIGNBOOKTASK ? (book.draftSrtStyle ? book.draftSrtStyle : '0') : (element.draftSrtStyle ? element.draftSrtStyle : "0"), - background_music: operateBookType == OperateBookType.ASSIGNBOOKTASK ? book.backgroundMusic : element.backgroundMusic, - friendly_reminder: operateBookType == OperateBookType.ASSIGNBOOKTASK ? (book.friendlyReminder ? book.bookFolderPath : '0') : (element.friendlyReminder ? element.friendlyReminder : "0"), - }]) - let res = await clipDraft.addDraft(); - if (res.code == 0) { - throw new Error(res.message) - } - result.push(res.draft_name); - } + let { draftName, configJsonPath } = await this.GenerateConfigFile(book, element); - let bookTaskDetails = await this.bookServiceBasic.GetBookTaskDetailData({ - bookTaskId: element.id - }); - let repalceObject: ReplaceOnject[] = [] - - for (let i = 0; i < bookTaskDetails.length; i++) { - const bookTaskDetail = bookTaskDetails[i]; - if (!isEmpty(bookTaskDetail.generateVideoPath) && await CheckFileOrDirExist(bookTaskDetail.generateVideoPath)) { - repalceObject.push({ - materialName: path.basename(bookTaskDetail.outImagePath), - videoPath: bookTaskDetail.generateVideoPath, - imagePath: bookTaskDetail.outImagePath - }) + // 开始调用 exe 执行 草稿的导出 + let jianyingExePath = path.join(define.scripts_path, "xiangbei_jianying_main.exe"); + if (!CheckFileOrDirExist(jianyingExePath)) { + throw new Error("没有找到导出剪映的执行文件,请检查"); + } + + // 开始执行exe + try { + // 首先尝试使用spawn方法执行 + const output = await this.executeScript(jianyingExePath, configJsonPath); + + // 检查stderr是否真的是错误 + if (output.stderr && (output.stderr.includes('Error') || output.stderr.includes('failed') || output.stderr.includes('UnicodeEncodeError'))) { + throw new Error(output.stderr); + } + + // 导出成功 + let stdout = output.stdout; + // 将导出的日志写道文件里面 + let exportLogPath = path.join(book.bookFolderPath, `scripts/JianYingExportLog/${draftName}_export_log_${new Date().getTime()}.txt`); + await CheckFolderExistsOrCreate(path.dirname(exportLogPath)); + await fs.promises.writeFile(exportLogPath, stdout, 'utf-8'); + + // 导出成功 将草稿名字返回 + result.push(draftName); + } catch (execError) { + // 如果spawn方法失败,尝试使用原来的execAsync方法作为备用 + try { + const env = { + ...process.env, + PYTHONIOENCODING: 'utf-8', + PYTHONLEGACYWINDOWSSTDIO: 'utf-8', + LANG: 'zh_CN.UTF-8', + PYTHONUTF8: '1' + }; + + // 尝试最简单的执行方式,不使用chcp + const simpleCommand = `"${jianyingExePath}" "${configJsonPath}"`; + + const output = await execAsync(simpleCommand, { + maxBuffer: 1024 * 1024 * 10, + encoding: 'utf-8', + env: env, + cwd: path.dirname(jianyingExePath), + timeout: 300000 + }); + + if (output.stderr && (output.stderr.includes('Error') || output.stderr.includes('failed') || output.stderr.includes('UnicodeEncodeError'))) { + throw new Error(output.stderr); + } + + // 导出成功 + let stdout = output.stdout; + // 将导出的日志写道文件里面 + let exportLogPath = path.join(book.bookFolderPath, `scripts/JianYingExportLog/${draftName}_export_log_${new Date().getTime()}.txt`); + await CheckFolderExistsOrCreate(path.dirname(exportLogPath)); + await fs.promises.writeFile(exportLogPath, stdout, 'utf-8'); + + // 导出成功 将草稿名字返回 + result.push(draftName); + } catch (fallbackError) { + // 记录详细的错误信息到文件 + const errorLogPath = path.join(book.bookFolderPath, `scripts/JianYingExportLog/error_${draftName}_${new Date().getTime()}.txt`); + await CheckFolderExistsOrCreate(path.dirname(errorLogPath)); + const errorInfo = { + spawnError: execError.message, + execAsyncError: fallbackError.message, + scriptPath: jianyingExePath, + configPath: configJsonPath, + timestamp: new Date().toISOString() + }; + await fs.promises.writeFile(errorLogPath, JSON.stringify(errorInfo, null, 2), 'utf-8'); + + throw new Error(`所有执行方法都失败了。详细错误已记录到: ${errorLogPath}`); + } } - } - // 这边操作草稿,修改数据(把图片替换为视频) - if (repalceObject && repalceObject.length > 0) { - await this.jianyingService.ReplaceDraftMaterialImageToVideo(book.name + "_" + element.name, repalceObject); } } // 所有的草稿都添加完毕之后开始返回 diff --git a/src/main/Service/MJ/mjApi.ts b/src/main/Service/MJ/mjApi.ts index 574704a..769a55c 100644 --- a/src/main/Service/MJ/mjApi.ts +++ b/src/main/Service/MJ/mjApi.ts @@ -19,6 +19,7 @@ class MJApi { mjSimpleSetting: MJSettingModel.MjSimpleSettingModel bootType: string imagineUrl: string + videoUrl: string fetchTaskUrl: string describeUrl: string @@ -56,6 +57,51 @@ class MJApi { return randomAccountId } + + async InitMJAPISetting() { + let defaultApiUrl = GetMJUrlOptions("api"); + // 获取自定义的API的地址 + let customApiUrl = await this.optionServices.GetOptionByKey(OptionKeyName.MJ_CustomAPISetting); + if (customApiUrl.code == 0) { + throw new Error("加载MJ设置失败,失败原因如下:" + customApiUrl.message) + } + if (!(customApiUrl.data == null || isEmpty(customApiUrl.data.value) || !ValidateJson(customApiUrl.data.value))) { + let customApiUrlData = ValidateJsonAndParse(customApiUrl.data.value) as any[]; + customApiUrlData.forEach((item: any) => { + let baseUrl = item.baseUrl.replace(/\/$/, '') + defaultApiUrl.push({ + label: item.name, + value: item.id, + isPackage: true, + mj_url: { + imagine: baseUrl + '/mj/submit/imagine', + video: baseUrl + '/mj/submit/video', + describe: baseUrl + '/mj/submit/describe', + update_file: baseUrl + '/mj/submit/upload-discord-images', + once_get_task: baseUrl + '/mj/task/${id}/fetch', + query_url: null + } as any, + buy_url: null + }) + }) + } + + let apiUrlIndex = defaultApiUrl.findIndex(item => item.value == this.mj_globalSetting.mj_apiSetting.mjApiUrl); + if (apiUrlIndex == -1) { + throw new Error('没有找到MJ API对应的请求URL,请检查配置'); + } + let apiUrlItem = defaultApiUrl[apiUrlIndex]; + if (apiUrlItem.mj_url == null) { + throw new Error('没有找到MJ API对应的请求URL,请检查配置'); + } + return { + imagineUrl: apiUrlItem.mj_url.imagine, + videoUrl: apiUrlItem.mj_url.video, + describeUrl: apiUrlItem.mj_url.describe, + fetchTaskUrl: apiUrlItem.mj_url.once_get_task + } + } + /** * 初始化MJ设置 */ @@ -77,6 +123,7 @@ class MJApi { this.bootType = this.mjSimpleSetting.selectRobot == MJRobotType.NIJI ? "NIJI_JOURNEY" : "MID_JOURNEY" if (this.mjSimpleSetting.type == MJImageType.REMOTE_MJ) { this.imagineUrl = define.remotemj_api + 'mj/submit/imagine' + this.videoUrl = undefined; // 远程MJ不支持视频 this.describeUrl = define.remotemj_api + 'mj/submit/describe' this.fetchTaskUrl = define.remotemj_api + 'mj/task/${id}/fetch' } else if (this.mjSimpleSetting.type == MJImageType.LOCAL_MJ) { @@ -90,6 +137,7 @@ class MJApi { localRemoteBaseUrl = localRemoteBaseUrl.slice(0, -1) } this.imagineUrl = localRemoteBaseUrl + ":" + localRemotePort + '/mj/submit/imagine' + this.videoUrl = undefined; // 本地代理模式不支持视频 this.describeUrl = localRemoteBaseUrl + ":" + localRemotePort + '/mj/submit/describe' this.fetchTaskUrl = localRemoteBaseUrl + ":" + localRemotePort + '/mj/task/${id}/fetch' } else if (this.mjSimpleSetting.type == MJImageType.PACKAGE_MJ) { @@ -109,6 +157,7 @@ class MJApi { isPackage: true, mj_url: { imagine: baseUrl + '/mj/submit/imagine', + video: undefined, // 生图包不支持视频 describe: baseUrl + '/mj/submit/describe', update_file: baseUrl + '/mj/submit/upload-discord-images', once_get_task: baseUrl + '/mj/task/${id}/fetch', @@ -128,47 +177,16 @@ class MJApi { throw new Error('没有找到MJ API对应的请求URL,请检查配置'); } this.imagineUrl = apiUrlItem.mj_url.imagine + this.videoUrl = apiUrlItem.mj_url.video this.describeUrl = apiUrlItem.mj_url.describe this.fetchTaskUrl = apiUrlItem.mj_url.once_get_task } else { - let defaultApiUrl = GetMJUrlOptions("api"); - // 获取自定义的API的地址 - let customApiUrl = await this.optionServices.GetOptionByKey(OptionKeyName.MJ_CustomAPISetting); - if (customApiUrl.code == 0) { - throw new Error("加载MJ设置失败,失败原因如下:" + customApiUrl.message) - } - if (!(customApiUrl.data == null || isEmpty(customApiUrl.data.value) || !ValidateJson(customApiUrl.data.value))) { - let customApiUrlData = ValidateJsonAndParse(customApiUrl.data.value) as any[]; - customApiUrlData.forEach((item: any) => { - let baseUrl = item.baseUrl.replace(/\/$/, '') - defaultApiUrl.push({ - label: item.name, - value: item.id, - isPackage: true, - mj_url: { - imagine: baseUrl + '/mj/submit/imagine', - describe: baseUrl + '/mj/submit/describe', - update_file: baseUrl + '/mj/submit/upload-discord-images', - once_get_task: baseUrl + '/mj/task/${id}/fetch', - query_url: null - }, - buy_url: null - }) - }) - } - - let apiUrlIndex = defaultApiUrl.findIndex(item => item.value == this.mj_globalSetting.mj_apiSetting.mjApiUrl); - if (apiUrlIndex == -1) { - throw new Error('没有找到MJ API对应的请求URL,请检查配置'); - } - let apiUrlItem = defaultApiUrl[apiUrlIndex]; - if (apiUrlItem.mj_url == null) { - throw new Error('没有找到MJ API对应的请求URL,请检查配置'); - } - this.imagineUrl = apiUrlItem.mj_url.imagine - this.describeUrl = apiUrlItem.mj_url.describe - this.fetchTaskUrl = apiUrlItem.mj_url.once_get_task + let { imagineUrl, videoUrl, describeUrl, fetchTaskUrl } = await this.InitMJAPISetting(); + this.imagineUrl = imagineUrl + this.videoUrl = videoUrl + this.describeUrl = describeUrl + this.fetchTaskUrl = fetchTaskUrl } } diff --git a/src/main/Service/Options/optionSerialization.ts b/src/main/Service/Options/optionSerialization.ts new file mode 100644 index 0000000..3b89f5c --- /dev/null +++ b/src/main/Service/Options/optionSerialization.ts @@ -0,0 +1,63 @@ +import { OptionType } from '@/define/enum/option' +import { OptionModel } from '@/model/option/option' +import { isEmpty } from 'lodash' + +/** + * 将字符串转换为指定类型的值 + * @param value 要转换的字符串值 + * @param type 目标类型 ('string'|'number'|'boolean'|'json') + * @returns 转换后的值 + */ +export function convertStringToType(value: string, type: OptionType, checkString?: string): T { + let checkErrorString = '请到 ' + checkString + ' 检查设置!' + // 如果值为空,直接报错 + if (value === undefined || value === null || value === '') { + throw new Error('当前值为空!' + checkString ? checkErrorString : '') + } + + try { + switch (type.toLowerCase()) { + case 'string': + return value as unknown as T + case 'number': + const num = Number(value) + if (isNaN(num)) { + throw new Error( + `Cannot convert "${value}" to number, ${checkString ? checkErrorString : ''}` + ) + } + return num as unknown as T + case 'boolean': + return (value.toLowerCase() === 'true' || value === '1') as unknown as T + case 'json': + try { + return JSON.parse(value) as T + } catch (e) { + throw new Error(`Invalid JSON string: ${value}, ${checkString ? checkErrorString : ''}`) + } + default: + throw new Error(`Unsupported type: ${type}`) + } + } catch (error) { + throw error + } +} + +/** + * 将选项对象的值转换为指定类型 + * @param option 选项对象 + * @param defaultValue 默认值,当值为空时返回的默认值 + * @returns + */ +export const optionSerialization = ( + option: OptionModel.OptionItem | null, + checkString?: string +): T => { + if (option == null) { + throw new Error('未找到选项对象,请检查所有的选项设置是否存在!') + } + if (option.value == null || option.value == undefined || isEmpty(option.value)) { + throw new Error('option value is null') + } + return convertStringToType(option.value, option.type as OptionType, checkString) +} diff --git a/src/main/Service/Options/optionServices.ts b/src/main/Service/Options/optionServices.ts index 5574836..9658062 100644 --- a/src/main/Service/Options/optionServices.ts +++ b/src/main/Service/Options/optionServices.ts @@ -99,7 +99,7 @@ export class OptionServices { if (ValidateJson(CW_AISettingData)) { return successMessage(JSON.parse(CW_AISettingData), "数据已存在,无需再次同步或初始化", "OptionOptions.InitCopyWritingAISetting") } else { - this.optionRealmService.ModifyOptionByKey(OptionKeyName.CW_AISetting, JSON.stringify(aiSetting), OptionType.JOSN); + this.optionRealmService.ModifyOptionByKey(OptionKeyName.CW_AISetting, JSON.stringify(aiSetting), OptionType.JSON); return successMessage(aiSetting, "数据已存在,但是数据格式不正确,已重新初始化", "OptionOptions.InitCopyWritingAISetting") } } @@ -112,16 +112,16 @@ export class OptionServices { let softwareData = software.toJSON()[0] let SynchronizeAISetting = softwareData["aiSetting"] as string if (ValidateJson(SynchronizeAISetting)) { - this.optionRealmService.ModifyOptionByKey(OptionKeyName.CW_AISetting, SynchronizeAISetting, OptionType.JOSN); + this.optionRealmService.ModifyOptionByKey(OptionKeyName.CW_AISetting, SynchronizeAISetting, OptionType.JSON); return successMessage(JSON.parse(SynchronizeAISetting), "同步旧文案处理AI设置数据成功", "OptionOptions.InitCopyWritingAISetting") } else { - this.optionRealmService.ModifyOptionByKey(OptionKeyName.CW_AISetting, JSON.stringify(aiSetting), OptionType.JOSN); + this.optionRealmService.ModifyOptionByKey(OptionKeyName.CW_AISetting, JSON.stringify(aiSetting), OptionType.JSON); return successMessage(aiSetting, "旧的文案处理AI设置无效,已重新重置", "OptionOptions.InitCopyWritingAISetting") } } // 新设置 - this.optionRealmService.ModifyOptionByKey(OptionKeyName.CW_AISetting, JSON.stringify(aiSetting), OptionType.JOSN); + this.optionRealmService.ModifyOptionByKey(OptionKeyName.CW_AISetting, JSON.stringify(aiSetting), OptionType.JSON); return successMessage(aiSetting, '初始化文案处理AI设置成功', 'OptionOptions.SynchronizeAISettingOldData') } catch (error: any) { return errorMessage( diff --git a/src/main/Service/system/electronInterface.ts b/src/main/Service/system/electronInterface.ts index 84e90bc..48b6bfc 100644 --- a/src/main/Service/system/electronInterface.ts +++ b/src/main/Service/system/electronInterface.ts @@ -102,4 +102,60 @@ export default class ElectronInterface { return errorMessage('选择文件错误,错误信息如下:' + error.message, 'SystemIpc_SelectMultipleFile'); } } + + /** + * 选择文件夹或指定后缀的文件 + * @param extensions 文件后缀列表(可选) + * @returns + */ + public async SelectFolderOrFile(extensions?: string[]): Promise { + try { + // 使用消息框让用户选择类型 + const choice = await dialog.showMessageBox({ + type: 'question', + title: '选择类型', + message: '请选择要选择的类型:', + buttons: ['选择文件', '选择文件夹', '取消'], + defaultId: 0, + cancelId: 2 + }); + + if (choice.response === 2) { + throw new Error('用户取消选择'); + } + + if (choice.response === 0) { + // 选择文件 + const result = await dialog.showOpenDialog({ + properties: ['openFile'], + filters: extensions && extensions.length > 0 ? [ + { name: 'Audio Files', extensions }, + { name: 'All Files', extensions: ['*'] } + ] : [{ name: 'All Files', extensions: ['*'] }], + title: '选择文件' + }); + + if (result.filePaths.length === 0) { + throw new Error('没有选择文件'); + } + + return successMessage(result.filePaths[0], '选择文件成功', 'SystemIpc_SelectFolderOrFile'); + } else { + // 选择文件夹 + const result = await dialog.showOpenDialog({ + properties: ['openDirectory'], + title: '选择文件夹' + }); + + if (result.filePaths.length === 0) { + throw new Error('没有选择文件夹'); + } + + return successMessage(result.filePaths[0], '选择文件夹成功', 'SystemIpc_SelectFolderOrFile'); + } + } catch (error) { + console.error('选择文件或文件夹错误:', error); + return errorMessage('选择文件或文件夹错误,错误信息如下:' + error.message, 'SystemIpc_SelectFolderOrFile'); + } + } } \ No newline at end of file diff --git a/src/main/Service/task/taskManage.ts b/src/main/Service/task/taskManage.ts index 7d17c93..72bb030 100644 --- a/src/main/Service/task/taskManage.ts +++ b/src/main/Service/task/taskManage.ts @@ -379,6 +379,8 @@ export class TaskManager { case BookBackTaskType.RUNWAY_VIDEO: case BookBackTaskType.LUMA_VIDEO: case BookBackTaskType.KLING_VIDEO: + case BookBackTaskType.MJ_VIDEO: + case BookBackTaskType.MJ_VIDEO_EXTEND: this.AddImageToVideo(task); break; diff --git a/src/main/Service/video/kling.ts b/src/main/Service/video/kling.ts index 2a62c88..e4526cb 100644 --- a/src/main/Service/video/kling.ts +++ b/src/main/Service/video/kling.ts @@ -137,6 +137,8 @@ export class KlingService { } } + + async FetchKlingVideoResult(bookTaskDetail: Book.SelectBookTaskDetail, task: TaskModal.Task, taskId: string, baseUrl: string, gptApiKey: string, useTransfer: boolean = false) { while (true) { try { @@ -243,5 +245,4 @@ export class KlingService { } } } - } \ No newline at end of file diff --git a/src/main/Service/video/mjVideo.ts b/src/main/Service/video/mjVideo.ts new file mode 100644 index 0000000..be6a810 --- /dev/null +++ b/src/main/Service/video/mjVideo.ts @@ -0,0 +1,515 @@ +import { TaskModal } from "@/model/task"; +import { BookBasicHandle } from "../Book/bookBasicHandle"; +import MJApi from "@/main/Service/MJ/mjApi" +import { ValidateJson } from "@/define/Tools/validate"; +import { BookTaskDetail } from "@/model/book/bookTaskDetail"; +import { ImageToVideoModels, MJVideoMotion, VideoStatus } from "@/define/enum/video"; +import { GetImageBase64 } from "@/define/Tools/image"; +import axios from "axios"; +import { SendMessageToRenderer } from "../globalService"; +import { ResponseMessageType } from "@/define/enum/softwareEnum"; +import { Book } from "@/model/book/book"; +import { cloneDeep, isEmpty } from "lodash"; +import { BookBackTaskStatus, BookTaskStatus } from "@/define/enum/bookEnum"; +import { errorMessage, successMessage } from "@/main/Public/generalTools"; +import { DEFINE_STRING } from "@/define/define_string"; +import path from "path"; +import { CheckFolderExistsOrCreate, CopyFileOrFolder } from "@/define/Tools/file"; +import { DownloadFile } from "@/define/Tools/common"; +import { define } from "@/define/define"; +import { c } from "naive-ui"; + +export class MJVideoService extends BookBasicHandle { + constructor() { + super(); + } + + //#region InitMJSetting + /** + * 初始化MJ设置 + * @returns 返回MJ全局设置和视频URL + */ + async InitMJSetting() { + try { + // 创建MJ API实例 + let mjApi = new MJApi(); + // 初始化MJ设置 + await mjApi.InitMJSetting(); + + let { imagineUrl, videoUrl, describeUrl, fetchTaskUrl } = await mjApi.InitMJAPISetting(); + // 返回全局设置和视频URL + return { + mj_globalSetting: mjApi.mj_globalSetting, + videoUrl: videoUrl, + fetchTaskUrl: fetchTaskUrl + } + } catch (error) { + // 如果初始化失败,抛出错误 + throw new Error(`初始化MJ设置失败,${error.message},请检查MJ配置`); + } + } + + //#endregion + + + //#region MJImageToVideo + /** + * MJ图片转视频处理方法 + * 将指定的图片通过Midjourney API转换为视频 + * @param task 任务对象,包含小说任务详情ID等信息 + * @returns Promise + * @throws 当初始化失败、参数错误或API调用失败时抛出异常 + */ + async MJImageToVideo(task: TaskModal.Task): Promise { + try { + await this.InitBookBasicHandle(); + let bookTaskDetail = this.bookTaskDetailService.GetBookTaskDetailDataById(task.bookTaskDetailId); + if (bookTaskDetail == null) { + throw new Error("未找到对应的小说批次任务分镜数据,请检查"); + } + + let videoMessage = bookTaskDetail.videoMessage; + if (videoMessage == null) { + throw new Error("小说批次任务分镜数据的转视频配置为空,请检查"); + } + + let mjVideoOptionsString = bookTaskDetail.videoMessage.mjVideoOptions; + if (!ValidateJson(mjVideoOptionsString)) { + throw new Error("MJ 图转视频 参数错误,请检查"); + } + let mjVideoOptions: BookTaskDetail.MjVideoOptions = JSON.parse(mjVideoOptionsString); + + let imageUrl = videoMessage.imageUrl?.trim() || mjVideoOptions.image?.trim() || ""; + let prompt = videoMessage.prompt?.trim(); + let motion: MJVideoMotion = mjVideoOptions.motion === MJVideoMotion.High + ? MJVideoMotion.High + : MJVideoMotion.Low; + + let raw = mjVideoOptions.raw || false; + + // 判断 图片是不是网络图片,不是网络图片的话判断当前图片再本地是不是存在,存在的话讲图片转为 base64 + if (!imageUrl.startsWith("http")) { + imageUrl = await GetImageBase64(imageUrl.split("?t=")[0]); + } + + // 判断是不是有 有效的提示词 有的话 判断是不是视频原始 是的话 在提示词后面添加 --raw + if (!isEmpty(prompt) && raw) { + prompt = prompt + " --raw"; + } + + let body = { + prompt: prompt, + image: imageUrl, + motion: motion, + } + + let useTransfer = false; + + let { mj_globalSetting, videoUrl, fetchTaskUrl } = await this.InitMJSetting(); + console.log("MJImageToVideo", mj_globalSetting, videoUrl); + + + let apiKey = mj_globalSetting.mj_apiSetting.apiKey; + // 开始请求 + let res = await axios.post(videoUrl, body, { + headers: { + "Authorization": apiKey + } + }); + console.log("MJImageToVideo response", res.data); + let resData = res.data; + let id = resData.result; + + // 修改Task, 将数据写入 + this.bookBackTaskListService.UpdateBackTaskData(task.id, { + taskId: id, + taskMessage: JSON.stringify(resData), + }); + + // 修改videoMessage数据 + videoMessage.taskId = id; + videoMessage.status = VideoStatus.WAIT; + videoMessage.messageData = JSON.stringify(resData); + videoMessage.msg = ""; + delete videoMessage.imageUrl; + this.bookTaskDetailService.UpdateBookTaskDetailVideoMessage(task.bookTaskDetailId, videoMessage); + + // 返回前端数据 + SendMessageToRenderer({ + code: 1, + id: task.bookTaskDetailId, + message: "MJ Video 合成任务提交成功", + type: ResponseMessageType.MJ_VIDEO, + data: JSON.stringify(videoMessage) + }, task.messageName); + + await this.FetchMJVideoResult(bookTaskDetail, task, id, fetchTaskUrl, apiKey, useTransfer) + } catch (error) { + throw new Error(`MJ 图转视频 失败,失败信息入下:${error.message}`); + } + } + + //#endregion + + //#region FetchMJVideoResult + + /** + * 获取MJ视频生成结果 + * 通过轮询方式检查Midjourney视频生成任务的状态,直到任务完成或失败 + * @param bookTaskDetail 小说任务详情对象,包含视频消息等信息 + * @param task 任务对象,包含任务ID、消息名称等信息 + * @param taskId Midjourney返回的任务ID,用于查询任务状态 + * @param fetchTaskUrl 查询任务状态的API地址模板 + * @param apiKey API密钥,用于身份验证 + * @param useTransfer 是否使用传输模式,默认为false + * @returns Promise + * @throws 当任务失败或API调用异常时抛出错误 + */ + async FetchMJVideoResult(bookTaskDetail: Book.SelectBookTaskDetail, task: TaskModal.Task, taskId: string, fetchTaskUrl: string, apiKey: string, useTransfer: boolean = false) { + while (true) { + try { + + let fetchUrl = fetchTaskUrl.replace("${id}", taskId); + + let res = await axios.get(fetchUrl, { + headers: { + "Authorization": apiKey + } + }) + + let resData = res.data; + + let status = resData.status.toLowerCase(); + let code = status == 'failure' || status == 'cancel' ? 0 : 1 + let progress = resData.progress && resData.progress.length > 0 + ? parseInt(resData.progress.slice(0, -1)) + : 0; + + if (code == 0) { + // 失败 + let videoMessage = cloneDeep(bookTaskDetail.videoMessage); + + videoMessage.status = VideoStatus.FAIL; + videoMessage.msg = resData.failReason; + videoMessage.taskId = taskId; + videoMessage.messageData = JSON.stringify(resData); + + delete videoMessage.imageUrl; + + // 修改 videoMessage数据 + this.bookTaskDetailService.UpdateBookTaskDetailVideoMessage(bookTaskDetail.id, videoMessage); + + // 修改TASK + this.bookBackTaskListService.UpdateBackTaskData(task.id, { + taskId: taskId, + taskMessage: JSON.stringify(resData), + }) + + // 返回前端数据 + SendMessageToRenderer({ + code: 0, + id: bookTaskDetail.id, + message: "MJ VIDEO 合成视频失败,错误信息如下:" + resData.failReason, + type: ResponseMessageType.MJ_VIDEO, + data: JSON.stringify(videoMessage) + }, task.messageName); + throw new Error("MJ Video 合成视频失败,错误信息如下:" + resData.failReason); + } + else { + // 请求成功 但是需要判断状态和返回的进度 + if (progress == 100 && status == 'success') { + let videoMessage = cloneDeep(bookTaskDetail.videoMessage); + videoMessage.status = VideoStatus.SUCCESS; + videoMessage.taskId = taskId; + if (resData.videoUrls && resData.videoUrls.length > 0) { + videoMessage.videoUrls = []; + resData.videoUrls.forEach((item: any) => { + videoMessage.videoUrls.push(item.url); + }) + videoMessage.videoUrl = videoMessage.videoUrls[0]; + } + videoMessage.messageData = JSON.stringify(resData); + delete videoMessage.imageUrl; + + this.bookTaskDetailService.UpdateBookTaskDetailVideoMessage(task.bookTaskDetailId, videoMessage); + + this.bookTaskService.UpdetedBookTaskData(task.bookTaskId, { + status: BookTaskStatus.IMAGE_TO_VIDEO_SUCCESS, + }) + + this.bookBackTaskListService.UpdateBackTaskData(task.id, { + status: BookBackTaskStatus.DONE, + taskId: taskId, + taskMessage: JSON.stringify(resData), + }) + + SendMessageToRenderer({ + code: 1, + id: bookTaskDetail.id, + message: "MJ VIDEO 合成视频完成", + type: ResponseMessageType.MJ_VIDEO, + data: JSON.stringify(videoMessage) + }, task.messageName); + break; + } + } + + // 再执行中 + let videoMessage = cloneDeep(bookTaskDetail.videoMessage); + videoMessage.status = VideoStatus.PROCESSING; + videoMessage.taskId = taskId; + videoMessage.messageData = JSON.stringify(resData); + delete videoMessage.imageUrl; + this.bookTaskDetailService.UpdateBookTaskDetailVideoMessage(task.bookTaskDetailId, videoMessage); + + SendMessageToRenderer({ + code: 1, + id: bookTaskDetail.id, + message: "MJ VIDEO 合成任务正在合成中", + type: ResponseMessageType.MJ_VIDEO, + data: JSON.stringify(videoMessage) + }, task.messageName); + + // 没有成功 等待二十秒后继续执行 + await new Promise(resolve => setTimeout(resolve, 20000)); + } catch (error) { + throw error; + } + } + } + + //#endregion + + //#region MJVideoExtend + + async MJVideoExtend(task: TaskModal.Task): Promise { + try { + await this.InitBookBasicHandle(); + + let bookTaskDetail = this.bookTaskDetailService.GetBookTaskDetailDataById(task.bookTaskDetailId); + if (bookTaskDetail == null) { + throw new Error("未找到对应的小说批次任务分镜数据,请检查"); + } + + let videoMessage = bookTaskDetail.videoMessage; + if (videoMessage == null) { + throw new Error("小说批次任务分镜数据的转视频配置为空,请检查"); + } + + let mjVideoOptionsString = bookTaskDetail.videoMessage.mjVideoOptions; + if (!ValidateJson(mjVideoOptionsString)) { + throw new Error("MJ 图转视频 参数错误,请检查"); + } + let mjVideoOptions: BookTaskDetail.MjVideoOptions = JSON.parse(mjVideoOptionsString); + + let { mj_globalSetting, videoUrl, fetchTaskUrl } = await this.InitMJSetting(); + + console.log("MJVideoExtend", mj_globalSetting, videoUrl); + + let prompt = videoMessage.prompt?.trim(); + let motion: MJVideoMotion = mjVideoOptions.motion === MJVideoMotion.High + ? MJVideoMotion.High + : MJVideoMotion.Low; + let action = 'extend'; + let index = mjVideoOptions.index; + let taskId = mjVideoOptions.taskId; + let raw = mjVideoOptions.raw || false; + + if (index == undefined || index < 0 || index > 3 || index == null) { + throw new Error("MJ视频拓展参数错误,index必须大于等于0且小于等于3,请检查"); + } + + if (isEmpty(taskId)) { + throw new Error("MJ视频拓展参数错误,taskId不能为空,请检查"); + } + + if (!isEmpty(prompt)) { + if (raw) { + prompt = prompt + " --raw"; + } + } + + let body = { + prompt, + motion, + action, + index, + taskId, + } + let apiKey = mj_globalSetting.mj_apiSetting.apiKey; + let res = await axios.post(videoUrl, body, { + headers: { + "Authorization": apiKey + } + }) + + console.log("MJVideoExtend response", res.data); + let resData = res.data; + + let id = resData.result; + + // 修改Task, 将数据写入 + this.bookBackTaskListService.UpdateBackTaskData(task.id, { + taskId: id, + taskMessage: JSON.stringify(resData), + }); + + // 修改videoMessage数据 + videoMessage.taskId = id; + videoMessage.status = VideoStatus.WAIT; + videoMessage.messageData = JSON.stringify(resData); + videoMessage.msg = ""; + delete videoMessage.imageUrl; + this.bookTaskDetailService.UpdateBookTaskDetailVideoMessage(task.bookTaskDetailId, videoMessage); + + // 返回前端数据 + // 返回前端数据 + SendMessageToRenderer({ + code: 1, + id: task.bookTaskDetailId, + message: "MJ Video EXTENT 任务提交成功", + type: ResponseMessageType.MJ_VIDEO_EXTEND, + data: JSON.stringify(videoMessage) + }, task.messageName); + + let useTransfer = false; + await this.FetchMJVideoResult(bookTaskDetail, task, id, fetchTaskUrl, apiKey, useTransfer) + } catch (error) { + console.error("MJVideoExtend Error:", error); + throw new Error(`MJ视频拓展初始化失败,错误信息:${error.message}`); + } + } + + //#endregion + + //#region ReloadMJVideoTask + + /** + * 重新加载MJ视频任务 + * 重新获取Midjourney视频任务的状态和结果,下载视频文件到本地并更新数据库 + * @param bookTaskDetail 小说任务详情对象,包含视频消息、任务ID等信息 + * @param taskId Midjourney返回的任务ID,用于查询任务状态 + * @returns Promise 返回成功或失败的消息对象 + * @throws 当任务状态不正确、文件下载失败或数据更新异常时抛出错误 + * + * 功能说明: + * 1. 验证任务状态必须为success且进度为100% + * 2. 重新获取视频URL并更新数据库状态 + * 3. 下载主视频文件到本地指定目录 + * 4. 下载所有子视频文件并更新路径信息 + * 5. 更新小说任务状态为图转视频成功 + */ + async ReloadMJVideoTask(bookTaskDetail: Book.SelectBookTaskDetail, taskId: string) { + await this.InitBookBasicHandle(); + + let { mj_globalSetting, videoUrl, fetchTaskUrl } = await this.InitMJSetting(); + let apiKey = mj_globalSetting.mj_apiSetting.apiKey; + let fetchUrl = fetchTaskUrl.replace("${id}", taskId); + + let res = await axios.get(fetchUrl, { + headers: { + "Authorization": apiKey + } + }) + + let resData = res.data; + if (res.status == 204) { + return errorMessage("当前分镜的视频任务状态为 204,没有数据返回,可能是任务不存在或者已被删除,请检查!"); + } + let status = resData.status.toLowerCase(); + let progress = resData.progress && resData.progress.length > 0 + ? parseInt(resData.progress.slice(0, -1)) + : 0; + + if (status != 'success' || progress != 100) { + return errorMessage("当前分镜的视频任务状态不为 success 或者进度不是 100%,不可重新加载!"); + } + + // 开始处理数据返回 + let videoMessage = cloneDeep(bookTaskDetail.videoMessage); + + videoMessage.status = VideoStatus.SUCCESS; + videoMessage.taskId = taskId; + videoMessage.msg = ""; + if (resData.videoUrls && resData.videoUrls.length > 0) { + videoMessage.videoUrls = []; + resData.videoUrls.forEach((item: any) => { + videoMessage.videoUrls.push(item.url); + }) + videoMessage.videoUrl = videoMessage.videoUrls[0]; + } + videoMessage.messageData = JSON.stringify(resData); + delete videoMessage.imageUrl; + + this.bookTaskDetailService.UpdateBookTaskDetailVideoMessage(bookTaskDetail.id, videoMessage); + + this.bookTaskService.UpdetedBookTaskData(bookTaskDetail.bookTaskId, { + status: BookTaskStatus.IMAGE_TO_VIDEO_SUCCESS, + }) + + // 这边开始下载视频 + let book = this.bookService.GetBookDataById(bookTaskDetail.bookId); + if (book == null) { + return errorMessage("重新加载视频任务失败,未找到对应的小说数据,请检查"); + } + + let remoteVideoUrl = bookTaskDetail.videoMessage.videoUrl; + let remoteVideoUrls = bookTaskDetail.videoMessage.videoUrls || []; + if (isEmpty(remoteVideoUrl)) { + return errorMessage("重新加载视频任务失败,未找到对应的小说分镜视频地址,请检查"); + } + + // 开始下载 remoteVideoUrl 并且修改对应的数据 + let videoPath = path.join(book.bookFolderPath, `data/video/temp/${bookTaskDetail.name}_${new Date().getTime()}.mp4`); + await CheckFolderExistsOrCreate(path.dirname(videoPath)); + await DownloadFile(remoteVideoUrl, videoPath); + + let targetPath = path.join(book.bookFolderPath, `data/video/${bookTaskDetail.name}.mp4`); + await CopyFileOrFolder(videoPath, targetPath); + // 开始修改信息 + this.bookTaskDetailService.UpdateBookTaskDetail(bookTaskDetail.id, { + generateVideoPath: targetPath, + }) + + // 开始处理 remoteVideoUrls + if (remoteVideoUrls && remoteVideoUrls.length > 0) { + let tempVideoUrls = bookTaskDetail.subVideoPath || []; + let newVideoUrls: Array = [] + + for (let i = 0; i < remoteVideoUrls.length; i++) { + let tempVideoUrl = remoteVideoUrls[i]; + let tmepVideoPath = path.join(book.bookFolderPath, `data/video/temp/${bookTaskDetail.name}_${i}_${new Date().getTime()}.mp4`); + await CheckFolderExistsOrCreate(path.dirname(tmepVideoPath)); + await DownloadFile(tempVideoUrl, tmepVideoPath); + // 开始修改信息 + // 将信息添加到里面 + let a = { + localPath: path.relative(define.project_path, tmepVideoPath), + remotePath: tempVideoUrl, + taskId: bookTaskDetail.videoMessage.taskId, + index: i, + type: ImageToVideoModels.MJ_VIDEO + } + newVideoUrls.push(JSON.stringify(a)); + } + + // 开始处理数据 + // 将原有的视频路径合并到新数组中 + newVideoUrls.push(...tempVideoUrls); + + this.bookTaskDetailService.UpdateBookTaskDetail(bookTaskDetail.id, { + subVideoPath: newVideoUrls, + }) + } + + let newBookTaskDetail = this.bookTaskDetailService.GetBookTaskDetailDataById(bookTaskDetail.id); + if (newBookTaskDetail == null) { + return errorMessage("重新加载视频任务失败,未找到对应的小说批次任务分镜数据,请检查"); + } + return successMessage(newBookTaskDetail, "重新加载视频任务完成!"); + } + + + //#endregion + +} \ No newline at end of file diff --git a/src/main/Service/video/videoGlobal.ts b/src/main/Service/video/videoGlobal.ts index 52e5117..095474a 100644 --- a/src/main/Service/video/videoGlobal.ts +++ b/src/main/Service/video/videoGlobal.ts @@ -1,4 +1,4 @@ -import { ImageToVideoModels, KlingMode, RunawayModel, RunwaySeconds, VideoModel, VideoStatus } from "@/define/enum/video"; +import { ImageToVideoModels, KlingMode, MappingTaskTypeToVideoModel, MJVideoMotion, RunawayModel, RunwaySeconds, VideoModel, VideoStatus } from "@/define/enum/video"; import { DownloadFile, GetBaseUrl } from "@/define/Tools/common"; import { errorMessage, successMessage } from "@/main/Public/generalTools"; import { BookTaskDetail } from "@/model/book/bookTaskDetail"; @@ -16,6 +16,7 @@ import { KlingService } from "./kling"; import { LumaService } from "./luma"; import { CheckFolderExistsOrCreate, CopyFileOrFolder } from "@/define/Tools/file"; import { ResponseMessageType } from "@/define/enum/softwareEnum"; +import { MJVideoService } from "./mjVideo"; /** * 小说图生视频的基础配置 @@ -26,22 +27,21 @@ export class VideoGlobal { runwayService: RunwayService lumaService: LumaService klingService: KlingService + mjVideoService: MJVideoService constructor() { this.gptService = new GptService(); this.bookServiceBasic = new BookServiceBasic(); this.runwayService = new RunwayService(); this.lumaService = new LumaService(); this.klingService = new KlingService(); + this.mjVideoService = new MJVideoService(); } //#region 初始化分镜的视频配置 - /** - * 初始化分镜的视频配置 - */ - async InitVideoMessage(bookTaskDetailId: string) { + async InitVideoMessageData(bookTaskDetailId: string) { try { - let defaultVideoMode = global.config.defaultVideoMode ?? ImageToVideoModels.RUNWAY; + let defaultVideoMode = global.config.defaultVideoMode ?? ImageToVideoModels.MJ_VIDEO; let { gptUrl, gptApiKey } = await this.gptService.RefreshGptSetting(); console.log("gptUrl", gptUrl, "gptApiKey", gptApiKey); @@ -59,7 +59,6 @@ export class VideoGlobal { seconds: RunwaySeconds.FIVE, }, }; - let options = JSON.stringify(optionObject); let lumaOptions: BookTaskDetail.lumaOptions = { user_prompt: "", @@ -79,6 +78,19 @@ export class VideoGlobal { duration: RunwaySeconds.FIVE, } + let mjVideoOptions: BookTaskDetail.MjVideoOptions = { + action: undefined, + image: !isEmpty(bookTaskDetail.outImagePath) ? path.relative(define.project_path, bookTaskDetail.outImagePath) : "", // 或者根据 Image 类型的定义提供默认值 + index: undefined, + motion: MJVideoMotion.High, // 根据 Motion 类型的定义提供默认值 + noStorage: false, + notifyHook: undefined, + prompt: null, + state: undefined, + taskId: undefined, + raw: false + } + let videoMessage: BookTaskDetail.VideoMessage = { id: bookTaskDetailId, msg: "", @@ -87,13 +99,27 @@ export class VideoGlobal { style: "", imageUrl: !isEmpty(bookTaskDetail.outImagePath) ? path.relative(define.project_path, bookTaskDetail.outImagePath) : "", bookTaskDetailId: bookTaskDetailId, - runwayOptions: options, + runwayOptions: JSON.stringify(optionObject), lumaOptions: JSON.stringify(lumaOptions), klingOptions: JSON.stringify(klingOptions), + mjVideoOptions: JSON.stringify(mjVideoOptions), status: VideoStatus.WAIT, model: VideoModel.IMAGE_TO_VIDEO } + return { optionObject, lumaOptions, klingOptions, mjVideoOptions, videoMessage }; + } catch (error) { + throw error; + } + } + + /** + * 初始化分镜的视频配置 + */ + async InitVideoMessage(bookTaskDetailId: string) { + try { + let { optionObject, lumaOptions, klingOptions, mjVideoOptions, videoMessage } = await this.InitVideoMessageData(bookTaskDetailId); + await this.bookServiceBasic.UpdateBookTaskDetail(bookTaskDetailId, { videoMessage: videoMessage, }) @@ -107,7 +133,7 @@ export class VideoGlobal { //#endregion - //#region 秀嘎视频消息 + //#region 修改视频消息 /** * 修改小说详情信息的VideoMessage * @param bookTaskDetailId @@ -140,36 +166,79 @@ export class VideoGlobal { await this.klingService.KlingImageToVideo(task, gptUrl, gptApiKey, useTransfer); break; + case BookBackTaskType.MJ_VIDEO: + // MJ视频的处理 + await this.mjVideoService.MJImageToVideo(task); + break; + case BookBackTaskType.MJ_VIDEO_EXTEND: + await this.mjVideoService.MJVideoExtend(task); + break; default: throw new Error("暂不支持的视频类型"); } + // return ; // 执行完毕,开始下载视频 let bookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(task.bookTaskDetailId); let book = await this.bookServiceBasic.GetBookDataById(task.bookId); let videoUrl = bookTaskDetail.videoMessage.videoUrl; + let videoUrls = bookTaskDetail.videoMessage.videoUrls || []; if (isEmpty(videoUrl)) { throw new Error("生成的视频地址为空,请检查"); } - // 开始下载 + // 开始下载 videoUrl let videoPath = path.join(book.bookFolderPath, `data/video/temp/${bookTaskDetail.name}_${new Date().getTime()}.mp4`); await CheckFolderExistsOrCreate(path.dirname(videoPath)); await DownloadFile(videoUrl, videoPath); - await CopyFileOrFolder(videoPath, path.join(book.bookFolderPath, `data/video/${bookTaskDetail.name}.mp4`)); + + let targetPath = path.join(book.bookFolderPath, `data/video/${bookTaskDetail.name}.mp4`); + await CopyFileOrFolder(videoPath, targetPath); // 开始修改信息 await this.bookServiceBasic.UpdateBookTaskDetail(bookTaskDetail.id, { - generateVideoPath: path.relative(define.project_path, videoPath), + generateVideoPath: targetPath, }) + // 开始下载 videoUrls + if (videoUrls.length > 0) { + let tempVideoUrls = bookTaskDetail.subVideoPath || []; + let newVideoUrls: string[] = [] + + for (let i = 0; i < videoUrls.length; i++) { + let tempVideoUrl = videoUrls[i]; + let tmepVideoPath = path.join(book.bookFolderPath, `data/video/temp/${bookTaskDetail.name}_${i}_${new Date().getTime()}.mp4`); + await CheckFolderExistsOrCreate(path.dirname(tmepVideoPath)); + await DownloadFile(tempVideoUrl, tmepVideoPath); + // 开始修改信息 + // 将信息添加到里面 + let a = { + localPath: path.relative(define.project_path, tmepVideoPath), + remotePath: tempVideoUrl, + taskId: bookTaskDetail.videoMessage.taskId, + index: i, + type: MappingTaskTypeToVideoModel(task.type), + } + newVideoUrls.push(JSON.stringify(a)); + } + + // 开始处理数据 + // 将原有的视频路径合并到新数组中 + newVideoUrls.push(...tempVideoUrls); + + await this.bookServiceBasic.UpdateBookTaskDetail(bookTaskDetail.id, { + subVideoPath: newVideoUrls, + }) + } + + let newBookTaskDetail = await this.bookServiceBasic.GetBookTaskDetailDataById(task.bookTaskDetailId); // 讲数据返回前端 SendMessageToRenderer({ code: 1, id: task.bookTaskDetailId, message: "视频生成成功", type: ResponseMessageType.VIDEO_SUCESS, - data: videoPath + "?t=" + new Date().getTime() + data: JSON.stringify(newBookTaskDetail) }, task.messageName); console.log("视频生成成功", videoPath); @@ -190,6 +259,7 @@ export class VideoGlobal { taskId: "", msg: message }) + SendMessageToRenderer({ code: 0, id: task.bookTaskDetailId, diff --git a/src/main/index.js b/src/main/index.js index ca14f06..956fdd7 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -21,7 +21,7 @@ import { DiscordIpc, RemoveDiscordIpc } from './IPCEvent/discordIpc.js' import { Logger } from './logger.js' import { RegisterIpc } from './IPCEvent/index' -import { InitRemoteMjSettingType } from './initFunc' +import { InitRemoteMjSettingType, InitData as a } from './initFunc' let tools = new Tools() let imageGenerate = new ImageGenerate(global) @@ -30,6 +30,7 @@ let softWareServiceBasic = new SoftWareServiceBasic() async function InitData(gl) { await InitRemoteMjSettingType() + await a() let res = await setting.getSettingDafultData() gl.config = res return res diff --git a/src/main/initFunc.ts b/src/main/initFunc.ts index 27afb84..c7180b0 100644 --- a/src/main/initFunc.ts +++ b/src/main/initFunc.ts @@ -1,6 +1,8 @@ import { isEmpty } from "lodash"; import { errorMessage, successMessage } from "./Public/generalTools"; import { SoftWareServiceBasic } from "./Service/ServiceBasic/softwareServiceBasic"; +import { OptionKeyName, OptionType } from "@/define/enum/option"; +import { OptionServices } from "./Service/Options/optionServices"; /** @@ -22,4 +24,42 @@ export async function InitRemoteMjSettingType() { } catch (error) { errorMessage("初始化远程MJ的设置类型失败," + error.toString(), "InitRemoteMjSettingType") } -} \ No newline at end of file +} + +/** + * 初始化数据函数 + * @description 用于初始化应用程序所需的选项数据 + */ +export async function InitData() { + // 初始化 Options 数据 + // 循环 initObject 进行添加,在添加之前需要判断数据是不是存在,存在的话不进行处理,直接跳过,只有当不存在的时候在添加 + let optionService = new OptionServices(); + // 遍历初始化对象数组 + for (let i = 0; i < initObject.length; i++) { + const item = initObject[i]; + // 通过键名获取选项数据 + let res = await optionService.GetOptionByKey(item.key); + if (res.code == 1 && res.data == null) { + // 不存在,进行添加 + await optionService.ModifyOptionByKey(item.key, item.value, item.type); + } else { + // 存在,跳过 + continue; + } + } +} + +const initObject = [ + { + table: "Options", + key: OptionKeyName.ImageToVideo_ShowRightPanel, + value: "true", + type: OptionType.BOOLEAN, + }, + { + table: "Options", + key: OptionKeyName.ImageToVideo_ShowPagination, + value: "true", + type: OptionType.BOOLEAN, + } +] \ No newline at end of file diff --git a/src/model/book/book.d.ts b/src/model/book/book.d.ts index 855bf36..66158a3 100644 --- a/src/model/book/book.d.ts +++ b/src/model/book/book.d.ts @@ -145,6 +145,14 @@ declare namespace Book { isSelect?: boolean } + interface subVideoPathModel { + localPath: string, + remotePath: string, + taskId: string, + index?: number, + type: ImageToVideoModels + } + type SelectBookTaskDetail = { id?: string no?: number @@ -154,6 +162,7 @@ declare namespace Book { videoPath?: string // 视频地址 generateVideoPath?: string // 生成的视频地址 subVideoPath?: string[] // 生成的批次视频的地址 + subVideoPathObject?: subVideoPath[] //生成视频的完成结构显示 audioPath?: string // 音频地址 draftDepend?: string // 草稿依赖 word?: string // 文案 diff --git a/src/model/book/bookTaskDetail.d.ts b/src/model/book/bookTaskDetail.d.ts index dfb2161..488de19 100644 --- a/src/model/book/bookTaskDetail.d.ts +++ b/src/model/book/bookTaskDetail.d.ts @@ -1,4 +1,4 @@ -import { ImageToVideoModels, KlingMode, RunawayModel, RunwaySeconds, VideoModel, VideoStatus } from "@/define/enum/video"; +import { ImageToVideoModels, KlingMode, MJVideoAction, MJVideoMotion, RunawayModel, RunwaySeconds, VideoModel, VideoStatus } from "@/define/enum/video"; declare namespace BookTaskDetail { @@ -73,5 +73,45 @@ declare namespace BookTaskDetail { callback_url?: string; // 回调地址,可选,生成视频完成后,会向该地址发送通知 } + interface MjVideoOptions { + /** + * 对视频任务进行操作。不为空时,index、taskId必填 + */ + action?: MJVideoAction; + /** + * 首帧图片,扩展时可为空 + */ + image: string; + /** + * 执行的视频索引号 + */ + index?: number; + /** + * 运动变化 + */ + motion: MJVideoMotion; + /** + * True时,返回官方链接 + */ + noStorage?: boolean; + /** + * 回调地址 + */ + notifyHook?: string; + /** 提示词 */ + prompt?: null | string; + + state?: string; + /** + * 需要操作的视频父任务ID + */ + taskId?: string; + + /** + * 是否减少视频的创意 + */ + raw?: boolean; + } + //#endregion } \ No newline at end of file diff --git a/src/model/task.d.ts b/src/model/task.d.ts index 2d353c4..b3280e1 100644 --- a/src/model/task.d.ts +++ b/src/model/task.d.ts @@ -16,6 +16,8 @@ declare namespace TaskModal { startTime?: number endTime?: number, messageName?: string + taskId?: string // 任务ID,可能是第三方服务的任务ID + taskMessage?: string // 任务消息,可能是第三方服务的任务消息 } interface TaskCondition { diff --git a/src/preload/book/video.ts b/src/preload/book/video.ts index 06ea7ef..18cc2b4 100644 --- a/src/preload/book/video.ts +++ b/src/preload/book/video.ts @@ -13,6 +13,11 @@ const Video = { return await ipcRenderer.invoke(DEFINE_STRING.BOOK.UPDATE_BOOK_TASK_DETAIL_VIDEO_MESSAGE, bookTaskDetailId, videoMessage) }, + /** 重新下载视频任务 */ + ReloadVideoTaskInfo: async (bookTaskDetailId: string) => { + return await ipcRenderer.invoke(DEFINE_STRING.BOOK.RELOAD_VIDEO_TASK_INFO, bookTaskDetailId) + }, + /** 获取指定条件的小说图转视频数据,包含子批次 */ GetVideoBookInfoList: async (condition: BookVideo.BookVideoInfoListQuertCondition) => { return await ipcRenderer.invoke(DEFINE_STRING.BOOK.GET_VIDEO_BOOK_INFO_LIST, condition) diff --git a/src/preload/system.ts b/src/preload/system.ts index fca0bc2..4c56758 100644 --- a/src/preload/system.ts +++ b/src/preload/system.ts @@ -22,5 +22,8 @@ const system = { /** 选择多个指定文件后缀的文件 */ SelectMultipleFile: (value: string[]) => ipcRenderer.invoke(DEFINE_STRING.SYSTEM.SELECT_MULTIPLE_FILE, value), + /** 选择文件夹或指定后缀的文件 */ + SelectFolderOrFile: (value?: string[]) => ipcRenderer.invoke(DEFINE_STRING.SYSTEM.SELECT_FOLDER_OR_FILE, value), + } export { system } diff --git a/src/renderer/components.d.ts b/src/renderer/components.d.ts index 91cd232..4ac2bc1 100644 --- a/src/renderer/components.d.ts +++ b/src/renderer/components.d.ts @@ -9,7 +9,6 @@ export {} declare module 'vue' { export interface GlobalComponents { NAlert: typeof import('naive-ui')['NAlert'] - NAvatar: typeof import('naive-ui')['NAvatar'] NButton: typeof import('naive-ui')['NButton'] NCard: typeof import('naive-ui')['NCard'] NCheckbox: typeof import('naive-ui')['NCheckbox'] @@ -17,16 +16,12 @@ declare module 'vue' { NColorPicker: typeof import('naive-ui')['NColorPicker'] NDataTable: typeof import('naive-ui')['NDataTable'] NDivider: typeof import('naive-ui')['NDivider'] - NDrawer: typeof import('naive-ui')['NDrawer'] - NDrawerContent: typeof import('naive-ui')['NDrawerContent'] NDropdown: typeof import('naive-ui')['NDropdown'] NDynamicInput: typeof import('naive-ui')['NDynamicInput'] NDynamicTags: typeof import('naive-ui')['NDynamicTags'] NEmpty: typeof import('naive-ui')['NEmpty'] NForm: typeof import('naive-ui')['NForm'] NFormItem: typeof import('naive-ui')['NFormItem'] - NGrid: typeof import('naive-ui')['NGrid'] - NGridItem: typeof import('naive-ui')['NGridItem'] NIcon: typeof import('naive-ui')['NIcon'] NImage: typeof import('naive-ui')['NImage'] NImageGroup: typeof import('naive-ui')['NImageGroup'] @@ -34,14 +29,10 @@ declare module 'vue' { NInputNumber: typeof import('naive-ui')['NInputNumber'] NLayout: typeof import('naive-ui')['NLayout'] NLayoutSider: typeof import('naive-ui')['NLayoutSider'] - NList: typeof import('naive-ui')['NList'] - NListItem: typeof import('naive-ui')['NListItem'] NLog: typeof import('naive-ui')['NLog'] NMenu: typeof import('naive-ui')['NMenu'] - NModal: typeof import('naive-ui')['NModal'] NPopover: typeof import('naive-ui')['NPopover'] NProgress: typeof import('naive-ui')['NProgress'] - NScrollbar: typeof import('naive-ui')['NScrollbar'] NSelect: typeof import('naive-ui')['NSelect'] NSlider: typeof import('naive-ui')['NSlider'] NSpace: typeof import('naive-ui')['NSpace'] @@ -52,7 +43,6 @@ declare module 'vue' { NTabs: typeof import('naive-ui')['NTabs'] NTag: typeof import('naive-ui')['NTag'] NText: typeof import('naive-ui')['NText'] - NThing: typeof import('naive-ui')['NThing'] NTooltip: typeof import('naive-ui')['NTooltip'] NTree: typeof import('naive-ui')['NTree'] NUpload: typeof import('naive-ui')['NUpload'] diff --git a/src/renderer/src/common/copyWriting.ts b/src/renderer/src/common/copyWriting.ts index 1cf580d..bf21b1c 100644 --- a/src/renderer/src/common/copyWriting.ts +++ b/src/renderer/src/common/copyWriting.ts @@ -9,7 +9,7 @@ import TextCommon from "./text"; */ async function SaveCWAISimpleSetting() { let optionStore = useOptionStore(); - let saveRes = await window.options.ModifyOptionByKey(OptionKeyName.CW_AISimpleSetting, JSON.stringify(optionStore.CW_AISimpleSetting), OptionType.JOSN); + let saveRes = await window.options.ModifyOptionByKey(OptionKeyName.CW_AISimpleSetting, JSON.stringify(optionStore.CW_AISimpleSetting), OptionType.JSON); if (saveRes.code == 0) { throw new Error(saveRes.message); } diff --git a/src/renderer/src/common/initCommon.ts b/src/renderer/src/common/initCommon.ts index 5f20b09..071b4bf 100644 --- a/src/renderer/src/common/initCommon.ts +++ b/src/renderer/src/common/initCommon.ts @@ -78,7 +78,7 @@ async function InitTTSGlobalSetting() { let saveRes = await window.options.ModifyOptionByKey( OptionKeyName.TTS_GlobalSetting, JSON.stringify(initData), - OptionType.JOSN + OptionType.JSON ) if (saveRes.code == 0) { window.api.showGlobalMessageDialog(saveRes) @@ -108,7 +108,7 @@ async function InitFluxModelList(isMust: boolean = false): Promise - + h(ManageBookTaskGenerateInformation, { bookTask: bookTask.value, @@ -202,6 +204,8 @@ async function GenerateVideo(e) { closeOnEsc: false, title: `合成视频前检查 ${bookTask.value.name}`, maskClosable: false, + showIcon: false, + style: 'width: 800px; max-width: 90vw', content: () => h(ManageBookTaskGenerateInformation, { bookTask: bookTask.value, diff --git a/src/renderer/src/components/Book/Components/ManageBook/ManageBookTaskGenerateInformation.vue b/src/renderer/src/components/Book/Components/ManageBook/ManageBookTaskGenerateInformation.vue index eb273e0..950f372 100644 --- a/src/renderer/src/components/Book/Components/ManageBook/ManageBookTaskGenerateInformation.vue +++ b/src/renderer/src/components/Book/Components/ManageBook/ManageBookTaskGenerateInformation.vue @@ -1,116 +1,230 @@ + + diff --git a/src/renderer/src/components/Book/Components/Video/GenerateVideoDialog.vue b/src/renderer/src/components/Book/Components/Video/GenerateVideoDialog.vue index 2ac73a4..0527c2c 100644 --- a/src/renderer/src/components/Book/Components/Video/GenerateVideoDialog.vue +++ b/src/renderer/src/components/Book/Components/Video/GenerateVideoDialog.vue @@ -62,7 +62,6 @@ let props = defineProps({ let videoType = ref(ImageToVideoModels) let reverseManageStore = useReverseManageStore() let message = useMessage() -let softwareStore = useSoftwareStore() let runwayRef = ref(null) let videoMessage = ref({}) @@ -71,7 +70,6 @@ let lumaOptions = ref({}) let klingOptions = ref({}) async function GetBookTaskDetailOption() { - debugger let res = await window.db.GetBookTaskDetailProperty(props.bookTaskDetailId, 'videoMessage') if (res.code != 1) { message.error(res.message) @@ -184,7 +182,6 @@ async function AddImageToVideoTask() { } else if (videoMessage.value.videoType == ImageToVideoModels.KLING) { type = BookBackTaskType.KLING_VIDEO } - debugger // 添加任务 let res = await window.task.AddBookBackTask( reverseManageStore.selectBook.id, diff --git a/src/renderer/src/components/Book/ManageBook.vue b/src/renderer/src/components/Book/ManageBook.vue index 25c7d27..0587777 100644 --- a/src/renderer/src/components/Book/ManageBook.vue +++ b/src/renderer/src/components/Book/ManageBook.vue @@ -68,10 +68,16 @@ export default defineComponent({ async function GetBookByCondition() { try { + // 只在第一次加载或者当前没有选中书籍时才重置 + if (!reverseManageStore.selectBook || !reverseManageStore.selectBook.id) { + reverseManageStore.resetSelectBook() + } + let res = await reverseManageStore.GetBookDataFromDB({ page: paginationReactive.page, pageSize: paginationReactive.pageSize }) + console.log('获取小说任务数据:', res) if (res.code == 0) { message.error(res.message) return @@ -82,14 +88,15 @@ export default defineComponent({ } pagination.value.pageCount = res_count - - // 开始获取批次任务 - let bookTask = await reverseManageStore.GetBookTaskDataFromDB({ - bookId: reverseManageStore.selectBook.id - }) - if (bookTask.code == 0) { - message.error(bookTask.message) - return + // 开始获取批次任务 - 只有当前有选中的书籍时才尝试加载任务 + if (reverseManageStore.selectBook && reverseManageStore.selectBook.id) { + let bookTask = await reverseManageStore.GetBookTaskDataFromDB({ + bookId: reverseManageStore.selectBook.id + }) + if (bookTask.code == 0) { + message.error(bookTask.message) + return + } } } catch (error) { message.error('获取小说数据失败') @@ -99,7 +106,7 @@ export default defineComponent({ async function InitData() { try { // 初始化数据 - + await GetBookByCondition() } catch (error) { message.error(error.message) diff --git a/src/renderer/src/components/Book/ManageBookTask.vue b/src/renderer/src/components/Book/ManageBookTask.vue index b0df464..cd2b1a1 100644 --- a/src/renderer/src/components/Book/ManageBookTask.vue +++ b/src/renderer/src/components/Book/ManageBookTask.vue @@ -1,7 +1,7 @@ diff --git a/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoConfig.vue b/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoConfig.vue index d284a22..0192bdc 100644 --- a/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoConfig.vue +++ b/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoConfig.vue @@ -1,46 +1,252 @@ diff --git a/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoMJVideo/ImageTextVideoInfoMJVideoExtend.vue b/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoMJVideo/ImageTextVideoInfoMJVideoExtend.vue new file mode 100644 index 0000000..58f4517 --- /dev/null +++ b/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoMJVideo/ImageTextVideoInfoMJVideoExtend.vue @@ -0,0 +1,344 @@ + + + + + diff --git a/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoMJVideo/ImageTextVideoInfoMJVideoImageToVideo.vue b/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoMJVideo/ImageTextVideoInfoMJVideoImageToVideo.vue new file mode 100644 index 0000000..a028cd2 --- /dev/null +++ b/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoMJVideo/ImageTextVideoInfoMJVideoImageToVideo.vue @@ -0,0 +1,263 @@ + + + + + diff --git a/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoMJVideo/ImageTextVideoInfoMJVideoInfo.vue b/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoMJVideo/ImageTextVideoInfoMJVideoInfo.vue new file mode 100644 index 0000000..fe2e565 --- /dev/null +++ b/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoMJVideo/ImageTextVideoInfoMJVideoInfo.vue @@ -0,0 +1,199 @@ + + + + + diff --git a/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoMJVideo/ImageTextVideoInfoMJVideoSelectParentTask.vue b/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoMJVideo/ImageTextVideoInfoMJVideoSelectParentTask.vue new file mode 100644 index 0000000..928366b --- /dev/null +++ b/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoMJVideo/ImageTextVideoInfoMJVideoSelectParentTask.vue @@ -0,0 +1,849 @@ + + + + + diff --git a/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoProgress.vue b/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoProgress.vue deleted file mode 100644 index e313ab5..0000000 --- a/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoProgress.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - - - diff --git a/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoTaskDetail.vue b/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoTaskDetail.vue index fbecde5..1e07a5e 100644 --- a/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoTaskDetail.vue +++ b/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoTaskDetail.vue @@ -3,62 +3,31 @@
任务详情 - - - - 编辑 - - - - 删除 - -
- - + - - + + - + diff --git a/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoTaskList.vue b/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoTaskList.vue index 15a52fa..36a7195 100644 --- a/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoTaskList.vue +++ b/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoTaskList.vue @@ -2,7 +2,15 @@
- 任务列表 +
+ + + 返回任务列表 + +
+ 分页 @@ -12,7 +20,7 @@ 右侧面板 - + @@ -24,11 +32,11 @@ + + diff --git a/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoVideoConfig.vue b/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoVideoConfig.vue index d46cf94..f2704ef 100644 --- a/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoVideoConfig.vue +++ b/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoVideoConfig.vue @@ -4,23 +4,37 @@
- - {{ videoMessage.status ?? 'wait' }} + + + {{ props.videoMessage?.msg ?? '' }} + + + {{ props.videoMessage?.status ?? 'wait' }} + + + {{ props.videoMessage?.taskId ?? '' }} +
diff --git a/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoVideoListInfo.vue b/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoVideoListInfo.vue index 83d62d9..6b74733 100644 --- a/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoVideoListInfo.vue +++ b/src/renderer/src/components/ImageTextVideo/components/ImageTextVideoInfo/ImageTextVideoInfoVideoListInfo.vue @@ -4,7 +4,7 @@
- 暂无视频 +
@@ -15,7 +15,7 @@ class="thumbnail-item" @click="handleShowModal" > -