Files
tools/压缩包/au3脚本/视频转码.au3
T

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