一气之下,手撸了一个抖音去水印的工具!

百因必有果

说一下我为什么要做个抖音视频去水印工具,其实是因为我的沙雕女友,她居然刚我~

有天晚上她在抖音看见一个非常具有 教育意义 的视频,“男人疼媳妇就该承包全部家务活”,然后它就想把视频下载下来,分享到她的姐妹群交流 驭夫 心得。

可是大家都知道抖音下载的视频是带水印,作为一个重度强迫症选手这是不被允许的,没办法那就找找有没有去水印工具吧,找了一圈要不就是收费,要么下载不下来,主上脸上的笑容也在逐渐消失。

我在边上调侃了一句:也没多难,要不我给你做一个!“你行吗?” 然后投来了一个不屑的眼神。

敢说我不行?

哎呀!本来就开个玩笑,居然说我不行,这就不能忍了,我得证明给你看看!男人嘛,就受不了这话

(建议用谷歌浏览器),工具线上预览效果:http://47.93.6.5:8888/index

去水印使用预览

下边和大家一起分析下做这个去水印工具的思路,很多人乍一听 去水印 ,下意识的觉得是一种什么牛比的算法,其实这是一种假象~

刨根问底

虽说要争口气,可刚开始做的时候我也真是一脸懵逼,因为根本不知道该从哪入手,去水印什么原理啊?难不成我还要写个算法?

找了一个抖音视频的分享链接,一点点分析,不难发现这是个经过处理的短链接,那这个短链接一定会重定向到真实的视频地址 URL

https://v.douyin.com/JSkuhE4/

浏览器中输入短链接得到了下边这个 URL ,以我的经验判断URL中的 6820792802394262795 很有可能是视频的唯一ID,而唯一ID通常用来作为获取详情接口的入参,哎嘿~ 好像有点头绪了。

https://www.iesdouyin.com/share/video/6820792802394262795/
有水印的链接

赶紧祭出 F12 大法打开控制台,在众多请求中发现这么一个接口,它居然用到了上边的唯一ID。

https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=6820792802394262795

更惊喜的是接口返回的数据那叫一个详细,作者信息、音频地址、视频地址、平面图都有。但唯独没有无水印的视频 URL只找到一个有水印的视频 URL,有点小失落,我又看了看这个地址,发现 wm 和我项目名有点像啊,不就是watermark 水印的缩写吗?

https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f030000bqk54kg2saj3lso3oh20&ratio=720p&line=0

好像又看到了一丝希望,我赶紧修改URL在浏览器中又试了一下,果然真的没水印了。

https://aweme.snssdk.com/aweme/v1/play/?video_id=v0200f030000bqk54kg2saj3lso3oh20&ratio=720p&line=0

到这才发现抖音去水印 简单的让人感动,哈哈哈~

身体力行

既然原理都清晰了,剩下的就是一步一步实现功能了,原理看着挺简单的,但实现中还是遇到一点点小坑,浪费了不少时间。

实现过程只有简单的三步:

  • 1、从输入框中过滤取出视频短连接

  • 2、短连接传到后端解析出无水印的视频 URL

  • 3、视频 URL传递给前端预览、下载

后端并没有什么难度,一步一步按照上边分析的流程解析真实视频 URL 就可以了。

注意 :我们想得到的地址URL,都是当前短连接URL 经过重定向后的URL。而抖音有些链接是不支持浏览器访问的,所以要手动修改 User-agent 属性模拟移动端访问才可以。

