Страница 1 из 1

Подсчёт времени воспроизведения медиафайлов

Добавлено: Ср май 06, 2026 23:33
Orion9
Кнопка для подсчёта времени воспроизведения аудио и видео файлов. Реализовано на скриптовом языке плагина Autorun.

Желательно установить последнюю версию плагина Autorun. На данный момент это 2.2.21.3 (15.10.2025)

Инструкция для установки (ниже на странице)
https://total.darkhost.ru/wiki/doku.php?id=ru:autorun

Особенности последней версии. Для версии 2.2.21.3 нет полного архива с дополнительными модулями (субплагинами) и файлами справки, поэтому установку придется проводить в два этапа. Сначала нужно установить версию 2.2.21 от 28.09.2025
https://total.darkhost.ru/files/autorun ... 1_beta.zip
Поверх неё накатить последний модуль от 15.10.2025
https://total.darkhost.ru/files/temp/au ... 251015.zip
Кнопка
TOTALCMD#BAR#DATA
71000

%COMMANDER_EXE%
Подсчёт времени воспроизведения|Alt - Использовать TCMediaInfo.wdx|Ctrl - Глубина каталога = 1|Shift - Отключить фильтр расширений


-1
Duration.aucfg

Код: Выделить всё

Pragma IncludeOnce

# код команды
RegisterCommand 71000 "Duration"

# путь к библиотеке MediaInfo 
Global gMediaInfoX32 = COMMANDER_PATH & "\Ini\Tools\MediaInfo_i386.dll"
Global gMediaInfoX64 = COMMANDER_PATH & "\Ini\Tools\MediaInfo.dll"

# флаги запущенной операции, тип подсказки, лог операции
Global gDurationInfo = 0, gDurationBreak = false, gDurationBaloon = 1, gDurationLog = ""

# фильтр расширений для аудио файлов
Global gFilterAudio = ".aac .ac3 .aif .aiff .aifc .afc .ape .au .snd .cda .dsf .dts .dtswav .dtshd .dtsma .eac3 .flac .fla .m1a .m2a .mka .mpa .mp1 .mp2 .mp3 .mp4 .m4a .m4b .m4r .mod .mpc .mp+ .mpp .oga .ogx .ogg .ra .spx .opus .qoa .svx .8svx .tak .tta .wav .wave .w64 .bwf .rf64 .wma .wv"

# фильтр расширений для видео файлов
Global gFilterVideo = ".avi .wmv .wmp .wm .asf .mpg .mpeg .mpe .m1v .m2v .mpv2 .mp2v .ts .tp .tpr .trp .vob .ifo .ogm .ogv .mp4 .m4v .m4p .m4b .3gp .3gpp .3g2 .3gp2 .mkv .rm .ram .rmvb .rpm .flv .swf .mov .qt .amr .nsv .dpg .m2ts .m2t .mts .dvr-ms .k3g .skm .evo .nsr .amv .divx .webm .wtv .f4v .mxf"

# внутренние переменные для подсказки
Global gBTipWndProc, gBTipWnd = Map(), gBTip = 0
Global gBText = Buffer(1024*4),  gBTool = Buffer(auX64 ? 56 : 40)
Global gBTipX, gBTipY
Global gBTipWP = Callback("BTipWndProc", "hwnd;uint;wparam;lparam")

# запуск операции
Func Duration(lParam)
    # операция уже запущена
    # прервать и выйти при повторном вызове 
    If gDurationInfo > 0 Then
       gDurationBreak = true
       Sleep(250)
       Return
    EndIf
    # создание окна подсказки
    If gDurationBaloon Then
       gBTip = CreateBaloonTip("MediaInfo", 1, 200)
       If gBTip = 0 Then Return MsgBox("Не удалось создать окно подсказки BaloonTip")       
    EndIf 
    # запуск подсчета в отдельном потоке
    RunThread DurationInfo
EndFunc

Func DurationInfo()
    # запоминание времени старта
    Local T1 = GetUptime(), T2 = T1
    
    # состояние клавиш модификаторов    
    Local bCaps = BitAND(DllCall("GetKeyState", "int", 0x14, "short"), 1)    
    Local bCtrl = IsPressed(0x11), bShift = IsPressed(0x10), bAlt = IsPressed(0x12)
    
    # загрузка библиотеки MediaInfo в зависимости от рязрядности ТС
    Static sLib = auX64 ? gMediaInfoX64 : gMediaInfoX32
    Static hLib = DllCall("LoadLibrary", "wstr", sLib, "ptr")
    
    # общий фильтр для медиа файлов    
    Static sExt = gFilterAudio & " " & gFilterVideo

    # модификатор Alt
    # выбор "движка" операции     
    If Not bAlt Then
       If hLib = 0 Then 
          ShowMediaHint("Error LoadLibrary " & sLib)
          Return 
       EndIf
       Static pNew = DllCall("GetProcAddress", "Ptr", hLib, "Str", "MediaInfo_New", "Ptr")
       Static pOpen = DllCall("GetProcAddress", "Ptr", hLib, "Str", "MediaInfo_Open", "Ptr")
       Static pGet = DllCall("GetProcAddress", "Ptr", hLib, "Str", "MediaInfo_Get", "Ptr")
       Static pDel = DllCall("GetProcAddress", "Ptr", hLib, "Str", "MediaInfo_Delete", "Ptr")
       Local hMI = DllCall(pNew, "Ptr")
       If hMI = 0 Then 
          ShowMediaHint("MediaInfo.dll couldn't create new object")
          Return
       Endif
    Else     
       Local obj = Plugin("TCMediaInfo")
       If ERROR <> 0 Then
          ShowMediaHint("TCMediaInfo.wdx plugin error " & ERROR)
          Return
       Endif
    EndIf

    # продолжительность 
    Local sDuration

    # выделение на панели    
    Local aSel = List()
          aSel.Text = GetSelectedItems(3, 0)

    # текущий файл на панели
    Local sPath = RequestCopyDataInfo("SP")    
    Local sName = RequestCopyDataInfo("SN")        
    Local sFile = sPath & sName

    # проверка существования файла    
    If aSel.Count = 0 Then
       If Not FileExist(sFile) Then 
          ShowMediaHint("Файл не существует " & sFile)
          Free(aSel)
          If bAlt Then Free(obj)
          Return
       Endif
    EndIf

    # вывод результата для одиночного файла    
    If aSel.Count = 0 And Not StrPos(FileGetAttr(sFile), "D") Then
       Free(aSel)
       If bAlt Then
          obj.FileName = sFile     
          sDuration = obj.GetValue(0)	# duration
          Free(obj)
       Else
          If DllCall(pOpen, "Ptr", hMI, "Wstr", sFile, "Uint") <> 1 Then
             Return ShowMediaHint("MediaInfo.dll coudn't open file " &  sFile)                            
          EndIf
          sDuration = DllCall(pGet, "Ptr", hMI, "Int", 0, "Int", 0, _
                                    "Wstr", "Duration/String3", "Int", 1, "Int", 0, "Wstr")
       EndIf   
       Return ShowMediaHint("Файл: " & sName & auCRLF & "Длительность: " & sDuration, 1) 
    Endif
    
    gDurationInfo = 1    
    gDurationBreak = false
    ShowMediaHint("Загрузка", 1)    
    
    Local i, j, valid = 0, extlog = false
    Local dirs = 0, files = 0, errs = 0, item = 0, aFiles = List()
    Local depth = (bCtrl ? 1 : 0), ms = 0, vc = "", ac = "", seconds = 0
        
    # загрузка файлов в массив    
    If aSel.Count > 0 Then
       For j = 0 To aSel.Count - 1
          sFile = sPath & aSel[j]
          If FileExist(sFile) Then 
             valid += 1
             aFiles.Add(sFile)
             If StrPos(FileGetAttr(sFile), "D") Then 
                If ListMediaFiles(sFile, aFiles, depth) > 0 Then aFiles.Count = 0
             EndIf   
          EndIf
       Next
       sName = aSel.Count & "/" & valid
    Else
       aFiles.Add(sFile)
       ListMediaFiles(sFile, aFiles, depth)
    EndIf
    
    Free(aSel)
    
    # файлов в массиве больше одного
    If aFiles.Count > 1 Then
       ShowMediaHint("Depth: " & depth & auCRLF & _
                     "Filter: " & (Not bShift ? "Yes" : "No") & auCRLF & _
                     (bAlt ? "TCMediaInfo.wdx" : "MediaInfo.dll") & auCRLF & _
                     "Элементов: " & aFiles.Count & auCRLF & _
                     "Подсчёт времени...", 1)
    EndIf
    
    T2 = GetUptime()
    DurationLogString("Duration.Name:" & sName)    
    DurationLogString("Duration.Items:" & aFiles.Count)

    # обработка массива    
    For i = 0 To aFiles.Count - 1
    
        # расширенный лог
        If extlog Then DurationLogString("Duration.File:" & aFiles[i])

        # количество каталогов        
        If StrPos(FileGetAttr(aFiles[i]), "D") Then
           dirs += 1
           # расширенный лог
           If extlog Then DurationLogString("Duration.Directory:" & aFiles[i])
           Continue 
        EndIf
        
        item += 1
        
        If bAlt Then
           # поле плагина TCMediaInfo
           obj.FileName = aFiles[i]
           ms = obj.GetValue(0,3)	# duration -> milliseconds
        Else
           # расширение текущего файла        
           ext = FileGetExt(aFiles[i])

           # фильтр расширений включен           
           If Not bShift Then
              # проверка текущего расширения
              If Not StrPos(sExt, '.' & ext) Then 
                 DurationLogString("Duration.Filter:" & aFiles[i])
                 Continue
              EndIf
           EndIf

           # ошибка библиотеки при открытии файла           
           If DllCall(pOpen, "Ptr", hMI, "Wstr", aFiles[i], "Uint") <> 1 Then
              errs += 1
              DurationLogString("Duration.Error:" & aFiles[i])
              Continue        
           EndIf
           
           # количество миллисекунд
           ms = DllCall(pGet, "Ptr", hMI, "Int", 0, "Int", 0, _
                              "Wstr", "Duration", "Int", 1, "Int", 0, "Wstr")
           # количество видео дорожек 
           vc = DllCall(pGet, "Ptr", hMI, "Int", 0, "Int", 0, _
                              "Wstr", "VideoCount", "Int", 1, "Int", 0, "Wstr")
           # количество аудио дорожек
           ac = DllCall(pGet, "Ptr", hMI, "Int", 0, "Int", 0, _
                              "Wstr", "AudioCount", "Int", 1, "Int", 0, "Wstr")
           # возможный файл субтитров                            
           If ms > 0 And vc = "" And ac = "" Then
              DurationLogString("Duration.Warning:" & aFiles[i])
              Continue
           EndIf                              
        EndIf

        # суммирование миллисекунд и количества файлов
        If ms > 0 Then
           seconds += ms
           files += 1
        Else
           DurationLogString("Duration.Empty:" & aFiles[i])
        EndIf

        # случилось прерывание операции
        If gDurationBreak = true Or (IsPressed (0x11) And IsPressed (0x1B)) Then
           ShowMediaHint("Операция прервана", 1)
           Sleep(500)           
           If gDurationBaloon Then
              Static buf = Buffer(160), 
              buf.Zero()
              buf.SetStr("Прервано" & Chr(0))
              SendMessage(gBTip, 1057, 3, buf.ptr)              
           EndIf   
           Break
        EndIf

        # обновление статуса каждые полсекунды        
        If Round(GetUptime() - T2, 0) > 500 Then
           ShowMediaHint("Depth: " & depth & auCRLF & _
                         "Filter: " & (Not bShift ? "Yes" : "No") & auCRLF & _
                         (bAlt ? "TCMediaInfo.wdx" : "MediaInfo.dll") & auCRLF & _
                         "Элементов: " & aFiles.Count & auCRLF & _
                         "Обработка: " & i & " из " & aFiles.Count, 1)
           T2 = GetUptime()
         EndIf
    Next
    
    If bAlt Then Free(obj)
    If Not bAlt And hMI <> 0 Then DllCall(pDel, "Ptr", hMI)
    gDurationInfo = 0    
    gDurationBreak = false

    # перевод в секунды
    seconds = Floor(seconds / 1000)

    # вычисление прочих состовляющих    
    Local day = Floor(seconds / 86400)
    Local hour = Floor((seconds - (day * 86400)) / 3600)
    Local min = Floor(((seconds - (day * 86400)) - (hour * 3600)) / 60)
    Local sec = seconds - (day * 86400 + hour * 3600 + min * 60)
    Local total = StrFormat("%02d:%02d:%02d:%02d",day,hour,min,sec)
    Local tl = Round(GetUptime() - T1, 0) / 1000

    # отчет текущей операции
    Local txt = "Имя: " & sName & auCRLF & _
                "Каталогов: " & dirs & auCRLF & _    
                "Элементов: " & aFiles.Count & auCRLF & _
                "Обработано: " & files & " из " & item & auCRLF & _
                "Ошибки DLL: " & errs & auCRLF & _
                "Секунды: " & seconds & auCRLF & _
                "Минуты: " & Round(seconds / 60, 2) & auCRLF & _
                "Часы: " & Round(seconds / 3600, 2) & auCRLF & _
                "Сутки: " & Round(seconds / 86400, 2) & auCRLF & _             
                "Время: " & Chr(0x2211) & " "& total & auCRLF & _
                "Powered by " & (bAlt ? "TCMediaInfo.wdx" : "MediaInfo.dll") & auCRLF & _
                "Время операции: " &  StrFormat("%.3f", tl) & " sec"
    Free(aFiles)
    
    # запись в лог
    DurationLogString(txt & auCRLF & auCRLF)    
    
    # копирование в буфер
    ClipPut(txt)
    
    # отображение отчета
    ShowMediaHint(txt, 1)
