JAVA通过URL链接获取视频文件信息(无需下载文件)

  最近项目碰到一个大坑: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() + "获取时长失败");
        }
    }

 

posted @ 2019-07-22 13:20  hello龙兄  阅读(11684)  评论(5编辑  收藏  举报