# ============================================================ # MOHO FILE ANALYZER # ============================================================ Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing Add-Type -AssemblyName System.IO.Compression Add-Type -AssemblyName System.IO.Compression.FileSystem # ============================================================ # SELECTED FOLDER # ============================================================ $script:SelectedFolder = "" # ============================================================ # MAIN FORM # ============================================================ $form = New-Object System.Windows.Forms.Form $form.Text = "Moho File Analyzer" $form.Size = New-Object System.Drawing.Size(1200, 700) $form.MinimumSize = New-Object System.Drawing.Size(850, 500) $form.StartPosition = "CenterScreen" $form.BackColor = [System.Drawing.Color]::White # ============================================================ # TOP PANEL # ============================================================ $topPanel = New-Object System.Windows.Forms.Panel $topPanel.Location = New-Object System.Drawing.Point(0, 0) $topPanel.Size = New-Object System.Drawing.Size(1200, 90) $topPanel.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right $form.Controls.Add($topPanel) # ============================================================ # FOLDER LABEL # ============================================================ $folderLabel = New-Object System.Windows.Forms.Label $folderLabel.Text = "Folder:" $folderLabel.Location = New-Object System.Drawing.Point(10, 18) $folderLabel.AutoSize = $true $folderLabel.Font = New-Object System.Drawing.Font( "Segoe UI", 9, [System.Drawing.FontStyle]::Regular ) $topPanel.Controls.Add($folderLabel) # ============================================================ # FOLDER TEXTBOX # ============================================================ $folderBox = New-Object System.Windows.Forms.TextBox $folderBox.Location = New-Object System.Drawing.Point(65, 14) $folderBox.Size = New-Object System.Drawing.Size(790, 25) $folderBox.ReadOnly = $true $folderBox.Text = "No folder selected" $folderBox.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right $topPanel.Controls.Add($folderBox) # ============================================================ # BROWSE BUTTON # ============================================================ $browseButton = New-Object System.Windows.Forms.Button $browseButton.Text = "Browse..." $browseButton.Location = New-Object System.Drawing.Point(865, 12) $browseButton.Size = New-Object System.Drawing.Size(100, 30) $browseButton.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Right $topPanel.Controls.Add($browseButton) # ============================================================ # SCAN BUTTON # ============================================================ $scanButton = New-Object System.Windows.Forms.Button $scanButton.Text = "SCAN" $scanButton.Location = New-Object System.Drawing.Point(975, 12) $scanButton.Size = New-Object System.Drawing.Size(100, 30) $scanButton.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Right $scanButton.Enabled = $false $topPanel.Controls.Add($scanButton) # ============================================================ # DISPLAY LABEL # ============================================================ $displayLabel = New-Object System.Windows.Forms.Label $displayLabel.Text = "What to show:" $displayLabel.Location = New-Object System.Drawing.Point(10, 55) $displayLabel.AutoSize = $true $displayLabel.Font = New-Object System.Drawing.Font( "Segoe UI", 9, [System.Drawing.FontStyle]::Bold ) $topPanel.Controls.Add($displayLabel) # ============================================================ # CHECKBOX - FILE NAME # ============================================================ $checkFileName = New-Object System.Windows.Forms.CheckBox $checkFileName.Text = "File Name" $checkFileName.Checked = $true $checkFileName.AutoSize = $true $checkFileName.Location = New-Object System.Drawing.Point(105, 53) $topPanel.Controls.Add($checkFileName) # ============================================================ # CHECKBOX - FRAME COUNT # ============================================================ $checkFrameCount = New-Object System.Windows.Forms.CheckBox $checkFrameCount.Text = "Frame Count" $checkFrameCount.Checked = $true $checkFrameCount.AutoSize = $true $checkFrameCount.Location = New-Object System.Drawing.Point(190, 53) $topPanel.Controls.Add($checkFrameCount) # ============================================================ # CHECKBOX - START FRAME # ============================================================ $checkStartFrame = New-Object System.Windows.Forms.CheckBox $checkStartFrame.Text = "Start Frame" $checkStartFrame.Checked = $true $checkStartFrame.AutoSize = $true $checkStartFrame.Location = New-Object System.Drawing.Point(300, 53) $topPanel.Controls.Add($checkStartFrame) # ============================================================ # CHECKBOX - END FRAME # ============================================================ $checkEndFrame = New-Object System.Windows.Forms.CheckBox $checkEndFrame.Text = "End Frame" $checkEndFrame.Checked = $true $checkEndFrame.AutoSize = $true $checkEndFrame.Location = New-Object System.Drawing.Point(405, 53) $topPanel.Controls.Add($checkEndFrame) # ============================================================ # CHECKBOX - FPS # ============================================================ $checkFPS = New-Object System.Windows.Forms.CheckBox $checkFPS.Text = "FPS" $checkFPS.Checked = $true $checkFPS.AutoSize = $true $checkFPS.Location = New-Object System.Drawing.Point(500, 53) $topPanel.Controls.Add($checkFPS) # ============================================================ # CHECKBOX - DIMENSION # ============================================================ $checkDimension = New-Object System.Windows.Forms.CheckBox $checkDimension.Text = "Dimension" $checkDimension.Checked = $true $checkDimension.AutoSize = $true $checkDimension.Location = New-Object System.Drawing.Point(550, 53) $topPanel.Controls.Add($checkDimension) # ============================================================ # CHECKBOX - FOLDER # ============================================================ $checkFolder = New-Object System.Windows.Forms.CheckBox $checkFolder.Text = "Folder" $checkFolder.Checked = $true $checkFolder.AutoSize = $true $checkFolder.Location = New-Object System.Drawing.Point(640, 53) $topPanel.Controls.Add($checkFolder) # ============================================================ # STATUS BAR # ============================================================ $statusLabel = New-Object System.Windows.Forms.Label $statusLabel.Text = "Ready - click Browse to select a folder." $statusLabel.Location = New-Object System.Drawing.Point(0, 645) $statusLabel.Size = New-Object System.Drawing.Size(1200, 25) $statusLabel.Anchor = [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right $statusLabel.Padding = New-Object System.Windows.Forms.Padding(8, 4, 0, 0) $statusLabel.Font = New-Object System.Drawing.Font( "Segoe UI", 9 ) $form.Controls.Add($statusLabel) # ============================================================ # DATA GRID # ============================================================ $grid = New-Object System.Windows.Forms.DataGridView $grid.Location = New-Object System.Drawing.Point(0, 90) $grid.Size = New-Object System.Drawing.Size(1200, 555) $grid.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right $grid.AllowUserToAddRows = $false $grid.AllowUserToDeleteRows = $false $grid.AllowUserToResizeRows = $false $grid.AllowUserToResizeColumns = $true $grid.ReadOnly = $true $grid.MultiSelect = $false $grid.SelectionMode = [System.Windows.Forms.DataGridViewSelectionMode]::FullRowSelect $grid.AutoGenerateColumns = $false $grid.RowHeadersVisible = $false # ============================================================ # HEADER # ============================================================ $grid.ColumnHeadersVisible = $true $grid.ColumnHeadersHeight = 32 $grid.ColumnHeadersHeightSizeMode = [System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode]::DisableResizing $grid.EnableHeadersVisualStyles = $false $grid.ColumnHeadersDefaultCellStyle.BackColor = [System.Drawing.Color]::LightGray $grid.ColumnHeadersDefaultCellStyle.ForeColor = [System.Drawing.Color]::Black $grid.ColumnHeadersDefaultCellStyle.Font = New-Object System.Drawing.Font( "Segoe UI", 9, [System.Drawing.FontStyle]::Bold ) $grid.ColumnHeadersDefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleCenter # ============================================================ # GRID APPEARANCE # ============================================================ $grid.BackgroundColor = [System.Drawing.Color]::White $grid.BorderStyle = [System.Windows.Forms.BorderStyle]::Fixed3D $grid.GridColor = [System.Drawing.Color]::LightGray $grid.CellBorderStyle = [System.Windows.Forms.DataGridViewCellBorderStyle]::SingleHorizontal $grid.AutoSizeRowsMode = [System.Windows.Forms.DataGridViewAutoSizeRowsMode]::None $grid.RowTemplate.Height = 25 # ============================================================ # FILE NAME COLUMN # ============================================================ $colFile = New-Object System.Windows.Forms.DataGridViewTextBoxColumn $colFile.Name = "FileName" $colFile.HeaderText = "File Name" $colFile.AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::Fill $colFile.FillWeight = 30 $grid.Columns.Add($colFile) # ============================================================ # FRAME COUNT COLUMN # ============================================================ $colFrames = New-Object System.Windows.Forms.DataGridViewTextBoxColumn $colFrames.Name = "FrameCount" $colFrames.HeaderText = "Frames" $colFrames.Width = 90 $colFrames.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleCenter $grid.Columns.Add($colFrames) # ============================================================ # START FRAME COLUMN # ============================================================ $colStart = New-Object System.Windows.Forms.DataGridViewTextBoxColumn $colStart.Name = "StartFrame" $colStart.HeaderText = "Start" $colStart.Width = 80 $colStart.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleCenter $grid.Columns.Add($colStart) # ============================================================ # END FRAME COLUMN # ============================================================ $colEnd = New-Object System.Windows.Forms.DataGridViewTextBoxColumn $colEnd.Name = "EndFrame" $colEnd.HeaderText = "End" $colEnd.Width = 80 $colEnd.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleCenter $grid.Columns.Add($colEnd) # ============================================================ # FPS COLUMN # ============================================================ $colFPS = New-Object System.Windows.Forms.DataGridViewTextBoxColumn $colFPS.Name = "FPS" $colFPS.HeaderText = "FPS" $colFPS.Width = 75 $colFPS.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleCenter $grid.Columns.Add($colFPS) # ============================================================ # DIMENSION COLUMN # ============================================================ $colDimension = New-Object System.Windows.Forms.DataGridViewTextBoxColumn $colDimension.Name = "Dimension" $colDimension.HeaderText = "Dimension" $colDimension.Width = 110 $colDimension.DefaultCellStyle.Alignment = [System.Windows.Forms.DataGridViewContentAlignment]::MiddleCenter $grid.Columns.Add($colDimension) # ============================================================ # FOLDER COLUMN # ============================================================ $colFolder = New-Object System.Windows.Forms.DataGridViewTextBoxColumn $colFolder.Name = "Folder" $colFolder.HeaderText = "Folder" $colFolder.AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::Fill $colFolder.FillWeight = 30 $grid.Columns.Add($colFolder) # ============================================================ # FULL PATH COLUMN # Hidden internal column # ============================================================ $colPath = New-Object System.Windows.Forms.DataGridViewTextBoxColumn $colPath.Name = "FullPath" $colPath.HeaderText = "Full Path" $colPath.Visible = $false $grid.Columns.Add($colPath) # ============================================================ # ADD GRID # ============================================================ $form.Controls.Add($grid) $topPanel.BringToFront() $statusLabel.BringToFront() # ============================================================ # READ MOHO FILE # ============================================================ function Read-MohoFile { param( [string]$FilePath ) $result = [PSCustomObject]@{ Success = $false StartFrame = "" EndFrame = "" FrameCount = "" FPS = "" Dimension = "" Error = "" } $zip = $null $stream = $null $reader = $null try { # ---------------------------------------------------- # OPEN MOHO AS ZIP # ---------------------------------------------------- $zip = [System.IO.Compression.ZipFile]::OpenRead( $FilePath ) # ---------------------------------------------------- # FIND PROJECT.MOHOPROJ # ---------------------------------------------------- $entry = $zip.GetEntry("Project.mohoproj") if ($null -eq $entry) { $result.Error = "Project.mohoproj not found" return $result } # ---------------------------------------------------- # READ PROJECT # ---------------------------------------------------- $stream = $entry.Open() $reader = New-Object System.IO.StreamReader($stream) $text = $reader.ReadToEnd() # ---------------------------------------------------- # START FRAME # ---------------------------------------------------- $start = 1 if ( $text -match '"start_frame"\s*:\s*(\d+)' ) { $start = [int]$matches[1] } # ---------------------------------------------------- # END FRAME # ---------------------------------------------------- $end = 0 if ( $text -match '"end_frame"\s*:\s*(\d+)' ) { $end = [int]$matches[1] } # ---------------------------------------------------- # FPS # ---------------------------------------------------- $fps = "" if ( $text -match '"fps"\s*:\s*([0-9]+(?:\.[0-9]+)?)' ) { $fps = $matches[1] } # ---------------------------------------------------- # WIDTH # ---------------------------------------------------- $width = "" if ( $text -match '"width"\s*:\s*(\d+)' ) { $width = [int]$matches[1] } # ---------------------------------------------------- # HEIGHT # ---------------------------------------------------- $height = "" if ( $text -match '"height"\s*:\s*(\d+)' ) { $height = [int]$matches[1] } # ---------------------------------------------------- # DIMENSION # ---------------------------------------------------- $dimension = "" if ( $width -ne "" -and $height -ne "" ) { $dimension = "$width x $height" } # ---------------------------------------------------- # VALIDATE FRAME RANGE # ---------------------------------------------------- if ($end -lt $start) { $result.Error = "Invalid frame range: Start=$start End=$end" return $result } # ---------------------------------------------------- # FRAME COUNT # ---------------------------------------------------- $frameCount = $end - $start + 1 # ---------------------------------------------------- # RESULT # ---------------------------------------------------- $result.Success = $true $result.StartFrame = $start $result.EndFrame = $end $result.FrameCount = $frameCount $result.FPS = $fps $result.Dimension = $dimension return $result } catch { $result.Error = $_.Exception.Message return $result } finally { if ($null -ne $reader) { try { $reader.Close() } catch {} } if ($null -ne $stream) { try { $stream.Close() } catch {} } if ($null -ne $zip) { try { $zip.Dispose() } catch {} } } } # ============================================================ # UPDATE COLUMN VISIBILITY # ============================================================ function Update-ColumnVisibility { $colFile.Visible = $checkFileName.Checked $colFrames.Visible = $checkFrameCount.Checked $colStart.Visible = $checkStartFrame.Checked $colEnd.Visible = $checkEndFrame.Checked $colFPS.Visible = $checkFPS.Checked $colDimension.Visible = $checkDimension.Checked $colFolder.Visible = $checkFolder.Checked $grid.PerformLayout() $grid.Refresh() } # ============================================================ # CHECKBOX EVENTS # ============================================================ $checkFileName.Add_CheckedChanged({ Update-ColumnVisibility }) $checkFrameCount.Add_CheckedChanged({ Update-ColumnVisibility }) $checkStartFrame.Add_CheckedChanged({ Update-ColumnVisibility }) $checkEndFrame.Add_CheckedChanged({ Update-ColumnVisibility }) $checkFPS.Add_CheckedChanged({ Update-ColumnVisibility }) $checkDimension.Add_CheckedChanged({ Update-ColumnVisibility }) $checkFolder.Add_CheckedChanged({ Update-ColumnVisibility }) # ============================================================ # BROWSE BUTTON # ============================================================ $browseButton.Add_Click({ try { $dialog = New-Object System.Windows.Forms.FolderBrowserDialog $dialog.Description = "Select the folder containing your Moho files" $dialog.ShowNewFolderButton = $false # ---------------------------------------------------- # REOPEN PREVIOUS FOLDER IF POSSIBLE # ---------------------------------------------------- if ( -not [string]::IsNullOrWhiteSpace( $script:SelectedFolder ) ) { if ( Test-Path ` -LiteralPath $script:SelectedFolder ` -PathType Container ) { $dialog.SelectedPath = $script:SelectedFolder } } # ---------------------------------------------------- # SHOW DIALOG # ---------------------------------------------------- $dialogResult = $dialog.ShowDialog($form) # ---------------------------------------------------- # USER SELECTED FOLDER # ---------------------------------------------------- if ( $dialogResult -eq [System.Windows.Forms.DialogResult]::OK ) { # IMPORTANT: # Keep selected path in SCRIPT scope. $script:SelectedFolder = $dialog.SelectedPath $folderBox.Text = $script:SelectedFolder $scanButton.Enabled = $true $grid.Rows.Clear() $statusLabel.Text = "Folder selected - click SCAN." $form.Refresh() } $dialog.Dispose() } catch { [System.Windows.Forms.MessageBox]::Show( "Browse error:`n`n$($_.Exception.Message)", "Moho File Analyzer", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error ) } }) # ============================================================ # SCAN MOHO FOLDER # ============================================================ function Scan-MohoFolder { # IMPORTANT: # Use the SAME script-scoped folder selected by Browse. $folderToScan = $script:SelectedFolder # -------------------------------------------------------- # CHECK FOLDER # -------------------------------------------------------- if ( [string]::IsNullOrWhiteSpace( $folderToScan ) ) { [System.Windows.Forms.MessageBox]::Show( "Please click Browse and select a folder first.", "No Folder Selected", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information ) return } # -------------------------------------------------------- # CHECK FOLDER EXISTS # -------------------------------------------------------- if ( -not ( Test-Path ` -LiteralPath $folderToScan ` -PathType Container ) ) { [System.Windows.Forms.MessageBox]::Show( "The selected folder no longer exists.`n`n$folderToScan", "Folder Not Found", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error ) return } # -------------------------------------------------------- # DISABLE BUTTONS # -------------------------------------------------------- $browseButton.Enabled = $false $scanButton.Enabled = $false # -------------------------------------------------------- # CLEAR RESULTS # -------------------------------------------------------- $grid.Rows.Clear() $statusLabel.Text = "Finding .moho files..." $form.Refresh() # -------------------------------------------------------- # FIND MOHO FILES RECURSIVELY # -------------------------------------------------------- try { $files = @( Get-ChildItem ` -LiteralPath $folderToScan ` -Filter "*.moho" ` -File ` -Recurse ` -ErrorAction Stop | Sort-Object FullName ) } catch { $browseButton.Enabled = $true $scanButton.Enabled = $true $statusLabel.Text = "Scan failed." [System.Windows.Forms.MessageBox]::Show( "Could not scan the selected folder.`n`n$($_.Exception.Message)", "Scan Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error ) return } # -------------------------------------------------------- # COUNTERS # -------------------------------------------------------- $total = $files.Count $success = 0 $errors = 0 $current = 0 # -------------------------------------------------------- # NO FILES # -------------------------------------------------------- if ($total -eq 0) { $statusLabel.Text = "Finished - no .moho files found." $browseButton.Enabled = $true $scanButton.Enabled = $true return } # -------------------------------------------------------- # PROCESS FILES # -------------------------------------------------------- foreach ($file in $files) { $current++ $statusLabel.Text = "Scanning $current / $total $($file.Name)" $form.Refresh() # ---------------------------------------------------- # RELATIVE FOLDER # ---------------------------------------------------- $relative = $file.DirectoryName.Substring( $folderToScan.Length ).TrimStart('\') if ( [string]::IsNullOrWhiteSpace( $relative ) ) { $folder = "" } else { $folder = $relative } # ---------------------------------------------------- # READ MOHO # ---------------------------------------------------- $result = Read-MohoFile ` -FilePath $file.FullName # ---------------------------------------------------- # SUCCESS # ---------------------------------------------------- if ($result.Success) { # ================================================= # COLUMN ORDER: # # File Name # Frames # Start # End # FPS # Dimension # Folder # Full Path # ================================================= [void]$grid.Rows.Add( $file.BaseName, $result.FrameCount, $result.StartFrame, $result.EndFrame, $result.FPS, $result.Dimension, $folder, $file.FullName ) $success++ } # ---------------------------------------------------- # ERROR # ---------------------------------------------------- else { $row = $grid.Rows.Add( $file.BaseName, "ERROR", "", "", "", "", $folder, $file.FullName ) $grid.Rows[$row].DefaultCellStyle.ForeColor = [System.Drawing.Color]::Red $grid.Rows[$row].Cells[ "FrameCount" ].ToolTipText = $result.Error $errors++ } } # -------------------------------------------------------- # FINISHED # -------------------------------------------------------- $statusLabel.Text = "Finished | Files: $total | OK: $success | Errors: $errors" $browseButton.Enabled = $true $scanButton.Enabled = $true $form.Refresh() } # ============================================================ # SCAN BUTTON # ============================================================ $scanButton.Add_Click({ try { Scan-MohoFolder } catch { $browseButton.Enabled = $true $scanButton.Enabled = $true [System.Windows.Forms.MessageBox]::Show( "Unexpected scan error:`n`n$($_.Exception.Message)", "Moho File Analyzer", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error ) } }) # ============================================================ # DOUBLE CLICK FILE # ============================================================ $grid.Add_CellDoubleClick({ param( $sender, $e ) if ($e.RowIndex -lt 0) { return } $path = $grid.Rows[ $e.RowIndex ].Cells[ "FullPath" ].Value if ( $path -and (Test-Path -LiteralPath $path) ) { Start-Process explorer.exe ` -ArgumentList "/select,`"$path`"" } }) # ============================================================ # RIGHT CLICK MENU # ============================================================ $contextMenu = New-Object System.Windows.Forms.ContextMenuStrip $openLocation = $contextMenu.Items.Add( "Open File Location" ) $copyPath = $contextMenu.Items.Add( "Copy Full Path" ) # ============================================================ # OPEN FILE LOCATION # ============================================================ $openLocation.Add_Click({ if ( $grid.SelectedRows.Count -eq 0 ) { return } $path = $grid.SelectedRows[0].Cells[ "FullPath" ].Value if ( $path -and (Test-Path -LiteralPath $path) ) { Start-Process explorer.exe ` -ArgumentList "/select,`"$path`"" } }) # ============================================================ # COPY FULL PATH # ============================================================ $copyPath.Add_Click({ if ( $grid.SelectedRows.Count -eq 0 ) { return } $path = $grid.SelectedRows[0].Cells[ "FullPath" ].Value if ($path) { [System.Windows.Forms.Clipboard]::SetText( $path ) } }) $grid.ContextMenuStrip = $contextMenu # ============================================================ # FORM SHOWN # ============================================================ $form.Add_Shown({ $statusLabel.Text = "Ready - click Browse to select a folder." Update-ColumnVisibility }) # ============================================================ # SHOW APPLICATION # ============================================================ try { [void]$form.ShowDialog() } catch { [System.Windows.Forms.MessageBox]::Show( "Unexpected error:`n`n$($_.Exception.Message)", "Moho File Analyzer Error", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error ) }