Yvision.kz
kk
Разное
Разное
399 771 постов41 подписчиков
Всяко-разно
0
19:52, 14 июня 2010

Загребаем консольный вывод мохнатой рукой

Blog post imageКак-то я писал про ужасные беды и в том же посте предположил, что было бы очень занимательно автоматизировать процесс: большие мальчики против хэндджоба.

Для получения готового mkv-контейнера, dts трэк из источника должен был пройти через несколько консольных экзекуций.

Цикл обработки сводился к следующему:

  1. Определяем индекс трэка DTS
  2. Достаем его из контейнера в отдельный файл
  3. Конвертируем в двухканальный WAV
  4. Конвертируем WAV в MP3
  5. Склеиваем трэки из оригинального контейнера и получившийся MP3 файл, выбрав его дефолтным
  6. ????????
  7. Имеем ПРОФИТ
Для каждой задачи имеется отдельная шурудилка:
  1. mkvinfo.exe
  2. mkvextract.exe
  3. valdec.exe из комплекта AC3Tools
  4. lame.exe
  5. mkvmerge.exe
  6. chmod -r 777 /mnt/hands
  7. profit.exe

Для получения консольного вывода можно использовать файлы, выполняя команду "console_blah.exe >OutFileName.txt", но это моветон и никак не комильфо. Решено было использовать Pipes(далее пайпы). Пайпы - это хитрые каналы, которые и были разработаны для подобных гнусных целей.

В задачу не входило создание унифицированного класса, но без финта ушами было бы не интересно. В итоге получился базовый класс, в задачу которого входит создание процесса, пайпа и присоединение второго к первому.

  1. Type TConsoleThread = class (Tthread) //создаем потомок класса TThread, тащемта поток
  2. private
  3. FFileName: string; // путь и имя консольной шурудилки
  4. FParamStr: string; // параметры запуска на орбиту
  5. FProcInfo: TProcessInformation; // при создании процесса получает информацию об оном
  6. FStartupInfo:TStartupInfo; // показывает CreateProcess информацию для запуска
  7. FSecurityAttr:TSecurityAttributes; // параметры безопасности для пайпа
  8. FReadHandle, FWriteHandle: THandle; // Хэндлы для пайпа
  9. FExitProc:TnotifyEvent; // ебаный стыд, который к тому же не работает, оставил для суровости
  10. procedure AppendOut(S:String); virtual; // процедура добавления буфера консольного вывода в общую кучу
  11. procedure SetExitProc(Value:TnotifyEvent); // опять ебаный стыд
  12. protected
  13. procedure Execute; override; // выполнение потока, запускается после создания
  14. public
  15. constructor Create(CreateSuspended: Boolean; const Filename,ParamStr: string); // создание потока
  16. property ExitProcedurePointer:TnotifyEvent read FExitProc write SetExitProc; // под конец все тот же ЁС
  17. end; // кончел

  1. constructor TConsoleThread.Create(CreateSuspended: Boolean;const Filename,ParamStr: string);
  2. begin
  3. inherited Create(CreateSuspended); // родительская процедура создания класса
  4. FExitProc:=nil; // убираем ЁС
  5. FFileName:=Filename; // берем имя из параметров
  6. FParamStr:=ParamStr; // берем параметры из параметров (каламбурчик)
  7. with FSecurityAttr do
  8. begin
  9. nLength := SizeOf(TSecurityAttributes);
  10. lpSecurityDescriptor := nil;
  11. bInheritHandle := true; //делаем указатели наследуемыми
  12. end;
  13. CreatePipe(FReadHandle, FWriteHandle, @FSecurityAttr, 0); // создаем пайп
  14. SetHandleInformation(FReadHandle, HANDLE_FLAG_INHERIT, 0) ; // очередное наследование хэндла, зачем - неизвестно
  15. GetStartupInfo(FStartupInfo); // получаем структуру
  16. with FStartupInfo do
  17. begin
  18. wShowWindow := SW_HIDE; // не показывать консоль
  19. dwFlags := dwFlags or STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES; // последнее важно для наследование хэндлов
  20. hStdOutput := FWriteHandle; // перенаправляем вывод в пайп
  21. hStdError := FWriteHandle; // ошибки туда же ( lame выводит информацию именно в hStdError)
  22. end;
  23. CreateProcess(pChar(FFileName),pChar(' '+FParamStr), nil, nil, true, CREATE_UNICODE_ENVIRONMENT,
  24. nil, nil, FStartupInfo, FProcInfo); // создаем процесс. помни, воен, что bInheritHandles:=true
  25. end;

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

  1. procedure TConsoleThread.Execute;
  2. var
  3. k:cardinal; // тут будет код завершения процесса
  4. ReadBuf:array[0..1000] of AnsiChar; // буфер для строки, аблизательно AnsiChar
  5. BytesRead,TotalB: Cardinal; // тут будет количество прочитаных байт и оставшихся в пайпе
  6. Finish:Boolean;
  7. begin
  8. repeat
  9. Sleep(10);
  10. FillChar(ReadBuf,High(ReadBuf),0); // очищаем буфер
  11. if PeekNamedPipe(FReadHandle, @ReadBuf[0], High(ReadBuf)-1, @BytesRead,@TotalB, nil) then
  12. // проверяем пайп на наличие информации. можно было сразу читать, но процедура чтения ассинхронная
  13. // и если информации в пайп не поступит то все повиснет как. повиснет вообщем.
  14. begin
  15. if TotalB>0 then // если есть что читать
  16. if ReadFile(FReadHandle, ReadBuf, High(ReadBuf)-1, BytesRead, nil) then // если прочитали
  17. if BytesRead > 0 then // и если даже есть буквы
  18. begin
  19. ReadBuf[BytesRead] := #0; // завершаем строку
  20. AppendOut(ReadBuf); // добавляем в кучу обзего вывода
  21. end;
  22. GetExitCodeProcess(FProcInfo.hProcess, k); // периодически опрашиваем процесс на живучесть
  23. Finish:=(k<>STILL_ACTIVE) and (TotalB=0); // если процесс завершился и в буферах кончились буквы
  24. // то мы на финише
  25. end;
  26. until Finish; // финиш ли
  27. CloseHandle(FProcInfo.hProcess); // кончил - оботри станок
  28. CloseHandle(FReadHandle);
  29. CloseHandle(FWriteHandle);
  30. end;

