admin commited on
Commit
0d6128e
·
1 Parent(s): 365ffa3
app.py CHANGED
@@ -1,17 +1,30 @@
1
  import gradio as gr
2
- from tiktok import tiktok_parser
3
- from bili import bili_parser
4
- from bvid2acid import bv2acid
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  if __name__ == "__main__":
7
  with gr.Blocks() as demo:
8
- gr.Markdown(
9
- "This site does not provide any video storage services, only to provide the most basic resolution services, please DO NOT abuse"
10
- )
11
- with gr.Tab("Tiktok"):
12
  tiktok_parser()
13
 
14
- with gr.Tab("Bilibili"):
15
  with gr.Column():
16
  bv2acid()
17
  bili_parser()
 
1
  import gradio as gr
2
+ from modules.tiktok import tiktok_parser
3
+ from modules.bili import bili_parser
4
+ from modules.bvid2acid import bv2acid
5
+ from utils import LANG
6
+
7
+ ZH2EN = {
8
+ "本站不提供任何视频存储服务,仅提供最基本的解析服务,请勿滥用": "This site does not provide any audio storage services, only provide the most basic parsing services, please DO NOT abuse",
9
+ "抖音": "Tiktok",
10
+ "B站": "Bilibili",
11
+ }
12
+
13
+
14
+ def _L(zh_txt: str):
15
+ if LANG:
16
+ return ZH2EN[zh_txt]
17
+ else:
18
+ return zh_txt
19
+
20
 
21
  if __name__ == "__main__":
22
  with gr.Blocks() as demo:
23
+ gr.Markdown(_L("本站不提供任何视频存储服务,仅提供最基本的解析服务,请勿滥用"))
24
+ with gr.Tab(_L("抖音")):
 
 
25
  tiktok_parser()
26
 
27
+ with gr.Tab(_L("B站")):
28
  with gr.Column():
29
  bv2acid()
30
  bili_parser()
config.py DELETED
@@ -1,11 +0,0 @@
1
- import os
2
-
3
- TIMEOUT = None
4
- TMP_DIR = "./__pycache__"
5
-
6
- API_BILI = os.getenv("api_bili")
7
- API_TIKTOK = os.getenv("api_tiktok")
8
-
9
- HEADER = {
10
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36"
11
- }
 
 
 
 
 
 
 
 
 
 
 
 
bili.py → modules/bili.py RENAMED
@@ -1,31 +1,28 @@
1
  import re
2
  import requests
3
  import gradio as gr
4
- from tqdm import tqdm
5
- from utils import timestamp, clean_dir
6
- from config import TMP_DIR, HEADER, TIMEOUT, API_BILI
7
-
8
-
9
- def download_file(url, video_id, cache_dir=f"{TMP_DIR}/bili"):
10
- clean_dir(cache_dir)
11
- local_file = f"{cache_dir}/{video_id}.mp4"
12
- response = requests.get(url, stream=True)
13
- if response.status_code == 200:
14
- total_size = int(response.headers.get("Content-Length", 0)) + 1
15
- time_stamp = timestamp()
16
- progress_bar = tqdm(
17
- total=total_size,
18
- unit="B",
19
- unit_scale=True,
20
- desc=f"[{time_stamp}] {local_file}",
21
- )
22
- with open(local_file, "wb") as f:
23
- for chunk in response.iter_content(chunk_size=8192):
24
- if chunk:
25
- f.write(chunk)
26
- progress_bar.update(len(chunk))
27
-
28
- return local_file
29
 
30
 
31
  def extract_fst_url(text):
@@ -51,42 +48,80 @@ def get_real_url(short_url):
51
  ).url.split("/?")[0]
52
 
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  # outer func
55
  def infer(video_url: str, p: int):
 
56
  title = cover = desc = dur = video = author = avatar = None
57
- if not video_url:
58
- title = "Empty video link!"
59
- return title, cover, video, desc, dur, avatar, author
60
-
61
  try:
 
 
 
62
  video_url = extract_fst_url(video_url)
63
  if "b23.tv" in video_url:
64
  video_url = get_real_url(video_url)
65
 
66
- response = requests.get(API_BILI, params={"url": video_url}, timeout=TIMEOUT)
 
 
 
 
 
 
 
67
  response_json = response.json()
68
  retcode = response_json["code"]
69
- if retcode == 1:
70
  title = response_json["title"]
71
  cover = response_json["imgurl"]
72
  desc = response_json["desc"]
73
 
74
  response_data = response_json["data"][int(p) - 1]
75
  dur = response_data["duration"]
76
- video_id = video_url.split("/")[-1]
77
- video = download_file(response_data["video_url"], video_id)
78
 
79
  author_data = response_json["user"]
80
  author = author_data["name"]
81
  avatar = author_data["user_img"]
82
 
83
  else:
84
- raise ConnectionError(f"Failed to call API, error code: {retcode}")
85
 
86
  except Exception as e:
87
- title = f"Failed to parse video: {e}"
88
 
89
- return title, cover, video, desc, dur, avatar, author
90
 
91
 
92
  def bili_parser():
@@ -94,30 +129,36 @@ def bili_parser():
94
  fn=infer,
95
  inputs=[
96
  gr.Textbox(
97
- label="Please input Bilibili video link",
98
  placeholder="https://www.bilibili.com/video/*",
99
  ),
100
- gr.Slider(label="Part", minimum=1, maximum=1000, step=1, value=1),
101
  ],
102
  outputs=[
103
- gr.Textbox(label="Video title", show_copy_button=True),
104
- gr.Image(label="Video cover", show_share_button=False),
 
105
  gr.Video(
106
- label="Download video",
107
  show_download_button=True,
108
  show_share_button=False,
 
109
  ),
110
- gr.TextArea(label="Video introduction", show_copy_button=True),
111
- gr.Textbox(label="Video duration(s)", show_copy_button=True),
112
- gr.Image(label="Uploader avatar", show_share_button=False),
113
- gr.Textbox(label="Uploader nickname", show_copy_button=True),
114
  ],
115
- title="Bilibili video parser",
116
  flagging_mode="never",
117
  examples=[
118
- ["BV1G8iRYBE4f", 1],
119
- ["https://b23.tv/LSoJzpW", 1],
120
- ["https://www.bilibili.com/video/BV1G8iRYBE4f", 1],
 
 
 
 
121
  ],
122
  cache_examples=False,
123
  )
 
1
  import re
2
  import requests
3
  import gradio as gr
4
+ from utils import download_file, HEADER, TIMEOUT, API_BILI_2, API_BILI_1, LANG, TMP_DIR
5
+
6
+ ZH2EN = {
7
+ "状态栏": "Status",
8
+ "请输入B站视频链接": "Please input Bilibili video link",
9
+ "分P": "Part",
10
+ "视频标题": "Video title",
11
+ "视频封面": "Video cover",
12
+ "视频下载": "Download video",
13
+ "视频简介": "Video introduction",
14
+ "视频时长(s)": "Video duration(s)",
15
+ "UP主头像": "Uploader avatar",
16
+ "UP主昵称": "Uploader nickname",
17
+ "B站视频解析": "Bilibili video parser",
18
+ }
19
+
20
+
21
+ def _L(zh_txt: str):
22
+ if LANG:
23
+ return ZH2EN[zh_txt]
24
+ else:
25
+ return zh_txt
 
 
 
26
 
27
 
28
  def extract_fst_url(text):
 
48
  ).url.split("/?")[0]
49
 
50
 
51
+ def get_video(video_url: str, p: int, cache=f"{TMP_DIR}/bili"):
52
+ bvid = video_url.split("bilibili.com/video/")[-1]
53
+ response = requests.get(
54
+ API_BILI_2,
55
+ params={
56
+ "bv": bvid,
57
+ "p": p,
58
+ "otype": "json",
59
+ },
60
+ timeout=TIMEOUT,
61
+ )
62
+ response_json = response.json()
63
+ retcode = response_json["code"]
64
+ if retcode == 0:
65
+ qualities: list = response_json["accept_quality"]
66
+ quality = min(qualities[0], 80)
67
+ url = requests.get(
68
+ API_BILI_2,
69
+ params={
70
+ "bv": bvid,
71
+ "p": p,
72
+ "q": quality,
73
+ "otype": "url",
74
+ },
75
+ timeout=TIMEOUT,
76
+ ).text
77
+ return download_file(url, bvid, cache)
78
+
79
+ else:
80
+ raise ConnectionError(f"Failed to get video: {retcode}")
81
+
82
+
83
  # outer func
84
  def infer(video_url: str, p: int):
85
+ status = "Success"
86
  title = cover = desc = dur = video = author = avatar = None
 
 
 
 
87
  try:
88
+ if not video_url:
89
+ raise ValueError("视频链接为空!")
90
+
91
  video_url = extract_fst_url(video_url)
92
  if "b23.tv" in video_url:
93
  video_url = get_real_url(video_url)
94
 
95
+ response = requests.get(
96
+ API_BILI_1,
97
+ params={
98
+ "url": video_url,
99
+ "type": "json",
100
+ },
101
+ timeout=TIMEOUT,
102
+ )
103
  response_json = response.json()
104
  retcode = response_json["code"]
105
+ if retcode == 200:
106
  title = response_json["title"]
107
  cover = response_json["imgurl"]
108
  desc = response_json["desc"]
109
 
110
  response_data = response_json["data"][int(p) - 1]
111
  dur = response_data["duration"]
112
+ video = get_video(video_url, p)
 
113
 
114
  author_data = response_json["user"]
115
  author = author_data["name"]
116
  avatar = author_data["user_img"]
117
 
118
  else:
119
+ raise ConnectionError(f"接口调用失败, 错误码: {retcode}")
120
 
121
  except Exception as e:
122
+ status = f"视频解析失败: {e}"
123
 
124
+ return status, title, cover, video, desc, dur, avatar, author
125
 
126
 
127
  def bili_parser():
 
129
  fn=infer,
130
  inputs=[
131
  gr.Textbox(
132
+ label=_L("请输入B站视频链接"),
133
  placeholder="https://www.bilibili.com/video/*",
134
  ),
135
+ gr.Slider(label=_L("分P"), minimum=1, maximum=1000, step=1, value=1),
136
  ],
137
  outputs=[
138
+ gr.Textbox(label=_L("状态栏"), show_copy_button=True),
139
+ gr.Textbox(label=_L("视频标题"), show_copy_button=True),
140
+ gr.Image(label=_L("视频封面"), show_share_button=False),
141
  gr.Video(
142
+ label=_L("视频下载"),
143
  show_download_button=True,
144
  show_share_button=False,
145
+ format="mp4",
146
  ),
147
+ gr.TextArea(label=_L("视频简介"), show_copy_button=True),
148
+ gr.Textbox(label=_L("视频时长(s)"), show_copy_button=True),
149
+ gr.Image(label=_L("UP主头像"), show_share_button=False),
150
+ gr.Textbox(label=_L("UP主昵称"), show_copy_button=True),
151
  ],
152
+ title=_L("B站视频解析"),
153
  flagging_mode="never",
154
  examples=[
155
+ ["BV1Dt4y1o7bU", 1],
156
+ ["https://b23.tv/LuTAbzj", 1],
157
+ ["https://www.bilibili.com/video/BV1Dt4y1o7bU", 1],
158
+ [
159
+ "【『新年おめでとう | 2024』你就如同烟花一般,在我心中绽放!-哔哩哔哩】 https://b23.tv/LuTAbzj",
160
+ 1,
161
+ ],
162
  ],
163
  cache_examples=False,
164
  )
bvid2acid.py → modules/bvid2acid.py RENAMED
@@ -1,32 +1,48 @@
1
  import requests
2
  import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
 
5
- # outer func
6
  def infer(bvid: str):
 
 
7
  try:
8
  response = requests.get(
9
  "https://api.bilibili.com/x/web-interface/view",
10
  params={"bvid": bvid},
11
- headers={
12
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0"
13
- },
14
  )
15
  data = response.json()["data"]