EndFunc

Func ListMediaFiles(sPath, ByRef aList, nDepth = 1)
    Local sFile, nAttr
    Local ffd = Buffer(604), bEsc = false
    Local hf = DllCall("FindFirstFileW", "wstr", sPath & "\*.*", "ptr", ffd.Ptr)
    If hf <> 0 then 
        While True 
            sFile = ffd.GetStr(44)         # cFileName
            nAttr = ffd.GetNum(0, "dword") # dwFileAttributes
            If BitAND(nAttr, 16) Then 
               If Not ((sFile = ".") Or (sFile = "..")) Then 
                  aList.Add(sPath & "\" & sFile)
                  If nDepth <> 1 Then
                     ListMediaFiles(sPath & "\" & sFile, aList, nDepth - 1)
                  EndIf   
               EndIf
            Else
               aList.Add(sPath & "\" & sFile)
            Endif     
            If DllCall("FindNextFileW", "handle", hf, "ptr", ffd.Ptr) = 0 Then Break
            If IsPressed (0x1B) Then
                bEsc = true
                Break
            EndIf    
        Wend
        DllCall("FindClose", "handle", hf)
    Endif 
    Free(ffd)
    Return bEsc
EndFunc

Func DurationLogString(LogString)
    gDurationLog &= LogString & auCRLF
EndFunc

Func ShowMediaHint(MediaHint, Persist = false)
    # удаление подсказки тотала
    If gDurationBaloon And gBTip > 0 Then
       Local hTip = WinFind(0, "TToolTip")
       If hTip > 0 Then SendMessage(hTip, 0x0010, 0, 0)
       UpdateBaloonTip(gBTip, MediaHint)
       Return
    EndIf
    # при неиспользовании BaloonTip
    # используется подсказка Autorun    
    SetHintParam("ShowHint", "Font", 11, "Arial")
    #SetHintParam("ShowHint", "BackColor", 0x000000)
    #SetHintParam("ShowHint", "Text", 0xFFFFFF)
    SetHintParam("ShowHint", "DarkBackColor", 0xFF0000)
    SetHintParam("ShowHint", "DarkText", 0xFFFFFF)
    If Persist Then
       ShowHint(MediaHint)    
    Else    
       ShowHint(MediaHint, "", "", 3000, 1)
    EndIf   
    Sleep(10)
    SetHintParam("ShowHint", "Reload")  
EndFunc

Func CreateBaloonTip(TipTitle, TipIcon, TipWidth)
    Static TTM_TRACKACTIVATE   = 1041, _
           TTM_TRACKPOSITION   = 1042, _
           TTM_SETMAXTIPWIDTH  = 1048, _
           TTM_SETTITLE        = 1057, _
           TTM_ADDTOOL         = 1074
    Local hTip
    hTip = DllCall("CreateWindowExW", _
                 "dword", 8, _
                 "wstr", "tooltips_class32", _
                 "wstr", "", _
                 "dword", 0x00000c0, _
                 "int", 0, "int", 0, "int", 0, "int", 0, _
                 "handle", AUTORUN_TCHANDLE, _
                 "handle", 0, "handle", 0, "ptr", 0, "handle")
    If hTip = 0 Then Return 0
    MouseGetPos("gBTipX","gBTipY")
    #gBTipWnd.Set(hTip, MakeInt(gBTipX, gBTipY, 2))
    gBTipWndProc = DllCall("SetWindowLong" & (auX64 ? "PtrW" : "W"), _
                           "hwnd", hTip, "int", -4, "long_ptr", gBTipWP.Ptr, "ptr")
    Local buf = Buffer(160)
    buf.Zero()
    buf.SetStr(TipTitle & Chr(0))
    SendMessage(hTip, TTM_SETTITLE, TipIcon, buf.ptr)
    gBText.Zero()
    gBText.SetStr("Инициализация" & Chr(0))    
    gBTool.Zero()                              # TTTOOLINFO
    If auX64 Then
       gBTool.SetNum(0, "uint", gBTool.size, _
                        "uint", 0x00a0, _
                        "hwnd", AUTORUN_TCHANDLE, _
                        "uint_ptr", 0)
       gBTool.SetNum(48, "ptr", gBText.ptr)    
    Else
       gBTool.SetNum(0, "uint", gBTool.size, _
                        "uint", 0x00a0, _
                        "hwnd", AUTORUN_TCHANDLE, _
                        "uint_ptr", 0, _
                        "long", 0, _
                        "long", 0, _
                        "long", 0, _
                        "long", 0, _
                        "ptr", 0, _
                        "ptr", gBText.ptr)
    EndIf
    SendMessage(hTip, TTM_SETMAXTIPWIDTH, 0, TipWidth)
    SendMessage(hTip, TTM_ADDTOOL, 0, gBTool.ptr)
    SendMessage(hTip, TTM_TRACKACTIVATE, 1, gBTool.ptr)
    SendMessage(hTip, TTM_TRACKPOSITION, 0, MakeInt(gBTipX, gBTipY, 0))
    Free(buf)
    Return hTip 
EndFunc

Func UpdateBaloonTip(hTip, TipText)
    Static TTM_UPDATETIPTEXT = 1081
    gBText.Zero()
    gBText.SetStr(TipText & Chr(0)) 
    SendMessage(gBTip, TTM_UPDATETIPTEXT, 0, gBTool.ptr)
EndFunc

Func BTipWndProc(hWnd, uMsg, wParam, lParam)
    Static IsDrag = 0, _
           WM_CREATE       = 0x0001, _
           WM_DESTROY      = 0x0002, _
           WM_SHOWWINDOW   = 0x0018, _     
           MK_LBUTTON      = 0x0001, _
           WM_MOUSEMOVE    = 0x0200, _
           WM_LBUTTONDOWN  = 0x0201, _
           WM_LBUTTONUP    = 0x0202, _
           WM_RBUTTONUP    = 0x0205, _           
           TTM_TRACKACTIVATE = 1041, _
           TTM_TRACKPOSITION = 1042
    Static SM_CXDRAG  = 68, _
           SM_CYDRAG  = 69, _
           DragWidth  = GetSystemMetrics(SM_CXDRAG), _
           DragHeight = GetSystemMetrics(SM_CYDRAG), _
           StartX     = 0, _
           StartY     = 0
    Local x, y
    Static tx, ty, buf = Buffer(8)
    Switch uMsg
       Case WM_SHOWWINDOW
          If wParam = 0 Then DllCall("DestroyWindow", "handle", hWnd)
       Case WM_DESTROY
          If gDurationInfo Then
             gDurationBreak = true
             MsgBox("Операция отменена")
             Return 0
          EndIf
       Case WM_LBUTTONDOWN
          If IsDrag = 0 Then
             buf.Zero()
             x = BitAND(lParam, 0xFFFF)
             y = BitAND(BitShift(lParam,16), 0xFFFF)
             StartX = x
             StartY = y
             buf.SetNum(0, "int", x, "int", y)
             DllCall("ClientToScreen", "hwnd", hWnd, "ptr", buf.ptr)
             tx = buf.GetNum(0)
             ty = buf.GetNum(4)
             WinGetPos("gBTipX", "gBTipY", "", "", hWnd)             
             IsDrag = 1
             DllCall("SetCapture", "hwnd", hWnd, "hwnd")
          EndIf          
       Case WM_LBUTTONUP
          If IsDrag = 1 Then
             WinGetPos("gBTipX", "gBTipY", "", "", hWnd)
             IsDrag = 0
             DllCall("ReleaseCapture")
          Else   
             DllCall("DestroyWindow", "handle", hWnd)             
          EndIf  
       Case WM_MOUSEMOVE
          If IsDrag = 1 And BitAND(wParam, MK_LBUTTON) Then
             x = BitAND(lParam, 0xFFFF)
             y = BitAND(BitShift(lParam,16), 0xFFFF)
             If Abs(x - startX) > DragWidth Or Abs(y - startY) > DragHeight Then
                buf.Zero()             
                buf.SetNum(0, "int", x, "int", y)
                DllCall("ClientToScreen", "hwnd", hWnd, "ptr", buf.ptr)
                x = buf.GetNum(0)
                y = buf.GetNum(4)                 
                SendMessage(hWnd, TTM_TRACKPOSITION, 0, MakeInt(gBTipX+(x-tx), gBTipY+(y-ty), 0))
             EndIf   
          EndIf
       Case WM_RBUTTONUP
            BTipShowMenu(hWnd)
    EndSwitch
    Return DllCall("CallWindowProcW", _
                   "ptr", gBTipWndProc, "hwnd", hWnd, "uint", uMsg, "wparam", wParam, "lparam", lParam)
EndFunc

Func BTipShowMenu(hWnd)
    ShowPopupMenu /D /F /I:0 "BTipCreateMenu"
EndFunc

Func BTipCreateMenu()
    Local txt
    txt &= 'MENUITEM "Лог операций", em_aucmd ' & (gDurationLog <> "" ?  "" : "/D") & ' -1 DurationText' & auCRLF
    Return txt
EndFunc

Func DurationText()
    Local sLogFile = TEMP & "\duration.txt"
    FileWrite(sLogFile, gDurationLog)
    ShellExec(sLogFile)
EndFunc
Модуль можно подключить к основной конфигурации плагина строкой

Код: Выделить всё

Pragma Include %COMMANDER_PATH%\Ini\Autorun\Duration.aucfg
Пример файла autorun.cfg чистого плагина
Показать

Код: Выделить всё

Pragma AutorunBlockUnload

LoadLibrary Plugins\Autorun_Tweaks.dll
LoadLibrary Plugins\Autorun_Sysinfo.dll
LoadLibrary Plugins\Autorun_Runtime.dll
LoadLibrary Plugins\Autorun_Process.dll

Pragma Include %COMMANDER_PATH%\wdx\Autorun\Duration.aucfg

Pragma AutorunFinalizeSection
В заголовке модуля необходимо задать путь к файлам библиотеки MediaInfo. Кнопка может также работать с плагином TCMediaInfo, если он установлен и настроен по дефолту.

Модификатор Shift позволяет отключить фильтр расширений, указанных в заголовке модуля. По умолчанию фильтр включен, т.к. он ускоряет операцию подсчета (MediaInfo не приходится открывать каждый файл в каталоге). Однако бывают случаи, когда нужно обработать все файлы. Если файл при этом не содержит сведений о времени воспроизведения, в лог заносится "Empty".

При отключенном фильтре текстовые файлы субтитров могут возвращать информацию о времени воспроизведения. В режиме MediaInfo.dll такие файлы в расчет не принимаются (в лог записывается "Warning"), но если по какой-то причине нужно посчитать длительность таких файлов, можно использовать режим плагина TCMediaInfo.wdx через запуск с Alt. Это возможно, поскольку плагин TCMediaInfo.wdx имеет собственный файл конфигурации и собственные фильтры.

Операцию можно прервать в любой момент либо повторным запуском, либо закрытием окна подсказки, либо комбинацией Ctrl+ESC. В первом и последнем случае отобразится промежуточный результат. Окно подсказки можно также закрыть двойным кликом по самой подсказке. Правый клик открывает меню лога операций.

Можно держать открытыми несколько окон подсказки и перемещать их по экрану.
Скрин с примером
Изображение
Ошибки и недочеты могут быть, но в основном кнопка рабочая. Надеюсь, кому-то пригодится.