diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..2b63946
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 2713ca6..8ff5046 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -5,44 +5,19 @@
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
@@ -88,8 +63,8 @@
@@ -97,7 +72,9 @@
+
+
@@ -151,28 +128,29 @@
-
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
+
-
+
@@ -346,8 +324,8 @@
-
+
@@ -378,7 +356,9 @@
-
+
+
+
1679928730544
@@ -387,7 +367,21 @@
1679928730544
-
+
+ 1681826752728
+
+
+
+ 1681826752728
+
+
+ 1681826809345
+
+
+
+ 1681826809345
+
+
diff --git a/FFMpegJ/build.gradle.kts b/FFMpegJ/build.gradle.kts
index 6971fb9..a0f8a81 100644
--- a/FFMpegJ/build.gradle.kts
+++ b/FFMpegJ/build.gradle.kts
@@ -21,7 +21,9 @@ dependencies {
annotationProcessor("org.projectlombok:lombok:1.18.26")
// https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2
implementation("com.alibaba.fastjson2:fastjson2:2.0.26")
- implementation("org.apache.tika:tika-core:2.7.0")
+ implementation(
+
+ "org.apache.tika:tika-core:2.7.0")
}
tasks.withType {
diff --git a/FFMpegJ/src/main/java/cn/wzpmc/filemanager/ffmpeg/FFMpegRuntime.java b/FFMpegJ/src/main/java/cn/wzpmc/filemanager/ffmpeg/FFMpegRuntime.java
index 1ce1e12..f7564cc 100644
--- a/FFMpegJ/src/main/java/cn/wzpmc/filemanager/ffmpeg/FFMpegRuntime.java
+++ b/FFMpegJ/src/main/java/cn/wzpmc/filemanager/ffmpeg/FFMpegRuntime.java
@@ -40,7 +40,7 @@ public class FFMpegRuntime {
if (contentType == null || !contentType.startsWith("video")) {
return null;
}
- ProcessBuilder processBuilder = new ProcessBuilder("ffprobe", "-v", "quiet", "-print_format", "json", "-show_streams", file.getAbsolutePath());
+ ProcessBuilder processBuilder = new ProcessBuilder(this.ffprobePath, "-v", "quiet", "-print_format", "json", "-show_streams", file.getAbsolutePath());
Process process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
diff --git a/FFMpegJ/src/main/java/cn/wzpmc/filemanager/ffmpeg/enums/VideoEncoder.java b/FFMpegJ/src/main/java/cn/wzpmc/filemanager/ffmpeg/enums/VideoEncoder.java
new file mode 100644
index 0000000..8426ff8
--- /dev/null
+++ b/FFMpegJ/src/main/java/cn/wzpmc/filemanager/ffmpeg/enums/VideoEncoder.java
@@ -0,0 +1,20 @@
+package cn.wzpmc.filemanager.ffmpeg.enums;
+
+public enum VideoEncoder {
+ H264("h264", "h264_nvenc", "h264_cuvid"),
+ HEVC("h265", "hevc_nvenc", "hevc_cuvid");
+ public final String name;
+ public final String encoderName;
+ public final String decoderName;
+ VideoEncoder(String name, String encoderName, String decoderName){
+ this.name = name;
+ this.encoderName = encoderName;
+ this.decoderName = decoderName;
+ }
+ public static VideoEncoder getEncoder(String name){
+ if (name.equals("h264")){
+ return H264;
+ }
+ return HEVC;
+ }
+}
diff --git a/FFMpegJ/src/main/java/cn/wzpmc/filemanager/ffmpeg/threads/TranscodingFileThread.java b/FFMpegJ/src/main/java/cn/wzpmc/filemanager/ffmpeg/threads/TranscodingFileThread.java
new file mode 100644
index 0000000..4376f02
--- /dev/null
+++ b/FFMpegJ/src/main/java/cn/wzpmc/filemanager/ffmpeg/threads/TranscodingFileThread.java
@@ -0,0 +1,128 @@
+package cn.wzpmc.filemanager.ffmpeg.threads;
+
+import cn.wzpmc.filemanager.ffmpeg.FFMpegRuntime;
+import cn.wzpmc.filemanager.ffmpeg.enums.VideoEncoder;
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import lombok.Getter;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Slf4j
+public class TranscodingFileThread extends Thread{
+ private final static Pattern getDataPattern = Pattern.compile("frame=\\s*(\\d+)\\s+fps=([\\d\\.]+)\\s+q=([\\d\\.]+)\\s+size=\\s*(\\d+kB)\\s+time=([\\d:.]+)\\s+bitrate=([\\d\\.]+kbits/s)\\s+speed=([\\d\\.]+x)\\s*");
+ private final File input;
+ @Getter
+ private final VideoEncoder outputEncoder;
+ @Getter
+ private final File output;
+ private final Callback callback;
+ private final FFMpegRuntime ffMpegRuntime;
+ private VideoEncoder inputEncoder;
+ @Getter
+ private long totalFrames;
+ @Getter
+ private float progress;
+ @Getter
+ private long frames;
+ @Getter
+ private float fps;
+ @SneakyThrows
+ public TranscodingFileThread(File input, VideoEncoder outputEncoder, File output, Callback callback, FFMpegRuntime ffMpegRuntime){
+ this.input = input;
+ this.outputEncoder = outputEncoder;
+ this.output = output;
+ this.callback = callback;
+ this.ffMpegRuntime = ffMpegRuntime;
+ ProcessBuilder probeProcessBuilder = new ProcessBuilder();
+ probeProcessBuilder.command(
+ this.ffMpegRuntime.getFfprobePath(),
+ "-v",
+ "quiet",
+ "-print_format",
+ "json",
+ "-show_streams",
+ this.input.getAbsolutePath()
+ );
+ Process probeProcess = probeProcessBuilder.start();
+ probeProcess.waitFor();
+ String fileProbeResult = new String(probeProcess.getInputStream().readAllBytes());
+ JSONObject probeResultObject = JSONObject.parseObject(fileProbeResult);
+ JSONArray streams = probeResultObject.getJSONArray("streams");
+ for (int i = 0; i < streams.size(); i++) {
+ JSONObject stream = streams.getJSONObject(i);
+ if (stream.getString("codec_type").equals("video")) {
+ this.inputEncoder = VideoEncoder.getEncoder(stream.getString("codec_name"));
+ this.totalFrames = Long.parseLong(stream.getString("nb_frames"));
+ }
+ }
+ if (this.inputEncoder == null){
+ throw new RuntimeException(new IOException("The input file isn't video file!"));
+ }
+ super.setName("Transcoding-File-Thread-" + input.getName());
+ }
+
+ @Override
+ public void run() {
+ try{
+ if (this.output.exists()){
+ boolean delete = this.output.delete();
+ if (!delete){
+ return;
+ }
+ }
+ ProcessBuilder processBuilder = new ProcessBuilder();
+ processBuilder.command(
+ this.ffMpegRuntime.getFfmpegPath(),
+ "-hide_banner",
+ "-vsync",
+ "0",
+ "-hwaccel",
+ "cuvid",
+ "-hwaccel_output_format",
+ "cuda",
+ "-c:v",
+ this.inputEncoder.decoderName,
+ "-i",
+ this.input.getAbsolutePath(),
+ "-c:v",
+ this.outputEncoder.encoderName,
+ "-c:a",
+ "copy",
+ this.output.getAbsolutePath()
+ );
+ System.out.println(String.join(" ", processBuilder.command()));
+ Process start = processBuilder.start();
+ BufferedInputStream in = new BufferedInputStream(start.getErrorStream());
+ BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
+ String lineStr;
+ while (start.isAlive()) {
+ lineStr = inBr.readLine();
+ if (lineStr == null){
+ this.frames = totalFrames;
+ progress = 1.0f;
+ return;
+ }
+ Matcher matcher = getDataPattern.matcher(lineStr);
+ if (matcher.find()) {
+ this.frames = Long.parseLong(matcher.group(1));
+ this.fps = Float.parseFloat(matcher.group(2));
+ progress = (float) this.frames / totalFrames;
+ }
+ }
+ }catch (Throwable throwable){
+ throwable.printStackTrace();
+ }finally {
+ this.callback.onDone(this);
+ }
+ }
+
+ @FunctionalInterface
+ public interface Callback{
+ void onDone(TranscodingFileThread thread);
+ }
+}
diff --git a/application.properties b/application.properties
new file mode 100644
index 0000000..079d89a
--- /dev/null
+++ b/application.properties
@@ -0,0 +1,8 @@
+server.port=8080
+spring.datasource.url=jdbc:mysql://192.168.1.2:3306/fs
+spring.datasource.username=FS
+spring.datasource.password=JiGSnWrwpPFnrthM
+save-path=/main/files
+hmac-key=rsnIkOKV,8#5Ex>B
+spring.servlet.multipart.max-request-size=20GB
+spring.servlet.multipart.max-file-size=20GB
\ No newline at end of file
diff --git a/src/main/java/cn/wzpmc/filemanager/FileManagerApplication.java b/src/main/java/cn/wzpmc/filemanager/FileManagerApplication.java
index c64dc7e..2dcfa0c 100644
--- a/src/main/java/cn/wzpmc/filemanager/FileManagerApplication.java
+++ b/src/main/java/cn/wzpmc/filemanager/FileManagerApplication.java
@@ -10,5 +10,4 @@ public class FileManagerApplication {
public static void main(String[] args) {
SpringApplication.run(FileManagerApplication.class, args);
}
-
}
diff --git a/src/main/java/cn/wzpmc/filemanager/controller/FileController.java b/src/main/java/cn/wzpmc/filemanager/controller/FileController.java
index 7b25a84..2200774 100644
--- a/src/main/java/cn/wzpmc/filemanager/controller/FileController.java
+++ b/src/main/java/cn/wzpmc/filemanager/controller/FileController.java
@@ -1,10 +1,14 @@
package cn.wzpmc.filemanager.controller;
import cn.wzpmc.filemanager.entities.CountableList;
+import cn.wzpmc.filemanager.entities.EncodingThreadInfo;
import cn.wzpmc.filemanager.entities.FileObject;
import cn.wzpmc.filemanager.enums.SearchType;
+import cn.wzpmc.filemanager.ffmpeg.enums.VideoEncoder;
import cn.wzpmc.filemanager.service.FileService;
import jakarta.servlet.http.HttpServletResponse;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@@ -14,21 +18,22 @@ import java.util.Map;
@RestController
@CrossOrigin
+@RequestMapping("/api/file")
public class FileController {
private final FileService service;
@Autowired
public FileController(FileService service){
this.service = service;
}
- @GetMapping("/api/file/count")
+ @GetMapping("/count")
public Long getFileCount(){
return service.getFileCount();
}
- @GetMapping("/api/file/get")
+ @GetMapping("/get")
public List getFiles(@RequestParam("page") int page){
return service.getFiles(page);
}
- @GetMapping("/api/file/search")
+ @GetMapping("/search")
public CountableList searchFiles(@RequestParam("type") SearchType type, @RequestParam("keywords") String keywords, @RequestParam("page") int page){
return switch (type){
case NAME -> service.searchFilesByName(keywords, page);
@@ -37,20 +42,28 @@ public class FileController {
case FORMAT -> service.searchFilesByFormat(keywords, page);
};
}
- @GetMapping("/api/file")
+ @GetMapping("/")
public void downloadFile(@RequestParam("id") int id, HttpServletResponse response){
service.downloadFile(id, response);
}
- @PostMapping("/api/file/upload")
+ @PostMapping("/upload")
public FileObject uploadFile(@RequestHeader("Authorization") String token, @RequestBody MultipartFile file){
return service.uploadFile(token, file);
}
- @PostMapping("/api/file/remove")
+ @PostMapping("/remove")
public boolean removeFile(@RequestHeader("Authorization") String token, @RequestBody FileObject file){
return service.removeFile(token, file);
}
- @GetMapping("/api/file/details")
+ @GetMapping("/details")
public Map getFileDetails(@RequestParam("id") int id){
return service.getFileDetails(id);
}
+ @PostMapping("/encoding")
+ public Object transEncoding(@RequestBody FileObject object, @RequestParam("enc") String encoder, @RequestHeader("Authorization") String token){
+ return service.encoding(object, VideoEncoder.getEncoder(encoder), token);
+ }
+ @GetMapping("/getEncodingInfo")
+ public List getEncodingInfo(){
+ return service.getEncodingInfo();
+ }
}
diff --git a/src/main/java/cn/wzpmc/filemanager/controller/UserController.java b/src/main/java/cn/wzpmc/filemanager/controller/UserController.java
index 20b2721..f879132 100644
--- a/src/main/java/cn/wzpmc/filemanager/controller/UserController.java
+++ b/src/main/java/cn/wzpmc/filemanager/controller/UserController.java
@@ -6,24 +6,22 @@ import cn.wzpmc.filemanager.service.UserService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.CrossOrigin;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
@RestController
@CrossOrigin
+@RequestMapping("/api/user/")
public class UserController {
private final UserService service;
@Autowired
public UserController(UserService service){
this.service = service;
}
- @PostMapping("/api/user/login")
+ @PostMapping("/login")
public ResponseResult login(@RequestBody User user, HttpServletRequest request, HttpServletResponse response){
return new ResponseResult<>(this.service.login(user, request.getRemoteAddr(), response));
}
- @PostMapping("/api/user/register")
+ @PostMapping("/register")
public User register(@RequestBody User user, HttpServletRequest request, HttpServletResponse response){
return this.service.register(user, request.getRemoteAddr(), response);
}
diff --git a/src/main/java/cn/wzpmc/filemanager/entities/EncodingThreadInfo.java b/src/main/java/cn/wzpmc/filemanager/entities/EncodingThreadInfo.java
new file mode 100644
index 0000000..86a50d4
--- /dev/null
+++ b/src/main/java/cn/wzpmc/filemanager/entities/EncodingThreadInfo.java
@@ -0,0 +1,16 @@
+package cn.wzpmc.filemanager.entities;
+
+import cn.wzpmc.filemanager.enums.EncodingStatus;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor
+public class EncodingThreadInfo {
+ private EncodingStatus status;
+ private FileObject file;
+ private float progress;
+ private long totalFrame;
+ private long nowFrame;
+ private float fps;
+}
diff --git a/src/main/java/cn/wzpmc/filemanager/enums/EncodingStatus.java b/src/main/java/cn/wzpmc/filemanager/enums/EncodingStatus.java
new file mode 100644
index 0000000..509b793
--- /dev/null
+++ b/src/main/java/cn/wzpmc/filemanager/enums/EncodingStatus.java
@@ -0,0 +1,5 @@
+package cn.wzpmc.filemanager.enums;
+
+public enum EncodingStatus {
+ WAITING, RUNNING, END
+}
diff --git a/src/main/java/cn/wzpmc/filemanager/service/FileService.java b/src/main/java/cn/wzpmc/filemanager/service/FileService.java
index 64c7d71..bdcd5cc 100644
--- a/src/main/java/cn/wzpmc/filemanager/service/FileService.java
+++ b/src/main/java/cn/wzpmc/filemanager/service/FileService.java
@@ -2,9 +2,13 @@ package cn.wzpmc.filemanager.service;
import cn.wzpmc.filemanager.dao.FileDao;
import cn.wzpmc.filemanager.entities.CountableList;
+import cn.wzpmc.filemanager.entities.EncodingThreadInfo;
import cn.wzpmc.filemanager.entities.FileObject;
import cn.wzpmc.filemanager.entities.User;
+import cn.wzpmc.filemanager.enums.EncodingStatus;
import cn.wzpmc.filemanager.ffmpeg.FFMpegRuntime;
+import cn.wzpmc.filemanager.ffmpeg.enums.VideoEncoder;
+import cn.wzpmc.filemanager.ffmpeg.threads.TranscodingFileThread;
import cn.wzpmc.filemanager.utils.JwtUtils;
import jakarta.servlet.http.HttpServletResponse;
import lombok.SneakyThrows;
@@ -19,6 +23,8 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
import java.security.MessageDigest;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
@@ -44,6 +50,8 @@ public class FileService {
log.info("成功创建保存文件夹!");
}
this.runtime = new FFMpegRuntime();
+ DaemonThread daemonThread = new DaemonThread();
+ daemonThread.start();
}
public Long getFileCount() {
@@ -88,12 +96,21 @@ public class FileService {
}
@SneakyThrows
public FileObject uploadFile(String token, MultipartFile file) {
+ String fullName = file.getOriginalFilename();
+ assert fullName != null;
+ File tempFile = File.createTempFile(fullName, "");
+ FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
+ StreamUtils.copy(file.getInputStream(), fileOutputStream);
+ fileOutputStream.close();
+ return uploadFile(token, tempFile, fullName);
+ }
+ public FileObject uploadFile(String token, File file, String fullName){
Optional user = jwtUtils.getUser(token);
if (user.isEmpty()) {
return null;
}
User requestUser = user.get();
- String fullName = file.getOriginalFilename();
+
assert fullName != null;
int i = fullName.lastIndexOf('.');
String name = fullName.substring(0, i);
@@ -102,20 +119,26 @@ public class FileService {
FileObject fileObject = new FileObject();
fileObject.setFileName(name);
fileObject.setFileFormat(format);
- fileObject.setFileSize(file.getSize());
+ fileObject.setFileSize(file.length());
fileObject.setUploader(requestUser.getUsername());
fileObject.setMd5(md5);
File saveFile = new File(this.savePath, md5);
- if (!saveFile.exists()) {
- StreamUtils.copy(file.getInputStream(), new FileOutputStream(saveFile));
+ try {
+ Files.move(file.toPath(), saveFile.toPath());
+ } catch (IOException e) {
+ e.printStackTrace();
+ log.warn("移动文件失败!, File={}", fileObject);
+ return null;
}
dao.uploadFile(fileObject);
return fileObject;
}
@SneakyThrows
- private static String getFileMd5(MultipartFile file){
+ private static String getFileMd5(File file){
MessageDigest MD5 = MessageDigest.getInstance("MD5");
- MD5.digest(file.getBytes());
+ FileInputStream fileInputStream = new FileInputStream(file);
+ MD5.digest(fileInputStream.readAllBytes());
+ fileInputStream.close();
return new String(Hex.encodeHex(MD5.digest()));
}
@@ -166,4 +189,58 @@ public class FileService {
}
return result;
}
+ private final Map transcodingFileThreadMap = new Hashtable<>();
+ private final Map statusMap = new Hashtable<>();
+ @SneakyThrows
+ public Object encoding(FileObject fileObject, VideoEncoder outputEnc, String token){
+ if (!runtime.check()) {
+ log.warn("服务端缺少FFMpeg环境(FFMpeg和FFProbe),会影响某些功能使用!");
+ }
+ if (transcodingFileThreadMap.containsKey(fileObject.getId())){
+ return "文件存在,无法转码!";
+ }
+ File outputTempFile = File.createTempFile("video-encoding-" + fileObject.getMd5(), ".mp4");
+ File inputFile = new File(savePath, fileObject.getMd5());
+ TranscodingFileThread transcodingFileThread = new TranscodingFileThread(inputFile, outputEnc, outputTempFile, (thread) -> {
+ statusMap.put(thread.getName(), EncodingStatus.END);
+ this.uploadFile(token, thread.getOutput(), fileObject.getFileName() + "-" + thread.getOutputEncoder().name + ".mp4");
+ }, runtime);
+ statusMap.put(transcodingFileThread.getName(), EncodingStatus.WAITING);
+ transcodingFileThreadMap.put(fileObject.getId(), transcodingFileThread);
+ return transcodingFileThreadMap.hashCode();
+ }
+
+ public List getEncodingInfo() {
+ List result = new ArrayList<>();
+ for (Map.Entry entry : transcodingFileThreadMap.entrySet()) {
+ Integer fileId = entry.getKey();
+ TranscodingFileThread value = entry.getValue();
+ FileObject fullFileInfo = dao.getFullFileInfo(fileId);
+ result.add(new EncodingThreadInfo(statusMap.get(value.getName()), fullFileInfo, value.getProgress(), value.getTotalFrames(), value.getFrames(), value.getFps()));
+ }
+ return result;
+ }
+ private class DaemonThread extends Thread {
+ @SneakyThrows
+ @Override
+ public void run() {
+ while (true){
+ for (TranscodingFileThread value : FileService.this.transcodingFileThreadMap.values()) {
+ String name = value.getName();
+ EncodingStatus status = FileService.this.statusMap.get(name);
+ if (status.equals(EncodingStatus.RUNNING)){
+ break;
+ }
+ if (status.equals(EncodingStatus.END)){
+ continue;
+ }
+ value.start();
+ FileService.this.statusMap.put(name, EncodingStatus.RUNNING);
+ break;
+ }
+ Thread.sleep(1000);
+ }
+
+ }
+ }
}