Thursday 31 March 2016

The Pluralsight Mobile App Sucks

There is a request to allow offline storage of courses on the SD card but it's been active for over two years now.

This can't be an insurmountable problem as quite a few apps quite happily allow this:


In any case, yesterday after I could not update several apps for lack of space for about the umpteenth time, I thought, enough is enough and I endeavored to find a solution.

I first wrote a small script in PowerShell that called Handle.exe on a loop, to get the list of files being opened by the pluralsight desktop app and wrote that list of files to a file:

if (-not (test-path 'handle.exe'))
{
 write-host "Can't find handle.exe"
 break;
}

$files= @()

while($true) 
{
 $video =.\Handle.exe -p pluralsight | Select-String mp4
 
 if ($video)
 {
  $path = $video.Line.Split(')')[1] 

  if (-not($files.Contains($path)))
  {
   Write-Host $path
   Write-Host "Files Added So far: " + $($files.Count)
   $files+=$path
   $files > file.txt
  }
 }
}

This required me to click on each chapter (video) of the course to get it to be logged by handle, which while a little bit annoying I thought could be automated somehow with CodedUI or AutoHotKey but the former was not very reliable and I didn't have the patience for the latter.

The above script worked on the first course I tried but it was a bit hit and miss on the second and after a lot of playing about I realized that for small videos it would simply not work, not sure why that is, is it reading the file quicker than the loop loops?  I've not stopped to do the maths but it seems odd in any case.

The second part of the process was the script below, which moves the files to a new folder so that they can be copied safely across to the phone/tablet:

param ( [Parameter(Mandatory)] [ValidateScript({Test-Path $_})][string]$sourceFile, [Parameter(Mandatory)][ValidateScript({Test-Path $_})][string]$destination)

$files = Get-Content $sourceFile
$counter = 0 

foreach($file in $files)
{ 
 $dest = $(join-path $destination $counter) + ".mp4"
 Copy-Item $file.Trim() $dest
 $counter++
}

If the first script grabbed all files, then this script worked fine, but like I said, it didn't always do it, it seemed not to work for small files.

So, I did a bit more googleing, alas, it seems that there is no strace equivalent in Windows and then when I was about to give up*, I hit upon Process Monitor, which does exactly what I needed, in other words, and among other things, it can list the files that an application opens. In order to do this, all that is needed is the correct filter and a little bit more of PowerShell scripting.

Open Process Monitor -> Filter and set the following filters (in green):


In reality, it's probably enough to do the path ends with mp4 filter as no operation seems to generate a single entry for a file read, which means that some processing will be needed, but the QueryNetworkOpenInformationFile Operation seems to generate the fewer hits so I stuck with that.

Note, sometimes it simply doesn't register anything, in which case, just restart Process Monitor.

With this running, I can now go through all the chapters of the course that I want to watch on my mobile and I get something like this:

Now it's time to export the results to a CSV file, which you can do by simply pressing CTRL + S

Finally, it's time to process the file that I've just saved, for which I slightly modified the second PowerShell script:

param ( [Parameter(Mandatory)] [ValidateScript({Test-Path $_})][string]$sourceFile, [Parameter(Mandatory)][ValidateScript({Test-Path $_})][string]$destination)

$source = Import-CSV $sourceFile
$files=@()
$source.Path | % { if (!$files.Contains($_)){$files+=$_}}

$counter = 0 

foreach($file in $files)
{
 if ($counter -lt 10) { $prefix= "0" + $counter} else {$prefix=$counter}
 $dest = $(join-path $destination $prefix) + ".mp4"
 Copy-Item $file.Trim() $dest
 $counter++
}

The above script is fine, but I'd rather have a single file than loads of files, so I use ffmpeg to concatenate the clips:
param ( [Parameter(Mandatory)] [ValidateScript({Test-Path $_})][string]$sourceFile, [Parameter(Mandatory)][string]$destFile, [Parameter(Mandatory)] [ValidateScript({Test-Path $_})][string]$ffmpeg)

$source = Import-CSV $sourceFile
$files=@()
$source.Path | % { if (!$files.Contains("file '" + $_.Replace('\','\\') + "'")){$files+="file '" + $_.Replace('\','\\') + "'"}}

$fileList = (Get-Date).Ticks

Set-Content -Path $fileList -Value $files

$x= $ffmpeg + " -f concat -i $filelist -codec copy $destFile"

Invoke-Expression $x

Remove-Item $fileList
Remove-Item $sourceFile -Confirm
Using this technique is actually quite flexible I can create individual videos for each module if a single video for the whole course is too unwieldy.

Unfortunately, this last step doesn't appear to work too well on new pluralsight videos, which is annoying as there doesn't appear to be a free Video Player for Android that does all of these:
  • Play all files from a directory.
  • Play at up 1.5x speed.
  • Allow the video to fill the screen.
So I say that there is no free video player that does this at the moment for Android.