wanghongbo

媒体分析

@@ -46,7 +46,6 @@ @@ -46,7 +46,6 @@
46 <artifactId>spring-boot-starter-json</artifactId> 46 <artifactId>spring-boot-starter-json</artifactId>
47 </dependency> 47 </dependency>
48 48
49 -  
50 <dependency> 49 <dependency>
51 <groupId>org.springframework.boot</groupId> 50 <groupId>org.springframework.boot</groupId>
52 <artifactId>spring-boot-configuration-processor</artifactId> 51 <artifactId>spring-boot-configuration-processor</artifactId>
@@ -58,27 +57,18 @@ @@ -58,27 +57,18 @@
58 <artifactId>lombok</artifactId> 57 <artifactId>lombok</artifactId>
59 </dependency> 58 </dependency>
60 59
61 -  
62 <dependency> 60 <dependency>
63 <groupId>com.baomidou</groupId> 61 <groupId>com.baomidou</groupId>
64 <artifactId>mybatis-plus-spring-boot3-starter</artifactId> 62 <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
65 <version>3.5.7</version> 63 <version>3.5.7</version>
66 -  
67 </dependency> 64 </dependency>
68 65
69 -  
70 -  
71 -  
72 -  
73 -  
74 <dependency> 66 <dependency>
75 <groupId>com.alibaba</groupId> 67 <groupId>com.alibaba</groupId>
76 <artifactId>fastjson</artifactId> 68 <artifactId>fastjson</artifactId>
77 <version>1.2.47</version> 69 <version>1.2.47</version>
78 </dependency> 70 </dependency>
79 71
80 -  
81 -  
82 <dependency> 72 <dependency>
83 <groupId>com.alibaba</groupId> 73 <groupId>com.alibaba</groupId>
84 <artifactId>druid</artifactId> 74 <artifactId>druid</artifactId>
@@ -122,16 +112,12 @@ @@ -122,16 +112,12 @@
122 <version>23.0</version> 112 <version>23.0</version>
123 </dependency> 113 </dependency>
124 114
125 -  
126 -  
127 <dependency> 115 <dependency>
128 <groupId>ch.qos.logback</groupId> 116 <groupId>ch.qos.logback</groupId>
129 <artifactId>logback-classic</artifactId> 117 <artifactId>logback-classic</artifactId>
130 <!-- <version>1.2.3</version>--> 118 <!-- <version>1.2.3</version>-->
131 </dependency> 119 </dependency>
132 120
133 -  
134 -  
135 <dependency> 121 <dependency>
136 <groupId>org.codehaus.janino</groupId> 122 <groupId>org.codehaus.janino</groupId>
137 <artifactId>janino</artifactId> 123 <artifactId>janino</artifactId>
@@ -143,6 +129,12 @@ @@ -143,6 +129,12 @@
143 <version>5.8.32</version> 129 <version>5.8.32</version>
144 </dependency> 130 </dependency>
145 131
  132 + <dependency>
  133 + <groupId>commons-io</groupId>
  134 + <artifactId>commons-io</artifactId>
  135 + <version>2.15.1</version>
  136 + </dependency>
  137 +
146 </dependencies> 138 </dependencies>
147 139
148 <build> 140 <build>
  1 +package com.wondertek.service;
  2 +
  3 +
  4 +import com.wondertek.vo.media.Media;
  5 +
  6 +/**
  7 + * @description 媒体分析
  8 + * @author W5669
  9 + * @date 2025/8/19 17:27
  10 +
  11 + */
  12 +
  13 +public interface MediaInfoService {
  14 +
  15 + /**
  16 + * 获取媒体信息
  17 + * @param mediaPath 媒体文件路径
  18 + * @param logId 日志id
  19 + * @return 返回null时说明处理有异常
  20 + */
  21 + Media getMediaInfo(String mediaPath, String logId) throws Exception;
  22 +}
1 package com.wondertek.service.impl; 1 package com.wondertek.service.impl;
2 2
3 import cn.hutool.core.collection.CollectionUtil; 3 import cn.hutool.core.collection.CollectionUtil;
  4 +import cn.hutool.core.convert.ConvertException;
4 import cn.hutool.core.util.ObjectUtil; 5 import cn.hutool.core.util.ObjectUtil;
5 import cn.hutool.http.HttpRequest; 6 import cn.hutool.http.HttpRequest;
6 import cn.hutool.http.HttpResponse; 7 import cn.hutool.http.HttpResponse;
  8 +import cn.hutool.json.JSONUtil;
  9 +import com.alibaba.fastjson.JSON;
7 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 10 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
8 import com.baomidou.mybatisplus.core.metadata.IPage; 11 import com.baomidou.mybatisplus.core.metadata.IPage;
9 import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 12 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -23,7 +26,10 @@ import com.wondertek.mapper.BackupMaterialMapper; @@ -23,7 +26,10 @@ import com.wondertek.mapper.BackupMaterialMapper;
23 import com.wondertek.mapper.StreamTaskMapper; 26 import com.wondertek.mapper.StreamTaskMapper;
24 import com.wondertek.service.BackupMaterialService; 27 import com.wondertek.service.BackupMaterialService;
25 import com.wondertek.service.FileService; 28 import com.wondertek.service.FileService;
  29 +import com.wondertek.service.MediaInfoService;
26 import com.wondertek.util.*; 30 import com.wondertek.util.*;
  31 +import com.wondertek.vo.media.Media;
  32 +import com.wondertek.vo.media.MediaType;
