Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ RUN apt install -y --no-install-recommends \
ca-certificates \
locales \
locales-all \
p7zip-full \
7zip \
tzdata \
wget \
&& locale-gen --no-purge en_US en_US.UTF-8
Expand All @@ -111,10 +111,10 @@ ENV TZ=Etc/UTC \
RUN apt install -y --install-suggests dotnet-runtime-10.0

# Install media tools
# https://packages.ubuntu.com/questing/ffmpeg
# https://packages.ubuntu.com/questing/handbrake-cli
# https://packages.ubuntu.com/questing/mediainfo
# https://packages.ubuntu.com/questing/mkvtoolnix
# https://packages.ubuntu.com/stonking/ffmpeg
# https://packages.ubuntu.com/stonking/handbrake-cli
# https://packages.ubuntu.com/stonking/mediainfo
# https://packages.ubuntu.com/stonking/mkvtoolnix
RUN apt install -y --no-install-recommends \
ffmpeg \
handbrake-cli \
Expand Down
11 changes: 10 additions & 1 deletion PlexCleaner/FfMpegTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,16 @@ public static bool GetVersion(string text, MediaToolInfo mediaToolInfo)
// "ffmpeg version 4.3.1-1ubuntu0~20.04.sav1 Copyright (c) 2000-2020 the FFmpeg developers"
// "ffprobe version 7.1.1-full_build-www.gyan.dev Copyright (c) 2007-2025 the FFmpeg developers"
// "ffprobe version 5.1.6-0+deb12u1 Copyright (c) 2007-2024 the FFmpeg developers"
// "ffmpeg version N-121061-g0123456789 Copyright (c) 2000-2025 the FFmpeg developers"

// Parse version
string[] lines = text.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
Match match = InstalledVersionRegex().Match(lines[0]);
Debug.Assert(match.Success && Version.TryParse(match.Groups["version"].Value, out _));
if (!match.Success)
{
match = BuildVersionRegex().Match(lines[0]);
}
Debug.Assert(match.Success);
mediaToolInfo.Version = match.Groups["version"].Value;
return true;
}
Expand Down Expand Up @@ -555,5 +560,9 @@ public bool GetIdetText(string fileName, out string text)
RegexOptions.IgnoreCase | RegexOptions.Multiline
)]
public static partial Regex InstalledVersionRegex();

// Development builds "N-121061-g0123456789" and "2025-08-25-git-abc-full_build-www.gyan.dev"
[GeneratedRegex(@"version\s+(?<version>\S+)", RegexOptions.IgnoreCase)]
public static partial Regex BuildVersionRegex();
}
}
2 changes: 1 addition & 1 deletion PlexCleaner/HandBrakeTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static bool GetVersion(string text, MediaToolInfo mediaToolInfo)
// Parse version
string[] lines = text.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
Match match = InstalledVersionRegex().Match(lines[0]);
Debug.Assert(match.Success && Version.TryParse(match.Groups["version"].Value, out _));
Debug.Assert(match.Success);
mediaToolInfo.Version = match.Groups["version"].Value;
return true;
}
Expand Down
2 changes: 1 addition & 1 deletion PlexCleaner/MediaInfoTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static bool GetVersion(string text, MediaToolInfo mediaToolInfo)
// Parse version
string[] lines = text.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
Match match = InstalledVersionRegex().Match(lines[1]);
Debug.Assert(match.Success && Version.TryParse(match.Groups["version"].Value, out _));
Debug.Assert(match.Success);
mediaToolInfo.Version = match.Groups["version"].Value;
return true;
}
Expand Down
104 changes: 95 additions & 9 deletions PlexCleaner/MediaTool.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
using System.Diagnostics;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using CliWrap;
using CliWrap.Buffered;

namespace PlexCleaner;

