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
您的支持是對博主最大的鼓勵,感謝您的認真閱讀。
本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。