27 import jakarta.annotation.Resource; 33 import jakarta.annotation.Resource;
28 import lombok.extern.slf4j.Slf4j; 34 import lombok.extern.slf4j.Slf4j;
29 import org.apache.commons.lang3.StringUtils; 35 import org.apache.commons.lang3.StringUtils;
@@ -59,6 +65,8 @@ public class BackupMaterialServiceImpl extends ServiceImpl<BackupMaterialMapper, @@ -59,6 +65,8 @@ public class BackupMaterialServiceImpl extends ServiceImpl<BackupMaterialMapper,
59 private String recGetImageUrl; 65 private String recGetImageUrl;
60 @Value("${file.previewUrl}") 66 @Value("${file.previewUrl}")
61 private String previewUrl; 67 private String previewUrl;
  68 + @Resource
  69 + private MediaInfoService mediaInfoService;
62 70
63 71
64 @Override 72 @Override
@@ -289,8 +297,6 @@ public class BackupMaterialServiceImpl extends ServiceImpl<BackupMaterialMapper, @@ -289,8 +297,6 @@ public class BackupMaterialServiceImpl extends ServiceImpl<BackupMaterialMapper,
289 } catch (Exception e) { 297 } catch (Exception e) {
290 return ResultBean.error("上传文件异常"); 298 return ResultBean.error("上传文件异常");
291 } 299 }
292 - //  
293 -  
294 300
295 //保存垫片素材表 301 //保存垫片素材表
296 BackupMaterial backupMaterial = new BackupMaterial(); 302 BackupMaterial backupMaterial = new BackupMaterial();
@@ -336,6 +342,23 @@ public class BackupMaterialServiceImpl extends ServiceImpl<BackupMaterialMapper, @@ -336,6 +342,23 @@ public class BackupMaterialServiceImpl extends ServiceImpl<BackupMaterialMapper,
336 log.error("请求能力平台定帧抽图接口失败,url:{}", switchStreamUrl, e); 342 log.error("请求能力平台定帧抽图接口失败,url:{}", switchStreamUrl, e);
337 } 343 }
338 344
  345 + //媒体分析获取时长
  346 + try {
  347 + Media media = mediaInfoService.getMediaInfo(destFilePath, fileId);
  348 + String type = media.getType();
  349 + Long duration = 0l;//时长
  350 + switch (type) {
  351 + case MediaType.VIDEO_TYPE:
  352 + cn.hutool.json.JSONObject jsonObject = JSONUtil.parseObj(media);
  353 + duration = Double.valueOf(jsonObject.get("duration", Double.class) * 1000).longValue();
  354 + backupMaterial.setDuration(duration);
  355 + }
  356 + } catch (NumberFormatException | ConvertException e) {
  357 + log.error("媒体分析获取时长报错:e={}", e);
  358 + } catch (Exception e) {
  359 + throw new RuntimeException(e);
  360 + }
  361 +
