initial commit

This commit is contained in:
Arne Moerman
2024-12-15 19:04:29 +01:00
commit ea58deab8c
15 changed files with 293 additions and 0 deletions

Binary file not shown.

View File

@@ -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

View File

@@ -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<string> 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<string> 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<string> Split(this string str, Func<char, bool> 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<string> arguments, char joinChar = ' ')
{
return string.Join(joinChar, arguments.Select(a => a.Contains(joinChar) ? $"\"{a}\"" : a));
}
}
}

View File

@@ -0,0 +1,8 @@
namespace LetterBoxdUnwatchedGenerator
{
public interface IMovie
{
public string Title { get; }
public int Year { get; }
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -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<LetterBoxdWatchedMovie> FromFile(string path)
{
var lines = File.ReadAllLines(path);
if (lines.Length < 1 || !lines[0].StartsWith("Date,Name,Year,Letterboxd URI")) return Enumerable.Empty<LetterBoxdWatchedMovie>();
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);
});
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<IMovie> 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());
}
}
}