微信通話自動錄音器

—————【下 載 地 址】———————
【?本章下載一】: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(None,?1,?"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?f?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=(0,?5))
    ????????????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=(6,?0))
    ????????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(500,?self.update_volume_bar)
    ?
    ????????ttk.Label(frame, text='錄音保存路徑:').pack(anchor='w', pady=(10,?0))
    ????????self.path_entry?=?ttk.Entry(frame)
    ????????self.path_entry.insert(0,?self.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(200,?self.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:
    ????????????????p?=?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', (64,?64), color='white')
    ????????????draw?=?ImageDraw.Draw(image)
    ????????????draw.rectangle((8,?20,?56,?44), 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?w?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
英文地址,請注明出處:http://en.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將屏蔽的函數中取消注釋即可