/// <inheritdoc /> public void RecordScanFile(AnalyzedFile file, Movie movie) { using (var db = new LiteDatabase(GlobalSettings.Default.ConnectionString)) { // update movie data var movieCollection = db.GetCollection <Movie>(); movieCollection.Upsert(movie); // update data hash var hashCollection = db.GetCollection <MediaFileHash>(); var hashEntity = hashCollection.FindOne(x => x.Hash == file.Hash) ?? new MediaFileHash(); hashEntity.Hash = file.Hash; hashEntity.ImdbId = movie.ImdbId; hashEntity.Title = movie.Title; hashEntity.Year = movie.Year; hashCollection.Upsert(hashEntity); // update media file var mediaCollection = db.GetCollection <MediaFile>(); var mediaEntity = mediaCollection.FindOne(x => x.Hash == file.Hash) ?? new MediaFile(); mediaEntity.TmdbId = movie.TmdbId; mediaEntity.Hash = file.Hash; mediaEntity.FullPath = file.FullPath; mediaEntity.LastSync = DateTime.Now; mediaCollection.Upsert(mediaEntity); } }
// identify a file private async Task <Movie> IdentifyFile(AnalyzedFile item) { Log.DebugFormat("Querying remote: {0} ({1})", item.Title, item.Year); Movie movie = null; try { List <SearchMovie> listContainer; // remote find if (!string.IsNullOrWhiteSpace(item.ImdbId)) { // search by id var results = await _apiClient.FindAsync(FindExternalSource.Imdb, item.ImdbId); listContainer = results.MovieResults; Log.Debug($"Got {results.MovieResults.Count:N0} of {results.MovieResults.Count:N0} results"); } else { // search by title var results = await _apiClient.SearchMovieAsync(item.Title); listContainer = results.Results; Log.Debug($"Got {results.Results.Count:N0} of {results.TotalResults:N0} results"); } // get most candidate var candidateMovieRaw = listContainer.OrderByDescending(x => GetMatch(x, item)).FirstOrDefault(); if (candidateMovieRaw == null) { return(null); } movie = _dal.MapMovieToEntity(await _apiClient.GetMovieAsync(candidateMovieRaw.Id), _apiClient.GetImageUrl("w185", candidateMovieRaw.PosterPath).ToString()); } catch (Exception e) { Log.Error("Unable to process request", e); } // return fetched or fallback return(movie ?? GetByFilename()); Movie GetByFilename() { // the file is not identified by guessing algorithm Log.Debug("Unable to guess title, fallback to filename"); return(new Movie { Title = Path.GetFileName(item.FullPath), }); } }
/// <inheritdoc /> public async Task <AnalyzedFile> Analyze(string filePath) { var file = new FileInfo(filePath); var item = new AnalyzedFile(filePath); Log.DebugFormat("Analyzing: {0}", file.Name); using (var db = new LiteDatabase(GlobalSettings.Default.ConnectionString)) { var hashCollection = db.GetCollection <MediaFileHash>(); var fileCollection = db.GetCollection <MediaFile>(); // lookup for hash var hash = IOExtension.QuickHash(filePath); item.Hash = hash; var hashEntity = hashCollection.FindOne(x => x.Hash == hash); // the file has recognized hash on database and has matching movie information if (hashEntity != null && fileCollection.Exists(x => x.Hash == hashEntity.Hash)) { item.Title = hashEntity.Title; item.Year = hashEntity.Year; item.ImdbId = hashEntity.ImdbId; item.IsKnown = true; Log.DebugFormat("File found on hash table: {0}", hash); } else { // the file only has one information on database, guessing is needed var cleaned = await _provider.GuessTitle(item.FullPath); item.Title = cleaned.Title; item.Year = cleaned.Year; item.ImdbId = cleaned.ImdbId; item.IsKnown = false; Log.DebugFormat("File hash recorded: {0}", hash); } } // find subtitle and poster var extraFile = FindAssociatedFiles(Path.GetDirectoryName(filePath)); item.SubtitlePath = extraFile.Item1; item.PosterPath = extraFile.Item2; return(item); }
// get a value how it's likely the candidate is the guessed movie private float GetMatch(SearchMovie movie, AnalyzedFile candidate) { float res = 0; var dtt = DamerauLevenshtein.Calculate(candidate.Title.ToUpper(), movie.Title.ToUpper()); var dto = DamerauLevenshtein.Calculate(candidate.Title.ToUpper(), movie.OriginalTitle.ToUpper()); res += Math.Max(0, 5 - Math.Min(dtt, dto)); if (Math.Abs(candidate.Year - (movie.ReleaseDate?.Year ?? 0)) <= 1) { res += 3; } return(res); // TODO: return value unchecked for 0<x<1 interval }
// Write to string builder the information used in csv file private void WriteCsvInformation(StringBuilder sb, OptionalInformation optionalInformation, AnalyzedFile file, string csvSeparator) { sb.Append(file.FileName).Append(csvSeparator).Append(file.Repeated.Words).Append(csvSeparator); if (optionalInformation.IncludeLocked) { sb.Append(file.Locked.Words).Append(csvSeparator); } if (optionalInformation.IncludePerfectMatch) { sb.Append(file.Perfect.Words).Append(csvSeparator); } if (optionalInformation.IncludeContextMatch) { sb.Append(file.InContextExact.Words).Append(csvSeparator); } if (optionalInformation.IncludeCrossRep) { sb.Append(file.CrossRep.Words).Append(csvSeparator); } // the 100% match is actually a sum of Exact, Perfect and InContextExact matches sb.Append((file.Exact.Words + file.Perfect.Words + file.InContextExact.Words).ToString()).Append(csvSeparator); foreach (var fuzzy in file.Fuzzies.OrderByDescending(fz => fz.Max)) { sb.Append(fuzzy.Words).Append(csvSeparator); var internalFuzzy = file.InternalFuzzy(fuzzy.Min, fuzzy.Max); sb.Append(internalFuzzy != null ? internalFuzzy.Words : 0).Append(csvSeparator); if (optionalInformation.IncludeInternalFuzzies) { sb.Append(internalFuzzy != null ? internalFuzzy.FullRecallWords : 0).Append(csvSeparator); } } if (optionalInformation.IncludeAdaptiveBaseline) { sb.Append(file.NewBaseline.FullRecallWords).Append(csvSeparator); } if (optionalInformation.IncludeAdaptiveLearnings) { sb.Append(file.NewLearnings.FullRecallWords).Append(csvSeparator); } sb.Append(file.Untranslated.Words).Append(csvSeparator).Append(file.Total.Words).Append(csvSeparator).Append(Environment.NewLine); }