nurjns

Video Compressor Tool Kontextmenü - TEIL 1

Jan 14th, 2026 (edited)
4,574
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. # Dieses Skript komprimiert alle markierten .mp4-Dateien über das Kontextmenü per FFmpeg mit einem wählbaren CRF-Wert wahlweise als H.265 oder AV1. Der ursprüngliche Zeitstempel (Änderungsdatum) der Datei wird im Nachhinein neu gesetzt, und die Ausgabedateien erhalten angepasste Namen (_crfXX.mp4 bzw. mit AV1 _AV1_crfXX.mp4). Bei Videos, die von einer DJI Action Kamera oder der DJI Mimo App stammen, wird automatisch das Datum aus dem Dateinamen verwendet, unabhängig von dem Änderungsdatum. Zusätzlich wird die Zeitzone aus der Abfrage berücksichtigt.
  2.  
  3. # Verwendung des Scripts:
  4.  
  5. # Script als "video_compress.ps1" mit Kodierung "UTF-8-BOM" anlegen.
  6. # "ffmpeg-release-essentials.zip" hier herunterladen, entpacken und aus dem Ordner "bin" die ffmpeg.exe und ffprobe.exe in das Verzeichnis $ToolDir (Hier im Script im Bereich "PFAD HIER ANPASSEN" definiert) kopieren: https://www.gyan.dev/ffmpeg/builds/
  7. # "exiftool-XXX.zip" hier herunterladen, entpacken, in das Verzeichnis $ToolDir kopieren und die .exe-Datei umbenennen in "exiftool.exe": https://exiftool.org/
  8. # REG-Datei mit Endung ".reg" erstellen: https://pastebin.com/V4sn7Mqc
  9. # Pfade in der REG-Datei anpassen, wo "video_compress.ps1" liegt
  10. # Datei doppelklicken und alles bejahen
  11.  
  12. # Version 1.0.4 - 24.01.2026 - @nurjns
  13.  
  14. param (
  15.     [string]$FilePath,
  16.     [int]$CRF = 25,
  17.     [string]$Codec = 'HEVC'
  18. )
  19.  
  20. # PFAD HIER ANPASSEN
  21. $ToolDir = 'X:\PFAD\ZU\ASSETS'
  22.  
  23. $FFmpeg = Join-Path $ToolDir 'ffmpeg.exe'
  24. $FFprobe = Join-Path $ToolDir 'ffprobe.exe'
  25. $ExifTool = Join-Path $ToolDir 'exiftool.exe'
  26.  
  27. Add-Type -AssemblyName System.Windows.Forms
  28. Add-Type -AssemblyName System.Drawing
  29.  
  30. # Mutex zur Instanz-Steuerung
  31. $Mutex = New-Object System.Threading.Mutex($false, 'VideoCompressor_nurjns_Mutex')
  32. $IsFirstInstance = $Mutex.WaitOne(10)
  33.  
  34. if (-not $IsFirstInstance) {
  35.     if (-not [string]::IsNullOrWhiteSpace($FilePath) -and $FilePath -match '\.mp4$') {
  36.         $Connected = $false
  37.         $Attempts = 0
  38.         while (-not $Connected -and $Attempts -lt 5) {
  39.             try {
  40.                 $Client = New-Object System.IO.Pipes.NamedPipeClientStream('.', 'VideoCompressorQueue', [System.IO.Pipes.PipeDirection]::Out)
  41.                 $Client.Connect(500)
  42.                 $Writer = New-Object System.IO.StreamWriter($Client)
  43.                 $Writer.WriteLine($FilePath)
  44.                 $Writer.Flush()
  45.                 $Client.Close()
  46.                 $Connected = $true
  47.             } catch {
  48.                 $Attempts++
  49.                 Start-Sleep -Milliseconds 200
  50.             }
  51.         }
  52.     }
  53.     exit
  54. }
  55.  
  56. $MissingTools = @()
  57. foreach ($Tool in @($FFmpeg, $FFprobe, $ExifTool)) {
  58.     if (-not (Test-Path -LiteralPath $Tool)) {
  59.         $MissingTools += $Tool
  60.     }
  61. }
  62.  
  63. if ($MissingTools.Count -gt 0) {
  64.     $Message = "Folgende Tools wurden nicht gefunden:`n`n" + ($MissingTools -join "`n")
  65.     [System.Windows.Forms.MessageBox]::Show($Message, 'Fehler: Tools fehlen', [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
  66.     $Mutex.ReleaseMutex()
  67.     exit
  68. }
  69.  
  70. $Queue = [System.Collections.Generic.List[string]]::new()
  71. # Nur hinzufügen, wenn es eine MP4 ist
  72. if (-not [string]::IsNullOrWhiteSpace($FilePath) -and $FilePath -match '\.mp4$') {
  73.     $Queue.Add($FilePath)
  74. }
  75.  
  76. $Form = New-Object System.Windows.Forms.Form
  77. $Form.Text = 'Video Compressor - 1.0.4 by @nurjns'
  78. $Form.Size = New-Object System.Drawing.Size(450, 230)
  79. $Form.StartPosition = 'CenterScreen'
  80. $Form.FormBorderStyle = 'FixedDialog'
  81. $Form.MaximizeBox = $false
  82. $Form.TopMost = $false
  83.  
  84. $LabelCurrent = New-Object System.Windows.Forms.Label
  85. $LabelCurrent.Location = New-Object System.Drawing.Point(15, 15)
  86. $LabelCurrent.Size = New-Object System.Drawing.Size(400, 45)
  87. $LabelCurrent.Text = 'Sammle Dateien...'
  88.  
  89. $ProgressCurrent = New-Object System.Windows.Forms.ProgressBar
  90. $ProgressCurrent.Location = New-Object System.Drawing.Point(15, 65)
  91. $ProgressCurrent.Size = New-Object System.Drawing.Size(400, 25)
  92.  
  93. $LabelTotal = New-Object System.Windows.Forms.Label
  94. $LabelTotal.Location = New-Object System.Drawing.Point(15, 110)
  95. $LabelTotal.Size = New-Object System.Drawing.Size(400, 20)
  96. $LabelTotal.Text = "Warte auf Start..."
  97.  
  98. $ProgressTotal = New-Object System.Windows.Forms.ProgressBar
  99. $ProgressTotal.Location = New-Object System.Drawing.Point(15, 135)
  100. $ProgressTotal.Size = New-Object System.Drawing.Size(400, 20)
  101.  
  102. $Form.Controls.AddRange(@($LabelCurrent, $ProgressCurrent, $LabelTotal, $ProgressTotal))
  103.  
  104. function Get-ExifTag {
  105.     param([string]$Tag, [string]$Path)
  106.     $val = & $ExifTool -s3 -api 'QuickTimeUTC' $Tag $Path | Out-String
  107.     return $val.Trim()
  108. }
  109.  
  110. $Form.Add_Shown({
  111.     $ProcessedCount = 0
  112.     $OverallSuccess = $true
  113.    
  114.     while ($true) {
  115.         $Server = New-Object System.IO.Pipes.NamedPipeServerStream('VideoCompressorQueue', [System.IO.Pipes.PipeDirection]::In, 1, [System.IO.Pipes.PipeTransmissionMode]::Byte, [System.IO.Pipes.PipeOptions]::Asynchronous)
  116.         $AsyncResult = $Server.BeginWaitForConnection($null, $null)
  117.  
  118.         while (-not $AsyncResult.AsyncWaitHandle.WaitOne(200)) {
  119.             if ($ProcessedCount -lt $Queue.Count) {
  120.                 $CurrentPath = $Queue[$ProcessedCount]
  121.  
  122.                 $ProgressTotal.Maximum = $Queue.Count
  123.                 $ProgressTotal.Value = $ProcessedCount
  124.                 $LabelTotal.Text = "Gesamtfortschritt: Video $($ProcessedCount + 1) von $($Queue.Count)"
  125.  
  126.                 $Result = Process-Video -Path $CurrentPath
  127.                 if ($Result -eq $false) { $OverallSuccess = $false }
  128.  
  129.                 $ProcessedCount++
  130.                 $ProgressTotal.Value = $ProcessedCount
  131.                 $LabelTotal.Text = "Gesamtfortschritt: Video $ProcessedCount von $($Queue.Count)"
  132.             } else {
  133.                 if (-not $AsyncResult.AsyncWaitHandle.WaitOne(1000)) {
  134.                     if ($ProcessedCount -gt 0 -and $OverallSuccess) {
  135.                         $LabelCurrent.Text = "Kodierung erfolgreich!"
  136.                         $ProgressCurrent.Value = $ProgressCurrent.Maximum
  137.                         [System.Windows.Forms.Application]::DoEvents()
  138.                         [console]::Beep(500, 200)
  139.                         Start-Sleep -Seconds 2
  140.                     }
  141.                     $Form.Close()
  142.                     return
  143.                 }
  144.             }
  145.             [System.Windows.Forms.Application]::DoEvents()
  146.         }
  147.  
  148.         try {
  149.             $Server.EndWaitForConnection($AsyncResult)
  150.             $Reader = New-Object System.IO.StreamReader($Server)
  151.             $NewPath = $Reader.ReadLine()
  152.             # Auch hier beim Empfang prüfen
  153.             if (-not [string]::IsNullOrWhiteSpace($NewPath) -and $NewPath -match '\.mp4$') {
  154.                 $Queue.Add($NewPath)
  155.             }
  156.         } catch {}
  157.         $Server.Dispose()
  158.     }
  159. })
  160.  
  161. function Process-Video {
  162.     param([string]$Path)
  163.    
  164.     # Sicherheitsprüfung auf MP4
  165.     if (-not (Test-Path -LiteralPath $Path) -or $Path -notmatch '\.mp4$') { return $false }
  166.    
  167.     $File = Get-Item -LiteralPath $Path
  168.     $BaseName = $File.BaseName
  169.    
  170.     if ($BaseName -match '(_crf\d+|_AV1_crf\d+)$') {
  171.         $LabelCurrent.Text = "Überspringe: $($File.Name)`n(Bereits verarbeitet)"
  172.         return $true
  173.     }
  174.    
  175.     if ($Codec -eq 'AV1') {
  176.         $Suffix = "_AV1_crf$CRF"
  177.         $CpuUsed = if ($CRF -le 20) { 2 } elseif ($CRF -le 28) { 4 } elseif ($CRF -le 35) { 6 } else { 8 }
  178.         $VArgs = "-c:v libaom-av1 -crf $CRF -cpu-used $CpuUsed -pix_fmt yuv420p"
  179.     } else {
  180.         $Suffix = "_crf$CRF"
  181.         $VArgs = "-c:v libx265 -crf $CRF -preset slow -pix_fmt yuv420p -tag:v hvc1"
  182.     }
  183.    
  184.     $OutFile = Join-Path $File.DirectoryName ($BaseName + $Suffix + '.mp4')
  185.     if (Test-Path -LiteralPath $OutFile) {
  186.         $LabelCurrent.Text = "Überspringe: Datei existiert bereits.`n$($File.Name)"
  187.         return $true
  188.     }
  189.  
  190.     $TotalFramesRaw = & $FFprobe -v error -select_streams v:0 -count_packets -show_entries stream=nb_read_packets -of csv=p=0 "$Path"
  191.     $TotalFrames = if ($TotalFramesRaw -match '^\d+$') { [int]$TotalFramesRaw } else { 0 }
  192.  
  193.     $ProgressCurrent.Style = if ($TotalFrames -gt 0) { 'Continuous' } else { 'Marquee' }
  194.     if ($TotalFrames -gt 0) { $ProgressCurrent.Maximum = $TotalFrames; $ProgressCurrent.Value = 0 }
  195.  
  196.     $Offset = Get-ExifTag '-OffsetTimeOriginal' "$Path"
  197.     if ([string]::IsNullOrWhiteSpace($Offset)) { $Offset = Get-ExifTag '-OffsetTime' "$Path" }
  198.     if ([string]::IsNullOrWhiteSpace($Offset)) {
  199.         $TZ = [System.TimeZoneInfo]::FindSystemTimeZoneById('W. Europe Standard Time')
  200.         $Offset = if ($TZ.IsDaylightSavingTime((Get-Date))) { '+02:00' } else { '+01:00' }
  201.     }
  202.  
  203.     $Timestamp = ''
  204.     if ($BaseName -match '^DJI_(\d{14})_') {
  205.         $dt = $Matches[1]
  206.         $Timestamp = $dt.Substring(0,4) + ':' + $dt.Substring(4,2) + ':' + $dt.Substring(6,2) + ' ' + $dt.Substring(8,2) + ':' + $dt.Substring(10,2) + ':' + $dt.Substring(12,2)
  207.     } elseif ($BaseName -match 'dji_mimo_(\d{8})_(\d{6})') {
  208.         $d = $Matches[1]; $t = $Matches[2]
  209.         $Timestamp = $d.Substring(0,4) + ':' + $d.Substring(4,2) + ':' + $d.Substring(6,2) + ' ' + $t.Substring(0,2) + ':' + $t.Substring(2,2) + ':' + $t.Substring(4,2)
  210.     } else {
  211.         $Timestamp = Get-ExifTag '-CreateDate' "$Path"
  212.         if ([string]::IsNullOrWhiteSpace($Timestamp) -or $Timestamp -eq '0000:00:00 00:00:00') {
  213.             $Timestamp = $File.LastWriteTime.ToString('yyyy:MM:dd HH:mm:ss')
  214.         }
  215.     }
  216.  
  217.     $StartInfo = New-Object System.Diagnostics.ProcessStartInfo
  218.     $StartInfo.FileName = $FFmpeg
  219.     $StartInfo.Arguments = "-y -i `"$Path`" $VArgs -movflags +faststart -c:a aac -b:a 160k `"$OutFile`""
  220.     $StartInfo.RedirectStandardError = $true
  221.     $StartInfo.UseShellExecute = $false
  222.     $StartInfo.CreateNoWindow = $true
  223.  
  224.     $Process = [System.Diagnostics.Process]::Start($StartInfo)
  225.     while (-not $Process.HasExited) {
  226.         $Line = $Process.StandardError.ReadLine()
  227.         if ($Line -match 'frame=\s*(\d+)') {
  228.             $CurrentFrame = [int]$Matches[1]
  229.             if ($TotalFrames -gt 0 -and $CurrentFrame -le $TotalFrames) {
  230.                 $ProgressCurrent.Value = $CurrentFrame
  231.                 $Percent = [math]::Round(($CurrentFrame / $TotalFrames) * 100)
  232.                 $LabelCurrent.Text = "Datei: $($File.Name)`nEncoding: $Percent% ($CurrentFrame / $TotalFrames Frames)"
  233.             }
  234.         }
  235.         [System.Windows.Forms.Application]::DoEvents()
  236.     }
  237.  
  238.     if ($Process.ExitCode -eq 0) {
  239.         $LabelCurrent.Text = "Schreibe Metadaten..."
  240.         [System.Windows.Forms.Application]::DoEvents()
  241.         $ExifArgs = @(
  242.             '-overwrite_original',
  243.             "-DateTimeOriginal=$Timestamp$Offset",
  244.             "-OffsetTimeOriginal=$Offset",
  245.             "-FileModifyDate=$Timestamp",
  246.             $OutFile
  247.         )
  248.         & $ExifTool $ExifArgs | Out-Null
  249.         return $true
  250.     } else {
  251.         [System.Windows.Forms.MessageBox]::Show('Fehler beim Encoding! ExitCode: ' + $Process.ExitCode, 'Fehler')
  252.         [console]::Beep(200, 500)
  253.         return $false
  254.     }
  255. }
  256.  
  257. $Form.ShowDialog()
  258. $Mutex.ReleaseMutex()
Advertisement