發布一個開源小工具,支持將酷狗和網易雲音樂的緩存文件轉碼為MP3文件。
以前寫過kgtemp文件轉mp3工具,正好當前又有網易雲音樂緩存文件需求,因此就在原來小工具的基礎上做了一點修改,增加了對網易雲音樂的支持,並簡單調整了下代碼結構,方便后續增加其他音樂軟件的支持。
工具使用介紹
下載程序(點擊下載),然后啟動程序,
- 首先,設置輸入目錄,也就是解密后的文件存放在哪里
- 然后將酷狗或者網易的緩存文件 or 整個文件夾,拖入到程序即可
打開轉碼結果目錄,可以看到轉碼后的結果
緩存目錄如何找
網易雲音樂的緩存目錄
打開設置 -- 下載設置 - 緩存目錄就是了
酷狗緩存目錄
如圖,在設置--下載設置里
工具代碼簡要說明
類圖
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, 感興趣的可以訪問進行
作者:Jadepeng
出處:jqpeng的技術記事本--http://www.cnblogs.com/xiaoqi
您的支持是對博主最大的鼓勵,感謝您的認真閱讀。
本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。