最近项目碰到一个大坑:APP上需要在获取视频列表时就获取视频的时长,但早期上传的时候数据库都没有保存这个数据,所以前段时间添加一个时长字段,在上传时手动输入视频时长,但是之前库中有上万条数据没这个信息,如果这样一条一条手动输入,人都得疯掉。所以谁也不提不管这破事,在这之前的视频时长信息就让它空在那。最近领导让我做个按类目分类统计视频时长信息,和领导反映了这个问题,最终解决方案就把没有的做0处理。在完成了这个功能后,我就在想能用什么方式把之前的视频时长全部给更新上去。手动输入这个肯定时不行的,必须得java后台来获取录入。但上网搜索了无数的帖子,最终通过java实现的只有一种方法能用,那就是先要下载到本地,然后再一个一个的遍历查询。看着服务器上的上万个视频,想想这方法就让人头皮发麻。

  虽然没找到可行方法,但基本上都是用jave获取视频信息的。于是就去查看jave的官方API,了解到是通过FFmpeg处理多媒体文件,接着又查看FFmpeg的API,发现ffmpeg在命令行中使用时可以通过url获取视频。但使用jave工具包时获取MultimediaInfo就必须得传入File,可是又不能通过url创建File。于是就就反编译jave的jar从源码上动手。

  1. // 源码
    public MultimediaInfo getInfo(File source)
  2. throws InputFormatException, EncoderException
  3. {
  4. FFMPEGExecutor ffmpeg;
  5. ffmpeg = locator.createExecutor();
  6. ffmpeg.addArgument("-i");
  7. ffmpeg.addArgument(source.getAbsolutePath());
  8. try
  9. {
  10. ffmpeg.execute();
  11. }
  12. catch(IOException e)
  13. {
  14. throw new EncoderException(e);
  15. }
  16. MultimediaInfo multimediainfo;
  17. RBufferedReader reader = null;
  18. reader = new RBufferedReader(new InputStreamReader(ffmpeg.getErrorStream()));
  19. multimediainfo = parseMultimediaInfo(source, reader);
  20. ffmpeg.destroy();
  21. return multimediainfo;
  22. Exception exception;
  23. exception;
  24. ffmpeg.destroy();
  25. throw exception;
  26. }

ffmpeg传入参数时使用的是

  1. source.getAbsolutePath()获取文件的绝对路径,所以通过url创建File在这是获取的就是 项目路径+url了。

然后就把传入path修改成了url,但是运行还是出现 InputFormatException异常。好吧,那就继续找问题吧

然后debug发现虽然修改了path,但是这路径细看还是不对

  1. http://v1.v.123.com\11\919\2019\zb\0181.mp4
    正确的url应该是这样的:http://v1.v.123.com/11/919/2019/zb/0181.mp4

接着更正问题。

  1. if(path.indexOf("http") != -1) {
  2. path = source.getPath();
  3. path = path.split(":")[0] + "://" + path.split(":")[1].substring(1);
  4. path = path.replace("\\", "/");
  5. }

这次终于没问题了,可以正常使用了。然后还有下面这个方法的调用,源码中有个获取异常信息的也得修改path值

  1. multimediainfo = parseMultimediaInfo(source, reader);

