diff --git a/src/i18n/lang/en-US/settings.ts b/src/i18n/lang/en-US/settings.ts index 67f83d2..7235b30 100644 --- a/src/i18n/lang/en-US/settings.ts +++ b/src/i18n/lang/en-US/settings.ts @@ -64,6 +64,7 @@ export default { configureMusicSources: 'Configure Sources', selectedMusicSources: 'Selected sources:', noMusicSources: 'No sources selected', + gdmusicInfo: 'GD Music Station intelligently resolves music from multiple platforms automatically', autoPlay: 'Auto Play', autoPlayDesc: 'Auto resume playback when reopening the app' }, diff --git a/src/i18n/lang/zh-CN/settings.ts b/src/i18n/lang/zh-CN/settings.ts index 9fa6581..4e888ba 100644 --- a/src/i18n/lang/zh-CN/settings.ts +++ b/src/i18n/lang/zh-CN/settings.ts @@ -64,6 +64,7 @@ export default { configureMusicSources: '配置音源', selectedMusicSources: '已选音源:', noMusicSources: '未选择音源', + gdmusicInfo: 'GD音乐台可自动解析多个平台音源,自动选择最佳结果', autoPlay: '自动播放', autoPlayDesc: '重新打开应用时是否自动继续播放' }, diff --git a/src/main/set.json b/src/main/set.json index 22fd2b9..d0c7882 100644 --- a/src/main/set.json +++ b/src/main/set.json @@ -23,5 +23,5 @@ "alwaysShowDownloadButton": false, "unlimitedDownload": false, "enableMusicUnblock": true, - "enabledMusicSources": ["migu", "kugou", "pyncmd","bilibili", "youtube"] + "enabledMusicSources": ["migu", "kugou", "pyncmd", "bilibili", "youtube"] } diff --git a/src/renderer/api/gdmusic.ts b/src/renderer/api/gdmusic.ts new file mode 100644 index 0000000..760172b --- /dev/null +++ b/src/renderer/api/gdmusic.ts @@ -0,0 +1,189 @@ +import axios from 'axios'; +import type { MusicSourceType } from '@/type/music'; + +/** + * GD音乐台解析服务 + */ +export interface GDMusicResponse { + url: string; + br: number; + size: number; + md5: string; + platform: string; + gain: number; +} + +export interface ParsedMusicResult { + data: { + data: GDMusicResponse; + params: { + id: number; + type: string; + } + } +} + +/** + * 从GD音乐台解析音乐URL + * @param id 音乐ID + * @param data 音乐数据,包含名称和艺术家信息 + * @param quality 音质设置 + * @returns 解析后的音乐URL及相关信息 + */ +export const parseFromGDMusic = async ( + id: number, + data: any, + quality: string = '320' +): Promise => { + try { + // 处理不同数据结构 + if (!data) { + console.error('GD音乐台解析:歌曲数据为空'); + throw new Error('歌曲数据为空'); + } + + const songName = data.name || ''; + let artistNames = ''; + + // 处理不同的艺术家字段结构 + if (data.artists && Array.isArray(data.artists)) { + artistNames = data.artists.map(artist => artist.name).join(' '); + } else if (data.ar && Array.isArray(data.ar)) { + artistNames = data.ar.map(artist => artist.name).join(' '); + } else if (data.artist) { + artistNames = typeof data.artist === 'string' ? data.artist : ''; + } + + const searchQuery = `${songName} ${artistNames}`.trim(); + + if (!searchQuery || searchQuery.length < 2) { + console.error('GD音乐台解析:搜索查询过短', { name: songName, artists: artistNames }); + throw new Error('搜索查询过短'); + } + + // 所有可用的音乐源 + const allSources = [ + 'tencent', 'kugou', 'kuwo', 'migu', 'netease', + 'joox', 'ytmusic', 'spotify', 'qobuz', 'deezer' + ] as MusicSourceType[]; + + console.log('GD音乐台开始搜索:', searchQuery); + + // 依次尝试所有音源 + for (const source of allSources) { + try { + const result = await searchAndGetUrl(source, searchQuery, quality); + if (result) { + console.log(`GD音乐台成功通过 ${result.source} 解析音乐!`); + // 返回符合原API格式的数据 + return { + data: { + data: { + url: result.url.replace(/\\/g, ''), + br: parseInt(result.br, 10) * 1000 || 320000, + size: result.size || 0, + md5: '', + platform: 'gdmusic', + gain: 0 + }, + params: { + id: parseInt(String(id), 10), + type: 'song' + } + } + }; + } + } catch (error) { + console.error(`GD音乐台 ${source} 音源解析失败:`, error); + // 该音源失败,继续尝试下一个音源 + continue; + } + } + + console.log('GD音乐台所有音源均解析失败'); + return null; + } catch (error) { + console.error('GD音乐台解析完全失败:', error); + return null; + } +}; + +/** + * 获取音质映射 + * @param qualitySetting 设置中的音质选项 + * @returns 映射到GD音乐台的音质参数 + */ +export const getQualityMapping = (qualitySetting: string): string => { + const qualityMap: Record = { + standard: '128', + higher: '320', + exhigh: '320', + lossless: '740', + hires: '999', + jyeffect: '999', + sky: '999', + dolby: '999', + jymaster: '999' + }; + return qualityMap[qualitySetting] || '320'; +}; + +interface GDMusicUrlResult { + url: string; + br: string; + size: number; + source: string; +} + +const baseUrl = 'https://music-api.gdstudio.xyz/api.php'; + +/** + * 在指定音源搜索歌曲并获取URL + * @param source 音源 + * @param searchQuery 搜索关键词 + * @param quality 音质 + * @returns 音乐URL结果 + */ +async function searchAndGetUrl( + source: MusicSourceType, + searchQuery: string, + quality: string +): Promise { + // 1. 搜索歌曲 + const searchUrl = `${baseUrl}?types=search&source=${source}&name=${encodeURIComponent(searchQuery)}&count=1&pages=1`; + console.log(`GD音乐台尝试音源 ${source} 搜索:`, searchUrl); + + const searchResponse = await axios.get(searchUrl, { timeout: 5000 }); + + if (searchResponse.data && Array.isArray(searchResponse.data) && searchResponse.data.length > 0) { + const firstResult = searchResponse.data[0]; + if (!firstResult || !firstResult.id) { + console.log(`GD音乐台 ${source} 搜索结果无效`); + return null; + } + + const trackId = firstResult.id; + const trackSource = firstResult.source || source; + + // 2. 获取歌曲URL + const songUrl = `${baseUrl}?types=url&source=${trackSource}&id=${trackId}&br=${quality}`; + console.log(`GD音乐台尝试获取 ${trackSource} 歌曲URL:`, songUrl); + + const songResponse = await axios.get(songUrl, { timeout: 5000 }); + + if (songResponse.data && songResponse.data.url) { + return { + url: songResponse.data.url, + br: songResponse.data.br, + size: songResponse.data.size || 0, + source: trackSource + }; + } else { + console.log(`GD音乐台 ${trackSource} 未返回有效URL`); + return null; + } + } else { + console.log(`GD音乐台 ${source} 搜索结果为空`); + return null; + } +} \ No newline at end of file diff --git a/src/renderer/api/music.ts b/src/renderer/api/music.ts index 6ab822f..f0afbba 100644 --- a/src/renderer/api/music.ts +++ b/src/renderer/api/music.ts @@ -5,6 +5,7 @@ import { isElectron } from '@/utils'; import request from '@/utils/request'; import requestMusic from '@/utils/request_music'; import { cloneDeep } from 'lodash'; +import { parseFromGDMusic, getQualityMapping } from './gdmusic'; const { addData, getData, deleteData } = musicDB; @@ -79,17 +80,39 @@ export const getMusicLrc = async (id: number) => { } }; -export const getParsingMusicUrl = (id: number, data: any) => { - - if (isElectron) { - const settingStore = useSettingsStore(); +export const getParsingMusicUrl = async (id: number, data: any) => { + const settingStore = useSettingsStore(); - // 如果禁用了音乐解析功能,则直接返回空结果 - if (!settingStore.setData.enableMusicUnblock) { - return Promise.resolve({ data: { code: 404, message: '音乐解析功能已禁用' } }); - } - return window.api.unblockMusic(id, cloneDeep(data), cloneDeep(settingStore.setData.enabledMusicSources)); + // 如果禁用了音乐解析功能,则直接返回空结果 + if (!settingStore.setData.enableMusicUnblock) { + return Promise.resolve({ data: { code: 404, message: '音乐解析功能已禁用' } }); } + + // 检查是否选择了GD音乐台解析 + const enabledSources = settingStore.setData.enabledMusicSources || []; + if (enabledSources.includes('gdmusic')) { + // 获取音质设置并转换为GD音乐台格式 + try { + const quality = getQualityMapping(settingStore.setData.musicQuality || 'higher'); + + // 调用封装的GD音乐台解析服务 + const gdResult = await parseFromGDMusic(id, data, quality); + if (gdResult) { + return gdResult; + } + } catch (error) { + console.error('GD音乐台解析失败:', error); + } + + console.log('GD音乐台所有音源均解析失败,尝试使用unblockMusic'); + } + + // 如果GD音乐台解析失败或者未启用,尝试使用unblockMusic + if (isElectron) { + const filteredSources = enabledSources.filter(source => source !== 'gdmusic'); + return window.api.unblockMusic(id, cloneDeep(data), cloneDeep(filteredSources)); + } + return requestMusic.get('/music', { params: { id } }); }; diff --git a/src/renderer/type/music.ts b/src/renderer/type/music.ts index c0cd037..21cd20e 100644 --- a/src/renderer/type/music.ts +++ b/src/renderer/type/music.ts @@ -239,3 +239,19 @@ export interface IArtists { img1v1: number; trans: null; } + +// 音乐源类型定义 +export type MusicSourceType = + | 'tencent' + | 'kugou' + | 'kuwo' + | 'migu' + | 'netease' + | 'joox' + | 'ytmusic' + | 'spotify' + | 'qobuz' + | 'deezer' + | 'gdmusic'; + +// 更多音乐相关的类型可以在这里定义 diff --git a/src/renderer/views/set/index.vue b/src/renderer/views/set/index.vue index 5b819dc..b1653c2 100644 --- a/src/renderer/views/set/index.vue +++ b/src/renderer/views/set/index.vue @@ -521,6 +521,16 @@ {{ source.label }} + @@ -528,6 +538,14 @@
{{ t('settings.playback.musicSourcesWarning') }}
+ + +
+

GD音乐台(music.gdstudio.xyz)设置

+

+ GD音乐台将自动尝试多个音乐平台进行解析,无需额外配置。优先级高于其他解析方式,但是请求可能较慢。感谢(music.gdstudio.xyz) +

+
@@ -555,9 +573,9 @@ import { checkUpdate, UpdateResult } from '@/utils/update'; import config from '../../../../package.json'; // 手动定义Platform类型,避免从主进程导入的问题 -type Platform = 'qq' | 'migu' | 'kugou' | 'pyncmd'| 'kuwo' | 'bilibili' | 'youtube'; +type Platform = 'qq' | 'migu' | 'kugou' | 'pyncmd' | 'joox' | 'kuwo' | 'bilibili' | 'youtube' | 'gdmusic'; // 所有平台 -const ALL_PLATFORMS: Platform[] = ['migu', 'kugou', 'pyncmd', 'kuwo', 'bilibili', 'youtube']; +const ALL_PLATFORMS: Platform[] = ['migu', 'kugou', 'pyncmd', 'kuwo', 'bilibili', 'youtube', 'gdmusic']; const settingsStore = useSettingsStore(); const userStore = useUserStore(); @@ -1050,7 +1068,8 @@ const musicSourceOptions = ref([ { label: 'pyncmd', value: 'pyncmd' }, { label: '酷我音乐', value: 'kuwo' }, { label: 'Bilibili音乐', value: 'bilibili' }, - { label: 'YouTube', value: 'youtube' } + { label: 'YouTube', value: 'youtube' }, + { label: 'GD音乐台', value: 'gdmusic' } ]); // 已选择的音源列表