Pentax-http.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
<#PSScriptInfo
 
.VERSION 0.0.1
 
.GUID fd054f67-0a09-4db8-8b5c-a1f2150043b9
 
.AUTHOR James O'Neill
 
.COMPANYNAME Mobula Consulting Ltd
 
.COPYRIGHT (c) James O'Neill 2016
 
.TAGS Pentax DSLR Remote WIFI
 
.LICENSEURI https://opensource.org/licenses/MS-PL
 
.DESCRIPTION
   Script to to manage Wifi equipped Pentax Cameras
#>
 

<#
    No warranty of any kind is offered for this script; it is provided "as-is" and without support.
    You may use it ONLY at your own risk.
    The information to get me started was at http://www.pentaxforums.com/forums/184-pentax-k-s1-k-s2/295501-k-s2-wifi-laptop-3.html
    and I learned lots by looking at Claus Zielke's work at https://sourceforge.net/projects/pentaxks2wifiremote/
    I recommend Claus's program if you want to shoot over WiFi with control over settings and/or with Live view.
    This script is designed to run while you are shooting and download preview images as they are shot - which his is not.
#>



<#
.Synopsis
    Script to to manage Wifi equipped Pentax Cameras
.Example
    Pentax-http
    Starts the script, outputs some information about the camera and its settings,
    downloads todays photos to the default folder (my pictures\temp) and
    monitors the camera for new photos - building a html file to preview them -
    until either the script is stopped or the connection to the cameras is lost.
.Example
    Pentax-http -NewOnly
    As with the previous example, except that it doesn't copy existing files from today.
.Example
    Pentax-http.ps1 -Path C:\Users\James\Pictures\Dec-26 -CutOff "26 December 2016 00:01"
    As with previous examples, copies files shot after 00:01 on 26th December and places them in a named folder
.Example
    Pentax-http.ps1 -ShowBrowser -ClearFiles
    Removes any pre-existing files, from the default folder and loads the preview in the default web browser (this
    works better with a tablet in portrait mode). The preview will be updated with each download.
    Uses the default folder and todays date as the cut off.
#>

[cmdletbinding()]
Param (
    #Specifies an IP address for the camera, if is not using the default.
    $CameraIP         = "192.168.0.1", 
    #Specifies a cut off date/time - older pictures will not be downloaded.
    [datetime]$CutOff = ([datetime]::Today ) ,
    #Location for files to be download to.
    $Path             = (Join-Path -Path ([environment]::GetFolderPath([System.Environment+specialfolder]::MyPictures)) -ChildPath "Temp"),
    #If Specified, files won't be downloaded (and -path will be ignored).
    [switch]$NoDownload,
    #If specified any JPEG files in the download folder will be deleted.
    [Switch]$ClearFiles,
    #Time to wait after polling the camera for new pictures.
    $PauseSeconds     = 10 ,
    #The script builds a preview HTML file, if ShowBrowser is specified it is opened and you can just refresh it.
    [switch]$ShowBrowser,
    #If specified only new files will be downloaded (and any value for CutOff is ignored). Old files will still be built into the preview HTML .
    [switch]$NewOnly   
)

