podflow 20250706__py3-none-any.whl → 20250820__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 CHANGED
@@ -34,7 +34,7 @@ default_config = {
34
34
  "QRcode": False, # 是否显示子博客地址二维码(仅在DisplayRSSaddress为True时有效)
35
35
  "BackwardUpdate": False, # 是否向后更新
36
36
  "BackwardUpdate_size": 3, # 向后更新数量(仅在BackwardUpdate为True时有效)
37
- "want_retry_count": 8, # 媒体获取失败后多少次后重试(小于等于该数量时将一直重试)
37
+ "want_retry_count": 25, # 媒体获取失败后多少次后重试(小于等于该数量时将一直重试)
38
38
  "title_change": [ # 标题文本修改(默认为无, 可多个条件,以列表形式存在)
39
39
  { # match和url参数至少有一个, 如都有将同时生效
40
40
  "mode": "add-left", # 修改模式(add-left: 开头添加, add-right: 结尾添加, replace: 内容替换)
@@ -146,6 +146,7 @@ class Application_parse:
146
146
  self.file = ""
147
147
  self.httpfs = False
148
148
  self.index = False
149
+ self.save = []
149
150
 
150
151
 
151
152
  # 创建 Application 类的实例
@@ -62,19 +62,19 @@ def correct_channelid(channelid, website):
62
62
  # 复制字典channelid, 遍历复制后的字典进行操作以避免在循环中删除元素导致的迭代错误
63
63
  channelid_copy = channelid.copy()
64
64
  # 对channelid的错误进行更正
65
- for channelid_key, channeli_value in channelid_copy.items():
65
+ for channelid_key, channelid_value in channelid_copy.items():
66
66
  # 判断是否为字典
67
- if not isinstance(channeli_value, dict):
68
- channeli_value = {"id": channeli_value}
69
- channelid[channelid_key] = channeli_value
67
+ if not isinstance(channelid_value, dict):
68
+ channelid_value = {"id": channelid_value}
69
+ channelid[channelid_key] = channelid_value
70
70
  # 判断id是否正确
71
71
  if (
72
- "id" not in channeli_value
72
+ "id" not in channelid_value
73
73
  or (
74
74
  website == "youtube"
75
- and not re.search(r"^UC.{22}", channeli_value["id"])
75
+ and not re.search(r"^UC.{22}", channelid_value["id"])
76
76
  )
77
- or (website == "bilibili" and not channeli_value["id"].isdigit())
77
+ or (website == "bilibili" and not channelid_value["id"].isdigit())
78
78
  ):
79
79
  # 删除错误的
80
80
  del channelid[channelid_key]
@@ -82,9 +82,9 @@ def correct_channelid(channelid, website):
82
82
  else:
83
83
  # 对update_size进行纠正
84
84
  if (
85
- "update_size" not in channeli_value
86
- or not isinstance(channeli_value["update_size"], int)
87
- or channeli_value["update_size"] <= 0
85
+ "update_size" not in channelid_value
86
+ or not isinstance(channelid_value["update_size"], int)
87
+ or channelid_value["update_size"] <= 0
88
88
  ):
89
89
  channelid[channelid_key]["update_size"] = default_config[
90
90
  f"channelid_{website}"
@@ -92,13 +92,13 @@ def correct_channelid(channelid, website):
92
92
  # 对id进行纠正
93
93
  if website == "youtube":
94
94
  channelid[channelid_key]["id"] = re.search(
95
- r"UC.{22}", channeli_value["id"]
95
+ r"UC.{22}", channelid_value["id"]
96
96
  ).group()
97
97
  # 对last_size进行纠正
98
98
  if (
99
- "last_size" not in channeli_value
100
- or not isinstance(channeli_value["last_size"], int)
101
- or channeli_value["last_size"] <= 0
99
+ "last_size" not in channelid_value
100
+ or not isinstance(channelid_value["last_size"], int)
101
+ or channelid_value["last_size"] <= 0
102
102
  ):
103
103
  channelid[channelid_key]["last_size"] = default_config[
104
104
  f"channelid_{website}"
@@ -108,77 +108,77 @@ def correct_channelid(channelid, website):
108
108
  channelid[channelid_key]["update_size"],
109
109
  )
110
110
  # 对title进行纠正
111
- if "title" not in channeli_value:
111
+ if "title" not in channelid_value:
112
112
  channelid[channelid_key]["title"] = channelid_key
113
113
  # 对quality进行纠正
114
114
  if (
115
115
  (
116
- "quality" not in channeli_value
117
- or channeli_value["quality"] not in dpi
116
+ "quality" not in channelid_value
117
+ or channelid_value["quality"] not in dpi
118
118
  )
119
- and "media" in channeli_value
120
- and channeli_value["media"] == "mp4"
119
+ and "media" in channelid_value
120
+ and channelid_value["media"] == "mp4"
121
121
  ):
122
122
  channelid[channelid_key]["quality"] = default_config[
123
123
  f"channelid_{website}"
124
124
  ][channelid_name]["quality"]
125
125
  # 对media进行纠正
126
126
  if (
127
- "media" in channeli_value
128
- and channeli_value["media"] not in media
129
- and channeli_value["media"] in video_media
127
+ "media" in channelid_value
128
+ and channelid_value["media"] not in media
129
+ and channelid_value["media"] in video_media
130
130
  ):
131
131
  channelid[channelid_key]["media"] = "mp4"
132
132
  elif (
133
- "media" in channeli_value
134
- and channeli_value["media"] not in media
135
- or "media" not in channeli_value
133
+ "media" in channelid_value
134
+ and channelid_value["media"] not in media
135
+ or "media" not in channelid_value
136
136
  ):
137
137
  channelid[channelid_key]["media"] = "m4a"
138
138
  # 对DisplayRSSaddress进行纠正
139
- if "DisplayRSSaddress" not in channeli_value or not isinstance(
140
- channeli_value["DisplayRSSaddress"], bool
139
+ if "DisplayRSSaddress" not in channelid_value or not isinstance(
140
+ channelid_value["DisplayRSSaddress"], bool
141
141
  ):
142
142
  channelid[channelid_key]["DisplayRSSaddress"] = False
143
143
  # 对InmainRSS进行纠正
144
- if "InmainRSS" in channeli_value and isinstance(
145
- channeli_value["InmainRSS"], bool
144
+ if "InmainRSS" in channelid_value and isinstance(
145
+ channelid_value["InmainRSS"], bool
146
146
  ):
147
- if channeli_value["InmainRSS"] is False:
147
+ if channelid_value["InmainRSS"] is False:
148
148
  channelid[channelid_key]["DisplayRSSaddress"] = True
149
149
  else:
150
150
  channelid[channelid_key]["InmainRSS"] = True
151
151
  # 对QRcode进行纠正
152
- if "QRcode" not in channeli_value or not isinstance(
153
- channeli_value["QRcode"], bool
152
+ if "QRcode" not in channelid_value or not isinstance(
153
+ channelid_value["QRcode"], bool
154
154
  ):
155
155
  channelid[channelid_key]["QRcode"] = False
156
156
  # 对BackwardUpdate进行纠正
157
- if "BackwardUpdate" not in channeli_value or not isinstance(
158
- channeli_value["BackwardUpdate"], bool
157
+ if "BackwardUpdate" not in channelid_value or not isinstance(
158
+ channelid_value["BackwardUpdate"], bool
159
159
  ):
160
160
  channelid[channelid_key]["BackwardUpdate"] = False
161
161
  # 对BackwardUpdate_size进行纠正
162
162
  if channelid[channelid_key]["BackwardUpdate"] and (
163
- "BackwardUpdate_size" not in channeli_value
164
- or not isinstance(channeli_value["BackwardUpdate_size"], int)
165
- or channeli_value["BackwardUpdate_size"] <= 0
163
+ "BackwardUpdate_size" not in channelid_value
164
+ or not isinstance(channelid_value["BackwardUpdate_size"], int)
165
+ or channelid_value["BackwardUpdate_size"] <= 0
166
166
  ):
167
167
  channelid[channelid_key]["BackwardUpdate_size"] = default_config[
168
168
  f"channelid_{website}"
169
169
  ][channelid_name]["BackwardUpdate_size"]
170
170
  # 对want_retry_count进行纠正
171
171
  if (
172
- "want_retry_count" not in channeli_value
173
- or not isinstance(channeli_value["want_retry_count"], int)
174
- or channeli_value["want_retry_count"] <= 0
172
+ "want_retry_count" not in channelid_value
173
+ or not isinstance(channelid_value["want_retry_count"], int)
174
+ or channelid_value["want_retry_count"] <= 0
175
175
  ):
176
176
  channelid[channelid_key]["want_retry_count"] = default_config[
177
177
  f"channelid_{website}"
178
178
  ][channelid_name]["want_retry_count"]
179
179
  # 对title_change进行纠正
180
- if "title_change" in channeli_value:
181
- title_changes = channeli_value["title_change"]
180
+ if "title_change" in channelid_value:
181
+ title_changes = channelid_value["title_change"]
182
182
  uphold_title_changes = []
183
183
  if isinstance(title_changes, list):
184
184
  for title_change in title_changes:
@@ -208,23 +208,23 @@ def correct_channelid(channelid, website):
208
208
  else:
209
209
  del channelid[channelid_key]["title_change"]
210
210
  if website == "bilibili" and (
211
- "AllPartGet" not in channeli_value
212
- or not isinstance(channeli_value["AllPartGet"], bool)
211
+ "AllPartGet" not in channelid_value
212
+ or not isinstance(channelid_value["AllPartGet"], bool)
213
213
  ):
214
214
  channelid[channelid_key]["AllPartGet"] = (
215
215
  channelid[channelid_key]["update_size"] > 5
216
216
  )
217
217
  if website == "youtube" and (
218
- "NoShorts" not in channeli_value
219
- or not isinstance(channeli_value["NoShorts"], bool)
218
+ "NoShorts" not in channelid_value
219
+ or not isinstance(channelid_value["NoShorts"], bool)
220
220
  ):
221
221
  channelid[channelid_key]["NoShorts"] = False
222
222
  if (
223
223
  channelid[channelid_key]["InmainRSS"] is False
224
- and f"{config['address']}/channel_rss/{channeli_value['id']}.xml"
224
+ and f"{config['address']}/channel_rss/{channelid_value['id']}.xml"
225
225
  not in parse.shortcuts_url_original
226
226
  ):
227
227
  gVar.shortcuts_url[channelid_key] = (
228
- f"{config['address']}/channel_rss/{channeli_value['id']}.xml"
228
+ f"{config['address']}/channel_rss/{channelid_value['id']}.xml"
229
229
  )
230
230
  return channelid
@@ -61,6 +61,7 @@ def download_video(
61
61
  "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",
62
62
  "Referer": "https://www.youtube.com/",
63
63
  }
64
+ ydl_opts["extractor_args"] = {"youtube": {"player-client": "web_embedded,web,tv"}}
64
65
  ydl_opts["cookiefile"] = cookies # cookies 是你的 cookies 文件名
65
66
  if playlist_num: # 播放列表的第n个视频
66
67
  ydl_opts["playliststart"] = playlist_num
@@ -21,6 +21,7 @@ from podflow.upload.time_key import check_time_key
21
21
  from podflow.basic.folder_build import folder_build
22
22
  from podflow.httpfs.get_channelid import get_channelid
23
23
  from podflow.basic.random_sequence import random_sequence
24
+ from podflow.upload.find_media_index import find_media_index
24
25
  from podflow.upload.store_users_info import store_users_info
25
26
 
26
27
 
@@ -47,6 +48,8 @@ class bottle_app:
47
48
  self.app_bottle.route("/login", callback=self.login)
48
49
  self.app_bottle.route("/upload", method="POST", callback=self.upload)
49
50
  self.app_bottle.route("/flush", method="POST", callback=self.clear_cache)
51
+ self.app_bottle.route("/remove", method="POST", callback=self.remove)
52
+ self.app_bottle.route("/download", callback=self.download)
50
53
  else:
51
54
  self.app_bottle.route("/index", callback=self.index)
52
55
  self.app_bottle.route("/getid", method="POST", callback=self.getid)
@@ -278,7 +281,7 @@ class bottle_app:
278
281
  self.print_out("newuser", 401)
279
282
  return {
280
283
  "code": -1,
281
- "message": "Unauthorized: Invalid Token",
284
+ "message": "Unauthorized: Invalid Token", # 未经授权: 无效的 Token
282
285
  }
283
286
 
284
287
  # 路由处理登陆请求
@@ -357,6 +360,12 @@ class bottle_app:
357
360
  "code": -6,
358
361
  "message": "ChannelId Does Not Exist", # 频道ID不存在
359
362
  }
363
+ if not filename:
364
+ self.print_out("upload", 404)
365
+ return {
366
+ "code": -14,
367
+ "message": "Filename Not Provided", # 未提供文件名
368
+ }
360
369
  address = f"channel_audiovisual/{channelid}"
361
370
  file_list = os.listdir(address) if os.path.exists(address) else []
362
371
  # 安全地分割文件名和后缀
@@ -431,7 +440,7 @@ class bottle_app:
431
440
  original_file.seek(0)
432
441
  if upload_hash == build_hash(original_file):
433
442
  self.print_out("upload same", 200)
434
- store_users_info(username,filename)
443
+ store_users_info(username, filename, channelid)
435
444
  close_file()
436
445
  return {
437
446
  "code": 1,
@@ -452,7 +461,7 @@ class bottle_app:
452
461
  ) # 传递文件对象
453
462
  # 打印成功信息并返回成功码
454
463
  self.print_out("upload", 200)
455
- store_users_info(username,filename)
464
+ store_users_info(username, filename, channelid)
456
465
  close_file()
457
466
  return {
458
467
  "code": 0,
@@ -465,7 +474,7 @@ class bottle_app:
465
474
  # 捕获所有其他可能的异常
466
475
  self.print_out("upload", 500)
467
476
  return {
468
- "code": -9,
477
+ "code": -10,
469
478
  "message": f"Server Error: {str(e)}", # 将异常信息返回给客户端
470
479
  }
471
480
  finally:
@@ -495,7 +504,7 @@ class bottle_app:
495
504
  original_file.seek(0)
496
505
  if upload_hash == build_hash(original_file):
497
506
  self.print_out("upload same", 200)
498
- store_users_info(username,filename)
507
+ store_users_info(username, filename, channelid)
499
508
  return {
500
509
  "code": 1,
501
510
  "message": "The Same File Exists", # 相同文件已存在
@@ -557,9 +566,199 @@ class bottle_app:
557
566
  else:
558
567
  self.print_out("flush", 404)
559
568
  return {
560
- "code": -10,
561
- "message": "Cache Does Not Exist", # 频道ID不存在
569
+ "code": -12,
570
+ "message": "Cache Does Not Exist", # 缓存不存在
571
+ }
572
+
573
+ # 路由处理删除请求
574
+ def remove(self):
575
+ # 获取已上传数据
576
+ upload_message = gVar.upload_message
577
+ # 获取上传数据配置(存储用户名和密码)
578
+ upload_data = gVar.upload_data
579
+ # 从请求参数中获取用户名,默认为空字符串
580
+ username = request.query.get("username", "")
581
+ # 从请求参数中获取密码,默认为空字符串
582
+ password = request.query.get("password", "")
583
+ if username not in upload_data:
584
+ self.print_out("login", 401)
585
+ return {
586
+ "code": -2,
587
+ "message": "Username Error", # 用户名错误
588
+ "error": None,
589
+ }
590
+ # 验证密码是否正确
591
+ if upload_data[username] != password:
592
+ self.print_out("login", 401)
593
+ return {
594
+ "code": -3,
595
+ "message": "Password Error", # 密码错误
596
+ "error": None,
597
+ }
598
+ mode = request.query.get("mode", "")
599
+ if mode not in ["file", "folder"]:
600
+ self.print_out("remove", 404)
601
+ return {
602
+ "code": -13,
603
+ "message": "Invalid Mode", # 无效的模式
604
+ "error": None,
605
+ }
606
+ channelid = request.query.get("channel_id", "")
607
+ if not channelid:
608
+ # 打印错误信息并返回错误码
609
+ self.print_out("remove", 404)
610
+ return {
611
+ "code": -6,
612
+ "message": "ChannelId Does Not Exist", # 频道ID不存在
613
+ "error": None,
614
+ }
615
+ if mode == "file":
616
+ filename = request.query.get("filename", "")
617
+ if not filename:
618
+ self.print_out("remove", 404)
619
+ return {
620
+ "code": -14,
621
+ "message": "Filename Not Provided", # 未提供文件名
622
+ "error": None,
623
+ }
624
+ index = find_media_index(upload_message, filename, "mediaid")
625
+ if index == -1:
626
+ self.print_out("remove", 404)
627
+ return{
628
+ "code": -15,
629
+ "message": "File Not In Data", # 文件不在数据中
630
+ "error": None,
631
+ }
632
+ if upload_message[index]["channelid"] != channelid:
633
+ self.print_out("remove", 404)
634
+ return {
635
+ "code": -16,
636
+ "message": "ChannelId Mismatch", # 频道ID不匹配
637
+ "error": None,
638
+ }
639
+ userlist = upload_message[index]["users"]
640
+ if username not in userlist:
641
+ self.print_out("remove", 404)
642
+ return {
643
+ "code": -17,
644
+ "message": "User Not In List", # 用户不在列表中
645
+ "error": None,
646
+ }
647
+ try:
648
+ os.remove(f"channel_audiovisual/{channelid}/{filename}")
649
+ except FileNotFoundError:
650
+ self.print_out("remove", 404)
651
+ return {
652
+ "code": -18,
653
+ "message": "File Not Found", # 文件未找到
654
+ "error": None,
655
+ }
656
+ except Exception as e:
657
+ self.print_out("remove", 500)
658
+ return {
659
+ "code": -19,
660
+ "message": f"Error Removing File: {str(e)}", # 删除文件错误
661
+ "error": str(e),
662
+ }
663
+ if len(userlist) == 1:
664
+ # 如果用户列表中只有当前用户, 则删除该条记录
665
+ del upload_message[index]
666
+ else:
667
+ # 如果用户列表中有多个用户, 则移除当前用户
668
+ upload_message[index]["users"].remove(username)
669
+ self.print_out("remove", 200)
670
+ return {
671
+ "code": 4,
672
+ "message": "File Removed Successfully", # 文件删除成功
673
+ "error": None,
674
+ }
675
+ else:
676
+ remove_num = 0
677
+ for item in upload_message:
678
+ userlist = item["users"]
679
+ if item["channelid"] == channelid and username in userlist:
680
+ try:
681
+ os.remove(f"channel_audiovisual/{channelid}/{item['mediaid']}")
682
+ remove_num += 1
683
+ except FileNotFoundError:
684
+ self.print_out("remove", 404)
685
+ return {
686
+ "code": -18,
687
+ "message": "File Not Found", # 文件未找到
688
+ "error": None,
689
+ }
690
+ except Exception as e:
691
+ self.print_out("remove", 500)
692
+ return {
693
+ "code": -19,
694
+ "message": f"Error Removing File: {str(e)}", # 删除文件错误
695
+ "error": str(e),
696
+ }
697
+ if len(userlist) == 1:
698
+ # 如果用户列表中只有当前用户, 则删除该条记录
699
+ del upload_message[index]
700
+ else:
701
+ # 如果用户列表中有多个用户, 则移除当前用户
702
+ upload_message[index]["users"].remove(username)
703
+ if remove_num == 0:
704
+ self.print_out("remove", 404)
705
+ return {
706
+ "code": -20,
707
+ "message": "No Files Found", # 未找到用户的文件
708
+ "error": None,
709
+ }
710
+ else:
711
+ self.print_out("remove", 200)
712
+ return {
713
+ "code": 5,
714
+ "message": "Folder Removed Successfully", # 文件夹删除成功
715
+ "error": None,
716
+ }
717
+ # 路由处理下载请求
718
+ def download(self):
719
+ # 获取已上传数据
720
+ upload_message = gVar.upload_message
721
+ # 获取上传数据配置(存储用户名和密码)
722
+ upload_data = gVar.upload_data
723
+ # 从请求参数中获取用户名,默认为空字符串
724
+ username = request.query.get("username", "")
725
+ # 从请求参数中获取密码,默认为空字符串
726
+ password = request.query.get("password", "")
727
+ channelid = request.query.get("channel_id", "")
728
+ filename = request.query.get("filename", "")
729
+ if username not in upload_data:
730
+ self.print_out("login", 401)
731
+ return {
732
+ "code": -2,
733
+ "message": "Username Error", # 用户名错误
734
+ "error": None,
735
+ }
736
+ # 验证密码是否正确
737
+ if upload_data[username] != password:
738
+ self.print_out("login", 401)
739
+ return {
740
+ "code": -3,
741
+ "message": "Password Error", # 密码错误
742
+ "error": None,
743
+ }
744
+ if not channelid:
745
+ self.print_out("download", 404)
746
+ return {
747
+ "code": -6,
748
+ "message": "ChannelId Does Not Exist", # 频道ID不存在
749
+ }
750
+ if not filename:
751
+ self.print_out("download", 404)
752
+ return {
753
+ "code": -14,
754
+ "message": "Filename Not Provided", # 未提供文件名
562
755
  }
756
+
757
+
758
+
759
+
760
+
761
+
563
762
 
564
763
  # 路由处理模板文件请求
565
764
  def serve_template_file(self, filepath):
podflow/main_podcast.py CHANGED
@@ -12,6 +12,7 @@ import cherrypy
12
12
 
13
13
  # 基本功能模块
14
14
  from podflow import gVar, parse
15
+ from podflow.basic.file_save import file_save
15
16
  from podflow.basic.split_dict import split_dict
16
17
  from podflow.basic.time_print import time_print
17
18
 
@@ -211,9 +212,9 @@ def main_podcast():
211
212
  progress_update(0.83, num=0.0049)
212
213
  if gVar.config["remove_media"]:
213
214
  # 删除不在rss中的媒体文件
214
- remove_file()
215
+ remove_file(upload_url)
215
216
  # 删除已抛弃的媒体文件夹
216
- remove_dir()
217
+ remove_dir(upload_url)
217
218
  progress_update(0.84)
218
219
  # 补全缺失媒体文件到字典
219
220
  make_up_file()
@@ -257,6 +258,12 @@ def main_podcast():
257
258
  if upload_url:
258
259
  thread_upload.join()
259
260
  time_print("频道无更新内容")
261
+ # 保存需要的变量
262
+ if parse.save:
263
+ for save_data in parse.save:
264
+ file_data = getattr(gVar, save_data, None)
265
+ if file_data:
266
+ file_save(file_data, f"{save_data}.json")
260
267
  # 清空变量内数据
261
268
  gVar.channelid_youtube_ids_update.clear() # 需更新的YouTube频道字典
262
269
  gVar.youtube_content_ytid_update.clear() # 需下载YouTube视频字典
@@ -170,15 +170,20 @@ error_reason = [
170
170
  "text",
171
171
  ],
172
172
  [
173
- r"Got error: HTTPSConnectionPool\(host='.+\.mcdn\.bilivideo\.cn', port=8082\): Read timed out\. \(read timeout=20\.0\)",
173
+ r"Got error: HTTPSConnectionPool\(host='.+\.mcdn\.bilivideo\.cn', port=[0-9]{4}\): Read timed out\. \(read timeout=20\.0\)",
174
174
  "\033[31m响应超时\033[0m",
175
175
  "regexp",
176
176
  ],
177
177
  [
178
- r"Got error: \<urllib3\.connection\.HTTPSConnection object at .{18}\>: Failed to establish a new connection: \[WinError 10061\] 由于目标计算机积极拒绝,无法连接。"
178
+ r"Got error: \<urllib3\.connection\.HTTPSConnection object at .{18}\>: Failed to establish a new connection: \[WinError 10061\] 由于目标计算机积极拒绝,无法连接。",
179
179
  "\033[31m链接拒绝\033[0m",
180
180
  "regexp",
181
181
  ],
182
+ [
183
+ r"YouTube said: The playlist does not exist.",
184
+ "\033[31m播放列表不存在\033[0m",
185
+ "text",
186
+ ],
182
187
  ]
183
188
 
184
189
 
@@ -194,6 +199,7 @@ def fail_message_initialize(message_error, video_url):
194
199
  .replace("[youtube] ", "")
195
200
  .replace("[download] ", "")
196
201
  .replace("[BiliBili] ", "")
202
+ .replace("[youtube:tab] ", "")
197
203
  )
198
204
  if video_url[:2] == "BV":
199
205
  fail_message = fail_message.replace(f"{video_url[2:]}: ", "")
@@ -39,6 +39,7 @@ def duration_and_formats(video_website, video_url, cookies):
39
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
40
  "Referer": "https://www.youtube.com/",
41
41
  }
42
+ ydl_opts["extractor_args"] = {"youtube": {"player-client": "web_embedded,web,tv"}}
42
43
  ydl_opts["cookiefile"] = cookies # cookies 是你的 cookies 文件名
43
44
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
44
45
  # 使用提供的 URL 提取视频信息
@@ -1,9 +1,24 @@
1
- # podflow/message/want_retry.py
1
+ # podflow/message/update_information_display.py
2
2
  # coding: utf-8
3
3
 
4
4
  import re
5
5
  import os
6
+ from podflow import gVar
6
7
  from podflow.basic.write_log import write_log
8
+ from podflow.message.want_retry import want_retry
9
+
10
+
11
+ def skip_display(name, channelid_key, channelid_value, id_update):
12
+ if name == "YouTube":
13
+ failed_count = gVar.channelid_youtube[channelid_value]["want_retry_count"]
14
+ elif name == "BiliBili":
15
+ failed_count = gVar.channelid_bilibili[channelid_value]["want_retry_count"]
16
+ else:
17
+ failed_count = 0
18
+ for video_id in id_update[channelid_key]:
19
+ if want_retry(video_id, failed_count):
20
+ return False
21
+ return True
7
22
 
8
23
 
9
24
  # 输出需要更新的信息模块
@@ -40,11 +55,28 @@ def update_information_display(
40
55
  channelid_key in content_id_update
41
56
  and channelid_key in content_id_backward_update
42
57
  ):
43
- print_channelid_ids_update += f"\033[34m{channelid_value}\033[0m"
58
+ if skip_display(
59
+ name, channelid_key, channelid_value, content_id_update
60
+ ) and skip_display(
61
+ name, channelid_key, channelid_value, content_id_backward_update
62
+ ):
63
+ print_channelid_ids_update += f"\033[97m{channelid_value}\033[0m"
64
+ else:
65
+ print_channelid_ids_update += f"\033[34m{channelid_value}\033[0m"
44
66
  elif channelid_key in content_id_update:
45
- print_channelid_ids_update += f"\033[32m{channelid_value}\033[0m"
67
+ if skip_display(
68
+ name, channelid_key, channelid_value, content_id_update
69
+ ):
70
+ print_channelid_ids_update += f"\033[97m{channelid_value}\033[0m"
71
+ else:
72
+ print_channelid_ids_update += f"\033[32m{channelid_value}\033[0m"
46
73
  elif channelid_key in content_id_backward_update:
47
- print_channelid_ids_update += f"\033[36m{channelid_value}\033[0m"
74
+ if skip_display(
75
+ name, channelid_key, channelid_value, content_id_backward_update
76
+ ):
77
+ print_channelid_ids_update += f"\033[97m{channelid_value}\033[0m"
78
+ else:
79
+ print_channelid_ids_update += f"\033[36m{channelid_value}\033[0m"
48
80
  else:
49
81
  print_channelid_ids_update += f"\033[33m{channelid_value}\033[0m"
50
82
  # 如果含有特殊字符将使用此输出
@@ -2,12 +2,28 @@
2
2
  # coding: utf-8
3
3
 
4
4
  import os
5
+ import re
5
6
  import html
6
7
  import hashlib
7
- from podflow.message.title_correction import title_correction
8
+ from podflow import gVar
8
9
  from podflow.basic.time_format import time_format
9
10
  from podflow.basic.get_duration import get_duration
10
- from podflow import gVar
11
+ from podflow.upload.find_media_index import find_media_index
12
+ from podflow.message.title_correction import title_correction
13
+
14
+
15
+ def get_duration_by_guid(xml_text: str, target_guid: str):
16
+ # 匹配包含目标 GUID 的 <item> 区块,并提取 <itunes:duration> 的值
17
+ pattern = re.compile(
18
+ r"<item>.*?<guid>"
19
+ + re.escape(target_guid)
20
+ + r"</guid>.*?<itunes:duration>([^<]+)</itunes:duration>.*?</item>",
21
+ re.S,
22
+ )
23
+ m = pattern.search(xml_text)
24
+ if m:
25
+ return m.group(1)
26
+ return "Unknown"
11
27
 
12
28
 
13
29
  # 生成item模块
@@ -53,9 +69,19 @@ def xml_item(
53
69
  output_format = "m4a"
54
70
  video_type = "audio/x-m4a"
55
71
  # 获取文件时长
56
- duration = time_format(
57
- get_duration(f"channel_audiovisual/{output_dir}/{video_url}.{output_format}")
58
- )
72
+ file_path = f"channel_audiovisual/{output_dir}/{video_url}.{output_format}"
73
+ duration = ""
74
+ if os.path.exists(file_path):
75
+ duration = time_format(get_duration(file_path))
76
+ elif gVar.config["upload"]:
77
+ index = find_media_index(gVar.upload_original, f"{video_url}.{output_format}")
78
+ if index != -1:
79
+ item = gVar.upload_original[index]
80
+ if "duration" in item:
81
+ duration = time_format(item["duration"])
82
+ else:
83
+ xml_text = gVar.xmls_original.get(output_dir, "")
84
+ duration = get_duration_by_guid(xml_text, video_url)
59
85
  # 生成url
60
86
  if gVar.config["token"]:
61
87
  input_string = f"{gVar.config['token']}/channel_audiovisual/{output_dir}/{video_url}.{output_format}"
@@ -73,6 +73,13 @@ def parse_arguments():
73
73
  action="store_true",
74
74
  help="Only upload server function, solely for LAN backup (applicable to iOS)",
75
75
  )
76
+ parser.add_argument(
77
+ "--save",
78
+ nargs="*",
79
+ type=str,
80
+ metavar="Variable",
81
+ help="Used during testing",
82
+ )
76
83
  parser.add_argument("--file", nargs="?", help=argparse.SUPPRESS) # 仅运行在ipynb中
77
84
  # 解析参数
78
85
  args = parser.parse_args()
@@ -83,6 +90,7 @@ def parse_arguments():
83
90
  parse.httpfs = args.httpfs
84
91
  parse.upload = args.upload
85
92
  parse.index = args.index
93
+ parse.save = args.save
86
94
  # 检查并处理参数的状态
87
95
  if args.times is not None:
88
96
  parse.update_num = int(args.times[0])
@@ -6,10 +6,72 @@ import re
6
6
  import shutil
7
7
  from podflow import gVar
8
8
  from podflow.basic.write_log import write_log
9
+ from podflow.basic.http_client import http_client
10
+
11
+
12
+ def judge_upload(upload_url, name):
13
+ if upload_url:
14
+ sign = True
15
+ upload_original = gVar.upload_original
16
+ for item in upload_original:
17
+ if item["channel_id"] == name and item["upload"] is True:
18
+ sign = False
19
+ break
20
+ if sign:
21
+ return True
22
+ result = {
23
+ -2: "用户名错误",
24
+ -3: "密码错误",
25
+ -13: "删除模式错误",
26
+ -6: "频道ID不存在",
27
+ -14: "未提供文件名",
28
+ -15: "文件名不匹配",
29
+ -16: "频道ID不匹配",
30
+ -17: "用户名不匹配",
31
+ -18: "文件不存在",
32
+ -19: "删除文件错误",
33
+ -20: "未找到用户文件",
34
+ }
35
+ username = gVar.upload_json["username"]
36
+ password = gVar.upload_json["password"]
37
+ data = {
38
+ "username": username,
39
+ "password": password,
40
+ "mode": "folder",
41
+ "channelid": name,
42
+ }
43
+ response, err = http_client(
44
+ url=f"{upload_url}/remove",
45
+ name="",
46
+ max_retries=3,
47
+ data=data,
48
+ mode="post",
49
+ mistake=True,
50
+ )
51
+ if response:
52
+ response = response.json()
53
+ code = response.get("code")
54
+ error = response.get("error", "")
55
+ if code == 5:
56
+ return True
57
+ else:
58
+ message = result.get(code, "未知错误")
59
+ if error:
60
+ message += f":\n{error}"
61
+ bottle_text = f"\033[31m远程删除文件夹失败\033[0m: {message}"
62
+ write_log(bottle_text)
63
+ return False
64
+ else:
65
+ bottle_text = f"\033[31m远程删除文件夹失败\033[0m: 网络连接失败{err}"
66
+ write_log(bottle_text)
67
+ elif gVar.config["upload"]:
68
+ return False
69
+ else:
70
+ return True
9
71
 
10
72
 
11
73
  # 删除已抛弃的媒体文件夹模块
12
- def remove_dir():
74
+ def remove_dir(upload_url):
13
75
  def remove_path(name):
14
76
  directory_path = f"channel_audiovisual/{name}"
15
77
  # 检查目录是否存在
@@ -25,9 +87,15 @@ def remove_dir():
25
87
  ]
26
88
  folder_names_youtube = [name for name in folder_names if re.match(r"UC.{22}", name)]
27
89
  for name in folder_names_youtube:
28
- if name not in gVar.channelid_youtube_ids_original:
90
+ if (
91
+ name not in gVar.channelid_youtube_ids_original
92
+ and judge_upload(upload_url, name)
93
+ ):
29
94
  remove_path(name)
30
95
  folder_names_bilibili = [name for name in folder_names if re.match(r"[0-9]+", name)]
31
96
  for name in folder_names_bilibili:
32
- if name not in gVar.channelid_bilibili_ids_original:
97
+ if (
98
+ name not in gVar.channelid_bilibili_ids_original
99
+ and judge_upload(upload_url, name)
100
+ ):
33
101
  remove_path(name)
@@ -4,21 +4,85 @@
4
4
  import os
5
5
  from podflow import gVar
6
6
  from podflow.basic.write_log import write_log
7
+ from podflow.basic.http_client import http_client
8
+ from podflow.upload.find_media_index import find_media_index
9
+
10
+
11
+ def judge_upload(upload_url, output_dir, file_name):
12
+ if upload_url:
13
+ upload_original = gVar.upload_original
14
+ index = find_media_index(upload_original, file_name)
15
+ if index != -1:
16
+ return True
17
+ item = upload_original[index]
18
+ if not item["upload"]:
19
+ return True
20
+ result = {
21
+ -2: "用户名错误",
22
+ -3: "密码错误",
23
+ -13: "删除模式错误",
24
+ -6: "频道ID不存在",
25
+ -14: "未提供文件名",
26
+ -15: "文件名不匹配",
27
+ -16: "频道ID不匹配",
28
+ -17: "用户名不匹配",
29
+ -18: "文件不存在",
30
+ -19: "删除文件错误",
31
+ -20: "未找到用户文件",
32
+ }
33
+ username = gVar.upload_json["username"]
34
+ password = gVar.upload_json["password"]
35
+ data = {
36
+ "username": username,
37
+ "password": password,
38
+ "mode": "file",
39
+ "channelid": output_dir,
40
+ "filename": file_name,
41
+ }
42
+ response, err = http_client(
43
+ url=f"{upload_url}/remove",
44
+ name="",
45
+ max_retries=3,
46
+ data=data,
47
+ mode="post",
48
+ mistake=True,
49
+ )
50
+ if response:
51
+ response = response.json()
52
+ code = response.get("code")
53
+ error = response.get("error", "")
54
+ if code == 4:
55
+ return True
56
+ else:
57
+ message = result.get(code, "未知错误")
58
+ if error:
59
+ message += f":\n{error}"
60
+ bottle_text = f"\033[31m远程删除文件失败\033[0m: {message}"
61
+ write_log(bottle_text)
62
+ return False
63
+ else:
64
+ bottle_text = f"\033[31m远程删除文件失败\033[0m: 网络连接失败{err}"
65
+ write_log(bottle_text)
66
+ elif gVar.config["upload"]:
67
+ return False
68
+ else:
69
+ return True
7
70
 
8
71
 
9
72
  # 删除多余媒体文件模块
10
- def remove_file():
73
+ def remove_file(upload_url):
11
74
  channelid_youtube_ids = gVar.channelid_youtube_ids
12
-
13
75
  for output_dir, name in channelid_youtube_ids.items():
14
76
  for file_name in os.listdir(f"channel_audiovisual/{output_dir}"):
15
77
  if file_name not in gVar.all_youtube_content_ytid[output_dir]:
16
- os.remove(f"channel_audiovisual/{output_dir}/{file_name}")
17
- write_log(f"{name}|{file_name}抛弃文件已删除")
78
+ if judge_upload(upload_url, output_dir, file_name):
79
+ os.remove(f"channel_audiovisual/{output_dir}/{file_name}")
80
+ write_log(f"{name}|{file_name}抛弃文件已删除")
18
81
 
19
82
  channelid_bilibili_ids = gVar.channelid_bilibili_ids
20
83
  for output_dir, name in channelid_bilibili_ids.items():
21
84
  for file_name in os.listdir(f"channel_audiovisual/{output_dir}"):
22
85
  if file_name not in gVar.all_bilibili_content_bvid[output_dir]:
23
- os.remove(f"channel_audiovisual/{output_dir}/{file_name}")
24
- write_log(f"{name}|{file_name}抛弃文件已删除")
86
+ if judge_upload(upload_url, output_dir, file_name):
87
+ os.remove(f"channel_audiovisual/{output_dir}/{file_name}")
88
+ write_log(f"{name}|{file_name}抛弃文件已删除")
@@ -3,8 +3,8 @@
3
3
 
4
4
 
5
5
  # 查找位置模块
6
- def find_media_index(upload_original, target_media_id):
6
+ def find_media_index(upload_original, target_media_id, key_name="media_id"):
7
7
  for index, item in enumerate(upload_original):
8
- if item.get("media_id") == target_media_id:
8
+ if item.get(key_name) == target_media_id:
9
9
  return index # 返回找到的索引
10
10
  return -1
@@ -6,13 +6,14 @@ from podflow.basic.file_save import file_save
6
6
  from podflow.upload.find_media_index import find_media_index
7
7
 
8
8
 
9
- def store_users_info(username,filename):
9
+ def store_users_info(username, filename, channelid):
10
10
  index = find_media_index(gVar.upload_message, filename)
11
11
  if index == -1:
12
12
  gVar.upload_message.append(
13
13
  {
14
- "media_id": filename,
15
- "users": [username]
14
+ "mediaid": filename,
15
+ "users": [username],
16
+ "channelid": channelid,
16
17
  }
17
18
  )
18
19
  elif username not in gVar.upload_message[index]["users"]:
@@ -0,0 +1,54 @@
1
+ # podflow/upload/uploaded_remove.py
2
+ # coding: utf-8
3
+
4
+ import os
5
+ import time
6
+ from datetime import datetime, timedelta
7
+ from podflow import gVar
8
+ from podflow.basic.write_log import write_log
9
+ from podflow.basic.time_format import time_format
10
+ from podflow.basic.get_duration import get_duration
11
+ from podflow.upload.find_media_index import find_media_index
12
+
13
+
14
+ # 过滤和排序上传媒体模块
15
+ def filter_and_sort_media(media_list):
16
+ filtered_sorted = sorted(
17
+ (
18
+ item
19
+ for item in media_list
20
+ if item["upload"]
21
+ and not item["remove"]
22
+ ),
23
+ key=lambda x: x["media_time"],
24
+ )
25
+ return [
26
+ {"media_id": item["media_id"], "channel_id": item["channel_id"]}
27
+ for item in filtered_sorted
28
+ ]
29
+
30
+ # 上传已删除媒体模块
31
+ def uploaded_remove(upload_url):
32
+ if upload_url:
33
+ # 当前时间
34
+ now = datetime.now()
35
+ # 30天前的时间
36
+ one_month_ago = now - timedelta(days=30)
37
+ # 转换为时间戳(秒级)
38
+ timestamp = int(time.mktime(one_month_ago.timetuple()))
39
+ result = filter_and_sort_media(gVar.upload_original)
40
+ num = 0
41
+ for item in result:
42
+ if num < 10 and item["media_time"] < timestamp:
43
+ break
44
+ num += 1
45
+ output_dir = item["channel_id"]
46
+ file_name = item["media_id"]
47
+ index = find_media_index(gVar.upload_original, file_name)
48
+ if index != -1:
49
+ gVar.upload_original[index]["remove"] = True
50
+ file_path = f"channel_audiovisual/{output_dir}/{file_name}"
51
+ duration = time_format(get_duration(file_path))
52
+ gVar.upload_original[index]["duration"] = duration
53
+ os.remove(file_path)
54
+ write_log(f"{file_name}本地文件已删除")
podflow/youtube/check.py CHANGED
@@ -5,6 +5,7 @@ import os
5
5
  import yt_dlp
6
6
  from podflow.basic.write_log import write_log
7
7
  from podflow.basic.time_print import time_print
8
+ from podflow.message.fail_message_initialize import fail_message_initialize
8
9
 
9
10
 
10
11
  # yt-dlp校验cookie模块
@@ -43,6 +44,7 @@ def yt_dlp_check(file, url):
43
44
  return False
44
45
  except yt_dlp.utils.DownloadError as e:
45
46
  error_message = str(e).lower()
47
+ e = fail_message_initialize(e, "WL")
46
48
  if any(
47
49
  keyword in error_message
48
50
  for keyword in ["login required", "sign in", "private", "forbidden"]
@@ -52,6 +54,7 @@ def yt_dlp_check(file, url):
52
54
  time_print(f"cookie无效或网络异常\n{e}")
53
55
  return False
54
56
  except Exception as e:
57
+ e = fail_message_initialize(e, "WL")
55
58
  time_print(f"cookie发生未知错误\n{e}")
56
59
  return False
57
60
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: podflow
3
- Version: 20250706
3
+ Version: 20250820
4
4
  Summary: A podcast server that includes YouTube and BiliBili
5
5
  Home-page: https://github.com/gruel-zxz/podflow
6
6
  Author: gruel_zxz
@@ -14,7 +14,7 @@ Requires-Python: >=3.8
14
14
  Description-Content-Type: text/markdown
15
15
  Requires-Dist: astral>=3.2
16
16
  Requires-Dist: bottle>=0.13.2
17
- Requires-Dist: yt-dlp>=2025.6.30
17
+ Requires-Dist: yt-dlp>=2025.8.20
18
18
  Requires-Dist: chardet>=5.2.0
19
19
  Requires-Dist: cherrypy>=18.10.0
20
20
  Requires-Dist: pyqrcode>=1.2.1
@@ -1,10 +1,10 @@
1
- podflow/__init__.py,sha256=hpC5LyzhH6gYwn2DRHUBjQY4eHADFA5oIDztzhES-Ew,7695
1
+ podflow/__init__.py,sha256=AYe5nm0FXsiKyny-6De6cGbNh2wHcsN5lEHK-TU6QzI,7719
2
2
  podflow/download_and_build.py,sha256=x7S7N26B1G9Yn2yr7YthDJgKUwEKBLtHBLlaqpofLas,746
3
3
  podflow/ffmpeg_judge.py,sha256=wM49pPXOFwFAA_8TKHal5fV6ka9sAA87yGQMDOssvXo,1340
4
4
  podflow/main.py,sha256=7zWdpw80jqPaYu1Un1nPqaZoiAb7Dqg8zaF-cUioU4c,755
5
- podflow/main_podcast.py,sha256=gH2ERZjZpPqaUnTajwGxnjMyV7dlrXyM7WJFoFXH3i4,12990
5
+ podflow/main_podcast.py,sha256=C99Jgt_AnOW_8fBmbpZUwHg1AR3T8eDRV9uxzrnYnCM,13303
6
6
  podflow/main_upload.py,sha256=VOihPtjr37lxRNEr2cJp70DB6W5q7lHvpanApFKA9qE,2595
7
- podflow/parse_arguments.py,sha256=h3a7EaRZS04kNMFYbxTW9Ch29KgZ7dyS-yqEEt_etQI,2592
7
+ podflow/parse_arguments.py,sha256=8t5_r0sfGFB5cSDE4qENlFSNUdMBeQRXIWJz6a9o9d8,2769
8
8
  podflow/basic/__init__.py,sha256=CAfI6mVQtz7KKbAiTIZ9_IbvaTXeAqxR1U7ov9GDoDo,44
9
9
  podflow/basic/file_save.py,sha256=6vu4EkbsN4df5-ci6sJOgIOUEhh-WaRBOyMJ8rpouXo,1233
10
10
  podflow/basic/folder_build.py,sha256=5oHyfiDcSp2YITXQWIPwriBF9XuU3qs7wZQOWJHYJ1s,546
@@ -28,7 +28,7 @@ podflow/bilibili/login.py,sha256=oHnuOMvlKZUnJ1OBHuZpTEnG04pboWsueOx871lQWjc,114
28
28
  podflow/config/__init__.py,sha256=MzgAlkSdV-5MFC11r0QSf-GPxmsLbrMc5ROIrJafjag,45
29
29
  podflow/config/build_original.py,sha256=pG9gCkBBIWwU8QxZA06Br6JRfI4XUSIEiN8eqo1-v7k,1809
30
30
  podflow/config/channge_icon.py,sha256=W-EU_5_wg94_yioNofk8OTyRD2X7SY8HEjvjFmSEyh0,5653
31
- podflow/config/correct_channelid.py,sha256=4Z6B3cR5wQq5Vce4LPGVDqqUGc-fW54B46w7f_XEKYc,9252
31
+ podflow/config/correct_channelid.py,sha256=RQMzeABdW1qYDIz-q1VMPCLTR2D82qlTay0GsRxHsZA,9301
32
32
  podflow/config/correct_config.py,sha256=slC3DYiapyZ8JO6xjDvNWQ5DBOZRvO7FvAQm-2J5H08,4121
33
33
  podflow/config/get_channelid.py,sha256=WUtvUZ1Q_L9n94ZGGaOqu-9QC2HlYMx5oU4gOCZ4_Ew,626
34
34
  podflow/config/get_channelid_id.py,sha256=VjExb_AXPHtQ5v8SaZiehaiLNkNmfMoPcv5HyUar6GU,571
@@ -36,12 +36,12 @@ podflow/config/get_config.py,sha256=TpLbh5GoPO7csWUJ-di65XXnkN3LSnMXkFw85h5ndHs,
36
36
  podflow/download/__init__.py,sha256=1lATXiOAEx5oDUDR99mQRiTrQlQ9CQkJNAKpsLrnPCo,47
37
37
  podflow/download/convert_bytes.py,sha256=6Q3TcPGzCO2FlhOKWbp9RB_GPmfuyKY5suIyE9EJf6k,543
38
38
  podflow/download/delete_part.py,sha256=wjY6WulpUMjLx38on0kTLXj0gA04rIuKAdwFidZtWGU,679
39
- podflow/download/dl_aideo_video.py,sha256=HoiAYf1QpHG17U9y--ixKOT2aupeGA2vAleVvC1JoV8,10765
39
+ podflow/download/dl_aideo_video.py,sha256=p9Awb0Zde3cH1xEtGUDIhTCyBJAUiryKs1HN6RrjS2M,10860
40
40
  podflow/download/show_progress.py,sha256=y46chchUC9eZCg4ZdNMFnx_bXJQV_IUq15jVzZtLlJM,2248
41
41
  podflow/download/wait_animation.py,sha256=AUTvszXF89QA7XYjocFIauPKV7Qj8cFqry44teClaLQ,1314
42
42
  podflow/download/youtube_and_bilibili_download.py,sha256=VCEhz6pGXFWXusdbGWqkCzi4f4VsKQVn6sZz1pfGsns,1335
43
43
  podflow/httpfs/__init__.py,sha256=BxEXkufjcx-a0F7sDVXo65hmyANqCCbZUd6EH9i8T2c,45
44
- podflow/httpfs/app_bottle.py,sha256=RunI3OVcYelgTk8lkYRJwwF8O2RxHOurih7Xn1RBPxo,28885
44
+ podflow/httpfs/app_bottle.py,sha256=j7G9iWSxJ_F1y-FHwmrUP-Gg0A2Bg76ptO2yEATIIcU,36941
45
45
  podflow/httpfs/browser.py,sha256=BJ4Xkfiki_tDr0Sc9RqAcEfIVpkAZ3RFOwo0aMHlY3U,197
46
46
  podflow/httpfs/download_bar.py,sha256=0n3HATEO3pdsIpx-E_IZG9OlXa6u-9SeBCoZVgUutyc,965
47
47
  podflow/httpfs/get_channelid.py,sha256=gcwy4IVHBWNQz7qPCpjwiAklGFLRGzvM33-UZz7oFvo,2296
@@ -57,30 +57,30 @@ podflow/message/__init__.py,sha256=pZkcrrtkdtxgMriEHBZ0_rptKaQrQeMPJvPSaoI1Awo,4
57
57
  podflow/message/backup_zip_save.py,sha256=c81jnx8IxHjTcO7G0OUAppawpBIPxa9wgkj9AQhqeJc,1864
58
58
  podflow/message/create_main_rss.py,sha256=kW2QvJhxl2ZcqGV-M-OztlOaQ27ODuQxADeP8poymBQ,3118
59
59
  podflow/message/display_qrcode_and_url.py,sha256=VqmRkDYYG03VihfW4SAU49HJVmfqWbLTgMxqCaREeCo,1037
60
- podflow/message/fail_message_initialize.py,sha256=0Itu-7bJfdPI3D508TN4Tr5MgDA_Z4ZxmPyubXrkNEQ,7864
60
+ podflow/message/fail_message_initialize.py,sha256=J8jhKa1PVHKJ6TUUNmakBjD2dd6nMHs8sVkiBf9oXmM,8040
61
61
  podflow/message/format_time.py,sha256=gveNh4FGeS3ytwDyYB-h12d1_Km6XoX7WSPcFmDfCBk,909
62
62
  podflow/message/get_media_name.py,sha256=5ULPQOQCZ2-lxdkILwlBP-ItzdFEgvEAKxeLtplACbQ,861
63
63
  podflow/message/get_original_rss.py,sha256=Bzy-Fs1vZEjwvQq6D6xp-2IUidliSyaL1P4WtkLJaRg,2450
64
64
  podflow/message/get_video_format.py,sha256=bPetnFwQlhIO9do_nq3B4AwHQRxFn6SDYvFItjwEx0o,5324
65
65
  podflow/message/get_video_format_multithread.py,sha256=tbgQDMpcntulrbmXBbKL4iVr4t_9evGsuMOIZLoLebI,1615
66
66
  podflow/message/get_youtube_and_bilibili_video_format.py,sha256=FACn7IYWTzMHvgNcv8DiUs9CrfoRvLz_9EBjrCxT_9A,4911
67
- podflow/message/media_format.py,sha256=Q4WoML4UqL0Ry-QN8DHFJqOQ2tXcFN6u5hmhdSLdP1g,7346
67
+ podflow/message/media_format.py,sha256=Phf8cpkjLMWk6iGxqHPJPwqN0KlveQ9bWwGEahIwaRM,7445
68
68
  podflow/message/optimize_download.py,sha256=-6YritASap8Dp0HqDuvn-kyeamAfw8UjnR-_5S0DbYw,1034
69
69
  podflow/message/original_rss_fail_print.py,sha256=7HM5Gwi3GqBIg2dtTTDlN_FRgZZjYv6ejizS3tDiePE,502
70
70
  podflow/message/rss_create_hash.py,sha256=M5OS9KcQ4mIxLes9ij4oNji-4VKgi56bg0Shv5nCIQ4,638
71
71
  podflow/message/save_rss.py,sha256=x-yRwT7bAUt2k-R9DWa5uToqpcOdaXkPW_4VH5Gbeo4,3193
72
72
  podflow/message/title_correction.py,sha256=Zieulj2wQY_o4r3u5ZRsDQP5y8KuZHrL_l8tnM96k6g,915
73
- podflow/message/update_information_display.py,sha256=Zn-Xhps4PKf7NbgQrT-qTwhP096RV-48OEncK_vuUe0,3061
73
+ podflow/message/update_information_display.py,sha256=w3sp-ZODL3gbJKFnMgUoRc4RYSMGIjWOyqsryhXfc-U,4462
74
74
  podflow/message/update_youtube_bilibili_rss.py,sha256=igt41NkGuikPzi0gqo-Hc29hMil65kHV19J4W-oGQfU,5962
75
75
  podflow/message/want_retry.py,sha256=3MtlAG4BZ2oznn0X5zYzAl2S0XzZkXhnN_LHVPcWZjA,699
76
- podflow/message/xml_item.py,sha256=jCB93aOoIDK6EaAFrZg5gd6mBMv7fP9uX-Z5eiTSyxg,3127
76
+ podflow/message/xml_item.py,sha256=5kJHk3chnW04m2HLAXw74Twq1q_VqWh_4tEkmX1R2bA,4096
77
77
  podflow/message/xml_original_item.py,sha256=mlORI0p6aSLP6PWIAuvI4uVN0JbxUDZX5-U52ltn9E4,4048
78
78
  podflow/message/xml_rss.py,sha256=ogCteSUXyJJXLhOE7-ZBcRdWYzrRr2Qykjt3oppRpC4,1679
79
79
  podflow/netscape/__init__.py,sha256=SUw_BtbV3moA324UdxRECkPLv1xHkjio8r_5JTkVfxI,47
80
80
  podflow/netscape/bulid_netscape.py,sha256=wmUPlDGF8G456GGyajU_6Ak5WJzsqsq4bZgPjCSTGhI,2279
81
81
  podflow/remove/__init__.py,sha256=x1pMfpIyE6xUrmIOkdl43mbvKLwndGo5pIoOBXhJsP4,45
82
- podflow/remove/remove_dir.py,sha256=zqgf47UgxiGiLipb1FeoWKzrSHSxcHaEuu0261bqR1s,1105
83
- podflow/remove/remove_file.py,sha256=ZusvZsBQX_yRdLuboD7bZKiOX4z4Rxg66zZK9KDWHwE,1006
82
+ podflow/remove/remove_dir.py,sha256=g68PW5xuBZWK3gV_33KVk10f1xCHiR9Wx6ZB0BeDbtw,3267
83
+ podflow/remove/remove_file.py,sha256=hM959EZdQD6A4XsObSug-QpsmGoZq4o6t7osQO-gzho,3271
84
84
  podflow/remove/remove_flush.py,sha256=HWCe5SjNJ3VXaXbgFtqGdTXeJ1R2vv5qllNilB-G0_g,1851
85
85
  podflow/repair/__init__.py,sha256=Gpc1i6xiSLodKjjmzH66c_Y1z0HQ9E9CS3p95FRnVFM,45
86
86
  podflow/repair/reverse_log.py,sha256=Wc_vAH0WB-z1fNdWx7FYaVH4caRPtot7tDwDwFhmpz4,1106
@@ -93,23 +93,24 @@ podflow/templates/js/qrcode.min.js,sha256=xUHvBjJ4hahBW8qN9gceFBibSFUzbe9PNttUve
93
93
  podflow/upload/__init__.py,sha256=AtOSXDrE5EjUe3z-iBd1NTDaH8n_X9qA5WXdBLkONjA,45
94
94
  podflow/upload/add_upload.py,sha256=LloIucGJSXARhxiWerAbJVW-FpTkTUxvYCAKPbl93Ew,1536
95
95
  podflow/upload/build_hash.py,sha256=9opa3xLd7nJbGGX5xa3uuKPS6dxlbkAb87ZdEiUxmxI,473
96
- podflow/upload/find_media_index.py,sha256=HAtKkp0e-mjqjyLv1jgxXcNlNYvz-RD_kWvGkFdLygY,299
96
+ podflow/upload/find_media_index.py,sha256=kJwk_iNOPt__7lyZiGBhsTM5-cK7Am7iKDUIjsXqa2Q,318
97
97
  podflow/upload/get_upload_original.py,sha256=TEDnRutumm2FZNIesPJIlExHyKWpfB3ZAHb3sZt7V6A,4312
98
98
  podflow/upload/linked_client.py,sha256=NdSi5uh0TbZUhOHbA_mkHo4aIz1XNKoSXHhT4rMRUpc,5288
99
99
  podflow/upload/linked_server.py,sha256=h-qSx13fP8_Ny2IKW3wCNPwqRqW6-Iz1pqxD9ga9-dM,2308
100
100
  podflow/upload/login.py,sha256=WwQoPCr1oSGZh6wEsEuUH5E8-PEYS_k36b-Qfe00rjY,4282
101
- podflow/upload/store_users_info.py,sha256=nnAUgPUStYR48ma1B8mtY9SFOr3rOGvnlCuKSNdee3M,679
101
+ podflow/upload/store_users_info.py,sha256=VO8eSUXU8steKW0QpUWJlSHRTvfHghSLZA_qIc67AlY,731
102
102
  podflow/upload/time_key.py,sha256=6jZ3cxUjzj_umYDwH27R0YNZlLXxfhNp-CqV_K22wlo,967
103
103
  podflow/upload/update_upload.py,sha256=tolV9WMRFg9KqdGSSC37REBy4N_f-d3GvCihciMlOlg,3456
104
104
  podflow/upload/upload_files.py,sha256=vI0sSjCxUILlu0K9doMLJpmR7KrqhMRsCJmcWrCKlA0,5564
105
105
  podflow/upload/upload_server.py,sha256=BFq3QrWE7U97LbC4EQiDhQXbLapEc4R00eRDBH12E6A,565
106
+ podflow/upload/uploaded_remove.py,sha256=qlQLEz29c4Ta5qtEj12gBW77eDvoUCS-G9zvNWN4Mz4,1844
106
107
  podflow/youtube/__init__.py,sha256=pgXod8gq0IijZxIkPSwgAOcb9JI5rd1mqMomoR7bcJ4,46
107
108
  podflow/youtube/build.py,sha256=j6SVq3HFFGlNNqRrHfnBIThdzsH88PFmwLnejosif1U,12311
108
- podflow/youtube/check.py,sha256=VUu2h7o4p2yZ7sFLtHRmxojURbgwqQUgYVNPeyhZ0wc,2009
109
+ podflow/youtube/check.py,sha256=UTk5GFkhc_3l_3GfrJDZuAyJnG9Esc_v01x7EHm-aKM,2175
109
110
  podflow/youtube/get.py,sha256=oO32GjTFvUgP5AfFX5AlIuXU2UT6QtOUOXWLFzi8XtI,17157
110
111
  podflow/youtube/login.py,sha256=pDJNgCJdLOKV02yGot_5JJ-962JACbWZ-GlISmDfgIk,2742
111
- podflow-20250706.dist-info/METADATA,sha256=euTyT4mYua63OHU-BSgOkAn-XFTNASWnVrN2dIIOmNo,14195
112
- podflow-20250706.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
113
- podflow-20250706.dist-info/entry_points.txt,sha256=mn7hD_c_dmpKe3XU0KNekheBvD01LhlJ9htY-Df0j2A,131
114
- podflow-20250706.dist-info/top_level.txt,sha256=fUujhhz-RrMI8aGvi-3Ey5y7FQnpOOgoFw9OWM3yLCU,8
115
- podflow-20250706.dist-info/RECORD,,
112
+ podflow-20250820.dist-info/METADATA,sha256=Ns2WspXUoeL-TagXtco_NKCkfb-kUhHWdWhuhzOU3Mc,14195
113
+ podflow-20250820.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
114
+ podflow-20250820.dist-info/entry_points.txt,sha256=mn7hD_c_dmpKe3XU0KNekheBvD01LhlJ9htY-Df0j2A,131
115
+ podflow-20250820.dist-info/top_level.txt,sha256=fUujhhz-RrMI8aGvi-3Ey5y7FQnpOOgoFw9OWM3yLCU,8
116
+ podflow-20250820.dist-info/RECORD,,