開源工具軟體XMusicDownloader——音樂下載神器
XMusicDownloader ,一款 支援從百度、網易、qq和酷狗等音樂網站搜尋並下載歌曲的程式。
緣起:
一直用網易音樂聽歌,但是諸如李健、周杰倫的不少歌曲,網易都沒有版權,要從QQ等音樂去下載,因此一直想寫一個小程式,可以從其他音樂網站下載相關歌曲,趁放假,花了幾小時做了這樣一個程式。
BTW: 之前寫過一個 從酷狗和網易音樂提取快取檔案的程式 ,感興趣的可以檢視。
功能
- 聚合搜尋多家音樂網站
- 支援音樂批量下載
- 搜尋結果綜合排序
- 可以編寫Provider程式,支援其他音樂網站
實現IMusicProvider即可,主要是搜尋和獲取下載連結的方法。
public interface IMusicProvider { string Name { get; } string getDownloadUrl(Song song); List<Song> SearchSongs(string keyword, int page, int pageSize); }
介面截圖
下載程式
https://github.com/jadepeng/XMusicDownloader/releases
實現方案介紹
定義song實體
public class Song { public string id { get; set; } public string name { get; set; } public string singer { get; set; } public string album { get; set; } public string source { get; set; } public double duration { get; set; } public double size { get; set; } public string url { get; set; } public int rate { get; set; } public int index { get; set; } public string getFileName() { return singer + "-" + name + ".mp3"; } public string getMergedKey() { return singer.Replace(" ", "") + name.Replace(" ", ""); } }
封裝各個音樂網站
抽象為MusicProvider,音樂提供方:),定義Name為名稱,SearchSongs搜尋歌曲,getDownloadUrl獲取音樂下載地址。
public interface IMusicProvider { string Name { get; } string getDownloadUrl(Song song); List<Song> SearchSongs(string keyword, int page, int pageSize); }
然後就是依次實現百度、網易等音樂網站,以QQ為例。
public class QQProvider : IMusicProvider { static HttpConfig DEFAULT_CONFIG = new HttpConfig { Referer = "http://m.y.qq.com", }; public string Name { get; } = "QQ"; static string[] prefixes = new string[] { "M800", "M500", "C400" }; public List<Song> SearchSongs(string keyword,int page,int pageSize) { var searchResult = HttpHelper.GET(string.Format("http://c.y.qq.com/soso/fcgi-bin/search_for_qq_cp?w={0}&format=json&p={1}&n={2}", keyword, page,pageSize), DEFAULT_CONFIG); var searchResultJson = JsonParser.Deserialize(searchResult).data.song; var result = new List<Song>(); var index = 1; foreach(var songItem in searchResultJson.list) { var song = new Song { id = songItem["songmid"], name = songItem["songname"], album = songItem["albumname"], rate = 128, size = songItem["size128"], source = Name, index = index++, duration = songItem["interval"] }; song.singer = ""; foreach (var ar in songItem["singer"]) { song.singer += ar["name"] + " "; } result.Add(song); } return result; } public string getDownloadUrl(Song song) { var guid = new Random().Next(1000000000, 2000000000); var key = JsonParser.Deserialize(HttpHelper.GET(string.Format("http://base.music.qq.com/fcgi-bin/fcg_musicexpress.fcg?guid={0}&format=json&json=3",guid), DEFAULT_CONFIG)).key; foreach(var prefix in prefixes) { var musicUrl = string.Format("http://dl.stream.qqmusic.qq.com/{0}{1}.mp3?vkey={2}&guid={3}&fromtag=1", prefix, song.id, key, guid); if (HttpHelper.GetUrlContentLength(musicUrl) > 0) { return musicUrl; } } return null; } }
- 搜尋呼叫
http://c.y.qq.com/soso/fcgi-bin/search_for_qq_cp?w={0}&format=json&p={1}&n={2}
介面,獲取下載地址呼叫http://base.music.qq.com/fcgi-bin/fcg_musicexpress.fcg?guid={0}&format=json&json=3
,然後再組合。
聚合搜尋
設計一個MusicProviders,載入所有的IMusicProvider,提供一個SearchSongs方法,併發呼叫各個網站的搜尋,然後merge到一起。
public List<MergedSong> SearchSongs(string keyword, int page, int pageSize) { var songs = new List<Song>(); Providers.AsParallel().ForAll(provider => { var currentSongs = provider.SearchSongs(keyword, page, pageSize); songs.AddRange(currentSongs); }); // merge return songs.GroupBy(s => s.getMergedKey()).Select(g => new MergedSong(g.ToList())).OrderByDescending(s => s.score).ToList(); }
關於merge,核心就是將相同的歌曲合併到一起,我們暫且認為歌手+歌曲名相同的為同一首歌曲:
public string getMergedKey() { return singer.Replace(" ", "") + name.Replace(" ", ""); }
因此按megekey分組,就能實現聚合。我們設計一個 MergedSong
來包裹。
public class MergedSong { public List<Song> items { get; set; } public MergedSong(List<Song> items) { this.items = items; } public string name { get { return this.items[0].name; } } public string singer { get { return this.items[0].singer; } } public string album { get { return this.items[0].album; } } public string source { get { return string.Join(",", this.items.Select(i => i.source).ToArray()); } } public double duration { get { return this.items[0].duration; } } public double size { get { return this.items[0].size; } } public double rate { get { return this.items[0].rate; } } public double score { get { // 投票+排序加權(各50%) return this.items.Count / (MusicProviders.Instance.Providers.Count - 1) + (20 - this.items.Average(i => i.index)) / 20; } } }
MergedSong的核心是定義了一個score,我們通過投票+搜尋結果排序,用來決定合併結果的排序。
下載
下載主要是通過provider獲取真實url,然後下載即可。
public class SongItemDownloader { MusicProviders musicProviders; string target; MergedSong song; public event DownloadFinishEvent DownloadFinish; public SongItemDownloader(MusicProviders musicProviders, string target, MergedSong song) { this.musicProviders = musicProviders; this.target = target; this.song = song; } public long totalBytes; public long bytesReceived; public double ReceiveProgress; public double receiveSpeed; DateTime lastTime = DateTime.Now; public void Download() { WebClient client = new WebClient(); client.DownloadProgressChanged += Client_DownloadProgressChanged; new Thread(() => { // 多來源,防止單個來源出錯 foreach (var item in song.items) { try { client.DownloadFile(musicProviders.getDownloadUrl(item), target + "\\" + item.getFileName()); DownloadFinish?.Invoke(this, this); break; } catch { } } }).Start(); } private void Client_DownloadProgressChanged(object sender, DownloadEventArgs e) { this.bytesReceived = e.bytesReceived; this.totalBytes = e.totalBytes; this.receiveSpeed = e.receiveSpeed; this.ReceiveProgress = e.ReceiveProgress; } }
參考
- 程式介面,使用了https://github.com/Gsangu/KugouDownloader程式碼
- 搜尋和下載方案參考 https://github.com/0xHJK/music-dl
作者:Jadepeng
出處:jqpeng的技術記事本-- http://www.cnblogs.com/xiaoqi
您的支援是對博主最大的鼓勵,感謝您的認真閱讀。
本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。