Страница 232 из 243

Добавлено: Ср май 13, 2026 23:08
Orion9
AkulaBig писал(а):Попробую, как время выберу
Я просто подумал, что если такая скорость устроит, то нет смысла заморачиваться с чтением из stdout. Выигрыш все-равно будет минимальный. Болван уже дал решение, как считать данные с чужой консоли, но адаптировать это решение под Autorun лень, да и нет смысла. Прирост в скорости и так очевиден — через временный файл работает быстро.

[cut]Если чужой процесс уже вывел текст в окно консоли, этот текст находится в экранном буфере (Screen Buffer). Чтобы прочитать его через API, используется AttachConsole в связке с GetStdHandle и ReadConsoleOutputCharacterW.Ниже представлен рабочий скрипт для AutoHotkey v2, который подключается к консоли чужого процесса по его PID, считывает весь текст из его экранного буфера и отключается.

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

#Requires AutoHotkey v2.0

; --- ТЕСТОВЫЙ ЗАПУСК (для демонстрации) ---
; Запустим скрытый cmd.exe, который сделает вывод, чтобы нам было что читать
DetectHiddenWindows(true)
Run(A_ComSpec " /c dir C:", , "Hide", &targetPID)
WinWait("ahk_pid " targetPID)
Sleep(500) ; Даем команде dir время отработать и заполнить буфер

; --- ОСНОВНОЙ ВЫЗОВ ФУНКЦИИ ---
textOutput := ReadConsoleBufferByPID(targetPID)
MsgBox(textOutput, "Результат чтения чужой консоли")

; Закрываем тестовый процесс
ProcessClose(targetPID)
ExitApp


; --- ФУНКЦИЯ ДЛЯ ЧТЕНИЯ БУФЕРА ЧУЖОГО ПРОЦЕССА ---
ReadConsoleBufferByPID(pid) {
    ; 1. Отключаемся от нашей текущей консоли (если она была)
    DllCall("FreeConsole")

    ; 2. Привязываемся к консоли чужого процесса по его PID
    if !DllCall("AttachConsole", "UInt", pid) {
        err := DllCall("GetLastError")
        return "Ошибка: Не удалось вызвать AttachConsole. Код ошибки: " err
    }

    ; 3. Получаем дескриптор буфера вывода (STD_OUTPUT_HANDLE = -11)
    hStdOut := DllCall("GetStdHandle", "Int", -11, "Ptr")
    if (hStdOut == -1 || hStdOut == 0) {
        DllCall("FreeConsole")
        return "Ошибка: Не удалось получить GetStdHandle."
    }

    ; 4. Получаем размеры экранного буфера консоли
    ; Структура CONSOLE_SCREEN_BUFFER_INFO занимает 22 байта
    csbi := Buffer(22, 0)
    if !DllCall("GetConsoleScreenBufferInfo", "Ptr", hStdOut, "Ptr", csbi) {
        err := DllCall("GetLastError")
        DllCall("FreeConsole")
        return "Ошибка: Не удалось получить инфо о буфере. Код ошибки: " err
    }

    ; Читаем размеры буфера из структуры (размер по оси X и Y)
    dwSizeX := NumGet(csbi, 0, "Short")
    dwSizeY := NumGet(csbi, 2, "Short")
    
    ; Общее количество символов для чтения
    totalChars := dwSizeX * dwSizeY
    if (totalChars <= 0) {
        DllCall("FreeConsole")
        return ""
    }

    ; 5. Выделяем буфер под текст (2 байта на символ для UTF-16/Unicode)
    textBuffer := Buffer((totalChars + 1) * 2, 0)

    ; Координаты начала чтения (0, 0) — упакованы в 4 байта (DWORD)
    dwReadCoord := 0 

    ; 6. Читаем символы из консоли
    bytesRead := 0
    if !DllCall("ReadConsoleOutputCharacterW", 
        "Ptr", hStdOut, 
        "Ptr", textBuffer, 
        "UInt", totalChars, 
        "UInt", dwReadCoord, 
        "UInt*", &bytesRead := 0) 
    {
        err := DllCall("GetLastError")
        DllCall("FreeConsole")
        return "Ошибка при чтении буфера ReadConsoleOutputCharacter. Код: " err
    }

    ; 7. Обязательно освобождаем чужую консоль
    DllCall("FreeConsole")

    ; 8. Преобразуем буфер в строку AHK
    rawText := StrGet(textBuffer, bytesRead, "UTF-16")

    ; Форматируем текст: разбиваем сплошную строку обратно на строки по ширине консоли
    formattedText := ""
    Loop dwSizeY {
        offset := ((A_Index - 1) * dwSizeX) + 1
        line := SubStr(rawText, offset, dwSizeX)
        ; Убираем лишние пробелы в конце строки, которые консоль добавляет для заполнения экрана
        line := RTrim(line, " `t`r`n") 
        if (line != "")
            formattedText .= line "`n"
    }

    return RTrim(formattedText, "`n")
}
[/cut]
Кстати, в Autohotkey можно попробовать считать данные StdOut через ComObject. Не уверен, что получится с ExifTool, но был такой пример в этом топике. Там, кстати, и перевод из досовской кодировки используется. Я вам, наверное, ерунду написал про

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