#Ensure System.Drawing.Bitmap is loaded - it is used to rotate images.
[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") | Out-Null
if ([System.Drawing.Imaging.ImageFormat]::Jpeg) {Write-Verbose -Message "System.Drawing assembly is availbable for image processing"}
else                                            {Write-Warning -Message "Images may not be rotated or saved because System.Drawing assembly did not load."}

#The script has download turned on unless -NoDownload is specified. Everything else requires -Download.
$Download  = -not $NoDownload 
#If the directory specified as -PATH doesn't exist, create it, or if -Clearfiles was specfied delete JPGs from it.
if (-not (Test-path -Path $Path -PathType Container)) {
                                  New-Item    -Path  $Path -ItemType Directory | Out-Null
}
if ($ClearFiles)                 {Remove-Item -Path "$path\*.JPG" -ErrorAction SilentlyContinue}

#After script has run details are in $cameraFileList, so you can do (for example) $cameraFileList | out-gridview;
#If it doesn't exist already, define it as an array.
if (-not $global:cameraFileList) { $global:cameraFileList = @() }
if ($NewOnly)                    { 
    # If there are files already in the list use them to calculate the cutoff for new files. Otherwise use the local time,
    # but if the computer and camera don't agree on the time there can be problems. Camera settings don't include its time.
    if ($global:cameraFileList.where({$_.datetime}))  {
           $CutOff      = ($global:cameraFileList.where({$_.datetime}) | Measure-Object -Maximum -Property datetime).Maximum
    }
    else { $CutOff      = [datetime]::Now }
}

Function Format-TimeValue        {
<#
.Synopsis
    Converts Pentax's time value (two integers with a "." between them) into a printable shutter speed.
.Example Format-TimeValue 1.250
Returns "1/250th"
.Example Format-TimeValue 25.10
Returns "2.5Sec"
#>

  Param (
        [parameter(ValueFromPipeline=$true)]
        [Alias("TV")]
        [string]$TimeValue
  ) 
  process {
    foreach ($tv in $TimeValue) {
        #if it is already formatted return it as is , otherwise it should be two integers seperated with a "."
        #if it is "1.xxx" 1.500, for example, that's 1/500th. If it written 25.10 that's 25/10 i.e. 2.5 secs.
        #So replace "1." with "1/" or do the division operation, and add "th" or "Sec" as needed. Don't forget to escape "." in regex!
        
        if     ($tv    -match "^1/\d+th$|^[.\d]+sec$") {$tv}
        elseif ($tv -notmatch "^\d+\.\d+$")            {Write-Warning "$tv isn't correctly formatted."; return}
        elseif ($tv    -match "1\.")                   {($tv -replace '1\.','1/') + 'th'}
        else  {
            [int[]]$v=$tv -split '\.'
            ($v[0]/$v[1]).ToString() + "Sec"
        }
    }
  }
} 

Function Write-Picinfo           {
<#
.Synopsis
    Writes a one line summary of a picture to the console.
.Example
    Get-CameraFiles -Download | Write-Picinfo
    Gets, downloads and rotates files writing a one line summary of each to the console.
#>

    Param   (
        #Picture(s) to write details to console.
        [Parameter(ValueFromPipeline=$true)]
        $Picture
    )
    Process {
        Foreach ($p in $Picture) { 
            $Listing = 'File: {0}/{1} {2:f} ISO:{3,7}, Exp. Comp:{4,5}, {6,9} @ {5,7}' -f $p.dir , $p.file, $p.dateTime , 
                                            $p.ISO , $p.ExposureCompensation, $p.Apperture, $p.Shutter 
            if ($p.SavePath -ne ""){$Listing += " SAVED "}
            if ($p.rotated  -gt 0) {$Listing += " Rotated " + $p.rotated}

            #Use a different colour so we can see if we are shooting JPG or RAW.
            if ($p.file -match '\.JPG') {Write-Host -ForegroundColor Yellow $Listing } 
            else                        {Write-Host -ForegroundColor Green  $Listing }
        }
    }
}

<# Requests to the camera generally take the form
     * Send an HTTP GET to a URL in the form http://<<ip Address>>/v1/<function>
     * Process the JSON data which is returned.
   Two different ones are POST to /v1/camera/shoot with body of af=on / af=off or af=auto takes a picture
   and PUT to /v1/params/camera sets params: put "" is equiv of the green button
          
   The following functions all return FALSE if there is an error (e.g. the camera was turned off!)
#>


Function Request-CameraSettings  {
<#
.Synopsis
    Asks the camera for its properties. By default returns a summary of the information.
.Example
    (Request-CameraSettings -Raw).avlist
    Gives the possible apperture settings
#>

param(
    #If Specified returns the data unprocessed
    [switch]$Raw
) 
    try   {
      $settings = Invoke-WebRequest -Method Get -Uri "http://$CameraIP/v1/props" | ConvertFrom-Json 
      if ($raw) {$settings} 
      else      {
        Select-Object -InputObject $settings -Property Model,SerialNo,FirmwareVersion, Battery , 
            @{n="Hot"                 ;e={[convert]::ToBoolean($_.hot)}} , 
            @{n="Space"               ;e={($_.storages | ForEach-Object {'{0}: {1,5} {2,6} shots remaining' -f $_.NAME,$_.REMAIN , $_.format } )-join '; ' }},
            @{n="Shots"               ;e={($_.storages | Measure-Object -Sum -Property remain).sum}},
            FocusMode, shootMode, effect, filter
            @{n="ExposureMode"        ;e={$_.exposuremode + ' ' + $_.exposureModeOption}} ,
            @{n="ExposureCompensation";e={$_.xv}}  ,
            @{n="Apperture"           ;e={"f/"+$_.av }},
            @{n="Shutter"             ;e={Format-TimeValue $_.tv } },
            @{n="ISO"                 ;e={$_.sv}}
        }
    }
    catch {$false}   #Use a return value of FALSE to indicate failure - we don't send any parameters to the camera, so probably means we couldn't see it.
}

Function Request-CameraFileList  {
[cmdletbinding()]
<#
.Synopsis
    Asks the camera for a full list of its files (which will include duplicates if it is set for RAW + JPG).
#>

   
    # TODO Haven't tested filling first card when in the 2 card sequential mode of K1
    # does camera continue to return card 1, or return card 1 + 2 or return only card the active card (which has become 2)
    # K1 (v1.40) appears to only show what is on the primary card (slot 2 if slot 1 is empty) so if RAWs are written to card 1
    # and JPEGS to card 2 only RAW files are returned
    Param()     
    try   {
     (Invoke-WebRequest -Method Get -Uri "http://$CameraIP/v1/photos" | ConvertFrom-Json).DIRS | ForEach-Object {
        $n = $_.name 
        Write-Verbose -Message ($_.files.count.ToString() + " files in $n")
        foreach ($f in $_.files) {
            $fSplit = $f -split '\.'  #Will want the file name and file extension as distinct parts
            New-Object -TypeName psobject -Property @{Dir=$n; File=$f; Name=$fsplit[0]; ext=$fSplit[1]; Apperture=''; Shutter=''; ISO=''; ExposureCompensation=''; DateTime=$null; Orientation=0 ; Rotated=0; SavePath=""}
        }  
     }
    }
    Catch {$false}     #Use a return value of FALSE to indicate failure - we don't send any parameters to the camera, so probably means we couldn't see it.
} 

Function Request-CameraPicInfo   {
<#
.Synopsis
    Asks the camera for information about a specific picture.
.Example
   Request-CameraPicInfo IMGP0123.JPG
   Gets details of the named picture, if it is found in 100PENTX, otherwise returns FALSE.
.Example
   Request-CameraPicInfo IMGP12345.JPG
   Will return FALSE because this file name is not valid (too long).
.Example
   Request-CameraPicInfo -dir 101PENTX -file IMGP1234.JPG
   Gets details of the named picture from the 101PENTX directory.
.Example
   Request-CameraFileList | select -Last 1 | Request-CameraPicInfo
   Gets the list of files from the camera and requests info about the most recent.
#>

    param (
        #The filename (e.g. IMGP0001.JPG). The K1 (V1.40) is case insentive for names.
        [Parameter(ValueFromPipelineByPropertyName=$true)] 
        [string[]]$File, 
        #The folder where the picture lives (e.g. 101PENTX) if nothing is specified, 100PENTX will be assumed.
        [parameter(ValueFromPipelineByPropertyName=$true)]
        [string]$Dir = "100PENTX"

    )
    process { 
        foreach ($f in $File) {
            try {
                $uri    = 'http://{0}/v1/photos/{1}/{2}/info' -f $CameraIP, $Dir, $f 
                $result = Invoke-WebRequest -Method Get -Uri $uri |  ConvertFrom-Json 
                if ($result.errMsg -eq "OK") {$result} 
                else                    {$false } 
            } #FALSE indicates failure - either bad request (wrong file or dir) or camera didn't answer.
            catch                       {$false }    
        }
    }
}

Function Request-CameraLatest    {
<#
.Synopsis
    Asks the camera for information the most recent picture.
#>

    try {
        Invoke-WebRequest "http://$CameraIP/v1/photos/latest/info" | ConvertFrom-Json
    }
    Catch {$false}   #Use a return value of FALSE to indicate failure - we don't send any parameters to the camera, so probably means we couldn't see it.
}

Function Copy-CameraPic          {
[cmdletbinding()]
<#
.SYNOPSIS
    Copies images from the camera to the computer.
.EXAMPLE
    Copy-CameraPic imgp0123.dng
    Copies the 720x480 JPG view of the specified picture, if it is found in the 100PENTX directory; returns false if it is not found.
.EXAMPLE
    Copy-CameraPic -dir 100PENTX -File imgp1234.jpg
    Copies the 720x480 JPG view of the specified picture from 101PENTX directory; returns false if it is not found.
.EXAMPLE
   Get-CameraFiles | select -first 3 | Copy-CameraPic -Destination $pwd | ft name,length,rotation
   Gets the 3 most recent picture and copies them to the current directory showing their name, file size and rotation applied.
.EXAMPLE
   start ( Request-CameraLatest | Copy-CameraPic )
   Downloads the most recent file and opens it in the default image app.
#>

    param(
       #The filename (e.g. IMGP0001.JPG). The K-1 (V1.40) is case insentive for names.
       [parameter(ValueFromPipelineByPropertyName=$true)]
       $File, 
       #The folder where the picture lives (e.g. 101PENTX) if nothing is specified, 100PENTX will be assumed.
       [parameter(ValueFromPipelineByPropertyName=$true)]
       $Dir = "100PENTX",
       #The orientation of the picture : Pentax use 8 for camera rolled left. 6 For rolled camera right , and 3 for camera inverted.
       #If the camera is rolled left the image is turned to the right and vice vesa, so 8 needs to be turned 270 degrees, and 6 turned 90 degrees clockwise.
       [parameter(ValueFromPipelineByPropertyName=$true)]
       $Orientation , 
       #Folder or full name where the file should be saved.
       $Destination = $script:Path,
       #Image size: WiFi throughput makes full size images slow (even timing out in some cases) but view files are ~50KB JPEGS. DNG files don't provide thumbs on K-1 with V1.40.
       [ValidateSet("view","thumb", "full")]
       $Size = "view"
    )
    Process {
        try {
            #Build the URI, GET the file, and convert to a bitmap object; rotate if needed, save as JPG.
            $uri    = 'http://{0}/v1/photos/{1}/{2}?size={3}' -f $CameraIP, $Dir, $File, $Size
            $webReq = Invoke-WebRequest -Uri $uri -Method Get
            $bitmap = New-Object -TypeName System.Drawing.Bitmap -ArgumentList $webReq.RawContentStream 
            if     ($Orientation -eq 6) {
                    Write-Verbose -Message "Picture loaded, rotating 90" 
                    $bitmap.RotateFlip([System.Drawing.RotateFlipType]::Rotate90FlipNone)   
                    $rotation =  90 
            }
            elseif ($Orientation -eq 3) {
                    Write-Verbose -Message "Picture loaded, rotating 180" 
                    $bitmap.RotateFlip([System.Drawing.RotateFlipType]::Rotate180FlipNone)   
                    $rotation =  180 
            }
            elseif ($Orientation -eq 8) {
                    Write-Verbose -Message "Picture loaded, rotating 270"
                    $bitmap.RotateFlip([System.Drawing.RotateFlipType]::Rotate270FlipNone)
                    $rotation =  270  
            }
            else   {
                    Write-Verbose -Message "Picture loaded, no rotation"
                    $rotation =  0
            } 
            #Need to specify the save format explictly otherwise rotated pictures don't save as JPG format (some apps DO read non-JPEG format with JPG extension).
            if (Test-Path -Path $Destination -PathType Container) {$SavePath = Join-Path -Path $Destination -ChildPath ($File -replace 'PEF$|DNG$',"JPG")}
            else                                             {$SavePath = $Destination }
            Write-Verbose -Message "Saving Picture to $Destination"
            $bitmap.Save($SavePath, [System.Drawing.Imaging.ImageFormat]::Jpeg) 
            Get-item -Path $SavePath | Add-Member -PassThru -NotePropertyName Rotation -NotePropertyValue $rotation
        }
        catch {$false}   #Use a return value of FALSE to indicate failure - bad file name/path, problem processing, or no connection to camera
    }          
}

Function Set-Camera              {
<#
.Synopsis
    Sets the camera options **without checking validity**
.Description
    Request-CameraSettings -Raw will show the camera settings, and various lists for values they can take.
    For example AV might be 8.0 and Avlist will show the possible apperture values
    so you could send "Av=11" to set f/11. If the value isn't on the list the camera will return "bad request"
    "AV=11.0" or "Av=8" will cause errors because they need to be written as in the list.
.Example
    Set-Camera
    Used with no paramters this is eqivalent to pressing the green button
.Example
    Set-Camera -raw | Format-Table av,@{n="tv";e={Format-TimeValue $_.tv}},sv,xv
    Set camera returns the values from the camera which format to show the selected settings
.Example
    Set-Camera "sv=auto","xv=0.0","","effect=cim_natural"
    Sets multiple options, ISO (sensitivity value) to auto -this will return the current value, not "auto" ;
    exposure compensation (xv) to 0, exposure to program line, and processing to natural
    (After setting "effect", the K-1 needs a moment to be ready for then next change, so set this last)
#>

    param   ( 
        #The key/value pair to set. If empty, is equivalent to pressing green to return to default settings
        [parameter(ValueFromPipeline=$true)]
        [string[]]$Setting = "",
        #if specified returns whatever came back from the camera
        [switch]$Raw
    ) 
    process {
        foreach   ($s in $setting) {
            try   {
                $result =  Invoke-WebRequest -Method Put -Uri "http://$CameraIP/v1/params/camera" -body $s | ConvertFrom-Json
                if     ($Raw)                    {$result}
                elseif ($result.errMsg -eq "OK") {
                    if (-not $s) {"Defaults set"}    
                    else         {
                        #return what the value IS - some things like exposure mode may not be set to what we asked for
                        $param = ($s -split "=")[0]
                        "{0}={1}" -f $param, $result.$param  
                    }
                } 
                else                             {Write-Warning "Camera responded to '$s' with '$($result.errMsg)'."}
            }
            catch {$false}
        }
    }
}
 
Function Request-CameraShoot     {
[cmdletbinding(SupportsShouldProcess=$true)]
<#
.Synopsis
    Requests the camera to take a picture.
.Description
    On K-1 with f/w 1.40 the return data suggests no shot was taken. But the shutter fires and picture is there.
#>
    
    Param (
        #Auto focus mode.
        [ValidateSet("auto","on","off")]
        [Alias("AF")]
        $AutoFocus   = "Auto", 
        #If specified downloads the file
        [Switch]$Download,
        #Size of the file to download (ignored if -Download not specified)
        [ValidateSet("view","thumb", "full")]
        $Size = "view", 
        #Where to download the file (ignored if -Download not specified)
        $Path = $Script:Path 
    )
    try   {
      if ($PSCmdlet.ShouldProcess("Camera", "Take photo")) {
        $result = Invoke-WebRequest -Method Post -Uri "http://$CameraIP/v1/camera/shoot" -body "af=$AutoFocus" |  ConvertFrom-Json 
        if     ($result.errMsg -ne "OK") {
                    Write-Warning -Message "Camera didn't take picture with Autofocus set to AF. Message was $($result.errmsg)."
                    return
        } 
        elseif ($Download) { 
                    Start-sleep -Seconds 5 # need to wait for the camera to save and update what is in latest .
                    Request-CameraLatest | Copy-CameraPic -size $size -Destination $Path 
        }
        else {$true }
      }
    }
    catch {   $false}
}

Function Request-CameraFocus     {
[cmdletbinding(SupportsShouldProcess=$true)]
<#
.Synopsis
    Requests the camera to take a focus. Returns "Focused" or "Did not focus" (if the camera responds)
#>
    
    Param ()
    try   {
      if ($PSCmdlet.ShouldProcess("Camera", "Focus")) {
        #on my K-1 with 1.40 firmware focus centers is always an empty list and focus effective area is always 70,64
        $result = Invoke-WebRequest -Method Post -Uri "http://$CameraIP/v1/lens/focus" |  ConvertFrom-Json 
        if     ($result.errMsg -ne "OK") {
                    Write-Warning -Message "Camera returned an error. Message was $($result.errmsg)."
                    return
        } 
        elseif ($result.focused) {return "Focused"}
        else                     {return "Did not focus" }
      }
    }
    catch {   $false}   #Use a return value of FALSE to indicate failure - we don't send any parameters to the camera, so probably means we couldn't see it.
}

Function Get-CameraFiles         {
<#
.Synopsis
    Reads the list of files from the camera, gets details of new ones and (optionally) downloads them.
.Example
    Get-CameraFiles -CutOff ([datetime]::Today) -download
    Gets images which have been shot today, and downloads their previews to the default folder.
.Example
    Get-CameraFiles -CutOff ([datetime]::Today) -download -path "C:\Users\james\OneDrive\Pictures\MyShoot"
    Gets images which have been shot today, and downloads their previews to the a specified directory (in this case one which will be sync'd later).
.Example
    Get-CameraFiles
    Gets details of all images on the camera but does not download any. SavePath will be filled in for any already downloaded.
#>

    param (       
        #If Specified pictures shot before this time will be ignored
        [datetime]$CutOff, 
        #Files will only be downloaded if specified
        [switch]$Download,
        #Location to save the downloaded files, if applicable
        $Path = $script:Path,
        #Download size, if applicable: WiFi throughput makes full size images too slow for most uses but view files are <50KB JPEGS
        [ValidateSet("view","thumb", "full")]
        $Size = "view"
    ) 
    #region get / extend the list of camera files
    Write-Progress -Activity "Getting list of files from the camera" 
    #Get the cameraFileList from the camera - and catch lack of connection.
    #Files which aren't already in the Filenames list and aren't JPGs must be new RAW files - add them to the file list
    #Files which aren't already in the Filenames list and *are* JPGs get added to the file list if they don't have a matching RAW
    #The file list is a global variable so we can access it after the script has run. (It needs at least script: scope)
    $fileNames              = $cameraFileList.name    
    $allFiles               = Request-cameraFileList 
    if (-not $allFiles)     { Write-Warning -Message "Can't find the camera at $CameraIP. Check you are connected to its private WiFi name and try again." ; return } 
    $newRaws                = $allFiles.Where({($_.ext -ne "JPG") -and ($fileNames -notcontains $_.name)} )
    $newRawnames            = $newRaws.name
    $global:cameraFileList += $newRaws + $allFiles.where({($_.ext -eq "JPG") -and ($newRawnames -notcontains $_.name) -and $fileNames -notcontains $_.name} ) 
    Write-Progress -Activity "Getting file list" -Completed
    #endregion
    
    #Now we have a list of files, process it as newest name first [don't handle wrap round of names yet], get properties and fill them in and download if specified.
    foreach ($file in ($cameraFileList | Sort-Object -Property name -Descending)) {  
        Write-Progress -Activity "Getting file info" -CurrentOperation $file.file 
        $properties = Request-CameraPicInfo -dir $file.dir -file $file.file 
        if ($properties.captured -eq $true) {
            $file.DateTime  =  [convert]::ToDateTime($properties.datetime)
            $file.Shutter   =  (Format-TimeValue -TV $properties.tv) 
            $file.Apperture            =      "f/" + $properties.av
            $file.ISO                  =             $properties.sv
            $file.ExposureCompensation =             $properties.xv
            $file.Orientation          =             $properties.orientation
            if ($Download -and ($file.DateTime  -gt  $CutOff)) {
                $destination           = $file.Name + ".JPG"
                $destination           = Join-Path  -Path $Path -ChildPath $destination
                $cp = $file | Copy-CameraPic -Destination $destination -size $Size
                if ($cp.FullName) {
                      $file.Rotated    = $cp.Rotation
                      $file.Savepath   = $cp.FullName    
                }
                else {Write-Warning -Message "Problem downloading $($file.file)" } 
            }
        }
        else {Write-Warning -Message "Couldn't get info for $($file.file)" } #Expect to see this if recent files have been deleted and script is re-run
        #We're working back from oldest to newest; bail out if we've passed the CutOff point, otherwise return the file object.
        #Note that the most recent file in the list will ALWAYs have the date set. Other things rely on that.
        if ($file.dateTime -le $CutOff) {break}
        else                            {$file}
    }

    #Add paths for any previously downloaded files.
    $SavedFiles = (dir $path\*.jpg).name -replace '\.jpg',''
    foreach ($file in $cameraFileList.Where({-not $_.SavePath -and $savedFiles -contains $_.name})) {
        $file.SavePath = Join-Path  -Path $Path -ChildPath ($file.Name + ".JPG")
    }  
    Write-Progress -Activity "Getting file info" -Completed
}    

Function Watch-Camera            {
<#
.Synopsis
    Polls the camera and gets new files when there are any.
.Example
    Watch-Camera -Download -CutOff [DateTime]::now() -PauseSeconds 10 -HtmlPath "$path\images.htm"
    Dowloads new images from the camera to the default drectory, pausing 10 seconds between each check. Rebuilds a preview html file after each download.
    Note that if the times are not in Sync between computer the first new file(s) may be ignored, or one or more old files may be downloaded when a new file arrives.
.Example
    Watch-Camera -Download -CutOff [DateTime]::now() -PauseSeconds 10 -HtmlPath "$path\images.htm" -passthrough
    Performs the same actions as before but outputs objects describing the image.
}
#>

    param (
        #Files will only be downloaded if -Download is specified.
        [switch]$Download,
        #Location to save the downloaded files, if applicable .
        $Path         = $script:Path,
        #Time to wait after checking for new files.
        $PauseSeconds = $Script:PauseSeconds,
        #Pictures shot before the time cut off time are assumed to have been processed already.
        $CutOff       = $Script:CutOff,
        #HTML File to update
        $HtmlPath     = $script:HtmlPath, 
        #If Specified, watch-camera will return objects for each picture rather than writing output.
        [switch]$PassThru
    )
 
    while ($true) {
        Write-Progress -Activity "Watching camera. Press [Ctrl]+[c] or break wifi conection to exit." -Status  (
                                 "Next check at "+ [datetime]::Now.AddSeconds($PauseSeconds).ToLongTimeString()  )
        Start-Sleep -Seconds $PauseSeconds
        $latest = Request-CameraLatest  #This returns false if the camera connection drops, if this happens exit the loop.
        if     (-not $latest)  {break} 
        elseif ($latest.captured -and ([convert]::ToDateTime($latest.datetime) -gt $CutOff)) {
            #There is at least one new image to download. Get all new images (though probably there is only one).
            #if -passthru is specified return the objects, otherwise put them through Write-PicInfo for CONSOLE output.
            if ($passthru) { Get-CameraFiles -CutOff $CutOff -Download:$Download -Path $Path }
            else           { Get-CameraFiles -CutOff $CutOff -Download:$Download -Path $Path | Write-Picinfo} 
            
            #Set CutOff to be the latest picture. (Note need to use Greater/Less than OR EQUAL TO when comparing with this date.
            $CutOff = ($global:cameraFileList.Where({$_.datetime}) | Measure-Object -Maximum -Property datetime).Maximum
            
            #Output quick HTML preview - most recent first. If there are portrait orientation pictures, it will work much better on a portrait format screen.
            $cameraFileList | Where-Object {$_.savepath} | Sort-Object -Property DateTime -Descending | 
                ForEach-Object -Begin    {'<html><body><table width="100%" align="Center">'}`
                               -Process  {'<tr><td align="Center"><img width="85%" src="{0}" /><br/><b>{1}</b></td></tr>' -f $_.savepath, $_.file} `
                               -End {'</table></body></html>'} | 
                      Out-File -FilePath $HtmlPath 
        } 
    }
}

$cameraSettings = Request-CameraSettings
if ($cameraSettings) {                    ##### DEFAULT START-UP change at will
    #region Display Camera status
    #Clear-Host
    Write-Host ("`r`n" * 6)
    Write-Host -ForegroundColor Yellow -BackgroundColor DarkCyan (" Monitoring {0,-12}, Firmware:{1,-8}, S/N:{2,-29}"     -f $CameraSettings.model, 
                                                                 $cameraSettings.firmwareVersion, $cameraSettings.serialNo)
    if     ($cameraSettings.battery -gt 66 ) {Write-host -ForegroundColor Black -BackgroundColor Green  -NoNewline ("Battery:{0,4}% "          -f $cameraSettings.battery)}
    elseif ($cameraSettings.battery -gt 33 ) {Write-host -ForegroundColor Black -BackgroundColor Yellow -NoNewline ("Battery:{0,4}% "          -f $cameraSettings.battery)}
    else                                     {Write-host -ForegroundColor Black -BackgroundColor Red    -NoNewline ("Battery:{0,4}% "          -f $cameraSettings.battery)}
    if     ($cameraSettings.Shots -gt 500 )  {Write-Host -ForegroundColor Black -BackgroundColor Green  -NoNewline (" Space available {0,-50}" -f $cameraSettings.Space)}
    elseif ($cameraSettings.Shots -gt 100 )  {Write-Host -ForegroundColor Black -BackgroundColor Yellow -NoNewline (" Space available {0,-50}" -f $cameraSettings.Space)}
    else                                     {Write-Host -ForegroundColor Black -BackgroundColor Red    -NoNewline (" Space available {0,-50}" -f $cameraSettings.Space)}
    if     ($cameraSettings.Hot)             {Write-host -ForegroundColor Black -BackgroundColor Red   "Temperature warning" }
    Else                                     {Write-host "" }
    Write-Host ("Focus Mode:{1}; Shot Mode:{0}; Exposure mode:{2}; Exposure Compensation:{3}" -f  $cameraSettings.shootMode,
                         $cameraSettings.focusMode.toupper(),       $cameraSettings.ExposureMode, $cameraSettings.ExposureCompensation )
    #endregion
    
    #Get files on the camera before the CutOff
    Get-CameraFiles -Download:$Download -Path $Path  -CutOff $CutOff | Write-Picinfo
    
    #Output HTML to display these files
    $HtmlPath = (Join-Path -Path $Path -ChildPath "IMAGES.htm")
    $cameraFileList.Where({$_.savepath})  | Sort-Object -Property DateTime -Descending | 
        ForEach-Object -Begin    {'<html><body><table width="100%" align="Center">'} `
                       -Process  {'<tr><td align="Center"><img width="85%" src="{0}" /><br/><b>{1}</b></td></tr>' -f $_.savepath, $_.file} `
                       -End {'</table></body></html>'} |
              Out-File -FilePath $HtmlPath 
    
    if ($ShowBrowser) {Start-Process -FilePath $HtmlPath}
    #Watch for new files
    $CutOff = ($cameraFileList.where({$_.datetime}) | Measure-Object -Maximum -Property datetime).Maximum
    Watch-camera -Download:$Download -Path $Path -CutOff $CutOff -PauseSeconds $PauseSeconds -HtmlPath $HtmlPath 
}
else                 { write-warning "Can't find the camera at $CameraIP. Check you are connected to its private WiFi name and try again." }