diff --git a/server/pom.xml b/server/pom.xml
index c441f616..e3f4006b 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -192,6 +192,64 @@
galimatias
0.2.1
+
+
+
+ org.bytedeco
+ javacv
+ 1.5.2
+
+
+
+ org.bytedeco
+ javacpp
+ 1.5.2
+
+
+
+
+ org.bytedeco
+ opencv
+ 4.1.2-1.5.2
+ linux-x86_64
+
+
+
+ org.bytedeco
+ opencv
+ 4.1.2-1.5.2
+ windows-x86_64
+
+
+
+ org.bytedeco
+ openblas
+ 0.3.6-1.5.1
+ linux-x86_64
+
+
+
+ org.bytedeco
+ openblas
+ 0.3.6-1.5.1
+ windows-x86_64
+
+
+
+ org.bytedeco
+ ffmpeg
+ 4.2.1-1.5.2
+ linux-x86_64
+
+
+
+ org.bytedeco
+ ffmpeg
+ 4.2.1-1.5.2
+ windows-x86_64
+
+
+
diff --git a/server/src/main/config/application.properties b/server/src/main/config/application.properties
index 9f6aca63..cde6c068 100644
--- a/server/src/main/config/application.properties
+++ b/server/src/main/config/application.properties
@@ -55,6 +55,11 @@ cache.enabled = ${KK_CACHE_ENABLED:true}
simText = ${KK_SIMTEXT:txt,html,htm,asp,jsp,xml,json,properties,md,gitignore,log,java,py,c,cpp,sql,sh,bat,m,bas,prg,cmd}
#多媒体类型,默认如下,可自定义添加
media = ${KK_MEDIA:mp3,wav,mp4,flv}
+#是否开启多媒体类型转视频格式转换,目前可转换视频格式有:avi,mov,wmv,3gp,rm
+#请谨慎开启此功能,建议异步调用添加到处理队列,并且增加任务队列处理线程,防止视频转换占用完线程资源,转换比较耗费时间,并且控制了只能串行处理转换任务
+media.convert.disable = ${KK_MEDIA_CONVERT_DISABLE:false}
+#支持转换的视频类型
+convertMedias = ${KK_CONVERTMEDIAS:avi,mov,wmv,mkv,3gp,rm}
#office类型文档(word ppt)样式,默认为图片(image),可配置为pdf(预览时也有按钮切换)
office.preview.type = ${KK_OFFICE_PREVIEW_TYPE:image}
#是否关闭office预览切换开关,默认为false,可配置为true关闭
diff --git a/server/src/main/java/cn/keking/config/ConfigConstants.java b/server/src/main/java/cn/keking/config/ConfigConstants.java
index d716640d..67e9ce2e 100644
--- a/server/src/main/java/cn/keking/config/ConfigConstants.java
+++ b/server/src/main/java/cn/keking/config/ConfigConstants.java
@@ -24,6 +24,8 @@ public class ConfigConstants {
private static Boolean cacheEnabled;
private static String[] simTexts = {};
private static String[] medias = {};
+ private static String[] convertMedias = {};
+ private static String mediaConvertDisable;
private static String officePreviewType;
private static String officePreviewSwitchDisabled;
private static String ftpUsername;
@@ -89,6 +91,33 @@ public class ConfigConstants {
ConfigConstants.medias = Media;
}
+ public static String[] getConvertMedias() {
+ return convertMedias;
+ }
+
+ @Value("${convertMedias:avi,mov,wmv,mkv,3gp,rm}")
+ public void setConvertMedias(String convertMedia) {
+ String[] mediaArr = convertMedia.split(",");
+ setConvertMediaValue(mediaArr);
+ }
+
+ public static void setConvertMediaValue(String[] ConvertMedia) {
+ ConfigConstants.convertMedias = ConvertMedia;
+ }
+
+ public static String getMediaConvertDisable() {
+ return mediaConvertDisable;
+ }
+
+
+ @Value("${media.convert.disable:true}")
+ public void setMediaConvertDisable(String mediaConvertDisable) {
+ setMediaConvertDisableValue(mediaConvertDisable);
+ }
+ public static void setMediaConvertDisableValue(String mediaConvertDisable) {
+ ConfigConstants.mediaConvertDisable = mediaConvertDisable;
+ }
+
public static String getOfficePreviewType() {
return officePreviewType;
}
diff --git a/server/src/main/java/cn/keking/model/FileType.java b/server/src/main/java/cn/keking/model/FileType.java
index 520995ec..3660fde7 100644
--- a/server/src/main/java/cn/keking/model/FileType.java
+++ b/server/src/main/java/cn/keking/model/FileType.java
@@ -33,6 +33,7 @@ public enum FileType {
private static final String[] SSIM_TEXT_TYPES = ConfigConstants.getSimText();
private static final String[] CODES = {"java", "c", "php", "go", "python", "py", "js", "html", "ftl", "css", "lua", "sh", "rb", "yml", "json", "h", "cpp", "cs", "aspx", "jsp"};
private static final String[] MEDIA_TYPES = ConfigConstants.getMedia();
+ public static final String[] MEDIA_TYPES_CONVERT = ConfigConstants.getConvertMedias();
private static final Map FILE_TYPE_MAPPER = new HashMap<>();
static {
@@ -51,6 +52,9 @@ public enum FileType {
for (String media : MEDIA_TYPES) {
FILE_TYPE_MAPPER.put(media, FileType.MEDIA);
}
+ for (String media : MEDIA_TYPES_CONVERT) {
+ FILE_TYPE_MAPPER.put(media, FileType.MEDIA);
+ }
for (String tif : TIFF_TYPES) {
FILE_TYPE_MAPPER.put(tif, FileType.TIFF);
}
diff --git a/server/src/main/java/cn/keking/service/FileHandlerService.java b/server/src/main/java/cn/keking/service/FileHandlerService.java
index 7de18ce3..d8ba3122 100644
--- a/server/src/main/java/cn/keking/service/FileHandlerService.java
+++ b/server/src/main/java/cn/keking/service/FileHandlerService.java
@@ -286,4 +286,27 @@ public class FileHandlerService {
}
return attribute;
}
+
+ /**
+ * @return 已转换过的视频文件集合(缓存)
+ */
+ public Map listConvertedMedias() {
+ return cacheService.getMediaConvertCache();
+ }
+
+ /**
+ * 添加转换后的视频文件缓存
+ * @param fileName
+ * @param value
+ */
+ public void addConvertedMedias(String fileName, String value) {
+ cacheService.putMediaConvertCache(fileName, value);
+ }
+
+ /**
+ * @return 已转换视频文件缓存,根据文件名获取
+ */
+ public String getConvertedMedias(String key) {
+ return cacheService.getMediaConvertCache(key);
+ }
}
diff --git a/server/src/main/java/cn/keking/service/cache/CacheService.java b/server/src/main/java/cn/keking/service/cache/CacheService.java
index bf43c2ef..1667513a 100644
--- a/server/src/main/java/cn/keking/service/cache/CacheService.java
+++ b/server/src/main/java/cn/keking/service/cache/CacheService.java
@@ -12,15 +12,18 @@ public interface CacheService {
String FILE_PREVIEW_PDF_KEY = "converted-preview-pdf-file";
String FILE_PREVIEW_IMGS_KEY = "converted-preview-imgs-file";//压缩包内图片文件集合
String FILE_PREVIEW_PDF_IMGS_KEY = "converted-preview-pdfimgs-file";
+ String FILE_PREVIEW_MEDIA_CONVERT_KEY = "converted-preview-media-file";
String TASK_QUEUE_NAME = "convert-task";
Integer DEFAULT_PDF_CAPACITY = 500000;
Integer DEFAULT_IMG_CAPACITY = 500000;
Integer DEFAULT_PDFIMG_CAPACITY = 500000;
+ Integer DEFAULT_MEDIACONVERT_CAPACITY = 500000;
void initPDFCachePool(Integer capacity);
void initIMGCachePool(Integer capacity);
void initPdfImagesCachePool(Integer capacity);
+ void initMediaConvertCachePool(Integer capacity);
void putPDFCache(String key, String value);
void putImgCache(String key, List value);
Map getPDFCache();
@@ -29,6 +32,9 @@ public interface CacheService {
List getImgCache(String key);
Integer getPdfImageCache(String key);
void putPdfImageCache(String pdfFilePath, int num);
+ Map getMediaConvertCache();
+ void putMediaConvertCache(String key, String value);
+ String getMediaConvertCache(String key);
void cleanCache();
void addQueueTask(String url);
String takeQueueTask() throws InterruptedException;
diff --git a/server/src/main/java/cn/keking/service/cache/impl/CacheServiceJDKImpl.java b/server/src/main/java/cn/keking/service/cache/impl/CacheServiceJDKImpl.java
index ef63ee63..d484c2ac 100644
--- a/server/src/main/java/cn/keking/service/cache/impl/CacheServiceJDKImpl.java
+++ b/server/src/main/java/cn/keking/service/cache/impl/CacheServiceJDKImpl.java
@@ -26,6 +26,7 @@ public class CacheServiceJDKImpl implements CacheService {
private Map pdfCache;
private Map> imgCache;
private Map pdfImagesCache;
+ private Map mediaConvertCache;
private static final int QUEUE_SIZE = 500000;
private final BlockingQueue blockingQueue = new ArrayBlockingQueue<>(QUEUE_SIZE);
@@ -34,6 +35,7 @@ public class CacheServiceJDKImpl implements CacheService {
initPDFCachePool(CacheService.DEFAULT_PDF_CAPACITY);
initIMGCachePool(CacheService.DEFAULT_IMG_CAPACITY);
initPdfImagesCachePool(CacheService.DEFAULT_PDFIMG_CAPACITY);
+ initMediaConvertCachePool(CacheService.DEFAULT_MEDIACONVERT_CAPACITY);
}
@Override
@@ -79,6 +81,21 @@ public class CacheServiceJDKImpl implements CacheService {
pdfImagesCache.put(pdfFilePath, num);
}
+ @Override
+ public Map getMediaConvertCache() {
+ return mediaConvertCache;
+ }
+
+ @Override
+ public void putMediaConvertCache(String key, String value) {
+ mediaConvertCache.put(key, value);
+ }
+
+ @Override
+ public String getMediaConvertCache(String key) {
+ return mediaConvertCache.get(key);
+ }
+
@Override
public void cleanCache() {
initPDFCachePool(CacheService.DEFAULT_PDF_CAPACITY);
@@ -116,4 +133,12 @@ public class CacheServiceJDKImpl implements CacheService {
.maximumWeightedCapacity(capacity).weigher(Weighers.singleton())
.build();
}
+
+ @Override
+ public void initMediaConvertCachePool(Integer capacity) {
+ mediaConvertCache = new ConcurrentLinkedHashMap.Builder()
+ .maximumWeightedCapacity(capacity).weigher(Weighers.singleton())
+ .build();
+ }
+
}
diff --git a/server/src/main/java/cn/keking/service/cache/impl/CacheServiceRedisImpl.java b/server/src/main/java/cn/keking/service/cache/impl/CacheServiceRedisImpl.java
index fb9cc483..0472a257 100644
--- a/server/src/main/java/cn/keking/service/cache/impl/CacheServiceRedisImpl.java
+++ b/server/src/main/java/cn/keking/service/cache/impl/CacheServiceRedisImpl.java
@@ -34,6 +34,11 @@ public class CacheServiceRedisImpl implements CacheService {
@Override
public void initPdfImagesCachePool(Integer capacity) { }
+ @Override
+ public void initMediaConvertCachePool(Integer capacity) {
+
+ }
+
@Override
public void putPDFCache(String key, String value) {
RMapCache convertedList = redissonClient.getMapCache(FILE_PREVIEW_PDF_KEY);
@@ -80,6 +85,23 @@ public class CacheServiceRedisImpl implements CacheService {
convertedList.fastPut(pdfFilePath, num);
}
+ @Override
+ public Map getMediaConvertCache() {
+ return redissonClient.getMapCache(FILE_PREVIEW_MEDIA_CONVERT_KEY);
+ }
+
+ @Override
+ public void putMediaConvertCache(String key, String value) {
+ RMapCache convertedList = redissonClient.getMapCache(FILE_PREVIEW_MEDIA_CONVERT_KEY);
+ convertedList.fastPut(key, value);
+ }
+
+ @Override
+ public String getMediaConvertCache(String key) {
+ RMapCache convertedList = redissonClient.getMapCache(FILE_PREVIEW_MEDIA_CONVERT_KEY);
+ return convertedList.get(key);
+ }
+
@Override
public void cleanCache() {
cleanPdfCache();
diff --git a/server/src/main/java/cn/keking/service/cache/impl/CacheServiceRocksDBImpl.java b/server/src/main/java/cn/keking/service/cache/impl/CacheServiceRocksDBImpl.java
index bc41f27a..bc3a1045 100644
--- a/server/src/main/java/cn/keking/service/cache/impl/CacheServiceRocksDBImpl.java
+++ b/server/src/main/java/cn/keking/service/cache/impl/CacheServiceRocksDBImpl.java
@@ -73,6 +73,11 @@ public class CacheServiceRocksDBImpl implements CacheService {
}
+ @Override
+ public void initMediaConvertCachePool(Integer capacity) {
+
+ }
+
@Override
public void putPDFCache(String key, String value) {
try {
@@ -171,6 +176,40 @@ public class CacheServiceRocksDBImpl implements CacheService {
}
}
+ @Override
+ public Map getMediaConvertCache() {
+ Map result = new HashMap<>();
+ try{
+ result = (Map) toObject(db.get(FILE_PREVIEW_MEDIA_CONVERT_KEY.getBytes()));
+ } catch (RocksDBException | IOException | ClassNotFoundException e) {
+ LOGGER.error("Get from RocksDB Exception" + e);
+ }
+ return result;
+ }
+
+ @Override
+ public void putMediaConvertCache(String key, String value) {
+ try {
+ Map mediaConvertCacheItem = getMediaConvertCache();
+ mediaConvertCacheItem.put(key, value);
+ db.put(FILE_PREVIEW_MEDIA_CONVERT_KEY.getBytes(), toByteArray(mediaConvertCacheItem));
+ } catch (RocksDBException | IOException e) {
+ LOGGER.error("Put into RocksDB Exception" + e);
+ }
+ }
+
+ @Override
+ public String getMediaConvertCache(String key) {
+ String result = "";
+ try{
+ Map map = (Map) toObject(db.get(FILE_PREVIEW_MEDIA_CONVERT_KEY.getBytes()));
+ result = map.get(key);
+ } catch (RocksDBException | IOException | ClassNotFoundException e) {
+ LOGGER.error("Get from RocksDB Exception" + e);
+ }
+ return result;
+ }
+
@Override
public void cleanCache() {
try {
diff --git a/server/src/main/java/cn/keking/service/impl/MediaFilePreviewImpl.java b/server/src/main/java/cn/keking/service/impl/MediaFilePreviewImpl.java
index eb1ec1ad..88e4a066 100644
--- a/server/src/main/java/cn/keking/service/impl/MediaFilePreviewImpl.java
+++ b/server/src/main/java/cn/keking/service/impl/MediaFilePreviewImpl.java
@@ -1,13 +1,21 @@
package cn.keking.service.impl;
+import cn.keking.config.ConfigConstants;
import cn.keking.model.FileAttribute;
+import cn.keking.model.FileType;
import cn.keking.model.ReturnResponse;
import cn.keking.service.FilePreview;
import cn.keking.utils.DownloadUtils;
import cn.keking.service.FileHandlerService;
import cn.keking.web.filter.BaseUrlFilter;
+import org.artofsolving.jodconverter.util.ConfigUtils;
+import org.bytedeco.ffmpeg.global.avcodec;
+import org.bytedeco.javacv.FFmpegFrameGrabber;
+import org.bytedeco.javacv.FFmpegFrameRecorder;
+import org.bytedeco.javacv.Frame;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;
+import java.io.File;
/**
* @author : kl
@@ -21,6 +29,8 @@ public class MediaFilePreviewImpl implements FilePreview {
private final FileHandlerService fileHandlerService;
private final OtherFilePreviewImpl otherFilePreview;
+ private static Object LOCK=new Object();
+
public MediaFilePreviewImpl(FileHandlerService fileHandlerService, OtherFilePreviewImpl otherFilePreview) {
this.fileHandlerService = fileHandlerService;
this.otherFilePreview = otherFilePreview;
@@ -34,14 +44,123 @@ public class MediaFilePreviewImpl implements FilePreview {
if (response.isFailure()) {
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
} else {
- model.addAttribute("mediaUrl", BaseUrlFilter.getBaseUrl() + fileHandlerService.getRelativePath(response.getContent()));
+ url=BaseUrlFilter.getBaseUrl() + fileHandlerService.getRelativePath(response.getContent());
+ fileAttribute.setUrl(url);
}
- } else {
- model.addAttribute("mediaUrl", url);
+ }
+
+ if(checkNeedConvert(fileAttribute.getSuffix())){
+ url=convertUrl(fileAttribute);
+ }else{
+ //正常media类型
+ String[] medias = ConfigConstants.getMedia();
+ for(String media:medias){
+ if(media.equals(fileAttribute.getSuffix())){
+ model.addAttribute("mediaUrl", url);
+ return MEDIA_FILE_PREVIEW_PAGE;
+ }
+ }
+ return otherFilePreview.notSupportedFile(model, fileAttribute, "暂不支持");
}
model.addAttribute("mediaUrl", url);
return MEDIA_FILE_PREVIEW_PAGE;
}
+ /**
+ * 检查视频文件处理逻辑
+ * 返回处理过后的url
+ * @return url
+ */
+ private String convertUrl(FileAttribute fileAttribute) {
+ String url = fileAttribute.getUrl();
+ if(fileHandlerService.listConvertedMedias().containsKey(url)){
+ url= fileHandlerService.getConvertedMedias(url);
+ }else{
+ if(!fileHandlerService.listConvertedMedias().containsKey(url)){
+ synchronized(LOCK){
+ if(!fileHandlerService.listConvertedMedias().containsKey(url)){
+ String convertedUrl=convertToMp4(fileAttribute);
+ //加入缓存
+ fileHandlerService.addConvertedMedias(url,convertedUrl);
+ url=convertedUrl;
+ }
+ }
+ }
+ }
+ return url;
+ }
+ /**
+ * 检查视频文件转换是否已开启,以及当前文件是否需要转换
+ * @return
+ */
+ private boolean checkNeedConvert(String suffix) {
+ //1.检查开关是否开启
+ if("false".equals(ConfigConstants.getMediaConvertDisable())){
+ return false;
+ }
+ //2.检查当前文件是否需要转换
+ String[] mediaTypesConvert = FileType.MEDIA_TYPES_CONVERT;
+ String type = suffix;
+ for(String temp : mediaTypesConvert){
+ if(type.equals(temp)){
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 将浏览器不兼容视频格式转换成MP4
+ * @param fileAttribute
+ * @return
+ */
+ private static String convertToMp4(FileAttribute fileAttribute) {
+
+ //说明:这里做临时处理,取上传文件的目录
+ String homePath = ConfigUtils.getHomePath();
+ String filePath = homePath+File.separator+"file"+File.separator+"demo"+File.separator+fileAttribute.getName();
+ String convertFileName=fileAttribute.getUrl().replace(fileAttribute.getSuffix(),"mp4");
+
+ File file=new File(filePath);
+ FFmpegFrameGrabber frameGrabber = new FFmpegFrameGrabber(file);
+ String fileName = null;
+ Frame captured_frame = null;
+ FFmpegFrameRecorder recorder = null;
+ try {
+ fileName = file.getAbsolutePath().replace(fileAttribute.getSuffix(),"mp4");
+ File desFile=new File(fileName);
+ //判断一下防止穿透缓存
+ if(desFile.exists()){
+ return fileName;
+ }
+
+ frameGrabber.start();
+ recorder = new FFmpegFrameRecorder(fileName, frameGrabber.getImageWidth(), frameGrabber.getImageHeight(), frameGrabber.getAudioChannels());
+ recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); //avcodec.AV_CODEC_ID_H264 //AV_CODEC_ID_MPEG4
+ recorder.setFormat("mp4");
+ recorder.setFrameRate(frameGrabber.getFrameRate());
+ //recorder.setSampleFormat(frameGrabber.getSampleFormat()); //
+ recorder.setSampleRate(frameGrabber.getSampleRate());
+
+ recorder.setAudioChannels(frameGrabber.getAudioChannels());
+ recorder.setFrameRate(frameGrabber.getFrameRate());
+ recorder.start();
+ while ((captured_frame = frameGrabber.grabFrame()) != null) {
+ try {
+ recorder.setTimestamp(frameGrabber.getTimestamp());
+ recorder.record(captured_frame);
+ } catch (Exception e) {
+ }
+ }
+ recorder.stop();
+ recorder.release();
+ frameGrabber.stop();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ //是否删除源文件
+ //file.delete();
+ return convertFileName;
+ }
}