Skip to content

Instantly share code, notes, and snippets.

@SQLDBAWithABeard
Last active August 26, 2025 12:36
Show Gist options
  • Select an option

  • Save SQLDBAWithABeard/daf0f7b5493c6a5f588029ed2a9a7b69 to your computer and use it in GitHub Desktop.

Select an option

Save SQLDBAWithABeard/daf0f7b5493c6a5f588029ed2a9a7b69 to your computer and use it in GitHub Desktop.
trail cam timelaps to video script
function Start-TimelapseProcessing {
<#
.SYNOPSIS
Copies image files from SD card, renames them, creates a timelapse video using ffmpeg, and compresses it with HandBrake.
- Source: D:\DCIM\100MEDIA
- Destination: $env:USERPROFILE\OneDrive\Desktop\100media\<date>
- Renames files to lowercase
- Creates timelapse video (mp4)
- Compresses video with HandBrake
#>
# Variables
$folder = Join-Path $env:USERPROFILE 'OneDrive\Desktop\100media'
$date = Get-Date -Format yyyyMMdd
$destinationFolder = "$folder\$date"
$sdcardPath = 'D:\DCIM\100MEDIA'
$imagePattern = "$destinationFolder\IMAG*.jpg"
$ffmpegFolder = "$folder\$date\imag%04d.jpg"
$ffmpegOutput = "$folder\$date-output.mp4"
$handbrakeOutput = "$folder/$date-handbrakeoutput.mp4"
$handbrakeCLI = './HandBrakeCLI'
$handbrakeDrive = 's:'
$zipFile = "$destinationFolder\$date-images.zip"
$networkpath = '\\Big_Beard_NAS\video\Timelapse\BillyCam\'
$zippedFolder = "$networkpath\zippedFiles"
$startTime = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
Write-PSFMessage -Level Significant -Message "Script started at $startTime"
write-PSFMessage -Level Significant -Message "Stop OneDrive sync temporarily to avoid file lock issues."
Get-Process onedrive | Stop-Process -Force
Write-PSFMessage -Level Host -Message "Starting script to copy files from SD card, rename them, create a timelapse video, and compress it with HandBrake."
Write-PSFMessage -Level Significant -Message "Checking if destination folder exists: $destinationFolder"
if (!(Test-Path -Path $destinationFolder)) {
Write-PSFMessage -Level Important -Message "Creating destination folder: $destinationFolder"
New-Item -ItemType Directory -Path $destinationFolder | Out-Null
}
Write-PSFMessage -Level Significant -Message "Getting files from SD card: $sdcardPath"
$sdcardfiles = Get-ChildItem -Path $sdcardPath | Sort-Object CreationTime
if ($sdcardfiles.Count -eq 0) {
Write-PSFMessage -Level Warning -Message "No files found in $sdcardPath"
break
} else {
Write-PSFMessage -Level Important -Message "Copying files from SD card to $destinationFolder"
$i = 1
$prefix = "imag"
$extension = ".jpg"
foreach ($file in $sdcardfiles) {
$newName = "{0}{1:D4}{2}" -f $prefix, $i, $extension
$destinationPath = Join-Path $destinationFolder $newName
Write-PSFMessage -Level Verbose -Message "Copying $($file.FullName) to $destinationFolder"
Copy-Item -Path $file.FullName -Destination $destinationPath
$i++
}
}
Write-PSFMessage -Level Important -Message "Creating timelapse video with ffmpeg: $ffmpegOutput"
$ffmpegexpression = 'ffmpeg -framerate 24 -i "{0}" -c:v libx264 -pix_fmt yuv420p "{1}"' -f $ffmpegFolder, $ffmpegOutput
Invoke-Expression $ffmpegexpression
# If the ffmpeg output file doesn't exist, stop the script
if (!(Test-Path -Path $ffmpegOutput)) {
Write-PSFMessage -Level Error -Message "ffmpeg output file does not exist: $ffmpegOutput"
break
} else {
Write-PSFMessage -Level Important -Message "ffmpeg output file created successfully: $ffmpegOutput"
}
$handbrakeOutputMsg = "Compressing video with HandBrakeCLI: $handbrakeOutput"
Write-PSFMessage -Level Important -Message $handbrakeOutputMsg
Set-Location -Path $handbrakeDrive
$handbrakeCmd = "$handbrakeCLI -i $ffmpegOutput -o '$handbrakeOutput' -f mp4 -t 1 --angle 1 -c 1-1 -m -e x264 -q 22 --encoder-preset fast --encoder-profile main --encoder-level 4.0 --decomb=mode=7 --comb-detect=mode=3:spatial-metric=2:spatial-thresh=1:motion-thresh=1:block-thresh=40:block-width=16:block-height=16 -r 30 --cfr --subtitle scan --subtitle-forced --subtitle-burned --audio-copy-mask aac --audio-fallback av_aac"
Write-PSFMessage -Level Significant -Message "Running HandBrakeCLI command: $handbrakeCmd"
Invoke-Expression $handbrakeCmd
# Validate that the HandBrake output file exists and is smaller than the ffmpeg output file but not zero bytes; if it is, delete the ffmpeg output file
if (Test-Path -Path $handbrakeOutput) {
$ffmpegFileInfo = Get-Item -Path $ffmpegOutput
$handbrakeFileInfo = Get-Item -Path $handbrakeOutput
if ($handbrakeFileInfo.Length -lt $ffmpegFileInfo.Length -and $handbrakeFileInfo.Length -gt 0) {
Write-PSFMessage -Level Important -Message "HandBrake output file is valid. Deleting ffmpeg output file: $ffmpegOutput"
Remove-Item -Path $ffmpegOutput
} else {
Write-PSFMessage -Level Warning -Message "HandBrake output file is not smaller than ffmpeg output file or is zero bytes. Keeping both files."
}
} else {
Write-PSFMessage -Level Error -Message "HandBrake output file does not exist: $handbrakeOutput"
break
}
# Creates a copy of the HandBrake output file to the network path
if (Test-Path -Path $handbrakeOutput -ErrorAction SilentlyContinue) {
$destinationPath = Join-Path $networkpath (Split-Path -Path $handbrakeOutput -Leaf)
Write-PSFMessage -Level Important -Message "Copying HandBrake output file to network path: $destinationPath"
Copy-Item -Path $handbrakeOutput -Destination $destinationPath
} else {
Write-PSFMessage -Level Error -Message "HandBrake output file does not exist: $handbrakeOutput"
}
## zip all the raw image files in the destination folder and move them to the network path
Write-PSFMessage -Level Important -Message "Zipping raw image files in destination folder: $destinationFolder"
if (Test-Path -Path $zipFile) {
Write-PSFMessage -Level Warning -Message "Zip file already exists. Deleting existing zip file: $zipFile"
Remove-Item -Path $zipFile
}
Compress-Archive -Path $imagePattern -DestinationPath $zipFile
# Creates date directory in network path if it doesn't exist and copies the zipped raw image files there
if (!(Test-Path -Path $zippedFolder)) {
Write-PSFMessage -Level Important -Message "Creating zipped files directory in network path: $zippedFolder"
New-Item -ItemType Directory -Path $zippedFolder
}
Write-PSFMessage -Level Important -Message "Moving zipped raw image files to network path: $zippedFolder"
Move-Item -Path $zipFile -Destination "$zippedFolder"
# delete all raw image files on sd card and processed folder after confirming the zip file exists in the network path
if (Test-Path -Path "$zippedFolder\$date-images.zip") {
Write-PSFMessage -Level Important -Message "Zip file exists in network path. Deleting raw image files from SD card and processed folder."
Write-PSFMessage -Level Important -Message "Deleting raw image files from SD card: $sdcardPath"
Get-ChildItem -Path $sdcardPath | Remove-Item -Force
Write-PSFMessage -Level Important -Message "Deleting raw image files from processed folder: $destinationFolder"
Get-ChildItem -Path $destinationFolder -Filter "imag*.jpg" | Remove-Item -Force
} else {
Write-PSFMessage -Level Warning -Message "Zip file does not exist in network path. Not deleting raw image files."
}
write-PSFMessage -Level Significant -Message "Restarting OneDrive sync."
# Restart OneDrive
Start-Process -FilePath "C:\Program Files\Microsoft OneDrive\OneDrive.exe" -ArgumentList "/background"
$endTime = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
$duration = (New-TimeSpan -Start $startTime -End $endTime).ToString("hh\:mm\:ss")
Write-PSFMessage -Level Important -Message "Script completed at $endTime and took $duration to run."
}
Just when I thought I was out, they pull me back in.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment