Several Fixes for ln.podget
parent
1044b830e0
commit
b8b42af1d8
|
@ -1,17 +1,17 @@
|
|||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ln.podget;
|
||||
namespace ln.tagorganizer;
|
||||
|
||||
public static class PathHelper
|
||||
{
|
||||
public static string GetValidPathElement(string pathElement)
|
||||
{
|
||||
return Regex.Replace(pathElement, "[^a-zA-Z0-9_ äöü]", "_");
|
||||
return Regex.Replace(pathElement, "[^a-zA-Z0-9_ äöü\\-\\[\\].]", "_");
|
||||
}
|
||||
|
||||
public static string GetValidPathElement(string folderPath, string pathElement)
|
||||
{
|
||||
string cleanElement = Regex.Replace(pathElement, "[^a-zA-Z0-9_ äöü]", "_");
|
||||
string cleanElement = Regex.Replace(pathElement, "[^a-zA-Z0-9_ äöü\\-\\[\\].]", "_");
|
||||
int remaining = 254 - folderPath.Length - cleanElement.Length;
|
||||
if (remaining < 0)
|
||||
{
|
|
@ -0,0 +1,9 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
|
@ -2,6 +2,10 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ln.podget", "ln.podget\ln.podget.csproj", "{97F573B1-9EBC-48F0-95BB-2AAEF06E85F4}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ln.tagorganizer", "ln.tagorganizer\ln.tagorganizer.csproj", "{BD0E70EC-FA33-47D3-8EF5-60B260EEDC33}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ln.pathhelper", "ln.pathhelper\ln.pathhelper.csproj", "{299D7772-0007-4F67-9E1B-22F92D55DDF4}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -13,5 +17,13 @@ Global
|
|||
{97F573B1-9EBC-48F0-95BB-2AAEF06E85F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{97F573B1-9EBC-48F0-95BB-2AAEF06E85F4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{97F573B1-9EBC-48F0-95BB-2AAEF06E85F4}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{BD0E70EC-FA33-47D3-8EF5-60B260EEDC33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BD0E70EC-FA33-47D3-8EF5-60B260EEDC33}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BD0E70EC-FA33-47D3-8EF5-60B260EEDC33}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BD0E70EC-FA33-47D3-8EF5-60B260EEDC33}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{299D7772-0007-4F67-9E1B-22F92D55DDF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{299D7772-0007-4F67-9E1B-22F92D55DDF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{299D7772-0007-4F67-9E1B-22F92D55DDF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{299D7772-0007-4F67-9E1B-22F92D55DDF4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
@ -77,7 +77,6 @@ public class PodcastCatcher : IDisposable
|
|||
Console.WriteLine($"Downloaded to {fileInfo}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void SaveFeeds()
|
||||
|
|
|
@ -11,7 +11,8 @@ namespace ln.podget
|
|||
public static void Main(string[] args,
|
||||
string libraryPath = "/var/cache/podcasts",
|
||||
bool createLibrary = false,
|
||||
bool skipDownload = false
|
||||
bool skipDownload = false,
|
||||
bool singleThread = false
|
||||
)
|
||||
{
|
||||
if (createLibrary)
|
||||
|
@ -22,7 +23,7 @@ namespace ln.podget
|
|||
}
|
||||
|
||||
string[] feeds = args;
|
||||
using (PodcastCatcher catcher = new PodcastCatcher(libraryPath, skipDownload))
|
||||
using (PodcastCatcher catcher = new PodcastCatcher(libraryPath, skipDownload, singleThread))
|
||||
{
|
||||
if (feeds.Length > 0)
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Xml;
|
||||
using ln.tagorganizer;
|
||||
|
||||
namespace ln.podget;
|
||||
|
||||
|
@ -9,10 +10,11 @@ public class RSSChannel : IDisposable
|
|||
public string Description { get; set; }
|
||||
public string Language { get; set; }
|
||||
public string Copyright { get; set; }
|
||||
public string Author { get; set; }
|
||||
|
||||
private HashSet<string> _itemIds = new HashSet<string>();
|
||||
private SortedList<long, RSSItem> _items = new SortedList<long, RSSItem>();
|
||||
public IEnumerable<RSSItem> Items => _items.Values;
|
||||
private List<RSSItem> _items = new List<RSSItem>();
|
||||
public IEnumerable<RSSItem> Items => _items;
|
||||
|
||||
public string StoragePath { get; }
|
||||
|
||||
|
@ -20,6 +22,9 @@ public class RSSChannel : IDisposable
|
|||
|
||||
public RSSChannel(XMLFeed xmlFeed, XmlNode channelNode)
|
||||
{
|
||||
var namespaceManager = new XmlNamespaceManager(channelNode.OwnerDocument.NameTable);
|
||||
namespaceManager.AddNamespace("itunes","http://www.itunes.com/dtds/podcast-1.0.dtd");
|
||||
|
||||
Title = channelNode?.SelectSingleNode("title")?.InnerText ?? "";
|
||||
var linkNode = channelNode?.SelectSingleNode("link");
|
||||
Link = linkNode != null ? new Uri(linkNode.InnerText) : null;
|
||||
|
@ -27,6 +32,7 @@ public class RSSChannel : IDisposable
|
|||
Description = channelNode.SelectSingleNode("description")?.InnerText ?? "";
|
||||
Language = channelNode.SelectSingleNode("language")?.InnerText ?? "";
|
||||
Copyright = channelNode.SelectSingleNode("copyright")?.InnerText ?? "";
|
||||
Author = channelNode.SelectSingleNode("itunes:author", namespaceManager)?.InnerText ?? Copyright;
|
||||
|
||||
XmlNode thumbNode = channelNode.SelectSingleNode("image/url");
|
||||
if (thumbNode is not null)
|
||||
|
@ -42,6 +48,12 @@ public class RSSChannel : IDisposable
|
|||
albumDocument.AppendChild(albumNode);
|
||||
albumNode.AppendChild(titleNode);
|
||||
|
||||
XmlNode albumArtistCredits = albumDocument.CreateElement("albumArtistCredits");
|
||||
XmlNode artist = albumDocument.CreateElement("artist");
|
||||
artist.InnerText = Author;
|
||||
albumArtistCredits.AppendChild(artist);
|
||||
albumNode.AppendChild(albumArtistCredits);
|
||||
|
||||
XmlNode plotNode = albumDocument.CreateElement("plot");
|
||||
plotNode.InnerText = Description;
|
||||
albumNode.AppendChild(plotNode);
|
||||
|
@ -68,10 +80,10 @@ public class RSSChannel : IDisposable
|
|||
foreach (XmlNode itemNode in channelNode.SelectNodes("item"))
|
||||
{
|
||||
var i = new RSSItem(this, itemNode);
|
||||
_items.Add(i.PubDate.ToFileTime(), i);
|
||||
_items.Add(i);
|
||||
}
|
||||
|
||||
foreach (var rssItem in _items.Values)
|
||||
foreach (var rssItem in _items.ToArray().Reverse())
|
||||
{
|
||||
if (rssItem.Number == 0)
|
||||
{
|
||||
|
|
|
@ -2,6 +2,7 @@ using System.Net;
|
|||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using ln.tagorganizer;
|
||||
|
||||
namespace ln.podget;
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ public class XMLFeed
|
|||
|
||||
XmlDocument xmlDocument = new XmlDocument();
|
||||
xmlDocument.Load(taskGet.Result.Content.ReadAsStream());
|
||||
|
||||
Initialize(xmlDocument);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,4 +19,8 @@
|
|||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ln.pathhelper\ln.pathhelper.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
namespace ln.tagorganizer;
|
||||
|
||||
public class FileMatcher
|
||||
{
|
||||
public static IEnumerable<string> ResolveWildcards(string wildcardPath)
|
||||
{
|
||||
wildcardPath = Path.GetFullPath(wildcardPath);
|
||||
string[] wildcardPathTokens = wildcardPath.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries);
|
||||
return ResolveWildcards(Path.GetPathRoot(wildcardPath), wildcardPathTokens);
|
||||
}
|
||||
|
||||
public static IEnumerable<string> ResolveWildcards(string searchDirectory, string[] wildcardPathTokens)
|
||||
{
|
||||
if (wildcardPathTokens.Length != 0)
|
||||
{
|
||||
if (wildcardPathTokens.Length == 1)
|
||||
{
|
||||
foreach (var fileName in Directory.GetFiles(searchDirectory, wildcardPathTokens[0]))
|
||||
yield return fileName;
|
||||
}
|
||||
|
||||
foreach (var directoryName in Directory.EnumerateDirectories(searchDirectory, wildcardPathTokens[0]))
|
||||
{
|
||||
if (wildcardPathTokens.Length > 1)
|
||||
{
|
||||
foreach (var matchedName in ResolveWildcards(Path.Combine(searchDirectory, directoryName),
|
||||
wildcardPathTokens[1..]))
|
||||
yield return matchedName;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return directoryName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
using System.Security.Cryptography;
|
||||
using ln.type;
|
||||
using TagLib;
|
||||
using File = System.IO.File;
|
||||
|
||||
namespace ln.tagorganizer;
|
||||
|
||||
public class MediaFile : IDisposable
|
||||
{
|
||||
public static MediaFile Create(string filename)
|
||||
{
|
||||
try
|
||||
{
|
||||
TagLib.File tagFile = TagLib.File.Create(filename);
|
||||
return new MediaFile(filename);
|
||||
}
|
||||
catch (UnsupportedFormatException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public string FileName { get; private set; }
|
||||
|
||||
public string Extension => Path.GetExtension(FileName);
|
||||
public string BaseName => Path.GetFileNameWithoutExtension(FileName);
|
||||
public string? DirectoryName => Path.GetDirectoryName(FileName);
|
||||
|
||||
public string HashCacheName => GetHashCacheName(FileName);
|
||||
|
||||
public MediaFile(string filename) :this(filename, null)
|
||||
{ }
|
||||
|
||||
private MediaFile(string filename, TagLib.File? tagFile)
|
||||
{
|
||||
FileName = filename;
|
||||
if (!File.Exists(FileName))
|
||||
throw new FileNotFoundException();
|
||||
_tagFile = tagFile;
|
||||
if (!LoadTagFile())
|
||||
throw new UnsupportedFormatException();
|
||||
}
|
||||
|
||||
public TagLib.Tag Tag => _tagFile.Tag;
|
||||
private TagLib.File _tagFile;
|
||||
|
||||
private bool LoadTagFile()
|
||||
{
|
||||
if (_tagFile is null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_tagFile = TagLib.File.Create(FileName);
|
||||
}
|
||||
catch (UnsupportedFormatException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UnloadTagFile()
|
||||
{
|
||||
_tagFile?.Dispose();
|
||||
_tagFile = null;
|
||||
}
|
||||
|
||||
private byte[] _hash;
|
||||
public byte[] Hash
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_hash is null)
|
||||
ComputeFileHash();
|
||||
return _hash;
|
||||
}
|
||||
}
|
||||
|
||||
public string HashString => Hash.ToHexString();
|
||||
|
||||
public void Move(string newFileName)
|
||||
{
|
||||
UnloadTagFile();
|
||||
File.Move(FileName, newFileName);
|
||||
if (File.Exists(HashCacheName))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Move(HashCacheName, GetHashCacheName(newFileName));
|
||||
}
|
||||
catch (Exception e) { }
|
||||
}
|
||||
|
||||
RemoveDirectoryIfEmpty(Path.GetDirectoryName(FileName));
|
||||
FileName = newFileName;
|
||||
LoadTagFile();
|
||||
}
|
||||
public MediaFile Copy(string newFileName)
|
||||
{
|
||||
File.Copy(FileName, newFileName);
|
||||
if (File.Exists(HashCacheName))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Copy(HashCacheName, GetHashCacheName(newFileName));
|
||||
}
|
||||
catch (Exception e) { }
|
||||
}
|
||||
|
||||
return new MediaFile(newFileName);
|
||||
}
|
||||
|
||||
private void RemoveDirectoryIfEmpty(string directoryName)
|
||||
{
|
||||
if (Directory.GetFileSystemEntries(directoryName).Length == 0)
|
||||
Directory.Delete(directoryName);
|
||||
}
|
||||
|
||||
private void ComputeFileHash()
|
||||
{
|
||||
if (File.Exists(HashCacheName) && (File.GetLastWriteTime(HashCacheName) > File.GetLastWriteTime(FileName)))
|
||||
{
|
||||
_hash = File.ReadAllBytes(HashCacheName);
|
||||
if (_hash.Length == 32)
|
||||
return;
|
||||
}
|
||||
|
||||
SHA256 sha256 = SHA256.Create();
|
||||
using (FileStream fs = new FileStream(FileName, FileMode.Open))
|
||||
_hash = sha256.ComputeHash(fs);
|
||||
|
||||
File.WriteAllBytes(HashCacheName, _hash);
|
||||
}
|
||||
|
||||
private static string GetHashCacheName(string filename)
|
||||
{
|
||||
string directoryName = Path.GetDirectoryName(filename);
|
||||
string baseName = Path.GetFileName(filename);
|
||||
string hashName = Path.Combine(directoryName, ".hash." + baseName);
|
||||
|
||||
return hashName;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj) => (obj is MediaFile other) && other.FileName.Equals(FileName);
|
||||
public override int GetHashCode() => Hash.GetHashCode();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_tagFile?.Dispose();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,215 @@
|
|||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using ln.type;
|
||||
using TagLib;
|
||||
using File = System.IO.File;
|
||||
|
||||
namespace ln.tagorganizer;
|
||||
|
||||
static class Extensions
|
||||
{
|
||||
public static bool IsNullOrEmpty(this string s) => (s is null) || string.Empty.Equals(s);
|
||||
}
|
||||
|
||||
public class TagOrganizer
|
||||
{
|
||||
public string LibraryPath { get; }
|
||||
public string? DuplicatesPath { get; set; }
|
||||
public bool CopyFiles { get; set; }
|
||||
|
||||
private Dictionary<string, string> _files = new Dictionary<string, string>();
|
||||
private Dictionary<string, List<string>?> _duplicates = new Dictionary<string, List<string>?>();
|
||||
|
||||
private HashSet<string> _supportedExtensions = new HashSet<string>();
|
||||
|
||||
public TagOrganizer(string libraryPath)
|
||||
{
|
||||
LibraryPath = Path.GetFullPath(libraryPath);
|
||||
if (!Directory.Exists(LibraryPath))
|
||||
throw new DirectoryNotFoundException(LibraryPath);
|
||||
|
||||
DuplicatesPath = Path.Combine(LibraryPath, "duplicates");
|
||||
|
||||
foreach (var fileType in TagLib.FileTypes.AvailableTypes.Values)
|
||||
{
|
||||
foreach (var supportedMimeType in fileType.GetCustomAttributes<SupportedMimeType>())
|
||||
{
|
||||
if (supportedMimeType.Extension is not null)
|
||||
_supportedExtensions.Add("." + supportedMimeType.Extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildTrackName(MediaFile mediaFile, string? suffix = null)
|
||||
{
|
||||
StringBuilder trackNameBuilder = new StringBuilder();
|
||||
if (mediaFile.Tag.Disc > 0)
|
||||
trackNameBuilder.AppendFormat("Disc {0:D2} - ", mediaFile.Tag.Disc);
|
||||
if (mediaFile.Tag.Track > 0)
|
||||
trackNameBuilder.AppendFormat("{0:D2} - ", mediaFile.Tag.Track);
|
||||
|
||||
trackNameBuilder.AppendFormat("{0}{2}{1}", mediaFile.Tag.Title, mediaFile.Extension, suffix ?? "");
|
||||
return PathHelper.GetValidPathElement(trackNameBuilder.ToString());
|
||||
}
|
||||
|
||||
public bool ReorganizeFile(MediaFile mediaFile)
|
||||
{
|
||||
if (mediaFile.Tag.JoinedAlbumArtists.IsNullOrEmpty() && mediaFile.Tag.JoinedPerformers.IsNullOrEmpty() && mediaFile.Tag.Album.IsNullOrEmpty())
|
||||
return false;
|
||||
|
||||
string? artist = mediaFile.Tag.JoinedAlbumArtists ?? mediaFile.Tag.JoinedPerformers;
|
||||
string albumFolderName = artist;
|
||||
if (artist is )
|
||||
mediaFile.Tag.Album is null ? : (artist is null ? mediaFile.Tag.Album : artist + " - " + mediaFile.Tag.Album);
|
||||
|
||||
string albumDirectoryName = Path.Combine(
|
||||
LibraryPath,
|
||||
albumFolderName
|
||||
);
|
||||
|
||||
string trackFileName = BuildTrackName(mediaFile);
|
||||
string targetPath = Path.Combine(
|
||||
albumDirectoryName,
|
||||
trackFileName
|
||||
);
|
||||
|
||||
if (!mediaFile.FileName.Equals(targetPath))
|
||||
{
|
||||
if (File.Exists(targetPath))
|
||||
{
|
||||
albumDirectoryName = Path.Combine(
|
||||
DuplicatesPath,
|
||||
albumFolderName
|
||||
);
|
||||
|
||||
int n = 0;
|
||||
do
|
||||
{
|
||||
n++;
|
||||
|
||||
trackFileName = BuildTrackName(mediaFile, $"[{n}]");
|
||||
|
||||
targetPath = Path.Combine(
|
||||
albumDirectoryName,
|
||||
trackFileName
|
||||
);
|
||||
|
||||
} while (File.Exists(targetPath));
|
||||
}
|
||||
|
||||
Console.WriteLine($"Moving to: {targetPath}");
|
||||
|
||||
if (!Directory.Exists(albumDirectoryName))
|
||||
Directory.CreateDirectory(albumDirectoryName);
|
||||
|
||||
if (CopyFiles)
|
||||
mediaFile.Copy(targetPath);
|
||||
else
|
||||
{
|
||||
mediaFile.Move(targetPath);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public void ScanDirectory(string directoryName)
|
||||
{
|
||||
if (DuplicatesPath.Equals(directoryName))
|
||||
return;
|
||||
|
||||
Console.WriteLine($"Directory: {directoryName}");
|
||||
foreach (var enumeratedFile in Directory.GetFiles(directoryName))
|
||||
ScanFile(enumeratedFile);
|
||||
|
||||
if (Directory.Exists(directoryName)) // Directory may have been removed by ScanFile()
|
||||
{
|
||||
var subDirectories = Directory.GetDirectories(directoryName);
|
||||
if (subDirectories.Length == 0)
|
||||
{
|
||||
if (Directory.GetFiles(directoryName).Length == 0)
|
||||
Directory.Delete(directoryName);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var enumeratedDirectory in subDirectories)
|
||||
ScanDirectory(enumeratedDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
public void ScanFile(string fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if ((Path.GetFileName(fileName)[0] == '.') || (!_supportedExtensions.Contains(Path.GetExtension(fileName))))
|
||||
return;
|
||||
|
||||
MediaFile mediaFile = MediaFile.Create(fileName);
|
||||
if (mediaFile is null)
|
||||
return;
|
||||
|
||||
if (_files.ContainsKey(mediaFile.HashString))
|
||||
{
|
||||
if (!CopyFiles)
|
||||
File.Delete(fileName);
|
||||
}
|
||||
else
|
||||
ReorganizeFile(mediaFile);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ApplicationException($"while working on {fileName}", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void Scan(string pathName)
|
||||
{
|
||||
if (Directory.Exists(pathName))
|
||||
ScanDirectory(pathName);
|
||||
else
|
||||
ScanFile(pathName);
|
||||
}
|
||||
|
||||
public void Scan(IEnumerable<string> pathNames)
|
||||
{
|
||||
foreach (var pathName in pathNames)
|
||||
Scan(pathName);
|
||||
}
|
||||
|
||||
public IEnumerable<string> ExpandWildcards(string[] wildcardPaths)
|
||||
{
|
||||
foreach (var wildcardPath in wildcardPaths)
|
||||
{
|
||||
foreach (var resolveWildcard in FileMatcher.ResolveWildcards(wildcardPath))
|
||||
{
|
||||
yield return resolveWildcard;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void Main(string[] args, bool copy, string libraryPath = ".", string? duplicatesPath = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var app = new TagOrganizer(libraryPath)
|
||||
{
|
||||
CopyFiles = copy,
|
||||
};
|
||||
|
||||
if (duplicatesPath is not null)
|
||||
app.DuplicatesPath = duplicatesPath;
|
||||
|
||||
if (args.Length > 0)
|
||||
app.Scan(app.ExpandWildcards(args));
|
||||
else
|
||||
app.ScanDirectory(libraryPath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.Error.WriteLine($"Exception: {e.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputType>Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ln.type" Version="0.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="8.0.0" />
|
||||
<PackageReference Include="System.CommandLine.DragonFruit" Version="0.4.0-alpha.22272.1" />
|
||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ln.pathhelper\ln.pathhelper.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
Loading…
Reference in New Issue