public async Task NowPlaying(Audio item, LastfmUser user) { var request = new NowPlayingRequest { Track = item.Name, Album = item.Album, Artist = item.Artists.First(), ApiKey = Strings.Keys.LastfmApiKey, Method = Strings.Methods.NowPlaying, SessionKey = user.SessionKey }; //Add duration if (item.RunTimeTicks != null) request.Duration = Convert.ToInt32(TimeSpan.FromTicks((long)item.RunTimeTicks).TotalSeconds); var response = await Post<NowPlayingRequest, ScrobbleResponse>(request); if (response != null && !response.IsError()) { Plugin.Logger.Info("{0} is now playing '{1}' - {2} - {3}", user.Username, request.Track, request.Album, request.Artist); return; } Plugin.Logger.Error("Failed to send now playing for track: {0}", item.Name); }
public IEnumerable<Audio> GetInstantMixFromSong(Audio item, User user) { var list = new List<Audio> { item }; return list.Concat(GetInstantMixFromGenres(item.Genres, user)); }
public async Task<DynamicImageResponse> GetImage(Audio item, List<MediaStream> imageStreams, CancellationToken cancellationToken) { var path = GetAudioImagePath(item); if (!_fileSystem.FileExists(path)) { var semaphore = GetLock(path); // Acquire a lock await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { // Check again in case it was saved while waiting for the lock if (!_fileSystem.FileExists(path)) { _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); var imageStream = imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("front", StringComparison.OrdinalIgnoreCase) != -1) ?? imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("cover", StringComparison.OrdinalIgnoreCase) != -1) ?? imageStreams.FirstOrDefault(); var imageStreamIndex = imageStream == null ? (int?)null : imageStream.Index; var tempFile = await _mediaEncoder.ExtractAudioImage(item.Path, imageStreamIndex, cancellationToken).ConfigureAwait(false); File.Copy(tempFile, path, true); try { File.Delete(tempFile); } catch { } } } finally { semaphore.Release(); } } return new DynamicImageResponse { HasImage = true, Path = path }; }
public PlaylistItem Create(Audio item, List<MediaStream> mediaStreams, DeviceProfile profile) { var playlistItem = new PlaylistItem { ItemId = item.Id.ToString("N"), MediaType = DlnaProfileType.Audio }; var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); var directPlay = profile.DirectPlayProfiles .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(i, item, audioStream)); if (directPlay != null) { var audioCodec = audioStream == null ? null : audioStream.Codec; // Make sure audio codec profiles are satisfied if (!string.IsNullOrEmpty(audioCodec) && profile.CodecProfiles.Where(i => i.Type == CodecType.Audio && i.ContainsCodec(audioCodec)) .All(i => AreConditionsSatisfied(i.Conditions, item.Path, null, audioStream))) { playlistItem.Transcode = false; playlistItem.Container = Path.GetExtension(item.Path); return playlistItem; } } var transcodingProfile = profile.TranscodingProfiles .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(profile, i, item)); if (transcodingProfile != null) { playlistItem.Transcode = true; playlistItem.Container = "." + transcodingProfile.Container.TrimStart('.'); playlistItem.AudioCodec = transcodingProfile.AudioCodec; var audioTranscodingConditions = profile.CodecProfiles .Where(i => i.Type == CodecType.Audio && i.ContainsCodec(transcodingProfile.AudioCodec)) .Take(1) .SelectMany(i => i.Conditions); ApplyTranscodingConditions(playlistItem, audioTranscodingConditions); } return playlistItem; }
public async Task<DynamicImageResponse> GetImage(Audio item, CancellationToken cancellationToken) { var path = GetAudioImagePath(item); if (!_fileSystem.FileExists(path)) { var semaphore = GetLock(path); // Acquire a lock await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { // Check again in case it was saved while waiting for the lock if (!_fileSystem.FileExists(path)) { _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); using (var stream = await _mediaEncoder.ExtractAudioImage(item.Path, cancellationToken).ConfigureAwait(false)) { using (var fileStream = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true)) { await stream.CopyToAsync(fileStream).ConfigureAwait(false); } } } } finally { semaphore.Release(); } } return new DynamicImageResponse { HasImage = true, Path = path }; }
public async Task Scrobble(Audio item, LastfmUser user) { var request = new ScrobbleRequest { Track = item.Name, Album = item.Album, Artist = item.Artists.First(), Timestamp = Helpers.CurrentTimestamp(), ApiKey = Strings.Keys.LastfmApiKey, Method = Strings.Methods.Scrobble, SessionKey = user.SessionKey }; var response = await Post<ScrobbleRequest, ScrobbleResponse>(request); if (response != null && !response.IsError()) { Plugin.Logger.Info("{0} played '{1}' - {2} - {3}", user.Username, request.Track, request.Album, request.Artist); return; } Plugin.Logger.Error("Failed to Scrobble track: {0}", item.Name); }
private void AddAudioResource(XmlElement container, Audio audio, string deviceId, Filter filter, StreamInfo streamInfo = null) { var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL); if (streamInfo == null) { var sources = _user == null ? audio.GetMediaSources(true).ToList() : audio.GetMediaSources(true, _user).ToList(); streamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions { ItemId = audio.Id.ToString("N"), MediaSources = sources, Profile = _profile, DeviceId = deviceId }); } var url = streamInfo.ToDlnaUrl(_serverAddress); res.InnerText = url; var mediaSource = streamInfo.MediaSource; if (mediaSource.RunTimeTicks.HasValue) { res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture)); } if (filter.Contains("res@size")) { if (streamInfo.IsDirectStream || streamInfo.EstimateContentLength) { var size = streamInfo.TargetSize; if (size.HasValue) { res.SetAttribute("size", size.Value.ToString(_usCulture)); } } } var targetAudioBitrate = streamInfo.TargetAudioBitrate; var targetSampleRate = streamInfo.TargetAudioSampleRate; var targetChannels = streamInfo.TargetAudioChannels; if (targetChannels.HasValue) { res.SetAttribute("nrAudioChannels", targetChannels.Value.ToString(_usCulture)); } if (targetSampleRate.HasValue) { res.SetAttribute("sampleFrequency", targetSampleRate.Value.ToString(_usCulture)); } if (targetAudioBitrate.HasValue) { res.SetAttribute("bitrate", targetAudioBitrate.Value.ToString(_usCulture)); } var mediaProfile = _profile.GetAudioMediaProfile(streamInfo.Container, streamInfo.AudioCodec, targetChannels, targetAudioBitrate); var filename = url.Substring(0, url.IndexOf('?')); var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType) ? MimeTypes.GetMimeType(filename) : mediaProfile.MimeType; var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(streamInfo.Container, streamInfo.TargetAudioCodec, targetAudioBitrate, targetSampleRate, targetChannels, streamInfo.IsDirectStream, streamInfo.RunTimeTicks, streamInfo.TranscodeSeekInfo); res.SetAttribute("protocolInfo", String.Format( "http-get:*:{0}:{1}", mimeType, contentFeatures )); container.AppendChild(res); }
public static LastfmTrack FindMatchedLastfmSong(List<LastfmTrack> tracks, Audio song) { return tracks.FirstOrDefault(lastfmTrack => StringHelper.IsLike(song.Name, lastfmTrack.Name)); }
/// <summary> /// Loves or unloves a track /// </summary> /// <param name="item">The track</param> /// <param name="user">The Lastfm User</param> /// <param name="love">If the track is loved or not</param> /// <returns></returns> public async Task<bool> LoveTrack(Audio item, LastfmUser user, bool love = true) { var request = new TrackLoveRequest { Artist = item.Artists.First(), Track = item.Name, ApiKey = Strings.Keys.LastfmApiKey, Method = love ? Strings.Methods.TrackLove : Strings.Methods.TrackUnlove, SessionKey = user.SessionKey, }; //Send the request var response = await Post<TrackLoveRequest, BaseResponse>(request); if (response != null && !response.IsError()) { Plugin.Logger.Info("{0} {2}loved track '{1}'", user.Username, item.Name, (love ? "" : "un")); return true; } Plugin.Logger.Error("{0} Failed to love = {3} track '{1}' - {2}", user.Username, item.Name, response.Message, love); return false; }
private async Task Sync(SyncJobItem jobItem, Audio item, DeviceProfile profile, CancellationToken cancellationToken) { var options = new AudioOptions { Context = EncodingContext.Static, ItemId = item.Id.ToString("N"), DeviceId = jobItem.TargetId, Profile = profile, MediaSources = item.GetMediaSources(false).ToList() }; var streamInfo = new StreamBuilder().BuildAudioItem(options); var mediaSource = streamInfo.MediaSource; jobItem.MediaSourceId = streamInfo.MediaSourceId; if (streamInfo.PlayMethod == PlayMethod.Transcode) { jobItem.Status = SyncJobItemStatus.Converting; await _syncRepo.Update(jobItem).ConfigureAwait(false); jobItem.OutputPath = await _mediaEncoder.EncodeAudio(new EncodingJobOptions(streamInfo, profile), new Progress<double>(), cancellationToken); } else { if (mediaSource.Protocol == MediaProtocol.File) { jobItem.OutputPath = mediaSource.Path; } else if (mediaSource.Protocol == MediaProtocol.Http) { jobItem.OutputPath = await DownloadFile(jobItem, mediaSource, cancellationToken).ConfigureAwait(false); } else { throw new InvalidOperationException(string.Format("Cannot direct stream {0} protocol", mediaSource.Protocol)); } } jobItem.Progress = 50; jobItem.Status = SyncJobItemStatus.Transferring; await _syncRepo.Update(jobItem).ConfigureAwait(false); }
/// <summary> /// Gets the audio image path. /// </summary> /// <param name="item">The item.</param> /// <returns>System.String.</returns> private string GetAudioImagePath(Audio item) { var album = item.Parent as MusicAlbum; var filename = item.Album ?? string.Empty; filename += item.Artists.FirstOrDefault() ?? string.Empty; filename += album == null ? item.Id.ToString("N") + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks + "_primary"; filename = filename.GetMD5() + ".jpg"; var prefix = filename.Substring(0, 1); return Path.Combine(AudioImagesPath, prefix, filename); }
private bool IsSupported(DirectPlayProfile profile, Audio item, MediaStream audioStream) { var mediaPath = item.Path; if (profile.Container.Length > 0) { // Check container type var mediaContainer = Path.GetExtension(mediaPath); if (!profile.GetContainers().Any(i => string.Equals("." + i.TrimStart('.'), mediaContainer, StringComparison.OrdinalIgnoreCase))) { return false; } } return true; }
private async Task Sync(SyncJobItem jobItem, SyncJob job, Audio item, User user, bool enableConversion, SyncOptions syncOptions, IProgress<double> progress, CancellationToken cancellationToken) { var jobOptions = _syncManager.GetAudioOptions(jobItem, job); var conversionOptions = new AudioOptions { Profile = jobOptions.DeviceProfile }; conversionOptions.DeviceId = jobItem.TargetId; conversionOptions.Context = EncodingContext.Static; conversionOptions.ItemId = item.Id.ToString("N"); conversionOptions.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, false, user).ToList(); var streamInfo = new StreamBuilder(_logger).BuildAudioItem(conversionOptions); var mediaSource = streamInfo.MediaSource; jobItem.MediaSourceId = streamInfo.MediaSourceId; jobItem.TemporaryPath = GetTemporaryPath(jobItem); if (streamInfo.PlayMethod == PlayMethod.Transcode && jobOptions.IsConverting) { if (!enableConversion) { return; } jobItem.Status = SyncJobItemStatus.Converting; await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); await UpdateJobStatus(job).ConfigureAwait(false); try { var lastJobUpdate = DateTime.MinValue; var innerProgress = new ActionableProgress<double>(); innerProgress.RegisterAction(async pct => { progress.Report(pct); if ((DateTime.UtcNow - lastJobUpdate).TotalSeconds >= DatabaseProgressUpdateIntervalSeconds) { jobItem.Progress = pct / 2; await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); await UpdateJobStatus(job).ConfigureAwait(false); } }); jobItem.OutputPath = await _mediaEncoder.EncodeAudio(new EncodingJobOptions(streamInfo, conversionOptions.Profile) { OutputDirectory = jobItem.TemporaryPath, CpuCoreLimit = syncOptions.TranscodingCpuCoreLimit }, innerProgress, cancellationToken); } catch (OperationCanceledException) { jobItem.Status = SyncJobItemStatus.Queued; jobItem.Progress = 0; } catch (Exception ex) { jobItem.Status = SyncJobItemStatus.Failed; _logger.ErrorException("Error during sync transcoding", ex); } if (jobItem.Status == SyncJobItemStatus.Failed || jobItem.Status == SyncJobItemStatus.Queued) { await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); return; } jobItem.MediaSource = await GetEncodedMediaSource(jobItem.OutputPath, user, false).ConfigureAwait(false); } else { if (mediaSource.Protocol == MediaProtocol.File) { jobItem.OutputPath = mediaSource.Path; } else if (mediaSource.Protocol == MediaProtocol.Http) { jobItem.OutputPath = await DownloadFile(jobItem, mediaSource, cancellationToken).ConfigureAwait(false); } else { throw new InvalidOperationException(string.Format("Cannot direct stream {0} protocol", mediaSource.Protocol)); } jobItem.MediaSource = mediaSource; } jobItem.MediaSource.SupportsTranscoding = false; jobItem.Progress = 50; jobItem.Status = SyncJobItemStatus.ReadyToTransfer; await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); }
private void AddAudioResource(XmlElement container, Audio audio, string deviceId, Filter filter) { var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL); var sources = _dtoService.GetMediaSources(audio); var streamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions { ItemId = audio.Id.ToString("N"), MediaSources = sources, Profile = _profile, DeviceId = deviceId }); var url = streamInfo.ToDlnaUrl(_serverAddress); res.InnerText = url; var mediaSource = sources.First(i => string.Equals(i.Id, streamInfo.MediaSourceId)); if (mediaSource.RunTimeTicks.HasValue) { res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture)); } if (filter.Contains("res@size")) { if (streamInfo.IsDirectStream || streamInfo.EstimateContentLength) { var size = streamInfo.TargetSize; if (size.HasValue) { res.SetAttribute("size", size.Value.ToString(_usCulture)); } } } var targetAudioBitrate = streamInfo.TargetAudioBitrate; var targetSampleRate = streamInfo.TargetAudioSampleRate; var targetChannels = streamInfo.TargetAudioChannels; if (targetChannels.HasValue) { res.SetAttribute("nrAudioChannels", targetChannels.Value.ToString(_usCulture)); } if (targetSampleRate.HasValue) { res.SetAttribute("sampleFrequency", targetSampleRate.Value.ToString(_usCulture)); } if (targetAudioBitrate.HasValue) { res.SetAttribute("bitrate", targetAudioBitrate.Value.ToString(_usCulture)); } var formatProfile = new MediaFormatProfileResolver().ResolveAudioFormat(streamInfo.Container, targetAudioBitrate, targetSampleRate, targetChannels); var filename = url.Substring(0, url.IndexOf('?')); var orgOpValue = DlnaMaps.GetOrgOpValue(mediaSource.RunTimeTicks.HasValue, streamInfo.IsDirectStream, streamInfo.TranscodeSeekInfo); var orgCi = streamInfo.IsDirectStream ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1"; res.SetAttribute("protocolInfo", String.Format( "http-get:*:{0}:DLNA.ORG_PN={1};DLNA.ORG_OP={2};DLNA.ORG_CI={3};DLNA.ORG_FLAGS={4}", MimeTypes.GetMimeType(filename), formatProfile, orgOpValue, orgCi, DlnaMaps.DefaultStreaming )); container.AppendChild(res); }
/// <summary> /// Unlove a track. This is the same as LoveTrack with love as false /// </summary> /// <param name="item">The track</param> /// <param name="user">The Lastfm User</param> /// <returns></returns> public async Task<bool> UnloveTrack(Audio item, LastfmUser user) { return await LoveTrack(item, user, false); }
private string GetAudioImagePath(Audio item) { var album = item.AlbumEntity; var filename = item.Album ?? string.Empty; filename += string.Join(",", item.Artists.ToArray()); filename += album == null ? item.Id.ToString("N") + "_primary" + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks + "_primary"; filename = filename.GetMD5() + ".jpg"; var prefix = filename.Substring(0, 1); return Path.Combine(AudioImagesPath, prefix, filename); }
/// <summary> /// Creates the images for song. /// </summary> /// <param name="item">The item.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> /// <exception cref="System.InvalidOperationException">Can't extract an image unless the audio file has an embedded image.</exception> private async Task CreateImagesForSong(Audio item, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var path = GetAudioImagePath(item); if (!File.Exists(path)) { var semaphore = GetLock(path); // Acquire a lock await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); // Check again if (!File.Exists(path)) { try { var parentPath = Path.GetDirectoryName(path); Directory.CreateDirectory(parentPath); await _mediaEncoder.ExtractImage(new[] { item.Path }, InputType.AudioFile, null, null, path, cancellationToken).ConfigureAwait(false); } finally { semaphore.Release(); } } else { semaphore.Release(); } } // Image is already in the cache item.PrimaryImagePath = path; }
private bool IsSupported(DeviceProfile profile, TranscodingProfile transcodingProfile, Audio item) { // Placeholder for future conditions return true; }
/// <summary> /// Creates the images for song. /// </summary> /// <param name="item">The item.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> /// <exception cref="System.InvalidOperationException">Can't extract an image unless the audio file has an embedded image.</exception> private async Task CreateImagesForSong(Audio item, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var album = item.Parent as MusicAlbum; var filename = item.Album ?? string.Empty; filename += item.Artists.FirstOrDefault() ?? string.Empty; filename += album == null ? item.Id.ToString("N") + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks; var path = ImageCache.GetResourcePath(filename + "_primary", ".jpg"); if (!File.Exists(path)) { var semaphore = GetLock(path); // Acquire a lock await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); // Check again if (!File.Exists(path)) { try { var parentPath = Path.GetDirectoryName(path); Directory.CreateDirectory(parentPath); await _mediaEncoder.ExtractImage(new[] { item.Path }, InputType.AudioFile, null, null, path, cancellationToken).ConfigureAwait(false); } finally { semaphore.Release(); } } else { semaphore.Release(); } } // Image is already in the cache item.PrimaryImagePath = path; }
private string GetAudioImagePath(Audio item) { var filename = item.Album ?? string.Empty; filename += string.Join(",", item.Artists.ToArray()); if (!string.IsNullOrWhiteSpace(item.Album)) { filename += "_" + item.Album; } else if (!string.IsNullOrWhiteSpace(item.Name)) { filename += "_" + item.Name; } else { filename += "_" + item.Id.ToString("N"); } filename = filename.GetMD5() + ".jpg"; var prefix = filename.Substring(0, 1); return Path.Combine(AudioImagesPath, prefix, filename); }
public IEnumerable<Audio> GetInstantMixFromSong(Audio item, User user) { return GetInstantMixFromGenres(item.Genres, user); }
private async Task<string> Sync(SyncJobItem jobItem, Audio item, DeviceProfile profile, CancellationToken cancellationToken) { var options = new AudioOptions { Context = EncodingContext.Streaming, ItemId = item.Id.ToString("N"), DeviceId = jobItem.TargetId, Profile = profile, MediaSources = item.GetMediaSources(false).ToList() }; var streamInfo = new StreamBuilder().BuildAudioItem(options); var mediaSource = streamInfo.MediaSource; if (streamInfo.PlayMethod != PlayMethod.Transcode) { if (mediaSource.Protocol == MediaProtocol.File) { return mediaSource.Path; } if (mediaSource.Protocol == MediaProtocol.Http) { return await DownloadFile(jobItem, mediaSource, cancellationToken).ConfigureAwait(false); } throw new InvalidOperationException(string.Format("Cannot direct stream {0} protocol", mediaSource.Protocol)); } // TODO: Transcode return mediaSource.Path; }