public abstract class MediaTool
public abstract partial class MediaTool
{
public enum ToolFamily
{
Expand Down Expand Up @@ -53,15 +55,99 @@ public enum ToolType
// Latest downloadable version
protected abstract bool GetLatestVersionWindows(out MediaToolInfo mediaToolInfo);

// Oldest version whose command line the tool wrapper relies on, null when any version works
public Version? MinimumVersion =>
GetToolFamily() == ToolFamily.FfMpeg ? new Version(8, 0) : null; // The README states this minimum
public Version? MinimumVersion => GetMinimum()?.Version;

// An unparsed version is taken to be a current git build, so it passes the gate
public static bool IsSupportedVersion(string version, Version? minimumVersion) =>
minimumVersion == null
|| !Version.TryParse(version, out Version? parsed)
|| parsed >= minimumVersion;
// Release date of the minimum version, compared with dated development builds
public DateOnly? MinimumDate => GetMinimum()?.Date;

private (Version Version, DateOnly Date)? GetMinimum() =>
GetToolFamily() switch
{
ToolFamily.FfMpeg => (new Version(8, 0), new DateOnly(2025, 8, 22)),
ToolFamily.HandBrake => (new Version(1, 10, 0), new DateOnly(2025, 8, 9)),
ToolFamily.MediaInfo => (new Version(25, 7), new DateOnly(2025, 7, 29)),
ToolFamily.MkvToolNix => (new Version(94, 0), new DateOnly(2025, 7, 27)),
ToolFamily.SevenZip => (new Version(25, 1), new DateOnly(2025, 8, 3)),
ToolFamily.None => null,
_ => null,
};

public enum VersionGate
{
Supported,
Unsupported,
Unknown,
}

// Release versions compare directly, development builds compare by branch or build date
public static VersionGate CheckVersion(
string version,
Version? minimumVersion,
DateOnly? minimumDate
)
{
if (minimumVersion == null)
{
return VersionGate.Supported;
}

if (Version.TryParse(version, out Version? parsed))
{
return parsed >= minimumVersion ? VersionGate.Supported : VersionGate.Unsupported;
}

// A release prefix with a suffix, such as "93.0-12-g1a2b3c4", compares by the prefix
Match prefix = VersionPrefixRegex().Match(version);
if (prefix.Success && Version.TryParse(prefix.Groups["version"].Value, out parsed))
{
return parsed >= minimumVersion ? VersionGate.Supported : VersionGate.Unsupported;
}

// HandBrake nightly "20230223192356-5c2b5d2d0-1.6.x" is a build of the 1.6 branch
Match branch = HandBrakeBranchRegex().Match(version);
if (
branch.Success
&& Version.TryParse($"{branch.Groups["branch"].Value}.0", out Version? branchVersion)
)
{
return branchVersion >= minimumVersion
? VersionGate.Supported
: VersionGate.Unsupported;
}

// A build timestamp "20230223192356" or a build date "2025-08-25-git" is compared with the release date
Match date = BuildDateRegex().Match(version);
return
minimumDate != null
&& date.Success
&& DateOnly.TryParseExact(
date.Groups["date"].Value,
["yyyyMMdd", "yyyy-MM-dd"],
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out DateOnly buildDate
)
? buildDate >= minimumDate
? VersionGate.Supported
: VersionGate.Unsupported
: VersionGate.Unknown;
}

// An unknown version is taken to be a current git build, so it passes the gate
public static bool IsSupportedVersion(
string version,
Version? minimumVersion,
DateOnly? minimumDate = null
) => CheckVersion(version, minimumVersion, minimumDate) != VersionGate.Unsupported;

[GeneratedRegex(@"^(?<version>\d+(\.\d+)+)")]
private static partial Regex VersionPrefixRegex();

[GeneratedRegex(@"^\d{14}-[0-9a-f]+-(?<branch>\d+\.\d+)\.x$", RegexOptions.IgnoreCase)]
private static partial Regex HandBrakeBranchRegex();

[GeneratedRegex(@"^(?<date>\d{8}(?=\d{6}-)|\d{4}-\d{2}-\d{2}(?=-))")]
private static partial Regex BuildDateRegex();

// Tool subfolder, e.g. /x64, /bin
protected virtual string GetSubFolder() => string.Empty;
Expand Down
2 changes: 1 addition & 1 deletion PlexCleaner/MkvMergeTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public static bool GetVersion(string text, MediaToolInfo mediaToolInfo)
// Parse version
string[] lines = text.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
Match match = InstalledVersionRegex().Match(lines[0]);
Debug.Assert(match.Success && Version.TryParse(match.Groups["version"].Value, out _));
Debug.Assert(match.Success);
mediaToolInfo.Version = match.Groups["version"].Value;
return true;
}
Expand Down
2 changes: 1 addition & 1 deletion PlexCleaner/SevenZipTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public static bool GetVersion(string text, MediaToolInfo mediaToolInfo)
// Parse version
string[] lines = text.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
Match match = InstalledVersionRegex().Match(lines[0]);
Debug.Assert(match.Success && Version.TryParse(match.Groups["version"].Value, out _));
Debug.Assert(match.Success);
mediaToolInfo.Version = match.Groups["version"].Value;
return true;
}
Expand Down
23 changes: 21 additions & 2 deletions PlexCleaner/Tools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,32 @@ public static bool VerifyTools()
private static bool VerifyToolVersions()
{
List<MediaTool> unsupportedTools = GetToolList()
.FindAll(tool => !MediaTool.IsSupportedVersion(tool.Info.Version, tool.MinimumVersion));
.FindAll(tool =>
MediaTool.CheckVersion(tool.Info.Version, tool.MinimumVersion, tool.MinimumDate)
== MediaTool.VersionGate.Unsupported
);
GetToolList()
.FindAll(tool =>
MediaTool.CheckVersion(tool.Info.Version, tool.MinimumVersion, tool.MinimumDate)
== MediaTool.VersionGate.Unknown
)
.ForEach(tool =>
Log.Warning(
"{Tool} version {Version} is not comparable, assuming it meets the minimum supported version {MinimumVersion} released {MinimumDate} : {FileName}",
tool.GetToolType(),
tool.Info.Version,
tool.MinimumVersion,
tool.MinimumDate,
tool.Info.FileName
)
);
unsupportedTools.ForEach(tool =>
Log.Error(
"{Tool} version {Version} is older than the minimum supported version {MinimumVersion} : {FileName}",
"{Tool} version {Version} is older than the minimum supported version {MinimumVersion} released {MinimumDate} : {FileName}",
tool.GetToolType(),
tool.Info.Version,
tool.MinimumVersion,
tool.MinimumDate,
tool.Info.FileName
)
);
Expand Down
Loading
Loading