241 lines
7.9 KiB
AutoIt
241 lines
7.9 KiB
AutoIt
; HLS_Batch_Converter_Multi.au3
|
|
; 支持最多 8 个并发转码 + 实时显示 ffmpeg 日志
|
|
|
|
#include <GUIConstantsEx.au3>
|
|
#include <EditConstants.au3>
|
|
#include <WindowsConstants.au3>
|
|
#include <File.au3>
|
|
#include <Array.au3>
|
|
|
|
; ========== 配置 ==========
|
|
Global Const $MAX_CONCURRENT = 8
|
|
|
|
; ========== 全局变量 ==========
|
|
Global $g_sSourceDir = ""
|
|
Global $g_hLogEdit, $g_hBtnStart, $g_hBtnSelect, $g_hLabelStatus
|
|
|
|
; 任务队列和运行中任务
|
|
Global $g_aPendingFiles[0] ; 待处理文件列表 (完整路径)
|
|
Global $g_aRunningTasks[0][4] ; [PID, FileName, OutputDir, LogHandle]
|
|
Global $g_iSuccess = 0
|
|
Global $g_bConverting = False
|
|
|
|
; ========== 创建主窗口 ==========
|
|
GUICreate("HLS 批量转码工具 - 多线程版", 700, 550)
|
|
GUICtrlCreateLabel("1. 请选择包含视频文件的源目录:", 10, 10, 400, 20)
|
|
|
|
$g_hBtnSelect = GUICtrlCreateButton("选择目录...", 10, 35, 100, 30)
|
|
$g_hLabelStatus = GUICtrlCreateLabel("", 120, 40, 570, 20)
|
|
GUICtrlSetColor(-1, 0x0000FF)
|
|
|
|
GUICtrlCreateLabel("2. 点击“开始转码”(最多 " & $MAX_CONCURRENT & " 个并发):", 10, 75, 400, 20)
|
|
$g_hBtnStart = GUICtrlCreateButton("开始转码", 10, 100, 100, 30)
|
|
GUICtrlSetState($g_hBtnStart, $GUI_DISABLE)
|
|
|
|
GUICtrlCreateLabel("实时日志(格式: [视频名] 日志内容):", 10, 140, 300, 20)
|
|
$g_hLogEdit = GUICtrlCreateEdit("", 10, 165, 680, 370, BitOR($ES_AUTOVSCROLL, $ES_READONLY, $WS_VSCROLL))
|
|
GUICtrlSetFont($g_hLogEdit, 9, 400, 0, "Consolas")
|
|
|
|
GUISetState(@SW_SHOW)
|
|
|
|
; ========== 消息循环 ==========
|
|
While 1
|
|
Switch GUIGetMsg()
|
|
Case $GUI_EVENT_CLOSE
|
|
; 清理子进程
|
|
_KillAllRunningTasks()
|
|
Exit
|
|
Case $g_hBtnSelect
|
|
_SelectSourceDir()
|
|
Case $g_hBtnStart
|
|
_StartConversion()
|
|
EndSwitch
|
|
|
|
; 如果正在转码,定期检查任务状态(模拟异步)
|
|
If $g_bConverting Then
|
|
_PollRunningTasks()
|
|
Sleep(50) ; 减轻 CPU 占用
|
|
EndIf
|
|
WEnd
|
|
|
|
; ========== 函数定义 ==========
|
|
Func _SelectSourceDir()
|
|
Local $sDir = FileSelectFolder("请选择视频所在的源目录", "")
|
|
If Not @error And $sDir <> "" Then
|
|
$g_sSourceDir = $sDir
|
|
GUICtrlSetData($g_hLabelStatus, "已选择: " & $g_sSourceDir)
|
|
GUICtrlSetState($g_hBtnStart, $GUI_ENABLE)
|
|
_Log("✅ 目录已选择:" & $g_sSourceDir)
|
|
Else
|
|
$g_sSourceDir = ""
|
|
GUICtrlSetData($g_hLabelStatus, "未选择目录")
|
|
GUICtrlSetState($g_hBtnStart, $GUI_DISABLE)
|
|
EndIf
|
|
EndFunc
|
|
|
|
Func _StartConversion()
|
|
If $g_sSourceDir = "" Or $g_bConverting Then Return
|
|
|
|
$g_bConverting = True
|
|
GUICtrlSetState($g_hBtnStart, $GUI_DISABLE)
|
|
GUICtrlSetState($g_hBtnSelect, $GUI_DISABLE)
|
|
_Log(@CRLF & "🚀 开始批量转码(最多 " & $MAX_CONCURRENT & " 个并发)..." & @CRLF)
|
|
|
|
; 初始化
|
|
$g_iSuccess = 0
|
|
Dim $g_aPendingFiles[0]
|
|
Dim $g_aRunningTasks[0][4]
|
|
|
|
; 获取视频文件列表
|
|
Local $aExtensions = [".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".mpeg", ".mpg", ".webm"]
|
|
Local $aFiles = _FileListToArray($g_sSourceDir, "*", $FLTA_FILES)
|
|
If @error Then
|
|
_Log("❌ 目录中没有找到任何文件。")
|
|
_FinishConversion()
|
|
Return
|
|
EndIf
|
|
|
|
; 填充待处理队列
|
|
For $i = 1 To $aFiles[0]
|
|
Local $sFile = $g_sSourceDir & "\" & $aFiles[$i]
|
|
Local $sExt = StringLower(StringRight($sFile, 4))
|
|
If _IsInArray($aExtensions, $sExt) Then
|
|
_ArrayAdd($g_aPendingFiles, $sFile)
|
|
EndIf
|
|
Next
|
|
|
|
If UBound($g_aPendingFiles) = 0 Then
|
|
_Log("❌ 未找到支持的视频文件。")
|
|
_FinishConversion()
|
|
Return
|
|
EndIf
|
|
|
|
; 启动第一批任务(最多 MAX_CONCURRENT 个)
|
|
_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)
|
|
|
|
Local $sFileName = StringRegExpReplace($sFilePath, "^.*\\", "") ; 仅文件名
|
|
Local $sBaseName = StringRegExpReplace($sFileName, "\.[^.\\]+$", "")
|
|
Local $sOutputDir = $g_sSourceDir & "\" & $sBaseName
|
|
|
|
If DirCreate($sOutputDir) = 0 Then
|
|
_Log("[系统] ⚠️ 无法创建目录: " & $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("[" & $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("[" & $sBaseName & "] 🔄 开始转码...")
|
|
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]
|
|
|
|
; 读取新日志
|
|
Local $sNewOutput = StdoutRead($iPID)
|
|
If $sNewOutput <> "" Then
|
|
; 按行分割并加前缀
|
|
Local $aLines = StringSplit(StringStripCR($sNewOutput), @LF, $STR_NOCOUNT)
|
|
For $j = 0 To UBound($aLines) - 1
|
|
If $aLines[$j] <> "" Then
|
|
_Log("[" & $sName & "] " & $aLines[$j])
|
|
EndIf
|
|
Next
|
|
EndIf
|
|
|
|
; 检查是否结束
|
|
If Not ProcessExists($iPID) Then
|
|
Local $iExitCode = @extended ; 注意:Run 返回的 PID 在 ProcessExists 失败后,@extended 是退出码!
|
|
If $iExitCode = 0 Then
|
|
$g_iSuccess += 1
|
|
_Log("[" & $sName & "] ✅ 成功完成")
|
|
Else
|
|
_Log("[" & $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(@CRLF & "🎉 所有转码任务完成!成功处理 " & $g_iSuccess & " 个视频。")
|
|
GUICtrlSetState($g_hBtnStart, $GUI_ENABLE)
|
|
GUICtrlSetState($g_hBtnSelect, $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($sText)
|
|
Local $sCurrent = GUICtrlRead($g_hLogEdit)
|
|
Local $sNew = $sCurrent & $sText & @CRLF
|
|
If StringLen($sNew) > 2000 Then
|
|
$sNew = StringRight($sNew, 2000)
|
|
EndIf
|
|
GUICtrlSetData($g_hLogEdit, $sNew)
|
|
GUICtrlSendMsg($g_hLogEdit, $EM_LINESCROLL, 0, GUICtrlSendMsg($g_hLogEdit, $EM_GETLINECOUNT, 0, 0))
|
|
EndFunc
|
|
|
|
Func _IsInArray($aArray, $sValue)
|
|
For $i = 0 To UBound($aArray) - 1
|
|
If $aArray[$i] = $sValue Then Return True
|
|
Next
|
|
Return False
|
|
EndFunc |