339 backupMaterialMapper.insert(backupMaterial); 362 backupMaterialMapper.insert(backupMaterial);
340 363
341 return ResultBean.ok("新增素材成功"); 364 return ResultBean.ok("新增素材成功");
  1 +package com.wondertek.service.impl;
  2 +
  3 +import cn.hutool.core.collection.CollectionUtil;
  4 +import cn.hutool.json.JSONUtil;
  5 +import com.fasterxml.jackson.databind.JsonNode;
  6 +import com.fasterxml.jackson.databind.ObjectMapper;
  7 +import com.wondertek.service.MediaInfoService;
  8 +import com.wondertek.util.MediaAnalysisHelper;
  9 +import com.wondertek.util.MediaInfoUtil;
  10 +import com.wondertek.vo.media.*;
  11 +import lombok.extern.slf4j.Slf4j;
  12 +import org.apache.commons.lang3.StringUtils;
  13 +import org.springframework.beans.factory.annotation.Autowired;
  14 +import org.springframework.stereotype.Service;
  15 +import org.springframework.util.CollectionUtils;
  16 +
  17 +import java.util.ArrayList;
  18 +import java.util.Arrays;
  19 +import java.util.Iterator;
  20 +import java.util.List;
  21 +
  22 +/**
  23 + * @description 媒体分析
  24 + * @author W5669
  25 + * @date 2025/8/19 17:57
  26 + * @param null
  27 +
  28 + */
  29 +
  30 +@Service
  31 +@Slf4j
  32 +public class MediaInfoServiceImpl implements MediaInfoService {
  33 +
  34 + @Autowired
  35 + private MediaInfoUtil mediaInfoUtil;
  36 +
  37 + private static final ObjectMapper MAPPER = new ObjectMapper();
  38 +
  39 + @Override
  40 + public Media getMediaInfo(String mediaPath, String logId) throws Exception {
  41 + log.info("[Media Analysis] getMediaInfo mediaPath:{},logId:{}", mediaPath, logId);
  42 + long startTime = System.currentTimeMillis();
  43 + // 工具绝对路径
  44 + String mediaInfoPath = mediaInfoUtil.mediaInfoPath();
  45 + log.info("[Media Analysis] mediaInfo tool path:{} ", mediaInfoPath);
  46 +
  47 + String mediaInfoJson;
  48 + try {
  49 + mediaInfoJson = MediaAnalysisHelper.analysis(mediaInfoPath, mediaPath, logId);
  50 + log.info("[Media Analysis] getMediaInfo mediaInfoJson:{}", mediaInfoJson);
  51 + } catch (Exception e) {
  52 + log.error("[Media Analysis] getMediaInfo Exception:{}", e);
  53 + throw e;
  54 + }
  55 +
  56 + if (StringUtils.isBlank(mediaInfoJson)) {
  57 + return null;
  58 + }
  59 +
  60 + Media media;
  61 + // 解析工具返回JOSN数据
  62 + try {
  63 + String mediaType = getMediaType(mediaInfoJson);
  64 + log.info("[Media Analysis] getMediaInfo mediaType:{}", mediaType);
  65 +
  66 + switch (mediaType) {
  67 + case MediaType.IMAGE_TYPE:
  68 + media = parseImageMedia(mediaInfoJson);
  69 + break;
  70 + case MediaType.AUDIO_TYPE:
  71 + media = parseAudioMedia(mediaInfoJson);
  72 + break;
  73 + case MediaType.VIDEO_TYPE:
  74 + media = parseVideoMedia(mediaInfoJson);
  75 + break;
  76 + default:
  77 + throw new Exception("MediaInfo Cannot analysis media format");
  78 + }
  79 + } catch (Exception e) {
  80 + log.error("[Media Analysis] getMediaInfo parse json error:{}", e);
  81 + throw e;
  82 + }
  83 + log.info("[Media Analysis] getMediaInfo,mediaPath={}logId={},spendTime={}", mediaPath, logId,
  84 + System.currentTimeMillis() - startTime);
  85 +
  86 + return media;
  87 + }
  88 +
  89 + /**
  90 + * 判断媒体类型
  91 + *
  92 + * @param mediaInfoJson
  93 + * @return MediaType类定义字符串,无法解析返回空字符串
  94 + * @throws Exception
  95 + */
  96 + private String getMediaType(String mediaInfoJson) throws Exception {
  97 + JsonNode root = MAPPER.readTree(mediaInfoJson);
  98 + JsonNode trackNode = root.get("media").get("track");
  99 + Iterator<JsonNode> elements = trackNode.elements();
  100 +
  101 + List<String> typeList = new ArrayList<>();
  102 + while (elements.hasNext()) {
  103 + JsonNode node = elements.next();
  104 + String type = node.get("@type").asText();
  105 + typeList.add(type);
  106 + }
  107 +
  108 + if (!CollectionUtils.isEmpty(typeList)) {
  109 + if (typeList.contains(MediaType.VIDEO_TYPE)) {
  110 + return MediaType.VIDEO_TYPE;
  111 + }
  112 + if (typeList.contains(MediaType.AUDIO_TYPE)) {
  113 + return MediaType.AUDIO_TYPE;
  114 + }
  115 + if (typeList.contains(MediaType.IMAGE_TYPE)) {
  116 + return MediaType.IMAGE_TYPE;
  117 + }
  118 + }
  119 +
  120 + return "";
  121 + }
  122 +
  123 + /**
  124 + * 图片JOSN数据的解析
  125 + *
  126 + * @param mediaInfoJson
  127 + * @return
  128 + */
  129 + private Media parseImageMedia(String mediaInfoJson) throws Exception {
  130 + log.info("[Media Analysis] parseImageMedia");
  131 + ImageMedia imageMedia = new ImageMedia();
  132 +
  133 + JsonNode root = MAPPER.readTree(mediaInfoJson);
  134 + JsonNode mediaNode = root.get("media");
  135 +
  136 + // Media Type
  137 + imageMedia.setType(MediaType.IMAGE_TYPE);
  138 +
  139 + // Media fileName
  140 + String fileName = mediaNode.get("@ref").asText();
  141 + imageMedia.setFileName(fileName);
  142 +
  143 + JsonNode trackNode = mediaNode.get("track");
  144 + Iterator<JsonNode> elemIterator = trackNode.elements();
  145 + while (elemIterator.hasNext()) {
  146 + JsonNode node = elemIterator.next();
  147 + String elementType = node.path("@type").asText();
  148 + if (elementType.equals("General")) {
  149 + // Media fileExtension
  150 + String fileExtension = paserUncertainNode(node, "FileExtension");
  151 + imageMedia.setFileExtension(fileExtension);
  152 + // Media format
  153 + String format = paserUncertainNode(node, "Format");
  154 + imageMedia.setFormat(format);
  155 + // Media size
  156 + String fileSize = paserUncertainNode(node, "FileSize");
  157 + imageMedia.setSize(fileSize);
  158 + } else if (elementType.equals(MediaType.IMAGE_TYPE)) {
  159 + // ImageMedia fileExtension
  160 + String width = parseNumberValueNode(node, "Width");
  161 + imageMedia.setWidth(width);
  162 + // ImageMedia Height
  163 + String height = parseNumberValueNode(node, "Height");
  164 + imageMedia.setHeight(height);
  165 + // ImageMedia colorSpace
  166 + String colorSpace = paserUncertainNode(node, "ColorSpace");
  167 + imageMedia.setColorSpace(colorSpace);
  168 + // ImageMedia chromaSubsampling
  169 + String chromaSubsampling = paserUncertainNode(node, "ChromaSubsampling");
  170 + imageMedia.setChromaSubsampling(chromaSubsampling);
  171 + // ImageMedia bitDepth
  172 + String bitDepth = paserUncertainNode(node, "BitDepth");
  173 + imageMedia.setBitDepth(bitDepth);
  174 + }
  175 + }
  176 +
  177 + log.info("[Media Analysis] parseImageMedia over:{}", imageMedia);
  178 + return imageMedia;
  179 + }
  180 +
  181 + /**
  182 + * 音频JOSN数据的解析
  183 + *
  184 + * @param mediaInfoJson
  185 + * @return
  186 + */
  187 + private Media parseAudioMedia(String mediaInfoJson) throws Exception {
  188 + log.info("[Media Analysis] parseAudioMedia");
  189 + AudioMedia audioMedia = new AudioMedia();
  190 +
  191 + JsonNode root = MAPPER.readTree(mediaInfoJson);
  192 + JsonNode mediaNode = root.get("media");
  193 +
  194 + // Media Type
  195 + audioMedia.setType(MediaType.AUDIO_TYPE);
  196 +
  197 + // Media fileName
  198 + String fileName = mediaNode.get("@ref").asText();
  199 + audioMedia.setFileName(fileName);
  200 +
  201 + JsonNode trackNode = mediaNode.get("track");
  202 + Iterator<JsonNode> elemIterator = trackNode.elements();
  203 +
  204 + List<Stream> streamList = new ArrayList<>();
  205 + while (elemIterator.hasNext()) {
  206 + JsonNode node = elemIterator.next();
  207 + String elementType = node.path("@type").asText();
  208 + if (elementType.equals("General")) {
  209 + // Media fileExtension
  210 + String fileExtension = paserUncertainNode(node, "FileExtension");
  211 + audioMedia.setFileExtension(fileExtension);
  212 + // Media format
  213 + String format = paserUncertainNode(node, "Format");
  214 + audioMedia.setFormat(format);
  215 + // Media size
  216 + String fileSize = paserUncertainNode(node, "FileSize");
  217 + audioMedia.setSize(fileSize);
  218 + // StreamMedia audioCount
  219 + String audioCount = paserUncertainNode(node, "AudioCount");
  220 + audioMedia.setAudioCount(audioCount);
  221 + // StreamMedia duration
  222 + String duration = parseNumberValueNode(node, "Duration");
  223 + audioMedia.setDuration(duration);
  224 + // StreamMedia overallBitRate
  225 + String overallBitRate = paserUncertainNode(node, "OverallBitRate");
  226 + audioMedia.setOverallBitRate(overallBitRate);
  227 + // StreamMedia overallBitRateMode
  228 + String overallBitRateMode = paserUncertainNode(node, "OverallBitRate_Mode");
  229 + audioMedia.setOverallBitRateMode(overallBitRateMode);
  230 + } else if (elementType.equals(MediaType.AUDIO_TYPE)) {
  231 + Stream stream = parseStream(node, MediaType.AUDIO_TYPE);
  232 + streamList.add(stream);
  233 + }
  234 + }
  235 + audioMedia.setSteams(streamList);
  236 +
  237 + log.info("[Media Analysis] parseAudioMedia over:{}", audioMedia);
  238 + return audioMedia;
  239 + }
  240 +
  241 + /**
  242 + * 视频JOSN数据的解析
  243 + *
  244 + * @param mediaInfoJson
  245 + * @return
  246 + */
  247 + private Media parseVideoMedia(String mediaInfoJson) throws Exception {
  248 + log.info("[Media Analysis] parseVideoMedia");
  249 + VideoMedia videoMedia = new VideoMedia();
  250 +
  251 + JsonNode root = MAPPER.readTree(mediaInfoJson);
  252 + JsonNode mediaNode = root.get("media");
  253 +
  254 + videoMedia.setOriginalInfo(JSONUtil.parseObj(mediaInfoJson).toString());
  255 + // Media Type
  256 + videoMedia.setType(MediaType.VIDEO_TYPE);
  257 +
  258 + // Media fileName
  259 + String fileName = mediaNode.get("@ref").asText();
  260 + videoMedia.setFileName(fileName);
  261 +
  262 + JsonNode trackNode = mediaNode.get("track");
  263 + Iterator<JsonNode> elemIterator = trackNode.elements();
  264 +
  265 + List<Stream> streamList = new ArrayList<>();
  266 + while (elemIterator.hasNext()) {
  267 + JsonNode node = elemIterator.next();
  268 + String elementType = node.path("@type").asText();
  269 + if (elementType.equals("General")) {
  270 + // Media fileExtension
  271 + String fileExtension = paserUncertainNode(node, "FileExtension");
  272 + videoMedia.setFileExtension(fileExtension);
  273 + // Media format
  274 + String format = paserUncertainNode(node, "Format");
  275 + videoMedia.setFormat(format);
  276 + // Media size
  277 + String fileSize = paserUncertainNode(node, "FileSize");
  278 + videoMedia.setSize(fileSize);
  279 + // StreamMedia audioCount
  280 + String audioCount = paserUncertainNode(node, "AudioCount");
  281 + videoMedia.setAudioCount(audioCount);
  282 + // StreamMedia duration
  283 + String duration = parseNumberValueNode(node, "Duration");
  284 + videoMedia.setDuration(duration);
  285 + // StreamMedia overallBitRate
  286 + String overallBitRate = paserUncertainNode(node, "OverallBitRate");
  287 + videoMedia.setOverallBitRate(overallBitRate);
  288 + // StreamMedia overallBitRateMode
  289 + String overallBitRateMode = paserUncertainNode(node, "OverallBitRate_Mode");
  290 + videoMedia.setOverallBitRateMode(overallBitRateMode);
  291 + // VideoMedia videoCount
  292 + String videoCount = paserUncertainNode(node, "VideoCount");
  293 + videoMedia.setVideoCount(videoCount);
  294 + // VideoMedia formatProfile
  295 + String formatProfile = paserUncertainNode(node, "Format_Profile");
  296 + videoMedia.setFormatProfile(formatProfile);
  297 + } else if (elementType.equals(MediaType.AUDIO_TYPE)) {
  298 + Stream stream = parseStream(node, MediaType.AUDIO_TYPE);
  299 + streamList.add(stream);
  300 + } else if (elementType.equals(MediaType.VIDEO_TYPE)) {
  301 + Stream stream = parseStream(node, MediaType.VIDEO_TYPE);
  302 + streamList.add(stream);
  303 + }
  304 + }
  305 + videoMedia.setSteams(streamList);
  306 +
  307 + log.info("[Media Analysis] parseVideoMedia over:{}", videoMedia);
  308 + return videoMedia;
  309 + }
  310 +
  311 + /**
  312 + * 处理一些不确定的节点
  313 + *
  314 + * @param parentNode
  315 + * 父节点
  316 + * @param name
  317 + * 子节点名称
  318 + * @return
  319 + */
  320 + private String paserUncertainNode(JsonNode parentNode, String name) {
  321 + JsonNode node = parentNode.get(name);
  322 + if (node != null) {
  323 + return node.asText();
  324 + }
  325 + return "";
  326 + }
  327 +
  328 + /**
  329 + * 解析数值字段
  330 + *
  331 + * @param parentNode
  332 + * @param name
  333 + * @return
  334 + */
  335 + private String parseNumberValueNode(JsonNode parentNode, String name) {
  336 + String parseResult = null;
  337 + try {
  338 + parseResult = paserUncertainNode(parentNode, name);
  339 + if (StringUtils.isNotBlank(parseResult)) {
  340 + String valueString = parseResult;
  341 + // 处理MediaInfo异常返回,比如:分辨率返回"1920 / 1920"这种情形
  342 + if (parseResult.contains("/")) {
  343 + List<String> splitResult = Arrays.asList(StringUtils.split(parseResult, "/"));
  344 + if (CollectionUtil.isNotEmpty(splitResult)) {
  345 + valueString = splitResult.get(0).trim();
  346 + }
  347 + }
  348 + Double valueDouble = Double.parseDouble(valueString); // 目的做个校验
  349 + log.info("[Media Analysis] parseNumberValueNode check number value:{}", valueDouble);
  350 + return valueString;
  351 + }
  352 + } catch (Exception e) {
  353 + log.error("[Media Analysis] parseNumberValueNode parser value:{} error:{}", parseResult, e);
  354 + return "0";
  355 + }
  356 +
  357 + return "0";
  358 + }
  359 +
  360 + /**
  361 + * 解析音视频流JSON数据
  362 + *
  363 + * @param parentNode
  364 + * 节点
  365 + * @param streamType
  366 + * 音频或者视频 MediaType取值
  367 + * @return
  368 + */
  369 + private Stream parseStream(JsonNode parentNode, String streamType) {
  370 + Stream stream = null;
  371 + if (streamType.equals(MediaType.AUDIO_TYPE)) {
  372 + stream = new AudioSteam();
  373 + } else if (streamType.equals(MediaType.VIDEO_TYPE)) {
  374 + stream = new VideoStream();
  375 + }
  376 +
  377 + if (stream == null) {
  378 + return null;
  379 + }
  380 +
  381 + // 共同的属性
  382 + String steamType = paserUncertainNode(parentNode, "@type");
  383 + stream.setStreamType(steamType);
  384 +
  385 + String id = paserUncertainNode(parentNode, "ID");
  386 + stream.setId(id);
  387 +
  388 + String streamOrder = paserUncertainNode(parentNode, "StreamOrder");
  389 + stream.setStreamOrder(streamOrder);
  390 +
  391 + String format = paserUncertainNode(parentNode, "Format");
  392 + stream.setFormat(format);
  393 +
  394 + String formatProfile = paserUncertainNode(parentNode, "Format_Profile");
  395 + stream.setFormatProfile(formatProfile);
  396 +
  397 + String duration = parseNumberValueNode(parentNode, "Duration");
  398 + stream.setDuration(duration);
  399 +
  400 + String bitRateMode = paserUncertainNode(parentNode, "BitRate_Mode");
  401 + stream.setBitRateMode(bitRateMode);
  402 +
  403 + String bitRate = paserUncertainNode(parentNode, "BitRate");
  404 + stream.setBitRate(bitRate);
  405 +
  406 + String bitRateMinimum = paserUncertainNode(parentNode, "BitRate_Minimum");
  407 + stream.setBitRateMinimum(bitRateMinimum);
  408 +
  409 + String bitRateMaximum = paserUncertainNode(parentNode, "BitRate_Maximum");
  410 + stream.setBitRateMaximum(bitRateMaximum);
  411 +
  412 + String frameRate = paserUncertainNode(parentNode, "FrameRate");
  413 + stream.setFrameRate(frameRate);
  414 +
  415 + String frameCount = paserUncertainNode(parentNode, "FrameCount");
  416 + stream.setFrameCount(frameCount);
  417 +
  418 + String bitDepth = paserUncertainNode(parentNode, "BitDepth");
  419 + stream.setBitDepth(bitDepth);
  420 +
  421 + String streamSize = paserUncertainNode(parentNode, "StreamSize");
  422 + stream.setStreamSize(streamSize);
  423 +
  424 + // 处理音视频独特的属性
  425 + if (streamType.equals(MediaType.AUDIO_TYPE)) {
  426 + AudioSteam audioSteam = (AudioSteam) stream;
  427 + String samplingRate = paserUncertainNode(parentNode, "SamplingRate");
  428 + audioSteam.setSamplingRate(samplingRate);
  429 +
  430 + String samplingCount = paserUncertainNode(parentNode, "SamplingCount");
  431 + audioSteam.setSamplingCount(samplingCount);
  432 +
  433 + String channels = paserUncertainNode(parentNode, "Channels");
  434 + audioSteam.setChannels(channels);
  435 +
  436 + } else if (streamType.equals(MediaType.VIDEO_TYPE)) {
  437 + VideoStream videoStream = (VideoStream) stream;
  438 + String formatLevel = paserUncertainNode(parentNode, "Format_Level");
  439 + videoStream.setFormatLevel(formatLevel);
  440 +
  441 + String formatSettingsGOP = paserUncertainNode(parentNode, "Format_Settings_GOP");
  442 + videoStream.setFormatSettingsGOP(formatSettingsGOP);
  443 +
  444 + String frameRateMode = paserUncertainNode(parentNode, "FrameRate_Mode");
  445 + videoStream.setFrameRateMode(frameRateMode);
  446 +
  447 + String width = parseNumberValueNode(parentNode, "Width");
  448 + videoStream.setWidth(width);
  449 +
  450 + String height = parseNumberValueNode(parentNode, "Height");
  451 + videoStream.setHeight(height);
  452 +
  453 + String displayAspectRatio = paserUncertainNode(parentNode, "DisplayAspectRatio");
  454 + videoStream.setDisplayAspectRatio(displayAspectRatio);
  455 +
  456 + String colorSpace = paserUncertainNode(parentNode, "ColorSpace");
  457 + videoStream.setColorSpace(colorSpace);
  458 +
  459 + String chromaSubsampling = paserUncertainNode(parentNode, "ChromaSubsampling");
  460 + videoStream.setChromaSubsampling(chromaSubsampling);
  461 +
  462 + String scanType = paserUncertainNode(parentNode, "ScanType");
  463 + videoStream.setScanType(scanType);
  464 +
  465 + }
  466 +
  467 + return stream;
  468 +
  469 + }
  470 +}
  1 +package com.wondertek.util;
  2 +
  3 +import lombok.extern.slf4j.Slf4j;
  4 +import org.apache.commons.io.IOUtils;
  5 +
  6 +import java.io.InputStream;
  7 +import java.nio.charset.StandardCharsets;
  8 +import java.util.ArrayList;
  9 +import java.util.List;
  10 +
  11 +/**
  12 + * @Auther: WANGJING
  13 + * @Description: 媒体分析工具类
  14 + * @Date: 2019/11/1 9:40
  15 + * @Modifier:
  16 + * @Version:
  17 + * @TaskId: YJ-348
  18 + */
  19 +
  20 +@Slf4j
  21 +public class MediaAnalysisHelper {
  22 +
  23 + /**
  24 + * @Description: 获取媒体分析JSON数据
  25 + * @Auther: WANGJING
  26 + * @Param mediaInfoPath mediaInfo工具路径
  27 + * @Param mediaFilePath 媒体文件路径
  28 + * @Param logId 日志id
  29 + * @Return: 返回分析结果JSON数据
  30 + * @Version:
  31 + * @TaskId: YJ-348
  32 + */
  33 + public static String analysis(String mediaInfoPath, String mediaFilePath, String logId) throws Exception {
  34 + long startTime = System.currentTimeMillis();
  35 + log.info("[Media Analysis] MediaAnalysisHelper mediaInfoPath:{}, mediaFilePath:{},logId:{}", mediaInfoPath,
  36 + mediaFilePath, logId);
  37 + List<String> commend = new ArrayList<>();
  38 + commend.add(mediaInfoPath);
  39 + commend.add("--OUTPUT=JSON"); // json格式输出
  40 + commend.add(mediaFilePath);
  41 +
  42 + ProcessBuilder builder = new ProcessBuilder();
  43 + builder.command(commend);
  44 + Process process = builder.start();
  45 + int result = process.waitFor();
  46 +
  47 + if (result != 0) {
  48 + log.info("[Media Analysis] MediaAnalysisHelper Process Failure result={}", result);
  49 + throw new Exception("MediaInfo工具处理异常");
  50 + }
  51 + log.info("[Media Analysis] MediaAnalysisHelper,mediaFilePath={}logId={},spendTime={}", mediaFilePath, logId,
  52 + System.currentTimeMillis() - startTime);
  53 +
  54 + InputStream inputStream = process.getInputStream();
  55 +
  56 +
  57 + String josnMediaInfo = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
  58 +
  59 + return josnMediaInfo;
  60 +
  61 + }
  62 +
  63 +}
  1 +package com.wondertek.util;
  2 +
  3 +import lombok.extern.slf4j.Slf4j;
  4 +import org.springframework.beans.factory.annotation.Value;
  5 +import org.springframework.stereotype.Component;
  6 +
  7 +/**
  8 + * @Auther: WANGJING
  9 + * @Description:
  10 + * @Modifier:
  11 + * @Version:
  12 + * @TaskId: YJ-348
  13 + */
  14 +
  15 +@Component
  16 +@Slf4j
  17 +public class MediaInfoUtil {
  18 +
  19 + @Value("${file.mediainfoPath}")
  20 + private String mediaInfoPath;
  21 +
  22 + /**
  23 + * 获取MediaInfo工具的路径
  24 + *
  25 + * @return
  26 + */
  27 + public String mediaInfoPath() {
  28 + log.info("[Media Analysis] mediaInfo path:{}", mediaInfoPath);
  29 + return mediaInfoPath;
  30 + }
  31 +
  32 +}
  1 +package com.wondertek.vo.media;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * @Auther: WANGJING
  7 + * @Description:
  8 + * @Modifier:
  9 + * @Version:
  10 + * @TaskId: YJ-348
  11 + */
  12 +
  13 +@Data
  14 +public class AudioMedia extends SteamMedia {
  15 +
  16 +}
  1 +package com.wondertek.vo.media;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * @Auther: WANGJING
  7 + * @Description:
  8 + * @Modifier:
  9 + * @Version:
  10 + * @TaskId: YJ-348
  11 + */
  12 +
  13 +@Data
  14 +public class AudioSteam extends Stream {
  15 +
  16 + /**
  17 + * 采样评率 单位是赫兹
  18 + */
  19 + private String samplingRate;
  20 +
  21 + /**
  22 + * 采样个数
  23 + */
  24 + private String samplingCount;
  25 +
  26 + /**
  27 + * 声道
  28 + */
  29 + private String channels;
  30 +
  31 +}
  1 +package com.wondertek.vo.media;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * @Auther: WANGJING
  7 + * @Description:
  8 + * @Modifier:
  9 + * @Version:
  10 + * @TaskId: YJ-348
  11 + */
  12 +
  13 +@Data
  14 +public class ImageMedia extends Media {
  15 + /**
  16 + * 图片宽度 (单位:像素)
  17 + */
  18 + private String width;
  19 +
  20 + /**
  21 + * 图片高度(单位:像素)
  22 + */
  23 + private String height;
  24 +
  25 + /**
  26 + * 色彩空间 RGB YUV
  27 + */
  28 + private String colorSpace;
  29 +
  30 + /**
  31 + * 色度采用 4:2:0 4:4:4等
  32 + */
  33 + private String chromaSubsampling;
  34 +
  35 + /**
  36 + * 位深
  37 + */
  38 + private String bitDepth;
  39 +
  40 +}
  1 +package com.wondertek.vo.media;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonInclude;
  4 +import lombok.Data;
  5 +
  6 +/**
  7 + * @Auther: WANGJING
  8 + * @Description:
  9 + * @Modifier:
  10 + * @Version:
  11 + * @TaskId: YJ-348
  12 + */
  13 +
  14 +@Data
  15 +@JsonInclude(JsonInclude.Include.NON_NULL)
  16 +public class Media {
  17 + /**
  18 + * 媒体文件 /mnt1/video/test.mp4
  19 + */
  20 + private String fileName;
  21 +
  22 + /**
  23 + * 文件后缀名(传入媒体文件后缀,不是分析结果)
  24 + */
  25 + private String fileExtension;
  26 +
  27 + /**
  28 + * 媒体类型 MediaType取值之一
  29 + */
  30 + private String type; // 媒体类型
  31 +
  32 + /**
  33 + * 媒体文件封装格式 视频:MPEG-4(mp4,mov),MPEG-TS(ts,mts),MPEG-PS(mpg),AVI(avi),Windows
  34 + * Media(wmv),Matroska(mkv)等 音频:MPEG Audio(mp3),Wave(wav)
  35 + * 图片:JPEG(jpg,jpeg),PNG(png),GIF(gif)
  36 + */
  37 + private String format;
  38 +
  39 + /**
  40 + * 文件大小(单位:字节)
  41 + */
  42 + private String size;
  43 +
  44 +
  45 +}
  1 +package com.wondertek.vo.media;
  2 +
  3 +/**
  4 + * @Auther: WANGJING
  5 + * @Description: 媒体类型
  6 + * @Modifier:
  7 + * @Version:
  8 + * @TaskId: YJ-348
  9 + */
  10 +
  11 +// 媒体类型
  12 +public class MediaType {
  13 + public static final String IMAGE_TYPE = "Image"; // 图片
  14 + public static final String AUDIO_TYPE = "Audio"; // 音频
  15 + public static final String VIDEO_TYPE = "Video"; // 视频
  16 +}
  1 +package com.wondertek.vo.media;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import java.util.List;
  6 +
  7 +/**
  8 + * @Auther: WANGJING
  9 + * @Description:
  10 + * @Modifier:
  11 + * @Version:
  12 + * @TaskId: YJ-348
  13 + */
  14 +
  15 +@Data
  16 +public class SteamMedia extends Media {
  17 +
  18 + /**
  19 + * 音频流的个数
  20 + */
  21 + private String audioCount;
  22 +
  23 + /**
  24 + * 时长(单位是:秒 274.176 精确到毫秒)
  25 + */
  26 + private String duration;
  27 +
  28 + /**
  29 + * 平均混合码率(音视频)
  30 + */
  31 + private String overallBitRate;
  32 +
  33 + /**
  34 + * 码率模式 VBR 变码率 ,CBR恒定码率
  35 + */
  36 + private String overallBitRateMode;
  37 +
  38 + /**
  39 + * 音视频流
  40 + */
  41 + private List<Stream> steams;
  42 +
  43 +}
  1 +package com.wondertek.vo.media;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * @Auther: WANGJING
  7 + * @Description:
  8 + * @Modifier:
  9 + * @Version:
  10 + * @TaskId: YJ-348
  11 + */
  12 +
  13 +@Data
  14 +public class Stream {
  15 + /**
  16 + * 流的类型(Video,Audio)
  17 + */
  18 + private String StreamType;
  19 +
  20 + /**
  21 + * 流的编号
  22 + */
  23 + private String id;
  24 +
  25 + /**
  26 + * 流的顺序
  27 + */
  28 + private String streamOrder;
  29 +
  30 + /**
  31 + * 流的编码格式 视频AVC,MPEG Video 音频AAC,MPEG Audio,PCM等
  32 + */
  33 + private String format;
  34 +
  35 + /**
  36 + * 编码的Profile:视频:Baseline Extended Main,High 音频:"Layer 3"
  37 + */
  38 + private String formatProfile;
  39 +
  40 + /**
  41 + * 时长 单位是秒:
  42 + */
  43 + private String duration;
  44 +
  45 + /**
  46 + * 码率模式 VBR 变码率 ,CBR恒定码率
  47 + */
  48 + private String bitRateMode;
  49 +
  50 + /**
  51 + * 码率 bps(VBR时是平均码率,CBR是恒定码率)
  52 + */
  53 + private String bitRate;
  54 +
  55 + /**
  56 + * 码率最小值 bps
  57 + */
  58 + private String bitRateMinimum;
  59 +
  60 + /**
  61 + * 码率最大值 bps
  62 + */
  63 + private String bitRateMaximum;
  64 +
  65 + /**
  66 + * 帧率 fps
  67 + */
  68 + private String frameRate;
  69 +
  70 + /**
  71 + * 帧数
  72 + */
  73 + private String frameCount;
  74 +
  75 + /**
  76 + * 视频图位深或者声音采样精度
  77 + */
  78 + private String bitDepth;
  79 +
  80 + /**
  81 + * 流大小 单位是字节
  82 + */
  83 + private String streamSize;
  84 +
  85 +}
  1 +package com.wondertek.vo.media;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * @Auther: WANGJING
  7 + * @Description:
  8 + * @Modifier:
  9 + * @Version:
  10 + * @TaskId: YJ-348
  11 + */
  12 +
  13 +@Data
  14 +public class VideoMedia extends SteamMedia {
  15 +
  16 + /**
  17 + * 视频流的个数
  18 + */
  19 + private String videoCount;
  20 +
  21 + /**
  22 + * 视频封装Profile
  23 + */
  24 + private String formatProfile;
  25 +
  26 + /**
  27 + * media原始分析数据
  28 + */
  29 + private String originalInfo;
  30 +
  31 +}
  1 +package com.wondertek.vo.media;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * @Auther: WANGJING
  7 + * @Description:
  8 + * @Modifier:
  9 + * @Version:
  10 + * @TaskId: YJ-348
  11 + */
  12 +
  13 +@Data
  14 +public class VideoStream extends Stream {
  15 +
  16 + /**
  17 + * 编码的Level 3.1,
  18 + */
  19 + private String formatLevel;
  20 +
  21 + /**
  22 + * 视频GOP值 例子"M=2, N=30"
  23 + */
  24 + private String formatSettingsGOP;
  25 +
  26 + /**
  27 + * 帧模式 CFR 恒帧 VFR变帧
  28 + */
  29 + private String frameRateMode;
  30 +
  31 + /**
  32 + * 视频宽度 单位像素
  33 + */
  34 + private String width;
  35 +
  36 + /**
  37 + * 视频高度 单位像素
  38 + */
  39 + private String height;
  40 +
  41 + /**
  42 + * 视频比例 1.778(16:9);1.33(4:3)
  43 + */
  44 + private String displayAspectRatio;
  45 +
  46 + /**
  47 + * 色彩空间 RGB YUV
  48 + */
  49 + private String colorSpace;
  50 +
  51 + /**
  52 + * 色度采用 4:2:0 4:4:4等
  53 + */
  54 + private String chromaSubsampling;
  55 +
  56 + /**
  57 + * 视频扫描方式:Progressive 逐行扫描;Interlaced 隔行扫描
  58 + */
  59 + private String scanType;
  60 +
  61 +}
@@ -72,6 +72,7 @@ rec: @@ -72,6 +72,7 @@ rec:
72 file: 72 file:
73 realPath: /home/wondertek/material_file_assets/dianpian/ 73 realPath: /home/wondertek/material_file_assets/dianpian/
74 previewUrl: https://dev.aivideo.cn/mdi/rehuo-wucai-file-service/preview/crp/ #预览前缀 74 previewUrl: https://dev.aivideo.cn/mdi/rehuo-wucai-file-service/preview/crp/ #预览前缀
  75 + mediainfoPath: /usr/bin/mediainfo
75 76
76 crp: 77 crp:
77 pIp: 180.167.180.242 78 pIp: 180.167.180.242