视频转码-合并版

This commit is contained in:
Abin
2026-04-18 17:36:54 +08:00
parent 142a045e34
commit 2706aa5bf5
2 changed files with 536 additions and 0 deletions
@@ -0,0 +1,536 @@
#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 <GUIConstantsEx.au3>
#include <EditConstants.au3>
#include <WindowsConstants.au3>
#include <File.au3>
#include <Array.au3>
#include <Excel.au3>
#include <Inet.au3>
#include <MsgBoxConstants.au3>
; ========== 配置 ==========
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)
; ========== 标签页1HLS批量转码 ==========
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
Binary file not shown.