buf.SetStr(ansi, 0, nSize, "CP:866")
utf8 = buf.GetStr(0, nSize, "CP:65001")

Добавлено: Чт май 14, 2026 09:13
AkulaBig
Orion9
Нашел, что плагин ExifToolWDX использует такую командную строку:

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

C:\PortablePrograms\FileManagers\TotalCommanderPortable\Plugins\wlx\ExifToolView\exiftool.exe -charset FileName=UTF8 -stay_open true -@ -
Нашел описание параметра -@ -:

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

Примечание. При записи аргументов в файл на диске после записи «-execute\n» сделана задержка до 0,01 секунды, прежде чем exiftool начнёт обработку команды. Этой задержки можно избежать, отправив сигнал CONT процессу exiftool сразу после записи «-execute\n». (Соответствующая задержка отсутствует при записи аргументов через конвейер с «-@ -», поэтому сигнал не требуется при использовании этого метода.)
И запутался совсем. Как использовать эту опцию? Почему нет временного файла? Не разбирались с этим?
Интересно, что плагин ExifToolView не запускает ExifTool. Похоже сам обрабатывает ее библиотеки. Не будем тревожить автора для разъяснений. Просто для инфы.

Добавлено спустя 50 минут:
Orion9 писал(а):нет смысла заморачиваться с чтением из stdout.
Не в этом дело. В вашем примере не решается главный вопрос wdx-плагина. Однократный запуск ET и многократное считывание данных.
Но ваш код натолкнул меня на мысль:

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

2. Привязываемся к консоли чужого процесса по его PID
Может быть мне надо такое подключение выполнить.
Сейчас я переписал одноразовый запуск ET в au3. Думал это поможет. Но нет, так и не могу получить данные. Возможно это как-раз из-за того, что консоли вывода разные. Хоть я и использую ПИД одной копии ET. Но похоже это не гарантирует, что я данные из ее консоли получаю. И временный файл данных эту проблему никак не решит.

Добавлено спустя 21 минуту:

А ведь кажется можно просто проверить, запущена ли ET и в wdx. Буду тестить.

Добавлено: Чт май 14, 2026 12:33
Orion9
AkulaBig писал(а):Как использовать эту опцию? Почему нет временного файла? Не разбирались с этим?
Читал, но написано там скудно, без примеров и хорошего объяснения. Болван реально лучше в этом плане помогает.

Очевидно опция -@ - предназначена для прямой работы с stdin и stdout, а сигнал CONT процессу, видимо, только для линуха.

Но как я писал выше, я бы не стал с этим заморачиваться. Работа через временный файл дает хороший прирост скорости (20-40 раз, и чем больше файлов для обработки, тем выше).
AkulaBig писал(а):В вашем примере не решается главный вопрос wdx-плагина. Однократный запуск ET и многократное считывание данных.
Так я и не стал дальше мучить болвана по озвученной выше причине. Но все можно сделать при желании. Другое дело, что это не решает проблемы Autorun с отображением набора колонок и связанных с этим глюками. Просто с выделенными файлами работает все хорошо.

