2017-05-14 19:00:35 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using Discord;
|
|
|
|
|
using NadekoBot.Extensions;
|
2017-05-24 04:43:00 +00:00
|
|
|
|
using NadekoBot.Services.Database.Models;
|
|
|
|
|
using NLog;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using VideoLibrary;
|
2017-05-29 04:13:22 +00:00
|
|
|
|
using System.Collections.Generic;
|
2017-07-01 06:15:58 +00:00
|
|
|
|
using Discord.Commands;
|
2017-07-01 19:22:11 +00:00
|
|
|
|
using Discord.WebSocket;
|
2017-07-02 17:00:25 +00:00
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
using System.Net.Http;
|
2017-05-14 19:00:35 +00:00
|
|
|
|
|
|
|
|
|
namespace NadekoBot.Services.Music
|
|
|
|
|
{
|
|
|
|
|
public class MusicService
|
|
|
|
|
{
|
2017-05-24 04:43:00 +00:00
|
|
|
|
public const string MusicDataPath = "data/musicdata";
|
|
|
|
|
|
2017-05-14 19:00:35 +00:00
|
|
|
|
private readonly IGoogleApiService _google;
|
2017-05-22 23:59:31 +00:00
|
|
|
|
private readonly NadekoStrings _strings;
|
|
|
|
|
private readonly ILocalization _localization;
|
2017-05-29 04:13:22 +00:00
|
|
|
|
private readonly DbService _db;
|
2017-05-24 04:43:00 +00:00
|
|
|
|
private readonly Logger _log;
|
|
|
|
|
private readonly SoundCloudApiService _sc;
|
|
|
|
|
private readonly IBotCredentials _creds;
|
2017-05-29 04:13:22 +00:00
|
|
|
|
private readonly ConcurrentDictionary<ulong, float> _defaultVolumes;
|
2017-07-01 19:22:11 +00:00
|
|
|
|
private readonly DiscordSocketClient _client;
|
2017-05-22 23:59:31 +00:00
|
|
|
|
|
2017-05-14 19:00:35 +00:00
|
|
|
|
public ConcurrentDictionary<ulong, MusicPlayer> MusicPlayers { get; } = new ConcurrentDictionary<ulong, MusicPlayer>();
|
|
|
|
|
|
2017-07-01 19:22:11 +00:00
|
|
|
|
public MusicService(DiscordSocketClient client, IGoogleApiService google,
|
2017-07-01 06:15:58 +00:00
|
|
|
|
NadekoStrings strings, ILocalization localization, DbService db,
|
2017-05-29 04:13:22 +00:00
|
|
|
|
SoundCloudApiService sc, IBotCredentials creds, IEnumerable<GuildConfig> gcs)
|
2017-05-14 19:00:35 +00:00
|
|
|
|
{
|
2017-07-01 19:22:11 +00:00
|
|
|
|
_client = client;
|
2017-05-14 19:00:35 +00:00
|
|
|
|
_google = google;
|
2017-05-22 23:59:31 +00:00
|
|
|
|
_strings = strings;
|
|
|
|
|
_localization = localization;
|
2017-05-24 04:43:00 +00:00
|
|
|
|
_db = db;
|
|
|
|
|
_sc = sc;
|
|
|
|
|
_creds = creds;
|
|
|
|
|
_log = LogManager.GetCurrentClassLogger();
|
|
|
|
|
|
|
|
|
|
try { Directory.Delete(MusicDataPath, true); } catch { }
|
|
|
|
|
|
2017-05-29 04:13:22 +00:00
|
|
|
|
_defaultVolumes = new ConcurrentDictionary<ulong, float>(gcs.ToDictionary(x => x.GuildId, x => x.DefaultMusicVolume));
|
|
|
|
|
|
2017-05-24 04:43:00 +00:00
|
|
|
|
Directory.CreateDirectory(MusicDataPath);
|
2017-05-14 19:00:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-01 06:15:58 +00:00
|
|
|
|
public float GetDefaultVolume(ulong guildId)
|
2017-05-14 19:00:35 +00:00
|
|
|
|
{
|
2017-07-01 06:15:58 +00:00
|
|
|
|
return _defaultVolumes.GetOrAdd(guildId, (id) =>
|
|
|
|
|
{
|
|
|
|
|
using (var uow = _db.UnitOfWork)
|
|
|
|
|
{
|
|
|
|
|
return uow.GuildConfigs.For(guildId, set => set).DefaultMusicVolume;
|
|
|
|
|
}
|
|
|
|
|
});
|
2017-05-14 19:00:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-01 06:15:58 +00:00
|
|
|
|
public Task<MusicPlayer> GetOrCreatePlayer(ICommandContext context)
|
|
|
|
|
{
|
|
|
|
|
var gUsr = (IGuildUser)context.User;
|
|
|
|
|
var txtCh = (ITextChannel)context.Channel;
|
|
|
|
|
var vCh = gUsr.VoiceChannel;
|
|
|
|
|
return GetOrCreatePlayer(context.Guild.Id, vCh, txtCh);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<MusicPlayer> GetOrCreatePlayer(ulong guildId, IVoiceChannel voiceCh, ITextChannel textCh)
|
2017-05-14 19:00:35 +00:00
|
|
|
|
{
|
|
|
|
|
string GetText(string text, params object[] replacements) =>
|
2017-05-22 23:59:31 +00:00
|
|
|
|
_strings.GetText(text, _localization.GetCultureInfo(textCh.Guild), "Music".ToLowerInvariant(), replacements);
|
2017-05-14 19:00:35 +00:00
|
|
|
|
|
2017-07-01 06:15:58 +00:00
|
|
|
|
if (voiceCh == null || voiceCh.Guild != textCh.Guild)
|
2017-05-14 19:00:35 +00:00
|
|
|
|
{
|
2017-07-01 06:15:58 +00:00
|
|
|
|
if (textCh != null)
|
2017-05-14 19:00:35 +00:00
|
|
|
|
{
|
2017-07-01 06:15:58 +00:00
|
|
|
|
await textCh.SendErrorAsync(GetText("must_be_in_voice")).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
throw new ArgumentException(nameof(voiceCh));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return MusicPlayers.GetOrAdd(guildId, _ =>
|
|
|
|
|
{
|
|
|
|
|
var vol = GetDefaultVolume(guildId);
|
2017-07-04 13:38:19 +00:00
|
|
|
|
var mp = new MusicPlayer(this, _google, voiceCh, textCh, vol);
|
2017-07-01 06:15:58 +00:00
|
|
|
|
|
2017-05-14 19:00:35 +00:00
|
|
|
|
IUserMessage playingMessage = null;
|
|
|
|
|
IUserMessage lastFinishedMessage = null;
|
|
|
|
|
mp.OnCompleted += async (s, song) =>
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
lastFinishedMessage?.DeleteAfter(0);
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
lastFinishedMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
|
|
|
|
.WithAuthor(eab => eab.WithName(GetText("finished_song")).WithMusicIcon())
|
|
|
|
|
.WithDescription(song.PrettyName)
|
|
|
|
|
.WithFooter(ef => ef.WithText(song.PrettyInfo)))
|
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// ignored
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// ignored
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
mp.OnStarted += async (player, song) =>
|
|
|
|
|
{
|
2017-07-01 06:15:58 +00:00
|
|
|
|
//try { await mp.UpdateSongDurationsAsync().ConfigureAwait(false); }
|
|
|
|
|
//catch
|
|
|
|
|
//{
|
|
|
|
|
// // ignored
|
|
|
|
|
//}
|
2017-05-14 19:00:35 +00:00
|
|
|
|
var sender = player;
|
|
|
|
|
if (sender == null)
|
|
|
|
|
return;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
playingMessage?.DeleteAfter(0);
|
|
|
|
|
|
|
|
|
|
playingMessage = await mp.OutputTextChannel.EmbedAsync(new EmbedBuilder().WithOkColor()
|
2017-07-03 10:40:12 +00:00
|
|
|
|
.WithAuthor(eab => eab.WithName(GetText("playing_song", song.Index + 1)).WithMusicIcon())
|
|
|
|
|
.WithDescription(song.Song.PrettyName)
|
|
|
|
|
.WithFooter(ef => ef.WithText(mp.PrettyVolume + " | " + song.Song.PrettyInfo)))
|
2017-05-14 19:00:35 +00:00
|
|
|
|
.ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// ignored
|
|
|
|
|
}
|
|
|
|
|
};
|
2017-07-01 06:15:58 +00:00
|
|
|
|
mp.OnPauseChanged += async (player, paused) =>
|
2017-05-14 19:00:35 +00:00
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
IUserMessage msg;
|
|
|
|
|
if (paused)
|
|
|
|
|
msg = await mp.OutputTextChannel.SendConfirmAsync(GetText("paused")).ConfigureAwait(false);
|
|
|
|
|
else
|
|
|
|
|
msg = await mp.OutputTextChannel.SendConfirmAsync(GetText("resumed")).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
msg?.DeleteAfter(10);
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// ignored
|
|
|
|
|
}
|
|
|
|
|
};
|
2017-07-02 17:00:25 +00:00
|
|
|
|
|
2017-05-14 19:00:35 +00:00
|
|
|
|
return mp;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-01 19:22:11 +00:00
|
|
|
|
public MusicPlayer GetPlayerOrDefault(ulong guildId)
|
|
|
|
|
{
|
|
|
|
|
if (MusicPlayers.TryGetValue(guildId, out var mp))
|
|
|
|
|
return mp;
|
|
|
|
|
else
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task TryQueueRelatedSongAsync(string query, ITextChannel txtCh, IVoiceChannel vch)
|
|
|
|
|
{
|
|
|
|
|
var related = (await _google.GetRelatedVideosAsync(query, 4)).ToArray();
|
|
|
|
|
if (!related.Any())
|
|
|
|
|
return;
|
|
|
|
|
|
2017-07-02 17:00:25 +00:00
|
|
|
|
var si = await ResolveSong(related[new NadekoRandom().Next(related.Length)], _client.CurrentUser.ToString(), MusicType.YouTube);
|
|
|
|
|
if (si == null)
|
|
|
|
|
throw new SongNotFoundException();
|
2017-07-01 19:22:11 +00:00
|
|
|
|
var mp = await GetOrCreatePlayer(txtCh.GuildId, vch, txtCh);
|
|
|
|
|
mp.Enqueue(si);
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-02 17:00:25 +00:00
|
|
|
|
public async Task<SongInfo> ResolveSong(string query, string queuerName, MusicType? musicType = null)
|
2017-05-14 19:00:35 +00:00
|
|
|
|
{
|
2017-07-01 06:15:58 +00:00
|
|
|
|
query.ThrowIfNull(nameof(query));
|
2017-05-14 19:00:35 +00:00
|
|
|
|
|
2017-07-02 17:00:25 +00:00
|
|
|
|
SongInfo sinfo = null;
|
|
|
|
|
switch (musicType)
|
|
|
|
|
{
|
|
|
|
|
case MusicType.YouTube:
|
|
|
|
|
sinfo = await ResolveYoutubeSong(query, queuerName);
|
|
|
|
|
break;
|
|
|
|
|
case MusicType.Radio:
|
|
|
|
|
try { sinfo = ResolveRadioSong(IsRadioLink(query) ? await HandleStreamContainers(query) : query, queuerName); } catch { };
|
|
|
|
|
break;
|
|
|
|
|
case MusicType.Local:
|
|
|
|
|
sinfo = ResolveLocalSong(query, queuerName);
|
|
|
|
|
break;
|
|
|
|
|
case MusicType.Soundcloud:
|
|
|
|
|
sinfo = await ResolveSoundCloudSong(query, queuerName);
|
|
|
|
|
break;
|
|
|
|
|
case null:
|
|
|
|
|
if (_sc.IsSoundCloudLink(query))
|
|
|
|
|
sinfo = await ResolveSoundCloudSong(query, queuerName);
|
|
|
|
|
else if (IsRadioLink(query))
|
|
|
|
|
sinfo = ResolveRadioSong(await HandleStreamContainers(query), queuerName);
|
|
|
|
|
else
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
sinfo = await ResolveYoutubeSong(query, queuerName);
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
sinfo = null;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
2017-05-14 19:00:35 +00:00
|
|
|
|
|
2017-07-01 06:15:58 +00:00
|
|
|
|
return sinfo;
|
2017-05-14 19:00:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-02 17:00:25 +00:00
|
|
|
|
public async Task<SongInfo> ResolveSoundCloudSong(string query, string queuerName)
|
|
|
|
|
{
|
|
|
|
|
var svideo = !_sc.IsSoundCloudLink(query) ?
|
|
|
|
|
await _sc.GetVideoByQueryAsync(query).ConfigureAwait(false):
|
|
|
|
|
await _sc.ResolveVideoAsync(query).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
if (svideo == null)
|
|
|
|
|
return null;
|
|
|
|
|
return await SongInfoFromSVideo(svideo, queuerName);
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-05 14:59:59 +00:00
|
|
|
|
public Task<SongInfo> SongInfoFromSVideo(SoundCloudVideo svideo, string queuerName) =>
|
|
|
|
|
Task.FromResult(new SongInfo
|
2017-07-02 17:00:25 +00:00
|
|
|
|
{
|
|
|
|
|
Title = svideo.FullName,
|
|
|
|
|
Provider = "SoundCloud",
|
2017-07-05 14:59:59 +00:00
|
|
|
|
Uri = () => svideo.StreamLink(),
|
2017-07-02 17:00:25 +00:00
|
|
|
|
ProviderType = MusicType.Soundcloud,
|
|
|
|
|
Query = svideo.TrackLink,
|
|
|
|
|
AlbumArt = svideo.artwork_url,
|
|
|
|
|
QueuerName = queuerName
|
2017-07-05 14:59:59 +00:00
|
|
|
|
});
|
2017-07-02 17:00:25 +00:00
|
|
|
|
|
|
|
|
|
public SongInfo ResolveLocalSong(string query, string queuerName)
|
|
|
|
|
{
|
|
|
|
|
return new SongInfo
|
|
|
|
|
{
|
2017-07-05 14:59:59 +00:00
|
|
|
|
Uri = () => Task.FromResult("\"" + Path.GetFullPath(query) + "\""),
|
2017-07-02 17:00:25 +00:00
|
|
|
|
Title = Path.GetFileNameWithoutExtension(query),
|
|
|
|
|
Provider = "Local File",
|
|
|
|
|
ProviderType = MusicType.Local,
|
|
|
|
|
Query = query,
|
|
|
|
|
QueuerName = queuerName
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public SongInfo ResolveRadioSong(string query, string queuerName)
|
|
|
|
|
{
|
|
|
|
|
return new SongInfo
|
|
|
|
|
{
|
2017-07-05 14:59:59 +00:00
|
|
|
|
Uri = () => Task.FromResult(query),
|
2017-07-02 17:00:25 +00:00
|
|
|
|
Title = query,
|
|
|
|
|
Provider = "Radio Stream",
|
|
|
|
|
ProviderType = MusicType.Radio,
|
|
|
|
|
Query = query,
|
|
|
|
|
QueuerName = queuerName
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-05 10:23:37 +00:00
|
|
|
|
public async Task DestroyAllPlayers()
|
|
|
|
|
{
|
|
|
|
|
foreach (var key in MusicPlayers.Keys)
|
|
|
|
|
{
|
|
|
|
|
await DestroyPlayer(key);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-01 06:15:58 +00:00
|
|
|
|
public async Task<SongInfo> ResolveYoutubeSong(string query, string queuerName)
|
2017-05-14 19:00:35 +00:00
|
|
|
|
{
|
2017-07-05 16:15:46 +00:00
|
|
|
|
_log.Info("Getting video");
|
2017-07-05 14:59:59 +00:00
|
|
|
|
var (link, video) = await GetYoutubeVideo(query);
|
2017-07-01 06:15:58 +00:00
|
|
|
|
|
|
|
|
|
if (video == null) // do something with this error
|
2017-07-03 10:40:12 +00:00
|
|
|
|
{
|
2017-07-02 17:00:25 +00:00
|
|
|
|
_log.Info("Could not load any video elements based on the query.");
|
2017-07-03 10:40:12 +00:00
|
|
|
|
return null;
|
|
|
|
|
}
|
2017-07-01 06:15:58 +00:00
|
|
|
|
//var m = Regex.Match(query, @"\?t=(?<t>\d*)");
|
|
|
|
|
//int gotoTime = 0;
|
|
|
|
|
//if (m.Captures.Count > 0)
|
|
|
|
|
// int.TryParse(m.Groups["t"].ToString(), out gotoTime);
|
2017-07-05 16:15:46 +00:00
|
|
|
|
|
|
|
|
|
_log.Info("Creating song info");
|
2017-07-01 06:15:58 +00:00
|
|
|
|
var song = new SongInfo
|
2017-05-24 04:43:00 +00:00
|
|
|
|
{
|
2017-07-01 06:15:58 +00:00
|
|
|
|
Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube"
|
|
|
|
|
Provider = "YouTube",
|
2017-07-05 14:59:59 +00:00
|
|
|
|
Uri = async () => {
|
|
|
|
|
var vid = await GetYoutubeVideo(query);
|
|
|
|
|
if (vid.Item2 == null)
|
|
|
|
|
throw new HttpRequestException();
|
|
|
|
|
|
|
|
|
|
return await vid.Item2.GetUriAsync();
|
|
|
|
|
},
|
2017-07-01 06:15:58 +00:00
|
|
|
|
Query = link,
|
2017-07-02 17:00:25 +00:00
|
|
|
|
ProviderType = MusicType.YouTube,
|
2017-07-01 06:15:58 +00:00
|
|
|
|
QueuerName = queuerName
|
|
|
|
|
};
|
|
|
|
|
return song;
|
2017-05-24 04:43:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-05 14:59:59 +00:00
|
|
|
|
private async Task<(string, YouTubeVideo)> GetYoutubeVideo(string query)
|
|
|
|
|
{
|
2017-07-05 16:15:46 +00:00
|
|
|
|
_log.Info("Getting link");
|
2017-07-05 14:59:59 +00:00
|
|
|
|
var link = (await _google.GetVideoLinksByKeywordAsync(query).ConfigureAwait(false)).FirstOrDefault();
|
|
|
|
|
if (string.IsNullOrWhiteSpace(link))
|
|
|
|
|
{
|
|
|
|
|
_log.Info("No song found.");
|
|
|
|
|
return (null, null);
|
|
|
|
|
}
|
2017-07-05 16:15:46 +00:00
|
|
|
|
_log.Info("Getting all videos");
|
2017-07-05 14:59:59 +00:00
|
|
|
|
var allVideos = await Task.Run(async () => { try { return await YouTube.Default.GetAllVideosAsync(link).ConfigureAwait(false); } catch { return Enumerable.Empty<YouTubeVideo>(); } }).ConfigureAwait(false);
|
|
|
|
|
var videos = allVideos.Where(v => v.AdaptiveKind == AdaptiveKind.Audio);
|
|
|
|
|
var video = videos
|
|
|
|
|
.Where(v => v.AudioBitrate < 256)
|
|
|
|
|
.OrderByDescending(v => v.AudioBitrate)
|
|
|
|
|
.FirstOrDefault();
|
|
|
|
|
|
|
|
|
|
return (link, video);
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-02 17:00:25 +00:00
|
|
|
|
private bool IsRadioLink(string query) =>
|
|
|
|
|
(query.StartsWith("http") ||
|
|
|
|
|
query.StartsWith("ww"))
|
|
|
|
|
&&
|
|
|
|
|
(query.Contains(".pls") ||
|
|
|
|
|
query.Contains(".m3u") ||
|
|
|
|
|
query.Contains(".asx") ||
|
|
|
|
|
query.Contains(".xspf"));
|
|
|
|
|
|
2017-07-01 06:16:06 +00:00
|
|
|
|
public async Task DestroyPlayer(ulong id)
|
2017-05-24 04:43:00 +00:00
|
|
|
|
{
|
2017-07-01 06:15:58 +00:00
|
|
|
|
if (MusicPlayers.TryRemove(id, out var mp))
|
2017-07-01 06:16:06 +00:00
|
|
|
|
await mp.Destroy();
|
2017-05-24 04:43:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-02 17:00:25 +00:00
|
|
|
|
private readonly Regex plsRegex = new Regex("File1=(?<url>.*?)\\n", RegexOptions.Compiled);
|
|
|
|
|
private readonly Regex m3uRegex = new Regex("(?<url>^[^#].*)", RegexOptions.Compiled | RegexOptions.Multiline);
|
|
|
|
|
private readonly Regex asxRegex = new Regex("<ref href=\"(?<url>.*?)\"", RegexOptions.Compiled);
|
|
|
|
|
private readonly Regex xspfRegex = new Regex("<location>(?<url>.*?)</location>", RegexOptions.Compiled);
|
|
|
|
|
|
|
|
|
|
private async Task<string> HandleStreamContainers(string query)
|
|
|
|
|
{
|
|
|
|
|
string file = null;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
using (var http = new HttpClient())
|
|
|
|
|
{
|
|
|
|
|
file = await http.GetStringAsync(query).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
return query;
|
|
|
|
|
}
|
|
|
|
|
if (query.Contains(".pls"))
|
|
|
|
|
{
|
|
|
|
|
//File1=http://armitunes.com:8000/
|
|
|
|
|
//Regex.Match(query)
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var m = plsRegex.Match(file);
|
|
|
|
|
var res = m.Groups["url"]?.ToString();
|
|
|
|
|
return res?.Trim();
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
_log.Warn($"Failed reading .pls:\n{file}");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (query.Contains(".m3u"))
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
# This is a comment
|
|
|
|
|
C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3
|
|
|
|
|
C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3
|
|
|
|
|
*/
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var m = m3uRegex.Match(file);
|
|
|
|
|
var res = m.Groups["url"]?.ToString();
|
|
|
|
|
return res?.Trim();
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
_log.Warn($"Failed reading .m3u:\n{file}");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
if (query.Contains(".asx"))
|
|
|
|
|
{
|
|
|
|
|
//<ref href="http://armitunes.com:8000"/>
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var m = asxRegex.Match(file);
|
|
|
|
|
var res = m.Groups["url"]?.ToString();
|
|
|
|
|
return res?.Trim();
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
_log.Warn($"Failed reading .asx:\n{file}");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (query.Contains(".xspf"))
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
<playlist version="1" xmlns="http://xspf.org/ns/0/">
|
|
|
|
|
<trackList>
|
|
|
|
|
<track><location>file:///mp3s/song_1.mp3</location></track>
|
|
|
|
|
*/
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var m = xspfRegex.Match(file);
|
|
|
|
|
var res = m.Groups["url"]?.ToString();
|
|
|
|
|
return res?.Trim();
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
_log.Warn($"Failed reading .xspf:\n{file}");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return query;
|
|
|
|
|
}
|
2017-05-14 19:00:35 +00:00
|
|
|
|
}
|
2017-07-01 06:15:58 +00:00
|
|
|
|
}
|