Files
tools/压缩包/au3脚本/视频转码-合并版.au3
T
2026-04-18 17:36:54 +08:00

536 lines
19 KiB
AutoIt
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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