none
Многопоточный запуск FFprobe скриптом Powershell RRS feed

  • Вопрос

  • Добрый день!

    Пытаюсь запустить проверку онлайн тв потоков посредством ffprobe.

    Такой скрипт:

    $soft = '.\ffprobe.exe'
    $utf8 = [System.Text.Encoding]::GetEncoding(65001)
    Get-Content .\Test-streams.txt -Encoding utf8 `
    | ForEach {
    cmd /c $utf8.GetString($utf8.GetBytes("$soft -timeout 6000000 -hide_banner -select_streams v:1 -print_format json=compact=1 -show_entries format=filename -show_programs -show_entries program=service_name -of default=noprint_wrappers=1:nokey=0 $_ >>out.json"))
    }

    работает отлично, на выходе нужная информация.

    Но такой код работает достаточно долго. Хотелось бы запустить ffprobe в три потока. Нашел в сети такой скрипт - 

    https://xaegr.wordpress.com/2011/07/12/threadping/

    Попытался переделать под свою задачу:

    Param (
    [string[]]$unc = 'http://109.248.67.1:4044',
    [string[]]$prt = ':1234',           
    [string[]]$Address = $(1..20 | %{"$unc/udp/239.195.1.$_$prt"}),            
    [int]$Threads = 3            
    )            
                
    write-host "Distributing addresses around jobs"            
    $JobAddresses = @{}            
    $CurJob = 0            
    $CurAddress = 0            
    while ($CurAddress -lt $Address.count)            
    {            
        $JobAddresses[$CurJob] += @($Address[$CurAddress])            
        $CurAddress++            
        if ($CurJob -eq $Threads -1)            
        {            
            $CurJob = 0            
        }            
        else            
        {            
            $CurJob++            
        }            
    }            
                
    $Jobs = @()            
    foreach ($n in 0 .. ($Threads-1))            
    {            
        Write-host "Starting job $n, for addresses $($JobAddresses[$n])"            
        $Jobs += Start-Job -ArgumentList "ffprobe.exe -timeout 6000000 -hide_banner -select_streams v:1 -print_format json=compact=1 -show_entries format=filename -show_programs -show_entries program=service_name -of default=noprint_wrappers=1:nokey=0 $JobAddresses[$n] >>out.json" -ScriptBlock {            
        
    cmd /c
        }            
    }            
                
    write-host "Waiting for jobs"            
    $ReceivedJobs = 0            
    while ($ReceivedJobs -le $Jobs.Count)            
    {            
        foreach ($CompletedJob in ($Jobs | where {$_.State -eq "Completed"}))            
        {            
            Receive-Job $CompletedJob | select status, address, roundtriptime            
            $ReceivedJobs ++            
            sleep 1            
        }            
    }            
                
    Remove-Job $Jobs            
    write-host "Done."

    Но никак не получается получить вывод с FFprobe.

    Подскажите, пожалуйста, возможен ли вообще многопоточный запуск ffprobe на powershell ?
    • Изменено Uragan66 8 декабря 2019 г. 13:40 Исправление кода
    8 декабря 2019 г. 10:30

Ответы

  • $i = 0

    $OL = 0

    get-content c:\input.txt | foreach { # 1 foreach вместо 2х и тем более 3х

    $OL++ Start-Job -Name ffprobe -ArgumentList $_, $OL -ScriptBlock {

    $OL = $args[1] $URI = $args[0] # Использование регекспа для замены одного октета это сильно $soft = 'C:\full\path\to your program\ffprobe.exe' # Полный путь, или можно пробовать играться с переменной $Path. Для начала я бы не заморачивался со вторым $utf8 = [System.Text.Encoding]::GetEncoding(65001) cmd /c $utf8.GetString($utf8.GetBytes("$soft -timeout 6000000 -hide_banner -select_streams v:1 -print_format json=compact=1 -show_entries format=filename -show_programs -show_entries program=service_name -of default=noprint_wrappers=1:nokey=0 $URI >> C:\temp\out_$OL.json")) # И снова полный путь }

    # Если запустить 255 потоков, то производительность сильно просядет (скорее всего)
    # По этому мое предложение запускать по 5 задач, ждать их выполнения, после чего запускать следующие 5 задач. При желании можете увеличить до 10 или 15 if (($i -ge 5) -or ($OL -eq 255){ Get-Job ffprobe | Wait-Job -Timeout 300 Get-Job ffprobe | Receive-Job Get-Job ffprobe | Remove-Job -Force $i = 0 } else {$i++} }


    The opinion expressed by me is not an official position of Microsoft

    • Помечено в качестве ответа Uragan66 9 декабря 2019 г. 11:58
    9 декабря 2019 г. 11:17
    Модератор
  • 1 Про полные пути я уже писал

    2 получится ли запустить команду в Job`e хз, нужно пробовать и править по месту

    3 пример скрипта:

    $i = 0 foreach ($OL in @(1..255)){ # 1 foreach вместо 2х и тем более 3х Start-Job -Name ffprobe -ArgumentList $OL -ScriptBlock { $OL = $args[0] $URI = "http://79.105.135.20:5055/udp/239.28.0.$OL`:2020" # Использование регекспа для замены одного октета это сильно $soft = 'C:\full\path\to your program\ffprobe.exe' # Полный путь, или можно пробовать играться с переменной $Path. Для начала я бы не заморачивался со вторым $utf8 = [System.Text.Encoding]::GetEncoding(65001) cmd /c $utf8.GetString($utf8.GetBytes("$soft -timeout 6000000 -hide_banner -select_streams v:1 -print_format json=compact=1 -show_entries format=filename -show_programs -show_entries program=service_name -of default=noprint_wrappers=1:nokey=0 $URI >> C:\temp\out_$OL.json")) # И снова полный путь }

    # Если запустить 255 потоков, то производительность сильно просядет (скорее всего)
    # По этому мое предложение запускать по 5 задач, ждать их выполнения, после чего запускать следующие 5 задач. При желании можете увеличить до 10 или 15 if (($i -ge 5) -or ($OL -eq 255){ Get-Job ffprobe | Wait-Job -Timeout 300 Get-Job ffprobe | Receive-Job Get-Job ffprobe | Remove-Job -Force $i = 0 } else {$i++} }



    The opinion expressed by me is not an official position of Microsoft

    • Изменено Vector BCOModerator 9 декабря 2019 г. 8:01
    • Помечено в качестве ответа Uragan66 9 декабря 2019 г. 11:58
    8 декабря 2019 г. 20:08
    Модератор
  • Добрый день

    возьмите мой пример из последнего помеченного ответа и измените следующий фрагмент

    # Если запустить 255 потоков, то производительность сильно просядет (скорее всего) # По этому мое предложение запускать по 5 задач, ждать их выполнения, после чего запускать следующие 5 задач. При желании можете увеличить до 10 или 15 if (($i -ge 5) -or ($OL -eq 255){ Get-Job ffprobe | Wait-Job -Timeout 300 Get-Job ffprobe | Receive-Job Get-Job ffprobe | Remove-Job -Force

    foreach ($file in $(gci c:\temp | where {$_.Name -like "out*.txt"})){

    get-content $file.fullname | out-file c:\out.txt -append

    remove-item $file -Force -Ea sylintlycontinue

    }

    $i = 0 } else {$i++}



    The opinion expressed by me is not an official position of Microsoft


    • Изменено Vector BCOModerator 10 декабря 2019 г. 14:39
    • Помечено в качестве ответа Uragan66 10 декабря 2019 г. 16:33
    10 декабря 2019 г. 14:38
    Модератор