Добавлено: Чт май 14, 2026 17:16
Orion9
Пришлось поставить charset filename=Russian для решения проблемы с русскими именами файлов. Это не решает проблемы юникодных файловых имен при передаче их в качестве параметров Exiftool (там вообще с этим довольно шняжно все), но хоть так.

Это касается только вызова через ProcessExecGetOutput, через -@ arg_file работает все нормально (еще один плюс использования опции -stay_open).

Можно еще ускорить работу через временный файл, заменив Sleep(20) на Sleep(10), это дает эффект.

Добавлено: Чт май 14, 2026 20:35
AkulaBig
Orion9 писал(а):Можно еще ускорить работу через временный файл
В общем я закончил тестирование. Как выше и писал, опция -stay_open совершенно не пригодна для работы в wdx-плагине, так как ET приходится запускать для каждого файла. По крайней мере с файлом аргументов. А про опцию -@ - мы ничего не знаем. Хотя и так понятно, что и она работу не ускоряет, судя по существующему wdx-плагину. Она действует только в пределах текущего файла.

Добавлено спустя 35 минут:

Забыл написать. Для каждого файла требуется новый запуск ET, потому что ПИД у них разный. Так что обращение к одному экземпляру ET не происходит. Конечно можно попытаться обрабатывать список файлов. Ведь в файле аргументов находятся данные о всех файлах в директории. Но не понятно, можно ли так сделать и как обрабатывать это в wdx-плагине. Вот тогда скорость конечно-бы повысилась.

Добавлено: Пт май 15, 2026 07:07
AkulaBig
Orion9
Провел тестирование на скорость. Мой набор файлов, в котором присутствуют запароленные pdf и другие тяжелые файлы, мой алгоритм обрабатывает за 21,5 с. Плагин ExifToolWDX за 3 мин 30 с. Вот что значит обрабатывать с заданным временным интервалом, что требует -stay-open. Один запароленный pdf плагин обрабатывал минуты 2.
Единственное, что поля, которые можно прочитать (даты), плагин вывел, а у меня не выводятся. То-есть просто сразу пропустились.

Добавлено: Пт май 15, 2026 11:29
Orion9
AkulaBig писал(а):опция -stay_open совершенно не пригодна для работы в wdx-плагине, так как ET приходится запускать для каждого файла.
Почему? Ничто вроде не мешает запустить exiftool.exe один раз и далее взаимодействовать с ним через файл аргументов.
AkulaBig писал(а): А про опцию -@ - мы ничего не знаем.
Болван тоже не знает?
AkulaBig писал(а):Хотя и так понятно, что и она работу не ускоряет, судя по существующему wdx-плагину. Она действует только в пределах текущего файла.
Это сейчас так. Но плагин не обновлялся 10 лет. Скорее всего автор использовал опцию -stay_open для ускорения, но с последними версиями exiftool.exe она не работает. Для правильной проверки нужно найти версию 10-летней давности.

Добавлено: Пт май 15, 2026 11:47
AkulaBig
Orion9 писал(а):Почему?
Я написал выше. При чтении данных используется ИД процесса. А он разный для каждого запуска в wdx-файле. Запускаем ET, читаем его ИД в коде получения данных. И для каждого обрабатываемого файла в wdx-плагине он разный.
Orion9 писал(а):Болван тоже не знает?
А вы каким пользуетесь? Я каким-то попробовал, сроду столько инфы, сколько выкладываете вы, не получаю.
Ну и я вам данные тестирования скорости плагинов выложил. При использовании -stay_open необходимо задать временной интервал. И это выливается в дичайшее замедление скорости на реальных файлах.

Добавлено: Пт май 15, 2026 12:08
Orion9
AkulaBig писал(а):Я написал выше. При чтении данных используется ИД процесса.
Зачем PID, когда взаимодействие с процессом происходит через файл. Один файл для ввода, другой для вывода.
AkulaBig писал(а):А вы каким пользуетесь?
Там сейчас этих болванов развелось, как ... Выбирайте любой — не прогадаете. Я обычный Google AI юзаю. Простой поиск забыл, как кошмарный сон.

