public async Task <bool> process( string filePath, Action <string> setPath, Action <string, MessageType> setStatus, Func <List <Tuple <string, string> >, int> selectResult, AutoTagConfig config ) { if (tvdb.Authentication.Token == null) { await tvdb.Authentication.AuthenticateAsync(apiKey); } FileMetadata result = new FileMetadata(FileMetadata.Types.TV); #region Filename parsing FileMetadata episodeData; if (string.IsNullOrEmpty(config.parsePattern)) { try { episodeData = EpisodeParser.ParseEpisodeInfo(Path.GetFileName(filePath)); // Parse info from filename } catch (FormatException ex) { setStatus($"Error: {ex.Message}", MessageType.Error); return(false); } } else { try { var match = Regex.Match(Path.GetFullPath(filePath), config.parsePattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); episodeData = new FileMetadata(FileMetadata.Types.TV); episodeData.SeriesName = match.Groups["SeriesName"].Value; episodeData.Season = int.Parse(match.Groups["Season"].Value); episodeData.Episode = int.Parse(match.Groups["Episode"].Value); } catch (FormatException ex) { if (config.verbose) { setStatus($"Error: Unable to parse required information from filename ({ex.GetType().Name}: {ex.Message})", MessageType.Error); } else { setStatus($"Error: Unable to parse required information from filename", MessageType.Error); } return(false); } } result.Season = episodeData.Season; result.Episode = episodeData.Episode; setStatus($"Parsed file as {episodeData}", MessageType.Information); #endregion #region TVDB API searching if (!seriesResultCache.ContainsKey(episodeData.SeriesName)) // if not already searched for series { TvDbResponse <SeriesSearchResult[]> seriesIdResponse; try { seriesIdResponse = await tvdb.Search.SearchSeriesByNameAsync(episodeData.SeriesName); } catch (TvDbServerException ex) { if (config.verbose) { setStatus($"Error: Cannot find series {episodeData.SeriesName} ({ex.GetType().Name}: {ex.Message})", MessageType.Error); } else { setStatus($"Error: Cannot find series {episodeData.SeriesName} on TheTVDB", MessageType.Error); } return(false); } // sort results by similarity to parsed series name List <SeriesSearchResult> seriesResults = seriesIdResponse.Data .OrderByDescending(seriesResult => SeriesNameSimilarity(episodeData.SeriesName, seriesResult.SeriesName)) .ToList(); if (config.manualMode && seriesResults.Count > 1) { int chosen = selectResult(seriesResults.Select( r => new Tuple <string, string>(r.SeriesName, r.FirstAired ?? "Unknown") ).ToList() ); // add only the chosen series to cache if in manual mode seriesResultCache.Add(episodeData.SeriesName, new List <SeriesSearchResult> { seriesResults[chosen] }); } else { seriesResultCache.Add(episodeData.SeriesName, seriesResults); } } // try searching for each series search result foreach (var series in seriesResultCache[episodeData.SeriesName]) { result.Id = series.Id; result.SeriesName = series.SeriesName; try { TvDbResponse <EpisodeRecord[]> episodeResponse = await tvdb.Series.GetEpisodesAsync(series.Id, 1, new EpisodeQuery { AiredSeason = episodeData.Season, AiredEpisode = episodeData.Episode } ); result.Title = episodeResponse.Data[0].EpisodeName; result.Overview = episodeResponse.Data[0].Overview; break; } catch (TvDbServerException ex) { if (series.Id == seriesResultCache[episodeData.SeriesName].Last().Id) { if (config.verbose) { setStatus($"Error: Cannot find {episodeData} ({ex.GetType().Name}: {ex.Message})", MessageType.Error); } else { setStatus($"Error: Cannot find {episodeData} on TheTVDB", MessageType.Error); } return(false); } } } setStatus($"Found {episodeData} ({result.Title}) on TheTVDB", MessageType.Information); TvDbResponse <TvDbSharper.Dto.Image[]> imagesResponse = null; if (config.addCoverArt) { try { imagesResponse = await tvdb.Series.GetImagesAsync(result.Id, new ImagesQuery { KeyType = KeyType.Season, SubKey = episodeData.Season.ToString() }); } catch (TvDbServerException) { try { // use a series image if a season-specific image is not available imagesResponse = await tvdb.Series.GetImagesAsync(result.Id, new ImagesQuery { KeyType = KeyType.Series }); } catch (TvDbServerException ex) { if (config.verbose) { setStatus($"Error: Failed to find episode cover ({ex.GetType().Name}: {ex.Message})", MessageType.Error); } else { setStatus("Error: Failed to find episode cover", MessageType.Error); } result.Complete = false; } } } string imageFilename = ""; if (imagesResponse != null) { imageFilename = imagesResponse.Data .OrderByDescending(img => img.RatingsInfo.Average) .First().FileName; // Find highest rated image } #endregion result.CoverURL = (String.IsNullOrEmpty(imageFilename)) ? null : $"https://artworks.thetvdb.com/banners/{imageFilename}"; result.CoverFilename = imageFilename.Split('/').Last(); bool taggingSuccess = FileWriter.write(filePath, result, setPath, setStatus, config); return(taggingSuccess && result.Success && result.Complete); }
public async Task <bool> process( string filePath, Action <string> setPath, Action <string, MessageType> setStatus, Func <List <Tuple <string, string> >, int> selectResult, AutoTagConfig config ) { FileMetadata result = new FileMetadata(FileMetadata.Types.Movie); #region "Filename parsing" string pattern = "^((?<Title>.+?)[\\. _-]?)" + // get title by reading from start to a field (whichever field comes first) "?(" + "([\\(]?(?<Year>(19|20)[0-9]{2})[\\)]?)|" + // year - extract for use in searching "([0-9]{3,4}(p|i))|" + // resolution (e.g. 1080p, 720i) "((?:PPV\\.)?[HPS]DTV|[. ](?:HD)?CAM[| ]|B[DR]Rip|[.| ](?:HD-?)?TS[.| ]|(?:PPV )?WEB-?DL(?: DVDRip)?|HDRip|DVDRip|CamRip|W[EB]Rip|BluRay|DvDScr|hdtv|REMUX|3D|Half-(OU|SBS)+|4K|NF|AMZN)|" + // rip type "(xvid|[hx]\\.?26[45]|AVC)|" + // video codec "(MP3|DD5\\.?1|Dual[\\- ]Audio|LiNE|DTS[-HD]+|AAC[.-]LC|AAC(?:\\.?2\\.0)?|AC3(?:\\.5\\.1)?|7\\.1|DDP5.1)|" + // audio codec "(REPACK|INTERNAL|PROPER)|" + // scene tags "\\.(mp4|m4v|mkv)$" + // file extensions ")"; Match match = Regex.Match(Path.GetFileName(filePath), pattern); string title, year; if (match.Success) { title = match.Groups["Title"].ToString(); year = match.Groups["Year"].ToString(); } else { setStatus("Error: Failed to parse required information from filename", MessageType.Error); return(false); } title = title.Replace('.', ' '); // change dots to spaces if (String.IsNullOrWhiteSpace(title)) { setStatus("Error: Failed to parse required information from filename", MessageType.Error); return(false); } setStatus($"Parsed file as {title}", MessageType.Information); #endregion #region "TMDB API Searching" SearchContainer <SearchMovie> searchResults; if (!String.IsNullOrWhiteSpace(year)) { searchResults = await tmdb.SearchMovieAsync(query : title, year : int.Parse(year)); // if year was parsed, use it to narrow down search further } else { searchResults = await tmdb.SearchMovieAsync(query : title); } int selected = 0; if (searchResults.Results.Count > 1 && (searchResults.Results[0].Title != title || config.manualMode)) { selected = selectResult( searchResults.Results .Select(m => new Tuple <string, string>( m.Title, m.ReleaseDate == null ? "Unknown" : m.ReleaseDate.Value.Year.ToString() )).ToList() ); } else if (searchResults.Results.Count == 0) { setStatus($"Error: failed to find title {title} on TheMovieDB", MessageType.Error); result.Success = false; return(false); } SearchMovie selectedResult = searchResults.Results[selected]; setStatus($"Found {selectedResult.Title} ({selectedResult.ReleaseDate.Value.Year}) on TheMovieDB", MessageType.Information); #endregion result.Title = selectedResult.Title; result.Overview = selectedResult.Overview; result.CoverURL = (String.IsNullOrEmpty(selectedResult.PosterPath)) ? null : $"https://image.tmdb.org/t/p/original{selectedResult.PosterPath}"; result.CoverFilename = selectedResult.PosterPath.Replace("/", ""); result.Date = selectedResult.ReleaseDate.Value; if (String.IsNullOrEmpty(result.CoverURL)) { setStatus("Error: failed to fetch movie cover", MessageType.Error); result.Complete = false; } bool taggingSuccess = FileWriter.write(filePath, result, setPath, setStatus, config); return(taggingSuccess && result.Success && result.Complete); }