#Region ;**** Directives created by AutoIt3Wrapper_GUI **** #AutoIt3Wrapper_Icon=icon.ico #AutoIt3Wrapper_Outfile=HLS_Converter_and_Renamer.exe #AutoIt3Wrapper_Compression=4 #AutoIt3Wrapper_UseUpx=y #EndRegion ;**** Directives created by AutoIt3Wrapper_GUI **** #include #include #include #include #include #include #include #include ; ========== 配置 ========== Global Const $MAX_CONCURRENT = 8 ; ========== 全局变量 - 转码模块 ========== Global $g_sSourceDir = "" Global $g_hLogEdit_Convert, $g_hBtnStart_Convert, $g_hBtnSelect_Convert, $g_hLabelStatus_Convert Global $g_aPendingFiles[0] Global $g_aRunningTasks[0][4] Global $g_iSuccess = 0 Global $g_bConverting = False Global $g_iTotalFiles = 0 Global $g_iCurrentIndex = 0 ; ========== 全局变量 - 重命名模块 ========== Global $g_sSelectedPath_Rename = "" Global $g_aMP4Files[0] Global $g_aResults[1][4] Global $g_iResultCount = 0 Global $g_bProcessing_Rename = False Global $g_hLogEdit_Rename, $g_hBtnSelectDir_Rename, $g_hBtnStart_Rename ; ========== 创建主窗口 ========== Global $hMainGUI = GUICreate("HLS转码与文件夹重命名一体化工具 v1.0", 750, 600) ; 创建TabControl Global $hTabControl = GUICtrlCreateTab(10, 10, 730, 580) ; ========== 标签页1:HLS批量转码 ========== Global $hTab_Convert = GUICtrlCreateTabItem("HLS批量转码") GUICtrlCreateLabel("1. 请选择包含视频文件的源目录:", 20, 50, 400, 20) $g_hBtnSelect_Convert = GUICtrlCreateButton("选择目录...", 20, 75, 100, 30) $g_hLabelStatus_Convert = GUICtrlCreateLabel("", 130, 80, 570, 20) GUICtrlSetColor(-1, 0x0000FF) GUICtrlCreateLabel("2. 点击开始转码(最多 " & $MAX_CONCURRENT & " 个并发):", 20, 115, 400, 20) $g_hBtnStart_Convert = GUICtrlCreateButton("开始转码", 20, 140, 100, 30) GUICtrlSetState($g_hBtnStart_Convert, $GUI_DISABLE) GUICtrlCreateLabel("实时日志(格式: [序号/总数] [视频名] 日志内容):", 20, 185, 300, 20) $g_hLogEdit_Convert = GUICtrlCreateEdit("", 20, 210, 700, 350, BitOR($ES_AUTOVSCROLL, $ES_READONLY, $WS_VSCROLL)) GUICtrlSetFont($g_hLogEdit_Convert, 9, 400, 0, "Consolas") ; ========== 标签页2:文件夹重命名 ========== Global $hTab_Rename = GUICtrlCreateTabItem("文件夹重命名 & Excel生成") GUICtrlCreateLabel("1. 请选择包含视频文件的源目录:", 20, 50, 400, 20) $g_hBtnSelectDir_Rename = GUICtrlCreateButton("📁 选择视频目录", 20, 75, 150, 40) GUICtrlSetFont(-1, 10, 600) GUICtrlCreateLabel("2. 点击开始执行处理所有视频文件:", 20, 125, 400, 20) $g_hBtnStart_Rename = GUICtrlCreateButton("▶️ 开始执行", 20, 150, 150, 40) GUICtrlSetFont(-1, 10, 600) GUICtrlSetState($g_hBtnStart_Rename, $GUI_DISABLE) GUICtrlCreateLabel("处理日志:", 20, 205, 300, 20) $g_hLogEdit_Rename = GUICtrlCreateEdit("", 20, 230, 700, 330, BitOR($ES_AUTOVSCROLL, $ES_READONLY, $WS_VSCROLL, $ES_MULTILINE)) GUICtrlSetFont($g_hLogEdit_Rename, 9, 400, 0, "Consolas") GUICtrlSetBkColor(-1, 0xFFFFFF) GUICtrlSetLimit(-1, 100000) ; 结束TabControl GUICtrlCreateTabItem("") GUISetState(@SW_SHOW, $hMainGUI) ; ========== 消息循环 ========== While 1 Switch GUIGetMsg() Case $GUI_EVENT_CLOSE _KillAllRunningTasks() Exit ; ========== 转码模块事件 ========== Case $g_hBtnSelect_Convert _SelectSourceDir_Convert() Case $g_hBtnStart_Convert _StartConversion() ; ========== 重命名模块事件 ========== Case $g_hBtnSelectDir_Rename _SelectSourceDir_Rename() Case $g_hBtnStart_Rename _StartRenaming() EndSwitch ; 转码模块:定期检查任务状态 If $g_bConverting Then _PollRunningTasks() Sleep(50) EndIf ; 重命名模块:定期检查处理状态(如果有需要的话) If $g_bProcessing_Rename Then Sleep(10) EndIf WEnd ; ====================================================================== ; ========== 转码模块函数 ========== ; ====================================================================== Func _SelectSourceDir_Convert() Local $sDir = FileSelectFolder("请选择视频所在的源目录", "") If Not @error And $sDir <> "" Then $g_sSourceDir = $sDir GUICtrlSetData($g_hLabelStatus_Convert, "已选择: " & $g_sSourceDir) GUICtrlSetState($g_hBtnStart_Convert, $GUI_ENABLE) _Log_Convert("✅ 目录已选择:" & $g_sSourceDir) Else $g_sSourceDir = "" GUICtrlSetData($g_hLabelStatus_Convert, "未选择目录") GUICtrlSetState($g_hBtnStart_Convert, $GUI_DISABLE) EndIf EndFunc Func _StartConversion() If $g_sSourceDir = "" Or $g_bConverting Then Return $g_bConverting = True GUICtrlSetState($g_hBtnStart_Convert, $GUI_DISABLE) GUICtrlSetState($g_hBtnSelect_Convert, $GUI_DISABLE) _Log_Convert(@CRLF & "🚀 开始批量转码(最多 " & $MAX_CONCURRENT & " 个并发)..." & @CRLF) ; 初始化 $g_iSuccess = 0 $g_iCurrentIndex = 0 Dim $g_aPendingFiles[0] Dim $g_aRunningTasks[0][4] ; 递归获取所有子目录中的视频文件(返回完整路径) Local $aFiles = _FileListToArrayRec($g_sSourceDir, "*.mp4;*.avi;*.mkv;*.mov;*.wmv;*.flv;*.mpeg;*.mpg;*.webm", $FLTAR_FILES, $FLTAR_RECUR, $FLTAR_SORT, $FLTAR_FULLPATH) If @error Then _Log_Convert("❌ 目录中没有找到任何支持的视频文件。") _FinishConversion() Return EndIf $g_iTotalFiles = $aFiles[0] ; 填充待处理队列 For $i = 1 To $aFiles[0] _ArrayAdd($g_aPendingFiles, $aFiles[$i]) Next ; 启动第一批任务 _LaunchNextTasks() EndFunc Func _LaunchNextTasks() While UBound($g_aRunningTasks, 1) < $MAX_CONCURRENT And UBound($g_aPendingFiles) > 0 Local $sFilePath = $g_aPendingFiles[0] _ArrayDelete($g_aPendingFiles, 0) $g_iCurrentIndex += 1 ; 提取文件名(不含路径和扩展名) Local $sFileName = StringRegExpReplace($sFilePath, "^.*\\", "") Local $sBaseName = StringRegExpReplace($sFileName, "\.[^.\\]+$", "") ; 获取视频所在目录 Local $sVideoDir = StringLeft($sFilePath, StringInStr($sFilePath, "\", 0, -1) - 1) ; 输出目录 = 视频所在目录 \ 视频名(无扩展名) Local $sOutputDir = $sVideoDir & "\" & $sBaseName If DirCreate($sOutputDir) = 0 Then _Log_Convert("[系统] ⚠️ 无法创建目录: " & $sOutputDir) ContinueLoop EndIf Local $sM3U8 = $sOutputDir & "\playlist.m3u8" Local $sTS = $sOutputDir & "\segment_%05d.ts" Local $sCmd = 'ffmpeg -i "' & $sFilePath & '" ' & _ '-c:v libx264 -crf 23 -preset fast -profile:v baseline -level 3.0 ' & _ '-c:a aac -b:a 128k ' & _ '-hls_time 2 ' & _ '-hls_playlist_type vod ' & _ '-hls_segment_filename "' & $sTS & '" ' & _ '"' & $sM3U8 & '"' ; 启动进程并捕获合并输出 Local $iPID = Run($sCmd, "", @SW_HIDE, $STDERR_MERGED) If @error Then _Log_Convert("[" & $g_iCurrentIndex & "/" & $g_iTotalFiles & "] [" & $sBaseName & "] ❌ 启动失败") ContinueLoop EndIf ; 记录任务 Local $iIndex = UBound($g_aRunningTasks, 1) ReDim $g_aRunningTasks[$iIndex + 1][4] $g_aRunningTasks[$iIndex][0] = $iPID $g_aRunningTasks[$iIndex][1] = $sBaseName $g_aRunningTasks[$iIndex][2] = $sOutputDir $g_aRunningTasks[$iIndex][3] = "" ; 打印序号/总数 + 文件名 + 完整路径 _Log_Convert("[" & $g_iCurrentIndex & "/" & $g_iTotalFiles & "] [" & $sBaseName & "] 🔄 开始转码...") _Log_Convert(" 路径: " & $sFilePath) WEnd EndFunc Func _PollRunningTasks() If UBound($g_aRunningTasks, 1) = 0 Then If UBound($g_aPendingFiles) = 0 Then _FinishConversion() EndIf Return EndIf Local $aToRemove[0] For $i = 0 To UBound($g_aRunningTasks, 1) - 1 Local $iPID = $g_aRunningTasks[$i][0] Local $sName = $g_aRunningTasks[$i][1] ; 静默读取输出流(避免 ffmpeg 因缓冲区满而挂起) Local $sDummy = StdoutRead($iPID) ; 检查进程是否结束 If Not ProcessExists($iPID) Then Local $iExitCode = @extended If $iExitCode = 0 Then $g_iSuccess += 1 _Log_Convert("[" & $sName & "] ✅ 成功完成") Else _Log_Convert("[" & $sName & "] ❌ 失败 (退出码: " & $iExitCode & ")") EndIf _ArrayAdd($aToRemove, $i) EndIf Next ; 移除已完成的任务(从后往前删) For $i = UBound($aToRemove) - 1 To 0 Step -1 Local $iIndex = $aToRemove[$i] _ArrayDelete($g_aRunningTasks, $iIndex) Next ; 启动新任务填补空缺 If UBound($aToRemove) > 0 Then _LaunchNextTasks() EndIf EndFunc Func _FinishConversion() $g_bConverting = False _Log_Convert(@CRLF & "🎉 所有转码任务完成!成功处理 " & $g_iSuccess & " 个视频。") GUICtrlSetState($g_hBtnStart_Convert, $GUI_ENABLE) GUICtrlSetState($g_hBtnSelect_Convert, $GUI_ENABLE) EndFunc Func _KillAllRunningTasks() If Not IsArray($g_aRunningTasks) Then Return For $i = 0 To UBound($g_aRunningTasks, 1) - 1 ProcessClose($g_aRunningTasks[$i][0]) Next EndFunc Func _Log_Convert($sText) Local $sCurrent = GUICtrlRead($g_hLogEdit_Convert) Local $sNew = $sCurrent & $sText & @CRLF If StringLen($sNew) > 2000 Then $sNew = StringRight($sNew, 2000) EndIf GUICtrlSetData($g_hLogEdit_Convert, $sNew) GUICtrlSendMsg($g_hLogEdit_Convert, $EM_LINESCROLL, 0, GUICtrlSendMsg($g_hLogEdit_Convert, $EM_GETLINECOUNT, 0, 0)) EndFunc ; ====================================================================== ; ========== 重命名模块函数 ========== ; ====================================================================== Func _SelectSourceDir_Rename() Local $sPath = FileSelectFolder("请选择包含视频的文件夹", "") If $sPath <> "" Then $g_sSelectedPath_Rename = $sPath _Log_Rename("✓ 已选择目录: " & $g_sSelectedPath_Rename) GUICtrlSetState($g_hBtnStart_Rename, $GUI_ENABLE) EndIf EndFunc Func _StartRenaming() If $g_sSelectedPath_Rename = "" Then MsgBox($MB_ICONWARNING, "警告", "请先选择视频目录!") Return EndIf ; 禁用按钮,防止重复点击 GUICtrlSetState($g_hBtnSelectDir_Rename, $GUI_DISABLE) GUICtrlSetState($g_hBtnStart_Rename, $GUI_DISABLE) $g_bProcessing_Rename = True ; 清空日志和结果 GUICtrlSetData($g_hLogEdit_Rename, "") $g_iResultCount = 0 ReDim $g_aResults[1][4] ; 扫描视频文件 _Log_Rename("🔍 正在扫描目录中的视频文件...") Local $aTemp = _FileListToArrayRec($g_sSelectedPath_Rename, "*.mp4;*.avi;*.mkv;*.mov;*.wmv;*.flv;*.mpeg;*.mpg;*.webm", $FLTAR_FILES, $FLTAR_RECUR, $FLTAR_SORT, $FLTAR_FULLPATH) If Not @error And IsArray($aTemp) And UBound($aTemp) > 0 Then $g_aMP4Files = $aTemp Else Dim $g_aMP4Files[0] EndIf If UBound($g_aMP4Files) = 0 Or (UBound($g_aMP4Files) = 1 And $g_aMP4Files[0] = 0) Then _Log_Rename("❌ 未找到任何视频文件!") GUICtrlSetState($g_hBtnSelectDir_Rename, $GUI_ENABLE) GUICtrlSetState($g_hBtnStart_Rename, $GUI_ENABLE) $g_bProcessing_Rename = False Return EndIf _Log_Rename("✓ 找到 " & UBound($g_aMP4Files) & " 个视频文件") _Log_Rename("") ; 处理每个视频文件 Local $iSuccessCount = 0 Local $iFailCount = 0 Local $aFailedFiles[0] ; 用于存储失败的文件信息 "文件路径|失败原因" For $i = 0 To UBound($g_aMP4Files) - 1 If Not $g_bProcessing_Rename Then ExitLoop Local $sMP4Path = $g_aMP4Files[$i] Local $sMP4Name = StringRegExpReplace($sMP4Path, "^.+[\\/]", "") Local $sMP4BaseName = StringRegExpReplace($sMP4Name, "(?i)\.(mp4|avi|mkv|mov|wmv|flv|mpeg|mpg|webm)$", "", 1) Local $sMP4Dir = StringRegExpReplace($sMP4Path, "[\\/][^\\/]+$", "") ; 检查同名文件夹是否存在 Local $sFolderName = $sMP4BaseName Local $sFolderPath = $sMP4Dir & "\" & $sFolderName If DirGetSize($sFolderPath) = -1 Then _Log_Rename("⚠️ [" & ($i + 1) & "/" & UBound($g_aMP4Files) & "] " & $sMP4Name & " - 未找到同名文件夹") _Log_Rename(" 路径: " & $sMP4Path) _ArrayAdd($aFailedFiles, $sMP4Path & "|" & "未找到同名文件夹") $iFailCount += 1 ContinueLoop EndIf ; 检查playlist.m3u8是否存在 Local $sM3U8Path = $sFolderPath & "\playlist.m3u8" If Not FileExists($sM3U8Path) Then _Log_Rename("⚠️ [" & ($i + 1) & "/" & UBound($g_aMP4Files) & "] " & $sMP4Name & " - 文件夹中不存在playlist.m3u8") _Log_Rename(" 路径: " & $sMP4Path) _ArrayAdd($aFailedFiles, $sMP4Path & "|" & "文件夹中不存在playlist.m3u8") $iFailCount += 1 ContinueLoop EndIf ; 获取新的UUID _Log_Rename("[" & ($i + 1) & "/" & UBound($g_aMP4Files) & "] " & $sMP4Name & " - 正在获取新UUID...") Local $sNewUUID = _GetNewUUID() If $sNewUUID = "" Then _Log_Rename("❌ [" & ($i + 1) & "/" & UBound($g_aMP4Files) & "] " & $sMP4Name & " - 获取UUID失败") _Log_Rename(" 路径: " & $sMP4Path) _ArrayAdd($aFailedFiles, $sMP4Path & "|" & "获取UUID失败") $iFailCount += 1 ContinueLoop EndIf ; 重命名文件夹 Local $sNewFolderPath = $sMP4Dir & "\" & $sNewUUID If DirMove($sFolderPath, $sNewFolderPath) Then _Log_Rename("✓ [" & ($i + 1) & "/" & UBound($g_aMP4Files) & "] " & $sMP4Name & " - 重命名成功: " & $sFolderName & " → " & $sNewUUID) _Log_Rename(" 路径: " & $sMP4Path) ; 扩容结果数组(如果需要) If $g_iResultCount >= UBound($g_aResults) Then ReDim $g_aResults[$g_iResultCount + 10][4] EndIf ; 保存结果 $g_aResults[$g_iResultCount][0] = $sMP4Name $g_aResults[$g_iResultCount][1] = $sMP4Path $g_aResults[$g_iResultCount][2] = $sFolderName $g_aResults[$g_iResultCount][3] = $sNewUUID $g_iResultCount += 1 $iSuccessCount += 1 Else _Log_Rename("❌ [" & ($i + 1) & "/" & UBound($g_aMP4Files) & "] " & $sMP4Name & " - 重命名失败") _Log_Rename(" 路径: " & $sMP4Path) _ArrayAdd($aFailedFiles, $sMP4Path & "|" & "重命名失败") $iFailCount += 1 EndIf ; 等待0.2秒 Sleep(200) Next ; 调整结果数组大小 If $g_iResultCount > 0 Then ReDim $g_aResults[$g_iResultCount][4] EndIf ; 生成Excel报告 If $g_iResultCount > 0 Then _Log_Rename("") _Log_Rename("📊 正在生成Excel报告...") If _GenerateExcelReport() Then _Log_Rename("✓ Excel报告已生成: " & @ScriptDir & "\VideoFolderMapping.xlsx") Else _Log_Rename("❌ Excel报告生成失败") EndIf EndIf ; 显示失败的文件详情 If $iFailCount > 0 Then _Log_Rename("") _Log_Rename("========================================") _Log_Rename("⚠️ 失败文件详情:") For $i = 0 To UBound($aFailedFiles) - 1 Local $aParts = StringSplit($aFailedFiles[$i], "|", $STR_ENTIRESPLIT) If $aParts[0] >= 2 Then _Log_Rename(" " & ($i + 1) & ". " & $aParts[1]) _Log_Rename(" 原因: " & $aParts[2]) EndIf Next _Log_Rename("========================================") EndIf ; 显示统计信息 _Log_Rename("") _Log_Rename("========================================") _Log_Rename("📊 处理完成!") _Log_Rename(" ✓ 成功: " & $iSuccessCount & " 个") _Log_Rename(" ⚠️ 失败: " & $iFailCount & " 个") _Log_Rename(" 📁 总计: " & UBound($g_aMP4Files) & " 个视频文件") _Log_Rename("========================================") ; 恢复按钮状态 GUICtrlSetState($g_hBtnSelectDir_Rename, $GUI_ENABLE) GUICtrlSetState($g_hBtnStart_Rename, $GUI_ENABLE) $g_bProcessing_Rename = False EndFunc Func _GetNewUUID() Local $sURL = "https://api.yeomai.site/API/ApiGetShortUUID.php" Local $sResponse = "" ; 尝试最多3次 For $i = 1 To 3 $sResponse = BinaryToString(InetRead($sURL, 10)) If @error = 0 And $sResponse <> "" Then ; 清理响应数据,移除空白字符 $sResponse = StringStripWS($sResponse, $STR_STRIPLEADING + $STR_STRIPTRAILING) Return $sResponse EndIf Sleep(500) Next Return "" EndFunc Func _Log_Rename($sText) Local $sCurrent = GUICtrlRead($g_hLogEdit_Rename) GUICtrlSetData($g_hLogEdit_Rename, $sCurrent & $sText & @CRLF) ; 滚动到最后一行 Local $iLines = GUICtrlSendMsg($g_hLogEdit_Rename, $EM_GETLINECOUNT, 0, 0) GUICtrlSendMsg($g_hLogEdit_Rename, $EM_LINESCROLL, 0, $iLines) EndFunc Func _GenerateExcelReport() If $g_iResultCount = 0 Then Return False Local $oExcel = _Excel_Open(False, False, False, False, True) If @error Then Return SetError(1, 0, False) Local $oWorkbook = _Excel_BookNew($oExcel, 1) If @error Then _Excel_Close($oExcel) Return SetError(2, 0, False) EndIf ; 设置表头 Local $aHeaders[1][4] = [["MP4文件名", "MP4完整路径", "原文件夹名", "新UUID文件夹名"]] _Excel_RangeWrite($oWorkbook, $oWorkbook.Activesheet, $aHeaders, "A1") ; 写入数据 _Excel_RangeWrite($oWorkbook, $oWorkbook.Activesheet, $g_aResults, "A2") ; 格式化表格 With $oWorkbook.Activesheet .Range("A1:D1").Font.Bold = True .Range("A1:D1").Interior.Color = 0xD3D3D3 .Columns("A:A").ColumnWidth = 30 .Columns("B:B").ColumnWidth = 60 .Columns("C:C").ColumnWidth = 30 .Columns("D:D").ColumnWidth = 35 .UsedRange.Borders.Weight = 2 EndWith ; 保存文件 Local $sSavePath = @ScriptDir & "\VideoFolderMapping.xlsx" _Excel_BookSaveAs($oWorkbook, $sSavePath, $xlOpenXMLWorkbook, True) If @error Then _Excel_BookClose($oWorkbook, False) _Excel_Close($oExcel) Return SetError(3, 0, False) EndIf _Excel_BookClose($oWorkbook, False) _Excel_Close($oExcel) Return True EndFunc