16
- return data["aid"], data["cid"]
 
17
 
18
  except Exception as e:
19
- return "Failed to parse aid / cid", f"{e}"
 
 
20
 
21
 
22
  def bv2acid():
23
  return gr.Interface(
24
  fn=infer,
25
- inputs=gr.Textbox(label="bvid", show_copy_button=True),
26
  outputs=[
 
27
  gr.Textbox(label="aid", show_copy_button=True),
28
  gr.Textbox(label="cid", show_copy_button=True),
29
  ],
30
- title="Bvid to aid / cid",
31
  flagging_mode="never",
32
  )
 
1
  import requests
2
  import gradio as gr
3
+ from utils import LANG, HEADER
4
+
5
+ ZH2EN = {
6
+ "状态栏": "Status",
7
+ "将 Bvid 转为 aid 或 cid": "Bvid to aid / cid",
8
+ }
9
+
10
+
11
+ def _L(zh_txt: str):
12
+ if LANG:
13
+ return ZH2EN[zh_txt]
14
+ else:
15
+ return zh_txt
16
 
17
 
 
18
  def infer(bvid: str):
19
+ status = "Success"
20
+ aid = cid = None
21
  try:
22
  response = requests.get(
23
  "https://api.bilibili.com/x/web-interface/view",
24
  params={"bvid": bvid},
25
+ headers=HEADER,
 
 
26
  )
27
  data = response.json()["data"]
28
+ aid = data["aid"]
29
+ cid = data["cid"]
30
 
31
  except Exception as e:
32
+ status = f"{e}"
33
+
34
+ return status, aid, cid
35
 
36
 
37
  def bv2acid():
38
  return gr.Interface(
39
  fn=infer,
40
+ inputs=gr.Textbox(label="bvid"),
41
  outputs=[
42
+ gr.Textbox(label=_L("状态栏"), show_copy_button=True),
43
  gr.Textbox(label="aid", show_copy_button=True),
44
  gr.Textbox(label="cid", show_copy_button=True),
45
  ],
46
+ title=_L("Bvid 转为 aid cid"),
47
  flagging_mode="never",
48
  )
tiktok.py → modules/tiktok.py RENAMED
@@ -1,31 +1,26 @@
1
  import re
2
  import requests
3
  import gradio as gr
4
- from tqdm import tqdm
5
- from utils import timestamp, clean_dir
6
- from config import API_TIKTOK, TIMEOUT, TMP_DIR
7
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
- def download_file(url, video_id, cache_dir=f"{TMP_DIR}/tiktok"):
10
- clean_dir(cache_dir)
11
- local_file = f"{cache_dir}/{video_id}.mp4"
12
- response = requests.get(url, stream=True)
13
- if response.status_code == 200:
14
- total_size = int(response.headers.get("Content-Length", 0)) + 1
15
- time_stamp = timestamp()
16
- progress_bar = tqdm(
17
- total=total_size,
18
- unit="B",
19
- unit_scale=True,
20
- desc=f"[{time_stamp}] {local_file}",
21
- )
22
- with open(local_file, "wb") as f:
23
- for chunk in response.iter_content(chunk_size=8192):
24
- if chunk:
25
- f.write(chunk)
26
- progress_bar.update(len(chunk))
27
 
28
- return local_file
 
 
 
 
29
 
30
 
31
  def extract_fst_url(text):
@@ -38,16 +33,16 @@ def extract_fst_url(text):
38
 
39
 
40
  # outer func
41
- def infer(video_url):
 
42
  video = parse_time = desc = avatar = author = sign = None
43
- if not video_url:
44
- desc = "The video sharing link is empty!"
45
- return video, desc, parse_time, avatar, author, sign
46
-
47
  try:
 
 
 
48
  video_url = extract_fst_url(video_url)
49
  if not video_url:
50
- raise ValueError("Please enter a valid video sharing link!")
51
 
52
  response = requests.get(API_TIKTOK, params={"url": video_url}, timeout=TIMEOUT)
53
  response_json = response.json()
@@ -55,7 +50,7 @@ def infer(video_url):
55
  if retcode == 200:
