Przeglądaj źródła

新增音频文件转码
新增音频导出至html文本

Suxue 1 rok temu
rodzic
commit
ef2024f90e
8 zmienionych plików z 251 dodań i 6 usunięć
  1. 166 0
      Helpers/ToolsHelper.cs
  2. 10 0
      HtmlExport.cs
  3. 8 0
      Model/WXModel.cs
  4. 7 6
      README.md
  5. BIN
      Tools/ffmpeg.exe
  6. BIN
      Tools/silk_v3_decoder.exe
  7. 52 0
      WXReader.cs
  8. 8 0
      WechatPCMsgBakTool.csproj

+ 166 - 0
Helpers/ToolsHelper.cs

@@ -0,0 +1,166 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace WechatPCMsgBakTool.Helpers
+{
+    public class ToolsHelper
+    {
+        public static TaskFactory factory = new TaskFactory(new LimitedConcurrencyLevelTaskScheduler(10));
+        public static string DecodeVoice(string source,string pcm,string to)
+        {
+            string ffmpeg = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Tools", "ffmpeg.exe");
+            string silk_decoder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Tools", "silk_v3_decoder.exe");
+
+            Task task = factory.StartNew(() =>
+            {
+                Process silk = new Process();
+                silk.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
+                silk.StartInfo.UseShellExecute = false;
+                silk.StartInfo.CreateNoWindow = true;
+                silk.StartInfo.FileName = silk_decoder;
+                silk.StartInfo.Arguments = string.Format("\"{0}\" \"{1}\"", source, pcm);
+                silk.Start();
+                silk.WaitForExit();
+
+                if (File.Exists(pcm))
+                {
+                    Process ff = new Process();
+                    ff.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
+                    ff.StartInfo.UseShellExecute= false;
+                    ff.StartInfo.CreateNoWindow = true;
+                    ff.StartInfo.FileName = ffmpeg;
+                    ff.StartInfo.Arguments = string.Format(" -y -f s16le -ar 24000 -ac 1 -i \"{0}\" -ar 24000 -b:a 320k \"{1}\"", pcm, to);
+                    ff.Start();
+                    ff.WaitForExit();
+                }
+            });
+            return "";
+        }
+
+    }
+
+    public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
+    {
+        // Indicates whether the current thread is processing work items.
+        [ThreadStatic]
+        private static bool _currentThreadIsProcessingItems;
+
+        // The list of tasks to be executed
+        private readonly LinkedList<Task> _tasks = new LinkedList<Task>(); // protected by lock(_tasks)
+
+        // The maximum concurrency level allowed by this scheduler.
+        private readonly int _maxDegreeOfParallelism;
+
+        // Indicates whether the scheduler is currently processing work items.
+        private int _delegatesQueuedOrRunning = 0;
+
+        // Creates a new instance with the specified degree of parallelism.
+        public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism)
+        {
+            if (maxDegreeOfParallelism < 1) throw new ArgumentOutOfRangeException("maxDegreeOfParallelism");
+            _maxDegreeOfParallelism = maxDegreeOfParallelism;
+        }
+
+        // Queues a task to the scheduler.
+        protected sealed override void QueueTask(Task task)
+        {
+            // Add the task to the list of tasks to be processed.  If there aren't enough
+            // delegates currently queued or running to process tasks, schedule another.
+            lock (_tasks)
+            {
+                _tasks.AddLast(task);
+                if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism)
+                {
+                    ++_delegatesQueuedOrRunning;
+                    NotifyThreadPoolOfPendingWork();
+                }
+            }
+        }
+
+        // Inform the ThreadPool that there's work to be executed for this scheduler.
+        private void NotifyThreadPoolOfPendingWork()
+        {
+            ThreadPool.UnsafeQueueUserWorkItem(_ =>
+            {
+                // Note that the current thread is now processing work items.
+                // This is necessary to enable inlining of tasks into this thread.
+                _currentThreadIsProcessingItems = true;
+                try
+                {
+                    // Process all available items in the queue.
+                    while (true)
+                    {
+                        Task item;
+                        lock (_tasks)
+                        {
+                            // When there are no more items to be processed,
+                            // note that we're done processing, and get out.
+                            if (_tasks.Count == 0)
+                            {
+                                --_delegatesQueuedOrRunning;
+                                break;
+                            }
+
+                            // Get the next item from the queue
+                            item = _tasks.First.Value;
+                            _tasks.RemoveFirst();
+                        }
+
+                        // Execute the task we pulled out of the queue
+                        base.TryExecuteTask(item);
+                    }
+                }
+                // We're done processing items on the current thread
+                finally { _currentThreadIsProcessingItems = false; }
+            }, null);
+        }
+
+        // Attempts to execute the specified task on the current thread.
+        protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
+        {
+            // If this thread isn't already processing a task, we don't support inlining
+            if (!_currentThreadIsProcessingItems) return false;
+
+            // If the task was previously queued, remove it from the queue
+            if (taskWasPreviouslyQueued)
+                // Try to run the task.
+                if (TryDequeue(task))
+                    return base.TryExecuteTask(task);
+                else
+                    return false;
+            else
+                return base.TryExecuteTask(task);
+        }
+
+        // Attempt to remove a previously scheduled task from the scheduler.
+        protected sealed override bool TryDequeue(Task task)
+        {
+            lock (_tasks) return _tasks.Remove(task);
+        }
+
+        // Gets the maximum concurrency level supported by this scheduler.
+        public sealed override int MaximumConcurrencyLevel { get { return _maxDegreeOfParallelism; } }
+
+        // Gets an enumerable of the tasks currently scheduled on this scheduler.
+        protected sealed override IEnumerable<Task> GetScheduledTasks()
+        {
+            bool lockTaken = false;
+            try
+            {
+                Monitor.TryEnter(_tasks, ref lockTaken);
+                if (lockTaken) return _tasks;
+                else throw new NotSupportedException();
+            }
+            finally
+            {
+                if (lockTaken) Monitor.Exit(_tasks);
+            }
+        }
+    }
+}

