微信通话自动录音器

—————【下 载 地 址】———————
【​本章下载一】:https://pan.xunlei.com/s/VOVvLpQuRxYadClkxTGwO2OnA1?pwd=vind#
【​本章下载二】:https://pan.xunlei.com/s/VOVvLpQuRxYadClkxTGwO2OnA1?pwd=vind#
【百款黑科技】:https://ucnygalh6wle.feishu.cn/wiki/HPQywvPc7iLZu1k0ODFcWMt2n0d?from=from_copylink
—————【下 载 地 址】———————

写在前面 最近在电脑上跟人聊天时发现它不能像手机那样自动录音,找了一圈也没发现类似的软件。于是作为小白的我,在 AI 的帮助下完成了这个工具。如果你也有类似需求,希望这款微信通话自动录音器能帮到你。
软件简介
微信通话自动录音器 是一款支持 Windows 平台的桌面小工具,主要功能包括:


  • 自动检测微信的通话窗口(支持语音 / 视频通话)

  • 一旦检测到通话,自动开始录音,通话结束自动保存 mp3 文件

  • 支持选择麦克风或虚拟声卡(推荐安装 VB-Audio Virtual Cable,可完整捕获 "你和对方" 的声音)

  • 支持自定义录音保存路径


使用步骤


  • 下载并解压本程序(建议放在英文路径下)

  • 安装 FFmpeg(用于录音处理,详见下文)

  • 安装虚拟声卡(推荐 VB-Audio Virtual Cable)

  • 设置系统“侦听”功能(详见下文)

  • 双击运行程序,选择输入设备与保存路径

  • 最小化后可在系统托盘中运行,程序将自动录音


虚拟声卡安装方法


  • 打开官网:[url=]https://vb-audio.com/Cable/[/url]

  • 点击 Download 下载压缩包(如 VBCABLE_Driver_Pack43.zip)

  • 解压后,右键以管理员身份运行 VBCABLE_Setup_x64.exe

  • 点击 Install Driver 并按提示完成安装,重启电脑后生效


系统“侦听”功能设置(非常重要)


  • 右键任务栏右下角喇叭图标 → 选择“声音设置”

  • 点击右侧“更多声音设置” → 切换到“录制”标签页

  • 找到要录音的设备(例如:“麦克风”、“CABLE Output” 等),右键 → 选择“属性”

  • 切换到“侦听”标签页 → 勾选“侦听此设备”

  • 播放设备选择你常用的扬声器或耳机(建议不要选虚拟声卡)

  • 点击“应用” → “确定”
  • [Python] 纯文本查看 复制代码
    ?
    001
    002
    003
    004
    005
    006
    007
    008
    009
    010
    011
    012
    013
    014
    015
    016
    017
    018
    019
    020
    021
    022
    023
    024
    025
    026
    027
    028
    029
    030
    031
    032
    033
    034
    035
    036
    037
    038
    039
    040
    041
    042
    043
    044
    045
    046
    047
    048
    049
    050
    051
    052
    053
    054
    055
    056
    057
    058
    059
    060
    061
    062
    063
    064
    065
    066
    067
    068
    069
    070
    071
    072
    073
    074
    075
    076
    077
    078
    079
    080
    081
    082
    083
    084
    085
    086
    087
    088
    089
    090
    091
    092
    093
    094
    095
    096
    097
    098
    099
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    405
    406
    407
    408
    409
    410
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    423
    424
    425
    426
    427
    428
    429
    430
    431
    432
    433
    434
    435
    436
    437
    438
    439
    440
    441
    442
    443
    444
    445
    446
    447
    448
    449
    450
    451
    452
    453
    454
    455
    456
    457
    458
    459
    460
    461
    462
    463
    464
    465
    466
    467
    468
    469
    470
    471
    472
    473
    474
    475
    476
    477
    478
    479
    480
    481
    482
    483
    484
    485
    486
    487
    488
    489
    490
    491
    492
    493
    494
    495
    496
    497
    498
    499
    500
    501
    502
    503
    504
    505
    506
    507
    508
    509
    510
    import os
    import sys
    import json
    import subprocess
    import threading
    import time
    import tkinter as tk
    from tkinter import ttk, filedialog, messagebox
    import pygetwindow as gw
    import pystray
    from PIL import Image, ImageDraw
    import logging
    import traceback
    from logging.handlers import TimedRotatingFileHandler
    import ctypes
    import webbrowser
    import glob
    import tempfile
    import shutil
    import stat
     
    def check_single_instance():
        mutex = ctypes.windll.kernel32.CreateMutexW(None1"WechatRecorderMutex-ByNightingale")
        last_error = ctypes.windll.kernel32.GetLastError()
        if last_error == 183:
            root = tk.Tk()
            root.withdraw()
            messagebox.showerror("已在运行""微信通话自动录音器已经在运行,无法多开。")
            sys.exit(0)
     
    check_single_instance()
     
    def resource_path(relative_path):
        if hasattr(sys, '_MEIPASS'):
            return os.path.join(sys._MEIPASS, relative_path)
        return os.path.join(os.path.dirname(__file__), relative_path)
     
    LOG_DIR = 'log'
    os.makedirs(LOG_DIR, exist_ok=True)
    log_file = os.path.join(LOG_DIR, 'wechat_recorder.log')
    handler = TimedRotatingFileHandler(
        log_file,
        when='midnight',
        interval=1,
        backupCount=30,
        encoding='utf-8'
    )
    handler.suffix = "%Y-%m-%d.log"
    logging.basicConfig(
        handlers=[handler],
        level=logging.DEBUG,
        format='%(asctime)s - %(levelname)s - %(threadName)s - %(module)s.%(funcName)s:%(lineno)d - %(message)s'
    )
     
    CONFIG_FILE = 'config.json'
    RECORDINGS_DIR = 'recordings'
    CALL_WINDOW_CLASSES = {"ILinkAudioWnd""AudioWnd""ILinkVoipTrayWnd"}
     
    def get_window_class(hwnd):
        buff = ctypes.create_unicode_buffer(256)
        ctypes.windll.user32.GetClassNameW(hwnd, buff, 256)
        return buff.value
     
    def clean_test_recordings(save_path):
        pattern = os.path.join(save_path, "测试录音_*.mp3")
        for in glob.glob(pattern):
            try:
                os.remove(f)
                logging.info(f'清理残留测试录音文件:{f}')
            except Exception:
                logging.warning(f'无法清理测试录音文件(被占用?):{f}')
     
    class WeChatRecorder:
        def __init__(self):
            logging.debug('初始化 WeChatRecorder 实例')
            self.load_config()
            os.makedirs(self.config.get('save_path', os.path.abspath(RECORDINGS_DIR)), exist_ok=True)
            clean_test_recordings(self.config.get('save_path', os.path.abspath(RECORDINGS_DIR)))
            self.recording = False
            self.recording_thread = None
            self.input_device_map = {}
            self.check_thread = threading.Thread(target=self.monitor_wechat_window, daemon=True, name='MonitorThread')
            self.input_devices = []
            self.ffmpeg_path = self.find_ffmpeg()
            if not self.ffmpeg_path:
                messagebox.showerror(
                    "无法找到FFmpeg",
                    "未检测到 ffmpeg.exe,请将其放入程序目录下的 ffmpeg_bin 文件夹,或安装到系统环境变量中。"
                )
                sys.exit(1)
            self.setup_ui()
            self.setup_tray_icon()
            logging.info('程序启动完成,UI 和托盘初始化完成')
     
        def find_ffmpeg(self):
            try:
                ffmpeg_path = os.path.join(os.path.dirname(sys.executable), 'ffmpeg.exe')
                if os.path.exists(ffmpeg_path):
                    logging.info(f"已找到同级 ffmpeg.exe: {ffmpeg_path}")
                    return ffmpeg_path
     
                # fallback:尝试系统环境变量
                subprocess.run(["ffmpeg""-version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=2)
                logging.info("使用系统环境变量中的 ffmpeg")
                return "ffmpeg"
     
            except Exception as e:
                logging.error(f"FFmpeg 检测失败: {e}")
                return None
     
        def load_config(self):
            try:
                if os.path.exists(CONFIG_FILE):
                    with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
                        self.config = json.load(f)
                    logging.debug(f'读取配置文件 {CONFIG_FILE}: {self.config}')
                else:
                    self.config = {
                        'input_device''default',
                        'save_path': os.path.abspath(RECORDINGS_DIR),
                        'on_close''minimize'
                    }
                    logging.debug(f'未找到配置文件,使用默认配置: {self.config}')
            except Exception as e:
                logging.error(f'加载配置失败: {e}\n{traceback.format_exc()}')
                self.config = {
                    'input_device''default',
                    'save_path': os.path.abspath(RECORDINGS_DIR),
                    'on_close''minimize'
                }
     
        def save_config(self):
            try:
                with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
                    json.dump(self.config, f, indent=2, ensure_ascii=False)
                logging.info(f'配置已保存到 {CONFIG_FILE}: {self.config}')
            except Exception as e:
                logging.error(f'保存配置失败: {e}\n{traceback.format_exc()}')
     
        def get_audio_devices(self):
            self.input_device_map = {}
            input_display_names = []
            try:
                import sounddevice as sd
                devices = sd.query_devices()
                default_input = sd.default.device[0]
                if default_input >= 0:
                    default_prefix = devices[default_input]['name'].split('(')[0].strip()
                    longest = ''
                    for dev in devices:
                        name = dev['name']
                        if dev['max_input_channels'] > 0:
                            prefix = name.split('(')[0].strip()
                            if prefix == default_prefix and len(name) > len(longest):
                                longest = name
                    if not longest:
                        longest = devices[default_input]['name']
                    self.input_device_map['default'= longest
                    input_display_names.append('default (系统默认设备)')
                    logging.info(f'系统默认输入设备用全称: {longest}')
                name_prefix_map = {}
                for dev in devices:
                    name = dev['name']
                    if dev['max_input_channels'] > 0:
                        prefix = name.split('(')[0].strip()
                        if (prefix not in name_prefix_map) or (len(name) > len(name_prefix_map[prefix])):
                            name_prefix_map[prefix] = name
                for name in sorted(name_prefix_map.values()):
                    self.input_device_map[name] = name
                    input_display_names.append(name)
                    logging.info(f'最终输入设备: {name}')
            except Exception as e:
                logging.error(f'设备检测错误: {e}\n{traceback.format_exc()}')
                input_display_names = ["default (系统默认设备)"]
            logging.info(f'最终输入设备列表: {input_display_names}')
            return input_display_names
     
        def detect_virtual_cable(self):
            for name in self.input_devices:
                if "VB-Audio Virtual Cable" in name or "CABLE Output" in name or "CABLE Input" in name:
                    return True
            return False
     
        def setup_ui(self):
            logging.debug('初始化UI界面')
            self.root = tk.Tk()
            ico_path = resource_path('wechat_recorder.ico')
            try:
                self.root.iconbitmap(ico_path)
            except Exception as e:
                logging.warning(f'icon设置失败: {e}')
            self.root.title('微信通话自动录音器--by夜莺')
            self.root.geometry('480x380')
            self.root.protocol("WM_DELETE_WINDOW"self.on_close)
     
            frame = ttk.Frame(self.root, padding=10)
            frame.pack(fill=tk.BOTH, expand=True)
     
            self.input_devices = self.get_audio_devices()
            has_virtual_cable = self.detect_virtual_cable()
            if not has_virtual_cable:
                top_notice = (
                    "⚠️ 未检测到虚拟声卡,建议安装 [VB-Audio Virtual Cable] 以获得完整录音效果。\n"
                    "请点击下方按钮打开官网下载页面,下载后手动安装(需管理员权限),安装成功后请重启本软件。"
                )
                lbl = ttk.Label(frame, text=top_notice, foreground="red", wraplength=450, justify="left")
                lbl.pack(fill=tk.X, pady=(05))
                ttk.Button(frame, text='打开VB-Audio官方主页',
                           command=lambda: webbrowser.open("https://vb-audio.com/Cable/")).pack(pady=2)
     
            ttk.Label(frame, text='选择录音输入设备:').pack(anchor='w')
            self.input_device_combo = ttk.Combobox(frame, values=self.input_devices, state='readonly')
            self.input_device_combo.pack(fill=tk.X)
            current_input = self.config.get('input_device''default')
            if current_input == 'default' and 'default (系统默认设备)' in self.input_devices:
                self.input_device_combo.set('default (系统默认设备)')
            elif current_input in self.input_device_map and current_input in self.input_devices:
                self.input_device_combo.set(current_input)
            elif self.input_devices:
                self.input_device_combo.set(self.input_devices[0])
            logging.debug(f'当前选择的输入设备: {self.input_device_combo.get()}')
     
            volume_frame = ttk.Frame(frame)
            volume_frame.pack(fill=tk.X, pady=(60))
            ttk.Label(volume_frame, text='实时音量:').pack(side=tk.LEFT)
            self.volume_progressbar = ttk.Progressbar(volume_frame, orient="horizontal", length=180, mode="determinate", maximum=100)
            self.volume_progressbar.pack(side=tk.LEFT, padx=5)
            self.volume_label = ttk.Label(volume_frame, text='0%')
            self.volume_label.pack(side=tk.LEFT, padx=5)
            self.test_record_btn = ttk.Button(volume_frame, text='播放录音', command=self.test_record_and_play)
            self.test_record_btn.pack(side=tk.LEFT, padx=8)
     
            self._monitor_volume = True
            self._recording_in_progress = False
            self.root.after(500self.update_volume_bar)
     
            ttk.Label(frame, text='录音保存路径:').pack(anchor='w', pady=(100))
            self.path_entry = ttk.Entry(frame)
            self.path_entry.insert(0self.config['save_path'])
            self.path_entry.pack(fill=tk.X)
            ttk.Button(frame, text='选择路径...', command=self.select_path).pack(pady=5)
     
            self.minimize_var = tk.StringVar(value=self.config.get('on_close''minimize'))
            ttk.Radiobutton(frame, text='最小化到托盘', variable=self.minimize_var, value='minimize').pack(anchor='w')
            ttk.Radiobutton(frame, text='直接退出', variable=self.minimize_var, value='exit').pack(anchor='w')
            ttk.Button(frame, text='保存设置', command=self.save_ui_config).pack(pady=10)
     
        def update_volume_bar(self):
            try:
                import sounddevice as sd
                import numpy as np
     
                if getattr(self'_recording_in_progress'False):
                    self.volume_progressbar['value'= 0
                    self.volume_label['foreground'= 'gray'
                    self.volume_label['text'= '录音中'
                else:
                    selected_name = self.input_device_combo.get()
                    devices = sd.query_devices()
                    matched_index = None
     
                    for idx, dev in enumerate(devices):
                        if selected_name in dev['name'and dev['max_input_channels'] > 0:
                            matched_index = idx
                            break
     
                    # 如果是“default (系统默认设备)”或找不到,就用默认设备
                    if selected_name == 'default (系统默认设备)' or matched_index is None:
                        matched_index = None
     
                    fs = 16000
                    duration = 0.07
                    data = sd.rec(int(duration * fs), samplerate=fs, channels=1, device=matched_index, blocking=True)
                    if data is not None and data.any():
                        rms = float(np.sqrt(np.mean(np.square(data))))
                        percent = min(int(rms * 4000), 100)
                        self.volume_progressbar['value'= percent
                        self.volume_label['foreground'= 'black'
                        self.volume_label['text'= f'{percent}%'
                    else:
                        self.volume_progressbar['value'= 0
                        self.volume_label['text'= '0%'
            except Exception as e:
                import traceback
                logging.warning(f'音量获取失败: {e}\n{traceback.format_exc()}')
                self.volume_progressbar['value'= 0
                self.volume_label['foreground'= 'black'
                self.volume_label['text'= '0%'
            finally:
                if getattr(self'_monitor_volume'False):
                    self.root.after(200self.update_volume_bar)
     
        def test_record_and_play(self):
            from datetime import datetime
     
            self.test_record_btn['state'= tk.DISABLED
            self.volume_label['text'= '测试中'
            self._recording_in_progress = True
            self.root.update_idletasks()
     
            timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
            filename = os.path.join(
                self.config.get('save_path', os.path.abspath(RECORDINGS_DIR)),
                f'测试录音_{timestamp}.mp3'
            )
            selected_device = self.input_device_combo.get()
            if selected_device == 'default (系统默认设备)':
                input_name = self.input_device_map['default']
            else:
                input_name = selected_device
     
            cmd = [
                self.ffmpeg_path,
                '-f''dshow''-i', f'audio={input_name}',
                '-t''5',
                '-acodec''libmp3lame',
                '-y', filename
            ]
            logging.info(f"测试录音命令: {' '.join(cmd)}")
            def record_and_play():
                try:
                    = subprocess.Popen(
                        cmd,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        creationflags=getattr(subprocess, 'CREATE_NO_WINDOW'0)
                    )
                    p.wait()
                    if os.path.exists(filename):
                        os.startfile(filename)
                    else:
                        messagebox.showerror("测试录音失败""未生成录音文件,请检查设备和设置。")
                except Exception as e:
                    messagebox.showerror("测试录音失败", f"录音失败:{e}")
                finally:
                    time.sleep(1)
                    self.test_record_btn['state'= tk.NORMAL
                    self._recording_in_progress = False
                    self.volume_label['text'= '0%'
     
            threading.Thread(target=record_and_play, daemon=True).start()
     
        def setup_tray_icon(self):
            logging.debug('初始化托盘图标')
            try:
                icon_path = resource_path('wechat_recorder.ico')
                image = Image.open(icon_path)
            except Exception:
                image = Image.new('RGB', (6464), color='white')
                draw = ImageDraw.Draw(image)
                draw.rectangle((8205644), fill='black')
            self.icon = pystray.Icon(
                "wechat_recorder",
                image,
                "微信自动录音器",
                menu=pystray.Menu(
                    pystray.MenuItem('打开设置'self.show_window),
                    pystray.MenuItem('退出'self.quit_app)
                )
            )
     
        def select_path(self):
            path = filedialog.askdirectory()
            if path:
                logging.info(f'用户选择保存路径: {path}')
                self.path_entry.delete(0, tk.END)
                self.path_entry.insert(0, path)
     
        def save_ui_config(self):
            selected_input = self.input_device_combo.get()
            self.config['input_device'= 'default' if selected_input == 'default (系统默认设备)' else selected_input
            self.config['save_path'= self.path_entry.get()
            self.config['on_close'= self.minimize_var.get()
            self.save_config()
            messagebox.showinfo("提示""设置已保存")
            logging.info(f"保存配置: 输入设备={self.config['input_device']} 路径={self.config['save_path']}")
     
        def on_close(self):
            if self.minimize_var.get() == 'minimize':
                self.root.withdraw()
                if not self.icon.visible:
                    threading.Thread(target=self.icon.run, name='TrayThread', daemon=True).start()
                logging.info("窗口最小化到托盘")
            else:
                self.quit_app()
     
        def show_window(self*args):
            logging.info('显示主窗口')
            try:
                self.root.deiconify()
            except Exception as e:
                logging.warning(f'主窗口显示异常: {e}')
     
        def quit_app(self*args):
            logging.info('接收到退出请求,准备退出')
            try:
                if hasattr(self"icon"and self.icon.visible:
                    self.icon.stop()
            except Exception as e:
                logging.warning(f'托盘退出异常: {e}')
            try:
                self.root.destroy()
            except Exception as e:
                logging.warning(f'窗口销毁异常: {e}')
            logging.info("程序退出")
            os._exit(0)
     
        def monitor_wechat_window(self):
            logging.debug('启动微信窗口监控线程')
            while True:
                try:
                    all_windows = gw.getAllWindows()
                    in_call = False
                    for in all_windows:
                        try:
                            cls = get_window_class(w._hWnd)
                            if cls in CALL_WINDOW_CLASSES:
                                in_call = True
                                break
                        except Exception:
                            continue
                    logging.info(f"检测窗口: {'在通话' if in_call else '未通话'},录音状态: {self.recording}")
                    if in_call and not self.recording:
                        logging.debug('检测到通话窗口出现,准备开始录音')
                        self.start_recording()
                    elif not in_call and self.recording:
                        logging.debug('检测到通话窗口关闭,准备停止录音')
                        self.stop_recording()
                    time.sleep(1)
                except Exception as e:
                    logging.error(f"监控微信窗口时出错: {e}\n{traceback.format_exc()}")
                    time.sleep(5)
     
        def start_recording(self):
            try:
                timestamp = time.strftime('%Y-%m-%d_%H-%M-%S')
                filename = os.path.join(self.config['save_path'], f'wechat_call_{timestamp}.mp3')
                self.last_record_file = filename
                selected_device = self.input_device_combo.get()
                if selected_device == 'default (系统默认设备)':
                    input_name = self.input_device_map['default']
                else:
                    input_name = selected_device
                cmd = [
                    self.ffmpeg_path,
                    '-f''dshow''-i', f'audio={input_name}',
                    '-acodec''libmp3lame',
                    '-y', filename
                ]
                logging.info(f"本次录音命令为: {' '.join(cmd)}")
                self.recording = True
                self._recording_in_progress = True
                self.volume_label['foreground'= 'gray'
                self.volume_label['text'= '录音中'
                self.volume_progressbar['value'= 0
                self.recording_thread = subprocess.Popen(
                    cmd,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    stdin=subprocess.PIPE,
                    creationflags=getattr(subprocess, 'CREATE_NO_WINDOW'0)
                )
                logging.info(f"录音进程已启动: {filename}")
            except Exception as e:
                logging.error(f"开始录音失败: {e}\n{traceback.format_exc()}")
                self.recording = False
                self._recording_in_progress = False
     
        def stop_recording(self):
            if self.recording_thread:
                try:
                    if self.recording_thread.stdin:
                        try:
                            self.recording_thread.stdin.write(b'q\n')
                            self.recording_thread.stdin.flush()
                        except Exception as e:
                            logging.warning(f'向ffmpeg发送q命令异常: {e}')
                    self.recording_thread.wait(timeout=5)
                    logging.info('录音进程已成功终止')
                    try:
                        stdout, stderr = self.recording_thread.communicate(timeout=2)
                        if stdout:
                            logging.info(f'ffmpeg stdout: {stdout.decode("utf-8", "ignore")}')
                        if stderr:
                            logging.info(f'ffmpeg stderr: {stderr.decode("utf-8", "ignore")}')
                    except Exception as e:
                        logging.warning(f'获取ffmpeg输出异常: {e}')
                except Exception as e:
                    logging.warning(f"终止录音进程失败: {e}\n{traceback.format_exc()}")
                finally:
                    self.recording_thread = None
            if hasattr(self'last_record_file'):
                if os.path.exists(self.last_record_file):
                    logging.info(f"录音文件已生成: {self.last_record_file}")
                else:
                    logging.error(f"录音进程结束但未发现录音文件: {self.last_record_file}")
            self.recording = False
            self._recording_in_progress = False
            self.volume_label['foreground'= 'black'
            self.update_volume_bar()
            logging.info("录音结束")
     
        def run(self):
            logging.info('启动主线程,进入主循环')
            self.check_thread.start()
            self.root.mainloop()
     
    if __name__ == '__main__':
        app = WeChatRecorder()
        app.run()

    最后
    这个工具比较小众,但希望它能帮到你。如果你也有自己的“小需求”,不妨动手试试。哪怕不全懂,有 AI 帮助,一切都变得简单了起来。

    本工具纯属个人学习作品,尚未支持多线程录音、静音检测等高级功能,请酌情使用。如遇问题,欢迎理性反馈或共同改进。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/90355.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/90355.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

05.原型模式:从影分身术到细胞分裂的编程艺术

目录序幕:当复制对象成为战略需求一、原型工厂的核心装备库1.1 Java原生的浅克隆术二、深度克隆的炼金法则2.1 手工克隆大法(硬核派)2.2 序列化克隆术(魔法派)三、原型模式的工业级装配3.1 原型注册管理局3.2 Spring框…

[NLP]如何在 Synopsys VCS 仿真脚本中处理多个 UPF 文件的加载

如何在 Synopsys VCS 仿真脚本中处理多个 UPF 文件的加载 摘要:我将详细解释在 Synopsys VCS(VCS)模拟脚本中如何处理多个 UPF 文件的加载,包括原理、命令选项、示例脚本以及注意事项。这基于 VCS 的 native low power verification 支持(IEEE 1801 UPF 标准)。如…

DNF: Decouple and Feedback Network for Seeing in the Dark

DNF:用于暗光视觉的解耦与反馈网络 摘要 RAW 数据的独特属性在低光照图像增强方面展现出巨大潜力。然而,现有架构在单阶段和多阶段方法中的固有局限性限制了其性能。跨两个不同域(噪声到干净和 RAW 到 sRGB)的混合映射&#xff0c…

论文精读《Frequency domain watermarking: An overview》

1. 数字水印技术基础概念与发展背景 数字水印技术作为信息隐藏领域的核心分支,其发展历程可以追溯到20世纪90年代中期计算机网络和信息技术的快速发展时期。随着大量版权作品以数字文件形式存在,电子出版逐渐普及,传统的版权保护方法面临前所未有的挑战。数字水印技术应运而…

北斗短报文兜底、5G-A增强:AORO P1100三防平板构建应急通信网络

公网中断的灾区现场,泥石流阻断了最后一条光缆。一支救援队却在废墟间有序穿行,队长手中的三防平板正闪烁着北斗卫星信号,定位坐标与伤亡信息化作一行行短报文,穿透通信孤岛直达指挥中心。这是AORO P1100三防平板搭载的北斗短报文…

Java排序算法之<冒泡排序>

目录 1、冒泡排序介绍 2、算法步骤 3、Java 实现(带优化) 4、算法复杂度分析 5、优点与缺点 前言 排序算法的“进化路线”: 冒泡排序 → 选择排序 → 插入排序 → 希尔排序 → 快速排序 → 归并排序 → 堆排序↓Java 内置排序&#xff…

生活毫无头绪就毫无头绪吧(7.24)

最近好长一段时间没有记录了明显感觉自己陷入了混乱中作息规律,专注力,心流,营养的饭菜如今下笔也没有什么头绪,前些日子本有感想但是又疲于记录,忘了许许多多最近在写论文,但尝试了游泳——蛙泳感觉太神奇…

vulhub-master 靶场Apache(httpd)漏洞

apache_parsing_vulnerability 漏洞原理在Apache1.x/2.x中Apache 解析⽂件的规则是从右到左开始判断解析,如果后缀名为不可识别⽂件解析,就再往左判断。如 1.php.xxxxx,Apache会试图识别你的代码,从右往左一个一个试。漏洞攻略参加一个1.php.jpg文件&…

Python 数据分析(一):NumPy 基础知识

目录 1. 简介2. 使用 2.1 ndarray2.2 数据类型2.3 索引与切片2.4 副本与视图2.5 轴的概念2.6 基本运算2.7 常用操作 1. 简介 NumPy(Numerical Python)是一个开源的 Python 科学计算扩展库,主要用来处理任意维度数组与矩阵,通常…

编程与数学 03-002 计算机网络 04_数据链路层功能

编程与数学 03-002 计算机网络 04_数据链路层功能一、数据链路层的基本任务(一)封装成帧(二)差错控制(三)流量控制二、差错检测与纠正方法(一)常用的差错检测码(二&#…

latex中既控制列内容位置又控制列宽,使用>{\centering\arraybackslash}p{0.85cm}

示例:\usepackage{array} % 为 >{...} 修饰符提供支持\begin{table*}[ht!]\centering \begin{tabular}{p{2.8cm} >{\centering\arraybackslash}p{0.85cm} >{\centering\arraybackslash}p{0.85cm} >{\centering\arraybackslash}p{0.85cm} >{\ce…

医疗数据挖掘Python机器学习案例

1. 医疗数据挖掘概述 医疗数据挖掘是从大量的医疗数据中提取有价值信息和知识的过程,旨在辅助医疗决策、疾病预测、治疗方案优化等。随着医疗信息化的发展,电子病历、医疗影像、基因数据等多源异构数据不断积累,为医疗数据挖掘提供了丰富的素…

人工智能概述

🌟 欢迎来到AI奇妙世界! 🌟 亲爱的开发者朋友们,大家好!👋 我是人工智能领域的探索者与分享者,很高兴在CSDN与你们相遇!🎉 在这里,我将持续输出AI前沿技术、实…

C++性能优化擂台技术文章大纲

引言性能优化在C开发中的重要性擂台赛形式的优势:激发创意,展示不同优化技巧目标读者:中高级C开发者擂台赛规则设计统一基准测试环境(硬件、编译器、优化标志)参赛代码需通过功能正确性验证性能指标:执行时…

AI人工智能时代,Bard的智能家政服务助手

AI人工智能时代,Bard的智能家政服务助手 关键词:人工智能、智能家居、Bard助手、机器学习、自然语言处理、物联网、智能服务 摘要:本文深入探讨了AI人工智能时代下,基于Bard技术的智能家政服务助手的实现原理、技术架构和应用场景。我们将从核心技术入手,分析其背后的机器…

MySQL(155)什么是MySQL的事件调度器?

MySQL的事件调度器(Event Scheduler)是一种强大的工具,用于在指定的时间间隔或特定时间点自动执行SQL语句。它类似于操作系统中的任务计划程序或Cron作业,适用于需要定时执行的任务,如数据归档、定期报告生成、定时清理…

【Zephyr开发实践系列】09_LittleFs文件系统操作

文章目录前言编写目的术语和缩写词方案选择一、Littlefs介绍二、Littlefs搭建步骤1.设备树构建2.自动挂载流程(二选一)2.1设备树启用自动挂载2.2 在 littlefs_fs.c 中,设备树宏会被展开2.3 模块注册初始化2.4 初始化阶段2.4.1注册Littlefs文件…

保护板测试仪:守护电池安全的“幕后卫士”

在现代科技飞速发展的今天,电池作为各类电子设备和新能源系统的核心动力源,其安全性与稳定性直接关系到设备的正常运行和使用者的安全。而保护板作为电池的“安全卫士”,承担着过充保护、过放保护、短路保护等关键功能。保护板测试仪则是专门…

【unitrix】 6.11 二进制数字标准化模块(normalize.rs)

一、源码 这个模块实现了类型级别的二进制数标准化处理&#xff0c;确保二进制数在组合时保持最简形式。 //! 二进制数字标准化模块 //! //! 提供二进制数字(B<H, L>)的组合前标准化功能&#xff0c;确保数字以最简形式表示&#xff0c; //! 避免同一数值有不同表示形式。…

解决OpenHarmony中找不到pthread_cancel和pthread_setcanceltype等libc符号的问题

笔者在移植三方库到OpenHarmony时遇到了pthread_cancel和pthread_setcanceltype函数找不到的问题&#xff0c;将解决办法分享如下&#xff1a; OpenHarmony的使用的c库musl中注释了这些函数的导出&#xff0c;在third_party/musl/libc.map.txt将屏蔽的函数中取消注释即可