mirror of
https://github.com/metatube-community/jellyfin-plugin-metatube.git
synced 2026-02-02 18:21:48 +08:00
Feature: match movies using Levenshtein (#585)
This commit is contained in:
54
Jellyfin.Plugin.MetaTube/Helpers/Levenshtein.cs
Normal file
54
Jellyfin.Plugin.MetaTube/Helpers/Levenshtein.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
namespace Jellyfin.Plugin.MetaTube.Helpers;
|
||||
|
||||
public static class Levenshtein
|
||||
{
|
||||
public static int Distance(string value1, string value2)
|
||||
{
|
||||
if (value2.Length == 0)
|
||||
{
|
||||
return value1.Length;
|
||||
}
|
||||
|
||||
int[] costs = new int[value2.Length];
|
||||
|
||||
for (int i = 0; i < costs.Length;)
|
||||
{
|
||||
costs[i] = ++i;
|
||||
}
|
||||
|
||||
for (int i = 0; i < value1.Length; i++)
|
||||
{
|
||||
int cost = i;
|
||||
int previousCost = i;
|
||||
|
||||
char value1Char = value1[i];
|
||||
|
||||
for (int j = 0; j < value2.Length; j++)
|
||||
{
|
||||
int currentCost = cost;
|
||||
|
||||
cost = costs[j];
|
||||
|
||||
if (value1Char != value2[j])
|
||||
{
|
||||
if (previousCost < currentCost)
|
||||
{
|
||||
currentCost = previousCost;
|
||||
}
|
||||
|
||||
if (cost < currentCost)
|
||||
{
|
||||
currentCost = cost;
|
||||
}
|
||||
|
||||
++currentCost;
|
||||
}
|
||||
|
||||
costs[j] = currentCost;
|
||||
previousCost = currentCost;
|
||||
}
|
||||
}
|
||||
|
||||
return costs[costs.Length - 1];
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Jellyfin.Plugin.MetaTube.Configuration;
|
||||
using Jellyfin.Plugin.MetaTube.Extensions;
|
||||
using Jellyfin.Plugin.MetaTube.Helpers;
|
||||
using Jellyfin.Plugin.MetaTube.Metadata;
|
||||
using Jellyfin.Plugin.MetaTube.Translation;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@@ -148,7 +150,6 @@ public class MovieProvider : BaseProvider, IRemoteMetadataProvider<Movie, MovieI
|
||||
Logger.Info("Add Collection for movie {0} [{1}]", pid.ToString(), m.Series);
|
||||
}
|
||||
|
||||
|
||||
// Add studio.
|
||||
if (!string.IsNullOrWhiteSpace(m.Maker))
|
||||
result.Item.AddStudio(m.Maker);
|
||||
@@ -300,16 +301,31 @@ public class MovieProvider : BaseProvider, IRemoteMetadataProvider<Movie, MovieI
|
||||
if (searchResults?.Any() != true)
|
||||
{
|
||||
Logger.Warn("Movie not found on AVBASE: {0}", m.Id);
|
||||
return;
|
||||
}
|
||||
else if (searchResults.Count > 1)
|
||||
|
||||
var matched = false;
|
||||
|
||||
foreach (var result in searchResults)
|
||||
{
|
||||
// Ignore multiple results to avoid ambiguity.
|
||||
Logger.Warn("Multiple movies found on AVBASE: {0}", m.Id);
|
||||
var similarity = CalculateTitleSimilarity(m, result);
|
||||
|
||||
Logger.Info("Calculate movie title similarity for {0} and {1}: {2:P2}",
|
||||
m.Id, result.Id, similarity);
|
||||
|
||||
if (similarity >= 0.8)
|
||||
{
|
||||
if (result.Actors?.Any() == true)
|
||||
m.Actors = result.Actors;
|
||||
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if (!matched)
|
||||
{
|
||||
var firstResult = searchResults.First();
|
||||
if (firstResult.Actors?.Any() == true) m.Actors = firstResult.Actors;
|
||||
Logger.Warn("No matching movie found on AVBASE for {0}", m.Id);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -318,6 +334,31 @@ public class MovieProvider : BaseProvider, IRemoteMetadataProvider<Movie, MovieI
|
||||
}
|
||||
}
|
||||
|
||||
private static double CalculateTitleSimilarity(MovieSearchResult source, MovieSearchResult target)
|
||||
{
|
||||
var sourceKey = Normalize(source.Number + source.Title);
|
||||
var targetKey = Normalize(target.Number + target.Title);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(sourceKey) || string.IsNullOrWhiteSpace(targetKey))
|
||||
return 0.0;
|
||||
|
||||
var distance = Levenshtein.Distance(sourceKey, targetKey);
|
||||
var avgLength = (sourceKey.Length + targetKey.Length) / 2.0;
|
||||
var similarity = 1.0 - distance / avgLength;
|
||||
|
||||
return Math.Clamp(similarity, 0.0, 1.0);
|
||||
|
||||
string Normalize(string s)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(s))
|
||||
return string.Empty;
|
||||
|
||||
s = s.ToLowerInvariant();
|
||||
s = Regex.Replace(s, @"[\s\[\]\(\)【】()]", "");
|
||||
return s.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TranslateMovieInfo(Metadata.MovieInfo m, string language, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
|
||||
Reference in New Issue
Block a user