+ 10 - 0
HtmlExport.cs

@@ -73,6 +73,16 @@ namespace WechatPCMsgBakTool
                     }
                     HtmlBody += string.Format("<p class=\"content\"><video controls style=\"max-height:300px;max-width:300px;\"><source src=\"{0}\" type=\"video/mp4\" /></video></p></div>", path);
                 }
+                else if(msg.Type == 34)
+                {
+                    string? path = reader.GetVoice(msg);
+                    if (path == null)
+                    {
+                        HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "视频不存在");
+                        continue;
+                    }
+                    HtmlBody += string.Format("<p class=\"content\"><audio controls src=\"{0}\"></audio></p></div>", path);
+                }
                 else
                 {
                     HtmlBody += string.Format("<p class=\"content\">{0}</p></div>", "暂未支持的消息");

+ 8 - 0
Model/WXModel.cs

@@ -85,6 +85,14 @@ namespace WechatPCMsgBakTool.Model
         public string StrContent { get; set; } = "";
     }
 
+    [Table("Media")]
+    public class WXMediaMsg
+    {
+        public int Key { get; set; }
+        public byte[]? Buf { get; set; }
+        public string Reserved0 { get; set; } = "";
+    }
+
     [Table("Contact")]
     public class WXContact
     {

+ 7 - 6
README.md

@@ -1,8 +1,8 @@
 # WechatPCMsgBakTool
 微信PC聊天记录备份工具,仅支持Windows
 
-- 支持3.9.6.33版本
-- 导出图片、视频
+- 当前仅支持3.9.6.33版本,后续将版本文件拆分出来
+- 导出图片、视频、音频
 - 导出Html文件
 
 本项目仅做学习使用,供个人备份自己的微信,请勿做其他用途使用。
@@ -12,7 +12,7 @@
 #### 使用
 <p>1.打开微信,并登录。</p>
 <p>2.将微信设置内的个人目录,填入用户文件夹的文本框,注意,需要带账号,你从微信设置里面点点打开文件夹那个路径就是对的了。</p>
-<p>3.依次点击,确定,解密,读取,即可在左侧见到会话列表了。>3.依次点击,确定,解密,读取,即可在左侧见到会话列表了。</p>
+<p>3.依次点击,确定,解密,读取,即可在左侧见到会话列表了。</p>
 <p>4.如果会话列表内没有这个人,你可以按账号搜索。</p>
 <p>5.如果使用过程中发生崩溃,请删除工作区试一下,工作区即根据用户名在运行目录下生成的md5文件夹。</p>
 <p>6.如果你已经读取过一次数据了,想离线使用,请填入微信个人目录后,选择使用已解密的工作区读取,即可本地离线加载。</p>
@@ -20,6 +20,7 @@
 
 #### 参考/引用
 都是站在大佬们的肩膀上完成的项目,本项目 参考/引用 了以下 项目/文章 内代码。
-##### [Mr0x01/WXDBDecrypt.NET](https://github.com/Mr0x01/WXDBDecrypt.NET")
-##### [AdminTest0/SharpWxDump](https://github.com/AdminTest0/SharpWxDump")
-##### [吾爱破解chenhahacjl/微信 DAT 图片解密 (C#)](https://www.52pojie.cn/forum.php?mod=viewthread&tid=1507922")
+##### [Mr0x01/WXDBDecrypt.NET](https://github.com/Mr0x01/WXDBDecrypt.NET)
+##### [AdminTest0/SharpWxDump](https://github.com/AdminTest0/SharpWxDump)
+##### [kn007/silk-v3-decoder](https://github.com/kn007/silk-v3-decoder)
+##### [吾爱破解chenhahacjl/微信 DAT 图片解密 (C#)](https://www.52pojie.cn/forum.php?mod=viewthread&tid=1507922)

BIN
Tools/ffmpeg.exe


BIN
Tools/silk_v3_decoder.exe


+ 52 - 0
WXReader.cs

@@ -6,6 +6,7 @@ using System.IO;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using System.Windows;
 using System.Windows.Documents;
 using System.Windows.Interop;
 using WechatPCMsgBakTool.Helpers;
@@ -96,6 +97,22 @@ namespace WechatPCMsgBakTool
             return tmp;
         }
 