/**
* @param url
* @author xiaofu
* @description 获取当前链接重定向后的url
* @date 2020/9/15 12:43
*/
public static String getLocation(String url) {try {URL serverUrl = new URL(url);HttpURLConnection conn = (HttpURLConnection) serverUrl.openConnection();conn.setRequestMethod("GET");conn.setInstanceFollowRedirects(false);conn.setRequestProperty("User-agent", "ua");//模拟手机连接conn.connect();String location = conn.getHeaderField("Location");return location;} catch (Exception e) {e.printStackTrace();}return "";}

下边是完整的后端实现,可以看到代码量非常的少。

/*** @author xiaofu-公众号:程序员内点事* @description 抖音无水印视频下载* @date 2020/9/15 18:44*/
@Slf4j
@Controller
public class DYController {public static String DOU_YIN_BASE_URL = "https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=";/*** @param url* @author xiaofu* @description 解析抖音无水印视频* @date 2020/9/15 12:43*/@RequestMapping("/parseVideoUrl")@ResponseBodypublic String parseVideoUrl(@RequestBody String url) throws Exception {DYDto dyDto = new DYDto();try {url = URLDecoder.decode(url).replace("url=", "");/*** 1、短连接重定向后的 URL*/String redirectUrl = CommonUtils.getLocation(url);/*** 2、拿到视频对应的 ItemId*/String videoUrl = "";String musicUrl = "";String videoPic = "";String desc = "";if (!StringUtils.isEmpty(redirectUrl)) {/*** 3、用 ItemId 拿视频的详细信息,包括无水印视频url*/String itemId = CommonUtils.matchNo(redirectUrl);StringBuilder sb = new StringBuilder();sb.append(DOU_YIN_BASE_URL).append(itemId);String videoResult = CommonUtils.httpGet(sb.toString());DYResult dyResult = JSON.parseObject(videoResult, DYResult.class);/*** 4、无水印视频 url*/videoUrl = dyResult.getItem_list().get(0).getVideo().getPlay_addr().getUrl_list().get(0).replace("playwm", "play");String videoRedirectUrl = CommonUtils.getLocation(videoUrl);dyDto.setVideoUrl(videoRedirectUrl);/*** 5、音频 url*/musicUrl = dyResult.getItem_list().get(0).getMusic().getPlay_url().getUri();dyDto.setMusicUrl(musicUrl);/*** 6、封面*/videoPic = dyResult.getItem_list().get(0).getVideo().getDynamic_cover().getUrl_list().get(0);dyDto.setVideoPic(videoPic);/*** 7、视频文案*/desc = dyResult.getItem_list().get(0).getDesc();dyDto.setDesc(desc);}} catch (Exception e) {log.error("去水印异常 {}", e);}return JSON.toJSONString(dyDto);}
}

前端实现也比较简单,拿到后端解析出来的视频URL 预览播放、下载就OK了。

在这里插入图片描述

为快速实现我用了老古董JQuery,我这个年纪的人对它感情还是很深厚的,UI 框架用的 layer.js。源码后边会分享给大家,就不全贴出来了。

$.ajax({url: '/parseVideoUrl',type: 'POST',data: {"url": link},success: function (data) {$('.qsy-submit').attr('disabled', false);try {var rows = JSON.parse(data);layer.close(index);layer.open({type: 1,title: false,closeBtn: 1,shadeClose: true,skin: 'yourclass',content: `<div style="overflow:hidden;height: 580px;width: 350px;"><div><div class="popButton"><a href="###" rel="noopener nofollow noreferrer" onclick="downloadVideo('${rows['videoUrl']}','${rows['desc']}')"><button class="layui-bg-red layui-btn-sm layui-btn">下载视频</button></a></div><div class="popButton"><textarea id="videourl" cols="1" rows="1" style="height:0;width:0;position: absolute;">${rows['videoUrl']}</textarea><button class="layui-btn-sm layui-bg-blue layui-btn" onclick="copy('videourl')">复制链接</button></div><div class="popButton"><a href="###" rel="noopener nofollow noreferrer" onclick="downloadVideo('${rows['musicUrl']}','${rows['desc']}')"><button class="layui-btn-sm layui-btn">下载音频</button></a></div><video id="video" width="360px" height="500px" src="${rows['videoUrl']}" controls = "true" poster="${rows['videoPic']}" preload="auto" webkit-playsinline="true" playsinline="true" x-webkit-airplay="allow" x5-video-player-type="h5" x5-video-player-fullscreen="true" x5-video-orientation="portraint" style="object-fit:fill"><source src="${rows['videoUrl']}" type="video/mp4"> </video></div></div>`//content: `<video id="video" src="${rows['videoUrl']}" controls = "true" poster="${rows['videoPic']}" preload="auto" webkit-playsinline="true" playsinline="true" x-webkit-airplay="allow" x5-video-player-type="h5" x5-video-player-fullscreen="true" x5-video-orientation="portraint" style="object-fit:fill"><source src="${rows['videoUrl']}" type="video/mp4"> </video>`});} catch (error) {layer.alert('错误信息:' + error, {title: '异常',skin: 'layui-layer-lan',closeBtn: 0,anim: 4 //动画类型});return false;}},error: function (err) {console.log(err);layer.close(index);$('.qsy-submit').attr('disabled', false);},done: function () {layer.close(index);}
})
})

注意:我们在自己的网站中引用其它网站的资源URL,由于不在同一个域名下referrer 不同,通常会遇到三方网站的防盗链拦截,所以要想正常访问三方资源,必须要隐藏请求的referrer,页面中设置如下参数。

 <!-- 解决访问视频url 请求403异常 --><meta name="referrer" content="no-referrer"/>

还简单做了下移动端适配,样式看着还可以,但是功能使用起来有点差强人意,后边在做优化了。

在这里插入图片描述

总结

很多东西就是这样,没认真研究之前总感觉深不可测,可一旦接触到技术的本质,又开始笑自己之前好蠢,懂与不懂有时就查那么一层窗户纸。

好了今天就到这,本文源码在 公众号回复【抖音水印】自取


如果对你有用,欢迎 在看、点赞、转发 ,您的认可是我最大的动力。


往期推荐

JDK15正式发布,新增功能预览!


URL 去重的 6 种方案!(附详细代码)


图解|查找数组中最大值的5种方法!


关注下方二维码,收获更多干货!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/545835.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

css 隐藏元素 显示元素_使用CSS打印时如何隐藏元素?

css 隐藏元素 显示元素Introduction: 介绍&#xff1a; We have come across many challenges while developing a website or web page and every challenge comes with new learnings. It is a trait of a good developer who develops or creates websites or web pages by…

C语言图形库——EasyX常用函数

上节讲到贴出一张图片的过程&#xff0c;本节接着介绍一些基本的图形库函数。 头文件 #include <graphics.h>1、图形窗口背景颜色设置函数&#xff1a; 例 setbkcolor(YELLOW)&#xff1b;//将图形窗口背景颜色设置为黄色。也可用 setbkcolor(RGB(180,24,137));利用三…

Adobe_Audition消除人声

傻瓜式: 方法一&#xff1a;效果 - 立体声声像 - 声道重混缩 - 选择Vocal Cut 新建左声道(左100;右-100) 新建右声道(左-100;右100;反相) 方法二&#xff1a;效果 - 立体声声像 - 析取中置通道 - Karaoke(效果预置) - 男声. (测试后&#xff0c;在傻瓜式中这种方法效果最好) 方…

Java新特性:数据类型可以扔掉了?

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;在很久很久以前&#xff0c;我们写代码时要慎重的考虑变量的数据类型&#xff0c;比如下面这些&#xff1a;枚举&#xff1a…

Spyder:Python中机器学习的强大武器

So, first of all, you would need to install Anaconda distribution which can be downloaded from the link https://www.anaconda.com/download/ (for Windows users only). 因此&#xff0c;首先&#xff0c;您需要安装Anaconda发行版 &#xff0c;可以从链接https://www.…

C语言+数据结构总结

一、C语言部分 1、数据类型&#xff1a; Int 整形 4字节Short 短整形 2字节Long 长整形 4字节Float 单精度浮点型 4字节Double 双精度浮点型 8字节Char 字符型 1字节2、变量 …

对内存重叠的深入认识

内存重叠&#xff1a;拷贝的目的地址在源地址范围内。所谓内存重叠就是拷贝的目的地址和源地址有重叠。在函数strcpy和函数memcpy都没有对内存重叠做处理的&#xff0c;使用这两个函数的时候只有程序员自己保证源地址和目标地址不重叠&#xff0c;或者使用memmove函数进行内存拷…

Android特效 五种Toast具体解释

Toast是Android中用来显示显示信息的一种机制&#xff0c;和Dialog不一样的是&#xff0c;Toast是没有焦点的&#xff0c;并且Toast显示的时间有限&#xff0c;过一定的时间就会自己主动消失。 1.默认效果: 代码:Toast.makeText(getApplicationContext(), "默认Toast样式&…

为什么阿里巴巴禁止使用BigDecimal的equals方法做等值比较?

△一个对Coding有着独特追求的人△作者 l Hollis来源 l Hollis&#xff08;ID&#xff1a;hollischuang&#xff09;BigDecimal&#xff0c;相信对于很多人来说都不陌生&#xff0c;很多人都知道他的用法&#xff0c;这是一种java.math包中提供的一种可以用来进行精确运算的类型…

日期getUTCMonth()方法以及JavaScript中的示例

JavaScript Date getUTCMonth()方法 (JavaScript Date getUTCMonth() method) getUTCMonth() method is a Dates class method and it is used to get the current month’s value according to the UTC (Universal time coordinated) between the range of 0 to 11, where 0 f…

VB另类技巧(可以用于VBA)-指针的使用

大家都知道C中可以使用指针&#xff0c;但现在VB&#xff08;当然也有VBA&#xff09;也可以使用指针了&#xff0c;这是我在网上看的一篇文章&#xff0c;大家参考一下。想当年东方不败&#xff0c;黑木崖密室一战&#xff0c;仅凭一根绣花针独战四大高手&#xff0c;神出鬼没…

动图演示:手撸堆栈的两种实现方法!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;正式开始之前&#xff0c;先和各位朋友聊聊公众号后期的一些打算&#xff0c;后面的文章计划写一些关于数据结构和算法的内容…

Java中的异常处理

1 class YiChang{2 public static void main(String[] args){3 A anew A();4 a.show();5 }6 }7 8 class A{9 int[] i{1,2,3}; 10 public void show(){ 11 System.out.println(i[3]); 12 } 13 } 运行上面代码&#xff0c;会抛出这样…

数组copyWithin()方法以及JavaScript中的示例

JavaScript copyWithin()方法 (JavaScript copyWithin() method) copyWithin() method is used to copy the specified elements from an array and replace from specified index within the same array. It changes the this array (actual array). copyWithin()方法用于从数…

多图带你彻底理解Java中的21种锁!

作者 | 悟空聊架构来源 | 悟空聊架构&#xff08;ID&#xff1a;PassJava666&#xff09;本篇主要内容如下&#xff1a;本篇主要内容本篇文章已收纳到我的Java在线文档、 Github我的SpringCloud实战项目持续更新中帮你总结好的锁&#xff1a;序号锁名称应用1乐观锁CAS2悲观锁sy…

杨辉三角——数组解决

杨辉三角如图下所示&#xff0c;每一行的第一个数和最后一个数都为1&#xff0c;每一行中间的数&#xff08;出去第一个和最后一个&#xff09;a等于上一行与其相同列数的数b与数b前面的数之和。例&#xff1a; 第3行第2列的数是3&#xff0c;它就等于第2行第2列的数&#xff…

作文议论文开头结尾

一、 开头篇 1. Nowadays, it is commonly/widely/generally believed that…., but I wonder… 如今&#xff0c;人们普遍认为……&#xff0c;但是我怀疑…… 2. With the rapid growth of… … have/has become increasingly important in our daily life. 随着……的快速…

VS生成的exe文件如何在其他电脑上运行

在VS编译器上编写的程序都会生成一个exe文件&#xff0c;有时候写了一个很装逼的程序想在别人电脑炫耀一下&#xff0c;奈何将这个exe文件拷贝过去并不能运行&#xff0c;直接宣告装逼失败。为此将介绍一下如何将生成的exe文件在其他电脑上运行&#xff0c;步骤如下&#xff1a…

dumpstack_Java Thread类的静态void dumpStack()方法(带示例)

dumpstack线程类静态void dumpStack() (Thread Class static void dumpStack()) This method is available in package java.lang.Thread.dumpStack(). 软件包java.lang.Thread.dumpStack()中提供了此方法。 This method is used to print or display stack tracing of the cur…

netty websocket 简单消息推送demo

2019独角兽企业重金招聘Python工程师标准>>> 今天心情很不好&#xff01;&#xff01;&#xff01; 原因保密。 这篇是基于"netty与websocket通信demo"。 错误想法&#xff1a;大量客户请求&#xff0c;共用一个worker&#xff0c;来实现推送。 正确作法&…