zoukankan      html  css  js  c++  java
  • 手写一个抖音视频去水印工具,千万别刚一个程序员

    百因必有果

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

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

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

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

    在这里插入图片描述

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

    先看下我做的去水印工具线上预览效果: 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")
        @ResponseBody
        public 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; 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;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"/>
    

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

    在这里插入图片描述

    总结

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

    好了今天就到这,本文源码在 本人公众号【程序员内点事】回复【源码】自取


    整理了几百本各类技术电子书,送给小伙伴们。关注公号回复【666】自行领取。和一些小伙伴们建了一个技术交流群,一起探讨技术、分享技术资料,旨在共同学习进步,如果感兴趣就加入我们吧!

    在这里插入图片描述

  • 相关阅读:
    C#判断网络链接状态
    C# 创建临时文件(转帖)
    C# 很久以前几个常用类
    正则附表
    如何判断WebBrowser浏览器网页加载完成
    控件阴影
    C# 使用WM_COPYDATA传输数据(两个窗体间通信)
    C# 调用POST请求
    改变无边框窗体的尺寸大小和移动无边框窗体
    IT学习网站
  • 原文地址:https://www.cnblogs.com/chengxy-nds/p/13683611.html
Copyright © 2011-2022 走看看