Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion videocaptioner/core/asr/faster_whisper.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
GPUtil = None # type: ignore[assignment]

from ..utils.logger import setup_logger
from ..utils.platform_utils import get_subprocess_kwargs
from ..utils.subprocess_helper import StreamReader
from .asr_data import ASRData, ASRDataSeg
from .base import BaseASR
Expand Down Expand Up @@ -258,7 +259,7 @@ def _default_callback(x, y):
text=True,
encoding="utf-8",
errors="ignore",
creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0,
**get_subprocess_kwargs(), # Windows 隐藏控制台窗口
)

# 使用 StreamReader 处理输出
Expand Down
4 changes: 3 additions & 1 deletion videocaptioner/core/asr/whisper_cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from ...config import MODEL_PATH
from ..utils.logger import setup_logger
from ..utils.platform_utils import get_subprocess_kwargs
from ..utils.subprocess_helper import StreamReader
from .asr_data import ASRData, ASRDataSeg
from .base import BaseASR
Expand Down Expand Up @@ -155,6 +156,7 @@ def _default_callback(_progress: int, _message: str) -> None:
text=True,
encoding="utf-8",
bufsize=1,
**get_subprocess_kwargs(), # Windows 隐藏控制台窗口
)

logger.debug(f"Whisper.cpp process started, PID: {self.process.pid}")
Expand Down Expand Up @@ -247,7 +249,7 @@ def get_audio_duration(self, filepath: str) -> int:
text=True,
encoding="utf-8",
errors="replace",
creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0,
**get_subprocess_kwargs(), # Windows 隐藏控制台窗口
)
info = result.stderr
if duration_match := re.search(r"Duration: (\d+):(\d+):(\d+\.\d+)", info):
Expand Down
19 changes: 5 additions & 14 deletions videocaptioner/core/subtitle/ass_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from videocaptioner.config import CACHE_PATH, FONTS_PATH, RESOURCE_PATH
from videocaptioner.core.entities import SubtitleLayoutEnum
from videocaptioner.core.utils.logger import setup_logger
from videocaptioner.core.utils.platform_utils import get_subprocess_kwargs

from .ass_utils import auto_wrap_ass_file

Expand Down Expand Up @@ -172,11 +173,7 @@ def render_ass_preview(
str(default_bg),
],
capture_output=True,
creationflags=(
getattr(subprocess, "CREATE_NO_WINDOW", 0)
if os.name == "nt"
else 0
),
**get_subprocess_kwargs(), # Windows 隐藏控制台窗口
)
bg_path_obj = default_bg

Expand Down Expand Up @@ -205,9 +202,7 @@ def render_ass_preview(
result = subprocess.run(
cmd,
capture_output=True,
creationflags=(
getattr(subprocess, "CREATE_NO_WINDOW", 0) if os.name == "nt" else 0
),
**get_subprocess_kwargs(), # Windows 隐藏控制台窗口
)

if result.returncode != 0:
Expand All @@ -228,9 +223,7 @@ def _get_video_resolution(video_path: str) -> Tuple[int, int]:
["ffmpeg", "-i", video_path],
capture_output=True,
text=True,
creationflags=(
getattr(subprocess, "CREATE_NO_WINDOW", 0) if os.name == "nt" else 0
),
**get_subprocess_kwargs(), # Windows 隐藏控制台窗口
)

# 从 ffmpeg 输出中解析分辨率
Expand Down Expand Up @@ -350,9 +343,7 @@ def render_ass_video(
text=True,
encoding="utf-8",
errors="replace",
creationflags=(
getattr(subprocess, "CREATE_NO_WINDOW", 0) if os.name == "nt" else 0
),
**get_subprocess_kwargs(), # Windows 隐藏控制台窗口
)

# 实时Reading输出并调用回调
Expand Down
7 changes: 3 additions & 4 deletions videocaptioner/core/subtitle/rounded_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from videocaptioner.core.entities import SubtitleLayoutEnum
from videocaptioner.core.utils.logger import setup_logger
from videocaptioner.core.utils.platform_utils import get_subprocess_kwargs

from .font_utils import FontType, get_font
from .styles import RoundedBgStyle
Expand All @@ -31,7 +32,7 @@ def _get_video_info(video_path: str) -> Tuple[int, int, float]:
text=True,
encoding="utf-8",
errors="replace",
creationflags=(getattr(subprocess, "CREATE_NO_WINDOW", 0) if os.name == "nt" else 0),
**get_subprocess_kwargs(), # Windows 隐藏控制台窗口
)

# 解析分辨率
Expand Down Expand Up @@ -442,9 +443,7 @@ def render_rounded_video(
text=True,
encoding="utf-8",
errors="replace",
creationflags=(
getattr(subprocess, "CREATE_NO_WINDOW", 0) if os.name == "nt" else 0
),
**get_subprocess_kwargs(), # Windows 隐藏控制台窗口
)

if result.returncode != 0:
Expand Down
24 changes: 22 additions & 2 deletions videocaptioner/core/utils/platform_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,36 @@ def get_subprocess_kwargs():
"""
获取跨平台的subprocess参数

在 Windows GUI 应用中,子进程(如 ffmpeg、ASR 解码器等)启动时会弹出控制台窗口,
这会干扰用户体验。本函数返回 Windows 下用于隐藏控制台窗口的参数:
- creationflags = CREATE_NO_WINDOW:最常用的隐藏方式
- startupinfo.wShowWindow = SW_HIDE:备用方案,确保窗口完全不可见

非 Windows 系统返回空字典,不影响正常行为。

Usage:
subprocess.run(cmd, **get_subprocess_kwargs())
subprocess.Popen(cmd, **get_subprocess_kwargs())

Returns:
dict: subprocess参数字典
dict: subprocess参数字典,Windows 下包含 creationflags/startupinfo,其他系统为空
"""
kwargs = {}

# 仅在Windows上添加CREATE_NO_WINDOW标志
# 仅在Windows上添加静默启动参数
if platform.system() == "Windows":
if hasattr(subprocess, "CREATE_NO_WINDOW"):
kwargs["creationflags"] = getattr(subprocess, "CREATE_NO_WINDOW", 0)

if hasattr(subprocess, "STARTUPINFO") and hasattr(
subprocess, "STARTF_USESHOWWINDOW"
):
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
if hasattr(subprocess, "SW_HIDE"):
startupinfo.wShowWindow = subprocess.SW_HIDE
kwargs["startupinfo"] = startupinfo

return kwargs


Expand Down
33 changes: 8 additions & 25 deletions videocaptioner/core/utils/video_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from ..subtitle.ass_utils import auto_wrap_ass_file
from ..subtitle.rounded_renderer import render_rounded_video
from ..utils.logger import setup_logger
from ..utils.platform_utils import get_subprocess_kwargs

if TYPE_CHECKING:
from videocaptioner.core.asr.asr_data import ASRData
Expand Down Expand Up @@ -104,9 +105,7 @@ def video2audio(input_file: str, output: str = "", audio_track_index: int = 0) -
check=True,
encoding="utf-8",
errors="replace",
creationflags=(
getattr(subprocess, "CREATE_NO_WINDOW", 0) if os.name == "nt" else 0
),
**get_subprocess_kwargs(), # Windows 隐藏控制台窗口
)
if result.returncode == 0 and Path(output).is_file():
logger.debug("Audio conversion complete")
Expand Down Expand Up @@ -136,9 +135,7 @@ def check_cuda_available() -> bool:
["ffmpeg", "-hwaccels"],
capture_output=True,
text=True,
creationflags=(
getattr(subprocess, "CREATE_NO_WINDOW", 0) if os.name == "nt" else 0
),
**get_subprocess_kwargs(), # Windows 隐藏控制台窗口
)
if "cuda" not in result.stdout.lower():
logger.debug("CUDA not in FFmpeg hwaccels list")
Expand All @@ -149,9 +146,7 @@ def check_cuda_available() -> bool:
["ffmpeg", "-hide_banner", "-init_hw_device", "cuda"],
capture_output=True,
text=True,
creationflags=(
getattr(subprocess, "CREATE_NO_WINDOW", 0) if os.name == "nt" else 0
),
**get_subprocess_kwargs(), # Windows 隐藏控制台窗口
)

# 如果stderr中包含"Cannot load cuda" 或 "Failed to load"等Error output,说明CUDA不可用
Expand Down Expand Up @@ -232,11 +227,7 @@ def add_subtitles(
text=True,
encoding="utf-8",
errors="replace",
creationflags=(
getattr(subprocess, "CREATE_NO_WINDOW", 0)
if os.name == "nt"
else 0
),
**get_subprocess_kwargs(), # Windows 隐藏控制台窗口
)
logger.debug("Soft subtitle added")
except subprocess.CalledProcessError as e:
Expand Down Expand Up @@ -301,11 +292,7 @@ def add_subtitles(
text=True,
encoding="utf-8",
errors="replace",
creationflags=(
getattr(subprocess, "CREATE_NO_WINDOW", 0)
if os.name == "nt"
else 0
),
**get_subprocess_kwargs(), # Windows 隐藏控制台窗口
)

# 实时Reading输出并调用回调函数
Expand Down Expand Up @@ -390,9 +377,7 @@ def get_video_info(
text=True,
encoding="utf-8",
errors="replace",
creationflags=(
getattr(subprocess, "CREATE_NO_WINDOW", 0) if os.name == "nt" else 0
),
**get_subprocess_kwargs(), # Windows 隐藏控制台窗口
)
info = result.stderr

Expand Down Expand Up @@ -514,9 +499,7 @@ def _extract_thumbnail(video_path: str, seek_time: float, thumbnail_path: str) -
text=True,
encoding="utf-8",
errors="replace",
creationflags=(
getattr(subprocess, "CREATE_NO_WINDOW", 0) if os.name == "nt" else 0
),
**get_subprocess_kwargs(), # Windows 隐藏控制台窗口
)
return result.returncode == 0

Expand Down
4 changes: 2 additions & 2 deletions videocaptioner/ui/components/FasterWhisperSettingWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
TranscribeLanguageEnum,
VadMethodEnum,
)
from videocaptioner.core.utils.platform_utils import open_folder
from videocaptioner.core.utils.platform_utils import get_subprocess_kwargs, open_folder
from videocaptioner.ui.common.config import cfg
from videocaptioner.ui.components.LineEditSettingCard import LineEditSettingCard
from videocaptioner.ui.components.SpinBoxSettingCard import DoubleSpinBoxSettingCard
Expand Down Expand Up @@ -166,7 +166,7 @@ def run(self):
subprocess.run(
["7z", "x", self.zip_file, f"-o{self.extract_path}", "-y"],
check=True,
creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0,
**get_subprocess_kwargs(), # Windows 隐藏控制台窗口
)
# 删除压缩包
os.remove(self.zip_file)
Expand Down
15 changes: 4 additions & 11 deletions videocaptioner/ui/view/batch_process_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ def __init__(self, parent=None):
super().__init__(parent=parent)
self.setObjectName("batchProcessInterface")
self.setWindowTitle(self.tr("批量处理"))
self.setAcceptDrops(True)

# 由 MainWindow 统一处理拖放,避免子界面抢占拖放目标
self.setAcceptDrops(False)

self.batch_thread = BatchProcessThread()

self.init_ui()
Expand Down Expand Up @@ -163,16 +166,6 @@ def on_add_file_clicked(self):
if files:
self.add_files(files)

def dragEnterEvent(self, event):
if event.mimeData().hasUrls():
event.accept()
else:
event.ignore()

def dropEvent(self, event):
files = [url.toLocalFile() for url in event.mimeData().urls()]
self.add_files(files)

def add_files(self, file_paths):
task_type = BatchTaskType(self.task_type_combo.currentText())

Expand Down
42 changes: 42 additions & 0 deletions videocaptioner/ui/view/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ def __init__(self):
# 注册退出处理, 清理进程
atexit.register(self.stop)

# 启用拖放支持
self.setAcceptDrops(True)

def initNavigation(self):
"""初始化导航栏"""
# 添加导航项
Expand Down Expand Up @@ -119,6 +122,45 @@ def initWindow(self):
self.show()
QApplication.processEvents()

def _get_drop_target(self):
"""获取当前可接收拖放文件的子界面"""
current = self.stackedWidget.currentWidget()
if not current or not hasattr(current, "add_files") or not current.isEnabled():
return None
return current

def dragEnterEvent(self, event):
"""窗口拖放进入事件,仅在当前页面可处理时接受"""
if not event.mimeData().hasUrls():
event.ignore()
return

current = self._get_drop_target()
if current is None:
event.ignore()
return

files = [url.toLocalFile() for url in event.mimeData().urls() if url.isLocalFile()]
if files:
event.acceptProposedAction()
else:
event.ignore()

def dropEvent(self, event):
"""窗口拖放放下事件,转发文件给当前子界面处理"""
current = self._get_drop_target()
if current is None:
event.ignore()
return

files = [url.toLocalFile() for url in event.mimeData().urls() if url.isLocalFile()]
if not files:
event.ignore()
return

current.add_files(files)
event.acceptProposedAction()

def onGithubDialog(self):
"""打开GitHub"""
w = MessageBox(
Expand Down