Skip to content

Instantly share code, notes, and snippets.

@sjorge
Created August 10, 2024 21:58
Show Gist options
  • Save sjorge/db3661f5d3a79349a380da6cdc85eb4e to your computer and use it in GitHub Desktop.
Save sjorge/db3661f5d3a79349a380da6cdc85eb4e to your computer and use it in GitHub Desktop.
using System;
using System.IO;
using System.Globalization;
using System.Text;
using System.Security.Cryptography;
using Microsoft.Data.Sqlite;
namespace jf_subtitle_cache_cleaner;
class Program
{
private static Guid GetMD5(string str)
{
return new Guid(MD5.HashData(Encoding.Unicode.GetBytes(str)));
}
private static string GetSubtitleCachePath(string subtitleCachePath, string path, int streamIndex, string outputSubtitleExtension)
{
var ticksParam = string.Empty;
var date = File.GetLastWriteTimeUtc(path);
ReadOnlySpan<char> filename = GetMD5(path + "_" + streamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam) + outputSubtitleExtension;
var prefix = filename.Slice(0, 1);
return Path.Join(subtitleCachePath, prefix, filename);
}
private static string GetSubtitleExtension(string codec)
{
if (codec.ToLower() == "ass" || codec.ToLower() == "ssa")
{
return "." + codec;
}
else
{
return ".srt";
}
}
private static bool IsTextFormat(string format)
{
string codec = format ?? string.Empty;
// microdvd and dvdsub/vobsub share the ".sub" file extension, but it's text-based.
return codec.Contains("microdvd", StringComparison.OrdinalIgnoreCase)
|| (!codec.Contains("pgs", StringComparison.OrdinalIgnoreCase)
&& !codec.Contains("dvdsub", StringComparison.OrdinalIgnoreCase)
&& !codec.Contains("dvbsub", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(codec, "sup", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(codec, "sub", StringComparison.OrdinalIgnoreCase));
}
static void Main(string[] args)
{
var dataPath = "/var/lib/jellyfin/data";
var dbPath = Path.Join(dataPath, "library.db");
var subtitleCachePath = Path.Join(dataPath, "subtitles");
IDictionary<string, string> pathMappingCache = new Dictionary<string, string>();
List<string> whitelistSubtitleCacheFiles = new List<string>{};
var purgeCount = 0;
var keepCount = 0;
Console.WriteLine("Opening " + dbPath + " ...");
using (var connection = new SqliteConnection("Data Source=" + dbPath + ";Mode=ReadOnly;"))
{
connection.Open();
var commandMediaStream = connection.CreateCommand();
commandMediaStream.CommandText =
@"
SELECT *
FROM mediastreams
WHERE StreamType = @streamType AND IsExternal = @isExternal;
";
commandMediaStream.Parameters.AddWithValue("@streamType", "Subtitle");
commandMediaStream.Parameters.AddWithValue("@isExternal", 0);
Console.WriteLine("Looking up subtitle mediastreams ...");
using (var readerMediaStream = commandMediaStream.ExecuteReader())
{
while (readerMediaStream.Read())
{
var guid = readerMediaStream.GetGuid(0);
if (!IsTextFormat(readerMediaStream.GetString(readerMediaStream.GetOrdinal("Codec"))))
{
continue;
}
if (!pathMappingCache.ContainsKey(guid.ToString()))
{
var commandBaseItem = connection.CreateCommand();
commandBaseItem.CommandText =
@"
SELECT *
FROM TypedBaseItems WHERE guid = @guid;
";
commandBaseItem.Parameters.AddWithValue("@guid", guid.ToByteArray());
using (var readerBaseItem = commandBaseItem.ExecuteReader())
{
while (readerBaseItem.Read())
{
pathMappingCache.Add(guid.ToString(), readerBaseItem.GetString(readerBaseItem.GetOrdinal("Path")));
}
}
}
var path = pathMappingCache[guid.ToString()];
var streamIndex = readerMediaStream.GetInt32(readerMediaStream.GetOrdinal("StreamIndex"));
var extension = GetSubtitleExtension(readerMediaStream.GetString(readerMediaStream.GetOrdinal("Codec")));
var subtitleCacheFile = GetSubtitleCachePath(subtitleCachePath, path, streamIndex, extension);
whitelistSubtitleCacheFiles.Add(subtitleCacheFile);
}
}
Console.WriteLine("Detected " + whitelistSubtitleCacheFiles.Count + " valid subtitle cache paths.");
foreach (string subtitleFile in Directory.GetFiles(subtitleCachePath, "*", SearchOption.AllDirectories))
{
if (!whitelistSubtitleCacheFiles.Contains(subtitleFile))
{
purgeCount += 1;
File.Delete(subtitleFile);
}
else
{
keepCount += 1;
}
}
Console.WriteLine("Subtitle cache: purged=" + purgeCount + ", kept=" + keepCount);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment