podflow 2025.1.26__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- Podflow/__init__.py +137 -0
- Podflow/basic/__init__.py +2 -0
- Podflow/basic/file_save.py +21 -0
- Podflow/basic/folder_build.py +16 -0
- Podflow/basic/get_duration.py +18 -0
- Podflow/basic/get_file_list.py +57 -0
- Podflow/basic/get_html_dict.py +30 -0
- Podflow/basic/http_client.py +75 -0
- Podflow/basic/list_merge_tidy.py +15 -0
- Podflow/basic/qr_code.py +55 -0
- Podflow/basic/split_dict.py +14 -0
- Podflow/basic/time_format.py +16 -0
- Podflow/basic/time_stamp.py +61 -0
- Podflow/basic/vary_replace.py +11 -0
- Podflow/basic/write_log.py +43 -0
- Podflow/bilibili/__init__.py +2 -0
- Podflow/bilibili/build.py +201 -0
- Podflow/bilibili/get.py +477 -0
- Podflow/bilibili/login.py +307 -0
- Podflow/config/__init__.py +2 -0
- Podflow/config/build_original.py +41 -0
- Podflow/config/channge_icon.py +163 -0
- Podflow/config/correct_channelid.py +230 -0
- Podflow/config/correct_config.py +103 -0
- Podflow/config/get_channelid.py +22 -0
- Podflow/config/get_channelid_id.py +21 -0
- Podflow/config/get_config.py +34 -0
- Podflow/download/__init__.py +2 -0
- Podflow/download/convert_bytes.py +18 -0
- Podflow/download/delete_part.py +20 -0
- Podflow/download/dl_aideo_video.py +307 -0
- Podflow/download/show_progress.py +46 -0
- Podflow/download/wait_animation.py +34 -0
- Podflow/download/youtube_and_bilibili_download.py +30 -0
- Podflow/ffmpeg_judge.py +45 -0
- Podflow/httpfs/__init__.py +2 -0
- Podflow/httpfs/app_bottle.py +212 -0
- Podflow/httpfs/port_judge.py +21 -0
- Podflow/main.py +248 -0
- Podflow/makeup/__init__.py +2 -0
- Podflow/makeup/del_makeup_yt_format_fail.py +19 -0
- Podflow/makeup/make_up_file.py +51 -0
- Podflow/makeup/make_up_file_format_mod.py +96 -0
- Podflow/makeup/make_up_file_mod.py +30 -0
- Podflow/message/__init__.py +2 -0
- Podflow/message/backup_zip_save.py +45 -0
- Podflow/message/create_main_rss.py +44 -0
- Podflow/message/display_qrcode_and_url.py +36 -0
- Podflow/message/fail_message_initialize.py +165 -0
- Podflow/message/format_time.py +27 -0
- Podflow/message/get_original_rss.py +65 -0
- Podflow/message/get_video_format.py +111 -0
- Podflow/message/get_video_format_multithread.py +42 -0
- Podflow/message/get_youtube_and_bilibili_video_format.py +87 -0
- Podflow/message/media_format.py +195 -0
- Podflow/message/original_rss_fail_print.py +15 -0
- Podflow/message/rss_create_hash.py +26 -0
- Podflow/message/title_correction.py +30 -0
- Podflow/message/update_information_display.py +72 -0
- Podflow/message/update_youtube_bilibili_rss.py +116 -0
- Podflow/message/want_retry.py +21 -0
- Podflow/message/xml_item.py +83 -0
- Podflow/message/xml_original_item.py +92 -0
- Podflow/message/xml_rss.py +46 -0
- Podflow/netscape/__init__.py +2 -0
- Podflow/netscape/bulid_netscape.py +44 -0
- Podflow/netscape/get_cookie_dict.py +21 -0
- Podflow/parse_arguments.py +80 -0
- Podflow/remove/__init__.py +2 -0
- Podflow/remove/remove_dir.py +33 -0
- Podflow/remove/remove_file.py +23 -0
- Podflow/youtube/__init__.py +2 -0
- Podflow/youtube/build.py +287 -0
- Podflow/youtube/get.py +376 -0
- Podflow/youtube/login.py +39 -0
- podflow-2025.1.26.dist-info/METADATA +214 -0
- podflow-2025.1.26.dist-info/RECORD +80 -0
- podflow-2025.1.26.dist-info/WHEEL +5 -0
- podflow-2025.1.26.dist-info/entry_points.txt +6 -0
- podflow-2025.1.26.dist-info/top_level.txt +1 -0
@@ -0,0 +1,195 @@
|
|
1
|
+
# Podflow/message/media_format.py
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
import yt_dlp
|
5
|
+
from Podflow.message.fail_message_initialize import fail_message_initialize
|
6
|
+
|
7
|
+
|
8
|
+
class MyLogger:
|
9
|
+
def debug(self, msg):
|
10
|
+
pass
|
11
|
+
|
12
|
+
def warning(self, msg):
|
13
|
+
pass
|
14
|
+
|
15
|
+
def info(self, msg):
|
16
|
+
pass
|
17
|
+
|
18
|
+
def error(self, msg):
|
19
|
+
pass
|
20
|
+
|
21
|
+
|
22
|
+
def duration_and_formats(video_website, video_url, cookies):
|
23
|
+
fail_message, infos = None, []
|
24
|
+
try:
|
25
|
+
# 初始化 yt_dlp 实例, 并忽略错误
|
26
|
+
ydl_opts = {
|
27
|
+
"no_warnings": True,
|
28
|
+
"quiet": True, # 禁止非错误信息的输出
|
29
|
+
"logger": MyLogger(),
|
30
|
+
}
|
31
|
+
if cookies:
|
32
|
+
if "www.bilibili.com" in video_website:
|
33
|
+
ydl_opts["http_headers"] = {
|
34
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
|
35
|
+
"Referer": "https://www.bilibili.com/",
|
36
|
+
}
|
37
|
+
elif "www.youtube.com" in video_website:
|
38
|
+
ydl_opts["http_headers"] = {
|
39
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
|
40
|
+
"Referer": "https://www.youtube.com/",
|
41
|
+
}
|
42
|
+
ydl_opts["cookiefile"] = cookies # cookies 是你的 cookies 文件名
|
43
|
+
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
44
|
+
# 使用提供的 URL 提取视频信息
|
45
|
+
if info_dict := ydl.extract_info(f"{video_website}", download=False):
|
46
|
+
# 获取视频时长并返回
|
47
|
+
entries = info_dict.get("entries", None)
|
48
|
+
download_url = info_dict.get("original_url", None)
|
49
|
+
if entries:
|
50
|
+
infos.extend(
|
51
|
+
{
|
52
|
+
"title": entry.get("title"),
|
53
|
+
"duration": entry.get("duration"),
|
54
|
+
"formats": entry.get("formats"),
|
55
|
+
"timestamp": entry.get("timestamp"),
|
56
|
+
"id": entry.get("id"),
|
57
|
+
"description": entry.get("description"),
|
58
|
+
"url": entry.get("webpage_url"),
|
59
|
+
"image": entry.get("thumbnail"),
|
60
|
+
"download": {
|
61
|
+
"url": download_url,
|
62
|
+
"num": playlist_num + 1,
|
63
|
+
},
|
64
|
+
"format_note": entry.get("format_note"),
|
65
|
+
}
|
66
|
+
for playlist_num, entry in enumerate(entries)
|
67
|
+
)
|
68
|
+
else:
|
69
|
+
infos.append(
|
70
|
+
{
|
71
|
+
"title": info_dict.get("title"),
|
72
|
+
"duration": info_dict.get("duration"),
|
73
|
+
"formats": info_dict.get("formats"),
|
74
|
+
"timestamp": info_dict.get("timestamp"),
|
75
|
+
"id": info_dict.get("id"),
|
76
|
+
"description": info_dict.get("description"),
|
77
|
+
"url": info_dict.get("webpage_url"),
|
78
|
+
"image": info_dict.get("thumbnail"),
|
79
|
+
"download": {"url": download_url, "num": None},
|
80
|
+
"format_note": info_dict.get("format_note"),
|
81
|
+
}
|
82
|
+
)
|
83
|
+
except Exception as message_error:
|
84
|
+
fail_message = fail_message_initialize(message_error, video_url)
|
85
|
+
return fail_message, infos
|
86
|
+
|
87
|
+
|
88
|
+
# 定义条件判断函数
|
89
|
+
def check_resolution(item, quality):
|
90
|
+
if "aspect_ratio" in item and (isinstance(item["aspect_ratio"], (float, int))):
|
91
|
+
if item["aspect_ratio"] >= 1:
|
92
|
+
return item["height"] <= int(quality)
|
93
|
+
else:
|
94
|
+
return item["width"] <= int(quality)
|
95
|
+
else:
|
96
|
+
return False
|
97
|
+
|
98
|
+
|
99
|
+
def check_ext(item, media):
|
100
|
+
return item["ext"] == media if "ext" in item else False
|
101
|
+
|
102
|
+
|
103
|
+
def check_vcodec(item):
|
104
|
+
if "vcodec" in item:
|
105
|
+
return (
|
106
|
+
"vp" not in item["vcodec"].lower()
|
107
|
+
and "av01" not in item["vcodec"].lower()
|
108
|
+
and "hev1" not in item["vcodec"].lower()
|
109
|
+
)
|
110
|
+
else:
|
111
|
+
return False
|
112
|
+
|
113
|
+
|
114
|
+
# 获取最好质量媒体的id
|
115
|
+
def best_format_id(formats):
|
116
|
+
tbr_max = 0.0
|
117
|
+
format_id_best = ""
|
118
|
+
vcodec_best = ""
|
119
|
+
for form in formats:
|
120
|
+
if (
|
121
|
+
"tbr" in form
|
122
|
+
and "drc" not in form["format_id"]
|
123
|
+
and form["protocol"] == "https"
|
124
|
+
and (isinstance(form["tbr"], (float, int)))
|
125
|
+
and form["tbr"] >= tbr_max
|
126
|
+
):
|
127
|
+
tbr_max = form["tbr"]
|
128
|
+
format_id_best = form["format_id"]
|
129
|
+
vcodec_best = form["vcodec"]
|
130
|
+
return format_id_best, vcodec_best
|
131
|
+
|
132
|
+
|
133
|
+
# 获取媒体时长和ID模块
|
134
|
+
def media_format(video_website, video_url, media="m4a", quality="480", cookies=None):
|
135
|
+
fail_message = None
|
136
|
+
video_id_count, change_error, fail_message, infos = 0, None, "", []
|
137
|
+
while (
|
138
|
+
video_id_count < 3
|
139
|
+
and change_error is None
|
140
|
+
and (fail_message is not None or not infos)
|
141
|
+
):
|
142
|
+
video_id_count += 1
|
143
|
+
fail_message, infos = duration_and_formats(video_website, video_url, cookies)
|
144
|
+
if fail_message is not None:
|
145
|
+
return fail_message
|
146
|
+
lists = []
|
147
|
+
for entry in infos:
|
148
|
+
duration = entry["duration"]
|
149
|
+
formats = entry["formats"]
|
150
|
+
if duration == "" or duration is None:
|
151
|
+
return "无法获取时长"
|
152
|
+
if formats == "" or formats is None:
|
153
|
+
return "无法获取格式"
|
154
|
+
# 进行筛选
|
155
|
+
formats_m4a = list(
|
156
|
+
filter(lambda item: check_ext(item, "m4a") and check_vcodec(item), formats)
|
157
|
+
)
|
158
|
+
(best_formats_m4a, vcodec_best) = best_format_id(formats_m4a)
|
159
|
+
if best_formats_m4a == "" or best_formats_m4a is None:
|
160
|
+
return (
|
161
|
+
"\033[31m试看\033[0m"
|
162
|
+
if entry["format_note"] == "试看"
|
163
|
+
else "无法获取音频ID"
|
164
|
+
)
|
165
|
+
duration_and_id = [duration, best_formats_m4a]
|
166
|
+
if media == "mp4":
|
167
|
+
formats_mp4 = list(
|
168
|
+
filter(
|
169
|
+
lambda item: check_resolution(item, quality)
|
170
|
+
and check_ext(item, "mp4")
|
171
|
+
and check_vcodec(item),
|
172
|
+
formats,
|
173
|
+
)
|
174
|
+
)
|
175
|
+
(best_formats_mp4, vcodec_best) = best_format_id(formats_mp4)
|
176
|
+
if best_formats_mp4 == "" or best_formats_mp4 is None:
|
177
|
+
return (
|
178
|
+
"\033[31m试看\033[0m"
|
179
|
+
if entry["format_note"] == "试看"
|
180
|
+
else "无法获取视频ID"
|
181
|
+
)
|
182
|
+
duration_and_id.extend((best_formats_mp4, vcodec_best))
|
183
|
+
lists.append(
|
184
|
+
{
|
185
|
+
"duration_and_id": duration_and_id,
|
186
|
+
"title": entry.get("title"),
|
187
|
+
"timestamp": entry.get("timestamp"),
|
188
|
+
"id": entry.get("id"),
|
189
|
+
"description": entry.get("description"),
|
190
|
+
"url": entry.get("url"),
|
191
|
+
"image": entry.get("image"),
|
192
|
+
"download": entry.get("download"),
|
193
|
+
}
|
194
|
+
)
|
195
|
+
return lists
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Podflow/message/original_rss_fail_print.py
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
from Podflow import gVar
|
5
|
+
from Podflow.basic.write_log import write_log
|
6
|
+
|
7
|
+
|
8
|
+
# 打印无法保留原节目信息模块
|
9
|
+
def original_rss_fail_print(xmls_original_fail):
|
10
|
+
channelid_ids = gVar.channelid_youtube_ids | gVar.channelid_bilibili_ids
|
11
|
+
for item in xmls_original_fail:
|
12
|
+
if item in channelid_ids.keys():
|
13
|
+
write_log(
|
14
|
+
f"RSS文件中不存在 {channelid_ids[item]} 无法保留原节目"
|
15
|
+
)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Podflow/message/rss_create_hash.py
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
import re
|
5
|
+
import hashlib
|
6
|
+
|
7
|
+
|
8
|
+
# 生成哈希值模块
|
9
|
+
def create_hash(data):
|
10
|
+
data_str = str(data)
|
11
|
+
hash_object = hashlib.sha256()
|
12
|
+
hash_object.update(data_str.encode())
|
13
|
+
return hash_object.hexdigest()
|
14
|
+
|
15
|
+
|
16
|
+
# rss生成哈希值模块
|
17
|
+
def rss_create_hash(data):
|
18
|
+
patterns = [
|
19
|
+
r"<lastBuildDate>(\w+), (\d{2}) (\w+) (\d{4}) (\d{2}):(\d{2}):(\d{2}) \+\d{4}</lastBuildDate>",
|
20
|
+
r"Podflow_light\.png",
|
21
|
+
r"Podflow_dark\.png",
|
22
|
+
]
|
23
|
+
replacement = ""
|
24
|
+
for pattern in patterns:
|
25
|
+
data = re.sub(pattern, replacement, data)
|
26
|
+
return create_hash(data)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Podflow/message/title_correction.py
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
import re
|
5
|
+
|
6
|
+
|
7
|
+
# 标题文本修改
|
8
|
+
def title_correction(title, video_url, title_changes: list):
|
9
|
+
for title_change in title_changes:
|
10
|
+
match = title_change.get("match", None)
|
11
|
+
mode = title_change["mode"]
|
12
|
+
text = title_change["text"]
|
13
|
+
table = title_change.get("table", [])
|
14
|
+
|
15
|
+
def add_title(mode, text, title):
|
16
|
+
if mode == "add-left":
|
17
|
+
return text + title
|
18
|
+
elif mode == "add-right":
|
19
|
+
return title + text
|
20
|
+
|
21
|
+
if text and text in title:
|
22
|
+
return title
|
23
|
+
elif video_url in table:
|
24
|
+
return add_title(mode, text, title)
|
25
|
+
elif match is not None and re.search(match, title):
|
26
|
+
if mode == "replace":
|
27
|
+
return re.sub(match, text, title)
|
28
|
+
else:
|
29
|
+
return add_title(mode, text, title)
|
30
|
+
return title
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# Podflow/message/want_retry.py
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
import re
|
5
|
+
import os
|
6
|
+
from Podflow.basic.write_log import write_log
|
7
|
+
|
8
|
+
|
9
|
+
# 输出需要更新的信息模块
|
10
|
+
def update_information_display(
|
11
|
+
channelid_ids_update, content_id_update, content_id_backward_update, name
|
12
|
+
):
|
13
|
+
if not channelid_ids_update:
|
14
|
+
return
|
15
|
+
print_channelid_ids_update = f"需更新的{name}频道:\n"
|
16
|
+
# 获取命令行字节宽度
|
17
|
+
try:
|
18
|
+
terminal_width = os.get_terminal_size().columns
|
19
|
+
except OSError:
|
20
|
+
terminal_width = 47
|
21
|
+
# 尝试拆分输出
|
22
|
+
try:
|
23
|
+
for channelid_key, channelid_value in channelid_ids_update.items():
|
24
|
+
if len(print_channelid_ids_update) != len(name) + 8:
|
25
|
+
if (
|
26
|
+
len(
|
27
|
+
re.sub(
|
28
|
+
r"\033\[[0-9;]+m",
|
29
|
+
"",
|
30
|
+
print_channelid_ids_update.split("\n")[-1],
|
31
|
+
).encode("GBK")
|
32
|
+
)
|
33
|
+
+ len(f" | {channelid_value}".encode("utf-8"))
|
34
|
+
<= terminal_width
|
35
|
+
):
|
36
|
+
print_channelid_ids_update += " | "
|
37
|
+
else:
|
38
|
+
print_channelid_ids_update += "\n"
|
39
|
+
if (
|
40
|
+
channelid_key in content_id_update
|
41
|
+
and channelid_key in content_id_backward_update
|
42
|
+
):
|
43
|
+
print_channelid_ids_update += f"\033[34m{channelid_value}\033[0m"
|
44
|
+
elif channelid_key in content_id_update:
|
45
|
+
print_channelid_ids_update += f"\033[32m{channelid_value}\033[0m"
|
46
|
+
elif channelid_key in content_id_backward_update:
|
47
|
+
print_channelid_ids_update += f"\033[36m{channelid_value}\033[0m"
|
48
|
+
else:
|
49
|
+
print_channelid_ids_update += f"\033[33m{channelid_value}\033[0m"
|
50
|
+
# 如果含有特殊字符将使用此输出
|
51
|
+
except Exception:
|
52
|
+
len_channelid_ids_update = len(channelid_ids_update)
|
53
|
+
count_channelid_ids_update = 1
|
54
|
+
for channelid_key, channelid_value in channelid_ids_update.items():
|
55
|
+
if (
|
56
|
+
channelid_key in content_id_update
|
57
|
+
and channelid_key in content_id_backward_update
|
58
|
+
):
|
59
|
+
print_channelid_ids_update += f"\033[34m{channelid_value}\033[0m"
|
60
|
+
elif channelid_key in content_id_update:
|
61
|
+
print_channelid_ids_update += f"\033[32m{channelid_value}\033[0m"
|
62
|
+
elif channelid_key in content_id_backward_update:
|
63
|
+
print_channelid_ids_update += f"\033[36m{channelid_value}\033[0m"
|
64
|
+
else:
|
65
|
+
print_channelid_ids_update += f"\033[33m{channelid_value}\033[0m"
|
66
|
+
if count_channelid_ids_update != len_channelid_ids_update:
|
67
|
+
if count_channelid_ids_update % 2 != 0:
|
68
|
+
print_channelid_ids_update += " | "
|
69
|
+
else:
|
70
|
+
print_channelid_ids_update += "\n"
|
71
|
+
count_channelid_ids_update += 1
|
72
|
+
write_log(print_channelid_ids_update)
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# Podflow/message/update_youtube_bilibili_rss.py
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
import re
|
5
|
+
import threading
|
6
|
+
from Podflow import gVar
|
7
|
+
from Podflow.basic.file_save import file_save
|
8
|
+
from Podflow.basic.write_log import write_log
|
9
|
+
from Podflow.youtube.get import youtube_rss_update
|
10
|
+
from Podflow.basic.folder_build import folder_build
|
11
|
+
from Podflow.bilibili.get import bilibili_rss_update
|
12
|
+
|
13
|
+
|
14
|
+
# 更新Youtube和哔哩哔哩频道xml多线程模块
|
15
|
+
def update_youtube_bilibili_rss():
|
16
|
+
pattern_youtube404 = r"Error 404 \(Not Found\)" # 设置要匹配的正则表达式模式
|
17
|
+
pattern_youtube_error = {
|
18
|
+
"This channel was removed because it violated our Community Guidelines.": "违反社区准则",
|
19
|
+
"This channel does not exist.": "不存在 (ID错误) ",
|
20
|
+
}
|
21
|
+
pattern_youtube_varys = [
|
22
|
+
r"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-2][0-9]:[0-6][0-9]:[0-6][0-9]\+00:00",
|
23
|
+
r'starRating count="[0-9]*"',
|
24
|
+
r'statistics views="[0-9]*"',
|
25
|
+
r"<id>yt:channel:(UC)?(.{22})?</id>",
|
26
|
+
r"<yt:channelId>(UC)?(.{22})?</yt:channelId>",
|
27
|
+
]
|
28
|
+
youtube_bilibili_rss_update_threads = [] # 创建线程列表
|
29
|
+
# Youtube多线程
|
30
|
+
for youtube_key, youtube_value in gVar.channelid_youtube_ids.items():
|
31
|
+
thread = threading.Thread(
|
32
|
+
target=youtube_rss_update,
|
33
|
+
args=(
|
34
|
+
youtube_key,
|
35
|
+
youtube_value,
|
36
|
+
pattern_youtube_varys,
|
37
|
+
pattern_youtube404,
|
38
|
+
pattern_youtube_error,
|
39
|
+
),
|
40
|
+
)
|
41
|
+
youtube_bilibili_rss_update_threads.append(thread)
|
42
|
+
# 开始多线程
|
43
|
+
thread.start()
|
44
|
+
# 哔哩哔哩多线程
|
45
|
+
for bilibili_key, bilibili_value in gVar.channelid_bilibili_ids.items():
|
46
|
+
thread = threading.Thread(
|
47
|
+
target=bilibili_rss_update, args=(bilibili_key, bilibili_value)
|
48
|
+
)
|
49
|
+
youtube_bilibili_rss_update_threads.append(thread)
|
50
|
+
# 开始多线程
|
51
|
+
thread.start()
|
52
|
+
# 等待所有线程完成
|
53
|
+
for thread in youtube_bilibili_rss_update_threads:
|
54
|
+
thread.join()
|
55
|
+
|
56
|
+
# 寻找错误原因
|
57
|
+
def youtube_error(youtube_content, pattern_youtube_error):
|
58
|
+
for (
|
59
|
+
pattern_youtube_error_key,
|
60
|
+
pattern_youtube_error_value,
|
61
|
+
) in pattern_youtube_error.items():
|
62
|
+
if pattern_youtube_error_key in youtube_content:
|
63
|
+
return pattern_youtube_error_value
|
64
|
+
|
65
|
+
# 更新Youtube频道
|
66
|
+
for youtube_key, youtube_value in gVar.channelid_youtube_ids.copy().items():
|
67
|
+
youtube_response = gVar.channelid_youtube_rss[youtube_key]["content"]
|
68
|
+
youtube_response_type = gVar.channelid_youtube_rss[youtube_key]["type"]
|
69
|
+
# xml分类及存储
|
70
|
+
if youtube_response is not None:
|
71
|
+
if youtube_response_type == "dict":
|
72
|
+
# 构建频道文件夹
|
73
|
+
folder_build(youtube_key, "channel_audiovisual")
|
74
|
+
else:
|
75
|
+
if youtube_response_type == "html":
|
76
|
+
youtube_content = youtube_response.text
|
77
|
+
elif youtube_response_type == "text":
|
78
|
+
youtube_content = youtube_response
|
79
|
+
write_log(f"YouTube频道 {youtube_value} 无法更新")
|
80
|
+
else:
|
81
|
+
youtube_content = ""
|
82
|
+
# 判断频道id是否正确
|
83
|
+
if re.search(pattern_youtube404, youtube_content, re.DOTALL):
|
84
|
+
del gVar.channelid_youtube_ids[youtube_key] # 删除错误ID
|
85
|
+
write_log(f"YouTube频道 {youtube_value} ID不正确无法获取")
|
86
|
+
elif youtube_error_message := youtube_error(
|
87
|
+
youtube_content, pattern_youtube_error
|
88
|
+
):
|
89
|
+
del gVar.channelid_youtube_ids[youtube_key] # 删除错误ID
|
90
|
+
write_log(f"YouTube频道 {youtube_value} {youtube_error_message}")
|
91
|
+
else:
|
92
|
+
# 构建文件
|
93
|
+
file_save(youtube_content, f"{youtube_key}.txt", "channel_id")
|
94
|
+
# 构建频道文件夹
|
95
|
+
folder_build(youtube_key, "channel_audiovisual")
|
96
|
+
else:
|
97
|
+
if youtube_response_type == "text":
|
98
|
+
del gVar.channelid_youtube_ids[youtube_key]
|
99
|
+
write_log(f"YouTube频道 {youtube_value} 无法更新")
|
100
|
+
# 更新哔哩哔哩频道
|
101
|
+
for bilibili_key, bilibili_value in gVar.channelid_bilibili_ids.copy().items():
|
102
|
+
bilibili_space = gVar.channelid_bilibili_rss[bilibili_key]["content"]
|
103
|
+
bilibili_space_type = gVar.channelid_bilibili_rss[bilibili_key]["type"]
|
104
|
+
# xml分类及存储
|
105
|
+
if bilibili_space_type == "int":
|
106
|
+
del gVar.channelid_bilibili_ids[bilibili_key] # 删除错误ID
|
107
|
+
write_log(f"BiliBili频道 {bilibili_value} ID不正确无法获取")
|
108
|
+
elif bilibili_space_type == "json":
|
109
|
+
write_log(f"BiliBili频道 {youtube_value} 无法更新")
|
110
|
+
if bilibili_space == {}:
|
111
|
+
del gVar.channelid_bilibili_ids[bilibili_key]
|
112
|
+
else:
|
113
|
+
# 构建文件
|
114
|
+
file_save(bilibili_space, f"{bilibili_key}.json", "channel_id")
|
115
|
+
# 构建频道文件夹
|
116
|
+
folder_build(bilibili_key, "channel_audiovisual")
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Podflow/message/want_retry.py
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
import re
|
5
|
+
|
6
|
+
|
7
|
+
# 判断是否重试模块
|
8
|
+
def want_retry(video_url, num=1):
|
9
|
+
# 定义正则表达式模式(不区分大小写)
|
10
|
+
pattern = rf'\|{video_url}\|(试看|跳过更新|删除或受限|充电专属|直播预约\|a few moments\.)'
|
11
|
+
# 读取 Podflow.log 文件
|
12
|
+
try:
|
13
|
+
with open('Podflow.log', 'r', encoding='utf-8') as file:
|
14
|
+
content = file.read() # 读取文件内容
|
15
|
+
# 使用 re.findall() 查找所有匹配项
|
16
|
+
matches = re.findall(pattern, content)
|
17
|
+
# 计算匹配的个数
|
18
|
+
count = len(matches)
|
19
|
+
except Exception:
|
20
|
+
count = 0
|
21
|
+
return count < num or count % num == 0
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# Podflow/message/xml_item.py
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
import os
|
5
|
+
import html
|
6
|
+
import hashlib
|
7
|
+
from Podflow.message.title_correction import title_correction
|
8
|
+
from Podflow.basic.time_format import time_format
|
9
|
+
from Podflow.basic.get_duration import get_duration
|
10
|
+
from Podflow import gVar
|
11
|
+
|
12
|
+
|
13
|
+
# 生成item模块
|
14
|
+
def xml_item(
|
15
|
+
video_url,
|
16
|
+
output_dir,
|
17
|
+
video_website,
|
18
|
+
channelid_title,
|
19
|
+
title,
|
20
|
+
description,
|
21
|
+
pubDate,
|
22
|
+
image,
|
23
|
+
title_change=None,
|
24
|
+
):
|
25
|
+
if title_change is None:
|
26
|
+
title_change = []
|
27
|
+
channelid_title = html.escape(channelid_title)
|
28
|
+
if title_change:
|
29
|
+
title = title_correction(title, video_url, title_change)
|
30
|
+
# 查看标题中是否有频道名称如无添加到描述中并去除空字符
|
31
|
+
title = title.replace("\x00", "")
|
32
|
+
if channelid_title not in title:
|
33
|
+
if description == "":
|
34
|
+
description = f"『{channelid_title}』{description}"
|
35
|
+
else:
|
36
|
+
description = f"『{channelid_title}』\n{description}".replace("\x00", "")
|
37
|
+
# 更换描述换行符
|
38
|
+
replacement_description = description.replace("\n", "
").replace("\t", "	")
|
39
|
+
# 获取文件后缀和文件字节大小
|
40
|
+
if os.path.exists(f"channel_audiovisual/{output_dir}/{video_url}.mp4"):
|
41
|
+
video_length_bytes = os.path.getsize(
|
42
|
+
f"channel_audiovisual/{output_dir}/{video_url}.mp4"
|
43
|
+
)
|
44
|
+
output_format = "mp4"
|
45
|
+
video_type = "video/mp4"
|
46
|
+
else:
|
47
|
+
if os.path.exists(f"channel_audiovisual/{output_dir}/{video_url}.m4a"):
|
48
|
+
video_length_bytes = os.path.getsize(
|
49
|
+
f"channel_audiovisual/{output_dir}/{video_url}.m4a"
|
50
|
+
)
|
51
|
+
else:
|
52
|
+
video_length_bytes = 0
|
53
|
+
output_format = "m4a"
|
54
|
+
video_type = "audio/x-m4a"
|
55
|
+
# 获取文件时长
|
56
|
+
duration = time_format(
|
57
|
+
get_duration(f"channel_audiovisual/{output_dir}/{video_url}.{output_format}")
|
58
|
+
)
|
59
|
+
# 生成url
|
60
|
+
if gVar.config["token"]:
|
61
|
+
input_string = f"{gVar.config['token']}/channel_audiovisual/{output_dir}/{video_url}.{output_format}"
|
62
|
+
else:
|
63
|
+
input_string = f"channel_audiovisual/{output_dir}/{video_url}.{output_format}"
|
64
|
+
sha256_hash = hashlib.sha256(input_string.encode()).hexdigest()
|
65
|
+
url = f"{gVar.config['address']}/channel_audiovisual/{output_dir}/{video_url}.{output_format}?token={sha256_hash}"
|
66
|
+
# 回显对应的item
|
67
|
+
return f"""
|
68
|
+
<item>
|
69
|
+
<guid>{video_url}</guid>
|
70
|
+
<title>{title}</title>
|
71
|
+
<link>{video_website}</link>
|
72
|
+
<description>{replacement_description}</description>
|
73
|
+
<pubDate>{pubDate}</pubDate>
|
74
|
+
<enclosure url="{url}" length="{video_length_bytes}" type="{video_type}"></enclosure>
|
75
|
+
<itunes:author>{title}</itunes:author>
|
76
|
+
<itunes:subtitle>{title}</itunes:subtitle>
|
77
|
+
<itunes:summary><![CDATA[{description}]]></itunes:summary>
|
78
|
+
<itunes:image href="{image}"></itunes:image>
|
79
|
+
<itunes:duration>{duration}</itunes:duration>
|
80
|
+
<itunes:explicit>no</itunes:explicit>
|
81
|
+
<itunes:order>1</itunes:order>
|
82
|
+
</item>
|
83
|
+
"""
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# Podflow/message/xml_original_item.py
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
import re
|
5
|
+
import html
|
6
|
+
import hashlib
|
7
|
+
from Podflow import gVar
|
8
|
+
from Podflow.message.title_correction import title_correction
|
9
|
+
|
10
|
+
|
11
|
+
# 生成原有的item模块
|
12
|
+
def xml_original_item(original_item, channelid_title, change_judgment=False, title_change=None):
|
13
|
+
if title_change:
|
14
|
+
title_change = []
|
15
|
+
def description_change(text, sep, title, channelid_title):
|
16
|
+
channelid_title = html.escape(channelid_title)
|
17
|
+
if sep in text:
|
18
|
+
text_one, text_two = text.split(sep, 1)
|
19
|
+
if text_one[0] == "『" and text_one[-1] == "』":
|
20
|
+
text = text_two
|
21
|
+
elif text:
|
22
|
+
if text[0] == "『" and text[-1] == "』":
|
23
|
+
text = ""
|
24
|
+
if channelid_title not in title:
|
25
|
+
if text == "":
|
26
|
+
text = f"『{channelid_title}』{text}"
|
27
|
+
else:
|
28
|
+
text = f"『{channelid_title}』{sep}{text}".replace('\x00', '')
|
29
|
+
return text
|
30
|
+
guid = re.search(r"(?<=<guid>).+(?=</guid>)", original_item).group()
|
31
|
+
title = re.search(r"(?<=<title>).+(?=</title>)", original_item).group()
|
32
|
+
if title_change:
|
33
|
+
title = title_correction(title, guid, title_change)
|
34
|
+
title = title.replace('\x00', '')
|
35
|
+
link = re.search(r"(?<=<link>).+(?=</link>)", original_item).group()
|
36
|
+
description = re.search(r"(?<=<description>).+(?=</description>)", original_item)
|
37
|
+
description = description.group() if description else ""
|
38
|
+
if change_judgment:
|
39
|
+
description = description_change(description, "
", title, channelid_title)
|
40
|
+
pubDate = re.search(r"(?<=<pubDate>).+(?=</pubDate>)", original_item).group()
|
41
|
+
url = re.search(r"(?<=<enclosure url\=\").+?(?=\")", original_item).group()
|
42
|
+
url = re.search(r"(?<=/channel_audiovisual/).+/.+\.(m4a|mp4)", url).group()
|
43
|
+
if gVar.config["token"]:
|
44
|
+
input_string = f"{gVar.config['token']}/channel_audiovisual/{url}"
|
45
|
+
else:
|
46
|
+
input_string = f"channel_audiovisual/{url}"
|
47
|
+
sha256_hash = hashlib.sha256(input_string.encode()).hexdigest()
|
48
|
+
url = f"{gVar.config['address']}/channel_audiovisual/{url}?token={sha256_hash}"
|
49
|
+
length = re.search(r"(?<=length\=\")[0-9]+(?=\")", original_item).group()
|
50
|
+
type_video = re.search(
|
51
|
+
r"(?<=type\=\")(video/mp4|audio/x-m4a|audio/mpeg)(?=\")", original_item
|
52
|
+
).group()
|
53
|
+
if type_video == "audio/mpeg":
|
54
|
+
type_video = "audio/x-m4a"
|
55
|
+
itunes_summary = re.search(
|
56
|
+
r"(?<=<itunes:summary><\!\[CDATA\[).+(?=\]\]></itunes:summary>)",
|
57
|
+
original_item,
|
58
|
+
flags=re.DOTALL,
|
59
|
+
)
|
60
|
+
itunes_summary = itunes_summary.group() if itunes_summary else ""
|
61
|
+
if change_judgment:
|
62
|
+
itunes_summary = description_change(itunes_summary, "\n", title, channelid_title)
|
63
|
+
itunes_image = re.search(
|
64
|
+
r"(?<=<itunes:image href\=\").+(?=\"></itunes:image>)", original_item
|
65
|
+
)
|
66
|
+
itunes_image = itunes_image.group() if itunes_image else ""
|
67
|
+
itunes_duration = re.search(
|
68
|
+
r"(?<=<itunes:duration>).+(?=</itunes:duration>)", original_item
|
69
|
+
).group()
|
70
|
+
itunes_explicit = re.search(
|
71
|
+
r"(?<=<itunes:explicit>).+(?=</itunes:explicit>)", original_item
|
72
|
+
).group()
|
73
|
+
itunes_order = re.search(
|
74
|
+
r"(?<=<itunes:order>).+(?=</itunes:order>)", original_item
|
75
|
+
).group()
|
76
|
+
return f"""
|
77
|
+
<item>
|
78
|
+
<guid>{guid}</guid>
|
79
|
+
<title>{title}</title>
|
80
|
+
<link>{link}</link>
|
81
|
+
<description>{description}</description>
|
82
|
+
<pubDate>{pubDate}</pubDate>
|
83
|
+
<enclosure url="{url}" length="{length}" type="{type_video}"></enclosure>
|
84
|
+
<itunes:author>{title}</itunes:author>
|
85
|
+
<itunes:subtitle>{title}</itunes:subtitle>
|
86
|
+
<itunes:summary><![CDATA[{itunes_summary}]]></itunes:summary>
|
87
|
+
<itunes:image href="{itunes_image}"></itunes:image>
|
88
|
+
<itunes:duration>{itunes_duration}</itunes:duration>
|
89
|
+
<itunes:explicit>{itunes_explicit}</itunes:explicit>
|
90
|
+
<itunes:order>{itunes_order}</itunes:order>
|
91
|
+
</item>
|
92
|
+
"""
|