Skip to content

Commit 3283a65

Browse files
committed
fix(ffmpeg): 重构调用 ffmpeg 下载直播功能
修复 MacOS 下载直播报错的问题
1 parent eeaa69f commit 3283a65

5 files changed

Lines changed: 120 additions & 65 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ demo()
218218
<li>本项目不会支持付费作品下载,请勿反馈任何关于付费作品下载的问题</li>
219219
<li>Windows 系统需要以管理员身份运行程序才能读取 Chromium、Chrome、Edge 浏览器 Cookie</li>
220220
<li>本项目并未针对程序多开的情况进行优化,如需程序多开,请复制整个项目的文件夹,避免出现预期之外的问题</li>
221+
<li>程序运行过程中,如需终止程序或 <code>ffmpeg</code>,请按下 <code>Ctrl + C</code> 终止运行,不要直接点击终端窗口的关闭按钮</li>
221222
</ul>
222223
<hr>
223224

README_EN.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ demo()
226226
<li>This project does not support downloading paid works. Please do not report any issues related to downloading paid works.</li>
227227
<li>On Windows systems, the program needs to be run as an administrator to read Cookies from Chromium, Chrome, and Edge browsers.</li>
228228
<li>This project has not been optimized for running multiple instances of the program. If you need to run multiple instances, please copy the entire project folder to avoid unexpected issues.</li>
229+
<li>During program execution, if you need to terminate the program or <code>ffmpeg</code>, please press <code>Ctrl + C</code> to stop the process. Do not click the close button on the terminal window directly.</li>
229230
</ul>
230231
<hr>
231232

docs/Release_Notes.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
**更新内容:**
22

33
1. 优化 API 模式搜索接口的响应提示
4-
2. 搜索数据为空时不再保存至文件
4+
2. 修复 MacOS 下载直播报错的问题
5+
3. 搜索数据为空时不再保存至文件
6+
4. 重构调用 ffmpeg 下载直播功能

docs/TikTokDownloader文档.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,7 @@ built with gcc 14.2.0 (crosstool-NG 1.27.0.18_7458341)
786786
<li>程序询问是否下载直播时,输入直播清晰度或者对应序号即可下载,例如:下载最高清晰度输入 <code>FULL_HD1</code> 或者 <code>1</code> 均可。</li>
787787
<li><del>程序调用内置下载器下载的直播文件,视频时长会显示为直播总时长,实际视频内容从下载时间开始,靠后部分的片段无法播放。</del></li>
788788
<li>直播视频会下载至 <code>root</code> 参数路径下的 <code>Live</code> 文件夹。</li>
789-
<li>经测试,强行终止程序或 <code>ffmpeg</code> 并不会导致已下载文件丢失或损坏,但无法继续下载。</li>
789+
<li>按下 <code>Ctrl + C</code> 终止程序或 <code>ffmpeg</code> 并不会导致已下载文件丢失或损坏,但无法继续下载。</li>
790790
</ul>
791791
<h3>采集作品评论数据(抖音)</h3>
792792
<p><strong>评论回复采集功能暂不开放!</strong></p>
@@ -1074,7 +1074,7 @@ built with gcc 14.2.0 (crosstool-NG 1.27.0.18_7458341)
10741074
<li>程序询问是否下载直播时,输入直播清晰度或者对应序号即可下载,例如:下载最高清晰度输入 <code>FULL_HD1</code> 或者 <code>1</code> 均可。</li>
10751075
<li><del>程序调用内置下载器下载的直播文件,视频时长会显示为直播总时长,实际视频内容从下载时间开始,靠后部分的片段无法播放。</del></li>
10761076
<li>直播视频会下载至 <code>root</code> 参数路径下的 <code>Live</code> 文件夹。</li>
1077-
<li>经测试,强行终止程序或 <code>ffmpeg</code> 并不会导致已下载文件丢失或损坏,但无法继续下载。</li>
1077+
<li>按下 <code>Ctrl + C</code> 终止程序或 <code>ffmpeg</code> 并不会导致已下载文件丢失或损坏,但无法继续下载。</li>
10781078
</ul>
10791079
<h3>批量下载视频原画(TikTok)</h3>
10801080
<p><strong>注意:本功能为实验性功能,依赖第三方 API 服务,可能不稳定或存在限制!</strong></p>

src/module/ffmpeg.py

Lines changed: 113 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,85 @@
11
from pathlib import Path
2-
from platform import system
32
from shutil import which
4-
from subprocess import Popen
3+
from platform import system
4+
from subprocess import Popen, run
5+
from textwrap import dedent
56

67
__all__ = ["FFMPEG"]
78

89

910
class FFMPEG:
11+
SYSTEM = system()
12+
13+
# 常见终端及其执行模板
14+
linux_terminal_templates = {
15+
# GNOME Terminal (Ubuntu)
16+
"gnome-terminal": ["gnome-terminal", "--", "bash", "-c", "{cmd}; exec bash"],
17+
# Deepin Terminal
18+
"deepin-terminal": ["deepin-terminal", "--", "bash", "-c", "{cmd}; exec bash"],
19+
# XFCE4 Terminal (MX Linux 默认)
20+
"xfce4-terminal": [
21+
"xfce4-terminal",
22+
"--hold",
23+
"-e",
24+
'bash -c "{cmd}; exec bash"',
25+
],
26+
# Konsole (KDE)
27+
"konsole": ["konsole", "-e", "bash", "-i", "-c", "{cmd}; bash"],
28+
# Terminator
29+
"terminator": ["terminator", "-x", "bash", "-c", "{cmd}; exec bash"],
30+
}
31+
1032
def __init__(self, path: str):
1133
self.path = self.__check_ffmpeg_path(Path(path))
12-
self.state = bool(self.path)
13-
self.command, self.shell = self._check_system_type()
34+
self.support = {
35+
"Darwin": self.generate_command_darwin,
36+
"Linux": self.generate_command_linux,
37+
"Windows": self.generate_command_windows,
38+
}
39+
self.run_command = self.support.get(self.SYSTEM, None)
40+
self.state = bool(self.path) if self.run_command else False
1441

1542
@staticmethod
16-
def _check_system_type():
17-
if (s := system()) == "Darwin": # macOS
18-
return ["open", "-a", "Terminal"], False
19-
elif s == "Windows": # Windows
20-
return ["start", "cmd", "/k"], True
21-
elif s == "Linux": # Linux
22-
return ["x-terminal-emulator"], False
43+
def generate_command_darwin(command: list) -> None:
44+
script = dedent(f"""
45+
tell application "Terminal"
46+
do script "{" ".join(command).replace('"', '\\"')}"
47+
activate
48+
end tell
49+
""")
50+
Popen(["osascript", "-e", script])
51+
52+
@staticmethod
53+
def generate_command_windows(command: list) -> None:
54+
Popen(
55+
" ".join(
56+
[
57+
"start",
58+
"cmd",
59+
"/k",
60+
]
61+
+ command
62+
),
63+
shell=True,
64+
)
65+
66+
@classmethod
67+
def generate_command_linux(cls, command: list) -> None:
68+
# TODO: Linux 系统尚未测试
69+
command = " ".join(command)
70+
print("ffmpeg command:", command)
71+
for term, template in cls.linux_terminal_templates.items():
72+
if which(term):
73+
# 填充命令并执行
74+
filled = [
75+
part.format(cmd=command) if "{cmd}" in part else part
76+
for part in template
77+
]
78+
run(
79+
filled,
80+
)
2381

2482
def __check_ffmpeg_path(self, path: Path):
25-
# return None # 调试使用
2683
return self.__check_system_ffmpeg() or self.__check_system_ffmpeg(path)
2784

2885
def download(self, data: list[tuple], proxy, user_agent):
@@ -33,66 +90,60 @@ def download(self, data: list[tuple], proxy, user_agent):
3390
proxy,
3491
user_agent,
3592
)
36-
Popen(command, shell=self.shell)
93+
self.run_command(command)
3794

3895
def __generate_command(
3996
self,
4097
url,
4198
file,
4299
proxy,
43100
user_agent,
44-
) -> str:
45-
command = self.command.copy()
46-
command.extend(
47-
[
48-
self.path,
49-
"-hide_banner",
50-
"-rw_timeout",
51-
f"{30 * 1000 * 1000}",
52-
"-loglevel",
53-
"info",
54-
"-protocol_whitelist",
55-
"rtmp,crypto,file,http,https,tcp,tls,udp,rtp,httpproxy",
56-
"-analyzeduration",
57-
f"{10 * 1000 * 1000}",
58-
"-probesize",
59-
f"{10 * 1000 * 1000}",
60-
"-fflags",
61-
"+discardcorrupt",
62-
"-user_agent",
63-
f'"{user_agent}"',
64-
"-i",
65-
f'"{url}"',
66-
"-bufsize",
67-
"10240k",
68-
"-map",
69-
"0",
70-
"-c:v",
71-
"copy",
72-
"-c:a",
73-
"copy",
74-
"-sn",
75-
"-dn",
76-
"-reconnect_delay_max",
77-
"60",
78-
"-reconnect_streamed",
79-
"-reconnect_at_eof",
80-
"-max_muxing_queue_size",
81-
"128",
82-
"-correct_ts_overflow",
83-
"1",
84-
"-f",
85-
"mp4",
86-
]
87-
)
101+
) -> list:
102+
command = [
103+
self.path,
104+
"-hide_banner",
105+
"-rw_timeout",
106+
f"{30 * 1000 * 1000}",
107+
"-loglevel",
108+
"info",
109+
"-protocol_whitelist",
110+
"rtmp,crypto,file,http,https,tcp,tls,udp,rtp,httpproxy",
111+
"-analyzeduration",
112+
f"{10 * 1000 * 1000}",
113+
"-probesize",
114+
f"{10 * 1000 * 1000}",
115+
"-fflags",
116+
"+discardcorrupt",
117+
"-user_agent",
118+
f'"{user_agent}"',
119+
"-i",
120+
f'"{url}"',
121+
"-bufsize",
122+
"10240k",
123+
"-map",
124+
"0",
125+
"-c:v",
126+
"copy",
127+
"-c:a",
128+
"copy",
129+
"-sn",
130+
"-dn",
131+
"-reconnect_delay_max",
132+
"60",
133+
"-reconnect_streamed",
134+
"-reconnect_at_eof",
135+
"-max_muxing_queue_size",
136+
"128",
137+
"-correct_ts_overflow",
138+
"1",
139+
"-f",
140+
"mp4",
141+
]
88142
if proxy:
89-
for insert_index, item in enumerate(
90-
("-http_proxy", proxy), start=len(self.command) + 2
91-
):
143+
for insert_index, item in enumerate(("-http_proxy", proxy), start=2):
92144
command.insert(insert_index, item)
93145
command.append(f'"{file}"')
94-
# print(" ".join(command)) # 调试使用
95-
return " ".join(command)
146+
return command
96147

97148
@staticmethod
98149
def __check_system_ffmpeg(path: Path = None):

0 commit comments

Comments
 (0)