Add Satsuma scripts
This commit is contained in:
+247
@@ -0,0 +1,247 @@
|
||||
# Satsuma installer. Run it with Install.bat.
|
||||
# Adds a "Convert to (Satsuma)" entry to the right-click menu. Nothing needs admin rights.
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
|
||||
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$root = Join-Path $env:LOCALAPPDATA 'Satsuma'
|
||||
$bin = Join-Path $root 'bin'
|
||||
$tmp = Join-Path $env:TEMP ('satsuma_setup_' + [guid]::NewGuid().ToString('N'))
|
||||
|
||||
function Say([string]$text) { Write-Host $text }
|
||||
function Good([string]$text) { Write-Host $text -ForegroundColor Green }
|
||||
function Warn([string]$text) { Write-Host $text -ForegroundColor Yellow }
|
||||
|
||||
function Set-Reg([string]$subKey, [string]$name, [string]$value) {
|
||||
$k = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey($subKey)
|
||||
$k.SetValue($name, $value)
|
||||
$k.Close()
|
||||
}
|
||||
|
||||
function Remove-Reg([string]$subKey) {
|
||||
try { [Microsoft.Win32.Registry]::CurrentUser.DeleteSubKeyTree($subKey, $false) } catch { }
|
||||
}
|
||||
|
||||
function Find-Magick {
|
||||
$dir = Join-Path $bin 'magick'
|
||||
if (Test-Path -LiteralPath $dir) {
|
||||
$f = Get-ChildItem -LiteralPath $dir -Filter 'magick.exe' -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||
if ($f) { return $f.FullName }
|
||||
}
|
||||
foreach ($base in @($env:ProgramFiles, ${env:ProgramFiles(x86)})) {
|
||||
if ($base -and (Test-Path -LiteralPath $base)) {
|
||||
$d = Get-ChildItem -LiteralPath $base -Filter 'ImageMagick*' -Directory -ErrorAction SilentlyContinue | Sort-Object Name -Descending | Select-Object -First 1
|
||||
if ($d) {
|
||||
$m = Join-Path $d.FullName 'magick.exe'
|
||||
if (Test-Path -LiteralPath $m) { return $m }
|
||||
}
|
||||
}
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host ' Satsuma setup' -ForegroundColor Yellow
|
||||
Write-Host ' Right-click file converter for Windows'
|
||||
Write-Host ''
|
||||
|
||||
try {
|
||||
[void](New-Item -ItemType Directory -Force -Path $root)
|
||||
[void](New-Item -ItemType Directory -Force -Path $bin)
|
||||
[void](New-Item -ItemType Directory -Force -Path $tmp)
|
||||
|
||||
# ---- 1. copy the program files ----
|
||||
Say 'Step 1 of 4: copying Satsuma files...'
|
||||
foreach ($f in @('Convert.ps1', 'Run.vbs', 'satsuma.ico')) {
|
||||
$from = Join-Path $here $f
|
||||
if (-not (Test-Path -LiteralPath $from)) { throw ('A file is missing next to the installer: ' + $f + '. Unzip the whole folder first.') }
|
||||
$to = Join-Path $root $f
|
||||
Copy-Item -LiteralPath $from -Destination $to -Force
|
||||
try { Unblock-File -LiteralPath $to } catch { }
|
||||
}
|
||||
Good ' done'
|
||||
|
||||
# ---- 2. ffmpeg (video, audio, webp) ----
|
||||
$ffmpegExe = Join-Path $bin 'ffmpeg.exe'
|
||||
if (Test-Path -LiteralPath $ffmpegExe) {
|
||||
Say 'Step 2 of 4: ffmpeg is already here.'
|
||||
} else {
|
||||
Say 'Step 2 of 4: downloading ffmpeg (about 90 MB, this can take a minute)...'
|
||||
$zip = Join-Path $tmp 'ffmpeg.zip'
|
||||
$urls = @(
|
||||
'https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip',
|
||||
'https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip'
|
||||
)
|
||||
$got = $false
|
||||
foreach ($u in $urls) {
|
||||
try {
|
||||
Invoke-WebRequest -Uri $u -OutFile $zip -UseBasicParsing
|
||||
$got = $true
|
||||
break
|
||||
} catch {
|
||||
Warn (' that source failed, trying the next one: ' + $_.Exception.Message)
|
||||
}
|
||||
}
|
||||
if (-not $got) { throw 'Could not download ffmpeg. Check the internet connection and run Install.bat again.' }
|
||||
$x = Join-Path $tmp 'ffmpeg_x'
|
||||
Expand-Archive -LiteralPath $zip -DestinationPath $x -Force
|
||||
$found = Get-ChildItem -LiteralPath $x -Filter 'ffmpeg.exe' -Recurse | Select-Object -First 1
|
||||
if (-not $found) { throw 'ffmpeg.exe was not inside the download.' }
|
||||
Copy-Item -LiteralPath $found.FullName -Destination $ffmpegExe -Force
|
||||
Good ' done'
|
||||
}
|
||||
try {
|
||||
$v = (& $ffmpegExe -version 2>&1 | Select-Object -First 1) | Out-String
|
||||
Say (' ' + $v.Trim())
|
||||
} catch {
|
||||
Warn ' ffmpeg was installed but did not start. Video and audio may not work.'
|
||||
}
|
||||
|
||||
# ---- 3. ImageMagick (HEIC, WebP, PNG, JPG) ----
|
||||
$magickPath = Find-Magick
|
||||
if ($magickPath) {
|
||||
Say 'Step 3 of 4: ImageMagick is already here.'
|
||||
} else {
|
||||
Say 'Step 3 of 4: downloading ImageMagick (for HEIC and other pictures)...'
|
||||
$base = 'https://imagemagick.org/archive/binaries/'
|
||||
$name = $null
|
||||
try {
|
||||
$html = (Invoke-WebRequest -Uri $base -UseBasicParsing).Content
|
||||
$best = $null
|
||||
$bestKey = $null
|
||||
foreach ($m in [regex]::Matches($html, 'ImageMagick-(\d+)\.(\d+)\.(\d+)-(\d+)-portable-Q16-HDRI-x64\.7z')) {
|
||||
$key = [version]('{0}.{1}.{2}.{3}' -f $m.Groups[1].Value, $m.Groups[2].Value, $m.Groups[3].Value, $m.Groups[4].Value)
|
||||
if (($null -eq $bestKey) -or ($key -gt $bestKey)) { $bestKey = $key; $best = $m.Value }
|
||||
}
|
||||
$name = $best
|
||||
} catch { }
|
||||
if (-not $name) { $name = 'ImageMagick-7.1.2-25-portable-Q16-HDRI-x64.7z' }
|
||||
|
||||
$ok = $false
|
||||
try {
|
||||
$arc = Join-Path $tmp 'magick.7z'
|
||||
$z = Join-Path $tmp '7zr.exe'
|
||||
Invoke-WebRequest -Uri ($base + $name) -OutFile $arc -UseBasicParsing
|
||||
Invoke-WebRequest -Uri 'https://www.7-zip.org/a/7zr.exe' -OutFile $z -UseBasicParsing
|
||||
$dest = Join-Path $bin 'magick'
|
||||
[void](New-Item -ItemType Directory -Force -Path $dest)
|
||||
& $z x $arc ('-o' + $dest) -y | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { throw 'The 7-Zip extractor reported an error.' }
|
||||
$ok = $true
|
||||
} catch {
|
||||
Warn (' portable download did not work: ' + $_.Exception.Message)
|
||||
}
|
||||
|
||||
if (-not $ok) {
|
||||
Say ' trying winget instead (Windows may ask for permission)...'
|
||||
try {
|
||||
$wg = Get-Command winget.exe -ErrorAction SilentlyContinue
|
||||
if ($wg) {
|
||||
& $wg.Source install --id ImageMagick.ImageMagick -e --silent --accept-package-agreements --accept-source-agreements
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
$magickPath = Find-Magick
|
||||
if ($magickPath) { Good ' done' }
|
||||
else { Warn ' ImageMagick could not be installed. Pictures will not convert, but video, audio and documents still can.' }
|
||||
}
|
||||
|
||||
if ($magickPath) {
|
||||
try {
|
||||
$fmt = & $magickPath -list format 2>&1 | Out-String
|
||||
if ($fmt -match '(?m)^\s*HEIC\b') { Good ' HEIC pictures are supported.' }
|
||||
else { Warn ' This ImageMagick build does not list HEIC support, so iPhone pictures may fail.' }
|
||||
} catch {
|
||||
Warn ' Could not check HEIC support.'
|
||||
}
|
||||
}
|
||||
|
||||
# ---- 4. the right-click menu ----
|
||||
Say 'Step 4 of 4: adding the right-click menu...'
|
||||
$iconPath = Join-Path $root 'satsuma.ico'
|
||||
$runVbs = Join-Path $root 'Run.vbs'
|
||||
|
||||
$video = @(
|
||||
@('mp4', 'MP4'), @('mkv', 'MKV'), @('webm', 'WebM'), @('avi', 'AVI'),
|
||||
@('mov', 'MOV'), @('wmv', 'WMV'), @('flv', 'FLV'), @('mpg', 'MPG')
|
||||
)
|
||||
$audio = @(
|
||||
@('mp3', 'MP3'), @('wav', 'WAV'), @('flac', 'FLAC'),
|
||||
@('ogg', 'OGG'), @('m4a', 'M4A'), @('opus', 'Opus')
|
||||
)
|
||||
$image = @(
|
||||
@('jpg', 'JPG'), @('png', 'PNG'), @('webp', 'WebP'),
|
||||
@('bmp', 'BMP'), @('tiff', 'TIFF'), @('pdf', 'PDF')
|
||||
)
|
||||
$same = @{ 'm4v' = 'mp4'; 'mpeg' = 'mpg'; 'jpeg' = 'jpg'; 'tif' = 'tiff'; 'heif' = 'heic' }
|
||||
|
||||
$groups = @(
|
||||
@{ Exts = @('mp4', 'm4v', 'mkv', 'webm', 'avi', 'mov', 'wmv', 'flv', 'mpg', 'mpeg', '3gp', 'ogv'); Items = ($video + @(, @('mp3', 'MP3 (sound only)'))) },
|
||||
@{ Exts = @('mp3', 'wav', 'flac', 'ogg', 'm4a', 'aac', 'opus', 'wma'); Items = $audio },
|
||||
@{ Exts = @('heic', 'heif', 'webp', 'png', 'jpg', 'jpeg', 'bmp', 'tif', 'tiff'); Items = $image },
|
||||
@{ Exts = @('gif'); Items = ($image + @(, @('mp4', 'MP4 video'))) },
|
||||
@{ Exts = @('doc', 'docx', 'rtf', 'txt', 'odt', 'xls', 'xlsx', 'ods', 'ppt', 'pptx', 'odp'); Items = @(, @('pdf', 'PDF')) }
|
||||
)
|
||||
|
||||
$count = 0
|
||||
foreach ($g in $groups) {
|
||||
foreach ($ext in $g.Exts) {
|
||||
$menuKey = 'Software\Classes\SystemFileAssociations\.' + $ext + '\shell\Satsuma'
|
||||
Remove-Reg $menuKey
|
||||
$mine = $ext
|
||||
if ($same.ContainsKey($ext)) { $mine = $same[$ext] }
|
||||
|
||||
Set-Reg $menuKey 'MUIVerb' 'Convert to (Satsuma)'
|
||||
Set-Reg $menuKey 'SubCommands' ''
|
||||
Set-Reg $menuKey 'Icon' $iconPath
|
||||
|
||||
$n = 0
|
||||
foreach ($item in $g.Items) {
|
||||
$t = $item[0]
|
||||
$label = $item[1]
|
||||
if ($t -eq $mine) { continue }
|
||||
$n++
|
||||
$verbKey = '{0}\shell\{1:D2}_{2}' -f $menuKey, $n, $t
|
||||
Set-Reg $verbKey '' $label
|
||||
Set-Reg ($verbKey + '\command') '' ('wscript.exe "{0}" {1} "%1"' -f $runVbs, $t)
|
||||
}
|
||||
$count++
|
||||
}
|
||||
}
|
||||
Good (' done (' + $count + ' file types)')
|
||||
|
||||
# ---- Windows 11 note ----
|
||||
$build = 0
|
||||
try { $build = [int](Get-CimInstance Win32_OperatingSystem).BuildNumber } catch { }
|
||||
if ($build -ge 22000) {
|
||||
$clsid = 'Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32'
|
||||
$existing = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($clsid)
|
||||
if ($null -eq $existing) {
|
||||
Write-Host ''
|
||||
Warn 'Windows 11 hides this menu under "Show more options" in the right-click menu.'
|
||||
$a = Read-Host 'Turn on the classic full right-click menu so Satsuma shows right away? (Y/N)'
|
||||
if ($a -match '^[Yy]') {
|
||||
Set-Reg $clsid '' ''
|
||||
Set-Content -LiteralPath (Join-Path $root 'classic-menu.txt') -Value 'set by Satsuma'
|
||||
Good ' classic menu turned on. Sign out and back in (or restart) to see it.'
|
||||
}
|
||||
} else {
|
||||
$existing.Close()
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Good 'All set.'
|
||||
Say 'Right-click a picture, video, sound or document file and look for "Convert to (Satsuma)".'
|
||||
Say 'The converted file lands next to the original. The original is never touched.'
|
||||
}
|
||||
catch {
|
||||
Write-Host ''
|
||||
Write-Host ('Setup stopped: ' + $_.Exception.Message) -ForegroundColor Red
|
||||
}
|
||||
finally {
|
||||
if (Test-Path -LiteralPath $tmp) { Remove-Item -LiteralPath $tmp -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
Reference in New Issue
Block a user