1
1
from urllib .parse import quote , urljoin , urlparse
2
- from flask import Flask , render_template , request , redirect , send_file
2
+ from flask import Flask , render_template , request , redirect , send_file , abort
3
3
from yt_dlp import YoutubeDL
4
4
from flask_cors import CORS
5
5
import json
31
31
32
32
tiktokArgs = {}
33
33
34
+ def getWebDataFromResponse (response ):
35
+ if response .status_code != 200 :
36
+ return None
37
+ # regex to find the json data: <script id="__UNIVERSAL_DATA_FOR_REHYDRATION__" type="application\/json">(.*)}<\/script>
38
+ rx = re .compile (r'<script id="__UNIVERSAL_DATA_FOR_REHYDRATION__" type="application\/json">(.*)}<\/script>' )
39
+ match = rx .search (response .text )
40
+ if match == None :
41
+ return None
42
+ data = match .group (1 ) + "}"
43
+ return json .loads (data )
44
+
45
+
34
46
def message (text ):
35
47
return render_template (
36
48
'message.html' ,
37
49
message = text ,
38
50
appname = config .currentConfig ["MAIN" ]["appName" ])
39
51
40
52
def findApiFormat (videoInfo ):
41
- for format in videoInfo ['formats' ]:
42
- if format ['format_id' ] == 'download_addr-0' :
43
- return format
44
- # not found, search for the next best one
45
- for format in videoInfo ['formats' ]:
46
- if format ['url' ].startswith ('http://api' ):
47
- return format
48
- # not found, return the first one
49
- return videoInfo ['formats' ][0 ]
53
+ vid = videoInfo ['video' ]
54
+ return {"width" : vid ['width' ], "height" : vid ['height' ], "url" : vid ["downloadAddr" ],"thumb" :vid ["cover" ]}
50
55
51
56
def stripURL (url ):
52
57
return urljoin (url , urlparse (url ).path )
53
58
54
- def getVideoFromPostURL (url ):
55
- with YoutubeDL (params = {"extractor_args" :{"tiktok" :tiktokArgs }}) as ydl :
56
- result = ydl .extract_info (url , download = False )
59
+ def getVideoFromPostURL (url ,includeCookies = False ):
60
+ rb = requests .get (url , headers = {
61
+ "User-Agent" : "Mozilla/5.0" ,
62
+ "Accept" :"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" ,
63
+ "Accept-Language" : "en-US,en;q=0.5" ,
64
+ "Sec-Fetch-Mode" : "navigate" ,
65
+ "Accept-Encoding" : "gzip, deflate, br"
66
+ })
67
+ videoInfo = getWebDataFromResponse (rb )
68
+
69
+ if "webapp.video-detail" not in videoInfo ["__DEFAULT_SCOPE__" ]:
70
+ return None
57
71
58
- if result ["formats" ][0 ]["url" ].endswith (".mp3" ) or (result ["formats" ][0 ]["width" ] == 0 and result ["formats" ][0 ]["height" ] == 0 ):
59
- # this is most likely a slideshow
60
- return getSlideshowFromPostURL (url )
61
- result ["slideshowData" ] = None
62
- return result
72
+ vdata = videoInfo ["__DEFAULT_SCOPE__" ]["webapp.video-detail" ]["itemInfo" ]["itemStruct" ]
73
+ if includeCookies :
74
+ vdata ["Cookies" ] = rb .cookies .get_dict ()
75
+ return vdata
76
+
77
+ def downloadVideoFromPostURL (url ):
78
+ videoInfo = getVideoFromPostURL (url ,includeCookies = True )
79
+ vFormat = findApiFormat (videoInfo )
80
+ cookies = videoInfo ["Cookies" ]
81
+
82
+ headers = {
83
+ "User-Agent" : "Mozilla/5.0" ,
84
+ "Accept" :"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" ,
85
+ "Accept-Language" : "en-US,en;q=0.5" ,
86
+ "Sec-Fetch-Mode" : "navigate" ,
87
+ "Accept-Encoding" : "gzip, deflate, br"
88
+ }
89
+ headers ["Cookie" ] = "; " .join ([f"{ k } ={ v } " for k ,v in cookies .items ()])
90
+
91
+
92
+ r = requests .get (vFormat ["url" ], headers = headers )
93
+ if r .status_code != 200 :
94
+ return None
95
+ return r .content
63
96
64
97
def getSlideshowFromPostURL (url ): # thsi function assumes the url is a slideshow
65
98
with YoutubeDL (params = {"dump_intermediate_pages" :True ,"extractor_args" :{"tiktok" :tiktokArgs }}) as ydl :
@@ -92,6 +125,10 @@ def getSlideshowFromPostURL(url): # thsi function assumes the url is a slideshow
92
125
return result
93
126
94
127
def build_stats_line (videoInfo ):
128
+ videoInfo ["view_count" ] = videoInfo ["stats" ]["playCount" ]
129
+ videoInfo ["like_count" ] = videoInfo ["stats" ]["diggCount" ]
130
+ videoInfo ["repost_count" ] = videoInfo ["stats" ]["shareCount" ]
131
+ videoInfo ["comment_count" ] = videoInfo ["stats" ]["commentCount" ]
95
132
if videoInfo ['view_count' ] > 0 or videoInfo ['like_count' ] > 0 or videoInfo ['repostCount' ] > 0 or videoInfo ['comment_count' ] > 0 :
96
133
text = ""
97
134
@@ -114,6 +151,8 @@ def getVideoDataFromCacheOrDl(post_link):
114
151
videoInfo = cachedItem
115
152
else :
116
153
videoInfo = getVideoFromPostURL (post_link )
154
+ if videoInfo == None :
155
+ return None
117
156
cache .addToCache (post_link , videoInfo )
118
157
return videoInfo
119
158
except Exception as e :
@@ -126,17 +165,26 @@ def embed_tiktok(post_link):
126
165
return message ("Failed to get video data from TikTok" )
127
166
if "slideshowData" not in videoInfo or videoInfo ["slideshowData" ] == None :
128
167
vFormat = findApiFormat (videoInfo )
129
- directURL = vFormat ['url' ]
168
+
169
+ directURL = f"https://" + config .currentConfig ["MAIN" ]["domainName" ]+ "/vid/" + videoInfo ["author" ]["uniqueId" ]+ "/" + videoInfo ["id" ]+ ".mp4"
130
170
else :
131
171
vFormat = {"width" : 1280 , "height" : 720 }
132
172
directURL = "https://" + config .currentConfig ["MAIN" ]["domainName" ]+ "/slideshow.mp4?url=" + post_link
133
173
statsLine = quote (build_stats_line (videoInfo ))
134
- return render_template ('video.html' , videoInfo = videoInfo , mp4URL = directURL , vFormat = vFormat , appname = config .currentConfig ["MAIN" ]["appName" ], statsLine = statsLine , domainName = config .currentConfig ["MAIN" ]["domainName" ])
174
+ return render_template ('video.html' , videoInfo = videoInfo , mp4URL = directURL , vFormat = vFormat , appname = config .currentConfig ["MAIN" ]["appName" ], statsLine = statsLine , domainName = config .currentConfig ["MAIN" ]["domainName" ], original_url = post_link )
135
175
136
176
@app .route ('/' )
137
177
def main ():
138
178
return redirect (config .currentConfig ["MAIN" ]["repoURL" ])
139
179
180
+ @app .route ('/vid/<author>/<vid>.mp4' )
181
+ def video (author , vid ):
182
+ post_link = f"https://www.tiktok.com/@{ author } /video/{ vid } "
183
+ videoData = downloadVideoFromPostURL (post_link )
184
+ if videoData == None :
185
+ abort (500 )
186
+ return send_file (io .BytesIO (videoData ), mimetype = 'video/mp4' )
187
+
140
188
@app .route ('/owoembed' )
141
189
def alternateJSON ():
142
190
return {
0 commit comments