[cut]Использование значения -@ - (с дефисом в конце) приказывает ExifTool читать список аргументов прямо из стандартного потока ввода (stdin), а не из физического файла на диске.Для реализации этого в AutoHotkey v2 необходимо использовать объект WshShell через COM-интерфейс, так как стандартная функция Run в AHK не умеет напрямую перенаправлять потоки stdin/stdout.🤖 Скрипт AHK v2 с передачей аргументов через StdinЭтот скрипт передает команды в ExifTool в режиме реального времени через оперативную память и сразу возвращает текстовый ответ программы в переменную AHK.

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

#Requires AutoHotkey v2.0

; 1. Конфигурация путей
exiftoolPath := "C:\Windows\exiftool.exe"
targetFile := "C:\Photos\image.jpg"

; 2. Формируем список аргументов (строго по одному на строку)
; Опция -S делает вывод компактным (Тег: Значение)
argsContent := 
(
"-S"
"-Artist=Новый Автор"
"-Copyright=2026 Студия"
)
; Добавляем путь к файлу в конец списка команд
argsContent .= "`n" targetFile

; 3. Запускаем процесс ExifTool с флагами чтения из stdin (-@ -)
shell := ComObject("Wscript.Shell")
exec := shell.Exec('"' exiftoolPath '" -@ -')

; 4. Отправляем команды в поток stdin программы
exec.StdIn.WriteLine(argsContent)
exec.StdIn.Close() ; Закрываем поток, чтобы ExifTool понял, что передача команд окончена, и начал выполнение

; 5. Считываем результат работы (stdout) из ExifTool в переменную
output := exec.StdOut.ReadAll()

