protected override void Execute(EpisodeFile episodeFile) { if (Model.Files.Contains(episodeFile) && !episodeFile.InProcessing) { episodeFile.ShowSelector.DoProcessFile(); } }
protected override void Execute(EpisodeFile episodeFile) { if (Model.RunningFiles.Contains(episodeFile) && (episodeFile.RetryTimer != null)) { episodeFile.Cancel(); Model.RunningFiles.Remove(episodeFile); } }
protected override void Execute(EpisodeFile episodeFile) { if (episodeFile.CanPerform) { Model.Files.Remove(episodeFile); Model.RunningFiles.Add(episodeFile); } }
public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode, bool newDownload, string downloadClient, string downloadClientId) { EpisodeInfo = episodeInfo; ImportedEpisode = importedEpisode; NewDownload = newDownload; DownloadClient = downloadClient; DownloadClientId = downloadClientId; }
public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode, bool newDownload, string downloadClient, string downloadId, bool isReadOnly) { EpisodeInfo = episodeInfo; ImportedEpisode = importedEpisode; NewDownload = newDownload; DownloadClient = downloadClient; DownloadId = downloadId; IsReadOnly = isReadOnly; }
public List<ImportDecision> Import(List<ImportDecision> decisions, bool newDownload = false) { var qualifiedImports = GetQualifiedImports(decisions); var imported = new List<ImportDecision>(); foreach (var importDecision in qualifiedImports) { var localEpisode = importDecision.LocalEpisode; try { if (imported.SelectMany(r => r.LocalEpisode.Episodes) .Select(e => e.Id) .ToList() .Intersect(localEpisode.Episodes.Select(e => e.Id)) .Any()) { continue; } var episodeFile = new EpisodeFile(); episodeFile.DateAdded = DateTime.UtcNow; episodeFile.SeriesId = localEpisode.Series.Id; episodeFile.Path = localEpisode.Path.CleanFilePath(); episodeFile.Size = _diskProvider.GetFileSize(localEpisode.Path); episodeFile.Quality = localEpisode.Quality; episodeFile.SeasonNumber = localEpisode.SeasonNumber; episodeFile.Episodes = localEpisode.Episodes; if (newDownload) { episodeFile.SceneName = Path.GetFileNameWithoutExtension(localEpisode.Path.CleanFilePath()); episodeFile.Path = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode); } _mediaFileService.Add(episodeFile); imported.Add(importDecision); if (newDownload) { _eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile)); _eventAggregator.PublishEvent(new EpisodeDownloadedEvent(localEpisode)); } } catch (Exception e) { _logger.WarnException("Couldn't import episode " + localEpisode, e); } } return imported; }
public void Setup() { _episodeFile = Builder <EpisodeFile> .CreateNew() .Build(); }
public override IEnumerable <ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile) { return(Enumerable.Empty <ExtraFile>()); }
public List <ImportResult> Import(List <ImportDecision> decisions, bool newDownload, DownloadClientItem downloadClientItem = null, ImportMode importMode = ImportMode.Auto) { var qualifiedImports = decisions.Where(c => c.Approved) .GroupBy(c => c.LocalEpisode.Series.Id, (i, s) => s .OrderByDescending(c => c.LocalEpisode.Quality, new QualityModelComparer(s.First().LocalEpisode.Series.QualityProfile)) .ThenByDescending(c => c.LocalEpisode.Language, new LanguageComparer(s.First().LocalEpisode.Series.LanguageProfile)) .ThenByDescending(c => c.LocalEpisode.Size)) .SelectMany(c => c) .ToList(); var importResults = new List <ImportResult>(); foreach (var importDecision in qualifiedImports.OrderBy(e => e.LocalEpisode.Episodes.Select(episode => episode.EpisodeNumber).MinOrDefault()) .ThenByDescending(e => e.LocalEpisode.Size)) { var localEpisode = importDecision.LocalEpisode; var oldFiles = new List <EpisodeFile>(); try { //check if already imported if (importResults.SelectMany(r => r.ImportDecision.LocalEpisode.Episodes) .Select(e => e.Id) .Intersect(localEpisode.Episodes.Select(e => e.Id)) .Any()) { importResults.Add(new ImportResult(importDecision, "Episode has already been imported")); continue; } var episodeFile = new EpisodeFile(); episodeFile.DateAdded = DateTime.UtcNow; episodeFile.SeriesId = localEpisode.Series.Id; episodeFile.Path = localEpisode.Path.CleanFilePath(); episodeFile.Size = _diskProvider.GetFileSize(localEpisode.Path); episodeFile.Quality = localEpisode.Quality; episodeFile.MediaInfo = localEpisode.MediaInfo; episodeFile.SeasonNumber = localEpisode.SeasonNumber; episodeFile.Episodes = localEpisode.Episodes; episodeFile.ReleaseGroup = localEpisode.ReleaseGroup; episodeFile.Language = localEpisode.Language; bool copyOnly; switch (importMode) { default: case ImportMode.Auto: copyOnly = downloadClientItem != null && !downloadClientItem.CanMoveFiles; break; case ImportMode.Move: copyOnly = false; break; case ImportMode.Copy: copyOnly = true; break; } if (newDownload) { episodeFile.SceneName = localEpisode.SceneName; episodeFile.OriginalFilePath = GetOriginalFilePath(downloadClientItem, localEpisode); var moveResult = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode, copyOnly); oldFiles = moveResult.OldFiles; } else { episodeFile.RelativePath = localEpisode.Series.Path.GetRelativePath(episodeFile.Path); // Delete existing files from the DB mapped to this path var previousFiles = _mediaFileService.GetFilesWithRelativePath(localEpisode.Series.Id, episodeFile.RelativePath); foreach (var previousFile in previousFiles) { _mediaFileService.Delete(previousFile, DeleteMediaFileReason.ManualOverride); } } episodeFile = _mediaFileService.Add(episodeFile); importResults.Add(new ImportResult(importDecision)); if (newDownload) { _extraService.ImportEpisode(localEpisode, episodeFile, copyOnly); } _eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, oldFiles, newDownload, downloadClientItem)); } catch (RootFolderNotFoundException e) { _logger.Warn(e, "Couldn't import episode " + localEpisode); _eventAggregator.PublishEvent(new EpisodeImportFailedEvent(e, localEpisode, newDownload, downloadClientItem)); importResults.Add(new ImportResult(importDecision, "Failed to import episode, Root folder missing.")); } catch (DestinationAlreadyExistsException e) { _logger.Warn(e, "Couldn't import episode " + localEpisode); importResults.Add(new ImportResult(importDecision, "Failed to import episode, Destination already exists.")); } catch (Exception e) { _logger.Warn(e, "Couldn't import episode " + localEpisode); importResults.Add(new ImportResult(importDecision, "Failed to import episode")); } } //Adding all the rejected decisions importResults.AddRange(decisions.Where(c => !c.Approved) .Select(d => new ImportResult(d, d.Rejections.Select(r => r.Reason).ToArray()))); return(importResults); }
public override MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile) { return(null); }
protected virtual bool CanExecute(EpisodeFile episodeFile) { return true; }
public EpisodeFileDeletedEvent(EpisodeFile episodeFile, DeleteMediaFileReason reason) { EpisodeFile = episodeFile; Reason = reason; }
public override void CreateForEpisodeFile(EpisodeFile episodeFile, TvdbSeries tvDbSeries) { //Create filename.tbn and filename.nfo var episodes = _episodeProvider.GetEpisodesByFileId(episodeFile.EpisodeFileId); if (!episodes.Any()) { _logger.Debug("No episodes where found for this episode file: {0}", episodeFile.EpisodeFileId); return; } var episodeFileThumbnail = tvDbSeries.Episodes.FirstOrDefault( e => e.SeasonNumber == episodeFile.SeasonNumber && e.EpisodeNumber == episodes.First().EpisodeNumber); if (episodeFileThumbnail == null || String.IsNullOrWhiteSpace(episodeFileThumbnail.BannerPath)) { _logger.Debug("No thumbnail is available for this episode"); } else { if (!_diskProvider.FileExists(episodeFile.Path.Replace(Path.GetExtension(episodeFile.Path), ".tbn"))) { _logger.Debug("Downloading episode thumbnail for: {0}", episodeFile.EpisodeFileId); _bannerProvider.Download(episodeFileThumbnail.BannerPath, episodeFile.Path.Replace(Path.GetExtension(episodeFile.Path), ".tbn")); } } _logger.Debug("Generating filename.nfo for: {0}", episodeFile.EpisodeFileId); var xmlResult = String.Empty; foreach (var episode in episodes) { var sb = new StringBuilder(); var xws = new XmlWriterSettings(); xws.OmitXmlDeclaration = true; xws.Indent = false; using (var xw = XmlWriter.Create(sb, xws)) { var doc = new XDocument(); var tvdbEpisode = tvDbSeries.Episodes.FirstOrDefault( e => e.Id == episode.TvDbEpisodeId); if (tvdbEpisode == null) { _logger.Debug("Looking up by TvDbEpisodeId failed, trying to match via season/episode number combination"); tvdbEpisode = tvDbSeries.Episodes.FirstOrDefault( e => e.SeasonNumber == episode.SeasonNumber && e.EpisodeNumber == episode.EpisodeNumber); } if (tvdbEpisode == null) { _logger.Debug("Unable to find episode from TvDb - skipping"); return; } var details = new XElement("episodedetails"); details.Add(new XElement("title", tvdbEpisode.EpisodeName)); details.Add(new XElement("season", tvdbEpisode.SeasonNumber)); details.Add(new XElement("episode", tvdbEpisode.EpisodeNumber)); details.Add(new XElement("aired", tvdbEpisode.FirstAired.ToString("yyyy-MM-dd"))); details.Add(new XElement("plot", tvdbEpisode.Overview)); details.Add(new XElement("displayseason")); details.Add(new XElement("displayepisode")); details.Add(new XElement("thumb", "http://www.thetvdb.com/banners/" + tvdbEpisode.BannerPath)); details.Add(new XElement("watched", "false")); details.Add(new XElement("credits", tvdbEpisode.Writer.FirstOrDefault())); details.Add(new XElement("director", tvdbEpisode.Directors.FirstOrDefault())); details.Add(new XElement("rating", tvdbEpisode.Rating)); foreach (var actor in tvdbEpisode.GuestStars) { if (!String.IsNullOrWhiteSpace(actor)) { continue; } details.Add(new XElement("actor", new XElement("name", actor) )); } foreach (var actor in tvDbSeries.TvdbActors) { details.Add(new XElement("actor", new XElement("name", actor.Name), new XElement("role", actor.Role), new XElement("thumb", "http://www.thetvdb.com/banners/" + actor.ActorImage.BannerPath) )); } doc.Add(details); doc.Save(xw); xmlResult += doc.ToString(); xmlResult += Environment.NewLine; } } var filename = episodeFile.Path.Replace(Path.GetExtension(episodeFile.Path), ".nfo"); _logger.Debug("Saving episodedetails to: {0}", filename); _diskProvider.WriteAllText(filename, xmlResult.Trim(EnvironmentProvider.NewLineChars)); }
public abstract List <ImageFileResult> EpisodeImages(Series series, EpisodeFile episodeFile);
public abstract MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile);
public abstract ExtraFile Import(Series series, EpisodeFile episodeFile, string path, string extension, bool readOnly);
public abstract IEnumerable <ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile);
private void AddMediaInfoTokens(Dictionary <string, Func <TokenMatch, string> > tokenHandlers, EpisodeFile episodeFile) { if (episodeFile.MediaInfo == null) { return; } string videoCodec; switch (episodeFile.MediaInfo.VideoCodec) { case "AVC": if (episodeFile.SceneName.IsNotNullOrWhiteSpace() && Path.GetFileNameWithoutExtension(episodeFile.SceneName).Contains("h264")) { videoCodec = "h264"; } else { videoCodec = "x264"; } break; case "V_MPEGH/ISO/HEVC": if (episodeFile.SceneName.IsNotNullOrWhiteSpace() && Path.GetFileNameWithoutExtension(episodeFile.SceneName).Contains("h265")) { videoCodec = "h265"; } else { videoCodec = "x265"; } break; default: videoCodec = episodeFile.MediaInfo.VideoCodec; break; } string audioCodec; switch (episodeFile.MediaInfo.AudioFormat) { case "AC-3": audioCodec = "AC3"; break; case "E-AC-3": audioCodec = "EAC3"; break; case "MPEG Audio": if (episodeFile.MediaInfo.AudioProfile == "Layer 3") { audioCodec = "MP3"; } else { audioCodec = episodeFile.MediaInfo.AudioFormat; } break; case "DTS": audioCodec = episodeFile.MediaInfo.AudioFormat; break; default: audioCodec = episodeFile.MediaInfo.AudioFormat; break; } var mediaInfoAudioLanguages = GetLanguagesToken(episodeFile.MediaInfo.AudioLanguages); if (!mediaInfoAudioLanguages.IsNullOrWhiteSpace()) { mediaInfoAudioLanguages = string.Format("[{0}]", mediaInfoAudioLanguages); } if (mediaInfoAudioLanguages == "[EN]") { mediaInfoAudioLanguages = string.Empty; } var mediaInfoSubtitleLanguages = GetLanguagesToken(episodeFile.MediaInfo.Subtitles); if (!mediaInfoSubtitleLanguages.IsNullOrWhiteSpace()) { mediaInfoSubtitleLanguages = string.Format("[{0}]", mediaInfoSubtitleLanguages); } var videoBitDepth = episodeFile.MediaInfo.VideoBitDepth > 0 ? episodeFile.MediaInfo.VideoBitDepth.ToString() : string.Empty; var audioChannels = episodeFile.MediaInfo.FormattedAudioChannels > 0 ? episodeFile.MediaInfo.FormattedAudioChannels.ToString("F1", CultureInfo.InvariantCulture) : string.Empty; tokenHandlers["{MediaInfo Video}"] = m => videoCodec; tokenHandlers["{MediaInfo VideoCodec}"] = m => videoCodec; tokenHandlers["{MediaInfo VideoBitDepth}"] = m => videoBitDepth; tokenHandlers["{MediaInfo Audio}"] = m => audioCodec; tokenHandlers["{MediaInfo AudioCodec}"] = m => audioCodec; tokenHandlers["{MediaInfo AudioChannels}"] = m => audioChannels; tokenHandlers["{MediaInfo Simple}"] = m => string.Format("{0} {1}", videoCodec, audioCodec); tokenHandlers["{MediaInfo Full}"] = m => string.Format("{0} {1}{2} {3}", videoCodec, audioCodec, mediaInfoAudioLanguages, mediaInfoSubtitleLanguages); }
public EpisodeImportedEvent(LocalEpisode episodeInfo, EpisodeFile importedEpisode, bool newDownload) { EpisodeInfo = episodeInfo; ImportedEpisode = importedEpisode; NewDownload = newDownload; }
private void AddEpisodeFileTokens(Dictionary <String, Func <TokenMatch, String> > tokenHandlers, Series series, EpisodeFile episodeFile) { tokenHandlers["{Original Title}"] = m => GetOriginalTitle(episodeFile); tokenHandlers["{Original Filename}"] = m => GetOriginalFileName(episodeFile); tokenHandlers["{Release Group}"] = m => episodeFile.ReleaseGroup ?? "Sonarr"; }
public virtual EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, bool newDownload = false) { if (episodeFile == null) { throw new ArgumentNullException("episodeFile"); } var series = _seriesProvider.GetSeries(episodeFile.SeriesId); var episodes = _episodeProvider.GetEpisodesByFileId(episodeFile.EpisodeFileId); string newFileName = _mediaFileProvider.GetNewFilename(episodes, series, episodeFile.Quality, episodeFile.Proper, episodeFile); var newFile = _mediaFileProvider.CalculateFilePath(series, episodes.First().SeasonNumber, newFileName, Path.GetExtension(episodeFile.Path)); //Only rename if existing and new filenames don't match if (DiskProvider.PathEquals(episodeFile.Path, newFile.FullName)) { Logger.Debug("Skipping file rename, source and destination are the same: {0}", episodeFile.Path); return(null); } if (!_diskProvider.FileExists(episodeFile.Path)) { Logger.Error("Episode file path does not exist, {0}", episodeFile.Path); return(null); } _diskProvider.CreateDirectory(newFile.DirectoryName); Logger.Debug("Moving [{0}] > [{1}]", episodeFile.Path, newFile.FullName); _diskProvider.MoveFile(episodeFile.Path, newFile.FullName); //Wrapped in Try/Catch to prevent this from causing issues with remote NAS boxes, the move worked, which is more important. try { _diskProvider.InheritFolderPermissions(newFile.FullName); } catch (UnauthorizedAccessException ex) { Logger.Debug("Unable to apply folder permissions to: ", newFile.FullName); Logger.TraceException(ex.Message, ex); } episodeFile.Path = newFile.FullName; _mediaFileProvider.Update(episodeFile); var parseResult = Parser.ParsePath(episodeFile.Path); parseResult.Series = series; parseResult.Quality = new QualityModel { Quality = episodeFile.Quality, Proper = episodeFile.Proper }; parseResult.Episodes = episodes; var message = _downloadProvider.GetDownloadTitle(parseResult); if (newDownload) { _externalNotificationProvider.OnDownload(message, series); foreach (var episode in episodes) { _signalRProvider.UpdateEpisodeStatus(episode.EpisodeId, EpisodeStatusType.Ready, parseResult.Quality); } } else { _externalNotificationProvider.OnRename(message, series); } return(episodeFile); }
private void AddMediaInfoTokens(Dictionary <String, Func <TokenMatch, String> > tokenHandlers, EpisodeFile episodeFile) { if (episodeFile.MediaInfo == null) { return; } String mediaInfoVideo; switch (episodeFile.MediaInfo.VideoCodec) { case "AVC": // TODO: What to do if the original SceneName is hashed? if (!episodeFile.SceneName.IsNullOrWhiteSpace() && Path.GetFileNameWithoutExtension(episodeFile.SceneName).Contains("h264")) { mediaInfoVideo = "h264"; } else { mediaInfoVideo = "x264"; } break; default: mediaInfoVideo = episodeFile.MediaInfo.VideoCodec; break; } String mediaInfoAudio; switch (episodeFile.MediaInfo.AudioFormat) { case "AC-3": mediaInfoAudio = "AC3"; break; case "MPEG Audio": if (episodeFile.MediaInfo.AudioProfile == "Layer 3") { mediaInfoAudio = "MP3"; } else { mediaInfoAudio = episodeFile.MediaInfo.AudioFormat; } break; case "DTS": mediaInfoAudio = episodeFile.MediaInfo.AudioFormat; break; default: mediaInfoAudio = episodeFile.MediaInfo.AudioFormat; break; } var mediaInfoAudioLanguages = GetLanguagesToken(episodeFile.MediaInfo.AudioLanguages); if (!mediaInfoAudioLanguages.IsNullOrWhiteSpace()) { mediaInfoAudioLanguages = String.Format("[{0}]", mediaInfoAudioLanguages); } if (mediaInfoAudioLanguages == "[EN]") { mediaInfoAudioLanguages = String.Empty; } var mediaInfoSubtitleLanguages = GetLanguagesToken(episodeFile.MediaInfo.Subtitles); if (!mediaInfoSubtitleLanguages.IsNullOrWhiteSpace()) { mediaInfoSubtitleLanguages = String.Format("[{0}]", mediaInfoSubtitleLanguages); } tokenHandlers["{MediaInfo Video}"] = m => mediaInfoVideo; tokenHandlers["{MediaInfo Audio}"] = m => mediaInfoAudio; tokenHandlers["{MediaInfo Simple}"] = m => String.Format("{0} {1}", mediaInfoVideo, mediaInfoAudio); tokenHandlers["{MediaInfo Full}"] = m => String.Format("{0} {1}{2} {3}", mediaInfoVideo, mediaInfoAudio, mediaInfoAudioLanguages, mediaInfoSubtitleLanguages); }
public EpisodeFileAddedEvent(EpisodeFile episodeFile) { EpisodeFile = episodeFile; }
private MetadataFile WriteEpisodeMetadata(Series series, EpisodeFile episodeFile, List <MetadataFile> existingMetadataFiles) { var filename = GetEpisodeMetadataFilename(episodeFile.Path); var relativePath = DiskProviderBase.GetRelativePath(series.Path, filename); var existingMetadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeMetadata && c.EpisodeFileId == episodeFile.Id); if (existingMetadata != null) { var fullPath = Path.Combine(series.Path, existingMetadata.RelativePath); if (!filename.PathEquals(fullPath)) { _diskProvider.MoveFile(fullPath, filename); existingMetadata.RelativePath = relativePath; } } _logger.Debug("Generating {0} for: {1}", filename, episodeFile.Path); var xmlResult = String.Empty; foreach (var episode in episodeFile.Episodes.Value) { var sb = new StringBuilder(); var xws = new XmlWriterSettings(); xws.OmitXmlDeclaration = true; xws.Indent = false; using (var xw = XmlWriter.Create(sb, xws)) { var doc = new XDocument(); var details = new XElement("video"); details.Add(new XElement("title", String.Format("{0} - {1}x{2} - {3}", series.Title, episode.SeasonNumber, episode.EpisodeNumber, episode.Title))); details.Add(new XElement("year", episode.AirDate)); details.Add(new XElement("genre", String.Join(" / ", series.Genres))); var actors = String.Join(" , ", series.Actors.ConvertAll(c => c.Name + " - " + c.Character).GetRange(0, Math.Min(3, series.Actors.Count))); details.Add(new XElement("actors", actors)); details.Add(new XElement("description", episode.Overview)); details.Add(new XElement("length", series.Runtime)); details.Add(new XElement("mpaa", ValidCertification.Contains(series.Certification.ToUpperInvariant()) ? series.Certification.ToUpperInvariant() : "UNRATED")); doc.Add(details); doc.Save(xw); xmlResult += doc.ToString(); xmlResult += Environment.NewLine; } } _logger.Debug("Saving episodedetails to: {0}", filename); _diskProvider.WriteAllText(filename, xmlResult.Trim(Environment.NewLine.ToCharArray())); var metadata = existingMetadata ?? new MetadataFile { SeriesId = series.Id, EpisodeFileId = episodeFile.Id, Consumer = GetType().Name, Type = MetadataType.EpisodeMetadata, RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename) }; return(metadata); }
public virtual EpisodeFile ImportFile(Series series, string filePath) { Logger.Trace("Importing file to database [{0}]", filePath); if (_mediaFileProvider.Exists(filePath)) { Logger.Trace("[{0}] already exists in the database. skipping.", filePath); return(null); } var parseResult = Parser.ParsePath(filePath); if (parseResult == null) { return(null); } var size = _diskProvider.GetSize(filePath); var runTime = _mediaInfoProvider.GetRunTime(filePath); if (series.IsDaily || parseResult.SeasonNumber > 0) { if (size < Constants.IgnoreFileSize && runTime < 180) { Logger.Trace("[{0}] appears to be a sample. skipping.", filePath); return(null); } } if (!_diskProvider.IsChildOfPath(filePath, series.Path)) { parseResult.SceneSource = true; } parseResult.SeriesTitle = series.Title; //replaces the nasty path as title to help with logging parseResult.Series = series; var episodes = _episodeProvider.GetEpisodesByParseResult(parseResult); if (episodes.Count <= 0) { Logger.Debug("Can't find any matching episodes in the database. Skipping {0}", filePath); return(null); } //Make sure this file is an upgrade for ALL episodes already on disk if (episodes.All(e => e.EpisodeFile == null || e.EpisodeFile.QualityWrapper < parseResult.Quality)) { Logger.Info("Deleting the existing file(s) on disk to upgrade to: {0}", filePath); //Do the delete for files where there is already an episode on disk episodes.Where(e => e.EpisodeFile != null).Select(e => e.EpisodeFile.Path).Distinct().ToList().ForEach(p => _recycleBinProvider.DeleteFile(p)); } else { //Skip this file because its not an upgrade Logger.Trace("This file isn't an upgrade for all episodes. Skipping {0}", filePath); return(null); } var episodeFile = new EpisodeFile(); episodeFile.DateAdded = DateTime.Now; episodeFile.SeriesId = series.SeriesId; episodeFile.Path = filePath.NormalizePath(); episodeFile.Size = size; episodeFile.Quality = parseResult.Quality.Quality; episodeFile.Proper = parseResult.Quality.Proper; episodeFile.SeasonNumber = parseResult.SeasonNumber; episodeFile.SceneName = Path.GetFileNameWithoutExtension(filePath.NormalizePath()); episodeFile.ReleaseGroup = parseResult.ReleaseGroup; //Todo: We shouldn't actually import the file until we confirm its the only one we want. //Todo: Separate episodeFile creation from importing (pass file to import to import) var fileId = _mediaFileProvider.Add(episodeFile); //Link file to all episodes foreach (var ep in episodes) { ep.EpisodeFileId = fileId; ep.PostDownloadStatus = PostDownloadStatusType.NoError; _episodeProvider.UpdateEpisode(ep); Logger.Debug("Linking [{0}] > [{1}]", filePath, ep); } return(episodeFile); }
private void AddEpisodeFileTokens(Dictionary <string, Func <TokenMatch, string> > tokenHandlers, EpisodeFile episodeFile) { tokenHandlers["{Original Title}"] = m => GetOriginalTitle(episodeFile); tokenHandlers["{Original Filename}"] = m => GetOriginalFileName(episodeFile); tokenHandlers["{Release Group}"] = m => episodeFile.ReleaseGroup ?? m.DefaultValue("Sonarr"); }
public override List <ImageFileResult> EpisodeImages(Series series, EpisodeFile episodeFile) { return(new List <ImageFileResult>()); }
private void AddQualityTokens(Dictionary <string, Func <TokenMatch, string> > tokenHandlers, Series series, EpisodeFile episodeFile) { var qualityTitle = _qualityDefinitionService.Get(episodeFile.Quality.Quality).Title; var qualityProper = GetQualityProper(series, episodeFile.Quality); var qualityReal = GetQualityReal(series, episodeFile.Quality); tokenHandlers["{Quality Full}"] = m => String.Format("{0} {1} {2}", qualityTitle, qualityProper, qualityReal); tokenHandlers["{Quality Title}"] = m => qualityTitle; tokenHandlers["{Quality Proper}"] = m => qualityProper; tokenHandlers["{Quality Real}"] = m => qualityReal; }
public override MetadataFileResult EpisodeMetadata(Series series, EpisodeFile episodeFile) { if (!Settings.EpisodeMetadata) { return(null); } _logger.Debug("Generating Episode Metadata for: {0}", Path.Combine(series.Path, episodeFile.RelativePath)); var xmlResult = string.Empty; foreach (var episode in episodeFile.Episodes.Value) { var sb = new StringBuilder(); var xws = new XmlWriterSettings(); xws.OmitXmlDeclaration = true; xws.Indent = false; using (var xw = XmlWriter.Create(sb, xws)) { var doc = new XDocument(); var image = episode.Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot); var details = new XElement("episodedetails"); details.Add(new XElement("title", episode.Title)); details.Add(new XElement("season", episode.SeasonNumber)); details.Add(new XElement("episode", episode.EpisodeNumber)); details.Add(new XElement("aired", episode.AirDate)); details.Add(new XElement("plot", episode.Overview)); //If trakt ever gets airs before information for specials we should add set it details.Add(new XElement("displayseason")); details.Add(new XElement("displayepisode")); if (image == null) { details.Add(new XElement("thumb")); } else { details.Add(new XElement("thumb", image.Url)); } details.Add(new XElement("watched", "false")); if (episode.Ratings != null && episode.Ratings.Votes > 0) { details.Add(new XElement("rating", episode.Ratings.Value)); } if (episodeFile.MediaInfo != null) { var fileInfo = new XElement("fileinfo"); var streamDetails = new XElement("streamdetails"); var video = new XElement("video"); video.Add(new XElement("aspect", (float)episodeFile.MediaInfo.Width / (float)episodeFile.MediaInfo.Height)); video.Add(new XElement("bitrate", episodeFile.MediaInfo.VideoBitrate)); video.Add(new XElement("codec", episodeFile.MediaInfo.VideoCodec)); video.Add(new XElement("framerate", episodeFile.MediaInfo.VideoFps)); video.Add(new XElement("height", episodeFile.MediaInfo.Height)); video.Add(new XElement("scantype", episodeFile.MediaInfo.ScanType)); video.Add(new XElement("width", episodeFile.MediaInfo.Width)); if (episodeFile.MediaInfo.RunTime != null) { video.Add(new XElement("duration", episodeFile.MediaInfo.RunTime.TotalMinutes)); video.Add(new XElement("durationinseconds", episodeFile.MediaInfo.RunTime.TotalSeconds)); } streamDetails.Add(video); var audio = new XElement("audio"); audio.Add(new XElement("bitrate", episodeFile.MediaInfo.AudioBitrate)); audio.Add(new XElement("channels", episodeFile.MediaInfo.AudioChannels)); audio.Add(new XElement("codec", GetAudioCodec(episodeFile.MediaInfo.AudioFormat))); audio.Add(new XElement("language", episodeFile.MediaInfo.AudioLanguages)); streamDetails.Add(audio); if (episodeFile.MediaInfo.Subtitles != null && episodeFile.MediaInfo.Subtitles.Length > 0) { var subtitle = new XElement("subtitle"); subtitle.Add(new XElement("language", episodeFile.MediaInfo.Subtitles)); streamDetails.Add(subtitle); } fileInfo.Add(streamDetails); details.Add(fileInfo); } //Todo: get guest stars, writer and director //details.Add(new XElement("credits", tvdbEpisode.Writer.FirstOrDefault())); //details.Add(new XElement("director", tvdbEpisode.Directors.FirstOrDefault())); doc.Add(details); doc.Save(xw); xmlResult += doc.ToString(); xmlResult += Environment.NewLine; } } return(new MetadataFileResult(GetEpisodeMetadataFilename(episodeFile.RelativePath), xmlResult.Trim(Environment.NewLine.ToCharArray()))); }
private void AddMediaInfoTokens(Dictionary <string, Func <TokenMatch, string> > tokenHandlers, EpisodeFile episodeFile) { if (episodeFile.MediaInfo == null) { return; } var sceneName = episodeFile.GetSceneOrFileName(); var videoCodec = MediaInfoFormatter.FormatVideoCodec(episodeFile.MediaInfo, sceneName); var audioCodec = MediaInfoFormatter.FormatAudioCodec(episodeFile.MediaInfo, sceneName); var audioChannels = MediaInfoFormatter.FormatAudioChannels(episodeFile.MediaInfo); var mediaInfoAudioLanguages = GetLanguagesToken(episodeFile.MediaInfo.AudioLanguages); if (!mediaInfoAudioLanguages.IsNullOrWhiteSpace()) { mediaInfoAudioLanguages = $"[{mediaInfoAudioLanguages}]"; } if (mediaInfoAudioLanguages == "[EN]") { mediaInfoAudioLanguages = string.Empty; } var mediaInfoSubtitleLanguages = GetLanguagesToken(episodeFile.MediaInfo.Subtitles); if (!mediaInfoSubtitleLanguages.IsNullOrWhiteSpace()) { mediaInfoSubtitleLanguages = $"[{mediaInfoSubtitleLanguages}]"; } var videoBitDepth = episodeFile.MediaInfo.VideoBitDepth > 0 ? episodeFile.MediaInfo.VideoBitDepth.ToString() : string.Empty; var audioChannelsFormatted = audioChannels > 0 ? audioChannels.ToString("F1", CultureInfo.InvariantCulture) : string.Empty; tokenHandlers["{MediaInfo Video}"] = m => videoCodec; tokenHandlers["{MediaInfo VideoCodec}"] = m => videoCodec; tokenHandlers["{MediaInfo VideoBitDepth}"] = m => videoBitDepth; tokenHandlers["{MediaInfo Audio}"] = m => audioCodec; tokenHandlers["{MediaInfo AudioCodec}"] = m => audioCodec; tokenHandlers["{MediaInfo AudioChannels}"] = m => audioChannelsFormatted; tokenHandlers["{MediaInfo Simple}"] = m => $"{videoCodec} {audioCodec}"; tokenHandlers["{MediaInfo Full}"] = m => $"{videoCodec} {audioCodec}{mediaInfoAudioLanguages} {mediaInfoSubtitleLanguages}"; }
public void ImportEpisode(LocalEpisode localEpisode, EpisodeFile episodeFile, bool isReadOnly) { ImportExtraFiles(localEpisode, episodeFile, isReadOnly); CreateAfterImport(localEpisode.Series, episodeFile); }
protected override bool CanExecute(EpisodeFile episodeFile) { return (Model.RunningFiles.Contains(episodeFile) && (episodeFile.RetryTimer != null)); }
public EpisodeFileAction(EpisodeFile file) { this.file = file; this.file.PropertyChanged += (s, e) => OnPropertyChanged("TargetPath"); }
public EpisodeFileDeletedEvent(EpisodeFile episodeFile, Boolean forUpgrade) { EpisodeFile = episodeFile; ForUpgrade = forUpgrade; }
protected override void Execute(EpisodeFile episodeFile) { episodeFile.Cancel(); Model.Files.Remove(episodeFile); }
private void ImportExtraFiles(LocalEpisode localEpisode, EpisodeFile episodeFile, bool isReadOnly) { if (!_configService.ImportExtraFiles) { return; } var sourcePath = localEpisode.Path; var sourceFolder = _diskProvider.GetParentFolder(sourcePath); var sourceFileName = Path.GetFileNameWithoutExtension(sourcePath); var files = _diskProvider.GetFiles(sourceFolder, SearchOption.TopDirectoryOnly); var wantedExtensions = _configService.ExtraFileExtensions.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(e => e.Trim(' ', '.')) .ToList(); var matchingFilenames = files.Where(f => Path.GetFileNameWithoutExtension(f).StartsWith(sourceFileName, StringComparison.InvariantCultureIgnoreCase)).ToList(); var filteredFilenames = new List <string>(); var hasNfo = false; foreach (var matchingFilename in matchingFilenames) { // Filter out duplicate NFO files if (matchingFilename.EndsWith(".nfo", StringComparison.InvariantCultureIgnoreCase)) { if (hasNfo) { continue; } hasNfo = true; } filteredFilenames.Add(matchingFilename); } foreach (var matchingFilename in filteredFilenames) { var matchingExtension = wantedExtensions.FirstOrDefault(e => matchingFilename.EndsWith(e)); if (matchingExtension == null) { continue; } try { foreach (var extraFileManager in _extraFileManagers) { var extension = Path.GetExtension(matchingFilename); var extraFile = extraFileManager.Import(localEpisode.Series, episodeFile, matchingFilename, extension, isReadOnly); if (extraFile != null) { break; } } } catch (Exception ex) { _logger.Warn(ex, "Failed to import extra file: {0}", matchingFilename); } } }
public List<ImportDecision> Import(List<ImportDecision> decisions, bool newDownload = false) { var qualifiedImports = decisions.Where(c => c.Approved) .GroupBy(c => c.LocalEpisode.Series.Id, (i, s) => s .OrderByDescending(c => c.LocalEpisode.Quality, new QualityModelComparer(s.First().LocalEpisode.Series.QualityProfile)) .ThenByDescending(c => c.LocalEpisode.Size)) .SelectMany(c => c) .ToList(); var imported = new List<ImportDecision>(); foreach (var importDecision in qualifiedImports.OrderByDescending(e => e.LocalEpisode.Size)) { var localEpisode = importDecision.LocalEpisode; var oldFiles = new List<EpisodeFile>(); try { //check if already imported if (imported.SelectMany(r => r.LocalEpisode.Episodes) .Select(e => e.Id) .Intersect(localEpisode.Episodes.Select(e => e.Id)) .Any()) { continue; } var episodeFile = new EpisodeFile(); episodeFile.DateAdded = DateTime.UtcNow; episodeFile.SeriesId = localEpisode.Series.Id; episodeFile.Path = localEpisode.Path.CleanFilePath(); episodeFile.Size = _diskProvider.GetFileSize(localEpisode.Path); episodeFile.Quality = localEpisode.Quality; episodeFile.SeasonNumber = localEpisode.SeasonNumber; episodeFile.Episodes = localEpisode.Episodes; episodeFile.ReleaseGroup = localEpisode.ParsedEpisodeInfo.ReleaseGroup; if (newDownload) { episodeFile.SceneName = Path.GetFileNameWithoutExtension(localEpisode.Path.CleanFilePath()); var moveResult = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode); oldFiles = moveResult.OldFiles; } _mediaFileService.Add(episodeFile); imported.Add(importDecision); _eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, newDownload)); if (newDownload) { _eventAggregator.PublishEvent(new EpisodeDownloadedEvent(localEpisode, episodeFile, oldFiles)); } } catch (Exception e) { _logger.WarnException("Couldn't import episode " + localEpisode, e); } } return imported; }
public override IEnumerable <ExtraFile> CreateAfterEpisodeImport(Series series, EpisodeFile episodeFile) { var files = new List <MetadataFile>(); foreach (var consumer in _metadataFactory.Enabled()) { files.AddIfNotNull(ProcessEpisodeMetadata(consumer, series, episodeFile, new List <MetadataFile>())); files.AddRange(ProcessEpisodeImages(consumer, series, episodeFile, new List <MetadataFile>())); } _metadataFileService.Upsert(files); return(files); }
public EpisodeImportedEvent(LocalEpisode droppedEpisode, EpisodeFile importedEpisode) { DroppedEpisode = droppedEpisode; ImportedEpisode = importedEpisode; }
public override ExtraFile Import(Series series, EpisodeFile episodeFile, string path, string extension, bool readOnly) { return(null); }
public EpisodeFolderCreatedEvent(Series series, EpisodeFile episodeFile) { Series = series; EpisodeFile = episodeFile; }
private MetadataFile ProcessEpisodeMetadata(IMetadata consumer, Series series, EpisodeFile episodeFile, List <MetadataFile> existingMetadataFiles) { var episodeMetadata = consumer.EpisodeMetadata(series, episodeFile); if (episodeMetadata == null) { return(null); } var fullPath = Path.Combine(series.Path, episodeMetadata.RelativePath); _otherExtraFileRenamer.RenameOtherExtraFile(series, fullPath); var existingMetadata = GetMetadataFile(series, existingMetadataFiles, c => c.Type == MetadataType.EpisodeMetadata && c.EpisodeFileId == episodeFile.Id); if (existingMetadata != null) { var existingFullPath = Path.Combine(series.Path, existingMetadata.RelativePath); if (fullPath.PathNotEquals(existingFullPath)) { _diskTransferService.TransferFile(existingFullPath, fullPath, TransferMode.Move); existingMetadata.RelativePath = episodeMetadata.RelativePath; } } var hash = episodeMetadata.Contents.SHA256Hash(); var metadata = existingMetadata ?? new MetadataFile { SeriesId = series.Id, SeasonNumber = episodeFile.SeasonNumber, EpisodeFileId = episodeFile.Id, Consumer = consumer.GetType().Name, Type = MetadataType.EpisodeMetadata, RelativePath = episodeMetadata.RelativePath, Extension = Path.GetExtension(fullPath) }; if (hash == metadata.Hash) { return(null); } _logger.Debug("Writing Episode Metadata to: {0}", fullPath); SaveMetadataFile(fullPath, episodeMetadata.Contents); metadata.Hash = hash; return(metadata); }
private List <MetadataFile> ProcessEpisodeImages(IMetadata consumer, Series series, EpisodeFile episodeFile, List <MetadataFile> existingMetadataFiles) { var result = new List <MetadataFile>(); foreach (var image in consumer.EpisodeImages(series, episodeFile)) { var fullPath = Path.Combine(series.Path, image.RelativePath); if (_diskProvider.FileExists(fullPath)) { _logger.Debug("Episode image already exists: {0}", fullPath); continue; } _otherExtraFileRenamer.RenameOtherExtraFile(series, fullPath); var existingMetadata = GetMetadataFile(series, existingMetadataFiles, c => c.Type == MetadataType.EpisodeImage && c.EpisodeFileId == episodeFile.Id); if (existingMetadata != null) { var existingFullPath = Path.Combine(series.Path, existingMetadata.RelativePath); if (fullPath.PathNotEquals(existingFullPath)) { _diskTransferService.TransferFile(existingFullPath, fullPath, TransferMode.Move); existingMetadata.RelativePath = image.RelativePath; return(new List <MetadataFile> { existingMetadata }); } } var metadata = existingMetadata ?? new MetadataFile { SeriesId = series.Id, SeasonNumber = episodeFile.SeasonNumber, EpisodeFileId = episodeFile.Id, Consumer = consumer.GetType().Name, Type = MetadataType.EpisodeImage, RelativePath = image.RelativePath, Extension = Path.GetExtension(fullPath) }; DownloadImage(series, image); result.Add(metadata); } return(result); }
public EpisodeDownloadedEvent(LocalEpisode episode, EpisodeFile episodeFile, List<EpisodeFile> oldFiles) { Episode = episode; EpisodeFile = episodeFile; OldFiles = oldFiles; }
public void RemoveEpisodeFromCollection(TraktSettings settings, Series series, EpisodeFile episodeFile) { var payload = new TraktCollectShowsResource { Shows = new List <TraktCollectShow>() }; var payloadEpisodes = new List <TraktEpisodeResource>(); foreach (var episode in episodeFile.Episodes.Value) { payloadEpisodes.Add(new TraktEpisodeResource { Number = episode.EpisodeNumber }); } var payloadSeasons = new List <TraktSeasonResource>(); payloadSeasons.Add(new TraktSeasonResource { Number = episodeFile.SeasonNumber, Episodes = payloadEpisodes }); payload.Shows.Add(new TraktCollectShow { Title = series.Title, Year = series.Year, Ids = new TraktShowIdsResource { Tvdb = series.TvdbId, Imdb = series.ImdbId ?? "", }, Seasons = payloadSeasons, }); _proxy.RemoveFromCollection(payload, settings.AccessToken); }
protected abstract void Execute(EpisodeFile episodeFile);
private string MapAudio(EpisodeFile episodeFile) { var traktAudioFormat = string.Empty; var audioCodec = episodeFile.MediaInfo != null?MediaInfoFormatter.FormatAudioCodec(episodeFile.MediaInfo, episodeFile.SceneName) : string.Empty; switch (audioCodec) { case "AC3": traktAudioFormat = "dolby_digital"; break; case "EAC3": traktAudioFormat = "dolby_digital_plus"; break; case "TrueHD": traktAudioFormat = "dolby_truehd"; break; case "EAC3 Atmos": traktAudioFormat = "dolby_digital_plus_atmos"; break; case "TrueHD Atmos": traktAudioFormat = "dolby_atmos"; break; case "DTS": case "DTS-ES": traktAudioFormat = "dts"; break; case "DTS-HD MA": traktAudioFormat = "dts_ma"; break; case "DTS-HD HRA": traktAudioFormat = "dts_hr"; break; case "DTS-X": traktAudioFormat = "dts_x"; break; case "MP3": traktAudioFormat = "mp3"; break; case "MP2": traktAudioFormat = "mp2"; break; case "Vorbis": traktAudioFormat = "ogg"; break; case "WMA": traktAudioFormat = "wma"; break; case "AAC": traktAudioFormat = "aac"; break; case "PCM": traktAudioFormat = "lpcm"; break; case "FLAC": traktAudioFormat = "flac"; break; case "Opus": traktAudioFormat = "ogg_opus"; break; } return(traktAudioFormat); }
public List<ImportResult> Import(List<ImportDecision> decisions, bool newDownload, DownloadClientItem downloadClientItem = null) { var qualifiedImports = decisions.Where(c => c.Approved) .GroupBy(c => c.LocalEpisode.Series.Id, (i, s) => s .OrderByDescending(c => c.LocalEpisode.Quality, new QualityModelComparer(s.First().LocalEpisode.Series.Profile)) .ThenByDescending(c => c.LocalEpisode.Size)) .SelectMany(c => c) .ToList(); var importResults = new List<ImportResult>(); foreach (var importDecision in qualifiedImports.OrderByDescending(e => e.LocalEpisode.Episodes.Select(episode => episode.EpisodeNumber).MinOrDefault()) .ThenByDescending(e => e.LocalEpisode.Size)) { var localEpisode = importDecision.LocalEpisode; var oldFiles = new List<EpisodeFile>(); try { //check if already imported if (importResults.SelectMany(r => r.ImportDecision.LocalEpisode.Episodes) .Select(e => e.Id) .Intersect(localEpisode.Episodes.Select(e => e.Id)) .Any()) { importResults.Add(new ImportResult(importDecision, "Episode has already been imported")); continue; } var episodeFile = new EpisodeFile(); episodeFile.DateAdded = DateTime.UtcNow; episodeFile.SeriesId = localEpisode.Series.Id; episodeFile.Path = localEpisode.Path.CleanFilePath(); episodeFile.Size = _diskProvider.GetFileSize(localEpisode.Path); episodeFile.Quality = localEpisode.Quality; episodeFile.MediaInfo = localEpisode.MediaInfo; episodeFile.SeasonNumber = localEpisode.SeasonNumber; episodeFile.Episodes = localEpisode.Episodes; episodeFile.ReleaseGroup = localEpisode.ParsedEpisodeInfo.ReleaseGroup; if (newDownload) { bool copyOnly = downloadClientItem != null && downloadClientItem.IsReadOnly; episodeFile.SceneName = GetSceneName(downloadClientItem, localEpisode); var moveResult = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode, copyOnly); oldFiles = moveResult.OldFiles; } else { episodeFile.RelativePath = localEpisode.Series.Path.GetRelativePath(episodeFile.Path); } _mediaFileService.Add(episodeFile); importResults.Add(new ImportResult(importDecision)); if (downloadClientItem != null) { _eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, newDownload, downloadClientItem.DownloadClient, downloadClientItem.DownloadId)); } else { _eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, newDownload)); } if (newDownload) { _eventAggregator.PublishEvent(new EpisodeDownloadedEvent(localEpisode, episodeFile, oldFiles)); } } catch (Exception e) { _logger.WarnException("Couldn't import episode " + localEpisode, e); importResults.Add(new ImportResult(importDecision, "Failed to import episode")); } } //Adding all the rejected decisions importResults.AddRange(decisions.Where(c => !c.Approved) .Select(d => new ImportResult(d, d.Rejections.Select(r => r.Reason).ToArray()))); return importResults; }
private string MapAudioChannels(EpisodeFile episodeFile, string audioFormat) { var audioChannels = episodeFile.MediaInfo != null?MediaInfoFormatter.FormatAudioChannels(episodeFile.MediaInfo).ToString("0.0") : string.Empty; return(audioChannels); }