+        public WXMediaMsg? GetVoiceMsg(string msgid)
+        {
+            for (int i = 0; i <= DecDBInfo.MaxMediaDBCount; i++)
+            {
+                SQLiteConnection con = DBInfo["MediaMSG" + i.ToString()];
+                if (con == null)
+                    continue;
+
+                string query = "select * from Media where Reserved0=?";
+                List<WXMediaMsg> wXMsgs = con.Query<WXMediaMsg>(query, msgid);
+                if(wXMsgs.Count != 0)
+                    return wXMsgs[0];
+            }
+            return null;
+        }
+
         public string? GetVideo(WXMsg msg)
         {
             WXSessionAttachInfo? attachInfo = GetWXMsgAtc(msg);
@@ -115,9 +132,43 @@ namespace WechatPCMsgBakTool
 
             if(!File.Exists(savePath))
                 File.Copy(resBasePath, savePath, false);
+
+            savePath = savePath.Replace(DecDBInfo.UserPath + "\\", "");
             return savePath;
         }
 
+        public string? GetVoice(WXMsg msg) { 
+            string tmp = Path.Combine(DecDBInfo.UserPath, msg.StrTalker, "tmp");
+            if (!Directory.Exists(tmp))
+            {
+                Directory.CreateDirectory(tmp);
+            }
+            WXMediaMsg? voiceMsg = GetVoiceMsg(msg.MsgSvrID);
+            if(voiceMsg != null)
+            {
+                if (voiceMsg.Buf == null)
+                    return null;
+
+                string voicePath = Path.Combine(DecDBInfo.UserPath, msg.StrTalker, "Voice");
+                if (!Directory.Exists(voicePath))
+                    Directory.CreateDirectory(voicePath);
+                // 从DB取音频文件到临时目录
+                string tmp_file_path = Path.Combine(tmp, voiceMsg.Key + ".arm");
+                using (FileStream stream = new FileStream(tmp_file_path,FileMode.OpenOrCreate))
+                {
+                    stream.Write(voiceMsg.Buf, 0, voiceMsg.Buf.Length);
+                }
+                // 调用silk_v3_decoder解码成pcm
+                string tmp_pcm_file_path = Path.Combine(tmp, voiceMsg.Key + ".pcm");
+                // 调用ffmpeg转换成mp3
+                string mp3_file_path = Path.Combine(voicePath, voiceMsg.Key + ".mp3");
+                ToolsHelper.DecodeVoice(tmp_file_path, tmp_pcm_file_path, mp3_file_path);
+                mp3_file_path = mp3_file_path.Replace(DecDBInfo.UserPath + "\\", "");
+                return mp3_file_path;
+            }
+            return null; 
+        }
+
         public string GetSavePath(WXSession session)
         {
             string savePath = Path.Combine(DecDBInfo.UserPath, session.UserName + ".html");
@@ -147,6 +198,7 @@ namespace WechatPCMsgBakTool
                 Directory.CreateDirectory(imgPath);
 
             string img = DecImage(resBasePath, imgPath);
+            img = img.Replace(DecDBInfo.UserPath + "\\", "");
             return img;
         }
 

+ 8 - 0
WechatPCMsgBakTool.csproj

@@ -6,6 +6,8 @@
     <Nullable>enable</Nullable>
     <UseWPF>true</UseWPF>
     <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
+    <AssemblyVersion>0.2.0.0</AssemblyVersion>
+    <FileVersion>0.2.0.0</FileVersion>
   </PropertyGroup>
 
   <ItemGroup>
@@ -19,6 +21,12 @@
     <None Update="libssl-1_1-x64.dll">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </None>
+    <None Update="Tools\ffmpeg.exe">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+    <None Update="Tools\silk_v3_decoder.exe">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
   </ItemGroup>
 
 </Project>