56
  response_data = response_json["data"]
57
  video_id = response_data["play_url"].split("video_id=")[1].split("&")[0]
58
- video = download_file(response_data["video_url"], video_id)
59
  parse_time = response_data["parse_time"]
60
 
61
  additional_data = response_data["additional_data"][0]
@@ -65,12 +60,12 @@ def infer(video_url):
65
  sign = additional_data["signature"]
66
 
67
  else:
68
- raise ConnectionError(f"Interface call failed, error code: HTTP {retcode}")
69
 
70
  except Exception as e:
71
- desc = f"Video parsing failed: {e}"
72
 
73
- return video, desc, parse_time, avatar, author, sign
74
 
75
 
76
  def tiktok_parser():
@@ -78,23 +73,24 @@ def tiktok_parser():
78
  fn=infer,
79
  inputs=[
80
  gr.Textbox(
81
- label="Please enter TikTok video sharing short link",
82
  placeholder="https://v.douyin.com/*",
83
  ),
84
  ],
85
  outputs=[
 
86
  gr.Video(
87
- label="Video download",
88
  show_download_button=True,
89
  show_share_button=False,
90
  ),
91
- gr.Textbox(label="Video description", show_copy_button=True),
92
- gr.Textbox(label="Parsing time", show_copy_button=True),
93
- gr.Image(label="Author avatar", show_share_button=False),
94
- gr.Textbox(label="Author nickname", show_copy_button=True),
95
- gr.TextArea(label="Author signature", show_copy_button=True),
96
  ],
97
- title="Parse TikTok video without watermark",
98
  flagging_mode="never",
