Download a part of youtube video using a powershell script - powershell

I'm writing this Powershell script:
$URL = "https://www.youtube.com/watch?v=KbuwueqEJL0"
$from = 00:06:15
$to = 00:09:17
$cmdOutput = (youtube-dl --get-url $URL)
ffmpeg -ss $from -to $to -i <video_url> -ss $from -to $to -i <audio_url> output.mkv
This script's purpose is to download a part of a Youtube video. I've set the variable $URL to specify the Youtube URL, while $from and $to is the start and end time of the part I want to download.
$cmdOutput is used to output the stream URL. The output would have two lines: the first one is the URL for the video stream, while the second one is the audio stream URL.
Currently, I don't know how to use the output as a variable and specify the line number of $cmdOutput to put it into the correct stream. I guess <video_url> and <audio_url> would be replaced by something like $cmdOutput[line 1], and $cmdOutput[line 2], though I know that those are incorrect.
I've consulted this answer, and it is handy for me to write this script. I've also read Boris Lipschitz's answer on how to do the same thing with Python, but his answer does not work.
In that script, the -ss <start_time> flag inputs the seeking point, and the -t <duration> flag tells FFmpeg to stop encoding after the specified duration. For example, if the start time is 00:02:00 and the duration is 00:03:00, FFmpeg would download from 00:02:00 to 00:05:00, which is not the expected outcome. For some reason, his Python script skips the first 5 seconds of output, even if I replace the -t flag with -to <end_time>. I've tried to edit his script, but it does not work unless you explicitly specify the time for both video and audio stream, as well as their respective stream URL.

Related

how to capture continuous Realtime progress stream of output (from ffmpeg)

I am having trouble showing the progress of ffmpeg through my script. I compile my script to exe with ps2exe and ffmpeg output on standard error instead of outputting on standard out
So I used to pipe 1 option
my script.ps1 now is:
# $nb_of_frames= #some_int
& $ffmpeg_path -progress pipe:1 -i input.mp4 -c:v libx264 -pix_fmt yuv420p -crf 25 -preset fast -an output.mp4
then I compile it with ps2exe. (to reproduce you don't need the compile, just use the above command with pipe:1 directly in cmd or PowerShell you will get the same behavior)
Normally with ffmpeg you get a progress reporting (that is interactive), one line containing the information and it keeps getting updated as 1 single line without spamming the console with 100s of lines, it looks like this.
frame= 7468 fps=115 q=22.0 size= 40704kB time=00:05:10.91 bitrate=1072.5kbits/s speed= 4.8x
But this does not appear in the compiled version of my script, so after digging I added -progress pipe:1 to get the progress to appear on std out
Now I get a continuous output every second that looks like this:
frame=778
fps=310.36
stream_0_0_q=16.0
bitrate= 855.4kbits/s
total_size=3407872
progress=continue
...
frame=1092
fps=311.04
stream_0_0_q=19.0
bitrate= 699.5kbits/s
total_size=3932160
progress=continue
I would like to print some sort of updatable percentage out of this, I can compute a percentage easily if I can capture that frame number, but in this case, I don't know how to capture a real-time output like this and how to make my progress reporting update 1 single line of percentage in real-time (or some progress bar via symbols) instead of spamming on many lines
(or if there is a way to make the default progress of FFmpeg appear in the compiled version of my script that would work too)
edit: a suggestion based on the below answer
#use the following lines instead of write-progress if using with ps2exe
#$a=($frame * 100 / $maxFrames)
#$str = "#"*$a
#$str2 = "-"*(100-$a)
#Write-Host -NoNewLine "`r$a% complete | $str $str2|"
Thanks
Here is an example how to capture current frame number from ffmpeg output, calculate percentage and pass it to Write-Progress:
$maxFrames = 12345
& $ffmpeg_path -progress pipe:1 -i input.mp4 -c:v libx264 -pix_fmt yuv420p -crf 25 -preset fast -an output.mp4 |
Select-String 'frame=(\d+)' | ForEach-Object {
$frame = [int] $_.Matches.Groups[1].Value
Write-Progress -Activity 'ffmpeg' -Status 'Converting' -PercentComplete ($frame * 100 / $maxFrames)
}
Remarks:
Select-String parameter is a regular expression that captures the frame number by the group (\d+) (where \d means a digit and + requires at least one digit). See this Regex101 demo.
ForEach-Object runs the given script block for each match of Select-String. Here $_.Matches.Groups[1].Value extracts the matched value from the first RegEx group. Then we convert it to integer to be able to use it for calculations.
Finally calculate the percentage and pass it to Write-Progress.

How can I use tar and tee in PowerShell to do a read once, write many, raw file copy

I'm using a small laptop to copy video files on location to multiple memory sticks (~8GB).
The copy has to be done without supervision once it's started and has to be fast.
I've identified a serious boundary to the speed, that when making several copies (eg 4 sticks, from 2 cameras, ie 8 transfers * 8Gb ) the multiple Reads use a lot of bandwidth, especially since the cameras are USB2.0 interface (two ports) and have limited capacity.
If I had unix I could use tar -cf - | tee tar -xf /stick1 | tee tar -xf /stick2 etc
which means I'd only have to pull 1 copy (2*8Gb) from each camera once, on the USB2.0 interface.
The memory sticks are generally on a hub on the single USB3.0 interface that is driven on different channel so write sufficently fast.
For reasons, I'm stuck using the current Win10 PowerShell.
I'm currently writing the whole command to a string (concatenating the various sources and the various targets) and then using Invoke-Process to execute the copy process while I'm entertaining and buying the rounds in the pub after the shoot. (hence the necessity to be afk).
I can tar cf - | tar xf a single file, but can't seem to get the tee functioning correctly.
I can also successfully use the microSD slot to do a single cameras card which is not as physically nice but is fast on one cameras recording, but I still have the bandwidth issue on the remaining camera(s). We may end up with 4-5 source cameras at the same time which means the read once, write many, is still going to be an issue.
Edit: I've just advanced to play with Get-Content -raw | tee \stick1\f1 | tee \stick2\f1 | out-null . Haven't done timings or file verification yet....
Edit2: It seems like the Get-Content -raw works properly, but the functionality of PowerShell pipelines violates two of the fundamental Commandments of programming: A program shall do one thing and do it well, Thou shalt not mess with the data stream.
For some unknown reason PowerShell default (and only) pipeline behaviour always modifies the datastream it is supposed to transfer from one stream to the next. Doesn't seem to have a -raw option nor does it seem to have a $session or $global I can set to remedy the mutilation.
How do PowerShell people transfer raw binary from one stream out, into the next process?
May be not quite what you want (if you insist on using built in Powershell commands), but if you care about speed, use streams and asynchronous Read/Write. Powershell is a great tool because it can use any .NET classes seamlessly.
The script below can easily be extended to write to more than 2 destinations and can potentially handle arbitrary streams. You might want to add some error handling via try/catch there too. You may also try to play with buffered streams with various buffer size to optimize the code.
Some references:
FileStream.ReadAsync
FileStream.WriteAsync
CancellationToken
Task.GetAwaiter
-- 2021-12-09 update: Code is modified a little to reflect suggestions from comments.
# $InputPath, $Output1Path, $Output2Path are parameters
[Threading.CancellationTokenSource] $cancellationTokenSource = [Threading.CancellationTokenSource]::new()
[Threading.CancellationToken] $cancellationToken = $cancellationTokenSource.Token
[int] $bufferSize = 64*1024
$fileStreamIn = [IO.FileStream]::new($inputPath,[IO.FileMode]::Open,[IO.FileAccess]::Read,[IO.FileShare]::None,$bufferSize,[IO.FileOptions]::SequentialScan)
$fileStreamOut1 = [IO.FileStream]::new($output1Path,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::None,$bufferSize)
$fileStreamOut2 = [IO.FileStream]::new($output2Path,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::None,$bufferSize)
try{
[Byte[]] $bufferToWriteFrom = [byte[]]::new($bufferSize)
[Byte[]] $bufferToReadTo = [byte[]]::new($bufferSize)
$Time = [System.Diagnostics.Stopwatch]::StartNew()
$bytesRead = $fileStreamIn.read($bufferToReadTo,0,$bufferSize)
while ($bytesRead -gt 0){
$bufferToWriteFrom,$bufferToReadTo = $bufferToReadTo,$bufferToWriteFrom
$writeTask1 = $fileStreamOut1.WriteAsync($bufferToWriteFrom,0,$bytesRead,$cancellationToken)
$writeTask2 = $fileStreamOut2.WriteAsync($bufferToWriteFrom,0,$bytesRead,$cancellationToken)
$readTask = $fileStreamIn.ReadAsync($bufferToReadTo,0,$bufferSize,$cancellationToken)
$writeTask1.Wait()
$writeTask2.Wait()
$bytesRead = $readTask.GetAwaiter().GetResult()
}
$time.Elapsed.TotalSeconds
}
catch {
throw $_
}
finally{
$fileStreamIn.Close()
$fileStreamOut1.Close()
$fileStreamOut2.Close()
}

How do I get notified when a command is finished and Powershell is back to prompt?

I'm doing some testing job, and a part of it is to regularly write files to some hardware. The thing is, the write can take as short as 20 seconds, or as long as several minutes. Staring at the screen and waiting for it to be done is a huge waste of time, so I'm wondering if there is a way to get notified (like making a beep sound by using [console]::beep() ) when the command is done and the Powershell is back to prompt?
You can edit your prompt function:
$function:prompt = #"
{0}
[console]::beep()
"# -f $function:prompt.ToString()
A more concise version (compliments of mklement0) if you don't mind semi-colon delimited commands:
$function:prompt = "$function:prompt; [console]::beep()"
PowerShell Alert when command is finished :
"Answer" | out-file response.txt
[console]::beep(500,300)
Powershell > [console]::beep for 1-Minute :
[console]::beep(500,60000)
Also - Try using the "Asterisk" Alert :
[System.Media.SystemSounds]::Asterisk.Play()

ffmpeg blackdetect, start_black value to mkv chapter?

I'm trying to do automatic detect chapter with blackdetect with ffmpeg.
When I use blackdetect I get result but what is the result? Its not frames? Also. Is it possible to do a script/bat-file (for windows 10, powershell or cmd) to convert the result to a "mkv xml-file" so It can be imported with mkvtoolnix?
ffmpeg -i "movie.mp4" -vf blackdetect=d=0.232:pix_th=0.1 -an -f null - 2>&1 | findstr black_duration > output.txt
result:
black_start:2457.04 black_end:2460.04 black_duration:3
black_start:3149.46 black_end:3152.88 black_duration:3.41667
black_start:3265.62 black_end:3268.83 black_duration:3.20833
black_start:3381.42 black_end:3381.92 black_duration:0.5
black_start:3386.88 black_end:3387.38 black_duration:0.5
black_start:3390.83 black_end:3391.33 black_duration:0.5
black_start:3824.29 black_end:3824.58 black_duration:0.291667
black_start:3832.71 black_end:3833.08 black_duration:0.375
black_start:3916.29 black_end:3920.29 black_duration:4
Please see the documentation on this function here. Specifically this line:
Output lines contains the time for the start, end and duration of the detected black interval expressed in seconds.
If you read further down the page you will see another function blackframe which does a similar thing but outputs the frame number rather than seconds.
If the mkvtoolnix xml file has a chapter definition facility you will be able to create a script that takes the ffmpeg output and dumps it into the correct format in any language of your choice.

Manage inputs from external command in a powershell script

First, I would like to apologize in case that the title is not descriptive enough, I'm having a hard time dealing with this problem. I'm trying to build an automation for a svn merge using a powershell script that will be executed for another process. The function that I'm using looks like this:
function($target){
svn merge $target
}
Now, my problem occurs when there are conflicts in the merge. The default behavior of the command is request an input from the user and proceed accordingly. I would like to automatize this process using predefined values (show the differences and then postpone the merge), but I haven't found a way to do it. In summary, the workflow that I am looking to accomplish is the following:
Detect whether the command execution requires any input to proceed
Provide a default inputs (in my particular case "df" and then "p")
Is there any way to do this in powershell? Thank you so much in advance for any help/clue that you can provide me.
Edit:
To clarify my question: I would like to automatically provide a value when a command executed within a powershell script require it, like in the following example:
Requesting user input
Edit 2:
Here is a test using the snippet provided by #mklement0. Unfortunately, It didn't work as expected, but I thought it was wort to add this edition to clarify the question per complete
Expected behavior:
Actual result:
Note:
This answer does not solve the OP's problem, because the specific target utility, svn, apparently suppresses prompts when the process' stdin input isn't coming from a terminal (console).
For utilities that do still prompt, however, the solution below should work, within the constraints stated.
Generally, before attempting to simulate user input, it's worth investigating whether the target utility offers programmatic control over the behavior, via its command-line options, which is both simpler and more robust.
While it would be far from trivial to detect whether a given external command is prompting for user input:
you can blindly send the presumptive responses,
which assumes that no situational variations are needed (except if a particular calls happens not to prompt at all, in which case the input is ignored).
Let's assume the following batch file, foo.cmd, which puts up 2 prompts and echoes the input:
#echo off
echo begin
set /p "input1=prompt 1: "
echo [%input1%]
set /p "input2=prompt 2: "
echo [%input2%]
echo end
Now let's send responses one and two to that batch file:
C: PS> Set-Content tmp.txt -Value 'one', 'two'; ./foo.cmd '<' tmp.txt; Remove-Item tmp.txt
begin
prompt 1: one
[one]
prompt 2: two
[two]
end
Note:
For reasons unknown to me, the use of an intermediate file is necessary for this approach to work on Windows - 'one', 'two' | ./foo.cmd does not work.
Note how the < must be represented as '<' to ensure that it is passed through to cmd.exe and not interpreted by PowerShell up front (where < isn't supported).
By contrast, 'one', 'two' | ./foo does work on Unix platforms (PowerShell Core).
You can store the SVN command line output into a variable and parse through that and branch as you desire. Each line of output is stored into a new enumerator (cli output stored in PS variables is in array format)
$var = & svn merge $target
$var