README.md
@@ -4,7 +4,7 @@ Windows 中使用 PowerShell 进行录屏的脚本,只在用户活动的时( | |||
4 | 4 | ||
5 | 5 | ## 准备 | |
6 | 6 | ||
7 | - | - 安装 `ffmpeg` 用来将截屏的png转换为视频. | |
7 | + | - 安装 `ffmpeg` 用来将截屏的png转换为视频. 如果不需要视频只要图片可以不装 | |
8 | 8 | ||
9 | 9 | ## 用法 | |
10 | 10 | 1. 用powsershell执行 `screenshot.ps1` 开始截屏生成png文件. |
README.md(文件已创建)
@@ -0,0 +1,15 @@ | |||
1 | + | # Windows录屏脚本 | |
2 | + | ||
3 | + | Windows 中使用 PowerShell 进行录屏的脚本,只在用户活动的时(鼠标点击或移动或敲打键盘)才进行录制 | |
4 | + | ||
5 | + | ## 准备 | |
6 | + | ||
7 | + | - 安装 `ffmpeg` 用来将截屏的png转换为视频. | |
8 | + | ||
9 | + | ## 用法 | |
10 | + | 1. 用powsershell执行 `screenshot.ps1` 开始截屏生成png文件. | |
11 | + | 2. 停止powershell,执行 `make.cmd` 开始导出视频. | |
12 | + | 3. `generate-input-list.py` 可选的python脚本用来重新生成`duration.txt`当你删除了部分png文件时. | |
13 | + | ||
14 | + | ## 说明 | |
15 | + | - `duration.txt` 是给`ffmpeg`合成视频时用的时间轴文件,里面指明了每个png图片的播放时长。如果删除了部分png图片,需要使用`generate-input-list.py`来重新生成它 |
generate-input-list.py(文件已创建)
@@ -0,0 +1,26 @@ | |||
1 | + | import os | |
2 | + | from datetime import datetime | |
3 | + | ||
4 | + | time_format = '%Y%m%d_%H%M%S%f' | |
5 | + | ||
6 | + | # 获取当前目录下所有的png文件名,无扩展名 | |
7 | + | png_files = [f.split('.')[0] for f in os.listdir('./shots') if f.endswith('.png')] | |
8 | + | ||
9 | + | # 解析文件名并排序 | |
10 | + | sorted_files = sorted(png_files, key=lambda x: datetime.strptime(x, time_format)) | |
11 | + | ||
12 | + | # 计算时间间隔并生成输入文件 | |
13 | + | with open('./shots/duration.txt', 'w') as f: | |
14 | + | for i in range(len(sorted_files)): | |
15 | + | current_file = sorted_files[i] | |
16 | + | current_time = datetime.strptime(current_file, time_format) | |
17 | + | ||
18 | + | if i != 0: | |
19 | + | prev_file = sorted_files[i-1] | |
20 | + | prev_time = datetime.strptime(prev_file, time_format) | |
21 | + | duration = (current_time - prev_time).total_seconds() | |
22 | + | f.write(f"duration {duration}\n") | |
23 | + | ||
24 | + | f.write(f"file '{current_file}.png'\n") | |
25 | + | ||
26 | + | print("输入文件列表已生成为 duration.txt") |
make.cmd(文件已创建)
@@ -0,0 +1 @@ | |||
1 | + | ffmpeg -f concat -i ./shots/duration.txt -pix_fmt yuv420p -c:v libx264 -r 5 output.mp4 |
screenshot.ps1(文件已创建)
@@ -0,0 +1,107 @@ | |||
1 | + | # 定义保存截图的文件夹路径为当前脚本所在目录下的shots文件夹 | |
2 | + | $folderPath = Join-Path -Path $PSScriptRoot -ChildPath "shots" | |
3 | + | if (-not (Test-Path -Path $folderPath)) { | |
4 | + | New-Item -ItemType Directory -Path $folderPath | |
5 | + | } | |
6 | + | ||
7 | + | # 导入用户32.dll,用于检测鼠标键盘活动 | |
8 | + | Add-Type @" | |
9 | + | using System; | |
10 | + | using System.Runtime.InteropServices; | |
11 | + | public class UserActivity { | |
12 | + | [DllImport("user32.dll")] | |
13 | + | public static extern bool GetLastInputInfo(ref LASTINPUTINFO plii); | |
14 | + | ||
15 | + | [StructLayout(LayoutKind.Sequential)] | |
16 | + | public struct LASTINPUTINFO { | |
17 | + | public uint cbSize; | |
18 | + | public uint dwTime; | |
19 | + | } | |
20 | + | } | |
21 | + | "@ | |
22 | + | ||
23 | + | # 检测用户是否活跃 | |
24 | + | function IsUserActive { | |
25 | + | $lastInput = New-Object UserActivity+LASTINPUTINFO | |
26 | + | $lastInput.cbSize = [System.Runtime.InteropServices.Marshal]::SizeOf($lastInput) | |
27 | + | ||
28 | + | if ([UserActivity]::GetLastInputInfo([ref]$lastInput)) { | |
29 | + | $idleTime = [Math]::Floor([TimeSpan]::FromMilliseconds([Environment]::TickCount - $lastInput.dwTime).TotalSeconds) | |
30 | + | # 如果空闲时间小于5秒,认为用户是活跃的 | |
31 | + | return ($idleTime -lt 1) | |
32 | + | } | |
33 | + | return $false | |
34 | + | } | |
35 | + | ||
36 | + | $durationTxt = Join-Path -Path $folderPath -ChildPath "duration.txt" | |
37 | + | $fileMatches = if (Test-Path -Path $durationTxt) { (Get-Content -Path $durationTxt -Tail 3 | Select-String -Pattern "^file '(.*\.png)'$").Matches } else { @() } | |
38 | + | $script:lastScreenshotFile = if ($fileMatches.Count -gt 0) { $fileMatches[0].Groups[1].Value } else { "" } | |
39 | + | # Write-Host "lastScreenshotFile: $lastScreenshotFile" | |
40 | + | $script:isFirstRun = $true | |
41 | + | $script:lastScreenshotTime = if ($script:lastScreenshotFile -ne "") { [DateTime]::ParseExact($script:lastScreenshotFile.Substring(0, 18), "yyyyMMdd_HHmmssfff", $null) } else { [DateTime]::MinValue } | |
42 | + | ||
43 | + | # 截屏函数 | |
44 | + | function TakeScreenshot { | |
45 | + | $now = Get-Date | |
46 | + | $timestamp = $now.ToString("yyyyMMdd_HHmmssfff") | |
47 | + | $filePath = Join-Path -Path $folderPath -ChildPath "$timestamp.png" | |
48 | + | ||
49 | + | Add-Type -AssemblyName System.Windows.Forms | |
50 | + | Add-Type -AssemblyName System.Drawing | |
51 | + | $bitmap = New-Object System.Drawing.Bitmap([System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Width, [System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Height) | |
52 | + | $graphics = [System.Drawing.Graphics]::FromImage($bitmap) | |
53 | + | # Capture the screen | |
54 | + | $graphics.CopyFromScreen(0, 0, 0, 0, $bitmap.Size) | |
55 | + | ||
56 | + | # Draw the cursor | |
57 | + | $cursorPosition = [System.Windows.Forms.Cursor]::Position | |
58 | + | $cursorRectangle = New-Object System.Drawing.Rectangle($cursorPosition, [System.Drawing.Size]::new(32, 32)) # Adjust size if needed | |
59 | + | # [System.Windows.Forms.Cursor]::Current.Draw($graphics, $cursorRectangle) | |
60 | + | [System.Windows.Forms.Cursors]::Arrow.Draw($graphics, $cursorRectangle) | |
61 | + | ||
62 | + | $bitmap.Save($filePath, [System.Drawing.Imaging.ImageFormat]::Png) | |
63 | + | ||
64 | + | # Add filename to duration.txt | |
65 | + | if ($script:lastScreenshotFile -ne "") { | |
66 | + | $duration = ([TimeSpan]::FromTicks(($now - $script:lastScreenshotTime).Ticks)).TotalSeconds | |
67 | + | $durationStr = "duration {0:F3}" -f $duration | |
68 | + | if ($script:isFirstRun) { | |
69 | + | $lastDuration = Get-Content -Path $durationTxt -Tail 1 | Select-String -Pattern "^duration [\d\.]+$" | |
70 | + | if ($lastDuration) { | |
71 | + | # remove last line | |
72 | + | $lines = Get-Content -Path $durationTxt | |
73 | + | $lines = $lines[0..($lines.Length - 2)] | |
74 | + | $lines += $durationStr | |
75 | + | $lines | Set-Content -Path $durationTxt | |
76 | + | } else { | |
77 | + | Add-Content -Path $durationTxt -Value $durationStr | |
78 | + | } | |
79 | + | $script:isFirstRun = $false | |
80 | + | } else { | |
81 | + | Add-Content -Path $durationTxt -Value $durationStr | |
82 | + | } | |
83 | + | } | |
84 | + | Add-Content -Path $durationTxt -Value "file '$timestamp.png'" | |
85 | + | ||
86 | + | Write-Host "Screenshot saved: $timestamp.png" | |
87 | + | [console]::beep(1000, 500) # 播放提示音 | |
88 | + | ||
89 | + | $script:lastScreenshotTime = $now | |
90 | + | $script:lastScreenshotFile = "$timestamp.png" | |
91 | + | } | |
92 | + | ||
93 | + | # Delay 3 seconds to start | |
94 | + | Start-Sleep -Seconds 3 | |
95 | + | ||
96 | + | # 主循环 | |
97 | + | while ($true) { | |
98 | + | try { | |
99 | + | if (IsUserActive) { | |
100 | + | TakeScreenshot | |
101 | + | } | |
102 | + | Start-Sleep -Seconds 1 | |
103 | + | } catch { | |
104 | + | Write-Host "An error occurred: $_" | |
105 | + | Start-Sleep -Seconds 5 # 出错时等待5秒再继续 | |
106 | + | } | |
107 | + | } |