0

So I needed a script that watches a directory and converts files using HandbrakeCLI. I found a part of this powershell here on stackoverflow and I adjusted some things for my project.

$global:watch = "C:\~\cmp\" ### watching directory
$global:convert = "C:\~\convert\" ### handbrakecli and preset location
$global:outp = "C:\~\output_folder\" ### output location
$global:extIn = ".mkv"
$global:extOut = ".mp4"

Write-Host "Watching directory =  $watch"
Write-Host "HandbrakeCLI / json preset location = $convert"
Write-Host "Output directory = $outp"
Write-Host "Input extension = $extIn ; Output extension = $extOut"
Write-Host "Waiting for change in directory..."

### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
$watcher = New-Object System.IO.FileSystemWatcher;
$watcher.Path = $watch;
$watcher.Filter = "*"+$extIn;
$watcher.IncludeSubdirectories = $false;
$watcher.EnableRaisingEvents = $false;

### DEFINE ACTIONS AFTER AN EVENT IS DETECTED
$action = 
{
    $path = $Event.SourceEventArgs.FullPath;
    $handbrakecli = $convert+"HandBrakeCLI.exe";
    $fl = Split-Path $path -leaf;
    Write-Host "New file found: $fl";

    $flName, $flExt = $fl.split('.')
    $mp4File = $watch+"\"+$flName+$extOut
    $changeType = $Event.SourceEventArgs.ChangeType
    $logline = "$(Get-Date), $changeType, $path"
    Add-content -path ($convert+"log.txt") -value $logline
    Write-Host "Added entry to log"

    Write-Host "Start converting using HandbrakeCLI..."
    & cmd.exe /c $handbrakecli -i $path -o $mp4File --preset-import-file my_preset.json
    Write-Host "Done converting!"

    Write-Host "Moving file to folder..."
    & cmd.exe /c move /Y $mp4File $outp
    Write-Host "File moved!"

    & cmd.exe /c del $path /F
    Write-Host "$fl has been removed from local folder"
    }    

### DECIDE WHICH EVENTS SHOULD BE WATCHED 
    Register-ObjectEvent $watcher "Created" -Action $action
    Register-ObjectEvent $watcher "Changed" -Action $action
    Register-ObjectEvent $watcher "Renamed" -Action $action
    while ($true) {sleep 5}

While at first everything seemed to work, I started to notice that "sometimes" the subtitles were not added or green frames were inserted (or replaced original frame) after every frame (normal - green - normal - green - etc.).

An example: I added 2 mkv files to the directory, the 1st one got converted just fine with subtitles while the 2nd file didn't have any subtitles.

I'm an amateur when it comes to this stuff, but I think it has something to do with the & cmd.exe /c. I also found that you could to Start-Process in powershell, but I don't know how to use it.

So if someone could help me convert this & cmd.exe /c $handbrakecli -i $path -o $mp4File --preset-import-file my_preset.json to something with Start-Process ..., maybe it will help me out.

EDIT

So I made the changes that Tomalak suggested (simpler this way), but Move-Item and Remove-Item don't seem to work.

EDIT 2

Added -LiteralPath as argument for Move-Item / Remove-Item (needed for filenames containt square brackets)

$inputFolder = "C:\~\cmp\"
$outputFolder = "C:\~\output_folder\"
$handbrake = "C:\~\convert\HandBrakeCLI.exe"
$presetJson ="C:\~\convert\my_preset.json"
$extIn = "mkv"
$extOut = "mp4"

while ($true) {
    Get-ChildItem -Path $inputFolder -Filter "*.$extIn" | ForEach-Object {
        $inFile = $_.FullName
        $outFile = $inputFolder + $_.FullName.split('\.')[-2] + ".$extOut" #changed this because I wanted the file in the same directory as input file

        Write-Host "Converting: $inFile"
        & $handbrake -i $inFile -o $outFile --preset-import-file $presetJson

        if ($LASTEXITCODE -eq 0) {
            Move-Item -LiteralPath $outFile $outputFolder -Force #move to output folder
            Write-Host "Done: $outFile"
            Remove-Item -LiteralPath $inFile -Force #removing the input item, not output
            Write-Host "Removed input file!"
        } else {
            Write-Error "Conversion failed!"
        }
    }
    sleep 5
}

While subtitles are added to all output files, I still get green-flickering sometimes. I used 3 files as a test run, result: 1st flickering, 2nd OK, 3rd flickering. I have no clue why some are fine and some got the flickering. So I'm considering to maybe use ffmpeg instead.

EDIT 3

For future visitors: use ffmpeg instead of HandbrakeCLI:

ffmpeg.exe -i "C:\~\inputfile.mkv" -filter_complex "subtitles='C\:/Users/~/inputfile.mkv'" -c:v libx264 -preset veryfast -b:v 2750k -c:a aac $outputfile.mp4

  • It's entirely unclear what's the point of using `cmd.exe` to start your handbrake CLI program in the first place. Powershell is perfectly capable of starting a program on its own, it doesn't need the help of `cmd.exe` for that. – Tomalak Oct 19 '17 at 11:57
  • Ahh, I get it, it's for parallel processing. If offloads the work to a new process whenever a file change notification arrives, so the main process can keep listening to file change notifications. – Tomalak Oct 19 '17 at 12:10
  • In terms of *"if someone could help me convert [...] to something with `Start-Process`"* - Have you looked at [the documentation](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/start-process?view=powershell-5.1)? There are actually quite a few helpful samples in there as well. – Tomalak Oct 19 '17 at 12:16
  • Wel I looked at the documentation for a bit and it's has to be somethings with `Start-Process -FilePath &handbrakecli -ArgumentList ""`. But for the argumentlist can I just copy/paste the arguments that I used before so that it becomes something like this `Start-Process -FilePath &handbrakecli -ArgumentList "-i $path -o $mp4File --preset-import-file my_preset.json"`? –  Oct 19 '17 at 14:24
  • I'd expect that to work, actually. Does it? – Tomalak Oct 19 '17 at 14:28
  • What I don't expect though, is that it makes much of a difference. Green frames and missing subtitles are something that points to Handbrake messing up, not the shell. In my tests I've noticed that file system change notifications will occur repeatedly for the same file. Maybe it has something to do with that. – Tomalak Oct 19 '17 at 14:34
  • Thinking about it, here is what I suggest. Scrap the entire "File System Notification" approach. It's over-engineered and more trouble than it's worth. You are planning on leaving the script running anyway, so whether you let it wait 5 seconds and then process file system notifications, or whether you let it wait 5 seconds and then simply loop all the files in the folder makes no difference at all. – Tomalak Oct 19 '17 at 15:13
  • Benefits: You can call handbrake directly from powershell, without the need for a sub process and the script will get *a whole lot* simpler. Drawbacks: None, actually. – Tomalak Oct 19 '17 at 15:15

1 Answers1

0

Instead of using file system notifications, structure your script around a simple endless loop:

$inputFolder = "C:\~\cmp"
$outputFolder = "C:\~\convert"
$handbrake = "C:\~\convert\HandBrakeCLI.exe"
$presetJson = "C:\~\convert\my_preset.json"
$extIn = "mkv"
$extOut = "mp4"

while ($true) {
    Get-ChildItem -Path $inputFolder -Filter "*.$extIn" | ForEach-Object {
        $inFile = $_.FullName
        $outFile = "$($_.BaseName).$extOut"

        if (Test-Path $outFile) { Remove-Item $outFile -Force -LiteralPath }

        Write-Host "Converting: $inFile"
        & $handbrake -i $inFile -o $outFile --preset-import-file $presetJson

        if ($LASTEXITCODE -eq 0) {
            Move-Item $outFile $outputFolder -Force -LiteralPath
            Write-Host "Done: $outFile"
        } else {
            Write-Error "Conversion not successful."
        }
    }
    sleep 5
}

The & makes Powershell execute whatever program the $handbrake variable points to.

As an exercise you can convert the top-level variables to script parameters, so that you can re-use the script for other batch jobs.

Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • See **Edit**. The `Remove-Item` and `Move-Item` commands don't seem to work (that's why I had `& cmd.exe /c move /Y $mp4File $outp` in the first place). –  Oct 20 '17 at 09:06
  • Powershell can delete and move files, there really is no need to let `cmd.exe` do that. "don't seem to work" is not an error description. Be specific. In my tests files got moved without any problems with this exact code. – Tomalak Oct 20 '17 at 09:15
  • Well I did some searching and by using `-LiteralPath` it solved the problem. It was probably because the filenames contained square brackets. –  Oct 20 '17 at 09:40
  • That could be a reason, yes. I didn't test with overly fancy filenames. :) – Tomalak Oct 20 '17 at 09:43
  • Thanks for your help ;) Learned a bit more about powershell today because of this. Still, that handbrakecli isnt working correctly everytime is just frustrating. I'm just gonna try ffmpeg another time and see how things work out. –  Oct 20 '17 at 09:51
  • Handbrake also uses ffmpeg AFAIK, so you might end up digging for a while. Good luck. I've edited in the `-LiteralPath`. – Tomalak Oct 20 '17 at 09:53