视频转码序号版本

This commit is contained in:
Abin
2026-04-18 16:37:45 +08:00
parent 1148884756
commit 142a045e34
2 changed files with 234 additions and 0 deletions
@@ -0,0 +1,234 @@
; 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
; 新增:用于显示进度序号
Global $g_iTotalFiles = 0
Global $g_iCurrentIndex = 0
; ========== 创建主窗口 ==========
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
$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("❌ 目录中没有找到任何支持的视频文件。")
_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, "^.*\\", "") ; 如 "video.mp4"
Local $sBaseName = StringRegExpReplace($sFileName, "\.[^.\\]+$", "") ; 如 "video"
; 获取视频所在目录(关键!)
Local $sVideoDir = StringLeft($sFilePath, StringInStr($sFilePath, "\", 0, -1) - 1)
; 输出目录 = 视频所在目录 \ 视频名(无扩展名)
Local $sOutputDir = $sVideoDir & "\" & $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("[" & $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("[" & $g_iCurrentIndex & "/" & $g_iTotalFiles & "] [" & $sBaseName & "] 🔄 开始转码...")
_Log(" 路径: " & $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("[" & $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