这个也和只需重复上面的操作就OK了。这样就完全搞定了。

  1. import lx.jave.AudioAttributes;
  2. import lx.jave.AudioInfo;
  3. import lx.jave.Encoder;
  4. import lx.jave.EncoderException;
  5. import lx.jave.EncodingAttributes;
  6. import lx.jave.InputFormatException;
  7. import lx.jave.MultimediaInfo;
  8. import lx.jave.VideoInfo;
  9. import lx.jave.VideoSize;
  10.  
  11. /**
  12. * jave多媒体工具类(需导出jave jar包)
  13. * @author longxiong
  14. *
  15. */
  16. public class JaveToolsTest {
  17.  
  18. public static void main(String[] args) throws InputFormatException, EncoderException, Exception {
  19.  
  20. /**
  21. * 获取本地多媒体文件信息
  22. */
  23. // 编码器
  24. Encoder encoder = new Encoder();
  25. File file = new File("http://*****018.mp4");
  26. // 多媒体信息
  27. MultimediaInfo info = encoder.getInfo(file);
  28. // 时长信息
  29. long duration = info.getDuration();
  30. System.out.println("视频时长为:" + duration / 1000 + "秒");
  31. // 音频信息
  32. AudioInfo audio = info.getAudio();
  33. int bitRate = audio.getBitRate(); // 比特率
  34. int channels = audio.getChannels(); // 声道
  35. String decoder = audio.getDecoder(); // 解码器
  36. int sRate = audio.getSamplingRate(); // 采样率
  37. System.out.println("解码器:" + decoder + ",声道:" + channels + ",比特率:" + bitRate + ",采样率:" + sRate);
  38. // 视频信息
  39. VideoInfo video = info.getVideo();
  40. int bitRate2 = video.getBitRate();
  41. Float fRate = video.getFrameRate(); // 帧率
  42. VideoSize videoSize = video.getSize();
  43. int height = videoSize.getHeight(); // 视频高度
  44. int width = videoSize.getWidth(); // 视频宽度
  45. System.out.println("视频帧率:" + fRate + ",比特率:" + bitRate2 + ",视频高度:" + height + ",视频宽度:" + width);
  46. }
  47. }

虽然是比较简单的修改,还是附上修改后的jar包吧。

链接:https://pan.baidu.com/s/1gqsfl_2Tq2swbMY-mQUQeg
提取码:zpdh

mac系统使用下面的jar包

链接:https://pan.baidu.com/s/12g9o7NgLtze7v2aSMaGadg

附带测试一下读取性能:

单线程读取20个视频:

多线程(开启了10个线程)读取20个视频:

从数据上看采用多线程性能还是可以的。不过几千上万的数据就不知道会不会崩了。下次有空在测试一下。

一次读取2000链接测试:

多线程的处理方法:

采用多线程读取大量数据测试时,由于数据的写入是等获取完所有信息后一次写入数据的,有时会因为ffmpeg.exe进程不能正常关闭,导致程序不能执行到最后。在这个问题上卡了一段时间,以本人目前所掌握的一点点知识,最终只能以下面方式处理这个问题。

  1. @RequestMapping(value="/initVideoDuration")
  2. public void initVideoDuration(Video video, Integer page, Integer rows) throws InterruptedException {
  3. // 通过分页处理数据
  4. Integer startRow = (page - 1) * rows;
  5. List<Video> videos = videoMapper.getVideoByPage(startRow, rows);
  6. Long start = System.currentTimeMillis();
  7. int corePoolSize = 10;
  8. int maxPoolSize = 10;
  9. long keepAliveTime = 15;
  10. TimeUnit unit = TimeUnit.SECONDS;
  11. BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();
  12. // 创建线程池
  13. ThreadPoolExecutor pool = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, unit, workQueue);
  14. pool.allowCoreThreadTimeOut(true);
  15. CountDownLatch countLatch = new CountDownLatch(videos.size());
  16. List<Video> list = new ArrayList<Video>();
  17. for(int i = 0; i < videos.size(); i++) {
  18. // 将任务加入线程池
  19. pool.execute(new ExecutorTask(countLatch, videos.get(i), list));
  20. }
  21. // 阻塞主线程
  22. countLatch.await();
  23. // 关闭线程池
  24. pool.shutdown();
  25. // 等待任务全部执行完毕
  26. // 由于CountDownLatch为0时任务还未执行完毕,这里通过判断线程池已完成总任务数继续对主进程进行阻塞
  27. while(pool.getCompletedTaskCount() != videos.size()) {
  28. }
  29. // 当线程池关闭后强制结束所有未正常关闭的ffmpeg.exe进程
  30. String cmd = "taskkill /F /IM ffmpeg.exe";
  31. Runtime runtime = Runtime.getRuntime();
  32. try {
  33. runtime.exec(cmd);
  34. } catch (IOException e) {
  35. e.printStackTrace();
  36. }
  37. long end = System.currentTimeMillis();
  38. System.out.println("总完成任务数:" + pool.getCompletedTaskCount());
  39. System.out.println("---------");
  40. System.out.println("读取" + videos.size() + "个链接,成功获取" + list.size() + "个视频,耗时:" + (end - start)/1000 + "秒");
  41. // 写入数据库
  42. videoMapper.addVideoDuration(list);
  43. }
  1. /**
  2. * @author longxiong
  3. * 使用CountDownLatch计数器监视任务的执行情况。
  4. * 由于ffmpeg进程可能会出现没能正常关闭,导致任务一直处于执行状态,所以在开启任务时就先将CountDownLatch减1,防止CountDownLatch值不能为0
  5. *
  6. */
  7. class ExecutorTask implements Runnable {
  8. private CountDownLatch latch;
  9. private Video video;
  10. private List<Video> videoList;
  11.  
  12. public ExecutorTask(CountDownLatch latch, Video video, List<Video> videoList) {
  13. this.latch = latch;
  14. this.video = video;
  15. this.videoList = videoList;
  16. }
  17. @Override
  18. public void run() {
  19. Encoder encoder = new Encoder();
  20. String url = video.getVideoUrl();
  21. File file = new File(url);
  22. try {
  23. // 计数器减1
  24. latch.countDown();
  25. MultimediaInfo info = encoder.getInfo(file);
  26. long duration = info.getDuration()/1000;
  27. String time = "";
  28. // 获取duration为总秒数,格式化为HH:mm:ss
  29. if(duration != 0) {
  30. if(duration/3600 != 0) {
  31. time += duration/3600 + ":";
  32. }
  33. if(duration/60 != 0) {
  34. if((duration%3600)/60 < 10) {
  35. time += "0";
  36. }
  37. time += (duration%3600)/60 + ":";
  38. } else {
  39. time += "00:";
  40. }
  41. if(duration%60 < 10) {
  42. time += "0";
  43. }
  44. time += duration%60;
  45. }
  46.  
  47. Video v = new Video();
  48. v.setId(video.getId());
  49. v.setDuration(time);
  50. videoList.add(v);
  51. System.out.println("视频ID:" + video.getId() + ",时长:" + time + ",总共:" + duration + "秒");
  52. } catch(Exception e) {
  53. System.out.println("视频" + video.getId() + "获取时长失败");
  54. }
  55. }

JAVA通过URL链接获取视频文件信息(无需下载文件)的更多相关文章

  1. C#实现多文件上传,写到文件夹中,获取文件信息以及下载文件和删除文件

    前台:.js //上传附件 function uploadAttachment() { if ($("#Tipbind").attr('checked')) { var ip = ...

  2. java 实现视频转换通用工具类:获取视频元数据信息(一)

    java 做视频转换主要用到开源的ffmpeg或者mencoder,还要有MP4Box. 注:由于平时都没有时间写博客,所以思路我就不写了,有问题问我,不一定马上回复. 详细介绍: ffmpeg:ht ...

  3. appium+java(八)获取Toast内容信息

    前言 Appium中很经典的问题了,在两年前也就是2017年3月6号07:22分,我才看到appium1.6.3版本的发布,更新内容为Ios上可以实现Toast的获取,而Windows也就是安卓端,还 ...

  4. java根据ip地址获取详细地域信息的方法

    通过淘宝IP地址库获取IP位置(也可以使用新浪的) 请求接口(GET):http://ip.taobao.com/service/getIpInfo.php?ip=[ip地址字串] 响应信息:(jso ...

  5. 把jmeter获取到的信息存到本地文件

    1.jmeter使用正则表达式提取器,获取到响应信息,把获取到的响应信息写到本地文件 2.添加后置Bean Shell ,写入以下脚本 3.打开本地文件查看,写入成功 脚本内容如下: FileWrit ...

  6. [Java反射基础二]获取类的信息

    本文接上文“Class类的使用”,以编写一个用来获取类的信息(成员函数.成员变量.构造函数)的工具类来讲解"反射之获取类的信息" 1.获取成员函数信息 /** * 获取成员函数信息 ...

  7. 玩玩微信公众号Java版之五:获取关注用户信息

    在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的.对于不同公众号,同一用户的openid不同).公众号可通过本接口来根据Op ...

  8. HDFS设计思路,HDFS使用,查看集群状态,HDFS,HDFS上传文件,HDFS下载文件,yarn web管理界面信息查看,运行一个mapreduce程序,mapreduce的demo

    26 集群使用初步 HDFS的设计思路 l 设计思想 分而治之:将大文件.大批量文件,分布式存放在大量服务器上,以便于采取分而治之的方式对海量数据进行运算分析: l 在大数据系统中作用: 为各类分布式 ...

  9. Springboot(九).多文件上传下载文件(并将url存入数据库表中)

    一.   文件上传 这里我们使用request.getSession().getServletContext().getRealPath("/static")的方式来设置文件的存储 ...

随机推荐

  1. LINUX基础内容

    在Linux中,有三种基本的文件类型: 1) 普通文件 普通文件是以字节为单位的数据流,包括文本文件.源码文件.可执行文件等.文本和二进制对Linux来说并无区别,对普通文件的解释由处理该文件的应用程 ...

  2. QTableWidget和 QTableView翻页效果(准确计算Scroll,然后使用setSliderPosition函数)

    以QTableView举例,QTableWidget使用相同   int CQTTableViewPageSwitch::pageCount(QTableView *p)//QTableView 总页 ...

  3. BI-学习之 商业智能平台的引入(传统关系型数据库的问题)

    早在 SQL Server 2005里面就有了这种 完整的商业智能平台了,那时候Nosql什么的都还停留在概念性的提出阶段,发展至2009年才一下子蹦了出来变得众所周知了.当然这个要扯就扯远了,咱们还 ...

  4. 获取其他进程中StatusBar的文本

    (*// 标题:获取其他进程中StatusBar的文本 说明:Window2000+Delphi6调试通过 设计:Zswang 支持:wjhu111@21cn.com 日期:2005-02-22 // ...

  5. Linux ssh及远程连接工具

    putty:http://www.so.com/link?url=http%3A%2F%2Fsoftdl.360tpcdn.com%2FPuTTY%2FPuTTY_0.67.zip&q=put ...

  6. Delphi 10.2 Tokyo的新特性

    Delphi 10.2(Tokyo)出来一段时间了,最重要的新特性就是支持Linux的服务端. 官网有详细的介绍: 这里是主要的特性介绍:https://www.embarcadero.com/pro ...

  7. JAVA 拼接了一个sql 语句,但是最后运行报错——SQL 命令未正确结束

    错误原因: 拼接的时候因为引号里的部分是直接引起来的,所以将这些语句整个拼接起来的时候就会成为一个“没有断句”的sql语句,如下面我的错误 将整句话拼接起来就相当于   select * from B ...

  8. 高性能高并发网站架构,教你搭建Redis5缓存集群

    一.Redis集群介绍 Redis真的是一个优秀的技术,它是一种key-value形式的NoSQL内存数据库,由ANSI C编写,遵守BSD协议.支持网络.可基于内存亦可持久化的日志型.Key-Val ...

  9. 基于 HTML5 Canvas 的元素周期表展示

    前言 之前在网上看到别人写的有关元素周期表的文章,深深的勾起了一波回忆,记忆里初中时期背的“氢氦锂铍硼,碳氮氧氟氖,钠镁铝硅磷,硫氯氩钾钙”.“养(氧)龟(硅)铝铁盖(钙),哪(钠)家(钾)没(镁)青 ...

  10. 机器学习经典算法之EM

    一.简介 EM 的英文是 Expectation Maximization,所以 EM 算法也叫最大期望算法. 我们先看一个简单的场景:假设你炒了一份菜,想要把它平均分到两个碟子里,该怎么分? 很少有 ...