java基礎(chǔ)欄目介紹去水印工具的方法。
相關(guān)學(xué)習(xí)推薦:java基礎(chǔ)
百因必有果
說(shuō)一下我為什么要做個(gè)抖音視頻去水印工具,其實(shí)是因?yàn)槲业纳车衽?,她居然剛我~
有天晚上她在抖音看見(jiàn)一個(gè)非常具有 教育意義
的視頻,“男人疼媳婦就該承包全部家務(wù)活”,然后它就想把視頻下載下來(lái),分享到她的姐妹群交流 馭夫
心得。
可是大家都知道抖音下載的視頻是帶水印,作為一個(gè)重度強(qiáng)迫癥選手這是不被允許的,沒(méi)辦法那就找找有沒(méi)有去水印工具吧,找了一圈要不就是收費(fèi),要么下載不下來(lái),主上臉上的笑容也在逐漸消失。
我在邊上調(diào)侃了一句:也沒(méi)多難,要不我給你做一個(gè)!“你行嗎?” 然后投來(lái)了一個(gè)不屑的眼神。
哎呀!本來(lái)就開(kāi)個(gè)玩笑,居然說(shuō)我不行,這就不能忍了,我得證明給你看看!男人嘛,就受不了這話(huà)
先看下我做的去水印工具線上預(yù)覽效果: 47.93.6.5:8888/index
下邊和大家一起分析下做這個(gè)去水印工具的思路,很多人乍一聽(tīng) 去水印
,下意識(shí)的覺(jué)得是一種什么牛比的算法,其實(shí)這是一種假象~
刨根問(wèn)底
雖說(shuō)要爭(zhēng)口氣,可剛開(kāi)始做的時(shí)候我也真是一臉懵逼,因?yàn)楦静恢涝搹哪娜胧?,去水印什么原理???難不成我還要寫(xiě)個(gè)算法?
找了一個(gè)抖音視頻的分享鏈接,一點(diǎn)點(diǎn)分析,不難發(fā)現(xiàn)這是個(gè)經(jīng)過(guò)處理的短鏈接,那這個(gè)短鏈接一定會(huì)重定向到真實(shí)的視頻地址 URL
。
https://v.douyin.com/JSkuhE4/
瀏覽器中輸入短鏈接得到了下邊這個(gè) URL
,以我的經(jīng)驗(yàn)判斷URL
中的 6820792802394262795
很有可能是視頻的唯一ID,而唯一ID通常用來(lái)作為獲取詳情接口的入?yún)?,哎嘿~ 好像有點(diǎn)頭緒了。
https://www.iesdouyin.com/share/video/6820792802394262795/
趕緊祭出 F12
大法打開(kāi)控制臺(tái),在眾多請(qǐng)求中發(fā)現(xiàn)這么一個(gè)接口,它居然用到了上邊的唯一ID。
https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=6820792802394262795
更驚喜的是接口返回的數(shù)據(jù)那叫一個(gè)詳細(xì),作者信息、音頻地址、視頻地址、平面圖都有。但唯獨(dú)沒(méi)有無(wú)水印的視頻 URL
。
只找到一個(gè)有水印的視頻 URL
,有點(diǎn)小失落,我又看了看這個(gè)地址,發(fā)現(xiàn) wm
和我項(xiàng)目名有點(diǎn)像啊,不就是watermark
水印的縮寫(xiě)嗎?
https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f030000bqk54kg2saj3lso3oh20&ratio=720p&line=0
好像又看到了一絲希望,我趕緊修改URL
在瀏覽器中又試了一下,果然真的沒(méi)水印了。
https://aweme.snssdk.com/aweme/v1/play/?video_id=v0200f030000bqk54kg2saj3lso3oh20&ratio=720p&line=0
到這才發(fā)現(xiàn)抖音去水印
簡(jiǎn)單的讓人感動(dòng),哈哈哈~
身體力行
既然原理都清晰了,剩下的就是一步一步實(shí)現(xiàn)功能了,原理看著挺簡(jiǎn)單的,但實(shí)現(xiàn)中還是遇到一點(diǎn)點(diǎn)小坑,浪費(fèi)了不少時(shí)間。
實(shí)現(xiàn)過(guò)程只有簡(jiǎn)單的三步:
- 1、從輸入框中過(guò)濾取出視頻短連接
- 2、短連接傳到后端解析出無(wú)水印的視頻
URL
- 3、視頻
URL
傳遞給前端預(yù)覽、下載
后端并沒(méi)有什么難度,一步一步按照上邊分析的流程解析真實(shí)視頻 URL
就可以了。
注意 :我們想得到的地址
URL
,都是當(dāng)前短連接URL
經(jīng)過(guò)重定向后的URL
。而抖音有些鏈接是不支持瀏覽器訪問(wèn)的,所以要手動(dòng)修改User-agent
屬性模擬移動(dòng)端訪問(wèn)才可以。
/** * @param url * @author xiaofu * @description 獲取當(dāng)前鏈接重定向后的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");//模擬手機(jī)連接 conn.connect(); String location = conn.getHeaderField("Location"); return location; } catch (Exception e) { e.printStackTrace(); } return ""; }
下邊是完整的后端實(shí)現(xiàn),可以看到代碼量非常的少。
/** * @author xiaofu-公眾號(hào):程序員內(nèi)點(diǎn)事 * @description 抖音無(wú)水印視頻下載 * @date 2020/9/15 18:44 */@Slf4j @Controllerpublic 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 解析抖音無(wú)水印視頻 * @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、拿到視頻對(duì)應(yīng)的 ItemId */ String videoUrl = ""; String musicUrl = ""; String videoPic = ""; String desc = ""; if (!StringUtils.isEmpty(redirectUrl)) { /** * 3、用 ItemId 拿視頻的詳細(xì)信息,包括無(wú)水印視頻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、無(wú)水印視頻 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); }}
前端實(shí)現(xiàn)也比較簡(jiǎn)單,拿到后端解析出來(lái)的視頻URL
預(yù)覽播放、下載就OK了。
為快速實(shí)現(xiàn)我用了老古董JQuery
,我這個(gè)年紀(jì)的人對(duì)它感情還是很深厚的,UI
框架用的 layer.js
。源碼后邊會(huì)分享給大家,就不全貼出來(lái)了。
$.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: `<p style="overflow:hidden;height: 580px;width: 350px;"><p><p 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></p><p 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')">復(fù)制鏈接</button></p><p class="popButton"><a href="###" rel="noopener nofollow noreferrer" onclick="downloadVideo('${rows['musicUrl']}','${rows['desc']}')"><button class="layui-btn-sm layui-btn">下載音頻</button></a></p><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></p></p>` //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('錯(cuò)誤信息:' + error, { title: '異常', skin: 'layui-layer-lan', closeBtn: 0, anim: 4 //動(dòng)畫(huà)類(lèi)型 }); return false; } }, error: function (err) { console.log(err); layer.close(index); $('.qsy-submit').attr('disabled', false); }, done: function () { layer.close(index); }})})
注意:我們?cè)谧约旱木W(wǎng)站中引用其它網(wǎng)站的資源
URL
,由于不在同一個(gè)域名下referrer
不同,通常會(huì)遇到三方網(wǎng)站的防盜鏈攔截,所以要想正常訪問(wèn)三方資源,必須要隱藏請(qǐng)求的referrer
,頁(yè)面中設(shè)置如下參數(shù)。
<!-- 解決訪問(wèn)視頻url 請(qǐng)求403異常 --> <meta name="referrer" content="no-referrer"/>
還簡(jiǎn)單做了下移動(dòng)端適配,樣式看著還可以,但是功能使用起來(lái)有點(diǎn)差強(qiáng)人意,后邊在做優(yōu)化了。
總結(jié)
很多東西就是這樣,沒(méi)認(rèn)真研究之前總感覺(jué)深不可測(cè),可一旦接觸到技術(shù)的本質(zhì),又開(kāi)始笑自己之前好蠢,懂與不懂有時(shí)就查那么一層窗戶(hù)紙。