视频转码更新批量子目录和日志输出精简

This commit is contained in:
Abin
2026-04-18 13:26:50 +08:00
parent d3d91c2539
commit d22fdc9e13
8 changed files with 816 additions and 0 deletions
+73
View File
@@ -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
@@ -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.