{"id":3154,"date":"2025-06-18T19:40:33","date_gmt":"2025-06-18T11:40:33","guid":{"rendered":"https:\/\/blog.kangyue.pro\/?p=3154"},"modified":"2025-08-13T19:37:15","modified_gmt":"2025-08-13T11:37:15","slug":"%e6%89%b9%e9%87%8f%e6%a3%80%e6%b5%8bmp3%e6%96%87%e4%bb%b6%e7%9a%84md5%e5%80%bc%e5%b9%b6%e8%87%aa%e5%8a%a8%e5%8e%bb%e9%99%a4%e9%87%8d%e5%a4%8d%e6%96%87%e4%bb%b6%e7%9a%84%e5%b0%8f%e5%b7%a5%e5%85%b7","status":"publish","type":"post","link":"https:\/\/blog.kangyue.pro\/?p=3154","title":{"rendered":"\u6279\u91cf\u68c0\u6d4b\u97f3\u9891\u6587\u4ef6\u7684MD5\u503c\u5e76\u81ea\u52a8\u53bb\u9664\u91cd\u590d\u6587\u4ef6\u7684\u5c0f\u5de5\u5177\u2014\u2014\u7ecf\u8c03\u6559ChatGPT\u800c\u6210"},"content":{"rendered":"\n<pre class=\"wp-block-code\"><code>\u672c\u6587\u4e2d\u7684Python\u4ee3\u7801\u7531ChatGPT\u751f\u6210\u3002\u7ecf\u672c\u4eba\u591a\u6b21\u8c03\u6559\uff0c\u7ec8\u6210\u672c\u7248\u3002<\/code><\/pre>\n\n\n\n<h1 class=\"wp-block-heading\">\u9700\u6c42\/\u6700\u7ec8\u6548\u679c<\/h1>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u6279\u91cf\u68c0\u6d4b\u97f3\u9891\u6587\u4ef6\u7684MD5\u503c\n<ul class=\"wp-block-list\">\n<li>\u6709\u91cd\u590d\u7684\uff0c\u9ad8\u4eae\u663e\u793a<\/li>\n\n\n\n<li>\u6309\u7167\u6587\u4ef6\u7684\u521b\u5efa\u65f6\u95f4\u68c0\u6d4b\uff0c\u5148\u68c0\u6d4b\u521b\u5efa\u65f6\u95f4\u6700\u65e9\u7684<\/li>\n\n\n\n<li>\u6240\u6709MD5\u4e00\u81f4\u7684\u6587\u4ef6\u4e2d\uff0c\u9664\u4e86\u521b\u5efa\u65f6\u95f4\u6700\u65e9\u7684\u5916\uff0c\u5747\u88ab\u89c6\u4e3a\u300c\u91cd\u590d\u300d<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>\u53ef\u5220\u9664\u91cd\u590d\u6587\u4ef6<\/li>\n<\/ul>\n\n\n\n<h1 class=\"wp-block-heading\">\u754c\u9762\u622a\u56fe<\/h1>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"952\" height=\"633\" src=\"https:\/\/blog.kangyue.pro\/wp-content\/uploads\/2025\/06\/image-99.png\" alt=\"\" class=\"wp-image-3159\" srcset=\"https:\/\/blog.kangyue.pro\/wp-content\/uploads\/2025\/06\/image-99.png 952w, https:\/\/blog.kangyue.pro\/wp-content\/uploads\/2025\/06\/image-99-768x511.png 768w\" sizes=\"auto, (max-width: 952px) 100vw, 952px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"1002\" height=\"633\" src=\"https:\/\/blog.kangyue.pro\/wp-content\/uploads\/2025\/06\/image-102.png\" alt=\"\" class=\"wp-image-3202\" srcset=\"https:\/\/blog.kangyue.pro\/wp-content\/uploads\/2025\/06\/image-102.png 1002w, https:\/\/blog.kangyue.pro\/wp-content\/uploads\/2025\/06\/image-102-768x485.png 768w\" sizes=\"auto, (max-width: 1002px) 100vw, 1002px\" \/><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">\u6240\u9700\u73af\u5883\/\u5de5\u5177\/\u5e93<\/h1>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Python<\/li>\n\n\n\n<li>ffmpeg<\/li>\n\n\n\n<li>mutagen\uff08\u8bfb\u53d6\u5143\u6570\u636e\u7684\u5e93\uff09\n<ul class=\"wp-block-list\">\n<li>\u5b89\u88c5\u547d\u4ee4\uff1a<code>pip install mutagen<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h1 class=\"wp-block-heading\">\u8c03\u6559\u8fc7\u7a0b<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\"><em>\u5b9e\u9645\u5171\u8c03\u6559\u3001\u6d4b\u8bd5\u6570\u5341\u6b21\u3002\u4e0b\u8868\u4ec5\u5c55\u793a\u76f8\u5bf9\u91cd\u8981\u9879\u3002<\/em><\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>\u8c03\u6559\u524d<\/th><th>\u8c03\u6559\u540e<\/th><\/tr><\/thead><tbody><tr><td>\u68c0\u6d4b\u5305\u62ec\u5143\u6570\u636e\u5728\u5185\u7684\u6587\u4ef6\u7684MD5<\/td><td>\u4ec5\u68c0\u6d4b\u97f3\u8f68\u5185\u5bb9\uff08\u4e0d\u5305\u6807\u9898\u3001\u827a\u672f\u5bb6\u7b49\u542b\u5143\u6570\u636e\uff09\uff1b\u4f46\u4e0d\u4fee\u6539\u539f\u59cb\u6587\u4ef6<\/td><\/tr><tr><td>\u6ca1\u6709\u63d0\u793a\u54ea\u4e9b\u6587\u4ef6\u662f\u91cd\u590d\u7684<\/td><td>\u9ad8\u4eae\u663e\u793a\u91cd\u590d\u7684\u6587\u4ef6\uff1b\u5e76\u4e14\u53ef\u6279\u91cf\u5220\u9664\u8fd9\u4e9b\u6587\u4ef6\uff0c\u4ec5\u4fdd\u7559\u521b\u5efa\u65f6\u95f4\u6700\u65e9\u8005<\/td><\/tr><tr><td>\u6587\u4ef6\u591a\u4e86\u53ef\u80fd\u4f1a\u5361\u4f4f\u3001\u63d0\u793a\u672a\u54cd\u5e94<\/td><td>\u591a\u7ebf\u7a0b\u5904\u7406<\/td><\/tr><tr><td>\u4ec5\u5728\u72b6\u6001\u680f\u4e2d\u63d0\u793a\u4e86\u6b63\u5728\u5904\u7406\u7684\u6587\u4ef6\uff0c\u4e14\u6587\u4ef6\u540d\u4e0eMD5\u503c\u672a\u5206\u5217<\/td><td>\u663e\u793a\u6240\u6709\u88ab\u68c0\u6d4b\u7684\u6587\u4ef6\uff0c\u5e76\u4e14\u6587\u4ef6\u540d\u4e0eMD5\u503c\u5206\u5217\u5c55\u793a<\/td><\/tr><tr><td><\/td><td>\u589e\u52a0\u300c\u4ec5\u67e5\u770b\u91cd\u590d\u9879\u300d\u7684\u590d\u9009\u6846\u3002<\/td><\/tr><tr><td>\u5217\u8868\u4e2d\u4ec5\u663e\u793a\u6587\u4ef6\u540d\u53caMD5\u503c<\/td><td>\u5728\u5217\u8868\u4e2d\u589e\u52a0\u300c\u6807\u9898\u300d\u300c\u53c2\u4e0e\u521b\u4f5c\u7684\u827a\u672f\u5bb6\u300d\u300c\u5531\u7247\u96c6\u300d\u300c\u6bd4\u7279\u7387\u300d\u5143\u6570\u636e<\/td><\/tr><tr><td>\u6b63\u5728\u68c0\u6d4b\u65f6\uff0c\u5217\u8868\u4e2d\u65e0\u5185\u5bb9<\/td><td>\u6b63\u5728\u68c0\u6d4b\u65f6\uff0c\u5b9e\u65f6\u663e\u793a\u5df2\u68c0\u6d4b\u7684\u6587\u4ef6\u7684\u4fe1\u606f<\/td><\/tr><tr><td>\u672a\u5c55\u793a\u7ad6\u5411\u6eda\u52a8\u6761<\/td><td>\u5217\u8868\u4e2d\u6587\u4ef6\u6570\u91cf\u591a\u65f6\uff0c\u5c55\u793a\u7ad6\u5411\u6eda\u52a8\u6761<\/td><\/tr><tr><td>\u672a\u663e\u793a\u6587\u4ef6\u6570\u91cf<\/td><td>\u68c0\u6d4b\u4efb\u52a1\u5b8c\u6210\u540e\uff0c\u663e\u793a\u603b\u6587\u4ef6\u6570\u91cf\u548c\u91cd\u590d\u7684\u6587\u4ef6\u6570\u91cf<\/td><\/tr><tr><td><\/td><td>\u589e\u52a0\u300c\u521b\u5efa\u65f6\u95f4\u300d\u4e00\u5217 \uff0c\u5e76\u589e\u52a0\u6392\u5e8f\u529f\u80fd<\/td><\/tr><\/tbody><\/table><figcaption class=\"wp-element-caption\">\u8868\uff1a\u8c03\u6559\u524d\u540e\u7684\u529f\u80fd\/\u6548\u679c\u5bf9\u6bd4<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><em>\u672c\u4eba\u4e0eChatGPT\u7684\u804a\u5929\u8bb0\u5f55\uff1a<\/em><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/chatgpt.com\/share\/68528fbd-b224-8009-b441-08094015a286\"><em>https:\/\/chatgpt.com\/share\/68528fbd-b224-8009-b441-08094015a286<\/em><\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/chatgpt.com\/share\/68529011-f114-8009-8cda-7a6e7b3f29af\"><em>https:\/\/chatgpt.com\/share\/68529011-f114-8009-8cda-7a6e7b3f29af<\/em><\/a><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><em>\u66f4\u591a\u622a\u56fe\uff1a<\/em><\/p>\n\n\n\n<div class=\"wp-block-cover wp-duotone-duotone-1\"><img loading=\"lazy\" decoding=\"async\" width=\"1126\" height=\"633\" class=\"wp-block-cover__image-background wp-image-3160\" alt=\"\" src=\"https:\/\/blog.kangyue.pro\/wp-content\/uploads\/2025\/06\/image-100.png\" data-object-fit=\"cover\" srcset=\"https:\/\/blog.kangyue.pro\/wp-content\/uploads\/2025\/06\/image-100.png 1126w, https:\/\/blog.kangyue.pro\/wp-content\/uploads\/2025\/06\/image-100-768x432.png 768w\" sizes=\"auto, (max-width: 1126px) 100vw, 1126px\" \/><span aria-hidden=\"true\" class=\"wp-block-cover__background has-background-dim\"><\/span><div class=\"wp-block-cover__inner-container is-layout-flow wp-block-cover-is-layout-flow\">\n<p class=\"has-text-align-center has-large-font-size wp-block-paragraph\">\u6309\u7167\u521b\u5efa\u65f6\u95f4\u68c0\u6d4b<\/p>\n<\/div><\/div>\n\n\n\n<h1 class=\"wp-block-heading\">\u5b8c\u6574Python\u4ee3\u7801<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">\u65b0\u7248\uff08\u8c03\u6574\u5e03\u5c40\u3001\u907f\u514d\u95ea\u70c1\u7b49\uff09\uff1a<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-contrast-3-color\"><em>\u8be5\u7248\u672c360\u53ef\u80fd\u4f1a\u963b\u6b62<code>filedialog.askdirectory()<\/code> \u5f39\u7a97\uff08\u5373\u70b9\u51fb\u300c\u6d4f\u89c8\u300d\u65e0\u6cd5\u5f39\u51fa\u5bf9\u8bdd\u6846\uff09\uff0c\u4f46\u952e\u5165\u8def\u5f84\u4e5f\u53ef\u4f7f\u7528\uff0c\u4e0d\u5f71\u54cd\u68c0\u6d4bMD5\u7684\u529f\u80fd<\/em>\u3002<\/mark><\/p>\n\n\n\t\t<div class='wp-block-bch-code-highlight  align' id='bhcCodeHighlight-7cfb834b-b' data-attributes='{&quot;cId&quot;:&quot;7cfb834b-b&quot;,&quot;language&quot;:&quot;python&quot;,&quot;code&quot;:&quot;import os\\nimport subprocess\\nimport tempfile\\nimport hashlib\\nimport tkinter as tk\\nfrom tkinter import ttk, filedialog, messagebox\\nfrom mutagen.mp3 import MP3\\nfrom mutagen.id3 import ID3NoHeaderError\\n\\nclass DuplicateFinderApp:\\n    def __init__(self, root):\\n        self.root = root\\n        root.title(\\&quot;MP3\\u91cd\\u590d\\u68c0\\u6d4b\\u5de5\\u5177\\uff08\\u53bb\\u6807\\u7b7eMD5\\uff09\\&quot;)\\n        \\n        # \\u7a97\\u53e3\\u9ed8\\u8ba4\\u603b\\u5bbd\\u9ad8\\n        root.geometry(\\&quot;1000x600\\&quot;)\\n\\n        self.folder_path = tk.StringVar()\\n        self.show_only_duplicates = tk.BooleanVar(value=False)\\n\\n        # \\u9876\\u90e8\\u9009\\u62e9\\u76ee\\u5f55\\u548c\\u590d\\u9009\\u6846\\n        frame_top = ttk.Frame(root)\\n        frame_top.pack(fill=&#039;x&#039;, padx=5, pady=5)\\n        ttk.Label(frame_top, text=\\&quot;\\u6587\\u4ef6\\u5939\\uff1a\\&quot;).pack(side=&#039;left&#039;)\\n        self.entry_path = ttk.Entry(frame_top, textvariable=self.folder_path)\\n        self.entry_path.pack(side=&#039;left&#039;, fill=&#039;x&#039;, expand=True, padx=5)\\n        ttk.Button(frame_top, text=\\&quot;\\u6d4f\\u89c8...\\&quot;, command=self.browse_folder).pack(side=&#039;left&#039;)\\n        ttk.Checkbutton(frame_top, text=\\&quot;\\u4ec5\\u67e5\\u770b\\u91cd\\u590d\\u9879\\&quot;, variable=self.show_only_duplicates, command=self.update_view).pack(side=&#039;left&#039;, padx=15)\\n\\n        # \\u5217\\u8868\\u533a\\uff0c\\u589e\\u52a0\\u4e86 &#039;create_time&#039; \\u4e00\\u5217\\uff0cindex\\u662f\\u5e8f\\u53f7\\n        columns = (&#039;index&#039;,&#039;md5&#039;, &#039;filename&#039;, &#039;duplicate&#039;, &#039;title&#039;, &#039;artist&#039;, &#039;album&#039;, &#039;bitrate&#039;, &#039;create_time&#039;)\\n\\n\\n\\n        # \\u5916\\u90e8\\u5bb9\\u5668\\uff08\\u4f7f Treeview \\u548c Scrollbar \\u540c\\u65f6\\u5e03\\u5c40\\uff09\\n        frame_tree = ttk.Frame(root)\\n        frame_tree.pack(fill=&#039;both&#039;, expand=True, padx=5, pady=5)\\n\\n        # \\u5782\\u76f4\\u6eda\\u52a8\\u6761\\n        scrollbar_y = ttk.Scrollbar(frame_tree, orient=&#039;vertical&#039;)\\n        scrollbar_y.pack(side=&#039;right&#039;, fill=&#039;y&#039;)\\n\\n        # Treeview \\u63a7\\u4ef6\\uff0c\\u663e\\u793a\\u8868\\u5934\\uff0c\\u7ed1\\u5b9a\\u6392\\u5e8f\\u4e8b\\u4ef6\\n        self.tree = ttk.Treeview(frame_tree, columns=columns, show=&#039;headings&#039;, yscrollcommand=scrollbar_y.set)\\n        headers = [&#039;\\u5e8f\\u53f7&#039;,&#039;MD5&#039;, &#039;\\u6587\\u4ef6\\u540d&#039;, &#039;\\u91cd\\u590d&#039;, &#039;\\u6807\\u9898&#039;, &#039;\\u827a\\u672f\\u5bb6&#039;, &#039;\\u5531\\u7247\\u96c6&#039;, &#039;\\u6bd4\\u7279\\u7387(kbps)&#039;, &#039;\\u521b\\u5efa\\u65f6\\u95f4&#039;]\\n        for col, text in zip(columns, headers):\\n            self.tree.heading(col, text=text, command=lambda _col=col: self.treeview_sort_column(_col, False))\\n            # \\u5bbd\\u5ea6\\u8bbe\\u7f6e\\n            if col == &#039;index&#039;:\\n                self.tree.column(col, width=30, anchor=&#039;center&#039;)\\n            elif col == &#039;filename&#039;:\\n                self.tree.column(col, width=220)\\n            elif col == &#039;md5&#039;:\\n                self.tree.column(col, width=80)\\n            elif col == &#039;duplicate&#039;:\\n                self.tree.column(col, width=40, anchor=&#039;center&#039;)\\n                \\n            elif col == &#039;artist&#039;: self.tree.column(col, width=70)\\n            \\n            elif col == &#039;album&#039;: self.tree.column(col, width=150)  # \\u2190 \\u8fd9\\u91cc\\u8bbe\\u7f6e\\u5531\\u7247\\u96c6\\u5217\\u5bbd\\n            \\n            elif col == &#039;bitrate&#039;:\\n                self.tree.column(col, width=55, anchor=&#039;center&#039;)\\n            elif col == &#039;create_time&#039;:\\n                self.tree.column(col, width=180, anchor=&#039;center&#039;)\\n            else:\\n                self.tree.column(col, width=120) # \\u2190 \\u9ed8\\u8ba4\\u5217\\u5bbd\\u8bbe\\u7f6e\\n\\n        self.tree.pack(side=&#039;left&#039;, fill=&#039;both&#039;, expand=True)\\n\\n        # \\u7ed1\\u5b9a\\u6eda\\u52a8\\u6761\\n        scrollbar_y.config(command=self.tree.yview)\\n\\n        # \\u5e95\\u90e8\\u6309\\u94ae\\u533a\\n        frame_bot = ttk.Frame(root)\\n        frame_bot.pack(fill=&#039;x&#039;, padx=5, pady=5)\\n        ttk.Button(frame_bot, text=\\&quot;\\u5f00\\u59cb\\u626b\\u63cf\\&quot;, command=self.start_scan).pack(side=&#039;left&#039;)\\n        ttk.Button(frame_bot, text=\\&quot;\\u5220\\u9664\\u91cd\\u590d\\u6587\\u4ef6\\&quot;, command=self.delete_duplicates).pack(side=&#039;left&#039;, padx=10)\\n\\n        # \\u663e\\u793a\\u603b\\u6587\\u4ef6\\u6570\\u548c\\u91cd\\u590d\\u6587\\u4ef6\\u6570\\n        self.label_stats = ttk.Label(frame_bot, text=\\&quot;\\u603b\\u6587\\u4ef6: 0    \\u91cd\\u590d\\u6587\\u4ef6: 0\\&quot;)\\n        self.label_stats.pack(side=&#039;left&#039;, padx=15)\\n\\n        ttk.Button(frame_bot, text=\\&quot;\\u9000\\u51fa\\&quot;, command=root.quit).pack(side=&#039;right&#039;)\\n\\n        # \\u65e5\\u5fd7\\u533a\\n        self.text_log = tk.Text(root, height=8)\\n        self.text_log.pack(fill=&#039;x&#039;, padx=5, pady=5)\\n\\n        # \\u6570\\u636e\\n        self.file_info_list = []  # \\u6bcf\\u9879\\u4e3a dict \\u5305\\u542b\\u8def\\u5f84\\u3001md5\\u3001\\u6807\\u7b7e\\u3001\\u662f\\u5426\\u91cd\\u590d\\u3001\\u521b\\u5efa\\u65f6\\u95f4\\n        self.md5_map = {}\\n        self.duplicates = []\\n\\n        # \\u7528\\u4e8e\\u6392\\u5e8f\\u72b6\\u6001\\u8bb0\\u5f55\\uff08\\u5217\\u540d -&gt; \\u662f\\u5426\\u5347\\u5e8f\\uff09\\n        self._sort_orders = {col: False for col in columns}\\n\\n    def log(self, msg):\\n        self.text_log.insert(&#039;end&#039;, msg + &#039;\\\\n&#039;)\\n        self.text_log.see(&#039;end&#039;)\\n        self.root.update()\\n\\n    def browse_folder(self):\\n        folder = filedialog.askdirectory()\\n        if folder:\\n            self.folder_path.set(folder)\\n\\n    def md5_of_audio(self, filepath):\\n        tmp_dir = tempfile.gettempdir()\\n        tmp_path = os.path.join(tmp_dir, &#039;tmp_ffmpeg_&#039; + next(tempfile._get_candidate_names()) + &#039;.wav&#039;)\\n        try:\\n            cmd = [\\n                &#039;ffmpeg&#039;,\\n                &#039;-y&#039;,\\n                &#039;-i&#039;, filepath,\\n                &#039;-vn&#039;,\\n                &#039;-f&#039;, &#039;wav&#039;,\\n                &#039;-hide_banner&#039;,\\n                &#039;-loglevel&#039;, &#039;error&#039;,\\n                tmp_path\\n            ]\\n            # subprocess.run(cmd, check=True)\\n            # \\u4e0b\\u884c\\u662f\\u4e3a\\u4e86\\u6253\\u5305exe\\u540e\\uff0c\\u5904\\u7406\\u6bcf\\u4e2a\\u6587\\u4ef6\\u65f6\\u4e5f\\u4e0d\\u663e\\u793a\\u547d\\u4ee4\\u63d0\\u793a\\u7b26\\u7a97\\u53e3\\uff08\\u5426\\u5219\\u4f1a\\u95ea\\u70c1\\uff09\\u3002\\n            subprocess.run(cmd, check=True, creationflags=subprocess.CREATE_NO_WINDOW)\\n\\n            \\n            with open(tmp_path, &#039;rb&#039;) as f:\\n                data = f.read()\\n            md5 = hashlib.md5(data).hexdigest()\\n            return md5\\n        finally:\\n            if os.path.exists(tmp_path):\\n                os.remove(tmp_path)\\n\\n    def get_mp3_tags(self, filepath):\\n        try:\\n            audio = MP3(filepath)\\n            tags = audio.tags\\n            title = artist = album = &#039;&#039;\\n            bitrate = round(audio.info.bitrate \\\/ 1000) if audio.info.bitrate else &#039;&#039;\\n            if tags:\\n                title = tags.get(&#039;TIT2&#039;)\\n                artist = tags.get(&#039;TPE1&#039;)\\n                album = tags.get(&#039;TALB&#039;)\\n                title = title.text[0] if title else &#039;&#039;\\n                artist = artist.text[0] if artist else &#039;&#039;\\n                album = album.text[0] if album else &#039;&#039;\\n            return title, artist, album, bitrate\\n        except ID3NoHeaderError:\\n            return &#039;&#039;, &#039;&#039;, &#039;&#039;, &#039;&#039;\\n        except Exception:\\n            return &#039;&#039;, &#039;&#039;, &#039;&#039;, &#039;&#039;\\n\\n    def start_scan(self):\\n        folder = self.folder_path.get().strip()\\n        if not folder or not os.path.isdir(folder):\\n            messagebox.showerror(\\&quot;\\u9519\\u8bef\\&quot;, \\&quot;\\u8bf7\\u9009\\u62e9\\u6709\\u6548\\u6587\\u4ef6\\u5939\\&quot;)\\n            return\\n\\n        self.tree.delete(*self.tree.get_children())\\n        self.file_info_list.clear()\\n        self.md5_map.clear()\\n        self.duplicates.clear()\\n        self.text_log.delete(&#039;1.0&#039;, &#039;end&#039;)\\n\\n        self.log(f\\&quot;\\u5f00\\u59cb\\u626b\\u63cf\\u6587\\u4ef6\\u5939: {folder}\\&quot;)\\n\\n        # \\u6309\\u7167\\u521b\\u5efa\\u65f6\\u95f4\\u7684\\u987a\\u5e8f\\u68c0\\u6d4b\\uff08\\u5148\\u68c0\\u6d4b\\u521b\\u5efa\\u65f6\\u95f4\\u6700\\u65e9\\u7684\\uff09\\n        files = sorted(\\n            [f for f in os.listdir(folder) if f.lower().endswith(&#039;.mp3&#039;)],\\n            key=lambda x: os.path.getctime(os.path.join(folder, x))\\n        )\\n\\n        self.log(f\\&quot;\\u53d1\\u73b0 {len(files)} \\u4e2aMP3\\u6587\\u4ef6\\&quot;)\\n\\n        for i, filename in enumerate(files, 1):\\n            full_path = os.path.join(folder, filename)\\n            try:\\n                self.log(f\\&quot;[{i}\\\/{len(files)}] \\u8ba1\\u7b97: {filename}\\&quot;)\\n                md5 = self.md5_of_audio(full_path)\\n                title, artist, album, bitrate = self.get_mp3_tags(full_path)\\n                create_time_ts = os.path.getctime(full_path)\\n                create_time_str = self.format_time(create_time_ts)\\n\\n                is_dup = False\\n                if md5 in self.md5_map:\\n                    is_dup = True\\n                    self.duplicates.append((full_path, self.md5_map[md5]))\\n                    self.log(f\\&quot;  \\u53d1\\u73b0\\u91cd\\u590d: {filename} \\u4e0e {os.path.basename(self.md5_map[md5])}\\&quot;)\\n                else:\\n                    self.md5_map[md5] = full_path\\n\\n                info = {\\n                    &#039;md5&#039;: md5,\\n                    &#039;filename&#039;: filename,\\n                    &#039;filepath&#039;: full_path,\\n                    &#039;duplicate&#039;: is_dup,\\n                    &#039;title&#039;: title,\\n                    &#039;artist&#039;: artist,\\n                    &#039;album&#039;: album,\\n                    &#039;bitrate&#039;: bitrate,\\n                    &#039;create_time&#039;: create_time_str,\\n                    &#039;create_time_ts&#039;: create_time_ts  # \\u7528\\u4e8e\\u6392\\u5e8f\\n                }\\n                self.file_info_list.append(info)\\n\\n                # \\u5b9e\\u65f6\\u663e\\u793a\\u5728\\u5217\\u8868\\u4e2d\\n                if not self.show_only_duplicates.get() or info[&#039;duplicate&#039;]:\\n                # \\u4e0b\\u4e00\\u884cfor\\u5e8f\\u53f7\\n                    vals = (len(self.tree.get_children()) + 1,\\n                    \\n                    # \\u91cd\\u590d\\uff1a\\u662f\\u5426\\u5747\\u663e\\u793a\\uff1a\\n                    # info[&#039;md5&#039;], info[&#039;filename&#039;], \\&quot;\\u662f\\&quot; if info[&#039;duplicate&#039;] else \\&quot;\\&quot;,\\n                    \\n                    # \\u91cd\\u590d\\uff1a\\u4ec5\\u663e\\u793a\\u300c\\u662f\\u300d\\uff0c\\u4e0d\\u663e\\u793a\\u300c\\u5426\\u300d\\uff1a\\n                    info[&#039;md5&#039;], info[&#039;filename&#039;], \\&quot;\\u662f\\&quot; if info[&#039;duplicate&#039;] else \\&quot;\\&quot;,\\n                    \\n                            info[&#039;title&#039;], info[&#039;artist&#039;], info[&#039;album&#039;], info[&#039;bitrate&#039;], info[&#039;create_time&#039;])\\n                    iid = self.tree.insert(&#039;&#039;, &#039;end&#039;, values=vals)\\n                    if info[&#039;duplicate&#039;]:\\n                        self.tree.item(iid, tags=(&#039;duplicate&#039;,))\\n\\n            except Exception as e:\\n                self.log(f\\&quot;  \\u9519\\u8bef\\u8df3\\u8fc7: {filename} ({e})\\&quot;)\\n\\n        self.log(\\&quot;\\u626b\\u63cf\\u5b8c\\u6210\\&quot;)\\n\\n        self.label_stats.config(text=f\\&quot;\\u603b\\u6587\\u4ef6: {len(self.file_info_list)}    \\u91cd\\u590d\\u6587\\u4ef6: {len(self.duplicates)}\\&quot;)\\n\\n        self.update_view()\\n\\n    def format_time(self, ts):\\n        import time\\n        return time.strftime(&#039;%Y-%m-%d %H:%M:%S&#039;, time.localtime(ts))\\n\\n    def update_view(self):\\n        self.tree.delete(*self.tree.get_children())\\n        show_only_dup = self.show_only_duplicates.get()\\n\\n        for info in self.file_info_list:\\n            if show_only_dup and not info[&#039;duplicate&#039;]:\\n                continue\\n            vals =(len(self.tree.get_children()) + 1,\\n                    \\n                    # info[&#039;md5&#039;], info[&#039;filename&#039;], \\&quot;\\u662f\\&quot; if info[&#039;duplicate&#039;] else \\&quot;\\u5426\\&quot;,\\n                    \\n                    #\\u4e0d\\u91cd\\u590d\\u7684\\u4e0d\\u663e\\u793a\\u300c\\u5426\\u300d \\n                    info[&#039;md5&#039;], info[&#039;filename&#039;], \\&quot;\\u662f\\&quot; if info[&#039;duplicate&#039;] else \\&quot;\\&quot;,\\n                    info[&#039;title&#039;], info[&#039;artist&#039;], info[&#039;album&#039;], info[&#039;bitrate&#039;], info[&#039;create_time&#039;])\\n            iid = self.tree.insert(&#039;&#039;, &#039;end&#039;, values=vals)\\n            if info[&#039;duplicate&#039;]:\\n                # \\u9ad8\\u4eae\\u91cd\\u590d\\u9879\\u80cc\\u666f\\u8272\\n                self.tree.item(iid, tags=(&#039;duplicate&#039;,))\\n        self.tree.tag_configure(&#039;duplicate&#039;, background=&#039;#ffdddd&#039;)\\n\\n    def treeview_sort_column(self, col, reverse):\\n        # \\u6839\\u636e\\u5217\\u540d\\u6392\\u5e8f file_info_list\\uff0c\\u7136\\u540e\\u5237\\u65b0\\u663e\\u793a\\n        # \\u9700\\u8981\\u533a\\u5206\\u6570\\u5b57\\u3001\\u65f6\\u95f4\\u548c\\u5b57\\u7b26\\u4e32\\u6392\\u5e8f\\n\\n        def sort_key(info):\\n            if col == &#039;bitrate&#039;:\\n                return info[col] if isinstance(info[col], int) else 0\\n            elif col == &#039;create_time&#039;:\\n                # \\u7528\\u65f6\\u95f4\\u6233\\u6392\\u5e8f\\uff0c\\u4e4b\\u524d\\u5b58\\u4e86 create_time_ts\\n                return info.get(&#039;create_time_ts&#039;, 0)\\n            elif col == &#039;duplicate&#039;:\\n                # \\u8f6c\\u6210\\u5e03\\u5c14\\u503c\\u6392\\u5e8f\\uff0c\\&quot;\\u662f\\&quot; &gt; \\&quot;\\u5426\\&quot;\\n                return info[col]\\n            else:\\n                # \\u5b57\\u7b26\\u4e32\\u6392\\u5e8f\\uff0c\\u5ffd\\u7565\\u5927\\u5c0f\\u5199\\n                v = info.get(col, &#039;&#039;)\\n                if isinstance(v, str):\\n                    return v.lower()\\n                return v\\n\\n        # \\u5207\\u6362\\u6392\\u5e8f\\u987a\\u5e8f\\n        self._sort_orders[col] = not self._sort_orders.get(col, False)\\n        reverse = self._sort_orders[col]\\n\\n        self.file_info_list.sort(key=sort_key, reverse=reverse)\\n        self.update_view()\\n\\n    def delete_duplicates(self):\\n        if not self.duplicates:\\n            messagebox.showinfo(\\&quot;\\u63d0\\u793a\\&quot;, \\&quot;\\u672a\\u53d1\\u73b0\\u91cd\\u590d\\u6587\\u4ef6\\uff0c\\u65e0\\u9700\\u5220\\u9664\\&quot;)\\n            return\\n\\n        if not messagebox.askyesno(\\&quot;\\u786e\\u8ba4\\&quot;, f\\&quot;\\u786e\\u5b9a\\u5220\\u9664 {len(self.duplicates)} \\u4e2a\\u91cd\\u590d\\u6587\\u4ef6\\uff0c\\u53ea\\u4fdd\\u7559\\u6bcf\\u7ec4\\u4e2d\\u6700\\u65e9\\u521b\\u5efa\\u7684\\u6587\\u4ef6\\u5417\\uff1f\\&quot;):\\n            return\\n\\n        deleted_count = 0\\n        for dup_path, orig_path in self.duplicates:\\n            try:\\n                ctime_dup = os.path.getctime(dup_path)\\n                ctime_orig = os.path.getctime(orig_path)\\n                to_delete = dup_path if ctime_dup &gt;= ctime_orig else orig_path\\n                os.remove(to_delete)\\n                self.log(f\\&quot;\\u5220\\u9664\\u6587\\u4ef6: {to_delete}\\&quot;)\\n                deleted_count += 1\\n            except Exception as e:\\n                self.log(f\\&quot;\\u5220\\u9664\\u5931\\u8d25: {dup_path} ({e})\\&quot;)\\n\\n        messagebox.showinfo(\\&quot;\\u5b8c\\u6210\\&quot;, f\\&quot;\\u6210\\u529f\\u5220\\u9664 {deleted_count} \\u4e2a\\u91cd\\u590d\\u6587\\u4ef6\\&quot;)\\n\\n        # \\u4e3a\\u4e86\\u5220\\u9664\\u6587\\u4ef6\\u540e\\u4e0d\\u91cd\\u590d\\u8ba1\\u7b97MD5\\n        self.duplicates.clear()\\n        self.update_view()\\n        self.label_stats.config(text=f\\&quot;\\u603b\\u6587\\u4ef6: {len(self.file_info_list)}    \\u91cd\\u590d\\u6587\\u4ef6: {len(self.duplicates)}\\&quot;)\\n\\n\\nif __name__ == \\&quot;__main__\\&quot;:\\n    root = tk.Tk()\\n    app = DuplicateFinderApp(root)\\n    root.mainloop()\\n&quot;,&quot;codeTypo&quot;:{&quot;desktop&quot;:9,&quot;tablet&quot;:15,&quot;mobile&quot;:14},&quot;height&quot;:{&quot;desktop&quot;:&quot;300px&quot;,&quot;tablet&quot;:&quot;0px&quot;,&quot;mobile&quot;:&quot;0px&quot;},&quot;background&quot;:{&quot;color&quot;:&quot;#d3cfcf42&quot;,&quot;type&quot;:&quot;solid&quot;,&quot;position&quot;:&quot;center center&quot;,&quot;image&quot;:{&quot;id&quot;:3091,&quot;url&quot;:&quot;https:\\\/\\\/blog.kangyue.pro\\\/wp-content\\\/uploads\\\/2025\\\/06\\\/image-85-scaled.png&quot;,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;image&quot;,&quot;caption&quot;:&quot;&quot;},&quot;attachment&quot;:&quot;initial&quot;,&quot;size&quot;:&quot;cover&quot;},&quot;align&quot;:&quot;&quot;,&quot;lineNumbers&quot;:true,&quot;theme&quot;:&quot;default&quot;,&quot;clipBoard&quot;:true,&quot;wordWrap&quot;:true,&quot;width&quot;:{&quot;desktop&quot;:&quot;100%&quot;,&quot;tablet&quot;:&quot;100%&quot;,&quot;mobile&quot;:&quot;100%&quot;},&quot;padding&quot;:{&quot;top&quot;:&quot;0px&quot;,&quot;right&quot;:&quot;0px&quot;,&quot;bottom&quot;:&quot;0px&quot;,&quot;left&quot;:&quot;0px&quot;},&quot;layout&quot;:{&quot;align&quot;:&quot;left&quot;},&quot;border&quot;:{&quot;color&quot;:&quot;#000&quot;,&quot;style&quot;:&quot;solid&quot;,&quot;width&quot;:&quot;0px&quot;},&quot;shadow&quot;:[],&quot;alignment&quot;:&quot;center&quot;,&quot;clipBoardColors&quot;:{&quot;color&quot;:&quot;#fff&quot;,&quot;bg&quot;:&quot;#00000024&quot;}}'><\/div>\r\n\r\n\t\t\n\n\n<p class=\"wp-block-paragraph\">\u975e\u65b0\u7248\uff1a<\/p>\n\n\n\t\t<div class='wp-block-bch-code-highlight  align' id='bhcCodeHighlight-6f488ba6-7' data-attributes='{&quot;cId&quot;:&quot;6f488ba6-7&quot;,&quot;language&quot;:&quot;python&quot;,&quot;code&quot;:&quot;import os\\nimport subprocess\\nimport tempfile\\nimport hashlib\\nimport tkinter as tk\\nfrom tkinter import ttk, filedialog, messagebox\\nfrom mutagen.mp3 import MP3\\nfrom mutagen.id3 import ID3NoHeaderError\\n\\nclass DuplicateFinderApp:\\n    def __init__(self, root):\\n        self.root = root\\n        root.title(\\&quot;MP3\\u91cd\\u590d\\u68c0\\u6d4b\\u5de5\\u5177\\uff08\\u53bb\\u6807\\u7b7eMD5\\uff09\\&quot;)\\n        root.geometry(\\&quot;950x600\\&quot;)\\n\\n        self.folder_path = tk.StringVar()\\n        self.show_only_duplicates = tk.BooleanVar(value=False)\\n\\n        # \\u9876\\u90e8\\u9009\\u62e9\\u76ee\\u5f55\\u548c\\u590d\\u9009\\u6846\\n        frame_top = ttk.Frame(root)\\n        frame_top.pack(fill=&#039;x&#039;, padx=5, pady=5)\\n        ttk.Label(frame_top, text=\\&quot;\\u6587\\u4ef6\\u5939\\uff1a\\&quot;).pack(side=&#039;left&#039;)\\n        self.entry_path = ttk.Entry(frame_top, textvariable=self.folder_path)\\n        self.entry_path.pack(side=&#039;left&#039;, fill=&#039;x&#039;, expand=True, padx=5)\\n        ttk.Button(frame_top, text=\\&quot;\\u6d4f\\u89c8...\\&quot;, command=self.browse_folder).pack(side=&#039;left&#039;)\\n        ttk.Checkbutton(frame_top, text=\\&quot;\\u4ec5\\u67e5\\u770b\\u91cd\\u590d\\u9879\\&quot;, variable=self.show_only_duplicates, command=self.update_view).pack(side=&#039;left&#039;, padx=15)\\n\\n        # \\u5217\\u8868\\u533a\\uff0c\\u589e\\u52a0\\u4e86 &#039;create_time&#039; \\u4e00\\u5217\\n        columns = (&#039;md5&#039;, &#039;filename&#039;, &#039;duplicate&#039;, &#039;title&#039;, &#039;artist&#039;, &#039;album&#039;, &#039;bitrate&#039;, &#039;create_time&#039;)\\n\\n        # \\u5916\\u90e8\\u5bb9\\u5668\\uff08\\u4f7f Treeview \\u548c Scrollbar \\u540c\\u65f6\\u5e03\\u5c40\\uff09\\n        frame_tree = ttk.Frame(root)\\n        frame_tree.pack(fill=&#039;both&#039;, expand=True, padx=5, pady=5)\\n\\n        # \\u5782\\u76f4\\u6eda\\u52a8\\u6761\\n        scrollbar_y = ttk.Scrollbar(frame_tree, orient=&#039;vertical&#039;)\\n        scrollbar_y.pack(side=&#039;right&#039;, fill=&#039;y&#039;)\\n\\n        # Treeview \\u63a7\\u4ef6\\uff0c\\u663e\\u793a\\u8868\\u5934\\uff0c\\u7ed1\\u5b9a\\u6392\\u5e8f\\u4e8b\\u4ef6\\n        self.tree = ttk.Treeview(frame_tree, columns=columns, show=&#039;headings&#039;, yscrollcommand=scrollbar_y.set)\\n        headers = [&#039;MD5&#039;, &#039;\\u6587\\u4ef6\\u540d&#039;, &#039;\\u91cd\\u590d&#039;, &#039;\\u6807\\u9898&#039;, &#039;\\u827a\\u672f\\u5bb6&#039;, &#039;\\u5531\\u7247\\u96c6&#039;, &#039;\\u6bd4\\u7279\\u7387(kbps)&#039;, &#039;\\u521b\\u5efa\\u65f6\\u95f4&#039;]\\n        for col, text in zip(columns, headers):\\n            self.tree.heading(col, text=text, command=lambda _col=col: self.treeview_sort_column(_col, False))\\n            # \\u5bbd\\u5ea6\\u8bbe\\u7f6e\\n            if col == &#039;filename&#039;:\\n                self.tree.column(col, width=200)\\n            elif col == &#039;md5&#039;:\\n                self.tree.column(col, width=180)\\n            elif col == &#039;duplicate&#039;:\\n                self.tree.column(col, width=60, anchor=&#039;center&#039;)\\n            elif col == &#039;bitrate&#039;:\\n                self.tree.column(col, width=120, anchor=&#039;center&#039;)\\n            elif col == &#039;create_time&#039;:\\n                self.tree.column(col, width=130, anchor=&#039;center&#039;)\\n            else:\\n                self.tree.column(col, width=120)\\n\\n        self.tree.pack(side=&#039;left&#039;, fill=&#039;both&#039;, expand=True)\\n\\n        # \\u7ed1\\u5b9a\\u6eda\\u52a8\\u6761\\n        scrollbar_y.config(command=self.tree.yview)\\n\\n        # \\u5e95\\u90e8\\u6309\\u94ae\\u533a\\n        frame_bot = ttk.Frame(root)\\n        frame_bot.pack(fill=&#039;x&#039;, padx=5, pady=5)\\n        ttk.Button(frame_bot, text=\\&quot;\\u5f00\\u59cb\\u626b\\u63cf\\&quot;, command=self.start_scan).pack(side=&#039;left&#039;)\\n        ttk.Button(frame_bot, text=\\&quot;\\u5220\\u9664\\u91cd\\u590d\\u6587\\u4ef6\\&quot;, command=self.delete_duplicates).pack(side=&#039;left&#039;, padx=10)\\n\\n        # \\u663e\\u793a\\u603b\\u6587\\u4ef6\\u6570\\u548c\\u91cd\\u590d\\u6587\\u4ef6\\u6570\\n        self.label_stats = ttk.Label(frame_bot, text=\\&quot;\\u603b\\u6587\\u4ef6: 0    \\u91cd\\u590d\\u6587\\u4ef6: 0\\&quot;)\\n        self.label_stats.pack(side=&#039;left&#039;, padx=15)\\n\\n        ttk.Button(frame_bot, text=\\&quot;\\u9000\\u51fa\\&quot;, command=root.quit).pack(side=&#039;right&#039;)\\n\\n        # \\u65e5\\u5fd7\\u533a\\n        self.text_log = tk.Text(root, height=8)\\n        self.text_log.pack(fill=&#039;x&#039;, padx=5, pady=5)\\n\\n        # \\u6570\\u636e\\n        self.file_info_list = []  # \\u6bcf\\u9879\\u4e3a dict \\u5305\\u542b\\u8def\\u5f84\\u3001md5\\u3001\\u6807\\u7b7e\\u3001\\u662f\\u5426\\u91cd\\u590d\\u3001\\u521b\\u5efa\\u65f6\\u95f4\\n        self.md5_map = {}\\n        self.duplicates = []\\n\\n        # \\u7528\\u4e8e\\u6392\\u5e8f\\u72b6\\u6001\\u8bb0\\u5f55\\uff08\\u5217\\u540d -&gt; \\u662f\\u5426\\u5347\\u5e8f\\uff09\\n        self._sort_orders = {col: False for col in columns}\\n\\n    def log(self, msg):\\n        self.text_log.insert(&#039;end&#039;, msg + &#039;\\\\n&#039;)\\n        self.text_log.see(&#039;end&#039;)\\n        self.root.update()\\n\\n    def browse_folder(self):\\n        folder = filedialog.askdirectory()\\n        if folder:\\n            self.folder_path.set(folder)\\n\\n    def md5_of_audio(self, filepath):\\n        tmp_dir = tempfile.gettempdir()\\n        tmp_path = os.path.join(tmp_dir, &#039;tmp_ffmpeg_&#039; + next(tempfile._get_candidate_names()) + &#039;.wav&#039;)\\n        try:\\n            cmd = [\\n                &#039;ffmpeg&#039;,\\n                &#039;-y&#039;,\\n                &#039;-i&#039;, filepath,\\n                &#039;-vn&#039;,\\n                &#039;-f&#039;, &#039;wav&#039;,\\n                &#039;-hide_banner&#039;,\\n                &#039;-loglevel&#039;, &#039;error&#039;,\\n                tmp_path\\n            ]\\n            subprocess.run(cmd, check=True)\\n            with open(tmp_path, &#039;rb&#039;) as f:\\n                data = f.read()\\n            md5 = hashlib.md5(data).hexdigest()\\n            return md5\\n        finally:\\n            if os.path.exists(tmp_path):\\n                os.remove(tmp_path)\\n\\n    def get_mp3_tags(self, filepath):\\n        try:\\n            audio = MP3(filepath)\\n            tags = audio.tags\\n            title = artist = album = &#039;&#039;\\n            bitrate = round(audio.info.bitrate \\\/ 1000) if audio.info.bitrate else &#039;&#039;\\n            if tags:\\n                title = tags.get(&#039;TIT2&#039;)\\n                artist = tags.get(&#039;TPE1&#039;)\\n                album = tags.get(&#039;TALB&#039;)\\n                title = title.text[0] if title else &#039;&#039;\\n                artist = artist.text[0] if artist else &#039;&#039;\\n                album = album.text[0] if album else &#039;&#039;\\n            return title, artist, album, bitrate\\n        except ID3NoHeaderError:\\n            return &#039;&#039;, &#039;&#039;, &#039;&#039;, &#039;&#039;\\n        except Exception:\\n            return &#039;&#039;, &#039;&#039;, &#039;&#039;, &#039;&#039;\\n\\n    def start_scan(self):\\n        folder = self.folder_path.get().strip()\\n        if not folder or not os.path.isdir(folder):\\n            messagebox.showerror(\\&quot;\\u9519\\u8bef\\&quot;, \\&quot;\\u8bf7\\u9009\\u62e9\\u6709\\u6548\\u6587\\u4ef6\\u5939\\&quot;)\\n            return\\n\\n        self.tree.delete(*self.tree.get_children())\\n        self.file_info_list.clear()\\n        self.md5_map.clear()\\n        self.duplicates.clear()\\n        self.text_log.delete(&#039;1.0&#039;, &#039;end&#039;)\\n\\n        self.log(f\\&quot;\\u5f00\\u59cb\\u626b\\u63cf\\u6587\\u4ef6\\u5939: {folder}\\&quot;)\\n\\n        # \\u6309\\u7167\\u521b\\u5efa\\u65f6\\u95f4\\u7684\\u987a\\u5e8f\\u68c0\\u6d4b\\uff08\\u5148\\u68c0\\u6d4b\\u521b\\u5efa\\u65f6\\u95f4\\u6700\\u65e9\\u7684\\uff09\\n        files = sorted(\\n            [f for f in os.listdir(folder) if f.lower().endswith(&#039;.mp3&#039;)],\\n            key=lambda x: os.path.getctime(os.path.join(folder, x))\\n        )\\n\\n        self.log(f\\&quot;\\u53d1\\u73b0 {len(files)} \\u4e2aMP3\\u6587\\u4ef6\\&quot;)\\n\\n        for i, filename in enumerate(files, 1):\\n            full_path = os.path.join(folder, filename)\\n            try:\\n                self.log(f\\&quot;[{i}\\\/{len(files)}] \\u8ba1\\u7b97: {filename}\\&quot;)\\n                md5 = self.md5_of_audio(full_path)\\n                title, artist, album, bitrate = self.get_mp3_tags(full_path)\\n                create_time_ts = os.path.getctime(full_path)\\n                create_time_str = self.format_time(create_time_ts)\\n\\n                is_dup = False\\n                if md5 in self.md5_map:\\n                    is_dup = True\\n                    self.duplicates.append((full_path, self.md5_map[md5]))\\n                    self.log(f\\&quot;  \\u53d1\\u73b0\\u91cd\\u590d: {filename} \\u4e0e {os.path.basename(self.md5_map[md5])}\\&quot;)\\n                else:\\n                    self.md5_map[md5] = full_path\\n\\n                info = {\\n                    &#039;md5&#039;: md5,\\n                    &#039;filename&#039;: filename,\\n                    &#039;filepath&#039;: full_path,\\n                    &#039;duplicate&#039;: is_dup,\\n                    &#039;title&#039;: title,\\n                    &#039;artist&#039;: artist,\\n                    &#039;album&#039;: album,\\n                    &#039;bitrate&#039;: bitrate,\\n                    &#039;create_time&#039;: create_time_str,\\n                    &#039;create_time_ts&#039;: create_time_ts  # \\u7528\\u4e8e\\u6392\\u5e8f\\n                }\\n                self.file_info_list.append(info)\\n\\n                # \\u5b9e\\u65f6\\u663e\\u793a\\u5728\\u5217\\u8868\\u4e2d\\n                if not self.show_only_duplicates.get() or info[&#039;duplicate&#039;]:\\n                    vals = (info[&#039;md5&#039;], info[&#039;filename&#039;], \\&quot;\\u662f\\&quot; if info[&#039;duplicate&#039;] else \\&quot;\\u5426\\&quot;,\\n                            info[&#039;title&#039;], info[&#039;artist&#039;], info[&#039;album&#039;], info[&#039;bitrate&#039;], info[&#039;create_time&#039;])\\n                    iid = self.tree.insert(&#039;&#039;, &#039;end&#039;, values=vals)\\n                    if info[&#039;duplicate&#039;]:\\n                        self.tree.item(iid, tags=(&#039;duplicate&#039;,))\\n\\n            except Exception as e:\\n                self.log(f\\&quot;  \\u9519\\u8bef\\u8df3\\u8fc7: {filename} ({e})\\&quot;)\\n\\n        self.log(\\&quot;\\u626b\\u63cf\\u5b8c\\u6210\\&quot;)\\n\\n        self.label_stats.config(text=f\\&quot;\\u603b\\u6587\\u4ef6: {len(self.file_info_list)}    \\u91cd\\u590d\\u6587\\u4ef6: {len(self.duplicates)}\\&quot;)\\n\\n        self.update_view()\\n\\n    def format_time(self, ts):\\n        import time\\n        return time.strftime(&#039;%Y-%m-%d %H:%M:%S&#039;, time.localtime(ts))\\n\\n    def update_view(self):\\n        self.tree.delete(*self.tree.get_children())\\n        show_only_dup = self.show_only_duplicates.get()\\n\\n        for info in self.file_info_list:\\n            if show_only_dup and not info[&#039;duplicate&#039;]:\\n                continue\\n            vals = (info[&#039;md5&#039;], info[&#039;filename&#039;], \\&quot;\\u662f\\&quot; if info[&#039;duplicate&#039;] else \\&quot;\\u5426\\&quot;,\\n                    info[&#039;title&#039;], info[&#039;artist&#039;], info[&#039;album&#039;], info[&#039;bitrate&#039;], info[&#039;create_time&#039;])\\n            iid = self.tree.insert(&#039;&#039;, &#039;end&#039;, values=vals)\\n            if info[&#039;duplicate&#039;]:\\n                # \\u9ad8\\u4eae\\u91cd\\u590d\\u9879\\u80cc\\u666f\\u8272\\n                self.tree.item(iid, tags=(&#039;duplicate&#039;,))\\n        self.tree.tag_configure(&#039;duplicate&#039;, background=&#039;#ffdddd&#039;)\\n\\n    def treeview_sort_column(self, col, reverse):\\n        # \\u6839\\u636e\\u5217\\u540d\\u6392\\u5e8f file_info_list\\uff0c\\u7136\\u540e\\u5237\\u65b0\\u663e\\u793a\\n        # \\u9700\\u8981\\u533a\\u5206\\u6570\\u5b57\\u3001\\u65f6\\u95f4\\u548c\\u5b57\\u7b26\\u4e32\\u6392\\u5e8f\\n\\n        def sort_key(info):\\n            if col == &#039;bitrate&#039;:\\n                return info[col] if isinstance(info[col], int) else 0\\n            elif col == &#039;create_time&#039;:\\n                # \\u7528\\u65f6\\u95f4\\u6233\\u6392\\u5e8f\\uff0c\\u4e4b\\u524d\\u5b58\\u4e86 create_time_ts\\n                return info.get(&#039;create_time_ts&#039;, 0)\\n            elif col == &#039;duplicate&#039;:\\n                # \\u8f6c\\u6210\\u5e03\\u5c14\\u503c\\u6392\\u5e8f\\uff0c\\&quot;\\u662f\\&quot; &gt; \\&quot;\\u5426\\&quot;\\n                return info[col]\\n            else:\\n                # \\u5b57\\u7b26\\u4e32\\u6392\\u5e8f\\uff0c\\u5ffd\\u7565\\u5927\\u5c0f\\u5199\\n                v = info.get(col, &#039;&#039;)\\n                if isinstance(v, str):\\n                    return v.lower()\\n                return v\\n\\n        # \\u5207\\u6362\\u6392\\u5e8f\\u987a\\u5e8f\\n        self._sort_orders[col] = not self._sort_orders.get(col, False)\\n        reverse = self._sort_orders[col]\\n\\n        self.file_info_list.sort(key=sort_key, reverse=reverse)\\n        self.update_view()\\n\\n    def delete_duplicates(self):\\n        if not self.duplicates:\\n            messagebox.showinfo(\\&quot;\\u63d0\\u793a\\&quot;, \\&quot;\\u672a\\u53d1\\u73b0\\u91cd\\u590d\\u6587\\u4ef6\\uff0c\\u65e0\\u9700\\u5220\\u9664\\&quot;)\\n            return\\n\\n        if not messagebox.askyesno(\\&quot;\\u786e\\u8ba4\\&quot;, f\\&quot;\\u786e\\u5b9a\\u5220\\u9664 {len(self.duplicates)} \\u4e2a\\u91cd\\u590d\\u6587\\u4ef6\\uff0c\\u53ea\\u4fdd\\u7559\\u6bcf\\u7ec4\\u4e2d\\u6700\\u65e9\\u521b\\u5efa\\u7684\\u6587\\u4ef6\\u5417\\uff1f\\&quot;):\\n            return\\n\\n        deleted_count = 0\\n        for dup_path, orig_path in self.duplicates:\\n            try:\\n                ctime_dup = os.path.getctime(dup_path)\\n                ctime_orig = os.path.getctime(orig_path)\\n                to_delete = dup_path if ctime_dup &gt;= ctime_orig else orig_path\\n                os.remove(to_delete)\\n                self.log(f\\&quot;\\u5220\\u9664\\u6587\\u4ef6: {to_delete}\\&quot;)\\n                deleted_count += 1\\n            except Exception as e:\\n                self.log(f\\&quot;\\u5220\\u9664\\u5931\\u8d25: {dup_path} ({e})\\&quot;)\\n\\n        messagebox.showinfo(\\&quot;\\u5b8c\\u6210\\&quot;, f\\&quot;\\u6210\\u529f\\u5220\\u9664 {deleted_count} \\u4e2a\\u91cd\\u590d\\u6587\\u4ef6\\&quot;)\\n\\n        # \\u4e3a\\u4e86\\u5220\\u9664\\u6587\\u4ef6\\u540e\\u4e0d\\u91cd\\u590d\\u8ba1\\u7b97MD5\\n        self.duplicates.clear()\\n        self.update_view()\\n        self.label_stats.config(text=f\\&quot;\\u603b\\u6587\\u4ef6: {len(self.file_info_list)}    \\u91cd\\u590d\\u6587\\u4ef6: {len(self.duplicates)}\\&quot;)\\n\\n\\nif __name__ == \\&quot;__main__\\&quot;:\\n    root = tk.Tk()\\n    app = DuplicateFinderApp(root)\\n    root.mainloop()\\n&quot;,&quot;theme&quot;:&quot;dark&quot;,&quot;codeTypo&quot;:{&quot;desktop&quot;:9,&quot;tablet&quot;:15,&quot;mobile&quot;:14},&quot;height&quot;:{&quot;desktop&quot;:&quot;300px&quot;,&quot;tablet&quot;:&quot;0px&quot;,&quot;mobile&quot;:&quot;0px&quot;},&quot;clipBoardColors&quot;:{&quot;color&quot;:&quot;#fff&quot;,&quot;bg&quot;:&quot;#00000024&quot;,&quot;bgType&quot;:&quot;gradient&quot;},&quot;align&quot;:&quot;&quot;,&quot;lineNumbers&quot;:true,&quot;clipBoard&quot;:true,&quot;wordWrap&quot;:true,&quot;width&quot;:{&quot;desktop&quot;:&quot;100%&quot;,&quot;tablet&quot;:&quot;100%&quot;,&quot;mobile&quot;:&quot;100%&quot;},&quot;padding&quot;:{&quot;top&quot;:&quot;0px&quot;,&quot;right&quot;:&quot;0px&quot;,&quot;bottom&quot;:&quot;0px&quot;,&quot;left&quot;:&quot;0px&quot;},&quot;background&quot;:{&quot;color&quot;:&quot;#d3cfcf42&quot;},&quot;layout&quot;:{&quot;align&quot;:&quot;left&quot;},&quot;border&quot;:{&quot;color&quot;:&quot;#000&quot;,&quot;style&quot;:&quot;solid&quot;,&quot;width&quot;:&quot;0px&quot;},&quot;shadow&quot;:[],&quot;alignment&quot;:&quot;center&quot;}'><\/div>\r\n\r\n\t\t\n\n\n<p class=\"wp-block-paragraph\">\u4e0d\u53bb\u9664\u6807\u7b7e\uff08\u5143\u6570\u636e\uff09\u68c0\u6d4b\uff1a<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-contrast-3-color\">\u628a\u539f\u6709\u7684def md5_of_audio(self, filepath)\u53ca\u5176\u5185\u5bb9\uff08\u5171\u7ea610\u4f59\u884c\uff09\u66ff\u6362\u4e3a\u4ee5\u4e0b\u5185\u5bb9\u5373\u53ef\u3002\u4f18\u70b9\u662f\u901f\u5ea6\u6781\u5feb\uff0c\u4f46\u53bb\u91cd\u6548\u679c\u4e0d\u4f73\u3002<\/mark><\/em><\/p>\n\n\n\t\t<div class='wp-block-bch-code-highlight  align' id='bhcCodeHighlight-a3649187-6' data-attributes='{&quot;cId&quot;:&quot;a3649187-6&quot;,&quot;language&quot;:&quot;python&quot;,&quot;code&quot;:&quot;    # \\u4e0d\\u53bb\\u6389\\u6807\\u7b7e\\u68c0\\u6d4b\\n    def md5_of_audio(self, filepath):\\n        hash_md5 = hashlib.md5()\\n        with open(filepath, &#039;rb&#039;) as f:\\n            for chunk in iter(lambda: f.read(8192), b&#039;&#039;):\\n                hash_md5.update(chunk)\\n                return hash_md5.hexdigest()&quot;,&quot;codeTypo&quot;:{&quot;desktop&quot;:9,&quot;tablet&quot;:15,&quot;mobile&quot;:14},&quot;align&quot;:&quot;&quot;,&quot;lineNumbers&quot;:true,&quot;theme&quot;:&quot;default&quot;,&quot;clipBoard&quot;:true,&quot;wordWrap&quot;:true,&quot;width&quot;:{&quot;desktop&quot;:&quot;100%&quot;,&quot;tablet&quot;:&quot;100%&quot;,&quot;mobile&quot;:&quot;100%&quot;},&quot;height&quot;:{&quot;desktop&quot;:&quot;0px&quot;,&quot;tablet&quot;:&quot;0px&quot;,&quot;mobile&quot;:&quot;0px&quot;},&quot;padding&quot;:{&quot;top&quot;:&quot;0px&quot;,&quot;right&quot;:&quot;0px&quot;,&quot;bottom&quot;:&quot;0px&quot;,&quot;left&quot;:&quot;0px&quot;},&quot;background&quot;:{&quot;color&quot;:&quot;#d3cfcf42&quot;},&quot;layout&quot;:{&quot;align&quot;:&quot;left&quot;},&quot;border&quot;:{&quot;color&quot;:&quot;#000&quot;,&quot;style&quot;:&quot;solid&quot;,&quot;width&quot;:&quot;0px&quot;},&quot;shadow&quot;:[],&quot;alignment&quot;:&quot;center&quot;,&quot;clipBoardColors&quot;:{&quot;color&quot;:&quot;#fff&quot;,&quot;bg&quot;:&quot;#00000024&quot;}}'><\/div>\r\n\r\n\t\t\n\n\n<h1 class=\"wp-block-heading\">\u6253\u5305\u4e3a\u53ef\u6267\u884c\u6587\u4ef6<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">\u5728.py\u6240\u5728\u7684\u6587\u4ef6\u5939\u7684\u5730\u5740\u680f\u8986\u76d6\u8f93\u5165cmd\u540e\u56de\u8f66\uff0c\u952e\u5165\u4ee5\u4e0b\u547d\u4ee4\uff08\u9009\u5176\u4e00\uff09\u3002<\/p>\n\n\n\n<h5 class=\"wp-block-heading\">\u65b9\u6cd5A\u2014\u2014\u8ffd\u6c42\u6253\u5f00\u65f6\u7684\u6548\u7387\uff0c\u4f46\u591a\u6587\u4ef6<\/h5>\n\n\n\n<pre class=\"wp-block-code\"><code>pyinstaller --windowed MP3_MD5.py<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">\u751f\u6210\u7684\u7ed3\u6784\uff1a<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\ud83d\udcc2 dist<br>\u2514\u2500\u2500 \ud83d\udcc2 <br>\u251c\u2500\u2500 MP3_MD5.exe \u2190 \u4e3b\u7a0b\u5e8f<br>\u251c\u2500\u2500 ffmpeg.exe \u2190 <strong>\u624b\u52a8\u590d\u5236\u8fc7\u6765\uff08\u5fc5\u987b\uff09<\/strong><br>\u251c\u2500\u2500 mutagen\/\u2026 \u2190 \u81ea\u52a8\u6253\u5305<br>\u251c\u2500\u2500 \u6240\u9700\u7684DLL\u7b49\u652f\u6301\u5e93<\/p>\n\n\n\n<h5 class=\"wp-block-heading\">\u65b9\u6cd5B\u2014\u2014\u4ec5\u4e3b\u7a0b\u5e8f\u548cFFmpeg\uff0c\u6253\u5f00\u6548\u7387\u4e2d\u7b49<\/h5>\n\n\n\n<p class=\"wp-block-paragraph\"><em>\u6253\u5305\u540e\uff0c\u540c\u6837\u9700\u8981\u624b\u52a8\u590d\u5236ffmpeg.exe\u5230\u4e3b\u7a0b\u5e8f\u6240\u5728\u76ee\u5f55\u3002<\/em><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pyinstaller --onefile --windowed MP3MD5.py<\/code><\/pre>\n\n\n\n<h5 class=\"wp-block-heading\">\u65b9\u6cd5C\u2014\u2014\u4ec5\u4e00\u4e2aexe\u6587\u4ef6\uff0c\u4f46\u6253\u5f00\u6548\u7387\u6700\u6162<\/h5>\n\n\n\n<p class=\"wp-block-paragraph\">\u63d0\u793a\uff1a\u8be5\u65b9\u6cd5\u8fd8\u9700\u63d0\u524d\u5728\u8c03\u7528 ffmpeg \u7684\u5730\u65b9\u4f7f\u7528\u3002\u672c\u4eba<strong>\u672a\u6d4b\u8bd5\u6210\u529f<\/strong>\u3002<\/p>\n\n\n\n<details class=\"wp-block-details is-layout-flow wp-block-details-is-layout-flow\"><summary>\u70b9\u51fb\u5c55\u5f00<\/summary>\n<p class=\"wp-block-paragraph\">1\u3001\u5728\u8c03\u7528 ffmpeg \u7684\u5730\u65b9\u4f7f\u7528\uff1a<code>ffmpeg = get_ffmpeg_path()<br>subprocess.run([ffmpeg, \"-version\"])<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">2\u3001\u5c06\u4ee5\u4e0b\u4ee3\u7801\u63d2\u5165\u81f3py\u6587\u4ef6\uff0c\u5927\u7ea6\u7b2c9\u884c\u3002<\/p>\n\n\n\t\t<div class='wp-block-bch-code-highlight  align' id='bhcCodeHighlight-cf10423a-5' data-attributes='{&quot;cId&quot;:&quot;cf10423a-5&quot;,&quot;code&quot;:&quot;import sys  # \\u786e\\u4fdd\\u4f60\\u5bfc\\u5165\\u4e86 sys \\u6a21\\u5757\\ndef get_ffmpeg_path():\\n    # \\u83b7\\u53d6 ffmpeg.exe \\u7684\\u8def\\u5f84\\uff0c\\u517c\\u5bb9\\u6253\\u5305\\u548c\\u672a\\u6253\\u5305\\u73af\\u5883\\n    if hasattr(sys, &#039;_MEIPASS&#039;):\\n        return os.path.join(sys._MEIPASS, \\&quot;ffmpeg.exe\\&quot;)\\n    else:\\n        return os.path.join(os.path.dirname(__file__), \\&quot;ffmpeg.exe\\&quot;)&quot;,&quot;codeTypo&quot;:{&quot;desktop&quot;:9,&quot;tablet&quot;:15,&quot;mobile&quot;:14},&quot;align&quot;:&quot;&quot;,&quot;language&quot;:&quot;javascript&quot;,&quot;lineNumbers&quot;:true,&quot;theme&quot;:&quot;default&quot;,&quot;clipBoard&quot;:true,&quot;wordWrap&quot;:true,&quot;width&quot;:{&quot;desktop&quot;:&quot;100%&quot;,&quot;tablet&quot;:&quot;100%&quot;,&quot;mobile&quot;:&quot;100%&quot;},&quot;height&quot;:{&quot;desktop&quot;:&quot;0px&quot;,&quot;tablet&quot;:&quot;0px&quot;,&quot;mobile&quot;:&quot;0px&quot;},&quot;padding&quot;:{&quot;top&quot;:&quot;0px&quot;,&quot;right&quot;:&quot;0px&quot;,&quot;bottom&quot;:&quot;0px&quot;,&quot;left&quot;:&quot;0px&quot;},&quot;background&quot;:{&quot;color&quot;:&quot;#d3cfcf42&quot;},&quot;layout&quot;:{&quot;align&quot;:&quot;left&quot;},&quot;border&quot;:{&quot;color&quot;:&quot;#000&quot;,&quot;style&quot;:&quot;solid&quot;,&quot;width&quot;:&quot;0px&quot;},&quot;shadow&quot;:[],&quot;alignment&quot;:&quot;center&quot;,&quot;clipBoardColors&quot;:{&quot;color&quot;:&quot;#fff&quot;,&quot;bg&quot;:&quot;#00000024&quot;}}'><\/div>\r\n\r\n\t\t\n\n\n<p class=\"wp-block-paragraph\">3\u3001\u4f7f\u7528\u4ee5\u4e0b\u547d\u4ee4\u6253\u5305\uff1a<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pyinstaller --onefile --windowed --add-binary \"ffmpeg.exe;.\" MP3MD5.py<\/code><\/pre>\n<\/details>\n\n\n\n<h1 class=\"wp-block-heading\">\u7b2c\u4e09\u65b9\u5de5\u5177\u6216\u5e93\u7684\u89e3\u91ca<\/h1>\n\n\n\n<h3 class=\"wp-block-heading\">ffmpeg<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\u987b\u5229\u7528ffmpeg\u7684\u5982\u4e0b\u547d\u4ee4<strong>\u5c06 MP3 \u8f6c\u4e3a\u65e0\u6807\u7b7e\u7684\u97f3\u9891\u6570\u636e<\/strong>\uff1a\nffmpeg -y -i input.mp3 -vn -f wav output.wav\n\u4e0d\u4f9d\u8d56\u5b83\u5c31\u65e0\u6cd5\u63d0\u53d6\u97f3\u9891\u3001\u4e5f\u65e0\u6cd5\u4fdd\u8bc1 MD5 \u662f\u201c\u97f3\u9891\u4e00\u81f4\u6027\u201d\u7684\u6807\u5fd7\u3002<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>\u5b98\u65b9\u7f51\u7ad9\uff1ahttps:\/\/ffmpeg.org\/download.html\uff08\u5982\u9700\u5728\u6b64\u5904\u4e0b\u8f7d\uff0c\u9009\u62e9release full\u7684\u538b\u7f29\u5305\uff09<\/code><\/pre>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u8bbf\u95ee\uff1a<a href=\"https:\/\/www.123912.com\/s\/iJ9WTd-HcRmH\">https:\/\/www.123912.com\/s\/iJ9WTd-HcRmH<\/a>\uff0c\u4e0b\u8f7d\u5176\u4e2d\u76843\u4e2aexe<\/li>\n\n\n\n<li>\u914d\u7f6e\u73af\u5883\u53d8\u91cf\uff1a\n<ul class=\"wp-block-list\">\n<li>\u5728\u201c\u7cfb\u7edf\u53d8\u91cf\u201d\u4e2d\u627e\u5230 <code>Path<\/code>\uff0c\u70b9\u51fb\u7f16\u8f91\u3001\u65b0\u5efa\uff0c\u6dfb\u52a0bin\u7684\u8def\u5f84<em>\uff08\u6807\u51c6\u505a\u6cd5\uff0c\u4f46\u672c\u4eba\u6d4b\u8bd5\u672a\u6210\u529f\uff09<\/em><\/li>\n\n\n\n<li>\u5c06\u4e09\u4e2aexe\u653e\u7f6e\u4e8e\uff1a<code>C:\\windows\\<\/code><em>\uff08\u8be5\u65b9\u6cd5\u6d4b\u8bd5\u6210\u529f\uff09<\/em><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>\u6d4b\u8bd5\n<ul class=\"wp-block-list\">\n<li>CMD\u4e2d\u8fd0\u884c\uff1a<code>ffmpeg -version<\/code>\uff0c\u82e5\u6709\u6b63\u786e\u8f93\u51fa\uff0c\u5219\u4ee3\u8868\u6210\u529f\u3002<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">mutagen\uff08\u4e3a\u8bfb\u53d6\u97f3\u9891\u6587\u4ef6\u7684\u5143\u6570\u636e\uff09<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>mutagen \u662f Python \u91cc\u4e00\u4e2a\u975e\u5e38\u6d41\u884c\u7684\u97f3\u9891\u6807\u7b7e\u5904\u7406\u5e93\uff0c\u4e13\u95e8\u7528\u6765\u8bfb\u5199\u5404\u79cd\u97f3\u9891\u6587\u4ef6\u7684\u5143\u6570\u636e\uff08\u6807\u7b7e\uff09\uff0c\u6bd4\u5982 MP3\u3001FLAC\u3001MP4\u3001OGG \u7b49\u683c\u5f0f\u3002<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">\u4f7f\u7528\u4ee5\u4e0b\u547d\u4ee4\u5b89\u88c5<\/p>\n\n\n\t\t<div class='wp-block-bch-code-highlight  align' id='bhcCodeHighlight-2ffc55d7-8' data-attributes='{&quot;cId&quot;:&quot;2ffc55d7-8&quot;,&quot;code&quot;:&quot;pip install mutagen&quot;,&quot;align&quot;:&quot;&quot;,&quot;language&quot;:&quot;javascript&quot;,&quot;lineNumbers&quot;:true,&quot;theme&quot;:&quot;default&quot;,&quot;codeTypo&quot;:{&quot;desktop&quot;:18,&quot;tablet&quot;:15,&quot;mobile&quot;:14},&quot;clipBoard&quot;:true,&quot;wordWrap&quot;:true,&quot;width&quot;:{&quot;desktop&quot;:&quot;100%&quot;,&quot;tablet&quot;:&quot;100%&quot;,&quot;mobile&quot;:&quot;100%&quot;},&quot;height&quot;:{&quot;desktop&quot;:&quot;0px&quot;,&quot;tablet&quot;:&quot;0px&quot;,&quot;mobile&quot;:&quot;0px&quot;},&quot;padding&quot;:{&quot;top&quot;:&quot;0px&quot;,&quot;right&quot;:&quot;0px&quot;,&quot;bottom&quot;:&quot;0px&quot;,&quot;left&quot;:&quot;0px&quot;},&quot;background&quot;:{&quot;color&quot;:&quot;#d3cfcf42&quot;},&quot;layout&quot;:{&quot;align&quot;:&quot;left&quot;},&quot;border&quot;:{&quot;color&quot;:&quot;#000&quot;,&quot;style&quot;:&quot;solid&quot;,&quot;width&quot;:&quot;0px&quot;},&quot;shadow&quot;:[],&quot;alignment&quot;:&quot;center&quot;,&quot;clipBoardColors&quot;:{&quot;color&quot;:&quot;#fff&quot;,&quot;bg&quot;:&quot;#00000024&quot;}}'><\/div>\r\n\r\n\t\t\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"651\" height=\"356\" src=\"https:\/\/blog.kangyue.pro\/wp-content\/uploads\/2025\/06\/image-98.png\" alt=\"\" class=\"wp-image-3155\"\/><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">\u6ce8\u91ca<\/h1>\n\n\n\n<ul class=\"wp-block-list\">\n<li>MD5\uff1a\u5c5e\u4e8e\u54c8\u5e0c\u7684\u4e00\u79cd\uff0c\u8ba1\u7b97\u901f\u5ea6\u6700\u5feb\u3002<\/li>\n<\/ul>\n\n\n\n<h1 class=\"wp-block-heading\">\u6210\u54c1\u5de5\u5177<\/h1>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/www.123912.com\/s\/iJ9WTd-acRmH\">https:\/\/www.123912.com\/s\/iJ9WTd-acRmH<\/a>\uff08\u591a\u6587\u4ef6\u7248\u538b\u7f29\u5305\uff0c\u63a8\u8350\uff09<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/www.123912.com\/s\/iJ9WTd-fcRmH\">https:\/\/www.123912.com\/s\/iJ9WTd-fcRmH<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>\u9700\u6c42\/\u6700\u7ec8\u6548\u679c \u754c\u9762\u622a\u56fe \u6240\u9700\u73af\u5883\/\u5de5\u5177\/\u5e93 \u8c03\u6559\u8fc7\u7a0b \u5b9e\u9645\u5171\u8c03\u6559\u3001\u6d4b\u8bd5\u6570\u5341\u6b21\u3002\u4e0b\u8868\u4ec5\u5c55\u793a\u76f8\u5bf9\u91cd\u8981\u9879\u3002 \u8c03\u6559\u524d [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[20,91,5,6],"tags":[],"class_list":["post-3154","post","type-post","status-publish","format-standard","hentry","category-windows","category-91","category-5","category-6"],"_links":{"self":[{"href":"https:\/\/blog.kangyue.pro\/index.php?rest_route=\/wp\/v2\/posts\/3154","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.kangyue.pro\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.kangyue.pro\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.kangyue.pro\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.kangyue.pro\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3154"}],"version-history":[{"count":47,"href":"https:\/\/blog.kangyue.pro\/index.php?rest_route=\/wp\/v2\/posts\/3154\/revisions"}],"predecessor-version":[{"id":3719,"href":"https:\/\/blog.kangyue.pro\/index.php?rest_route=\/wp\/v2\/posts\/3154\/revisions\/3719"}],"wp:attachment":[{"href":"https:\/\/blog.kangyue.pro\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3154"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.kangyue.pro\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3154"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.kangyue.pro\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3154"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}