Javacv 音视频小工具 - 下载抖音无水印视频
一、前言
大家好, 俗话说的好, 学习新的知识后要学以致用, 在学习音视频的过程中, 你有没有疑问, 不知道音视频可以用来做什么. 下面举几个例子, 比较耳熟能详, 被吹到风口的一些场景有: AI视觉计算, AI人脸识别. 细化到一些小的领域, 如现在直播技术, 摄像头监控拉流; 其他还有抖音中的美颜, 滤镜, 其背后是使用的音视频领域的数字化妆技术.由此可见,音视频技术应用已经应用于我们生活的方方面面。
二、开发背景
想写这篇文章的目的是因为,我有个朋友平时喜欢刷抖音, 就经常有一些视频被作者设置成了不可下载保存, 朋友下次想再看的话就找不到了. 还有朋友想下载暗恋的妹纸的作品. 所以就把苦闷告诉我了, 作为朋友当然有义务帮助他走出困境啦, 终于, Two thousand years later 的今天, 这个小工具终于问世, 因为时间原因, 来不及写前端页面了, 后面有需要的同学可以关注或者私信我, 我们一起学习, 另外, 写本文的目的纯粹是以学习为主, 如不小心被不法分子滥用以盈利为目的, 与本人无关, 请广大道友积极爱护学习环境, 记得不要连累我。
三、核心思想
四、主要技术点
1
2
3
4
5
6
来自网上的一段介绍/概括
FFmpegFrameGrabber用于采集/抓取视频图像和音频采样。封装了检索流信息,自动猜测视频解码格式,音视频解码等具体API,并把解码完的像素数据(可配置像素格式)或音频数据保存到Frame中返回等功能。
7
ffmpeg -i https://xxx.mp4 -c copy -f flv 艾北.flv
8
以下是音视频大佬eguid对于FrameRecorder的介绍概括
FrameRecorder用于音视频/图片的封装、编码、推流和录制保存等操作。把从FrameGrabber或者FrameFilter获取的Frame中的数据取出并进行编码、封装、推流发送等操作流程。为了方便理解和阅读,下文开始我们统一把FrameRecorder简称为:录制器
五、详细思路
1、链接解析&接口解析
(1)、Java 正则表达式从字符串中提取出 url。
(2)、使用RestTemplate.headForHeaders() 方法获取某个资源的URI的请求头信息,并且只专注于获取HTTP请求头信息。
(3)、第一步中提取出的url在浏览器中模拟可以发现, 会重定向到一个新的地址, 从请求头中获取重定向后的地址, 即从header中获取location, 然后从location中获取视频的真实id。
(4)、根据视频真实id和抖音的接口去获取视频信息,如播放信息, 作者信息, 背景音乐信息等等, 使用json一层一层解析出来播放地址的url。
2、ffmpeg拉流并保存
(1)、使用ffmpeg 获取url视频帧的第一帧,检测视频是否是空视频。
(2)、创建视频流录制器,设置视频参数,分辨率,格式,输出位置。
(3)、循环获取视频帧, 使用录制器recorder逐帧录制视频帧。
六、核心代码
1、使用正则提取 url
/**
* 正则表达式提取 url
* @param text
* @return
*/
public static String pickURI(String text) {
// eg: text = "5.1 GV:/ 一出场就给人一种江南的感觉%刘亦菲 %精彩片段 %歌曲红马 https://v.douyin.com/e614JkV/ 腹制佌lian接,打开Dou音搜索,直接观kan视頻!";
Pattern pattern = Pattern.compile("https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]");
Matcher matcher = pattern.matcher(text);
if (matcher.find()) {
return matcher.group();
}
return "";
}
2、发起网络调用,解析json获取真实地址
public final static String DOU_YIN_WEB_API = "https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=";
/**
* 根据赋值的分享码下载抖音视频
* @param text
* @throws FrameGrabber.Exception
* @throws FrameRecorder.Exception
*/
public static String douYin(String text) throws FrameGrabber.Exception, FrameRecorder.Exception {
//
String url = pickURI(text);
RestTemplate client = new RestTemplate();
//
HttpHeaders headers = client.headForHeaders(url);
String location = headers.getLocation().toString();
String vid = StringUtils.substringBetween(location, "/video/", "/?");
RestTemplate restTemplate = new RestTemplate();
HttpHeaders queryHeaders = new HttpHeaders();
queryHeaders.set(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36");
HttpEntity entity = new HttpEntity<>(queryHeaders);
ResponseEntity response = restTemplate.exchange(DOU_YIN_WEB_API + vid, HttpMethod.GET, entity, JSONObject.class);
if(response.getStatusCodeValue() != 200) {
return "";
}
JSONObject body = response.getBody();
assert body != null;
List list = JSONArray.parseObject(body.getJSONArray("item_list").toJSONString(), new TypeReference>(){});
if(list.size() == 0) {
return "";
}
JSONObject item = list.get(0);
JSONObject video = item.getJSONObject("video");
JSONObject playAddr = video.getJSONObject("play_addr");
JSONArray urlList = playAddr.getJSONArray("url_list");
List urlListArr = JSONArray.parseObject(urlList.toJSONString(), new TypeReference>(){});
if(urlListArr.size() == 0) {
return "";
}
// https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200fg10000c5sjikbc77u7rghj00a0&ratio=720p&line=0
// playwm 表示有水印的地址 替换成无水印的地址 wm是water mark的缩写
return urlListArr.get(0).replace("playwm", "play");
// VideoConvert.record(finalAddr, "/home/yinyue/upload/红马.flv");
}
3、ffmpeg拉流并保存
/**
* 转存视频流
* @param input
* @param outFile
* @throws FrameGrabber.Exception
* @throws FrameRecorder.Exception
*/
public static void record(String input, String outFile) throws FrameGrabber.Exception, FrameRecorder.Exception {
FrameGrabber grabber = new FFmpegFrameGrabber(input);
grabber.start();
OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();
Frame frame = grabber.grab();
opencv_core.IplImage image = null;
if(frame == null) {
System.out.println("第一帧为空,请检查视频源");
return;
}
image = converter.convert(frame);
FrameRecorder recorder = FrameRecorder.createDefault(outFile, frame.imageWidth, frame.imageHeight);
recorder.setVideoCodec(AV_CODEC_ID_H264);
recorder.setFormat("flv");
recorder.setFrameRate(25);
recorder.setGopSize(25);
recorder.start();
Frame saveFrame;
while((frame = grabber.grab()) != null) {
saveFrame = converter.convert(image);
// 获取类型, 视频或者音频
// EnumSet videoOrAudio = saveFrame.getTypes();
// 录制视频
recorder.record(saveFrame);
}
recorder.close();
}
七、运行截图


运行完成后本地成功生成了下载的视频文件

八、作者心得
我们生在一个技术百花齐放, 日新月异的年代, 生于这个时代即是幸运也是悲哀, 在如此浩瀚无穷无尽的知识更迭浪潮中, 很难保证全能, 尽善尽美; 有的人专注于算法, 有的人专注与数据处理, 还有的人动手能力不行, 但是理论能力极强, 比如著名物理学家杨振宁, 有的人专注于如何应用落地, 致力于将技术应用于社会生活, 所以, 如果本文对你有用, 请不吝赞赏, 如果你感觉内容过于浅薄或者是令你感到不适, 也请缄默不言, 互相留一份体面。
感谢阅读, 如需源码请联系作者。