и даже после этого мы не получим заветных буков, потому что нет у нас процедуры которая добавляла бы их к общему выводу. А вот и она:

  1. procedure TConsoleThread.AppendOut(S: string);
  2. begin
  3. // заглушка

Сиё унылое говно имеет место быть лишь потому, что для данного класса необходимо только создать процесс, пайп, прилинковать их, прокрутить вывод и весело завершиться.

Для чтения создаем дочерний класс:

  1. Type TConsoleOutputThread = class (TConsoleThread)
  2. private
  3. FStrings: TStrings; // будем тут строки хранить
  4. procedure SetOutput(Value: TStrings); // неиллюзорно привязываемся в компоненту на форме
  5. procedure AppendOut(S:String); override; // перекрывающая функция обработки буфера
  6. public
  7. property OutputStrings: TStrings read FStrings write SetOutput; // публичная привязка, о как
  8. end;

При создании нового экземпляра класса TConsoleOutputThread мы присвоим ему компонент, в который он будет скидывать буквы.

  1. ParserOut:=TConsoleOutputThread.Create(false,ExtractFilePath(ParamStr(0))+MKVINFO,'"'+filename+'"');
  2. //создаем экземпляр класса TConsoleOutputThread
  3. ParserOut.FreeOnTerminate:=true;
  4. // проперти из TThread указывает что после экзекуции процесс должен почистить за собой
  5. ParserOut.OutputStrings:=Outp;
  6. // Outp был объявлен и создан раньше (КО). Вместо него можно сунуть Memo1.lines

Осталось только дописать процедуры привязки класса/компонента для вывода информации и собственно процедуру вывода в него строк (не хотит кодом вставлять, процитируем)

procedure TConsoleOutputThread.SetOutput(Value: TStrings);
begin
FStrings:= Value; //привязываем компонент для вывода
end;

procedure TConsoleOutputThread.AppendOut(S: string);
begin
FStrings.Text:=FStrings.Text+S; // добавляем буфер к имеющемуся тексту
end;

Как результат: получаем уютненький построчный вывод, причем не после завершения консольного приложения, а во время его выполнения.

В подробности вдаваться не буду, скажу только что долго рыл всяческие дельфикондом, кодерзком и мсдн. Только с мсдн что-то более менее прояснилось.

Полученную утилиту сразу применил в деле. Буду теперь смотреть грусный фильм про собачку на большом экране, рыдать и дергать пинус

Blog post image

PS: Картинка в начале является де-факто бложиков, без нее на пост и смотреть бы не стали. Вот такое вероломное оно, первое что выдал яндекс на слово "консоль".

PS1: По-хорошему можно прилинковать вывод одной консольной программы к вводу другой, так все будет быстрее и многопоточно. но лень

0
268
2