#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# @Author: https://github.com/Evil0ctal/
# @Time: 2021/11/06
# @Update: 2022/01/31
# @Function:
"""
@Function:
This project uses PyWebIO, Requests, Flask as Python libraries
to download Douyin/TikTok's videos/gallery without watermark.
It can be used to download videos/gallery that the author has forbidden to download.
At the same time, it can be used with iOS shortcut APP
to cooperate with this project's API to realize internal download.
"""
from pywebio import config, session
from pywebio.input import *
from pywebio.output import *
from pywebio.platform.flask import webio_view
from retrying import retry
from werkzeug.urls import url_quote
from tiktok_downloader import info_post, tikmate
from flask import Flask, request, jsonify, make_response
import re
import json
import time
import requests
import unicodedata
app = Flask(__name__)
title = "Douyin/TikTok Downloader"
description = "Douyin/TikTok video/gallery analysis online"
headers = {
'user-agent': 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Mobile Safari/537.36 Edg/87.0.664.66'
}
def find_url(string):
# Parse the link in the Douyin share password and return to the list
url = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', string)
return url
def valid_check(kou_ling):
# Verify the content of the input
url_list = find_url(kou_ling)
# Verify each item in the content of the input
if url_list:
for i in url_list:
if 'douyin.com' in i[:31]:
if i == url_list[-1]:
return None
elif 'tiktok.com' in i[:31]:
if i == url_list[-1]:
return None
else:
return 'Please make sure that the input links are all valid Douyin/TikTok links!'
elif kou_ling == 'wyn':
return None
else:
return 'Douyin or TikTok share link is wrong!'
def clean_filename(string, author_name):
# Replace characters that cannot be used in file names
rstr = r"[\/\\\:\*\?\"\<\>\|]" # '/ \ : * ? " < > |'
new_title = re.sub(rstr, "_", string) # Replace with underscore
filename = 'douyin.wtf_TikTok_online_analysis' + new_title + '_' + author_name
return filename
def error_do(e, func_name):
# Output a useless message
put_html("
")
put_error("An unexpected error occurred. \nplease check whether the input value is valid!")
put_html('⚠Details
')
put_table([
['Function name', '原因'],
[func_name, str(e)]])
put_html("
")
put_markdown(
'A lot of analysis of TikTok may cause its firewall to limit current!\nPlease wait 1-2 minutes and try again!\nIf you still fail after multiple attempts, please click [issues](https://github.com/Evil0ctal/TikTokDownloader_PyWebIO/issues).\nYou can view the error log of this site in the About menu in the upper right corner.)')
put_link('return to home page', '/')
# 将错误记录在logs.txt中
date = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
with open('logs.txt', 'a') as f:
f.write(date + " " + func_name + ': ' + str(e) + '\n')
def loading(url_lists):
# Write a progress bar and pretend it :)
total_len = len(url_lists)
set_scope('bar', position=3)
with use_scope('bar'):
put_processbar('bar')
for i in range(1, 4):
set_processbar('bar', i / 3)
time.sleep(0.1)
@retry(stop_max_attempt_number=3)
def get_video_info(original_url):
# Use the official interface to parse the link information
try:
# Original video link
r = requests.get(url=original_url, allow_redirects=False)
try:
# 2021/12/11 It is found that Douyin has restricted it, and it will automatically redirect the URL. The video ID cannot be obtained by the previous method, but it can still be obtained from the request header.
long_url = r.headers['Location']
except:
# After the error is reported, it is judged as a long link, and the video id is directly intercepted
long_url = original_url
key = re.findall('video/(\d+)?', long_url)[0]
api_url = f'https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids={key}'
print("Sending request to: " + '\n' + api_url)
js = json.loads(requests.get(url=api_url, headers=headers).text)
# Determine whether it is an atlas
try:
image_data = js['item_list'][0]['images']
# Gallery background audio
image_music = str(js['item_list'][0]['music']['play_url']['url_list'][0])
# Gallery title
image_title = str(js['item_list'][0]['desc'])
# Atlas author's nickname
image_author = str(js['item_list'][0]['author']['nickname'])
# Atlas Author Tik Tok
image_author_id = str(js['item_list'][0]['author']['unique_id'])
if image_author_id == "":
# If the author has not modified the Douyin ID, this value should be used to avoid being unable to obtain its Douyin ID
image_author_id = str(js['item_list'][0]['author']['short_id'])
# Remove watermark atlas link
images_url = []
for data in image_data:
images_url.append(data['url_list'][0])
image_info = [images_url, image_music, image_title, image_author, image_author_id, original_url]
return image_info, 'image', api_url
# Determined as a video after reporting an error
except:
# After removing the watermark, the video link (the URL obtained by Douyin APi on January 1, 2022 will be redirected, and the direct link needs to be obtained in the Location)
video_url = str(js['item_list'][0]['video']['play_addr']['url_list'][0]).replace('playwm', 'play')
r = requests.get(url=video_url, headers=headers, allow_redirects=False)
video_url = r.headers['Location']
# Video background audio
video_music = str(js['item_list'][0]['music']['play_url']['url_list'][0])
# Video title
video_title = str(js['item_list'][0]['desc'])
# Video author nickname
video_author = str(js['item_list'][0]['author']['nickname'])
# Video author Douyin
video_author_id = str(js['item_list'][0]['author']['unique_id'])
if video_author_id == "":
# If the author has not modified the Douyin ID, this value should be used to avoid being unable to obtain its Douyin ID
video_author_id = str(js['item_list'][0]['author']['short_id'])
# Return a list containing data
video_info = [video_url, video_music, video_title, video_author, video_author_id, original_url]
return video_info, 'video', api_url
except Exception as e:
# Exception capture
error_do(e, 'get_video_info')
@retry(stop_max_attempt_number=3)
def get_video_info_tiktok(tiktok_url):
# Analyze TikTok video
try:
tiktok_headers = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"authority": "www.tiktok.com",
"Accept-Encoding": "gzip, deflate",
"Connection": "keep-alive",
"Host": "www.tiktok.com",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) coc_coc_browser/86.0.170 Chrome/80.0.3987.170 Safari/537.36",
}
html = requests.get(url=tiktok_url, headers=tiktok_headers)
res = re.search('