视频转码更新批量子目录和日志输出精简
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
#include <File.au3>
|
||||
#include <MsgBoxConstants.au3>
|
||||
|
||||
; ==============================
|
||||
; 脚本功能:批量重命名 index.m3u8 为 playlist.m3u8
|
||||
; 作者:AI助手
|
||||
; 日期:2026年1月7日
|
||||
; ==============================
|
||||
|
||||
; 1. 选择目标目录
|
||||
Local $sSourceDir = FileSelectFolder("请选择要处理的根目录", "")
|
||||
If @error Then
|
||||
MsgBox($MB_ICONINFORMATION, "操作取消", "您未选择任何目录,程序将退出。")
|
||||
Exit
|
||||
EndIf
|
||||
|
||||
; 2. 检查目录是否存在
|
||||
If Not FileExists($sSourceDir) Then
|
||||
MsgBox($MB_ICONERROR, "错误", "所选目录不存在!")
|
||||
Exit
|
||||
EndIf
|
||||
|
||||
; 3. 初始化计数器
|
||||
Local $iRenamedCount = 0
|
||||
Local $iErrorCount = 0
|
||||
|
||||
; 4. 遍历目录及子目录
|
||||
Local $aFileList = _FileListToArrayRec($sSourceDir, "index.m3u8", $FLTAR_FILES, $FLTAR_RECUR, $FLTAR_NOSORT, $FLTAR_FULLPATH)
|
||||
If @error Then
|
||||
If @error = 1 Then
|
||||
MsgBox($MB_ICONINFORMATION, "完成", "在指定目录中未找到任何 index.m3u8 文件。")
|
||||
Else
|
||||
MsgBox($MB_ICONERROR, "错误", "遍历文件时发生错误:" & @error)
|
||||
EndIf
|
||||
Exit
|
||||
EndIf
|
||||
|
||||
; 5. 处理每个找到的文件
|
||||
For $i = 1 To $aFileList[0]
|
||||
Local $sOldPath = $aFileList[$i]
|
||||
Local $sNewPath = StringReplace($sOldPath, "index.m3u8", "playlist.m3u8")
|
||||
|
||||
; 检查目标文件名是否已存在
|
||||
If FileExists($sNewPath) Then
|
||||
; 添加数字后缀避免冲突 (例如: playlist(1).m3u8)
|
||||
Local $iSuffix = 1
|
||||
Local $sTempNewPath = $sNewPath
|
||||
While FileExists($sTempNewPath)
|
||||
$sTempNewPath = StringReplace($sNewPath, ".m3u8", "(" & $iSuffix & ").m3u8")
|
||||
$iSuffix += 1
|
||||
WEnd
|
||||
$sNewPath = $sTempNewPath
|
||||
EndIf
|
||||
|
||||
; 执行重命名
|
||||
If FileMove($sOldPath, $sNewPath, 1) Then ; 1 表示覆盖目标文件(如果已存在且我们处理了冲突)
|
||||
$iRenamedCount += 1
|
||||
ConsoleWrite("成功重命名: " & $sOldPath & " -> " & $sNewPath & @CRLF)
|
||||
Else
|
||||
$iErrorCount += 1
|
||||
ConsoleWrite("重命名失败: " & $sOldPath & " (错误代码: " & @error & ")" & @CRLF)
|
||||
EndIf
|
||||
Next
|
||||
|
||||
; 6. 显示结果
|
||||
Local $sMsg = "处理完成!" & @CRLF & @CRLF & _
|
||||
"成功重命名: " & $iRenamedCount & " 个文件" & @CRLF & _
|
||||
"重命名失败: " & $iErrorCount & " 个文件"
|
||||
|
||||
MsgBox($MB_ICONINFORMATION, "结果", $sMsg)
|
||||
|
||||
; 可选:打开控制台窗口查看详细日志(如果使用 SciTE 编辑器运行)
|
||||
; 如果你想在独立的命令行窗口看到输出,请确保脚本是通过编译或从命令行运行的。
|
||||
@@ -0,0 +1,291 @@
|
||||
#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
|
||||
#AutoIt3Wrapper_Icon=icon.ico
|
||||
#AutoIt3Wrapper_Outfile=VideoFolderRenamer.exe
|
||||
#AutoIt3Wrapper_Compression=4
|
||||
#AutoIt3Wrapper_UseUpx=y
|
||||
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
|
||||
|
||||
#include <GUIConstantsEx.au3>
|
||||
#include <WindowsConstants.au3>
|
||||
#include <EditConstants.au3>
|
||||
#include <FileConstants.au3>
|
||||
#include <File.au3>
|
||||
#include <Array.au3>
|
||||
#include <Excel.au3>
|
||||
#include <Inet.au3>
|
||||
#include <MsgBoxConstants.au3>
|
||||
|
||||
; ============================================
|
||||
; 全局变量(兼容 AutoIt 3.3.18.0)
|
||||
; ============================================
|
||||
Global $g_sSelectedPath = ""
|
||||
Global $g_aMP4Files[0] ; 将在 _FileListToArrayRec 后赋值
|
||||
Global $g_aResults[1][4] ; 预分配二维数组,列: [MP4文件名, MP4路径, 原文件夹名, 新UUID]
|
||||
Global $g_iResultCount = 0
|
||||
Global $g_bProcessing = False
|
||||
|
||||
; ============================================
|
||||
; 主程序入口
|
||||
; ============================================
|
||||
Main()
|
||||
|
||||
Func Main()
|
||||
; 创建GUI界面
|
||||
Local $hGUI = GUICreate("视频文件夹重命名工具 v1.0", 600, 400, -1, -1)
|
||||
|
||||
; 选择目录按钮
|
||||
Local $hBtnSelectDir = GUICtrlCreateButton("📁 选择视频目录", 20, 20, 150, 40)
|
||||
GUICtrlSetFont(-1, 10, 600)
|
||||
|
||||
; 开始执行按钮
|
||||
Local $hBtnStart = GUICtrlCreateButton("▶️ 开始执行", 200, 20, 150, 40)
|
||||
GUICtrlSetFont(-1, 10, 600)
|
||||
GUICtrlSetState(-1, $GUI_DISABLE) ; 初始禁用
|
||||
|
||||
; 日志显示框
|
||||
Local $hLogEdit = GUICtrlCreateEdit("", 20, 80, 560, 280, BitOR($ES_AUTOVSCROLL, $ES_READONLY, $WS_VSCROLL, $ES_MULTILINE))
|
||||
GUICtrlSetFont(-1, 9, 400, 0, "Consolas")
|
||||
GUICtrlSetBkColor(-1, 0xFFFFFF)
|
||||
GUICtrlSetLimit(-1, 100000)
|
||||
|
||||
GUISetState(@SW_SHOW, $hGUI)
|
||||
|
||||
; 消息循环
|
||||
While 1
|
||||
Switch GUIGetMsg()
|
||||
Case $GUI_EVENT_CLOSE
|
||||
ExitLoop
|
||||
|
||||
Case $hBtnSelectDir
|
||||
; 选择目录
|
||||
Local $sPath = FileSelectFolder("请选择包含MP4视频的文件夹", "")
|
||||
If $sPath <> "" Then
|
||||
$g_sSelectedPath = $sPath
|
||||
_Log($hLogEdit, "✓ 已选择目录: " & $g_sSelectedPath)
|
||||
GUICtrlSetState($hBtnStart, $GUI_ENABLE)
|
||||
EndIf
|
||||
|
||||
Case $hBtnStart
|
||||
If $g_sSelectedPath = "" Then
|
||||
MsgBox($MB_ICONWARNING, "警告", "请先选择视频目录!")
|
||||
ContinueCase
|
||||
EndIf
|
||||
|
||||
; 禁用按钮,防止重复点击
|
||||
GUICtrlSetState($hBtnSelectDir, $GUI_DISABLE)
|
||||
GUICtrlSetState($hBtnStart, $GUI_DISABLE)
|
||||
$g_bProcessing = True
|
||||
|
||||
; 清空日志和结果
|
||||
GUICtrlSetData($hLogEdit, "")
|
||||
$g_iResultCount = 0
|
||||
ReDim $g_aResults[1][4] ; 重置结果数组
|
||||
|
||||
; 扫描MP4文件
|
||||
_Log($hLogEdit, "🔍 正在扫描目录中的MP4文件...")
|
||||
Local $aTemp = _FileListToArrayRec($g_sSelectedPath, "*.mp4", $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] ; ✅ 正确创建空数组(UBound = 0)
|
||||
EndIf
|
||||
|
||||
If UBound($g_aMP4Files) = 0 Or (UBound($g_aMP4Files) = 1 And $g_aMP4Files[0] = 0) Then
|
||||
_Log($hLogEdit, "❌ 未找到任何MP4文件!")
|
||||
GUICtrlSetState($hBtnSelectDir, $GUI_ENABLE)
|
||||
GUICtrlSetState($hBtnStart, $GUI_ENABLE)
|
||||
$g_bProcessing = False
|
||||
ContinueCase
|
||||
EndIf
|
||||
|
||||
_Log($hLogEdit, "✓ 找到 " & UBound($g_aMP4Files) & " 个MP4文件")
|
||||
_Log($hLogEdit, "")
|
||||
|
||||
; 处理每个MP4文件
|
||||
Local $iSuccessCount = 0
|
||||
Local $iFailCount = 0
|
||||
|
||||
For $i = 0 To UBound($g_aMP4Files) - 1
|
||||
If Not $g_bProcessing Then ExitLoop
|
||||
|
||||
Local $sMP4Path = $g_aMP4Files[$i]
|
||||
Local $sMP4Name = StringRegExpReplace($sMP4Path, "^.+[\\/]", "")
|
||||
Local $sMP4BaseName = StringRegExpReplace($sMP4Name, "\.mp4$", "", 1)
|
||||
Local $sMP4Dir = StringRegExpReplace($sMP4Path, "[\\/][^\\/]+$", "")
|
||||
|
||||
; 检查同名文件夹是否存在
|
||||
Local $sFolderName = $sMP4BaseName
|
||||
Local $sFolderPath = $sMP4Dir & "\" & $sFolderName
|
||||
|
||||
If DirGetSize($sFolderPath) = -1 Then
|
||||
_Log($hLogEdit, "⚠️ [" & ($i + 1) & "/" & UBound($g_aMP4Files) & "] " & $sMP4Name & " - 未找到同名文件夹")
|
||||
$iFailCount += 1
|
||||
ContinueLoop
|
||||
EndIf
|
||||
|
||||
; 检查playlist.m3u8是否存在
|
||||
Local $sM3U8Path = $sFolderPath & "\playlist.m3u8"
|
||||
If Not FileExists($sM3U8Path) Then
|
||||
_Log($hLogEdit, "⚠️ [" & ($i + 1) & "/" & UBound($g_aMP4Files) & "] " & $sMP4Name & " - 文件夹中不存在playlist.m3u8")
|
||||
$iFailCount += 1
|
||||
ContinueLoop
|
||||
EndIf
|
||||
|
||||
; 获取新的UUID
|
||||
_Log($hLogEdit, "[" & ($i + 1) & "/" & UBound($g_aMP4Files) & "] " & $sMP4Name & " - 正在获取新UUID...")
|
||||
Local $sNewUUID = _GetNewUUID()
|
||||
|
||||
If $sNewUUID = "" Then
|
||||
_Log($hLogEdit, "❌ [" & ($i + 1) & "/" & UBound($g_aMP4Files) & "] " & $sMP4Name & " - 获取UUID失败")
|
||||
$iFailCount += 1
|
||||
ContinueLoop
|
||||
EndIf
|
||||
|
||||
; 重命名文件夹
|
||||
Local $sNewFolderPath = $sMP4Dir & "\" & $sNewUUID
|
||||
|
||||
If DirMove($sFolderPath, $sNewFolderPath) Then
|
||||
_Log($hLogEdit, "✓ [" & ($i + 1) & "/" & UBound($g_aMP4Files) & "] " & $sMP4Name & " - 重命名成功: " & $sFolderName & " → " & $sNewUUID)
|
||||
|
||||
; 扩容结果数组(如果需要)
|
||||
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($hLogEdit, "❌ [" & ($i + 1) & "/" & UBound($g_aMP4Files) & "] " & $sMP4Name & " - 重命名失败")
|
||||
$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($hLogEdit, "")
|
||||
_Log($hLogEdit, "📊 正在生成Excel报告...")
|
||||
If _GenerateExcelReport() Then
|
||||
_Log($hLogEdit, "✓ Excel报告已生成: " & @ScriptDir & "\VideoFolderMapping.xlsx")
|
||||
Else
|
||||
_Log($hLogEdit, "❌ Excel报告生成失败")
|
||||
EndIf
|
||||
EndIf
|
||||
|
||||
; 显示统计信息
|
||||
_Log($hLogEdit, "")
|
||||
_Log($hLogEdit, "========================================")
|
||||
_Log($hLogEdit, "📊 处理完成!")
|
||||
_Log($hLogEdit, " ✓ 成功: " & $iSuccessCount & " 个")
|
||||
_Log($hLogEdit, " ⚠️ 失败: " & $iFailCount & " 个")
|
||||
_Log($hLogEdit, " 📁 总计: " & UBound($g_aMP4Files) & " 个MP4文件")
|
||||
_Log($hLogEdit, "========================================")
|
||||
|
||||
; 恢复按钮状态
|
||||
GUICtrlSetState($hBtnSelectDir, $GUI_ENABLE)
|
||||
GUICtrlSetState($hBtnStart, $GUI_ENABLE)
|
||||
$g_bProcessing = False
|
||||
|
||||
EndSwitch
|
||||
WEnd
|
||||
|
||||
GUIDelete($hGUI)
|
||||
EndFunc
|
||||
|
||||
; ============================================
|
||||
; 获取新的UUID
|
||||
; ============================================
|
||||
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($hEdit, $sText)
|
||||
Local $sCurrent = GUICtrlRead($hEdit)
|
||||
GUICtrlSetData($hEdit, $sCurrent & $sText & @CRLF)
|
||||
|
||||
; 滚动到最后一行
|
||||
Local $iLines = GUICtrlSendMsg($hEdit, $EM_GETLINECOUNT, 0, 0)
|
||||
GUICtrlSendMsg($hEdit, $EM_LINESCROLL, 0, $iLines)
|
||||
EndFunc
|
||||
|
||||
; ============================================
|
||||
; 生成Excel报告
|
||||
; ============================================
|
||||
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.
@@ -0,0 +1,222 @@
|
||||
; 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 $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
|
||||
|
||||
; 填充待处理队列($aFiles[1] 到 $aFiles[$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)
|
||||
|
||||
; 提取文件名(不含路径和扩展名)
|
||||
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("[" & $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]
|
||||
|
||||
; 静默读取输出流(避免 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
|
||||
Binary file not shown.
@@ -0,0 +1,230 @@
|
||||
; 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 $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
|
||||
|
||||
; 填充待处理队列($aFiles[1] 到 $aFiles[$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)
|
||||
|
||||
; 提取文件名(不含路径和扩展名)
|
||||
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("[" & $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
|
||||
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
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user