; 6. Показываем результат
MsgBox(output, "Результат ExifTool")
⚠️ Критические правила при работе с -@ -Закрытие потока: Метод exec.StdIn.Close() обязателен. Пока поток открыт, ExifTool будет бесконечно ждать продолжения ввода и зависнет.Кавычки не нужны: Внутри строки argsContent пути к файлам и текстовые значения с пробелами нельзя оборачивать в кавычки.Символы перевода строки: Каждая опция и её значение должны разделяться стандартным символом новой строки (``n`).Если вам необходимо развить этот скрипт, уточните:Нужно ли обрабатывать несколько файлов по очереди в одном долгоживущем процессе ExifTool (режим -stay_open)?Требуется ли обрабатывать ошибки, возникающие при работе утилиты (чтение потока StdErr)?Какого формата данные вы хотите извлечь из файла (строки, JSON или XML)?
[/cut]

Добавлено: Пт май 15, 2026 13:11
AkulaBig
Orion9 писал(а):Зачем PID
Скорей всего нужен.
Orion9 писал(а):Один файл для ввода, другой для вывода.
А что будет обрабатывать эти файлы?
Ну и wdx-плагины тоже имеют свои правила для работы. Например, программа, которую они используют, закрывается после каждого файла.
В общем может я не все протестировал, но еще раз напишу, -stay_open работает только для одного файла. А для следующего файла ее опять надо запускать.
Попробуйте сделать именно скрипт для wdx-плагина. Может у вас получится.

Добавлено: Пт май 15, 2026 16:16
Orion9
AkulaBig писал(а):Скорей всего нужен.
При работе с файлами не нужен. Разве что проверить существует ранее запущенный процесс или нет, но это можно сделать и без PID.
AkulaBig писал(а):Попробуйте сделать именно скрипт для wdx-плагина. Может у вас получится.
Спасибо, конечно. Но как говорил один дзен мастер "свои 15000 строк я уже написал, теперь ваша очередь" :mrgreen:

Если серьёзно, чем вас не устроил этот вариант для vbs? Имхо, это самый быстрый и простой способ для плагина WinscriptAdv с использование -stay_open.

Добавлено: Пт май 15, 2026 17:23
AkulaBig
Orion9 писал(а):чем вас не устроил этот вариант для vbs?
Не устраивает тем, что все эти варианты не работают в wdx-плагине.
Ну вот переписал я его под au3:
[cut]

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

#NoTrayIcon

If StringInStr(FileGetAttrib($filename), "D") Then Exit

Local $sExifPath = EnvGet("util_ET") ; Путь к exiftool
Global $sArgFile = @TempDir & "\exiftool_args.txt"
Global $sOutFile  = @TempDir & "\exiftool_out.txt"

; Подготовка файла аргументов
FileDelete($sArgFile)
FileWrite($sArgFile, "")
FileDelete($sOutFile)

; Запуск процесса ExifTool (один раз)
; Используем $STDIN_CHILD + $STDOUT_CHILD для взаимодействия
Global $iPID = Run($sExifPath & ' -stay_open True -@ "' & $sArgFile & '"', "", @SW_HIDE, 0x3)

$content = _ExifTool_GetTag($filename, "Title")
 ; MsgBox(64, "Результат", $content)

; Закрываем ExifTool
_ExifTool_Quit()

ConsoleWrite($content)

; --- Функция получения данных ---
Func _ExifTool_GetTag($sFile, $sTag)
    ; Если файл вывода существует, удаляем его перед новой командой
    FileDelete($sOutFile)

    ; Формируем команду
    ; -s3: только значение
    Local $sCmd = "-s3" & @LF & "-" & $sTag & @LF & $sFile & @LF & "-W!" & @LF & $sOutFile & @LF & "-execute" & @LF

    ; Записываем команду в файл аргументов
    Local $hFile = FileOpen($sArgFile, 1) ; 1 = Write mode (append)
    FileWrite($hFile, $sCmd)
    FileClose($hFile)

    ; ; 3. Ждем, пока ExifTool создаст файл (цикл ожидания)
    ; Local $iWaitCount = 0
    ; While Not FileExists("$sOutFile")
    ;     Sleep(50)
    ;     $iWaitCount += $iWaitCount
    ;     if $iWaitCount > 2000 then ExitLoop ; Таймаут 2 сек
    ; Wend

    Local $sTagValue = "<time-out>"
   For $i = 1 To 50
      If FileExists("$sOutFile") Then
         $sTagValue = FileRead("$sOutFile")
         ExitLoop
      EndIf
      Sleep(20)
   Next


;     ; 3. Ждем, пока ExifTool создаст файл (цикл ожидания)
;     Local $iWaitCount = 0
;     While Not FileExists("$sOutFile")
;         Sleep(50)
;         $iWaitCount += $iWaitCount
;         if $iWaitCount > 2000 then ExitLoop ; Таймаут 2 сек
;     Wend
; 
; 
;     ; 4. Читаем результат в переменную
;     Local $sTagValue = FileRead("$sOutFile")
; MsgBox(64, "Результат", $sTagValue)
    Return $sTagValue
EndFunc

; --- Функция закрытия ---
Func _ExifTool_Quit()
    Local $hFile = FileOpen($sArgFile, 1)
    FileWrite($hFile, "-stay_open" & @LF & "False" & @LF)
    FileClose($hFile)
    ProcessWaitClose("exiftool.exe")
    FileDelete($sArgFile)
    FileDelete($sOutFile)
EndFunc
[/cut]
И вот результат:
Изображение
Несколько вариантов задержки пробовал. На всех языках результат примерно одинаковый. Файл аргументов правильный, выходной файл правильный. Что еще надо? Конечно, как видите, ET запускается для каждого файла. Но, как минимум, первый файл обязан правильно отработать. Тем более что в выходном файле значение правильное.
Orion9 писал(а):как говорил один дзен мастер "свои 15000 строк я уже написал, теперь ваша очередь
Но этот-же сансей говорил: "Докажи своим примером, что это возможно" :lol:
Кстати, с использованием потоков у меня результат даже лучше. Там хотя-бы в MsgBox правильное значение показывается. А вот в консоль такое-же выводится.

Добавлено: Пт май 15, 2026 18:13
Orion9
AkulaBig писал(а):ET запускается для каждого файла
Его нужно запустить только один раз, а дальше проверять существование процесса. Плохо, что WinScriptAdv не предоставляет возможности работать с другими глобальными переменными, кроме content и filename. Как минимум можно было сделать еще несколько переменных для случаях, когда необходимо хранить промежуточный результат при обработке файлов.

Но можно обойтись простой проверкой существования процесса "exiftool.exe". Если процесс не существует, то запустить в режиме -stay_open. Если существует, то сразу писать в файл аргументов и ждать ответа в файл вывода. Должно сработать. И скорость значительно возрастет. Если есть возможность заставить AutoIt не выгружаться (вроде там была какая-то библиотека COM или что-то в этом роде), то скорость вообще будет космической.

Если нет возможности держать AutoIt открытым, то придется делать на vbs, если скорость не понравится.

AkulaBig писал(а):"Докажи своим примером, что это возможно"
Как говорила Сара Коннор "пока есть процессор, память и чипсет — возможно всё", однако Гислер с этим не согласен :lol:

Добавлено: Пт май 15, 2026 18:48
AkulaBig
Orion9 писал(а):Должно сработать
Но не работает. Ненужный вам ПИД становится равным нулю и все... На vbs тоже не работает. В общем ладно, заканчиваем бессмысленное обсуждение.

Добавлено: Сб май 16, 2026 12:04
Orion9
AkulaBig писал(а):Ненужный вам ПИД
Не мне ненужный, а скрипту. Разница хоть небольшая, но она все же есть )

С точки зрения скрипта, работа происходит с двумя файлами, что происходит с процессом, ему неинтересно. От него требуется только записать в один файл и ждать результата для второго, на этом все.

[cut]

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

'Option Explicit

Dim fso, argFile, outFile
Set fso = CreateObject("Scripting.FileSystemObject")

argFile  = fso.GetSpecialFolder(2) & "\exiftool_args.txt"
outFile  = fso.GetSpecialFolder(2) & "\exiftool_out.txt"

'content  = GetExifTag(filename, "ImageSize") 
content = GetExifTag(filename, "FileName")

Function GetExifTag(fileName, tagName)
    Dim stream, result
   
    'If fso.FileExists(outFile) Then fso.DeleteFile(outFile)
   
    Set stream = fso.OpenTextFile(argFile, 8) ' 8 = Append
    stream.WriteLine "-s3"
    'stream.WriteLine "-" & tagName
    stream.WriteLine "-W+!"
    stream.WriteLine outFile
    stream.WriteLine fileName    
    stream.WriteLine "-execute"
    stream.Close
   
    Dim timeout
    timeout = 0
    Do While Not fso.FileExists(outFile) And timeout < 5000
        'WScript.Sleep(10)
        timeout = timeout + 1
    Loop
   
    If fso.FileExists(outFile) Then
        Set stream = fso.OpenTextFile(outFile, 1)
        If Not stream.AtEndOfStream Then
            result = Trim(stream.ReadAll)
        Else
            result = "Empty"
        End If
        stream.Close
        GetExifTag = result
    Else
        GetExifTag = "Error: Timeout"
    End If
End Function
[/cut]
А как запустить Exiftool с опцией -stay_open, это вопрос другой. Либо отдельным тригером (как у меня по Alt+F12), либо проверкой существования процесса. Но оно того не стоит. Как видите, мне пришлось закомментировать строку удаления файла и строку WScript.Sleep(10) из-за неприятных ограничений работы через СОМ. Можно искать разные решения, но это будет костыль на костыле. WinScriptAdv сам по себе костыль. Городить костыль на костыле нет смысла. Для нормальной работы нужен отдельный wdx, который будет напрямую взаимодействовать с exiftool.exe. Тогда и скорость будет и качество.

Решение на Autorun, конечно, красивое. Но есть проблемы с наборами колонок. Не зря не получется посмотреть данные плагина через Wdx Guide и uwdx. Придется ждать Loopback, чтобы получить от него разъяснения.