Add Chinese locale, fix audio device duplicates, remove display lock restriction

- Add Simplified Chinese (中文) locale with all 906 translation keys
- Fix duplicate audio devices: WASAPI two-pass enumerate avoids double-listing
  loopback endpoints; cross-engine dedup by (name, is_loopback) prefers
  higher-priority engine
- Remove redundant display lock check from capture template, picture source,
  and postprocessing template test endpoints — screen capture is read-only
  and concurrent access is safe

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 14:34:24 +03:00
parent 147ef3b4eb
commit a0c9cb0039
9 changed files with 977 additions and 86 deletions

View File

@@ -253,13 +253,27 @@ class AudioCaptureManager:
"""List available audio devices from all registered engines.
Returns list of dicts with device info, each tagged with engine_type.
Deduplicates by (name, is_loopback), keeping the entry from the
highest-priority engine.
"""
# Collect from all engines, sorted by descending priority
engines = [
(engine_class.ENGINE_PRIORITY, engine_type, engine_class)
for engine_type, engine_class in AudioEngineRegistry.get_all_engines().items()
]
engines.sort(key=lambda x: x[0], reverse=True)
seen: set = set()
result = []
for engine_type, engine_class in AudioEngineRegistry.get_all_engines().items():
for _priority, engine_type, engine_class in engines:
try:
if not engine_class.is_available():
continue
for dev in engine_class.enumerate_devices():
key = (dev.name, dev.is_loopback)
if key in seen:
continue
seen.add(key)
result.append({
"index": dev.index,
"name": dev.name,

View File

@@ -163,34 +163,56 @@ class WasapiEngine(AudioCaptureEngine):
wasapi_idx = wasapi_info["index"]
result = []
loopback_names: set = set()
device_count = pa.get_device_count()
# First pass: collect input devices. PyAudioWPatch creates
# dedicated loopback input endpoints for output devices; these
# show up as input devices whose name already contains
# "[Loopback]". We mark them as loopback and remember the name
# so the second pass won't duplicate them.
for i in range(device_count):
dev = pa.get_device_info_by_index(i)
if dev["hostApi"] != wasapi_idx:
continue
if dev["maxInputChannels"] <= 0:
continue
is_input = dev["maxInputChannels"] > 0
is_output = dev["maxOutputChannels"] > 0
name = dev["name"]
is_loopback = "[Loopback]" in name
if is_loopback:
loopback_names.add(name)
if is_input:
result.append(AudioDeviceInfo(
index=i,
name=dev["name"],
is_input=True,
is_loopback=False,
channels=dev["maxInputChannels"],
default_samplerate=dev["defaultSampleRate"],
))
result.append(AudioDeviceInfo(
index=i,
name=name,
is_input=True,
is_loopback=is_loopback,
channels=dev["maxInputChannels"],
default_samplerate=dev["defaultSampleRate"],
))
if is_output:
result.append(AudioDeviceInfo(
index=i,
name=f"{dev['name']} [Loopback]",
is_input=False,
is_loopback=True,
channels=dev["maxOutputChannels"],
default_samplerate=dev["defaultSampleRate"],
))
# Second pass: add loopback entries for output devices that
# don't already have a dedicated loopback input endpoint.
for i in range(device_count):
dev = pa.get_device_info_by_index(i)
if dev["hostApi"] != wasapi_idx:
continue
if dev["maxOutputChannels"] <= 0:
continue
loopback_name = f"{dev['name']} [Loopback]"
if loopback_name in loopback_names:
continue # already covered by a dedicated loopback endpoint
result.append(AudioDeviceInfo(
index=i,
name=loopback_name,
is_input=False,
is_loopback=True,
channels=dev["maxOutputChannels"],
default_samplerate=dev["defaultSampleRate"],
))
return result