99
  examples=[
100
  "https://v.douyin.com/8FVe5DzarE0",
 
1
  import re
2
  import requests
3
  import gradio as gr
4
+ from utils import download_file, API_TIKTOK, TIMEOUT, LANG, TMP_DIR
 
 
5
 
6
+ ZH2EN = {
7
+ "状态栏": "Status",
8
+ "请输入抖音视频分享短链接": "Please enter TikTok video sharing short link",
9
+ "视频下载": "Video download",
10
+ "视频描述": "Video description",
11
+ "解析耗时": "Parsing time",
12
+ "作者头像": "Author avatar",
13
+ "作者昵称": "Author nickname",
14
+ "作者签名": "Author signature",
15
+ "抖音无水印视频解析": "Parse TikTok video without watermark",
16
+ }
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
+ def _L(zh_txt: str):
20
+ if LANG:
21
+ return ZH2EN[zh_txt]
22
+ else:
23
+ return zh_txt
24
 
25
 
26
  def extract_fst_url(text):
 
33
 
34
 
35
  # outer func
36
+ def infer(video_url, cache=f"{TMP_DIR}/tiktok"):
37
+ status = "Success"
38
  video = parse_time = desc = avatar = author = sign = None
 
 
 
 
39
  try:
40
+ if not video_url:
41
+ raise ValueError("视频分享链接为空!")
42
+
43
  video_url = extract_fst_url(video_url)
44
  if not video_url:
45
+ raise ValueError("请输入有效的视频分享链接!")
46
 
47
  response = requests.get(API_TIKTOK, params={"url": video_url}, timeout=TIMEOUT)
48
  response_json = response.json()
 
50
  if retcode == 200:
51
  response_data = response_json["data"]
52
  video_id = response_data["play_url"].split("video_id=")[1].split("&")[0]
53
+ video = download_file(response_data["video_url"], video_id, cache)
54
  parse_time = response_data["parse_time"]
55
 
56
  additional_data = response_data["additional_data"][0]
 
60
  sign = additional_data["signature"]
61
 
62
  else:
63
+ raise ConnectionError(f"接口调用失败, 错误码: HTTP {retcode}")
64
 
65
  except Exception as e:
66
+ status = f"视频解析失败: {e}"
67
 
68
+ return status, video, desc, parse_time, avatar, author, sign
69
 
70
 
71
  def tiktok_parser():
 
73
  fn=infer,
74
  inputs=[
75
  gr.Textbox(
76
+ label=_L("请输入抖音视频分享短链接"),
77
  placeholder="https://v.douyin.com/*",
78
  ),
79
  ],
80
  outputs=[
81
+ gr.Textbox(label=_L("状态栏"), show_copy_button=True),
82
  gr.Video(
83
+ label=_L("视频下载"),
84
  show_download_button=True,
85
  show_share_button=False,
86
  ),
87
+ gr.Textbox(label=_L("视频描述"), show_copy_button=True),
88
+ gr.Textbox(label=_L("解析耗时"), show_copy_button=True),
89
+ gr.Image(label=_L("作者头像"), show_share_button=False),
90
+ gr.Textbox(label=_L("作者昵称"), show_copy_button=True),
91
+ gr.TextArea(label=_L("作者签名"), show_copy_button=True),
92
  ],
93
+ title=_L("抖音无水印视频解析"),
94
  flagging_mode="never",
95
  examples=[
96
  "https://v.douyin.com/8FVe5DzarE0",
utils.py CHANGED
@@ -1,17 +1,30 @@
1
  import os
2
  import shutil
 
 
3
  from datetime import datetime
4
- from zoneinfo import ZoneInfo
5
- from tzlocal import get_localzone
6
 
7
 
8
- def timestamp(naive_time: datetime = None, target_tz=ZoneInfo("Asia/Shanghai")):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  if not naive_time:
10
  naive_time = datetime.now()
11
 
12
- local_tz = get_localzone()
13
- aware_local = naive_time.replace(tzinfo=local_tz)
14
- return aware_local.astimezone(target_tz).strftime("%Y-%m-%d %H:%M:%S")
15
 
16
 
17
  def mk_dir(dirpath: str):
@@ -27,3 +40,28 @@ def rm_dir(dirpath: str):
27
  def clean_dir(dirpath: str):
28
  rm_dir(dirpath)
29
  os.makedirs(dirpath)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import shutil
3
+ import requests
4
+ from tqdm import tqdm
5
  from datetime import datetime
 
 
6
 
7
 
8
+ LANG = os.getenv("language")
9
+ API_TIKTOK = os.getenv("api_tiktok")
10
+ API_BILI_1 = os.getenv("api_bili_1")
11
+ API_BILI_2 = os.getenv("api_bili_2")
12
+ if not (API_TIKTOK and API_BILI_1 and API_BILI_2):
13
+ print("请检查环境变量")
14
+ exit()
15
+
16
+ TIMEOUT = None
17
+ TMP_DIR = "./__pycache__"
18
+ HEADER = {
19
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36"
20
+ }
21
+
22
+
23
+ def timestamp(naive_time: datetime = None):
24
  if not naive_time:
25
  naive_time = datetime.now()
26
 
27
+ return naive_time.strftime("%Y-%m-%d %H:%M:%S")
 
 
28
 
29
 
30
  def mk_dir(dirpath: str):
 
40
  def clean_dir(dirpath: str):
41
  rm_dir(dirpath)
42
  os.makedirs(dirpath)
43
+
44
+
45
+ def download_file(url, video_id, cache_dir: str):
46
+ clean_dir(cache_dir)
47
+ local_file = f"{cache_dir}/{video_id}.mp4"
48
+ response = requests.get(url, headers=HEADER, stream=True)
49
+ if response.status_code == 200:
50
+ total_size = int(response.headers.get("Content-Length", 0)) + 1
51
+ time_stamp = timestamp()
52
+ progress_bar = tqdm(
53
+ total=total_size,
54
+ unit="B",
55
+ unit_scale=True,
56
+ desc=f"[{time_stamp}] {local_file}",
57
+ )
58
+ with open(local_file, "wb") as f:
59
+ for chunk in response.iter_content(chunk_size=8192):
60
+ if chunk: # 确保 chunk 不为空
61
+ f.write(chunk) # 更新进度条
62
+ progress_bar.update(len(chunk))
63
+
64
+ else:
65
+ raise ConnectionError(f"HTTP: {response.status_code}")
66
+
67
+ return local_file