From bcdb5ce0e616abad40fc72c6292129750e32d8cc Mon Sep 17 00:00:00 2001 From: zhangxiaoxiao9527 <37830864+zhangxiaoxiao9527@users.noreply.github.com> Date: Sun, 18 Apr 2021 12:40:59 +0800 Subject: [PATCH] =?UTF-8?q?=E9=9B=86=E6=88=90=E8=A7=86=E9=A2=91=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E8=BD=AC=E6=8D=A2=E5=8A=9F=E8=83=BD1.0=EF=BC=88?= =?UTF-8?q?=E5=9F=BA=E4=BA=8Ejavacv=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/pom.xml | 58 ++++++++ server/src/main/config/application.properties | 5 + .../cn/keking/config/ConfigConstants.java | 29 ++++ .../main/java/cn/keking/model/FileType.java | 4 + .../cn/keking/service/FileHandlerService.java | 23 ++++ .../cn/keking/service/cache/CacheService.java | 6 + .../cache/impl/CacheServiceJDKImpl.java | 25 ++++ .../cache/impl/CacheServiceRedisImpl.java | 22 +++ .../cache/impl/CacheServiceRocksDBImpl.java | 39 ++++++ .../service/impl/MediaFilePreviewImpl.java | 125 +++++++++++++++++- 10 files changed, 333 insertions(+), 3 deletions(-) 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; + } }