diff --git a/Services/Video/VideoProcess.cs b/Services/Video/VideoProcess.cs index e99dbc4..7549863 100644 --- a/Services/Video/VideoProcess.cs +++ b/Services/Video/VideoProcess.cs @@ -12,132 +12,210 @@ namespace VideoConcat.Services.Video public class VideoProcess { /// - /// 同步删除视频指定帧和对应音频片段 + /// 在前30%、中间30%、后30%三个位置各随机删除一帧,确保通过广告平台相似度检查 /// /// 输入文件路径 /// 输出文件路径 - /// 要删除的帧编号(从1开始) /// 操作是否成功 public static async Task RemoveFrameRandomeAsync(string inputPath, string outputPath) { if (!File.Exists(inputPath)) + { + LogUtils.Error($"输入文件不存在:{inputPath}"); return false; + } + // 创建临时目录 string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); try { + Directory.CreateDirectory(tempDir); + // 1. 获取视频信息 IMediaAnalysis mediaInfo = await FFProbe.AnalyseAsync(inputPath); var videoStream = mediaInfo.PrimaryVideoStream; if (videoStream == null) { - Console.WriteLine("没有找到视频流"); + LogUtils.Error("没有找到视频流"); return false; } + // 视频总时长(秒) + double totalDuration = mediaInfo.Duration.TotalSeconds; + double frameRate = videoStream.FrameRate; + + // 确保视频时长足够(至少20秒) + if (totalDuration < 20) + { + LogUtils.Error($"视频时长太短({totalDuration:F2}秒),无法抽帧(需要至少20秒)"); + return false; + } + + // 计算总帧数 + int totalFrames = (int)(totalDuration * frameRate); + if (totalFrames <= 0) + { + LogUtils.Error($"无法计算视频总帧数,帧率={frameRate}"); + return false; + } + + // 如果是 HEVC 编码,先转换为 H.264(select 过滤器在某些编码格式下可能不稳定) bool isHevc = videoStream.CodecName == "hevc"; - - Directory.CreateDirectory(tempDir); - + string workingInputPath = inputPath; + if (isHevc) { try { - - // 临时文件路径 string videoConvert = Path.Combine(tempDir, $"{Guid.NewGuid()}.mp4"); - + LogUtils.Info($"检测到 HEVC 编码,先转换为 H.264:{inputPath} -> {videoConvert}"); + await FFMpegArguments.FromFileInput(inputPath) - .OutputToFile(videoConvert, true, opt => // 设置输出格式 + .OutputToFile(videoConvert, true, opt => opt.WithVideoCodec("libx264") + .WithAudioCodec("copy") // 复制音频流,不重新编码 ).ProcessAsynchronously(); + + // 重新分析转换后的视频 mediaInfo = await FFProbe.AnalyseAsync(videoConvert); - - inputPath = videoConvert; + videoStream = mediaInfo.PrimaryVideoStream; + if (videoStream == null) + { + LogUtils.Error("转换后没有找到视频流"); + return false; + } + + totalDuration = mediaInfo.Duration.TotalSeconds; + frameRate = videoStream.FrameRate; + totalFrames = (int)(totalDuration * frameRate); + workingInputPath = videoConvert; + + LogUtils.Info($"HEVC 转换完成,新帧率={frameRate}, 总帧数={totalFrames}"); } - catch (Exception) + catch (Exception ex) { - throw new Exception("转换失败!"); + LogUtils.Error("HEVC 转换失败", ex); + return false; } } - // 1. 获取视频信息 - mediaInfo = await FFProbe.AnalyseAsync(inputPath); - videoStream = mediaInfo.PrimaryVideoStream; - if (videoStream == null) - { - Console.WriteLine("没有找到视频流"); - return false; - } - // 视频总时长(秒) - double totalDuration = mediaInfo.Duration.TotalSeconds; - double frameRate = videoStream.FrameRate; - double frameDuration = Math.Round(1.0 / frameRate, 6); // 一帧时长(秒) - int totalFram = (int)(totalDuration * frameRate); - // 确保视频时长足够(至少20秒) - if (totalDuration < 20) - { - LogUtils.Error($"视频时长太短({totalDuration}秒),无法抽帧"); - return false; - } - - var random = new Random(); - // 随机选择要删除的帧时间点(避开开头和结尾) - int maxFrameTime = (int)totalDuration - 5; // 确保有足够的空间 - int minFrameTime = 20; // 从20秒开始 - if (maxFrameTime <= minFrameTime) - { - maxFrameTime = minFrameTime + 1; - } - var randomFrame = random.Next(minFrameTime, maxFrameTime); - - //return RemoveVideoFrame(inputPath, outputPath, randomFrame); - - - string videoPart1 = Path.Combine(tempDir, $"{Guid.NewGuid()}.mp4"); - string videoPart2 = Path.Combine(tempDir, $"{Guid.NewGuid()}.mp4"); + // 2. 计算三个区域的范围(前30%、中间30%、后30%) + // 前30%:0% 到 30% + int front30StartFrame = 0; + int front30EndFrame = (int)(totalFrames * 0.30); - LogUtils.Info($"开始抽帧:输入={inputPath}, 输出={outputPath}, 删除帧时间={randomFrame}秒"); + // 中间30%:35% 到 65%(避开边界) + int middle30StartFrame = (int)(totalFrames * 0.35); + int middle30EndFrame = (int)(totalFrames * 0.65); - bool hasSubVideo1 = SubVideo(inputPath, videoPart1, 0, randomFrame - 0.016); - if (!hasSubVideo1) - { - LogUtils.Error($"裁剪第一部分视频失败:{videoPart1}"); - return false; - } + // 后30%:70% 到 100% + int back30StartFrame = (int)(totalFrames * 0.70); + int back30EndFrame = totalFrames - 1; // 最后一帧 + + // 确保范围有效 + if (front30EndFrame <= front30StartFrame) front30EndFrame = front30StartFrame + 1; + if (middle30EndFrame <= middle30StartFrame) middle30EndFrame = middle30StartFrame + 1; + if (back30EndFrame <= back30StartFrame) back30EndFrame = back30StartFrame + 1; + + // 使用时间戳和文件路径作为随机种子,确保每次处理都不同 + var random = new Random((int)(DateTime.Now.Ticks % int.MaxValue) + inputPath.GetHashCode()); - bool hasSubVideo2 = SubVideo(inputPath, videoPart2, randomFrame, totalDuration); - if (!hasSubVideo2) + // 3. 在每个区域随机选择一帧删除 + int frame1 = random.Next(front30StartFrame, front30EndFrame + 1); // 前30% + int frame2 = random.Next(middle30StartFrame, middle30EndFrame + 1); // 中间30% + int frame3 = random.Next(back30StartFrame, back30EndFrame + 1); // 后30% + + // 确保三帧不重复(如果重复,调整其中一个) + if (frame2 == frame1) frame2 = (frame2 + 1) % totalFrames; + if (frame3 == frame1 || frame3 == frame2) { - LogUtils.Error($"裁剪第二部分视频失败:{videoPart2}"); - return false; + frame3 = (frame3 + 1) % totalFrames; + if (frame3 == frame1 || frame3 == frame2) frame3 = (frame3 + 1) % totalFrames; } - LogUtils.Info($"视频裁剪成功,开始合并:{videoPart1} + {videoPart2} -> {outputPath}"); - bool isJoinSuccess = JoinVideo(outputPath, [videoPart1, videoPart2]); - if (!isJoinSuccess) - { - LogUtils.Error($"合并视频失败:{outputPath}"); - return false; - } + double time1 = frame1 / frameRate; + double time2 = frame2 / frameRate; + double time3 = frame3 / frameRate; + + LogUtils.Info($"开始精确抽帧:输入={inputPath}, 输出={outputPath}, 总帧数={totalFrames}"); + LogUtils.Info($"删除帧1(前30%):帧编号={frame1}, 时间≈{time1:F2}秒"); + LogUtils.Info($"删除帧2(中间30%):帧编号={frame2}, 时间≈{time2:F2}秒"); + LogUtils.Info($"删除帧3(后30%):帧编号={frame3}, 时间≈{time3:F2}秒"); - // 验证输出文件是否存在 - if (File.Exists(outputPath)) + // 4. 只删除视频帧,音频直接复制,使用最快编码设置以达到1秒内完成 + // 注意:使用 select 过滤器删除帧时,视频需要重新编码,但使用最快设置 + LogUtils.Info("使用最快模式删除帧(仅处理视频,音频直接复制)..."); + + var ffmpegArgs = FFMpegArguments + .FromFileInput(workingInputPath) + .OutputToFile(outputPath, true, options => + { + // 使用 select 过滤器删除三帧(只删除视频帧) + options.WithCustomArgument($"-vf select='not(eq(n\\,{frame1})+eq(n\\,{frame2})+eq(n\\,{frame3}))'"); + + // 视频编码:使用最快设置 + options.WithVideoCodec("libx264"); + options.WithCustomArgument("-preset ultrafast"); // 最快编码预设 + options.WithCustomArgument("-tune zerolatency"); // 零延迟调优 + options.WithConstantRateFactor(28); // 质量设置(28比23快) + options.WithCustomArgument("-g 30"); // 减少关键帧间隔 + options.WithCustomArgument("-threads 0"); // 使用所有CPU核心 + options.WithCustomArgument("-vsync cfr"); // 恒定帧率 + + // 音频:直接复制,不重新编码(最快,不处理) + options.WithAudioCodec("copy"); // 直接复制音频流,零处理时间 + }); + + bool success = ffmpegArgs.ProcessSynchronously(); + + // 验证输出文件是否存在且有效 + if (success && File.Exists(outputPath)) { - LogUtils.Info($"抽帧成功:{outputPath}"); - return true; + FileInfo fileInfo = new FileInfo(outputPath); + if (fileInfo.Length > 0) + { + // 验证输出视频的帧数是否正确(应该比原视频少3帧) + try + { + var outputMediaInfo = await FFProbe.AnalyseAsync(outputPath); + var outputVideoStream = outputMediaInfo.PrimaryVideoStream; + if (outputVideoStream != null) + { + int expectedFrames = totalFrames - 3; + double outputDuration = outputMediaInfo.Duration.TotalSeconds; + int actualFrames = (int)(outputDuration * outputVideoStream.FrameRate); + + LogUtils.Info($"抽帧完成:输出文件={outputPath}, 大小={fileInfo.Length / 1024 / 1024:F2}MB, 时长={outputDuration:F2}秒, 帧数≈{actualFrames} (预期少3帧)"); + + // 验证时长是否合理(应该略小于原视频) + if (outputDuration >= totalDuration) + { + LogUtils.Warn($"警告:输出视频时长({outputDuration:F2}秒) >= 原视频时长({totalDuration:F2}秒),可能未成功删除帧"); + } + } + } + catch (Exception ex) + { + LogUtils.Warn($"验证输出视频信息时出错(不影响结果):{ex.Message}"); + } + + return true; + } + else + { + LogUtils.Error($"输出文件存在但大小为0:{outputPath}"); + return false; + } } else { - LogUtils.Error($"抽帧失败:输出文件不存在 - {outputPath}"); + LogUtils.Error($"抽帧失败:success={success}, 文件存在={File.Exists(outputPath)}, 输出路径={outputPath}"); return false; } - } catch (Exception ex) { - LogUtils.Error("抽帧失败", ex); - Console.WriteLine($"操作失败: {ex.Message}"); + LogUtils.Error("抽帧过程发生异常", ex); return false; } finally diff --git a/wails/assets/assets/index-BDrFF8AO.css b/wails/assets/assets/index-BDrFF8AO.css new file mode 100644 index 0000000..4a66165 --- /dev/null +++ b/wails/assets/assets/index-BDrFF8AO.css @@ -0,0 +1 @@ +.video-tab[data-v-54d3cc11]{max-width:900px;margin:0 auto}h2[data-v-54d3cc11]{margin-bottom:20px;color:#333}.form-group[data-v-54d3cc11]{margin-bottom:20px}label[data-v-54d3cc11]{display:block;margin-bottom:8px;font-weight:500;color:#555}.input-group[data-v-54d3cc11]{display:flex;gap:10px}.input-group input[data-v-54d3cc11]{flex:1;padding:8px 12px;border:1px solid #ddd;border-radius:4px}.input-group button[data-v-54d3cc11]{padding:8px 16px;background:#7163ba;color:#fff;border:none;border-radius:4px;cursor:pointer}.input-group button[data-v-54d3cc11]:hover{background:#5a5080}.radio-group[data-v-54d3cc11]{display:flex;flex-direction:column;gap:10px}.radio-group label[data-v-54d3cc11]{display:flex;align-items:center;gap:8px;font-weight:400}input[type=number][data-v-54d3cc11]{width:100px;padding:8px;border:1px solid #ddd;border-radius:4px}.hint[data-v-54d3cc11]{margin-left:10px;color:#888;font-size:14px}.btn-primary[data-v-54d3cc11]{padding:12px 24px;background:#7163ba;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:16px}.btn-primary[data-v-54d3cc11]:hover:not(:disabled){background:#5a5080}.btn-primary[data-v-54d3cc11]:disabled{background:#ccc;cursor:not-allowed}.folder-list[data-v-54d3cc11]{border:1px solid #ddd;border-radius:4px;padding:10px;max-height:200px;overflow-y:auto}.folder-item[data-v-54d3cc11]{display:flex;justify-content:space-between;padding:8px;border-bottom:1px solid #eee}.folder-item[data-v-54d3cc11]:last-child{border-bottom:none}.video-count[data-v-54d3cc11]{color:#888;font-size:14px}.results[data-v-54d3cc11]{margin-top:30px}.results h3[data-v-54d3cc11]{margin-bottom:15px}table[data-v-54d3cc11]{width:100%;border-collapse:collapse;background:#fff;border-radius:4px;overflow:hidden}thead[data-v-54d3cc11]{background:#7163ba;color:#fff}th[data-v-54d3cc11],td[data-v-54d3cc11]{padding:12px;text-align:left;border-bottom:1px solid #eee}.success[data-v-54d3cc11]{color:#28a745}.error[data-v-54d3cc11]{color:#dc3545}.progress-bar[data-v-54d3cc11]{position:relative;width:100px;height:20px;background:#eee;border-radius:10px;overflow:hidden}.progress-fill[data-v-54d3cc11]{position:absolute;top:0;left:0;height:100%;background:#28a745;transition:width .3s}.progress-text[data-v-54d3cc11]{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:12px;z-index:1}.extract-tab[data-v-efad5166]{max-width:900px;margin:0 auto}h2[data-v-efad5166]{margin-bottom:20px;color:#333}.form-group[data-v-efad5166]{margin-bottom:20px}label[data-v-efad5166]{display:block;margin-bottom:8px;font-weight:500;color:#555}.input-group[data-v-efad5166]{display:flex;gap:10px}.input-group input[data-v-efad5166]{flex:1;padding:8px 12px;border:1px solid #ddd;border-radius:4px}.input-group button[data-v-efad5166]{padding:8px 16px;background:#7163ba;color:#fff;border:none;border-radius:4px;cursor:pointer}.input-group button[data-v-efad5166]:hover{background:#5a5080}input[type=number][data-v-efad5166]{width:100px;padding:8px;border:1px solid #ddd;border-radius:4px}.hint[data-v-efad5166]{margin-top:5px;color:#888;font-size:14px}.btn-primary[data-v-efad5166]{padding:12px 24px;background:#7163ba;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:16px}.btn-primary[data-v-efad5166]:hover:not(:disabled){background:#5a5080}.btn-primary[data-v-efad5166]:disabled{background:#ccc;cursor:not-allowed}.btn-secondary[data-v-efad5166]{padding:12px 24px;background:#6c757d;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:16px}.btn-secondary[data-v-efad5166]:hover:not(:disabled){background:#5a6268}.btn-secondary[data-v-efad5166]:disabled{background:#ccc;cursor:not-allowed}.video-list[data-v-efad5166]{border:1px solid #ddd;border-radius:4px;padding:10px;max-height:200px;overflow-y:auto}.video-item[data-v-efad5166]{padding:8px;border-bottom:1px solid #eee}.video-item[data-v-efad5166]:last-child{border-bottom:none}.help-info[data-v-efad5166]{margin-top:20px;padding:15px;background:#f8f9fa;border-radius:4px;white-space:pre-line;line-height:1.6}.results[data-v-efad5166]{margin-top:30px}.results h3[data-v-efad5166]{margin-bottom:15px}.result-summary[data-v-efad5166]{padding:15px;background:#fff;border-radius:4px;border:1px solid #ddd}.result-summary p[data-v-efad5166]{margin:5px 0}.dialog-overlay[data-v-123e9c29]{position:fixed;top:0;left:0;right:0;bottom:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.dialog[data-v-123e9c29]{background:#fff;border-radius:8px;width:400px;max-width:90vw;box-shadow:0 4px 20px #0000004d}.dialog-header[data-v-123e9c29]{display:flex;justify-content:space-between;align-items:center;padding:20px;border-bottom:1px solid #eee}.dialog-header h3[data-v-123e9c29]{margin:0;color:#333}.close-btn[data-v-123e9c29]{background:none;border:none;font-size:24px;cursor:pointer;color:#999;padding:0;width:30px;height:30px;line-height:30px}.close-btn[data-v-123e9c29]:hover{color:#333}.dialog-body[data-v-123e9c29]{padding:20px}.form-group[data-v-123e9c29]{margin-bottom:15px}.form-group label[data-v-123e9c29]{display:block;margin-bottom:5px;color:#555;font-weight:500}.form-group input[data-v-123e9c29]{width:100%;padding:8px 12px;border:1px solid #ddd;border-radius:4px;font-size:14px}.form-group input[data-v-123e9c29]:focus{outline:none;border-color:#7163ba}.error-message[data-v-123e9c29]{color:#dc3545;font-size:14px;margin-top:10px}.dialog-footer[data-v-123e9c29]{display:flex;justify-content:flex-end;gap:10px;padding:20px;border-top:1px solid #eee}.btn-primary[data-v-123e9c29]{padding:8px 16px;background:#7163ba;color:#fff;border:none;border-radius:4px;cursor:pointer}.btn-primary[data-v-123e9c29]:hover:not(:disabled){background:#5a5080}.btn-primary[data-v-123e9c29]:disabled{background:#ccc;cursor:not-allowed}.btn-secondary[data-v-123e9c29]{padding:8px 16px;background:#6c757d;color:#fff;border:none;border-radius:4px;cursor:pointer}.btn-secondary[data-v-123e9c29]:hover{background:#5a6268}.app-container[data-v-75b1df04]{display:flex;width:100vw;height:100vh;background:linear-gradient(to bottom,#fefefe,#ededef)}.sidebar[data-v-75b1df04]{width:70px;background:#7163ba;display:flex;flex-direction:column;justify-content:space-between;padding:10px 0;border-right:2px solid #ebedf3}.menu-items[data-v-75b1df04]{display:flex;flex-direction:column;gap:5px}.menu-item[data-v-75b1df04]{width:50px;height:50px;margin:0 auto;background:transparent;border:2px solid #000;border-radius:5px;color:#fff;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:12px;transition:all .2s}.menu-item[data-v-75b1df04]:hover{background:#5a5080}.menu-item.active[data-v-75b1df04]{background:#8b4513}.menu-item .icon[data-v-75b1df04]{font-size:18px}.separator[data-v-75b1df04]{height:1px;background:#ffffff4d;margin:5px 10px}.menu-bottom[data-v-75b1df04]{display:flex;flex-direction:column}.main-content[data-v-75b1df04]{flex:1;padding:20px;overflow:auto}.login-overlay[data-v-75b1df04]{position:fixed;top:0;left:0;right:0;bottom:0;background:#000000b3;display:flex;align-items:center;justify-content:center;z-index:999}.login-message[data-v-75b1df04]{background:#fff;padding:40px;border-radius:8px;text-align:center;box-shadow:0 4px 20px #0000004d}.login-message h2[data-v-75b1df04]{margin:0 0 10px;color:#333;font-size:24px}.login-message p[data-v-75b1df04]{margin:0;color:#666;font-size:16px}.main-interface[data-v-75b1df04]{display:flex;width:100%;height:100%} diff --git a/wails/assets/assets/index-BaD48VVT.css b/wails/assets/assets/index-BaD48VVT.css deleted file mode 100644 index f6888bc..0000000 --- a/wails/assets/assets/index-BaD48VVT.css +++ /dev/null @@ -1 +0,0 @@ -.video-tab[data-v-976c97bb]{max-width:900px;margin:0 auto}h2[data-v-976c97bb]{margin-bottom:20px;color:#333}.form-group[data-v-976c97bb]{margin-bottom:20px}label[data-v-976c97bb]{display:block;margin-bottom:8px;font-weight:500;color:#555}.input-group[data-v-976c97bb]{display:flex;gap:10px}.input-group input[data-v-976c97bb]{flex:1;padding:8px 12px;border:1px solid #ddd;border-radius:4px}.input-group button[data-v-976c97bb]{padding:8px 16px;background:#7163ba;color:#fff;border:none;border-radius:4px;cursor:pointer}.input-group button[data-v-976c97bb]:hover{background:#5a5080}.radio-group[data-v-976c97bb]{display:flex;flex-direction:column;gap:10px}.radio-group label[data-v-976c97bb]{display:flex;align-items:center;gap:8px;font-weight:400}input[type=number][data-v-976c97bb]{width:100px;padding:8px;border:1px solid #ddd;border-radius:4px}.hint[data-v-976c97bb]{margin-left:10px;color:#888;font-size:14px}.btn-primary[data-v-976c97bb]{padding:12px 24px;background:#7163ba;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:16px}.btn-primary[data-v-976c97bb]:hover:not(:disabled){background:#5a5080}.btn-primary[data-v-976c97bb]:disabled{background:#ccc;cursor:not-allowed}.folder-list[data-v-976c97bb]{border:1px solid #ddd;border-radius:4px;padding:10px;max-height:200px;overflow-y:auto}.folder-item[data-v-976c97bb]{display:flex;justify-content:space-between;padding:8px;border-bottom:1px solid #eee}.folder-item[data-v-976c97bb]:last-child{border-bottom:none}.video-count[data-v-976c97bb]{color:#888;font-size:14px}.results[data-v-976c97bb]{margin-top:30px}.results h3[data-v-976c97bb]{margin-bottom:15px}table[data-v-976c97bb]{width:100%;border-collapse:collapse;background:#fff;border-radius:4px;overflow:hidden}thead[data-v-976c97bb]{background:#7163ba;color:#fff}th[data-v-976c97bb],td[data-v-976c97bb]{padding:12px;text-align:left;border-bottom:1px solid #eee}.success[data-v-976c97bb]{color:#28a745}.error[data-v-976c97bb]{color:#dc3545}.progress-bar[data-v-976c97bb]{position:relative;width:100px;height:20px;background:#eee;border-radius:10px;overflow:hidden}.progress-fill[data-v-976c97bb]{position:absolute;top:0;left:0;height:100%;background:#28a745;transition:width .3s}.progress-text[data-v-976c97bb]{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:12px;z-index:1}.extract-tab[data-v-32ae0b7c]{max-width:900px;margin:0 auto}h2[data-v-32ae0b7c]{margin-bottom:20px;color:#333}.form-group[data-v-32ae0b7c]{margin-bottom:20px}label[data-v-32ae0b7c]{display:block;margin-bottom:8px;font-weight:500;color:#555}.input-group[data-v-32ae0b7c]{display:flex;gap:10px}.input-group input[data-v-32ae0b7c]{flex:1;padding:8px 12px;border:1px solid #ddd;border-radius:4px}.input-group button[data-v-32ae0b7c]{padding:8px 16px;background:#7163ba;color:#fff;border:none;border-radius:4px;cursor:pointer}.input-group button[data-v-32ae0b7c]:hover{background:#5a5080}input[type=number][data-v-32ae0b7c]{width:100px;padding:8px;border:1px solid #ddd;border-radius:4px}.hint[data-v-32ae0b7c]{margin-top:5px;color:#888;font-size:14px}.btn-primary[data-v-32ae0b7c]{padding:12px 24px;background:#7163ba;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:16px}.btn-primary[data-v-32ae0b7c]:hover:not(:disabled){background:#5a5080}.btn-primary[data-v-32ae0b7c]:disabled{background:#ccc;cursor:not-allowed}.btn-secondary[data-v-32ae0b7c]{padding:12px 24px;background:#6c757d;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:16px}.btn-secondary[data-v-32ae0b7c]:hover:not(:disabled){background:#5a6268}.btn-secondary[data-v-32ae0b7c]:disabled{background:#ccc;cursor:not-allowed}.video-list[data-v-32ae0b7c]{border:1px solid #ddd;border-radius:4px;padding:10px;max-height:200px;overflow-y:auto}.video-item[data-v-32ae0b7c]{padding:8px;border-bottom:1px solid #eee}.video-item[data-v-32ae0b7c]:last-child{border-bottom:none}.help-info[data-v-32ae0b7c]{margin-top:20px;padding:15px;background:#f8f9fa;border-radius:4px;white-space:pre-line;line-height:1.6}.results[data-v-32ae0b7c]{margin-top:30px}.results h3[data-v-32ae0b7c]{margin-bottom:15px}.result-summary[data-v-32ae0b7c]{padding:15px;background:#fff;border-radius:4px;border:1px solid #ddd}.result-summary p[data-v-32ae0b7c]{margin:5px 0}.dialog-overlay[data-v-f054741d]{position:fixed;top:0;left:0;right:0;bottom:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.dialog[data-v-f054741d]{background:#fff;border-radius:8px;width:400px;max-width:90vw;box-shadow:0 4px 20px #0000004d}.dialog-header[data-v-f054741d]{display:flex;justify-content:space-between;align-items:center;padding:20px;border-bottom:1px solid #eee}.dialog-header h3[data-v-f054741d]{margin:0;color:#333}.close-btn[data-v-f054741d]{background:none;border:none;font-size:24px;cursor:pointer;color:#999;padding:0;width:30px;height:30px;line-height:30px}.close-btn[data-v-f054741d]:hover{color:#333}.dialog-body[data-v-f054741d]{padding:20px}.form-group[data-v-f054741d]{margin-bottom:15px}.form-group label[data-v-f054741d]{display:block;margin-bottom:5px;color:#555;font-weight:500}.form-group input[data-v-f054741d]{width:100%;padding:8px 12px;border:1px solid #ddd;border-radius:4px;font-size:14px}.form-group input[data-v-f054741d]:focus{outline:none;border-color:#7163ba}.error-message[data-v-f054741d]{color:#dc3545;font-size:14px;margin-top:10px}.dialog-footer[data-v-f054741d]{display:flex;justify-content:flex-end;gap:10px;padding:20px;border-top:1px solid #eee}.btn-primary[data-v-f054741d]{padding:8px 16px;background:#7163ba;color:#fff;border:none;border-radius:4px;cursor:pointer}.btn-primary[data-v-f054741d]:hover:not(:disabled){background:#5a5080}.btn-primary[data-v-f054741d]:disabled{background:#ccc;cursor:not-allowed}.btn-secondary[data-v-f054741d]{padding:8px 16px;background:#6c757d;color:#fff;border:none;border-radius:4px;cursor:pointer}.btn-secondary[data-v-f054741d]:hover{background:#5a6268}.app-container[data-v-8a8ce913]{display:flex;width:100vw;height:100vh;background:linear-gradient(to bottom,#fefefe,#ededef)}.sidebar[data-v-8a8ce913]{width:70px;background:#7163ba;display:flex;flex-direction:column;justify-content:space-between;padding:10px 0;border-right:2px solid #ebedf3}.menu-items[data-v-8a8ce913]{display:flex;flex-direction:column;gap:5px}.menu-item[data-v-8a8ce913]{width:50px;height:50px;margin:0 auto;background:transparent;border:2px solid #000;border-radius:5px;color:#fff;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:12px;transition:all .2s}.menu-item[data-v-8a8ce913]:hover{background:#5a5080}.menu-item.active[data-v-8a8ce913]{background:#8b4513}.menu-item .icon[data-v-8a8ce913]{font-size:18px}.separator[data-v-8a8ce913]{height:1px;background:#ffffff4d;margin:5px 10px}.menu-bottom[data-v-8a8ce913]{display:flex;flex-direction:column}.main-content[data-v-8a8ce913]{flex:1;padding:20px;overflow:auto}.login-overlay[data-v-8a8ce913]{position:fixed;top:0;left:0;right:0;bottom:0;background:#000000b3;display:flex;align-items:center;justify-content:center;z-index:999}.login-message[data-v-8a8ce913]{background:#fff;padding:40px;border-radius:8px;text-align:center;box-shadow:0 4px 20px #0000004d}.login-message h2[data-v-8a8ce913]{margin:0 0 10px;color:#333;font-size:24px}.login-message p[data-v-8a8ce913]{margin:0;color:#666;font-size:16px}.main-interface[data-v-8a8ce913]{display:flex;width:100%;height:100%} diff --git a/wails/assets/assets/index-IwiMqFON.js b/wails/assets/assets/index-DSuQhuGl.js similarity index 99% rename from wails/assets/assets/index-IwiMqFON.js rename to wails/assets/assets/index-DSuQhuGl.js index e1ced2f..04a86c0 100644 --- a/wails/assets/assets/index-IwiMqFON.js +++ b/wails/assets/assets/index-DSuQhuGl.js @@ -14,4 +14,4 @@ import{Create as _e,Call as vt}from"@wailsio/runtime";(function(){const t=docume * @vue/runtime-dom v3.5.26 * (c) 2018-present Yuxi (Evan) You and Vue contributors * @license MIT -**/let qs;const jn=typeof window<"u"&&window.trustedTypes;if(jn)try{qs=jn.createPolicy("vue",{createHTML:e=>e})}catch{}const si=qs?e=>qs.createHTML(e):e=>e,il="http://www.w3.org/2000/svg",ol="http://www.w3.org/1998/Math/MathML",Re=typeof document<"u"?document:null,Hn=Re&&Re.createElement("template"),ll={insert:(e,t,s)=>{t.insertBefore(e,s||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,s,n)=>{const r=t==="svg"?Re.createElementNS(il,e):t==="mathml"?Re.createElementNS(ol,e):s?Re.createElement(e,{is:s}):Re.createElement(e);return e==="select"&&n&&n.multiple!=null&&r.setAttribute("multiple",n.multiple),r},createText:e=>Re.createTextNode(e),createComment:e=>Re.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Re.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,s,n,r,i){const o=s?s.previousSibling:t.lastChild;if(r&&(r===i||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),s),!(r===i||!(r=r.nextSibling)););else{Hn.innerHTML=si(n==="svg"?`${e}`:n==="mathml"?`${e}`:e);const l=Hn.content;if(n==="svg"||n==="mathml"){const u=l.firstChild;for(;u.firstChild;)l.appendChild(u.firstChild);l.removeChild(u)}t.insertBefore(l,s)}return[o?o.nextSibling:t.firstChild,s?s.previousSibling:t.lastChild]}},cl=Symbol("_vtc");function ul(e,t,s){const n=e[cl];n&&(t=(t?[t,...n]:[...n]).join(" ")),t==null?e.removeAttribute("class"):s?e.setAttribute("class",t):e.className=t}const ls=Symbol("_vod"),ni=Symbol("_vsh"),fl={name:"show",beforeMount(e,{value:t},{transition:s}){e[ls]=e.style.display==="none"?"":e.style.display,s&&t?s.beforeEnter(e):Ct(e,t)},mounted(e,{value:t},{transition:s}){s&&t&&s.enter(e)},updated(e,{value:t,oldValue:s},{transition:n}){!t!=!s&&(n?t?(n.beforeEnter(e),Ct(e,!0),n.enter(e)):n.leave(e,()=>{Ct(e,!1)}):Ct(e,t))},beforeUnmount(e,{value:t}){Ct(e,t)}};function Ct(e,t){e.style.display=t?e[ls]:"none",e[ni]=!t}const al=Symbol(""),dl=/(?:^|;)\s*display\s*:/;function hl(e,t,s){const n=e.style,r=X(s);let i=!1;if(s&&!r){if(t)if(X(t))for(const o of t.split(";")){const l=o.slice(0,o.indexOf(":")).trim();s[l]==null&&Qt(n,l,"")}else for(const o in t)s[o]==null&&Qt(n,o,"");for(const o in s)o==="display"&&(i=!0),Qt(n,o,s[o])}else if(r){if(t!==s){const o=n[al];o&&(s+=";"+o),n.cssText=s,i=dl.test(s)}}else t&&e.removeAttribute("style");ls in e&&(e[ls]=i?n.display:"",e[ni]&&(n.display="none"))}const Vn=/\s*!important$/;function Qt(e,t,s){if(R(s))s.forEach(n=>Qt(e,t,n));else if(s==null&&(s=""),t.startsWith("--"))e.setProperty(t,s);else{const n=pl(e,t);Vn.test(s)?e.setProperty(it(n),s.replace(Vn,""),"important"):e[n]=s}}const Un=["Webkit","Moz","ms"],Fs={};function pl(e,t){const s=Fs[t];if(s)return s;let n=Je(t);if(n!=="filter"&&n in e)return Fs[t]=n;n=nr(n);for(let r=0;rRs||(_l.then(()=>Rs=0),Rs=Date.now());function yl(e,t){const s=n=>{if(!n._vts)n._vts=Date.now();else if(n._vts<=s.attached)return;Me(xl(n,s.value),t,5,[n])};return s.value=e,s.attached=bl(),s}function xl(e,t){if(R(t)){const s=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{s.call(e),e._stopped=!0},t.map(n=>r=>!r._stopped&&n&&n(r))}else return t}const Jn=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,Sl=(e,t,s,n,r,i)=>{const o=r==="svg";t==="class"?ul(e,n,o):t==="style"?hl(e,s,n):us(t)?Gs(t)||ml(e,t,s,n,i):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):wl(e,t,n,o))?(Wn(e,t,n),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&Bn(e,t,n,o,i,t!=="value")):e._isVueCE&&(/[A-Z]/.test(t)||!X(n))?Wn(e,Je(t),n,i,t):(t==="true-value"?e._trueValue=n:t==="false-value"&&(e._falseValue=n),Bn(e,t,n,o))};function wl(e,t,s,n){if(n)return!!(t==="innerHTML"||t==="textContent"||t in e&&Jn(t)&&D(s));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="autocorrect"||t==="sandbox"&&e.tagName==="IFRAME"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const r=e.tagName;if(r==="IMG"||r==="VIDEO"||r==="CANVAS"||r==="SOURCE")return!1}return Jn(t)&&X(s)?!1:t in e}const cs=e=>{const t=e.props["onUpdate:modelValue"]||!1;return R(t)?s=>Yt(t,s):t};function Cl(e){e.target.composing=!0}function Gn(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}const ht=Symbol("_assign");function Yn(e,t,s){return t&&(e=e.trim()),s&&(e=Xs(e)),e}const rt={created(e,{modifiers:{lazy:t,trim:s,number:n}},r){e[ht]=cs(r);const i=n||r.props&&r.props.type==="number";tt(e,t?"change":"input",o=>{o.target.composing||e[ht](Yn(e.value,s,i))}),(s||i)&&tt(e,"change",()=>{e.value=Yn(e.value,s,i)}),t||(tt(e,"compositionstart",Cl),tt(e,"compositionend",Gn),tt(e,"change",Gn))},mounted(e,{value:t}){e.value=t??""},beforeUpdate(e,{value:t,oldValue:s,modifiers:{lazy:n,trim:r,number:i}},o){if(e[ht]=cs(o),e.composing)return;const l=(i||e.type==="number")&&!/^0\d/.test(e.value)?Xs(e.value):e.value,u=t??"";l!==u&&(document.activeElement===e&&e.type!=="range"&&(n&&t===s||r&&e.value.trim()===u)||(e.value=u))}},zn={created(e,{value:t},s){e.checked=es(t,s.props.value),e[ht]=cs(s),tt(e,"change",()=>{e[ht](Tl(e))})},beforeUpdate(e,{value:t,oldValue:s},n){e[ht]=cs(n),t!==s&&(e.checked=es(t,n.props.value))}};function Tl(e){return"_value"in e?e._value:e.value}const El=["ctrl","shift","alt","meta"],Ol={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>El.some(s=>e[`${s}Key`]&&!t.includes(s))},Pl=(e,t)=>{const s=e._withMods||(e._withMods={}),n=t.join(".");return s[n]||(s[n]=(r,...i)=>{for(let o=0;o{const t=Il().createApp(...e),{mount:s}=t;return t.mount=n=>{const r=Rl(n);if(!r)return;const i=t._component;!D(i)&&!i.render&&!i.template&&(i.template=r.innerHTML),r.nodeType===1&&(r.textContent="");const o=s(r,!1,Fl(r));return r instanceof Element&&(r.removeAttribute("v-cloak"),r.setAttribute("data-v-app","")),o},t};function Fl(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function Rl(e){return X(e)?document.querySelector(e):e}class dn{constructor(t={}){"videoPath"in t||(this.videoPath=""),"outputPath"in t||(this.outputPath=""),"success"in t||(this.success=!1),Object.assign(this,t)}static createFrom(t={}){let s=typeof t=="string"?JSON.parse(t):t;return new dn(s)}}class bs{constructor(t={}){"path"in t||(this.path=""),"name"in t||(this.name=""),"videoCount"in t||(this.videoCount=0),"videoPaths"in t||(this.videoPaths=[]),Object.assign(this,t)}static createFrom(t={}){const s=$l;let n=typeof t=="string"?JSON.parse(t):t;return"videoPaths"in n&&(n.videoPaths=s(n.videoPaths)),new bs(n)}}class hn{constructor(t={}){"Code"in t||(this.Code=0),"Msg"in t||(this.Msg=""),"Data"in t||(this.Data=null),Object.assign(this,t)}static createFrom(t={}){let s=typeof t=="string"?JSON.parse(t):t;return new hn(s)}}class pn{constructor(t={}){"index"in t||(this.index=0),"fileName"in t||(this.fileName=""),"filePath"in t||(this.filePath=""),"size"in t||(this.size=""),"seconds"in t||(this.seconds=0),"status"in t||(this.status=""),"progress"in t||(this.progress=""),Object.assign(this,t)}static createFrom(t={}){let s=typeof t=="string"?JSON.parse(t):t;return new pn(s)}}const $l=_e.Array(_e.Any),Dl=bs.createFrom;_e.Array(Dl);function Ll(e,t){return vt.ByID(2350837569,e,t).then(s=>jl(s))}const Nl=hn.createFrom,jl=_e.Nullable(Nl);function Hl(e){return vt.ByID(1728131056,e).then(t=>Kl(t))}function Vl(e){return vt.ByID(2398906893,e).then(t=>Bl(t))}const Ul=dn.createFrom,Kl=_e.Array(Ul),Bl=_e.Array(_e.Any);function ri(){return vt.ByID(23551676)}_e.Array(_e.Any);function Wl(e){return vt.ByID(2660164351,e).then(t=>Jl(t))}function kl(e){return vt.ByID(2209109868,e).then(t=>Yl(t))}const ql=pn.createFrom,Jl=_e.Array(ql),Gl=bs.createFrom,Yl=_e.Array(Gl),ys=(e,t)=>{const s=e.__vccOpts||e;for(const[n,r]of t)s[n]=r;return s},zl={class:"video-tab"},Xl={class:"form-group"},Zl={class:"input-group"},Ql={key:0,class:"form-group"},ec={class:"folder-list"},tc={class:"video-count"},sc={class:"form-group"},nc={class:"radio-group"},rc={class:"form-group"},ic=["max","disabled"],oc={class:"hint"},lc={class:"form-group"},cc={class:"input-group"},uc={class:"form-group"},fc=["disabled"],ac={key:1,class:"results"},dc={class:"progress-bar"},hc={class:"progress-text"},pc={__name:"VideoTab",setup(e){const t=Y(""),s=Y([]),n=Y(1),r=Y(1),i=Y(0),o=Y(""),l=Y(!1),u=Y([]),d=ct(()=>t.value&&r.value>0&&r.value<=i.value&&!l.value),a=async()=>{try{const P=await ri();if(P&&P.trim()){t.value=P.trim();try{s.value=await kl(t.value),w()}catch(g){alert("列出文件夹失败: "+g.message)}}}catch(P){console.error("选择文件夹失败:",P),alert("选择文件夹失败: "+(P.message||"未知错误"))}},p=()=>{const P=document.createElement("input");P.type="file",P.accept="image/*",P.onchange=g=>{const E=g.target.files[0];E&&(o.value=E.name)},P.click()},w=()=>{n.value===1?i.value=s.value.reduce((P,g)=>P*g.videoCount,1):i.value=s.value.length>0?s.value[0].videoCount:0},O=async()=>{if(d.value){l.value=!0,u.value=[];try{const P={folderPath:t.value,num:r.value,joinType:n.value,auditImagePath:o.value,folderInfos:s.value};try{const g=await Wl(P);g&&(u.value=g)}catch(g){alert("拼接视频失败: "+g.message)}}catch(P){alert("拼接失败: "+P.message)}finally{l.value=!1}}};return(P,g)=>(J(),Q("div",zl,[g[14]||(g[14]=_("h2",null,"视频拼接",-1)),_("div",Xl,[g[5]||(g[5]=_("label",null,"选择文件夹:",-1)),_("div",Zl,[Pe(_("input",{type:"text","onUpdate:modelValue":g[0]||(g[0]=E=>t.value=E),placeholder:"请选择包含视频文件夹的目录",readonly:""},null,512),[[rt,t.value]]),_("button",{onClick:a},"选择文件夹")])]),s.value.length>0?(J(),Q("div",Ql,[g[6]||(g[6]=_("label",null,"文件夹列表:",-1)),_("div",ec,[(J(!0),Q(de,null,Hs(s.value,(E,$)=>(J(),Q("div",{key:$,class:"folder-item"},[_("span",null,z(E.name),1),_("span",tc,z(E.videoCount)+" 个视频",1)]))),128))])])):ge("",!0),_("div",sc,[g[9]||(g[9]=_("label",null,"拼接模式:",-1)),_("div",nc,[_("label",null,[Pe(_("input",{type:"radio","onUpdate:modelValue":g[1]||(g[1]=E=>n.value=E),value:1},null,512),[[zn,n.value]]),g[7]||(g[7]=Ws(" 组合拼接(从每个文件夹随机选择) ",-1))]),_("label",null,[Pe(_("input",{type:"radio","onUpdate:modelValue":g[2]||(g[2]=E=>n.value=E),value:2},null,512),[[zn,n.value]]),g[8]||(g[8]=Ws(" 顺序拼接(按索引顺序) ",-1))])])]),_("div",rc,[g[10]||(g[10]=_("label",null,"生成数量:",-1)),Pe(_("input",{type:"number","onUpdate:modelValue":g[3]||(g[3]=E=>r.value=E),min:1,max:i.value,disabled:l.value},null,8,ic),[[rt,r.value,void 0,{number:!0}]]),_("span",oc,"最大可生成:"+z(i.value)+" 个",1)]),_("div",lc,[g[11]||(g[11]=_("label",null,"审核图片(可选):",-1)),_("div",cc,[Pe(_("input",{type:"text","onUpdate:modelValue":g[4]||(g[4]=E=>o.value=E),placeholder:"选择审核图片",readonly:""},null,512),[[rt,o.value]]),_("button",{onClick:p},"选择图片")])]),_("div",uc,[_("button",{class:"btn-primary",onClick:O,disabled:!d.value||l.value},z(l.value?"处理中...":"开始拼接"),9,fc)]),u.value.length>0?(J(),Q("div",ac,[g[13]||(g[13]=_("h3",null,"拼接结果",-1)),_("table",null,[g[12]||(g[12]=_("thead",null,[_("tr",null,[_("th",null,"序号"),_("th",null,"文件名"),_("th",null,"大小"),_("th",null,"时长"),_("th",null,"状态"),_("th",null,"进度")])],-1)),_("tbody",null,[(J(!0),Q(de,null,Hs(u.value,E=>(J(),Q("tr",{key:E.index},[_("td",null,z(E.index),1),_("td",null,z(E.fileName),1),_("td",null,z(E.size),1),_("td",null,z(E.seconds)+"秒",1),_("td",{class:pt(E.status==="拼接成功"?"success":"error")},z(E.status),3),_("td",null,[_("div",dc,[_("div",{class:"progress-fill",style:ds({width:E.progress})},null,4),_("span",hc,z(E.progress),1)])])]))),128))])])])):ge("",!0)]))}},gc=ys(pc,[["__scopeId","data-v-976c97bb"]]),mc={class:"extract-tab"},vc={class:"form-group"},_c={class:"input-group"},bc={key:0,class:"form-group"},yc={class:"video-list"},xc={class:"hint"},Sc={class:"form-group"},wc=["disabled"],Cc={class:"form-group"},Tc=["disabled"],Ec=["disabled"],Oc={key:1,class:"help-info"},Pc={key:2,class:"results"},Ac={class:"result-summary"},Ic={__name:"ExtractTab",setup(e){const t=Y(""),s=Y([]),n=Y(1),r=Y(!1),i=Y(""),o=Y([]),l=ct(()=>t.value&&s.value.length>0&&n.value>0&&!r.value),u=ct(()=>t.value&&s.value.length>0&&!r.value),d=ct(()=>o.value.filter(g=>g.success).length),a=ct(()=>o.value.filter(g=>!g.success).length),p=async()=>{try{const g=await ri();if(g&&g.trim()){t.value=g.trim();try{s.value=await Vl(t.value)}catch(E){alert("列出视频失败: "+E.message)}}}catch(g){console.error("选择文件夹失败:",g),alert("选择文件夹失败: "+(g.message||"未知错误"))}},w=g=>g.split("/").pop()||g.split("\\").pop()||g,O=async()=>{if(l.value){r.value=!0,o.value=[],i.value=`开始处理,每个视频将生成 ${n.value} 个抽帧视频...`;try{const g={folderPath:t.value,extractCount:n.value},E=s.value.length*n.value;i.value=`处理中... (0/${E})`;try{const $=await Hl(g);$&&(o.value=$,i.value=`全部完成! 共处理 ${E} 个任务,成功 ${d.value} 个,失败 ${a.value} 个`)}catch($){alert("抽帧失败: "+$.message),i.value="处理失败"}}catch(g){alert("抽帧失败: "+g.message),i.value="处理失败: "+g.message}finally{r.value=!1}}},P=async()=>{if(u.value){r.value=!0,o.value=[],i.value="开始修改元数据...";try{if(extractService&&extractService.ModifyVideosMetadata){const g=s.value.length;i.value=`处理中... (0/${g})`;const E=await extractService.ModifyVideosMetadata(t.value);E&&(o.value=E,i.value=`全部完成! 共处理 ${g} 个任务,成功 ${d.value} 个,失败 ${a.value} 个`)}}catch(g){alert("修改失败: "+g.message),i.value="处理失败: "+g.message}finally{r.value=!1}}};return(g,E)=>(J(),Q("div",mc,[E[6]||(E[6]=_("h2",null,"视频抽帧",-1)),_("div",vc,[E[2]||(E[2]=_("label",null,"选择文件夹:",-1)),_("div",_c,[Pe(_("input",{type:"text","onUpdate:modelValue":E[0]||(E[0]=$=>t.value=$),placeholder:"请选择包含视频文件的目录",readonly:""},null,512),[[rt,t.value]]),_("button",{onClick:p},"选择文件夹")])]),s.value.length>0?(J(),Q("div",bc,[E[3]||(E[3]=_("label",null,"视频文件:",-1)),_("div",yc,[(J(!0),Q(de,null,Hs(s.value,($,V)=>(J(),Q("div",{key:V,class:"video-item"},z(w($)),1))),128))]),_("p",xc,"共 "+z(s.value.length)+" 个视频文件",1)])):ge("",!0),_("div",Sc,[E[4]||(E[4]=_("label",null,"每个视频生成数量:",-1)),Pe(_("input",{type:"number","onUpdate:modelValue":E[1]||(E[1]=$=>n.value=$),min:1,disabled:r.value},null,8,wc),[[rt,n.value,void 0,{number:!0}]])]),_("div",Cc,[_("button",{class:"btn-primary",onClick:O,disabled:!l.value||r.value},z(r.value?"处理中...":"开始抽帧"),9,Tc),_("button",{class:"btn-secondary",onClick:P,disabled:!u.value||r.value,style:{"margin-left":"10px"}},z(r.value?"处理中...":"开始修改元数据"),9,Ec)]),i.value?(J(),Q("div",Oc,z(i.value),1)):ge("",!0),o.value.length>0?(J(),Q("div",Pc,[E[5]||(E[5]=_("h3",null,"处理结果",-1)),_("div",Ac,[_("p",null,"成功: "+z(d.value)+" 个",1),_("p",null,"失败: "+z(a.value)+" 个",1)])])):ge("",!0)]))}},Mc=ys(Ic,[["__scopeId","data-v-32ae0b7c"]]),Fc={class:"dialog-header"},Rc={class:"dialog-body"},$c={class:"form-group"},Dc={class:"form-group"},Lc={key:0,class:"error-message"},Nc={class:"dialog-footer"},jc=["disabled"],Hc={__name:"LoginDialog",props:{allowClose:{type:Boolean,default:!1}},emits:["close","login-success"],setup(e,{emit:t}){const s=e,n=t,r=Y(""),i=Y(""),o=Y(!1),l=Y(""),u=()=>{s.allowClose&&n("close")},d=async()=>{if(!r.value||!i.value){l.value="请输入用户名和密码";return}o.value=!0,l.value="";try{const a=await Ll(r.value,i.value);a&&a.Code===200?(n("login-success"),n("close")):l.value=a&&a.Msg||"登录失败"}catch(a){l.value="登录失败: "+(a.message||a)}finally{o.value=!1}};return(a,p)=>(J(),Q("div",{class:"dialog-overlay",onClick:u},[_("div",{class:"dialog",onClick:p[4]||(p[4]=Pl(()=>{},["stop"]))},[_("div",Fc,[p[5]||(p[5]=_("h3",null,"用户登录",-1)),e.allowClose?(J(),Q("button",{key:0,class:"close-btn",onClick:p[0]||(p[0]=w=>a.$emit("close"))},"×")):ge("",!0)]),_("div",Rc,[_("div",$c,[p[6]||(p[6]=_("label",null,"用户名:",-1)),Pe(_("input",{type:"text","onUpdate:modelValue":p[1]||(p[1]=w=>r.value=w),placeholder:"请输入用户名"},null,512),[[rt,r.value]])]),_("div",Dc,[p[7]||(p[7]=_("label",null,"密码:",-1)),Pe(_("input",{type:"password","onUpdate:modelValue":p[2]||(p[2]=w=>i.value=w),placeholder:"请输入密码"},null,512),[[rt,i.value]])]),l.value?(J(),Q("div",Lc,z(l.value),1)):ge("",!0)]),_("div",Nc,[e.allowClose?(J(),Q("button",{key:0,class:"btn-secondary",onClick:p[3]||(p[3]=w=>a.$emit("close"))},"取消")):ge("",!0),_("button",{class:"btn-primary",onClick:d,disabled:o.value},z(o.value?"登录中...":"登录"),9,jc)])])]))}},Vc=ys(Hc,[["__scopeId","data-v-f054741d"]]),Uc={class:"app-container"},Kc={key:0,class:"login-overlay"},Bc={class:"main-interface"},Wc={class:"sidebar"},kc={class:"menu-items"},qc={class:"main-content"},Jc={__name:"App",setup(e){const t=Y("video"),s=Y(!1),n=Y(!1),r=()=>{const u=localStorage.getItem("isLoggedIn"),d=localStorage.getItem("loginTime");if(u==="true"&&d){const a=Date.now(),p=parseInt(d);(a-p)/(1e3*60*60)<24?n.value=!0:(localStorage.removeItem("isLoggedIn"),localStorage.removeItem("loginTime"),n.value=!1,s.value=!0)}else n.value=!1,s.value=!0},i=()=>{n.value=!0,s.value=!1,localStorage.setItem("isLoggedIn","true"),localStorage.setItem("loginTime",Date.now().toString())},o=()=>{n.value&&(s.value=!1)},l=()=>{confirm("确定要退出登录吗?")&&(n.value=!1,s.value=!0,localStorage.removeItem("isLoggedIn"),localStorage.removeItem("loginTime"))};return Rr(()=>{r()}),(u,d)=>(J(),Q("div",Uc,[n.value?ge("",!0):(J(),Q("div",Kc,[...d[2]||(d[2]=[_("div",{class:"login-message"},[_("h2",null,"请先登录"),_("p",null,"您需要登录后才能使用此应用")],-1)])])),Pe(_("div",Bc,[_("div",Wc,[_("div",kc,[_("button",{class:pt(["menu-item",{active:t.value==="video"}]),onClick:d[0]||(d[0]=a=>t.value="video")},[...d[3]||(d[3]=[_("span",{class:"icon"},"🎬",-1),_("span",null,"视频",-1)])],2),d[5]||(d[5]=_("div",{class:"separator"},null,-1)),_("button",{class:pt(["menu-item",{active:t.value==="extract"}]),onClick:d[1]||(d[1]=a=>t.value="extract")},[...d[4]||(d[4]=[_("span",{class:"icon"},"✂️",-1),_("span",null,"抽帧",-1)])],2)]),_("div",{class:"menu-bottom"},[_("button",{class:"menu-item",onClick:l},[...d[6]||(d[6]=[_("span",{class:"icon"},"👤",-1)])])])]),_("div",qc,[t.value==="video"?(J(),Xt(gc,{key:0})):ge("",!0),t.value==="extract"?(J(),Xt(Mc,{key:1})):ge("",!0)])],512),[[fl,n.value]]),s.value?(J(),Xt(Vc,{key:1,"allow-close":n.value,onClose:o,onLoginSuccess:i},null,8,["allow-close"])):ge("",!0)]))}},Gc=ys(Jc,[["__scopeId","data-v-8a8ce913"]]);Ml(Gc).mount("#app"); +**/let qs;const jn=typeof window<"u"&&window.trustedTypes;if(jn)try{qs=jn.createPolicy("vue",{createHTML:e=>e})}catch{}const si=qs?e=>qs.createHTML(e):e=>e,il="http://www.w3.org/2000/svg",ol="http://www.w3.org/1998/Math/MathML",Re=typeof document<"u"?document:null,Hn=Re&&Re.createElement("template"),ll={insert:(e,t,s)=>{t.insertBefore(e,s||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,s,n)=>{const r=t==="svg"?Re.createElementNS(il,e):t==="mathml"?Re.createElementNS(ol,e):s?Re.createElement(e,{is:s}):Re.createElement(e);return e==="select"&&n&&n.multiple!=null&&r.setAttribute("multiple",n.multiple),r},createText:e=>Re.createTextNode(e),createComment:e=>Re.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Re.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,s,n,r,i){const o=s?s.previousSibling:t.lastChild;if(r&&(r===i||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),s),!(r===i||!(r=r.nextSibling)););else{Hn.innerHTML=si(n==="svg"?`${e}`:n==="mathml"?`${e}`:e);const l=Hn.content;if(n==="svg"||n==="mathml"){const u=l.firstChild;for(;u.firstChild;)l.appendChild(u.firstChild);l.removeChild(u)}t.insertBefore(l,s)}return[o?o.nextSibling:t.firstChild,s?s.previousSibling:t.lastChild]}},cl=Symbol("_vtc");function ul(e,t,s){const n=e[cl];n&&(t=(t?[t,...n]:[...n]).join(" ")),t==null?e.removeAttribute("class"):s?e.setAttribute("class",t):e.className=t}const ls=Symbol("_vod"),ni=Symbol("_vsh"),fl={name:"show",beforeMount(e,{value:t},{transition:s}){e[ls]=e.style.display==="none"?"":e.style.display,s&&t?s.beforeEnter(e):Ct(e,t)},mounted(e,{value:t},{transition:s}){s&&t&&s.enter(e)},updated(e,{value:t,oldValue:s},{transition:n}){!t!=!s&&(n?t?(n.beforeEnter(e),Ct(e,!0),n.enter(e)):n.leave(e,()=>{Ct(e,!1)}):Ct(e,t))},beforeUnmount(e,{value:t}){Ct(e,t)}};function Ct(e,t){e.style.display=t?e[ls]:"none",e[ni]=!t}const al=Symbol(""),dl=/(?:^|;)\s*display\s*:/;function hl(e,t,s){const n=e.style,r=X(s);let i=!1;if(s&&!r){if(t)if(X(t))for(const o of t.split(";")){const l=o.slice(0,o.indexOf(":")).trim();s[l]==null&&Qt(n,l,"")}else for(const o in t)s[o]==null&&Qt(n,o,"");for(const o in s)o==="display"&&(i=!0),Qt(n,o,s[o])}else if(r){if(t!==s){const o=n[al];o&&(s+=";"+o),n.cssText=s,i=dl.test(s)}}else t&&e.removeAttribute("style");ls in e&&(e[ls]=i?n.display:"",e[ni]&&(n.display="none"))}const Vn=/\s*!important$/;function Qt(e,t,s){if(R(s))s.forEach(n=>Qt(e,t,n));else if(s==null&&(s=""),t.startsWith("--"))e.setProperty(t,s);else{const n=pl(e,t);Vn.test(s)?e.setProperty(it(n),s.replace(Vn,""),"important"):e[n]=s}}const Un=["Webkit","Moz","ms"],Fs={};function pl(e,t){const s=Fs[t];if(s)return s;let n=Je(t);if(n!=="filter"&&n in e)return Fs[t]=n;n=nr(n);for(let r=0;rRs||(_l.then(()=>Rs=0),Rs=Date.now());function yl(e,t){const s=n=>{if(!n._vts)n._vts=Date.now();else if(n._vts<=s.attached)return;Me(xl(n,s.value),t,5,[n])};return s.value=e,s.attached=bl(),s}function xl(e,t){if(R(t)){const s=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{s.call(e),e._stopped=!0},t.map(n=>r=>!r._stopped&&n&&n(r))}else return t}const Jn=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,Sl=(e,t,s,n,r,i)=>{const o=r==="svg";t==="class"?ul(e,n,o):t==="style"?hl(e,s,n):us(t)?Gs(t)||ml(e,t,s,n,i):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):wl(e,t,n,o))?(Wn(e,t,n),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&Bn(e,t,n,o,i,t!=="value")):e._isVueCE&&(/[A-Z]/.test(t)||!X(n))?Wn(e,Je(t),n,i,t):(t==="true-value"?e._trueValue=n:t==="false-value"&&(e._falseValue=n),Bn(e,t,n,o))};function wl(e,t,s,n){if(n)return!!(t==="innerHTML"||t==="textContent"||t in e&&Jn(t)&&D(s));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="autocorrect"||t==="sandbox"&&e.tagName==="IFRAME"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const r=e.tagName;if(r==="IMG"||r==="VIDEO"||r==="CANVAS"||r==="SOURCE")return!1}return Jn(t)&&X(s)?!1:t in e}const cs=e=>{const t=e.props["onUpdate:modelValue"]||!1;return R(t)?s=>Yt(t,s):t};function Cl(e){e.target.composing=!0}function Gn(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}const ht=Symbol("_assign");function Yn(e,t,s){return t&&(e=e.trim()),s&&(e=Xs(e)),e}const rt={created(e,{modifiers:{lazy:t,trim:s,number:n}},r){e[ht]=cs(r);const i=n||r.props&&r.props.type==="number";tt(e,t?"change":"input",o=>{o.target.composing||e[ht](Yn(e.value,s,i))}),(s||i)&&tt(e,"change",()=>{e.value=Yn(e.value,s,i)}),t||(tt(e,"compositionstart",Cl),tt(e,"compositionend",Gn),tt(e,"change",Gn))},mounted(e,{value:t}){e.value=t??""},beforeUpdate(e,{value:t,oldValue:s,modifiers:{lazy:n,trim:r,number:i}},o){if(e[ht]=cs(o),e.composing)return;const l=(i||e.type==="number")&&!/^0\d/.test(e.value)?Xs(e.value):e.value,u=t??"";l!==u&&(document.activeElement===e&&e.type!=="range"&&(n&&t===s||r&&e.value.trim()===u)||(e.value=u))}},zn={created(e,{value:t},s){e.checked=es(t,s.props.value),e[ht]=cs(s),tt(e,"change",()=>{e[ht](Tl(e))})},beforeUpdate(e,{value:t,oldValue:s},n){e[ht]=cs(n),t!==s&&(e.checked=es(t,n.props.value))}};function Tl(e){return"_value"in e?e._value:e.value}const El=["ctrl","shift","alt","meta"],Ol={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>El.some(s=>e[`${s}Key`]&&!t.includes(s))},Pl=(e,t)=>{const s=e._withMods||(e._withMods={}),n=t.join(".");return s[n]||(s[n]=(r,...i)=>{for(let o=0;o{const t=Il().createApp(...e),{mount:s}=t;return t.mount=n=>{const r=Rl(n);if(!r)return;const i=t._component;!D(i)&&!i.render&&!i.template&&(i.template=r.innerHTML),r.nodeType===1&&(r.textContent="");const o=s(r,!1,Fl(r));return r instanceof Element&&(r.removeAttribute("v-cloak"),r.setAttribute("data-v-app","")),o},t};function Fl(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function Rl(e){return X(e)?document.querySelector(e):e}class dn{constructor(t={}){"videoPath"in t||(this.videoPath=""),"outputPath"in t||(this.outputPath=""),"success"in t||(this.success=!1),Object.assign(this,t)}static createFrom(t={}){let s=typeof t=="string"?JSON.parse(t):t;return new dn(s)}}class bs{constructor(t={}){"path"in t||(this.path=""),"name"in t||(this.name=""),"videoCount"in t||(this.videoCount=0),"videoPaths"in t||(this.videoPaths=[]),Object.assign(this,t)}static createFrom(t={}){const s=$l;let n=typeof t=="string"?JSON.parse(t):t;return"videoPaths"in n&&(n.videoPaths=s(n.videoPaths)),new bs(n)}}class hn{constructor(t={}){"Code"in t||(this.Code=0),"Msg"in t||(this.Msg=""),"Data"in t||(this.Data=null),Object.assign(this,t)}static createFrom(t={}){let s=typeof t=="string"?JSON.parse(t):t;return new hn(s)}}class pn{constructor(t={}){"index"in t||(this.index=0),"fileName"in t||(this.fileName=""),"filePath"in t||(this.filePath=""),"size"in t||(this.size=""),"seconds"in t||(this.seconds=0),"status"in t||(this.status=""),"progress"in t||(this.progress=""),Object.assign(this,t)}static createFrom(t={}){let s=typeof t=="string"?JSON.parse(t):t;return new pn(s)}}const $l=_e.Array(_e.Any),Dl=bs.createFrom;_e.Array(Dl);function Ll(e,t){return vt.ByID(2350837569,e,t).then(s=>jl(s))}const Nl=hn.createFrom,jl=_e.Nullable(Nl);function Hl(e){return vt.ByID(1728131056,e).then(t=>Kl(t))}function Vl(e){return vt.ByID(2398906893,e).then(t=>Bl(t))}const Ul=dn.createFrom,Kl=_e.Array(Ul),Bl=_e.Array(_e.Any);function ri(){return vt.ByID(23551676)}_e.Array(_e.Any);function Wl(e){return vt.ByID(2660164351,e).then(t=>Jl(t))}function kl(e){return vt.ByID(2209109868,e).then(t=>Yl(t))}const ql=pn.createFrom,Jl=_e.Array(ql),Gl=bs.createFrom,Yl=_e.Array(Gl),ys=(e,t)=>{const s=e.__vccOpts||e;for(const[n,r]of t)s[n]=r;return s},zl={class:"video-tab"},Xl={class:"form-group"},Zl={class:"input-group"},Ql={key:0,class:"form-group"},ec={class:"folder-list"},tc={class:"video-count"},sc={class:"form-group"},nc={class:"radio-group"},rc={class:"form-group"},ic=["max","disabled"],oc={class:"hint"},lc={class:"form-group"},cc={class:"input-group"},uc={class:"form-group"},fc=["disabled"],ac={key:1,class:"results"},dc={class:"progress-bar"},hc={class:"progress-text"},pc={__name:"VideoTab",setup(e){const t=Y(""),s=Y([]),n=Y(1),r=Y(1),i=Y(0),o=Y(""),l=Y(!1),u=Y([]),d=ct(()=>t.value&&r.value>0&&r.value<=i.value&&!l.value),a=async()=>{try{const P=await ri();if(P&&P.trim()){t.value=P.trim();try{s.value=await kl(t.value),w()}catch(g){alert("列出文件夹失败: "+g.message)}}}catch(P){console.error("选择文件夹失败:",P),alert("选择文件夹失败: "+(P.message||"未知错误"))}},p=()=>{const P=document.createElement("input");P.type="file",P.accept="image/*",P.onchange=g=>{const E=g.target.files[0];E&&(o.value=E.name)},P.click()},w=()=>{n.value===1?i.value=s.value.reduce((P,g)=>P*g.videoCount,1):i.value=s.value.length>0?s.value[0].videoCount:0},O=async()=>{if(d.value){l.value=!0,u.value=[];try{const P={folderPath:t.value,num:r.value,joinType:n.value,auditImagePath:o.value,folderInfos:s.value};try{const g=await Wl(P);g&&(u.value=g)}catch(g){alert("拼接视频失败: "+g.message)}}catch(P){alert("拼接失败: "+P.message)}finally{l.value=!1}}};return(P,g)=>(J(),Q("div",zl,[g[14]||(g[14]=_("h2",null,"视频拼接",-1)),_("div",Xl,[g[5]||(g[5]=_("label",null,"选择文件夹:",-1)),_("div",Zl,[Pe(_("input",{type:"text","onUpdate:modelValue":g[0]||(g[0]=E=>t.value=E),placeholder:"请选择包含视频文件夹的目录",readonly:""},null,512),[[rt,t.value]]),_("button",{onClick:a},"选择文件夹")])]),s.value.length>0?(J(),Q("div",Ql,[g[6]||(g[6]=_("label",null,"文件夹列表:",-1)),_("div",ec,[(J(!0),Q(de,null,Hs(s.value,(E,$)=>(J(),Q("div",{key:$,class:"folder-item"},[_("span",null,z(E.name),1),_("span",tc,z(E.videoCount)+" 个视频",1)]))),128))])])):ge("",!0),_("div",sc,[g[9]||(g[9]=_("label",null,"拼接模式:",-1)),_("div",nc,[_("label",null,[Pe(_("input",{type:"radio","onUpdate:modelValue":g[1]||(g[1]=E=>n.value=E),value:1},null,512),[[zn,n.value]]),g[7]||(g[7]=Ws(" 组合拼接(从每个文件夹随机选择) ",-1))]),_("label",null,[Pe(_("input",{type:"radio","onUpdate:modelValue":g[2]||(g[2]=E=>n.value=E),value:2},null,512),[[zn,n.value]]),g[8]||(g[8]=Ws(" 顺序拼接(按索引顺序) ",-1))])])]),_("div",rc,[g[10]||(g[10]=_("label",null,"生成数量:",-1)),Pe(_("input",{type:"number","onUpdate:modelValue":g[3]||(g[3]=E=>r.value=E),min:1,max:i.value,disabled:l.value},null,8,ic),[[rt,r.value,void 0,{number:!0}]]),_("span",oc,"最大可生成:"+z(i.value)+" 个",1)]),_("div",lc,[g[11]||(g[11]=_("label",null,"审核图片(可选):",-1)),_("div",cc,[Pe(_("input",{type:"text","onUpdate:modelValue":g[4]||(g[4]=E=>o.value=E),placeholder:"选择审核图片",readonly:""},null,512),[[rt,o.value]]),_("button",{onClick:p},"选择图片")])]),_("div",uc,[_("button",{class:"btn-primary",onClick:O,disabled:!d.value||l.value},z(l.value?"处理中...":"开始拼接"),9,fc)]),u.value.length>0?(J(),Q("div",ac,[g[13]||(g[13]=_("h3",null,"拼接结果",-1)),_("table",null,[g[12]||(g[12]=_("thead",null,[_("tr",null,[_("th",null,"序号"),_("th",null,"文件名"),_("th",null,"大小"),_("th",null,"时长"),_("th",null,"状态"),_("th",null,"进度")])],-1)),_("tbody",null,[(J(!0),Q(de,null,Hs(u.value,E=>(J(),Q("tr",{key:E.index},[_("td",null,z(E.index),1),_("td",null,z(E.fileName),1),_("td",null,z(E.size),1),_("td",null,z(E.seconds)+"秒",1),_("td",{class:pt(E.status==="拼接成功"?"success":"error")},z(E.status),3),_("td",null,[_("div",dc,[_("div",{class:"progress-fill",style:ds({width:E.progress})},null,4),_("span",hc,z(E.progress),1)])])]))),128))])])])):ge("",!0)]))}},gc=ys(pc,[["__scopeId","data-v-54d3cc11"]]),mc={class:"extract-tab"},vc={class:"form-group"},_c={class:"input-group"},bc={key:0,class:"form-group"},yc={class:"video-list"},xc={class:"hint"},Sc={class:"form-group"},wc=["disabled"],Cc={class:"form-group"},Tc=["disabled"],Ec=["disabled"],Oc={key:1,class:"help-info"},Pc={key:2,class:"results"},Ac={class:"result-summary"},Ic={__name:"ExtractTab",setup(e){const t=Y(""),s=Y([]),n=Y(1),r=Y(!1),i=Y(""),o=Y([]),l=ct(()=>t.value&&s.value.length>0&&n.value>0&&!r.value),u=ct(()=>t.value&&s.value.length>0&&!r.value),d=ct(()=>o.value.filter(g=>g.success).length),a=ct(()=>o.value.filter(g=>!g.success).length),p=async()=>{try{const g=await ri();if(g&&g.trim()){t.value=g.trim();try{s.value=await Vl(t.value)}catch(E){alert("列出视频失败: "+E.message)}}}catch(g){console.error("选择文件夹失败:",g),alert("选择文件夹失败: "+(g.message||"未知错误"))}},w=g=>g.split("/").pop()||g.split("\\").pop()||g,O=async()=>{if(l.value){r.value=!0,o.value=[],i.value=`开始处理,每个视频将生成 ${n.value} 个抽帧视频...`;try{const g={folderPath:t.value,extractCount:n.value},E=s.value.length*n.value;i.value=`处理中... (0/${E})`;try{const $=await Hl(g);$&&(o.value=$,i.value=`全部完成! 共处理 ${E} 个任务,成功 ${d.value} 个,失败 ${a.value} 个`)}catch($){alert("抽帧失败: "+$.message),i.value="处理失败"}}catch(g){alert("抽帧失败: "+g.message),i.value="处理失败: "+g.message}finally{r.value=!1}}},P=async()=>{if(u.value){r.value=!0,o.value=[],i.value="开始修改元数据...";try{if(extractService&&extractService.ModifyVideosMetadata){const g=s.value.length;i.value=`处理中... (0/${g})`;const E=await extractService.ModifyVideosMetadata(t.value);E&&(o.value=E,i.value=`全部完成! 共处理 ${g} 个任务,成功 ${d.value} 个,失败 ${a.value} 个`)}}catch(g){alert("修改失败: "+g.message),i.value="处理失败: "+g.message}finally{r.value=!1}}};return(g,E)=>(J(),Q("div",mc,[E[6]||(E[6]=_("h2",null,"视频抽帧",-1)),_("div",vc,[E[2]||(E[2]=_("label",null,"选择文件夹:",-1)),_("div",_c,[Pe(_("input",{type:"text","onUpdate:modelValue":E[0]||(E[0]=$=>t.value=$),placeholder:"请选择包含视频文件的目录",readonly:""},null,512),[[rt,t.value]]),_("button",{onClick:p},"选择文件夹")])]),s.value.length>0?(J(),Q("div",bc,[E[3]||(E[3]=_("label",null,"视频文件:",-1)),_("div",yc,[(J(!0),Q(de,null,Hs(s.value,($,V)=>(J(),Q("div",{key:V,class:"video-item"},z(w($)),1))),128))]),_("p",xc,"共 "+z(s.value.length)+" 个视频文件",1)])):ge("",!0),_("div",Sc,[E[4]||(E[4]=_("label",null,"每个视频生成数量:",-1)),Pe(_("input",{type:"number","onUpdate:modelValue":E[1]||(E[1]=$=>n.value=$),min:1,disabled:r.value},null,8,wc),[[rt,n.value,void 0,{number:!0}]])]),_("div",Cc,[_("button",{class:"btn-primary",onClick:O,disabled:!l.value||r.value},z(r.value?"处理中...":"开始抽帧"),9,Tc),_("button",{class:"btn-secondary",onClick:P,disabled:!u.value||r.value,style:{"margin-left":"10px"}},z(r.value?"处理中...":"开始修改元数据"),9,Ec)]),i.value?(J(),Q("div",Oc,z(i.value),1)):ge("",!0),o.value.length>0?(J(),Q("div",Pc,[E[5]||(E[5]=_("h3",null,"处理结果",-1)),_("div",Ac,[_("p",null,"成功: "+z(d.value)+" 个",1),_("p",null,"失败: "+z(a.value)+" 个",1)])])):ge("",!0)]))}},Mc=ys(Ic,[["__scopeId","data-v-efad5166"]]),Fc={class:"dialog-header"},Rc={class:"dialog-body"},$c={class:"form-group"},Dc={class:"form-group"},Lc={key:0,class:"error-message"},Nc={class:"dialog-footer"},jc=["disabled"],Hc={__name:"LoginDialog",props:{allowClose:{type:Boolean,default:!1}},emits:["close","login-success"],setup(e,{emit:t}){const s=e,n=t,r=Y(""),i=Y(""),o=Y(!1),l=Y(""),u=()=>{s.allowClose&&n("close")},d=async()=>{if(!r.value||!i.value){l.value="请输入用户名和密码";return}o.value=!0,l.value="";try{const a=await Ll(r.value,i.value);a&&a.Code===200?(n("login-success"),n("close")):l.value=a&&a.Msg||"登录失败"}catch(a){l.value="登录失败: "+(a.message||a)}finally{o.value=!1}};return(a,p)=>(J(),Q("div",{class:"dialog-overlay",onClick:u},[_("div",{class:"dialog",onClick:p[4]||(p[4]=Pl(()=>{},["stop"]))},[_("div",Fc,[p[5]||(p[5]=_("h3",null,"用户登录",-1)),e.allowClose?(J(),Q("button",{key:0,class:"close-btn",onClick:p[0]||(p[0]=w=>a.$emit("close"))},"×")):ge("",!0)]),_("div",Rc,[_("div",$c,[p[6]||(p[6]=_("label",null,"用户名:",-1)),Pe(_("input",{type:"text","onUpdate:modelValue":p[1]||(p[1]=w=>r.value=w),placeholder:"请输入用户名"},null,512),[[rt,r.value]])]),_("div",Dc,[p[7]||(p[7]=_("label",null,"密码:",-1)),Pe(_("input",{type:"password","onUpdate:modelValue":p[2]||(p[2]=w=>i.value=w),placeholder:"请输入密码"},null,512),[[rt,i.value]])]),l.value?(J(),Q("div",Lc,z(l.value),1)):ge("",!0)]),_("div",Nc,[e.allowClose?(J(),Q("button",{key:0,class:"btn-secondary",onClick:p[3]||(p[3]=w=>a.$emit("close"))},"取消")):ge("",!0),_("button",{class:"btn-primary",onClick:d,disabled:o.value},z(o.value?"登录中...":"登录"),9,jc)])])]))}},Vc=ys(Hc,[["__scopeId","data-v-123e9c29"]]),Uc={class:"app-container"},Kc={key:0,class:"login-overlay"},Bc={class:"main-interface"},Wc={class:"sidebar"},kc={class:"menu-items"},qc={class:"main-content"},Jc={__name:"App",setup(e){const t=Y("video"),s=Y(!1),n=Y(!1),r=()=>{const u=localStorage.getItem("isLoggedIn"),d=localStorage.getItem("loginTime");if(u==="true"&&d){const a=Date.now(),p=parseInt(d);(a-p)/(1e3*60*60)<24?n.value=!0:(localStorage.removeItem("isLoggedIn"),localStorage.removeItem("loginTime"),n.value=!1,s.value=!0)}else n.value=!1,s.value=!0},i=()=>{n.value=!0,s.value=!1,localStorage.setItem("isLoggedIn","true"),localStorage.setItem("loginTime",Date.now().toString())},o=()=>{n.value&&(s.value=!1)},l=()=>{confirm("确定要退出登录吗?")&&(n.value=!1,s.value=!0,localStorage.removeItem("isLoggedIn"),localStorage.removeItem("loginTime"))};return Rr(()=>{r()}),(u,d)=>(J(),Q("div",Uc,[n.value?ge("",!0):(J(),Q("div",Kc,[...d[2]||(d[2]=[_("div",{class:"login-message"},[_("h2",null,"请先登录"),_("p",null,"您需要登录后才能使用此应用")],-1)])])),Pe(_("div",Bc,[_("div",Wc,[_("div",kc,[_("button",{class:pt(["menu-item",{active:t.value==="video"}]),onClick:d[0]||(d[0]=a=>t.value="video")},[...d[3]||(d[3]=[_("span",{class:"icon"},"🎬",-1),_("span",null,"视频",-1)])],2),d[5]||(d[5]=_("div",{class:"separator"},null,-1)),_("button",{class:pt(["menu-item",{active:t.value==="extract"}]),onClick:d[1]||(d[1]=a=>t.value="extract")},[...d[4]||(d[4]=[_("span",{class:"icon"},"✂️",-1),_("span",null,"抽帧",-1)])],2)]),_("div",{class:"menu-bottom"},[_("button",{class:"menu-item",onClick:l},[...d[6]||(d[6]=[_("span",{class:"icon"},"👤",-1)])])])]),_("div",qc,[t.value==="video"?(J(),Xt(gc,{key:0})):ge("",!0),t.value==="extract"?(J(),Xt(Mc,{key:1})):ge("",!0)])],512),[[fl,n.value]]),s.value?(J(),Xt(Vc,{key:1,"allow-close":n.value,onClose:o,onLoginSuccess:i},null,8,["allow-close"])):ge("",!0)]))}},Gc=ys(Jc,[["__scopeId","data-v-75b1df04"]]);Ml(Gc).mount("#app"); diff --git a/wails/assets/index.html b/wails/assets/index.html index bfabf9b..4cf238a 100644 --- a/wails/assets/index.html +++ b/wails/assets/index.html @@ -1,9 +1,9 @@ - - - - - - 视频拼接工具 + + + + + + 视频拼接工具 - - - - -
- - - + + + + + +
+ + +