Skip to content

Instantly share code, notes, and snippets.

@JPVenson
Last active July 19, 2023 13:03
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save JPVenson/e765d97c3c1eb92fb11f0856c6034e6a to your computer and use it in GitHub Desktop.
Save JPVenson/e765d97c3c1eb92fb11f0856c6034e6a to your computer and use it in GitHub Desktop.
cleanup collections.xml for jellyfin

BEFORE YOU START: BACKUP YOUR CONFIGURATION. YOU. HAVE. BEEN. WARNED!

This is the Cs version of the script posted by michaelkrieger at jellyfin/jellyfin#1907 (comment) to cleanup the collection.xml for missing link entires.

Linux Host

To run this on linus, ensure you have a dotnet version installed.
https://learn.microsoft.com/en-us/dotnet/core/install/linux
or for the most part, this oneliner will install dotnet on your system:

curl -sL "https://dot.net/v1/dotnet-install.sh" | bash

Windows Host

Most should have any one dotnet version installed on the system. However i recommand using the latest version (Net7) or at least Net6. Visit the mirosoft page https://dotnet.microsoft.com/en-us/download/dotnet and download it or verifiy that you have the latest dotnet version installed
by opening a new commandline and running:

dotnet --info

this should list under ".NET SDKs installed" at least some versions starting with "6.0.000" or higher.
You can also just use this command to ensure dotnet7 is installed

winget install Microsoft.DotNet.SDK.7

Setup the Script

After the install has been completed, navigate to a new folder and create a new c# project via:

dotnet new console

then download this script and download it in the same directory you just created the new project in like this:

curl  "https://gist.githubusercontent.com/JPVenson/e765d97c3c1eb92fb11f0856c6034e6a/raw/5200079b939c7be8e5b2da274d7229a0c4dd5bed/program.cs" -o Program.cs

Build and Run

The script can be run Interactivly or controlled via commandline arguments or enviorment variables.

Whatif

The script can dryrun without applying modifications.
Syntax: whatif=y
[EnviormentVariable] [arguments]

targetPathSubsitution

Replaces the root of the media path as read from the collection with a fixed other. Helpfull when you BACKUP your collection folder and move it to another system
Syntax: targetPathSubsitution=ORIGINAL:NEW
[EnviormentVariable] [arguments]

input

Path to the directory where the collection.xml files are located in. Usually under you config directory \data\collections.
Syntax: input=PATH
[Interactive] [EnviormentVariable] [arguments]

mediaRoot

The path to the root of your media.
Syntax: mediaRoot=PATH
[Interactive] [EnviormentVariable] [arguments]

Run the Script

after that, compile and run this file with:

dotnet run

To add arguments, use a blank -- to seperate arguments to the dotnet call and the app.
For example, this sets the RunDry flag and substitutes the root path \mnt\media with X:\videos\store in a case where you run the script on a windows host and want to modify the collections in the linux path format.

dotnet run -- whatif=y "targetPathSubsitution=\mnt\media\:X:\videos\store"
/*
* BEFORE YOU START: BACKUP YOUR CONFIGURATION. YOU. HAVE. BEEN. WARNED!
* BEFORE YOU START: BACKUP YOUR CONFIGURATION. YOU. HAVE. BEEN. WARNED!
* BEFORE YOU START: BACKUP YOUR CONFIGURATION. YOU. HAVE. BEEN. WARNED!
* BEFORE YOU START: BACKUP YOUR CONFIGURATION. YOU. HAVE. BEEN. WARNED!
* BEFORE YOU START: BACKUP YOUR CONFIGURATION. YOU. HAVE. BEEN. WARNED!
* READ.THE.MANUAL
*/
using System.Xml.Linq;
using System.Xml.XPath;
string? GetVariableValue(string variableName)
{
var variableValue = Environment.GetEnvironmentVariable(variableName);
if (variableValue != null)
{
return variableValue;
}
return args.FirstOrDefault(e => e.StartsWith(variableName + "="))?.Remove(0, variableName.Length + 1);
}
var whatif = GetVariableValue("whatif") == "y";
var targetPathSubsitution = GetVariableValue("targetPathSubsitution=")
?? Environment.GetEnvironmentVariable("targetPathSubsitution");
Console.WriteLine("Path to the directory the 'collection.xml' files are located in.");
var collectionDirectory = GetVariableValue("input") ?? Console.ReadLine();
if (!Directory.Exists(collectionDirectory))
{
Console.WriteLine("File does not exist. Exit.");
return;
}
Console.WriteLine("Media Root:");
var mediaRoot = GetVariableValue("mediaRoot") ?? Console.ReadLine();
if (!Directory.Exists(mediaRoot))
{
Console.WriteLine("Media Directory does not exist. Exit.");
return;
}
foreach (var collectionFile in Directory.EnumerateFiles(collectionDirectory, "collection.xml", SearchOption.AllDirectories))
{
await using var documentStream = File.Open(collectionFile, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
var document = await XDocument.LoadAsync(documentStream, LoadOptions.PreserveWhitespace, CancellationToken.None);
var paths = document.XPathSelectElements("//CollectionItem/Path");
foreach (var path in paths)
{
var sourcePath = path.Value;
if (!string.IsNullOrWhiteSpace(targetPathSubsitution))
{
var subsitutionParts = targetPathSubsitution.Split(':');
sourcePath = sourcePath.Replace(subsitutionParts[0], subsitutionParts[1]);
}
sourcePath = sourcePath.TrimStart('/', '\\');
var targetPath = Path.Combine(mediaRoot, sourcePath);
Console.Write($"Check '{targetPath}': ");
if (File.Exists(targetPath))
{
Console.WriteLine("Exists.");
}
else
{
Console.Write("Does not exist.");
if (whatif)
{
Console.WriteLine();
}
else
{
Console.WriteLine("Delete entry.");
path.Parent.Remove();
}
}
}
if (!whatif)
{
documentStream.Seek(0, SeekOrigin.Begin);
documentStream.SetLength(0);
await document.SaveAsync(documentStream, SaveOptions.None, CancellationToken.None);
await documentStream.FlushAsync();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment