当前位置: 首页 > web >正文

微信通话自动录音器

—————【下 载 地 址】———————
【​本章下载一】: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.xdnf.cn/news/16286.html

相关文章:

  • 复矩阵与共轭转置矩阵乘积及其平方根矩阵
  • 基于xxl-job的分片实现分库分表后的扫表
  • MySQL深度理解-MySQL事务优化
  • 深度分析Java类加载机制
  • 智能小e-同步说明文档
  • 力扣189:轮转数组
  • 基于springboot的工商局商家管理系统
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘notebook’问题
  • 电子书转PDF格式教程,实现epub转PDF步骤
  • SGLang + 分布式推理部署DeepSeek671B满血版
  • Edwards爱德华泵软件 支持nEXT85和nXDS系列泵,包括nXRi, nRVi和nXLi增强型 nEXT nXDS nXLi
  • YOLO11有效涨点优化:注意力魔改 | 新颖的多尺度卷积注意力(MSCA),即插即用,助力小目标检测
  • 工具分享02 | Python批量文件重命名工具
  • 从零用java实现 小红书 springboot vue uniapp(14) 集成阿里云短信验证码
  • 核心数据结构:DataFrame
  • 征服 Linux 网络:核心服务与实战解析
  • 从指标定义到AI执行流:衡石SENSE 6.0的BI PaaS如何重构ISV分析链路
  • day46.通道注意力
  • jina-embedding-v4 环境搭建全过程
  • 实验-OSPF
  • 智能Agent场景实战指南 Day 20:Agent多模态交互能力
  • Windows 系统中 CURL 命令使用指南及常见错误解析
  • ai存在意义的对话
  • Unity UI的未来之路:从UGUI到UI Toolkit的架构演进与特性剖析(3)
  • UFS 描述符、标志和属性(二)
  • Java进阶3:Java集合框架、ArrayList、LinkedList、HashSet、HashMap和他们的迭代器
  • 外企本土化布局对国内连接器企业影响几何?
  • IO密集型、CPU密集型、负载、负载均衡
  • 从零开发Java坦克大战:架构设计与难点突破 (上)
  • 使用Jmeter进行http接口性能测试