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

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

// 源码
public MultimediaInfo getInfo(File source)
throws InputFormatException, EncoderException
{
FFMPEGExecutor ffmpeg;
ffmpeg = locator.createExecutor();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(source.getAbsolutePath());
try
{
ffmpeg.execute();
}
catch(IOException e)
{
throw new EncoderException(e);
}
MultimediaInfo multimediainfo;
RBufferedReader reader = null;
reader = new RBufferedReader(new InputStreamReader(ffmpeg.getErrorStream()));
multimediainfo = parseMultimediaInfo(source, reader);
ffmpeg.destroy();
return multimediainfo;
Exception exception;
exception;
ffmpeg.destroy();
throw exception;
}

ffmpeg传入参数时使用的是

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

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

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

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

接着更正问题。

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

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

multimediainfo = parseMultimediaInfo(source, reader);

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

import lx.jave.AudioAttributes;
import lx.jave.AudioInfo;
import lx.jave.Encoder;
import lx.jave.EncoderException;
import lx.jave.EncodingAttributes;
import lx.jave.InputFormatException;
import lx.jave.MultimediaInfo;
import lx.jave.VideoInfo;
import lx.jave.VideoSize; /**
* jave多媒体工具类(需导出jave jar包)
* @author longxiong
*
*/
public class JaveToolsTest { public static void main(String[] args) throws InputFormatException, EncoderException, Exception { /**
* 获取本地多媒体文件信息
*/
// 编码器
Encoder encoder = new Encoder();
File file = new File("http://*****018.mp4");
// 多媒体信息
MultimediaInfo info = encoder.getInfo(file);
// 时长信息
long duration = info.getDuration();
System.out.println("视频时长为:" + duration / 1000 + "秒");
// 音频信息
AudioInfo audio = info.getAudio();
int bitRate = audio.getBitRate(); // 比特率
int channels = audio.getChannels(); // 声道
String decoder = audio.getDecoder(); // 解码器
int sRate = audio.getSamplingRate(); // 采样率
System.out.println("解码器:" + decoder + ",声道:" + channels + ",比特率:" + bitRate + ",采样率:" + sRate);
// 视频信息
VideoInfo video = info.getVideo();
int bitRate2 = video.getBitRate();
Float fRate = video.getFrameRate(); // 帧率
VideoSize videoSize = video.getSize();
int height = videoSize.getHeight(); // 视频高度
int width = videoSize.getWidth(); // 视频宽度
System.out.println("视频帧率:" + fRate + ",比特率:" + bitRate2 + ",视频高度:" + height + ",视频宽度:" + width);
}
}

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

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

mac系统使用下面的jar包

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

附带测试一下读取性能:

单线程读取20个视频:

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

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

一次读取2000链接测试:

多线程的处理方法:

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

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

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. How to setup Assigned Access in Windows 10 (Kiosk Mode) 设置分配的访问权限(Kiosk模式)

    Let’s say you’re building some sort of ingenious mechanical contraption to be displayed in public th ...

  2. JavaScript MVC框架PK:Angular、Backbone、CanJS与Ember(转载)

    原文地址:http://sporto.github.io/.../comparison-angular-backbone-can-ember/ 原文作者:Sebastian Porto @Twitte ...

  3. Google三驾马车:GFS、MapReduce和Bigtable

    谈到分布式系统,就不得不提Google的三驾马车:Google fs[1],Mapreduce[2],Bigtable[3]. 虽然Google没有公布这三个产品的源码,但是他发布了这三个产品的详细设 ...

  4. sublime3使用笔记

    1.ctrl+n 新建一个文件: 2.alt+shift+数字 分屏显示: 3.ctrl+alt+down(向下键) 连选很多行的指定开始位置: 如图: 紧接着再按shift+right(选中需要更改 ...

  5. 3022Java_运算

    运算 1.运算符分类 算术运算符 二元运算符 +,-,*,/,% 一元运算符 ++,-- 赋值运算符   = 扩展运算符 +=,-=,*=,/= 关系运算符 >,<,>=,<= ...

  6. ZooKeeper学习第七期--ZooKeeper一致性原理(转)

    转载来源:https://www.cnblogs.com/sunddenly/p/4138580.html 一.ZooKeeper 的实现 1.1 ZooKeeper处理单点故障 我们知道可以通过Zo ...

  7. Spring源码解读之BeanFactoryPostProcessor的处理

    前言 前段时间旁听了某课堂两节Spring源码解析课,刚好最近自己又在重新学习中,便在这里记录一下学习所得.我之前写过一篇博文,是介绍BeanFactoryPostProcessor跟BeanPost ...

  8. 视频编解码的理论和实践2:Ffmpeg视频编解码

    近几年,视频编解码技术在理论及应用方面都取得了重大的进展,越来越多的人想要了解编解码技术.因此,网易云信研发工程师为大家进行了归纳梳理,从理论及实践两个方面简单介绍视频编解码技术. 相关阅读推荐 &l ...

  9. 修改npm默认安装路径

    npm config ls npm config set prefix D:\ag\npm

  10. 妹子问我maven是啥?从相亲说起。。

    自从上一篇原创文章: 第一次教妹子安装IDEA 在<java技术之家>公号发表之后,大家的好评如潮,这给了我继续写下去的信心.感谢你们的支持,我会继续努力的. 自从漂亮妹妹加入我们研发团队 ...