Все ответы

  • из того что бросается в глаза

    1 путь к файлу ffprobe.exe не указан, а дефолтный путь в джобе c:\windows\system32

    2 вы используете -ArgumentList с длинной балолайкой 1 аргумента, но сам аргумент ($args[0]) не используете в блоке -ScriptBlock

    3 вы делаете вывод команды в файл (которых хз где находится) причем все джобы пишут в 1 файл - что как бы не але так как возникают конфликты записи + если инфа выводится построчно (а так скорее и есть), то вы с большой вероятностью получите венигрет из разных тредов

    4 вангую что если убрать вывод в вайл, то вывод все равно будет строкой а не обьектом, а если так то select вам выберет ровным счетом ничего

    5 3 цикла вместо одного, это не увеличение производительности, а уменьшение, в связи с чем первые 2 цикла вы можете без проблем обьединить, а третий убрать


    The opinion expressed by me is not an official position of Microsoft

    8 декабря 2019 г. 15:00
    Модератор
  • Спасибо за ответ!

    Я неправильно объяснил суть задачи. Постараюсь более подробно:

    1. ffprobe.exe находится в папке запуска скрипта, поэтому путь к нему не указывал.

    2. Суть работы скрипта заключается в сканировании программой ffprobe 255 мультикастов (адресов где изменяется последняя цифра в локальном IP, к примеру, http://79.105.135.20:5055/udp/239.28.0.1:2020, то есть адреса на вход ffprobe поступают типа таких http://79.105.135.20:5055/udp/239.28.0.1:2020 (первый) - http://79.105.135.20:5055/udp/239.28.0.255:2020 (последний))

    ffprobe по каждому адресу подключается к серверу, получает ответ и пишет его в файл out.json.

    3. -ArgumentList такой длинный, так как в нём прописал все аргументы, необходимые для запуска ffprobe. Ответ от сервера получается записывать только в out.json. Далее он распарсивается и сохраняется в нужный файл.

    Скрипт сейчас переделал, рабочий вариант такой:

    	$x = 'http://79.105.135.20:5055/udp/239.28.0.1:2020'
    	$soft = '.\ffprobe.exe'
    	$utf8 = [System.Text.Encoding]::GetEncoding(65001)
    
    	$(1..255 | ForEach {$x -replace "(http://\d+?`.\d+?`.\d+?`.\d+?`.:\d+?/.*)/(\d+?`.\d+?`.\d+?)`.\d+?`:(\d+?)","`$1/`$2`.$_`:`$3"}) `
    	| ForEach {
    		cmd /c $utf8.GetString($utf8.GetBytes("$soft -timeout 6000000 -hide_banner -select_streams v:1 -print_format json=compact=1 -show_entries format=filename -show_programs -show_entries program=service_name -of default=noprint_wrappers=1:nokey=0 $_ >>out.json"))
    	}

    Файл out.json выглядит примерно так:

    TAG:service_name=01 ПЕРВЫЙ КАНАЛ
    TAG:service_provider=РТРС
    filename=http://79.105.135.20:5055/udp/239.28.0.1:2020
    TAG:service_name=02 РОССИЯ-1
    TAG:service_provider=РТРС
    filename=http://79.105.135.20:5055/udp/239.28.0.2:2020
    TAG:service_name=03 МАТЧ!
    TAG:service_provider=РТРС
    filename=http://79.105.135.20:5055/udp/239.28.0.3:2020
    TAG:service_name=04 НТВ
    TAG:service_provider=РТРС
    filename=http://79.105.135.20:5055/udp/239.28.0.4:2020
    Но таким кодом скрипт работает достаточно долго, пока будут получены ответы по всем 255 адресам.

    Вот и хотелось как-то ускорить этот процесс. Возможно запуск посредством Start-Job в данном варианте и не подходит...

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

    Подскажите, пожалуйста, есть ли какие-то варианты ускорить работу моего скрипта, кроме как через Start-Job ?


    • Изменено Uragan66 8 декабря 2019 г. 17:16
    8 декабря 2019 г. 17:15
  • 1 Про полные пути я уже писал

    2 получится ли запустить команду в Job`e хз, нужно пробовать и править по месту

    3 пример скрипта:

    $i = 0 foreach ($OL in @(1..255)){ # 1 foreach вместо 2х и тем более 3х Start-Job -Name ffprobe -ArgumentList $OL -ScriptBlock { $OL = $args[0] $URI = "http://79.105.135.20:5055/udp/239.28.0.$OL`:2020" # Использование регекспа для замены одного октета это сильно $soft = 'C:\full\path\to your program\ffprobe.exe' # Полный путь, или можно пробовать играться с переменной $Path. Для начала я бы не заморачивался со вторым $utf8 = [System.Text.Encoding]::GetEncoding(65001) cmd /c $utf8.GetString($utf8.GetBytes("$soft -timeout 6000000 -hide_banner -select_streams v:1 -print_format json=compact=1 -show_entries format=filename -show_programs -show_entries program=service_name -of default=noprint_wrappers=1:nokey=0 $URI >> C:\temp\out_$OL.json")) # И снова полный путь }

    # Если запустить 255 потоков, то производительность сильно просядет (скорее всего)
    # По этому мое предложение запускать по 5 задач, ждать их выполнения, после чего запускать следующие 5 задач. При желании можете увеличить до 10 или 15 if (($i -ge 5) -or ($OL -eq 255){ Get-Job ffprobe | Wait-Job -Timeout 300 Get-Job ffprobe | Receive-Job Get-Job ffprobe | Remove-Job -Force $i = 0 } else {$i++} }



    The opinion expressed by me is not an official position of Microsoft

    • Изменено Vector BCOModerator 9 декабря 2019 г. 8:01
    • Помечено в качестве ответа Uragan66 9 декабря 2019 г. 11:58
    8 декабря 2019 г. 20:08
    Модератор
  • Здравствуйте!

    Отлично! Спасибо большое!

    Ваш код работает как нужно, хотя есть некоторые нюансы:

    1. Код работает только при указании полных путей.

    Пробовал подставлять переменные :

    $path = $pwd.path
    $soft = $path\ffrobe.exe
    или так
    $soft = (ls ffrobe.exe).FullName

    Ошибок никаких нет, но и скрипт не работает, замирает после запуска 5 задач.

    С ffprobe то можно выкрутиться, прописав директорию в системную Path, потом просто

    $soft = 'ffprobe.exe'

    Но с выходным файлом это не проходит. Нужно указывать только полный путь.

    Даже при использовании Windows Form с TextBox, с указанием в нём полного пути для выходного файла, не проходит. Переменная $textBox.Text в аргументах запуска ffprobe не работает.

    2. Ну и второй, более важный нюанс, это создание выходного файла для каждой итерации.

    Если их 255, то нормально, потом можно "склеить" в один и дальше с ним работать.

    Но адреса на входе могут быть разные, в некоторых нужно подключать текстовый файл для замены имён, их может быть несколько тысяч.

    Нельзя ли как-то обойтись созданием одного выходного файла ?

    3. По поводу использования регекспа для замены одного октета в моём коде. Адреса то разные, это для примера я указал один. В моём скрипте код работает через форму, с вставкой адреса в textBox, поэтому пришлось его разбирать рег. выражением.

    Ещё раз спасибо большое за помощь и подсказки.

    9 декабря 2019 г. 8:23
  • $i = 0

    $OL = 0

    get-content c:\input.txt | foreach { # 1 foreach вместо 2х и тем более 3х

    $OL++ Start-Job -Name ffprobe -ArgumentList $_, $OL -ScriptBlock {

    $OL = $args[1] $URI = $args[0] # Использование регекспа для замены одного октета это сильно $soft = 'C:\full\path\to your program\ffprobe.exe' # Полный путь, или можно пробовать играться с переменной $Path. Для начала я бы не заморачивался со вторым $utf8 = [System.Text.Encoding]::GetEncoding(65001) cmd /c $utf8.GetString($utf8.GetBytes("$soft -timeout 6000000 -hide_banner -select_streams v:1 -print_format json=compact=1 -show_entries format=filename -show_programs -show_entries program=service_name -of default=noprint_wrappers=1:nokey=0 $URI >> C:\temp\out_$OL.json")) # И снова полный путь }

    # Если запустить 255 потоков, то производительность сильно просядет (скорее всего)
    # По этому мое предложение запускать по 5 задач, ждать их выполнения, после чего запускать следующие 5 задач. При желании можете увеличить до 10 или 15 if (($i -ge 5) -or ($OL -eq 255){ Get-Job ffprobe | Wait-Job -Timeout 300 Get-Job ffprobe | Receive-Job Get-Job ffprobe | Remove-Job -Force $i = 0 } else {$i++} }


    The opinion expressed by me is not an official position of Microsoft

    • Помечено в качестве ответа Uragan66 9 декабря 2019 г. 11:58
    9 декабря 2019 г. 11:17
    Модератор
  • С путями немного разобрался. Если запускать ffprobe посредством Start-Process таким кодом:

    $soft = '.\ffprobe.exe'
            Start-Process -FilePath $soft -ArgumentList " -timeout 6000000 -hide_banner -select_streams v:1 -print_format json=compact=1 -show_entries format=filename -show_programs -show_entries program=service_name -of default=noprint_wrappers=1:nokey=0 $URI" -NoNewWindow -Wait -RedirectStandardOutput $js\out_$OL.json

    то вполне нормально работает и относительный путь.

    Для выходного файла добавил на форму textBox, передать переменную в ScriptBlock получилось:

        $js = $textBox4.Text
        Start-Job -Name ffprobe -ArgumentList $OL, $URI, $js -ScriptBlock {
            $OL = $args[0]
            $URI = $args[1]
            $js = $args[2]
            $soft = '.\ffprobe.exe'
            Start-Process -FilePath $soft -ArgumentList " -timeout 6000000 -hide_banner -select_streams v:1 -print_format json=compact=1 -show_entries format=filename -show_programs -show_entries program=service_name -of default=noprint_wrappers=1:nokey=0 $URI" -NoNewWindow -Wait -RedirectStandardOutput $js\out_$OL.json
        }

    Если бы ещё сделать один выходной файл, было бы вообще отлично.

    Или в случае с Start-Job это невозможно ?

    9 декабря 2019 г. 11:21
  • С путями немного разобрался. Если запускать ffprobe посредством Start-Process таким кодом:

    $soft = '.\ffprobe.exe'
            Start-Process -FilePath $soft -ArgumentList " -timeout 6000000 -hide_banner -select_streams v:1 -print_format json=compact=1 -show_entries format=filename -show_programs -show_entries program=service_name -of default=noprint_wrappers=1:nokey=0 $URI" -NoNewWindow -Wait -RedirectStandardOutput $js\out_$OL.json

    то вполне нормально работает и относительный путь.

    Для выходного файла добавил на форму textBox, передать переменную в ScriptBlock получилось:

        $js = $textBox4.Text
        Start-Job -Name ffprobe -ArgumentList $OL, $URI, $js -ScriptBlock {
            $OL = $args[0]
            $URI = $args[1]
            $js = $args[2]
            $soft = '.\ffprobe.exe'
            Start-Process -FilePath $soft -ArgumentList " -timeout 6000000 -hide_banner -select_streams v:1 -print_format json=compact=1 -show_entries format=filename -show_programs -show_entries program=service_name -of default=noprint_wrappers=1:nokey=0 $URI" -NoNewWindow -Wait -RedirectStandardOutput $js\out_$OL.json
        }

    Если бы ещё сделать один выходной файл, было бы вообще отлично.

    Или в случае с Start-Job это невозможно ?

    проблема не в start-job а в том что вы получите кашу на выходе + часть инфы нормально не попадет в файл вовсе

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

    если вы хотите собирать все в 1 файл в блоке ожидание после завершения задач добавьте перезапись всех новосозданных файлов в 1 с последующим их удалением. Таким образом файлов у вас будет создаваться с десяток, перезаписываться после чего будет создаваться новый десяток и тд.


    The opinion expressed by me is not an official position of Microsoft

    9 декабря 2019 г. 13:29
    Модератор
  • Добрый день!

    Vector BCO, спасибо большое за помощь!

    С перезаписью и удалением файлов вроде получается, но не совсем.

    Так как ffprobe по разным адресам получает ответы не сразу, то выходные файлы out.json сразу создаются нулевого размера. По тем адресам, где ответ получен, он записывается в out.json, соответственно файл увеличивается в размере.

    Поэтому добавил код с контролем размера  out.json :

     if ($i -ge 5){
            Get-Job ffprobe | Wait-Job -Timeout 300
            Get-Job ffprobe | Receive-Job
            Get-Job ffprobe | Remove-Job -Force
            Get-Content ($fl = Get-Childitem -Path $js\out -Include *.json -Recurse | Where-Object {$_.length -ge 57}) | Out-File $js\out\out.txt -Append -Encoding default
            $fl | Remove-Item
            $i = 0
        } else {$i++}

    Но при таком подходе, файлы out.json, по адресам которых ffprobe не получила ответ, так и остаются нулевого размера, соответственно они и не удаляются.

    Если же убрать проверку размера:

        if ($i -ge 5){
            Get-Job ffprobe | Wait-Job -Timeout 300
            Get-Job ffprobe | Receive-Job
            Get-Job ffprobe | Remove-Job -Force
            Get-Content ($fl = Get-Childitem -Path $js\out -Include *.json -Recurse) | Out-File $js\out\out.txt -Append -Encoding default
            $fl | Remove-Item
            $i = 0
        } else {$i++}

    то в консоли по некоторым файлам идут ошибки "Процесс не может получить доступ к файлу..."

    Подскажите, пожалуйста, как к первому варианту кода добавить удаление out.json, которые в итоге так и останутся нулевыми ?

    Или может код по другому нужно написать ?


    • Изменено Uragan66 10 декабря 2019 г. 10:40
    10 декабря 2019 г. 10:39
  • Добрый день

    возьмите мой пример из последнего помеченного ответа и измените следующий фрагмент

    # Если запустить 255 потоков, то производительность сильно просядет (скорее всего) # По этому мое предложение запускать по 5 задач, ждать их выполнения, после чего запускать следующие 5 задач. При желании можете увеличить до 10 или 15 if (($i -ge 5) -or ($OL -eq 255){ Get-Job ffprobe | Wait-Job -Timeout 300 Get-Job ffprobe | Receive-Job Get-Job ffprobe | Remove-Job -Force

    foreach ($file in $(gci c:\temp | where {$_.Name -like "out*.txt"})){

    get-content $file.fullname | out-file c:\out.txt -append

    remove-item $file -Force -Ea sylintlycontinue

    }

    $i = 0 } else {$i++}



    The opinion expressed by me is not an official position of Microsoft


    • Изменено Vector BCOModerator 10 декабря 2019 г. 14:39
    • Помечено в качестве ответа Uragan66 10 декабря 2019 г. 16:33
    10 декабря 2019 г. 14:38
    Модератор
  • В PowerShell 7.0 вы можете использовать ForEach-Object -Parallel

    Сазонов Илья

    https://isazonov.wordpress.com/

    11 декабря 2019 г. 19:23
    Модератор
  • В PowerShell 7.0 вы можете использовать ForEach-Object -Parallel

    Сазонов Илья

    https://isazonov.wordpress.com/

    Илья, PoSh 7 и PoSh Core это одно и тоже? Если нет, то 7 версия заменяет 5.1?

    The opinion expressed by me is not an official position of Microsoft

    11 декабря 2019 г. 21:03
    Модератор