上一篇 java下载m3u8视频,解密并合并ts(二)——获取m3u8链接

编写代码

加载jar包

由于java不支持AES/CBC/PKCS7Padding模式解密,所以我们要借助第一篇下载好的jar包

当类加载时,通过静态代码块加载

  /**** 解决java不支持AES/CBC/PKCS7Padding模式解密**/static {Security.addProvider(new BouncyCastleProvider());}

所需类字段

  //要下载的m3u8链接private final String DOWNLOADURL;//线程数private int threadCount = 1;//重试次数private int retryCount = 30;//链接连接超时时间(单位:毫秒)private long timeoutMillisecond = 1000L;//合并后的文件存储目录private String dir;//合并后的视频文件名称private String fileName;//已完成ts片段个数private int finishedCount = 0;//解密算法名称private String method;//密钥private String key = "";//所有ts片段下载链接private Set<String> tsSet = new LinkedHashSet<>();//解密后的片段private Set<File> finishedFiles = new ConcurrentSkipListSet<>(Comparator.comparingInt(o -> Integer.parseInt(o.getName().replace(".xyz", ""))));//已经下载的文件大小private BigDecimal downloadBytes = new BigDecimal(0);

获取链接内容

模拟HTTP请求,获取链接相应内容

       /*** 模拟http请求获取内容** @param urls http链接* @return 内容*/private StringBuilder getUrlContent(String urls) {int count = 1;HttpURLConnection httpURLConnection = null;StringBuilder content = new StringBuilder();while (count <= retryCount) {try {URL url = new URL(urls);httpURLConnection = (HttpURLConnection) url.openConnection();httpURLConnection.setConnectTimeout((int) timeoutMillisecond);httpURLConnection.setReadTimeout((int) timeoutMillisecond);httpURLConnection.setUseCaches(false);httpURLConnection.setDoInput(true);String line;InputStream inputStream = httpURLConnection.getInputStream();BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));while ((line = bufferedReader.readLine()) != null)content.append(line).append("\n");bufferedReader.close();inputStream.close();System.out.println(content);break;} catch (Exception e) {
//                    System.out.println("第" + count + "获取链接重试!\t" + urls);count++;
//                    e.printStackTrace();} finally {if (httpURLConnection != null) {httpURLConnection.disconnect();}}}if (count > retryCount)throw new M3u8Exception("连接超时!");return content;}

判断是否需要解密

首先将m3u8链接内容通过getUrlContent方法获取到,然后解析,如果内容含有#EXT-X-KEY标签,则说明这个链接是需要进行ts文件解密的,然后通过下面的.m3u8的if语句获取含有密钥以及ts片段的链接。

如果含有#EXTINF,则说明这个链接就是含有ts视频片段的链接,没有第二个m3u8链接了。

之后我们要获取密钥的getKey方法,即时不需要密钥。并把ts片段加进set集合,即tsSet字段。

 /*** 获取所有的ts片段下载链接** @return 链接是否被加密,null为非加密*/private String getTsUrl() {StringBuilder content = getUrlContent(DOWNLOADURL);//判断是否是m3u8链接if (!content.toString().contains("#EXTM3U"))throw new M3u8Exception(DOWNLOADURL + "不是m3u8链接!");String[] split = content.toString().split("\\n");String keyUrl = "";boolean isKey = false;for (String s : split) {//如果含有此字段,则说明只有一层m3u8链接if (s.contains("#EXT-X-KEY") || s.contains("#EXTINF")) {isKey = true;keyUrl = DOWNLOADURL;break;}//如果含有此字段,则说明ts片段链接需要从第二个m3u8链接获取if (s.contains(".m3u8")) {if (StringUtils.isUrl(s))return s;String relativeUrl = DOWNLOADURL.substring(0, DOWNLOADURL.lastIndexOf("/") + 1);keyUrl = relativeUrl + s;break;}}if (StringUtils.isEmpty(keyUrl))throw new M3u8Exception("未发现key链接!");//获取密钥String key1 = isKey ? getKey(keyUrl, content) : getKey(keyUrl, null);if (StringUtils.isNotEmpty(key1))key = key1;else key = null;return key;}

获取密钥

如果参数content不为空,则说明密钥信息从此字段取,否则则访问第二个m3u8链接,然后获取信息。

也就是说,如果content为空,说明则为样例一,三的情况,第一个m3u8文件里面没有ts片段信息,需要从第二个m3u8文件取。

如果发现不需要解密,此方法将会返回null。需要解密的话,那么解密算法将会存在method字段,密钥将存在key字段。

/*** 获取ts解密的密钥,并把ts片段加入set集合** @param url     密钥链接,如果无密钥的m3u8,则此字段可为空* @param content 内容,如果有密钥,则此字段可以为空* @return ts是否需要解密,null为不解密*/private String getKey(String url, StringBuilder content) {StringBuilder urlContent;if (content == null || StringUtils.isEmpty(content.toString()))urlContent = getUrlContent(url);else urlContent = content;if (!urlContent.toString().contains("#EXTM3U"))throw new M3u8Exception(DOWNLOADURL + "不是m3u8链接!");String[] split = urlContent.toString().split("\\n");for (String s : split) {//如果含有此字段,则获取加密算法以及获取密钥的链接if (s.contains("EXT-X-KEY")) {String[] split1 = s.split(",", 2);if (split1[0].contains("METHOD"))method = split1[0].split("=", 2)[1];if (split1[1].contains("URI"))key = split1[1].split("=", 2)[1];}}String relativeUrl = url.substring(0, url.lastIndexOf("/") + 1);//将ts片段链接加入set集合for (int i = 0; i < split.length; i++) {String s = split[i];if (s.contains("#EXTINF"))tsSet.add(relativeUrl + split[++i]);}if (!StringUtils.isEmpty(key)) {key = key.replace("\"", "");return getUrlContent(relativeUrl + key).toString().replaceAll("\\s+", "");}return null;}

解密ts片段

目前此程序只支持AES算法,因为目前我没有遇到别的。。。

如果你的m3u8发现了EXT-X-KEY标签,并且后面后IV键值对,那么请new IvParameterSpec(new byte[16]);的参数换成IV后面的值(把字符串通过getBytes换成字节数组)(git代码已实现此功能)

 
/*** 解密ts** @param sSrc ts文件字节数组* @param sKey 密钥* @return 解密后的字节数组*/private static byte[] decrypt(byte[] sSrc, String sKey, String method) {try {if (StringUtils.isNotEmpty(method) && !method.contains("AES"))throw new M3u8Exception("未知的算法!");// 判断Key是否正确if (StringUtils.isEmpty(sKey)) {return sSrc;}// 判断Key是否为16位if (sKey.length() != 16) {System.out.print("Key长度不是16位");return null;}Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");SecretKeySpec keySpec = new SecretKeySpec(sKey.getBytes("utf-8"), "AES");//如果m3u8有IV标签,那么IvParameterSpec构造函数就把IV标签后的内容转成字节数组传进去AlgorithmParameterSpec paramSpec = new IvParameterSpec(new byte[16]);cipher.init(Cipher.DECRYPT_MODE, keySpec, paramSpec);return cipher.doFinal(sSrc);} catch (Exception ex) {ex.printStackTrace();return null;}}

启动线程下载ts片段

代码中xy后缀文件是未解密的ts片段,xyz是解密后的ts片段,这两个后缀起成什么无所谓。

如果线程数设置的大,那么占内存就会很多,这个是因为代码中byte1变量没有进行复用,垃圾回收没有即时回收引起的,可以自己优化一下。

/*** 开启下载线程** @param urls ts片段链接* @param i    ts片段序号* @return 线程*/private Thread getThread(String urls, int i) {return new Thread(() -> {int count = 1;HttpURLConnection httpURLConnection = null;//xy为未解密的ts片段,如果存在,则删除File file2 = new File(dir + "\\" + i + ".xy");if (file2.exists())file2.delete();OutputStream outputStream = null;InputStream inputStream1 = null;FileOutputStream outputStream1 = null;//重试次数判断while (count <= retryCount) {try {//模拟http请求获取ts片段文件URL url = new URL(urls);httpURLConnection = (HttpURLConnection) url.openConnection();httpURLConnection.setConnectTimeout((int) timeoutMillisecond);httpURLConnection.setUseCaches(false);httpURLConnection.setReadTimeout((int) timeoutMillisecond);httpURLConnection.setDoInput(true);InputStream inputStream = httpURLConnection.getInputStream();try {outputStream = new FileOutputStream(file2);} catch (FileNotFoundException e) {e.printStackTrace();}int len;byte[] bytes = new byte[1024];//将未解密的ts片段写入文件while ((len = inputStream.read(bytes)) != -1) {outputStream.write(bytes, 0, len);synchronized (this) {downloadBytes = downloadBytes.add(new BigDecimal(len));}}outputStream.flush();inputStream.close();inputStream1 = new FileInputStream(file2);byte[] bytes1 = new byte[inputStream1.available()];inputStream1.read(bytes1);File file = new File(dir + "\\" + i + ".xyz");outputStream1 = new FileOutputStream(file);//开始解密ts片段,这里我们把ts后缀改为了xyz,改不改都一样outputStream1.write(decrypt(bytes1, key, method));finishedFiles.add(file);break;} catch (Exception e) {//                        System.out.println("第" + count + "获取链接重试!\t" + urls);count++;
//                        e.printStackTrace();} finally {try {if (inputStream1 != null)inputStream1.close();if (outputStream1 != null)outputStream1.close();if (outputStream != null)outputStream.close();} catch (IOException e) {e.printStackTrace();}if (httpURLConnection != null) {httpURLConnection.disconnect();}}}if (count > retryCount)//自定义异常throw new M3u8Exception("连接超时!");finishedCount++;
//                System.out.println(urls + "下载完毕!\t已完成" + finishedCount + "个,还剩" + (tsSet.size() - finishedCount) + "个!");});}

合并以及删除多余的ts片段

/*** 合并下载好的ts片段*/private void mergeTs() {try {File file = new File(dir + "/" + fileName + ".mp4");if (file.exists())file.delete();else file.createNewFile();FileOutputStream fileOutputStream = new FileOutputStream(file);byte[] b = new byte[4096];for (File f : finishedFiles) {FileInputStream fileInputStream = new FileInputStream(f);int len;while ((len = fileInputStream.read(b)) != -1) {fileOutputStream.write(b, 0, len);}fileInputStream.close();fileOutputStream.flush();}fileOutputStream.close();} catch (Exception e) {e.printStackTrace();}}/*** 删除下载好的片段*/private void deleteFiles() {File file = new File(dir);for (File f : file.listFiles()) {if (!f.getName().contains(fileName + ".mp4"))f.deleteOnExit();}}

开始多线程下载

这里有个问题,就是System.out.println("视频合并完成,欢迎使用!");打印出来了,但是文件还没有删除完,当控制台输出Process finished with exit code 0的时候才说明执行完。

/*** 下载视频*/private void startDownload() {//线程池final ExecutorService fixedThreadPool = Executors.newFixedThreadPool(threadCount);int i = 0;//如果生成目录不存在,则创建File file1 = new File(dir);if (!file1.exists())file1.mkdirs();//执行多线程下载for (String s : tsSet) {i++;fixedThreadPool.execute(getThread(s, i));}fixedThreadPool.shutdown();//下载过程监视new Thread(() -> {int consume = 0;//轮询是否下载成功while (!fixedThreadPool.isTerminated()) {try {consume++;BigDecimal bigDecimal = new BigDecimal(downloadBytes.toString());Thread.sleep(1000L);System.out.print("已用时" + consume + "秒!\t下载速度:" + StringUtils.convertToDownloadSpeed(new BigDecimal(downloadBytes.toString()).subtract(bigDecimal), 3) + "/s");System.out.print("\t已完成" + finishedCount + "个,还剩" + (tsSet.size() - finishedCount) + "个!");System.out.println(new BigDecimal(finishedCount).divide(new BigDecimal(tsSet.size()), 4, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP) + "%");} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("下载完成,正在合并文件!共" + finishedFiles.size() + "个!" + StringUtils.convertToDownloadSpeed(downloadBytes, 3));//开始合并视频mergeTs();//删除多余的ts片段deleteFiles();System.out.println("视频合并完成,欢迎使用!");}).start();}

启动入口

startDownload()方法可以放进getTsUrl()方法里面。

        /*** 开始下载视频*/public void start() {checkField();String tsUrl = getTsUrl();if(StringUtils.isEmpty(tsUrl))System.out.println("不需要解密");startDownload();}

测试类

public class M3u8Main {private static final String M3U8URL = "https://XXX/index.m3u8";public static void main(String[] args) {M3u8DownloadFactory.M3u8Download m3u8Download =  M3u8DownloadFactory.getInstance(M3U8URL);//设置生成目录m3u8Download.setDir("F://m3u8JavaTest");//设置视频名称m3u8Download.setFileName("test");//设置线程数m3u8Download.setThreadCount(100);//设置重试次数m3u8Download.setRetryCount(100);//设置连接超时时间(单位:毫秒)m3u8Download.setTimeoutMillisecond(10000L);m3u8Download.start();}
}

100个线程测试效果

git地址:https://github.com/qq494257084/m3u8Download

上一篇 java下载m3u8视频,解密并合并ts(二)——获取m3u8链接

java下载m3u8视频,解密并合并ts(三)相关推荐

  1. java下载m3u8视频,解密并合并ts(二)

    上一篇 java下载m3u8视频,解密并合并ts(一)--m3u8概述 下一篇java下载m3u8视频,解密并合并ts(三)--代码实现 m3u8链接的获取 样例一:两个m3u8无key 首先在浏览器 ...

  2. Java合并m3u8_m3u8Download java 下载 m3u8 视频,解密并合并 ts java download m3u8 vide @codeKK Android开源站...

    java 下载 m3u8 视频,解密并合并 ts m3u8 链接获取方式以及代码分析请参见: https://blog.csdn.net/qq494257084/article/details/103 ...

  3. PHP下载m3u8视频及解析下载ts资源文件

    上一篇写了PHP生成合并ts脚本,后来又想直接下载m3u8里面的ts,又码了一段. <?php /** PHP下载m3u8视频和及解析下载ts资源文件** @file m3u8download. ...

  4. android 转码工具下载,m3u8视频转码工具

    m3u8视频转码工具是一款非常好用的个m3u8视频工具可以帮助我们对视频进行很好的处理.M3u8视频合并软件支持各种短视频.Mp4文件,通过M3u8合并工具可以快速将这些视频合并在一起.软件操作简单, ...

  5. nodejs使用fluent-ffmpeg下载m3u8视频

    nodejs使用 fluent-ffmpeg下载m3u8视频 在使用fluent-ffmpeg之前,要确保电脑上已经安装了ffmpeg 安装包 fluent-ffmpeg npm install fl ...

  6. 使用 ffmpeg 下载 m3u8 视频,linux 后台运行 ffmpeg

    使用 ffmpeg 下载 m3u8 视频,linux 后台运行 ffmpeg 下载 m3u8 现在的视频大多数都是 m3u8 格式,网上的介绍也不少,那我直接记录一下下载的方法 方法 1 M3U8-D ...

  7. 下载m3u8视频及在Linux下将ts合并为mp4格式

    背景 在爬取视频时偶尔会遇见m3u8格式的视频链接,视频下载后为多个ts文件,下面分享如何下载m3u8格式视频以及在Linux下将ts文件合成mp4 文件. m3u8格式链接解析 url = xxxx ...

  8. java下载m3u8转ts合成mp4

    java 基础不太行,就当练习,有能力的话可以写个浏览器获取视频m3u8 合并ts时内存占用高,有待优化成逐个合成 package com.huasejj.j;import org.apache.co ...

  9. python 下载m3u8视频

    有一个朋友,通过一个网站(https://leetcode-cn.com/problems/course-schedule/solution/ke-cheng-biao-by-leetcode-sol ...

最新文章

  1. mysql修改表中某个字段的默认值
  2. MySQL同步复制搭建方法指南详细步骤
  3. RotateWorldTest对层动作
  4. python selenium grid_selenium grid 使用
  5. Linux C: 文件操作相关的系统调用
  6. boost::histogram::axis::circular用法的测试程序
  7. android 使用Photoshop获取图片某一点的颜色
  8. 深入理解和应用Float属性
  9. Webpack 4进阶--从前的日色变得慢 ,一下午只够打一次包
  10. linux之task_struct
  11. java删除换行符号_如何从Java中删除文件中的换行符?
  12. SPOOLing假脱机技术详细介绍
  13. 【Python】line.strip().split(‘,‘)含义
  14. 谷歌悄悄修复4个 0day
  15. UVA10945 Mother bear【Ad Hoc】
  16. 目标服务器已挂起重新启动_CentOS 7 Apache 服务器证书安装
  17. 免费车型车系品牌api
  18. 深入解析CAS算法原理
  19. android4.4呼叫转移,安卓呼叫转移
  20. C++ struct内部定义函数

热门文章

  1. 程序员修炼之路:算法的力量
  2. 验证码短信接口可以广泛应用在网站会员手机验证、APP应用手机验证、订单通知、物流提醒等触发类短信应用。
  3. 百度地图(BD-09)坐标系和高德(GCJ-02)坐标系的相互转换(PHP版)
  4. 网页loading GIF图片(加载)
  5. 原来Glide是这样加载GIF图的
  6. 图像去雾算法的原理、实现、效果
  7. 换标是起点,金蝶新一轮变革的引擎强劲开启?
  8. JavaScript实现网站首页动态效果
  9. 基于Springboot的高校食堂管理系统
  10. 关于“QTableWidget设置表头筛选”实现及小改——“QTableWidget设置表头筛选”引用自博主“~南柯一梦~”