开源小工具 酷狗、网易音乐缓存文件转mp3工具

发布一个开源小工具,支持将酷狗和网易云音乐的缓存文件转码为MP3文件。

以前写过kgtemp文件转mp3工具,正好当前又有网易云音乐缓存文件需求,因此就在原来小工具的基础上做了一点修改,增加了对网易云音乐的支持,并简单调整了下代码结构,方便后续增加其他音乐软件的支持。

工具使用介绍

下载程序(点击下载),然后启动程序,

enter description here
enter description here
  • 首先,设置输入目录,也就是解密后的文件存放在哪里
  • 然后将酷狗或者网易的缓存文件 or 整个文件夹,拖入到程序即可
转码中
转码中

打开转码结果目录,可以看到转码后的结果

转码结果
转码结果

缓存目录如何找

网易云音乐的缓存目录

打开设置 -- 下载设置 - 缓存目录就是了

enter description here
enter description here

酷狗缓存目录

如图,在设置--下载设置里

enter description here
enter description here

工具代码简要说明

类图

enter description here
enter description here

ICacheDecrypt

我们定义一个解码接口ICacheDecrypt,实现将缓存文件字节流转换为mp3字节流。

 /// <summary>
    /// 解密接口
    /// </summary>
    public interface ICacheDecrypt
    {

        string AcceptableExtension
        {
            get;
        }

        bool isAcceptable(string cacheFile);

        /// <summary>
        /// 解密文件
        /// </summary>
        /// <param name="cacheFile">缓存文件</param>
        /// <returns>解密后二进制数据</returns>
        byte[] Decrypt(string cacheFile);

        /// <summary>
        /// 解密文件
        /// </summary>
        /// <param name="cacheFileData">缓存文件数据</param>
        /// <returns></returns>
        byte[] Decrypt(byte[] cacheFileData);

        /// <summary>
        /// 解密文件
        /// </summary>
        /// <param name="cacheFile">cache文件</param>
        /// <param name="decodedFile">解密后文件</param>
        void Decrypt(string cacheFile,string decodedFile);

    }

BaseCacheDecrypt

然后,实现一个默认的抽象类BaseCacheDecrypt,实现一些公共的东西,具体的转码工作让子类去实现,比如网易和酷狗可以分别建一个子类。

public abstract class BaseCacheDecrypt : ICacheDecrypt
    {

        protected string currentCacheFile;

        public abstract string AcceptableExtension
        {
            get;
        }

        public abstract byte[] Decrypt(byte[] cacheFileData);

        public byte[] Decrypt(string cacheFile)
        {
            currentCacheFile = cacheFile;
            return Decrypt(File.ReadAllBytes(cacheFile));
        }

        public void Decrypt(string cacheFile, string decodedFile)
        {
            File.WriteAllBytes(decodedFile, Decrypt(cacheFile));
        }

        public  bool isAcceptable(string cacheFile)
        {
            return cacheFile.EndsWith(AcceptableExtension);
        }
    }

NetMusicCacheDecrypt

然后,分别实现酷狗和网易云音乐的解码工作,酷狗的上次已经写了如何解码,这里只贴网易的,解码很简单,异或0xa3就可以了。网易音乐在测试时发现好多mp3没有ID3信息,经过观察发现缓存文件名里包含歌曲的id信息,因此可以根据这个id信息去抓取歌曲网页,解析出歌手和歌曲名称,然后写入到ID3里,这里ID3的读写采用了GitHub上的一个开源库


    /// <summary>
    /// 网易云缓存解密
    /// </summary>
    public class NetMusicCacheDecrypt : BaseCacheDecrypt
    {
        public override string AcceptableExtension
        {
            get
            {
                return ".uc";
            }
        }

        string cut(string str,string start,string end)
        {
            var startIndex = str.IndexOf(start);
            if (startIndex == -1)
            {
                return "";
            }
            startIndex += start.Length;
            var endIndex = str.IndexOf(end, startIndex);
            if (endIndex == -1)
            {
                return "";
            }
            return str.Substring(startIndex, endIndex - startIndex);
        }

        public override byte[] Decrypt(byte[] cacheFileData)
        {         
            for (var i = 0; i < cacheFileData.Length; i++)
            {
                // 异或0xa3
                cacheFileData[i] ^= 0xa3;
            }

            var fileName = new FileInfo(currentCacheFile).Name;
            var songId = fileName.Substring(0, fileName.IndexOf("-"));
            var html = HttpHelper.SendGet("http://music.163.com/song?id=" + songId);
            if (html.Length > 0)
            {
                var title = cut(html, "<title>", "</title>").Trim();
                var tempFile = currentCacheFile+ Guid.NewGuid().ToString();
                File.WriteAllBytes(tempFile, cacheFileData);
                Track theTrack = new Track(tempFile);
                // 父亲写的散文诗(时光版) - 许飞 - 单曲 - 网易云音乐
                theTrack.Artist = cut(title, "-", "-").Trim();
                theTrack.Title = title.Substring(0, title.IndexOf("-")).Trim();
                // Save modifications on the disc
                theTrack.Save();
                cacheFileData = File.ReadAllBytes(tempFile);
                File.Delete(tempFile);

            }
            
            return cacheFileData;
        }

    }

接着介绍核心的Decryptor,实现转码的调度,这里的思路就是将所有的解码器放到一个list里,当一个文件过来的时候,遍历所有解码器,如果accetbale,就处理,否则跳过。
两个主要工作:

  • 加载所有的BaseCacheDecrypt
  • 进行解码工作

加载所有的BaseCacheDecrypt

两种方法,一是自己实例化,一是使用反射,这里当然用反射了:)


private Decryptor()
        {
           
        }

        public static Decryptor Instance
        {
            get
            {
                return Holder.decryptor;
            }
        }
 static class Holder
        {
            public static Decryptor decryptor = Load();


            /// <summary>
            /// 从当前Assembly加载
            /// </summary>
            /// <returns></returns>
            private static Decryptor Load()
            {
                Assembly assembly = Assembly.GetExecutingAssembly();
                List<Type> hostTypes = new List<Type>();

                foreach (var type in assembly.GetExportedTypes())
                {
                    //确定type为类并且继承自(实现)IMyInstance
                    if (type.IsClass && typeof(BaseCacheDecrypt).IsAssignableFrom(type) && !type.IsAbstract)
                        hostTypes.Add(type);
                }

                Decryptor decryptor = new Decryptor();
                foreach (var type in hostTypes)
                {
                    ICacheDecrypt instance = (ICacheDecrypt)Activator.CreateInstance(type);
                    decryptor.cacheDecryptors.Add(instance);
                }

                return decryptor;
            }
        }

Decryptor通过单例模式对外提供调用。

进行解码

判断拖入的是文件夹还是文件,文件夹的话遍历子文件,依次处理。解码方式就是钢说的,遍历decryptors,如果支持就解码。
解码完后,读取ID3信息,对文件进行重命名。

 public int Process(string path)
        {
            int success = 0;

            if (Directory.Exists(path))//如果是文件夹
            {
                DirectoryInfo dinfo = new DirectoryInfo(path);//实例化一个DirectoryInfo对象
                foreach (FileInfo fs in dinfo.GetFiles()) //查找.kgtemp文件
                {
                    ProcessFile(fs.FullName);
                    success++;
                }
            }
            else
            {
                ProcessFile(path);
                success = 1;
            }

            return success;
        }

        private string GetCleanFileName(string fileName)
        {
            StringBuilder rBuilder = new StringBuilder(fileName);
            foreach (char rInvalidChar in Path.GetInvalidFileNameChars())
                rBuilder.Replace(rInvalidChar.ToString(), string.Empty);
            return rBuilder.ToString();
        }

        private string GetTargetFileName(string fileName)
        {
            var fileinfo = new FileInfo(fileName);
            var rawName = fileinfo.Name.Substring(0, fileinfo.Name.IndexOf("."));
            return TargetDirectory + Path.DirectorySeparatorChar + rawName + ".mp3";
        }


        void ProcessFile(string fileName)
        {
            _logger.Info("开始处理" + fileName);
            try
            {
                foreach (var decryptor in cacheDecryptors)
                {
                    if (decryptor.isAcceptable(fileName))
                    {
                        var targetName = TargetDirectory + Path.DirectorySeparatorChar + new FileInfo(fileName).Name + ".mp3";

                        decryptor.Decrypt(fileName, targetName);

                        // 重命名
                        if (AutoRename)
                        {
                            var mp3 = ID3Helper.ReadMp3(targetName);

                            if (mp3.Title.Length > 0)
                            {
                                string realFileName = GetTargetFileName(GetCleanFileName(mp3.Title + "-" + mp3.Artist + ".mp3"));

                                _logger.Info("重命名" + realFileName);
                                if (File.Exists(realFileName))
                                {
                                    File.Delete(realFileName);
                                }

                                File.Move(targetName, realFileName);
                            }
                        }
                    }
                }
                _logger.Info(fileName + "处理完成");
            }
            catch(Exception ex)
            {
                _logger.Error(fileName + "出现异常" + ex.Message);
            }
          
        }

开源地址

代码托管到了GitHub,musicDecryptor, 感兴趣的可以访问进行

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 229,908评论 6 541
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,324评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,018评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,675评论 1 317
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,417评论 6 412
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,783评论 1 329
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,779评论 3 446
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,960评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,522评论 1 335
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,267评论 3 358
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,471评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,009评论 5 363
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,698评论 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,099评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,386评论 1 294
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,204评论 3 398
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,436评论 2 378

推荐阅读更多精彩内容