commit ea58deab8cda53b4bd744cb0b9b1e1eed68dd760 Author: Arne Moerman Date: Sun Dec 15 19:04:29 2024 +0100 initial commit diff --git a/.vs/LetterBoxdUnwatchedGenerator/DesignTimeBuild/.dtbcache.v2 b/.vs/LetterBoxdUnwatchedGenerator/DesignTimeBuild/.dtbcache.v2 new file mode 100644 index 0000000..c4c4b1f Binary files /dev/null and b/.vs/LetterBoxdUnwatchedGenerator/DesignTimeBuild/.dtbcache.v2 differ diff --git a/.vs/LetterBoxdUnwatchedGenerator/FileContentIndex/4d62bf6e-28ef-44ea-83c5-7c950723e7a6.vsidx b/.vs/LetterBoxdUnwatchedGenerator/FileContentIndex/4d62bf6e-28ef-44ea-83c5-7c950723e7a6.vsidx new file mode 100644 index 0000000..f4822ef Binary files /dev/null and b/.vs/LetterBoxdUnwatchedGenerator/FileContentIndex/4d62bf6e-28ef-44ea-83c5-7c950723e7a6.vsidx differ diff --git a/.vs/LetterBoxdUnwatchedGenerator/FileContentIndex/bf076abb-6035-4b43-b4c4-af53df5dcc1a.vsidx b/.vs/LetterBoxdUnwatchedGenerator/FileContentIndex/bf076abb-6035-4b43-b4c4-af53df5dcc1a.vsidx new file mode 100644 index 0000000..fc2a789 Binary files /dev/null and b/.vs/LetterBoxdUnwatchedGenerator/FileContentIndex/bf076abb-6035-4b43-b4c4-af53df5dcc1a.vsidx differ diff --git a/.vs/LetterBoxdUnwatchedGenerator/FileContentIndex/d3d138b7-8cc4-49e4-9660-a72627ece054.vsidx b/.vs/LetterBoxdUnwatchedGenerator/FileContentIndex/d3d138b7-8cc4-49e4-9660-a72627ece054.vsidx new file mode 100644 index 0000000..a5322ba Binary files /dev/null and b/.vs/LetterBoxdUnwatchedGenerator/FileContentIndex/d3d138b7-8cc4-49e4-9660-a72627ece054.vsidx differ diff --git a/.vs/LetterBoxdUnwatchedGenerator/FileContentIndex/eb69531b-3217-4624-9534-f4c52d3425f4.vsidx b/.vs/LetterBoxdUnwatchedGenerator/FileContentIndex/eb69531b-3217-4624-9534-f4c52d3425f4.vsidx new file mode 100644 index 0000000..89d5367 Binary files /dev/null and b/.vs/LetterBoxdUnwatchedGenerator/FileContentIndex/eb69531b-3217-4624-9534-f4c52d3425f4.vsidx differ diff --git a/.vs/LetterBoxdUnwatchedGenerator/FileContentIndex/read.lock b/.vs/LetterBoxdUnwatchedGenerator/FileContentIndex/read.lock new file mode 100644 index 0000000..e69de29 diff --git a/.vs/LetterBoxdUnwatchedGenerator/v17/.suo b/.vs/LetterBoxdUnwatchedGenerator/v17/.suo new file mode 100644 index 0000000..af496de Binary files /dev/null and b/.vs/LetterBoxdUnwatchedGenerator/v17/.suo differ diff --git a/LetterBoxdUnwatchedGenerator.sln b/LetterBoxdUnwatchedGenerator.sln new file mode 100644 index 0000000..b1879a1 --- /dev/null +++ b/LetterBoxdUnwatchedGenerator.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33723.286 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LetterBoxdUnwatchedGenerator", "LetterBoxdUnwatchedGenerator\LetterBoxdUnwatchedGenerator.csproj", "{AA177F53-376A-40C9-B372-BA5FD25FEAD7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AA177F53-376A-40C9-B372-BA5FD25FEAD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA177F53-376A-40C9-B372-BA5FD25FEAD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA177F53-376A-40C9-B372-BA5FD25FEAD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA177F53-376A-40C9-B372-BA5FD25FEAD7}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F57C2260-910C-480D-A2FF-22FF5A6EEDE7} + EndGlobalSection +EndGlobal diff --git a/LetterBoxdUnwatchedGenerator/CommandLineSplit.cs b/LetterBoxdUnwatchedGenerator/CommandLineSplit.cs new file mode 100644 index 0000000..7d83b5f --- /dev/null +++ b/LetterBoxdUnwatchedGenerator/CommandLineSplit.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LetterBoxdUnwatchedGenerator +{ + internal static class CommandLineSplitExt + { + + //public static IEnumerable SplitCommandLine(this string commandLine, char splitChar = ' ') + //{ + // bool inQuotes = false; + // bool isEscaping = false; + + // StringBuilder currentArg = new StringBuilder(); + + // foreach (char c in commandLine) + // { + // if (c == '\\' && !isEscaping) { isEscaping = true; continue; } + // if (c == '\"' && !isEscaping) inQuotes = !inQuotes; + // isEscaping = false; + + // if (!inQuotes && (splitChar == ' ' ? Char.IsWhiteSpace(c) : c == splitChar)) + // { + // if (currentArg.Length > 0) + // { + // yield return currentArg.ToString(); + // currentArg.Clear(); + // } + // } + // else + // { + // currentArg.Append(c); + // } + // } + + // if (currentArg.Length > 0) + // { + // yield return currentArg.ToString(); + // } + //} + + + public static IEnumerable SplitCommandLine(this string commandLine, char splitChar = ' ') + { + bool inQuotes = false; + bool isEscaping = false; + + return commandLine.Split(c => + { + if (c == '\\' && !isEscaping) { isEscaping = true; return false; } + if (c == '\"' && !isEscaping) inQuotes = !inQuotes; + isEscaping = false; + + return !inQuotes && (splitChar == ' ' ? Char.IsWhiteSpace(c) : c == splitChar); //Char.IsWhiteSpace(c)/*c == ' '*/; + }).Select(arg => arg.Trim().TrimMatchingQuotes('\"').Replace("\\\"", "\"")); + } + + public static IEnumerable Split(this string str, Func controller) + { + int nextPiece = 0; + + for (int c = 0; c < str.Length; c++) + { + if (controller(str[c])) + { + yield return str.Substring(nextPiece, c - nextPiece); + nextPiece = c + 1; + } + } + + yield return str.Substring(nextPiece); + } + + public static string TrimMatchingQuotes(this string input, char quote) + { + if ((input.Length >= 2) && + (input[0] == quote) && (input[input.Length - 1] == quote)) + return input.Substring(1, input.Length - 2); + + return input; + } + + public static string JoinCommandLine(this IEnumerable arguments, char joinChar = ' ') + { + return string.Join(joinChar, arguments.Select(a => a.Contains(joinChar) ? $"\"{a}\"" : a)); + } + } +} diff --git a/LetterBoxdUnwatchedGenerator/IMovie.cs b/LetterBoxdUnwatchedGenerator/IMovie.cs new file mode 100644 index 0000000..dcaf6cc --- /dev/null +++ b/LetterBoxdUnwatchedGenerator/IMovie.cs @@ -0,0 +1,8 @@ +namespace LetterBoxdUnwatchedGenerator +{ + public interface IMovie + { + public string Title { get; } + public int Year { get; } + } +} diff --git a/LetterBoxdUnwatchedGenerator/LetterBoxdConnection.cs b/LetterBoxdUnwatchedGenerator/LetterBoxdConnection.cs new file mode 100644 index 0000000..0ec393e --- /dev/null +++ b/LetterBoxdUnwatchedGenerator/LetterBoxdConnection.cs @@ -0,0 +1,25 @@ +using System.Net.Http.Headers; + +namespace LetterBoxdUnwatchedGenerator +{ + public class LetterBoxdConnection + { + const string baseUrl = "https://letterboxd.com/api/v0/"; + HttpClient httpClient; + + public LetterBoxdConnection() + { + //Authentication + httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); + //add url form encoded content + var results = httpClient.PostAsync(baseUrl + "auth/token", new StringContent("grant_type=password&username=USERNAME&password=PASSWORD", MediaTypeHeaderValue.Parse("application/x-www-form-urlencoded"))); //, Encoding.UTF8, "application/x-www-form-urlencoded")); + + results.Result.EnsureSuccessStatusCode(); + var bearer = results.Result.Content.ReadAsStringAsync().Result; + + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearer); + + } + } +} diff --git a/LetterBoxdUnwatchedGenerator/LetterBoxdUnwatchedGenerator.csproj b/LetterBoxdUnwatchedGenerator/LetterBoxdUnwatchedGenerator.csproj new file mode 100644 index 0000000..f02677b --- /dev/null +++ b/LetterBoxdUnwatchedGenerator/LetterBoxdUnwatchedGenerator.csproj @@ -0,0 +1,10 @@ + + + + Exe + net7.0 + enable + enable + + + diff --git a/LetterBoxdUnwatchedGenerator/LetterBoxdWatchedMovie.cs b/LetterBoxdUnwatchedGenerator/LetterBoxdWatchedMovie.cs new file mode 100644 index 0000000..45b8925 --- /dev/null +++ b/LetterBoxdUnwatchedGenerator/LetterBoxdWatchedMovie.cs @@ -0,0 +1,37 @@ +namespace LetterBoxdUnwatchedGenerator +{ + public class LetterBoxdWatchedMovie : IMovie + { + public DateTime Date { get; } + public string Title { get; } + public int Year { get; } + public string LetterboxdUri { get; } + + public LetterBoxdWatchedMovie(DateTime date, string title, int year, string letterboxdUri) + { + Date = date; + Title = title; + Year = year; + LetterboxdUri = letterboxdUri; + } + + public static IEnumerable FromFile(string path) + { + var lines = File.ReadAllLines(path); + + if (lines.Length < 1 || !lines[0].StartsWith("Date,Name,Year,Letterboxd URI")) return Enumerable.Empty(); + + return lines.Skip(1).Select(line => + { + var lineParts = line.SplitCommandLine(','); + + var date = !string.IsNullOrEmpty(lineParts.First()) && DateTime.TryParse(lineParts.First(), out DateTime dateOut) ? dateOut : DateTime.MinValue; + var title = lineParts.Skip(1).First(); + var year = !string.IsNullOrEmpty(lineParts.Skip(2).First()) && int.TryParse(lineParts.Skip(2).First(), out int yearOut) ? yearOut : 0; + var letterboxdUri = lineParts.Skip(3).First(); + + return new LetterBoxdWatchedMovie(date, title, year, letterboxdUri); + }); + } + } +} diff --git a/LetterBoxdUnwatchedGenerator/LocalMovie.cs b/LetterBoxdUnwatchedGenerator/LocalMovie.cs new file mode 100644 index 0000000..fd06506 --- /dev/null +++ b/LetterBoxdUnwatchedGenerator/LocalMovie.cs @@ -0,0 +1,58 @@ +namespace LetterBoxdUnwatchedGenerator +{ + public class LocalMovie : IMovie + { + public string Title { get; } + public int Year { get; } + public string? Resolution { get; } + public string Directory { get; } + + public LocalMovie(string title, int year, string? resolution, string directory) + { + Title = title; + Year = year; + Resolution = resolution; + Directory = directory; + } + public static LocalMovie FromDirectory(string directoryName, string directoryFullName) + { + string title; + int? year; + string? resolution; + + var yearIndex = directoryName.LastIndexOf('('); + var resolutionIndex = directoryName.LastIndexOf('['); + if (yearIndex == -1) + { + year = null; + if (resolutionIndex == -1) + { + resolution = null; + title = directoryName; + } + else + { + resolution = directoryName.Substring(resolutionIndex + 1, directoryName.LastIndexOf(']') - resolutionIndex - 1); + title = directoryName[..(resolutionIndex - 1)].Trim(); + } + } + else + { + if (int.TryParse(directoryName.AsSpan(yearIndex + 1, directoryName.LastIndexOf(')') - yearIndex - 1), out int yearConverted)) year = yearConverted; + else year = null; + if (resolutionIndex == -1) + { + resolution = null; + title = directoryName[..(yearIndex - 1)].Trim(); + } + else + { + resolution = directoryName.Substring(resolutionIndex + 1, directoryName.LastIndexOf(']') - resolutionIndex - 1); + title = directoryName[..(yearIndex - 1)].Trim(); + } + } + + return new LocalMovie(title, year ?? 0, resolution, directoryFullName); + } + } +} diff --git a/LetterBoxdUnwatchedGenerator/Program.cs b/LetterBoxdUnwatchedGenerator/Program.cs new file mode 100644 index 0000000..2ee3860 --- /dev/null +++ b/LetterBoxdUnwatchedGenerator/Program.cs @@ -0,0 +1,39 @@ +using System.Text; + +namespace LetterBoxdUnwatchedGenerator +{ + internal class Program + { + static void Main(string[] args) + { + DirectoryInfo di = new DirectoryInfo(@"A:\Video\Movies\All"); + var localMovies = di.EnumerateDirectories().Select(di => LocalMovie.FromDirectory(di.Name, di.FullName)).ToList(); + + var watchedMovies = LetterBoxdWatchedMovie.FromFile(@"A:\homes\@DH-ARNEHOME\0\arne-1103\Documents\Inactive\2023-07-01 Letterboxd download\letterboxd-thearne4-2023-07-01-10-32-utc\watched.csv"); + + var unwatchedMovies = localMovies.Where(lm => !watchedMovies.Any(wm => lm.Title.Replace("-", "").Replace("(", "").Replace(")", "").Replace(" ", "").ToLowerInvariant().EndsWith(wm.Title.Replace("\\", "").Replace("/", "").Replace(":", "").Replace("\"", "").Replace("?", "").Replace("<", "").Replace(">", "").Replace("|", "").Replace("-", "").Replace(" ", "").ToLowerInvariant()) && wm.Year == lm.Year)); + + WriteToCsv(@"A:\homes\@DH-ARNEHOME\0\arne-1103\Documents\Inactive\2023-07-01 Letterboxd download\local-unwatched.csv", unwatchedMovies); + + //unwatchedMovies.ForEach(um => Console.WriteLine(um.Title)); + + Console.WriteLine("-------------"); + Console.WriteLine($"Total: {unwatchedMovies.Count()}"); + Console.ReadKey(); + } + static void WriteToCsv(string path, IEnumerable unwatchedMovies) + { + StringBuilder sb = new(); + sb.AppendLine("Title,Year"); + foreach (IMovie movie in unwatchedMovies) + { + var title = movie.Title.Contains(',') ? $"\"{movie.Title}\"" : movie.Title; + var year = movie.Year == 0 ? "" : movie.Year.ToString(); + + sb.AppendLine($"{title},{year}"); + } + + File.WriteAllText(path, sb.ToString()); + } + } +} \ No newline at end of file