使用javaFX 做一个屏幕录像工具
程序员文章站
2024-02-06 22:46:46
...
基于https://blog.csdn.net/qq_36270700/article/details/109334232 上一篇,用javaFX做一个屏幕选择框后,借助ffmpeg,实现屏幕录制
整体思路为
1、选取屏幕区域,通过屏幕选择框,可以得到选取得坐标
2、根据帧率 , 设置间隔时间,再通过Robot截取指定区域屏幕
3、通过ffmpeg将截图合成视频
4、通过ffmpeg将视频转换为gif
重要的是 通过视频合成的gif体积较小!
实现效果:
代码就两个类,跑Main的main函数即可
这边默认帧率为100,实际上会加速,因为通过计算,每秒期望截10张图,但是执行过程中会有短暂的延迟,导致和期待帧率有偏差,这个可以自己调整
比较简单总共两个类加一个ffmpeg.exe
ScreenUtil
package screen;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.imageio.ImageIO;
public class ScreenUtil {
static String ffmpeg = ScreenUtil.class.getResource("ffmpeg.exe").getFile();
int bigNum = 1000000000;
private String PATH = null;
private String fileTypeName = "jpg";
private String fileType = "." + fileTypeName;
private String fieldName = "test";
public ScreenUtil(String fieldName) {
this.fieldName = fieldName;
}
public String getPATH() {
return PATH;
}
public void setPATH(String PATH) {
File file = new File(PATH + "/" + this.fieldName);
while(file.exists()){
this.fieldName = this.fieldName + "1";
file = new File(PATH + "/" + this.fieldName);
}
this.PATH = PATH + "/";
}
public String getFileNamePatten(){
return PATH + fieldName + "/1%09d" + fileType ;
}
//画鼠标
private void buildMousePic(BufferedImage image,Double x,Double y) {
Point p= MouseInfo.getPointerInfo().getLocation();
image.createGraphics().drawOval(p.x-x.intValue() -20, p.y-y.intValue() -20,20,20);
}
public void printScreen(Double x,Double y,Double w ,Double h,int num) throws AWTException, IOException {
//转视屏的时候需要32的倍数和2的倍数
if (w % 32 != 0)
{
w = w.intValue() / 32 * 32D;
}
if (h % 2 != 0) {
h = h.intValue() / 2 * 2D ;
}
// 截取屏幕
BufferedImage image = new Robot().createScreenCapture(new Rectangle(x.intValue(),y.intValue(),w.intValue(),h.intValue()));
buildMousePic(image,x,y);
// 创建一个用于保存图片的文件夹
File screenCaptureDirectory = new File(PATH + fieldName);
if (!screenCaptureDirectory.exists()) {
screenCaptureDirectory.mkdirs();
System.out.println("The directory " + screenCaptureDirectory.getName() + " is created.");
}
// 指定路径,并以特定的日期格式作为图片的名称
File imageFile = new File(screenCaptureDirectory, (bigNum +num + fileType));
// 以指定的图片格式将截取的图片写到指定的文件
ImageIO.write(image, fileTypeName, imageFile);
}
public void deleteField(){
System.out.println(PATH + fieldName);
File file = new File(PATH + fieldName);
deleteFile(file);
}
/**
* 删除文件
* @param file
*/
private void deleteFile(File file){
if(file.isDirectory() && file.listFiles().length > 0){
for(File f : file.listFiles()){
deleteFile(f);
}
file.delete();
}else{
file.delete();
}
}
//默认帧率
int framerate = 60;
public void toGif() throws IOException, InterruptedException {
//转视频
String outPutMp4 = PATH + fieldName + "/temp.mp4";
ProcessBuilder processBuilder = new ProcessBuilder(ffmpeg, "-f", "image2","-framerate" , framerate+"",
"-i",getFileNamePatten(),
"-vcodec" , "libx264" , "-r" ,framerate + "",
outPutMp4);
doProcessBuilder(processBuilder);
//mp4 转 gif
String outPutGif = PATH + fieldName + ".gif";
processBuilder = new ProcessBuilder(ffmpeg, "-i", outPutMp4, "-r" ,framerate + "",outPutGif);
doProcessBuilder(processBuilder);
//执行完毕删除截图文件,仅保留gif文件
deleteField();
System.out.println("over ");
try {
Desktop.getDesktop().open(new File( PATH));
} catch (Exception e) {
e.printStackTrace();
}
}
private void doProcessBuilder(ProcessBuilder processBuilder) throws InterruptedException, IOException {
Process process = processBuilder.start();
java.io.BufferedReader readerInput = new java.io.BufferedReader(
new InputStreamReader(process.getErrorStream()));
String inputLine = "";
while ((inputLine = readerInput.readLine()) != null) {
System.out.println(inputLine);
}
process.waitFor();
process.destroy();
}
}
Main:
package screen;
import java.awt.*;
import java.io.File;
import java.util.concurrent.ExecutorService;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.Background;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.DirectoryChooser;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import static java.util.concurrent.Executors.newFixedThreadPool;
public class Main extends Application {
/**
* 屏幕尺寸
*/
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
private int width = (int)screenSize.getWidth();
private int height = (int)screenSize.getHeight();
/**
* 屏幕中心开始,画一个默认的正方形状
*/
private int defaultWidth = 100;
/**
* 默认线条宽度
*/
private int defaultLineWidth = 10;
/**
* 当点击的时候,给个误差值,确保能达到拖拽的目的
*/
private int defaultFixWidth = 10;
/**
* 定义
* 左上角坐标 (x,y)
* 右下角坐标(w,h)
*/
double x,y,w,h = 0.0;
/**
* a
* d ▢ b
* c
* 定义矩形的四个边,被点击后为true
*/
boolean a,b,c,d = false;
/**
*
* @param primaryStage
*/
boolean stop = true;
private Stage stage;
private ExecutorService executorService = newFixedThreadPool(1);
ScreenUtil screenUtil = new ScreenUtil("random");
@Override
public void start(Stage primaryStage) {
stage = primaryStage;
StackPane root = new StackPane();
Canvas canvas = new Canvas(width, height);
Button startBtn = new Button("start");
Button exitBtn = new Button("exit");
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setStroke(Color.RED);
gc.setLineWidth(defaultLineWidth);
x = width/2 - defaultWidth;
y = height/2 - defaultWidth;
w = width/2 + defaultWidth;
h = height/2 + defaultWidth;
drawRect(gc,x,y,w,h);
//拖拽事件
canvas.setOnMouseDragged(event ->{
//清空一下
gc.clearRect(0, 0, width, height);
if(a || c){
//上边被点击
if(d){
//左边被点击(=左上角被点击)
x = event.getX();
}else if(b){
//右边被点击(=右上角被点击)
w = event.getX();
}
if(a){
y = event.getY();
}else{
h = event.getY();
}
}else if(b || d){
//右边被点击
if(a){
//右上角
y = event.getY();
}else if(c){
//右下角
h = event.getY();
}
if(b){
w = event.getX();
}else{
x = event.getX();
}
}
drawRect(gc,x,y,w,h);
startBtn.setTranslateX(w-width/2+startBtn.getWidth()/2);
startBtn.setTranslateY(y-height/2+startBtn.getHeight()/2);
exitBtn.setTranslateX(w-width/2+startBtn.getWidth()/2);
exitBtn.setTranslateY(y-height/2+startBtn.getHeight()/2 + 35);
});
//当点击的时候,取得点击的abcd哪些边
canvas.setOnMousePressed(event ->{
a=b=c=d=false;
//点击上边界
if(event.getY() < (y + defaultFixWidth)) {
a = true;
}
//下边界
if(event.getY() > (h - defaultFixWidth)){
c = true;
}
//点击左边界
if(event.getX() < (x + defaultFixWidth)) {
d = true;
}
//右边界
if(event.getX() > (w - defaultFixWidth)){
b = true;
}
});
root.getChildren().add(canvas);
//开始按钮
root.getChildren().add(startBtn(startBtn));
//退出按钮
root.getChildren().add(exitBtn(exitBtn));
//透明底
root.setBackground(Background.EMPTY);
primaryStage.initStyle(StageStyle.TRANSPARENT);
Scene scene = new Scene(root, width, height);
scene.setFill(null);
primaryStage.setScene(scene);
//常驻顶层
primaryStage.setAlwaysOnTop(true);
primaryStage.show();
if(screenUtil.getPATH() == null){
primaryStage.close();
DirectoryChooser directoryChooser = new DirectoryChooser();
directoryChooser.setTitle("请先选择保存路径");
File directory = directoryChooser.showDialog(new Stage());
if (directory != null) {
screenUtil.setPATH(directory.getAbsolutePath());
primaryStage.show();
}
}
}
private Button startBtn(Button startBtn){
startBtn.setMinWidth(50);
startBtn.setMinHeight(30);
startBtn.setTranslateX(w-width/2+30);
startBtn.setTranslateY(y-height/2+15);
startBtn.setOnAction(event -> {
stop = !stop;
System.out.println(stop);
if(stop){
startBtn.setText("start");
dialog();
}else{
startBtn.setText("stop");
printScreenGif();
}
});
return startBtn;
}
private Button exitBtn(Button exitBtn){
exitBtn.setMinWidth(50);
exitBtn.setMinHeight(30);
exitBtn.setTranslateX(w-width/2+30);
exitBtn.setTranslateY(y-height/2+15+35);
exitBtn.setOnAction(event -> System.exit(0));
return exitBtn;
}
private void drawRect(GraphicsContext gc, double x, double y, double w,double h){
gc.strokeLine(x,y,w,y);
gc.strokeLine(x,y,x,h);
gc.strokeLine(w,h,w,y);
gc.strokeLine(w,h,x,h);
}
private void printScreenGif(){
executorService.execute(() ->{
try {
for(int i = 0 ; i < 99999999 ; i++){
screenUtil.printScreen(x+defaultLineWidth,y,w-x-defaultLineWidth-defaultLineWidth,h-y-defaultLineWidth-defaultLineWidth,i);
Thread.sleep(1000/screenUtil.framerate);
if(stop){
break;
}
}
screenUtil.toGif();
endJob();
} catch (Exception e) {
e.printStackTrace();
}
});
}
private Stage dialogStage;
private void dialog() {
dialogStage = new Stage();
ProgressIndicator progressIndicator = new ProgressIndicator();
// 窗口父子关系
dialogStage.initOwner(stage);
dialogStage.initStyle(StageStyle.UNDECORATED);
dialogStage.initStyle(StageStyle.TRANSPARENT);
dialogStage.initModality(Modality.APPLICATION_MODAL);
// progress bar
javafx.scene.control.Label label = new Label("操作中....想取消,请杀进程");
label.setTextFill(Color.RED);
progressIndicator.setProgress(-1F);
VBox vBox = new VBox();
vBox.setSpacing(10);
vBox.setBackground(Background.EMPTY);
vBox.getChildren().addAll(progressIndicator, label);
Scene scene = new Scene(vBox);
scene.setFill(null);
dialogStage.setScene(scene);
dialogStage.show();
}
/**
* 收尾工作
*/
private void endJob() {
Platform.runLater(() -> {
dialogStage.close();
});
}
public static void main(String[] args) {
launch(args);
}
}
ffmpeg则要自己去下载,这边提供免费下载链接 https://download.csdn.net/download/qq_36270700/13086883
上一篇: 用C开发PHP扩张初探
推荐阅读