From 3e23e859e897a8e24388aa7173c88f7374305c67 Mon Sep 17 00:00:00 2001
From: CodeConscious <50596087+codeconscious@users.noreply.github.com>
Date: Mon, 12 Jan 2026 20:53:01 +0900
Subject: [PATCH 01/12] Add NuGet package
---
src/CCVTAC.Main/CCVTAC.Main.fsproj | 1 +
src/CCVTAC.Tests/CCVTAC.Tests.fsproj | 1 +
2 files changed, 2 insertions(+)
diff --git a/src/CCVTAC.Main/CCVTAC.Main.fsproj b/src/CCVTAC.Main/CCVTAC.Main.fsproj
index 5f7431d..73632f6 100644
--- a/src/CCVTAC.Main/CCVTAC.Main.fsproj
+++ b/src/CCVTAC.Main/CCVTAC.Main.fsproj
@@ -41,6 +41,7 @@
+
diff --git a/src/CCVTAC.Tests/CCVTAC.Tests.fsproj b/src/CCVTAC.Tests/CCVTAC.Tests.fsproj
index bf184e0..778b9c2 100644
--- a/src/CCVTAC.Tests/CCVTAC.Tests.fsproj
+++ b/src/CCVTAC.Tests/CCVTAC.Tests.fsproj
@@ -15,6 +15,7 @@
+
From bae4a258291b9402986dc6c8bb241066049e4b4d Mon Sep 17 00:00:00 2001
From: CodeConscious <50596087+codeconscious@users.noreply.github.com>
Date: Mon, 12 Jan 2026 21:03:10 +0900
Subject: [PATCH 02/12] Use package funcs
---
src/CCVTAC.Main/IoUtilities/Directories.fs | 1 +
src/CCVTAC.Main/IoUtilities/Files.fs | 3 ++-
src/CCVTAC.Main/PostProcessing/Mover.fs | 2 +-
src/CCVTAC.Main/ResultTracker.fs | 2 +-
src/CCVTAC.Main/Shared.fs | 6 ------
5 files changed, 5 insertions(+), 9 deletions(-)
diff --git a/src/CCVTAC.Main/IoUtilities/Directories.fs b/src/CCVTAC.Main/IoUtilities/Directories.fs
index 4b34243..2b1b42a 100644
--- a/src/CCVTAC.Main/IoUtilities/Directories.fs
+++ b/src/CCVTAC.Main/IoUtilities/Directories.fs
@@ -1,6 +1,7 @@
namespace CCVTAC.Main.IoUtilities
open CCVTAC.Main
+open CCFSharpUtils.Library
open System.IO
module Directories =
diff --git a/src/CCVTAC.Main/IoUtilities/Files.fs b/src/CCVTAC.Main/IoUtilities/Files.fs
index c8d3510..7fe18ea 100644
--- a/src/CCVTAC.Main/IoUtilities/Files.fs
+++ b/src/CCVTAC.Main/IoUtilities/Files.fs
@@ -1,7 +1,8 @@
namespace CCVTAC.Main.IoUtilities
-open System.IO
open CCVTAC.Main
+open CCFSharpUtils.Library
+open System.IO
module Files =
diff --git a/src/CCVTAC.Main/PostProcessing/Mover.fs b/src/CCVTAC.Main/PostProcessing/Mover.fs
index e8ab9f8..bdbc62f 100644
--- a/src/CCVTAC.Main/PostProcessing/Mover.fs
+++ b/src/CCVTAC.Main/PostProcessing/Mover.fs
@@ -29,7 +29,7 @@ module Mover =
let playlistImages = images |> List.filter (fun i -> isPlaylistImage i.FullName)
if not (List.isEmpty playlistImages)
then Some playlistImages[0]
- elif audioFileCount > 1 && images.Length = 1
+ elif audioFileCount > 1 && List.hasOne images
then Some images[0]
else None
diff --git a/src/CCVTAC.Main/ResultTracker.fs b/src/CCVTAC.Main/ResultTracker.fs
index 1a60397..fdefdb2 100644
--- a/src/CCVTAC.Main/ResultTracker.fs
+++ b/src/CCVTAC.Main/ResultTracker.fs
@@ -33,7 +33,7 @@ type ResultTracker<'a>(printer: Printer) =
| Ok _ ->
successCount <- successCount + 1UL
| Error e ->
- let msg = if e.Length > 0 then List.head e else String.Empty
+ let msg = if e |> List.isNotEmpty then List.head e else String.Empty
if not (failures.TryAdd(input, msg)) then
failures[input] <- msg
diff --git a/src/CCVTAC.Main/Shared.fs b/src/CCVTAC.Main/Shared.fs
index d607d8a..7c6d7ff 100644
--- a/src/CCVTAC.Main/Shared.fs
+++ b/src/CCVTAC.Main/Shared.fs
@@ -8,12 +8,6 @@ module Shared =
type ResultMessageCollection = { Successes: string list; Failures: string list }
- /// Safely runs a function that might raise an exception.
- /// If an exception is thrown, only returns its message.
- let ofTry (f: unit -> 'a) : Result<'a, string> =
- try Ok (f())
- with exn -> Error exn.Message
-
let sleep workingMsgFn doneMsgFn seconds : string =
let rec loop remaining (ctx: StatusContext) =
if remaining > 0us then
From 4fa624f8adb288c52f0c40586775c50500f9ec26 Mon Sep 17 00:00:00 2001
From: CodeConscious <50596087+codeconscious@users.noreply.github.com>
Date: Mon, 12 Jan 2026 21:21:59 +0900
Subject: [PATCH 03/12] Upgrade package
---
src/CCVTAC.Main/CCVTAC.Main.fsproj | 2 +-
src/CCVTAC.Tests/CCVTAC.Tests.fsproj | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/CCVTAC.Main/CCVTAC.Main.fsproj b/src/CCVTAC.Main/CCVTAC.Main.fsproj
index 73632f6..ab66807 100644
--- a/src/CCVTAC.Main/CCVTAC.Main.fsproj
+++ b/src/CCVTAC.Main/CCVTAC.Main.fsproj
@@ -41,7 +41,7 @@
-
+
diff --git a/src/CCVTAC.Tests/CCVTAC.Tests.fsproj b/src/CCVTAC.Tests/CCVTAC.Tests.fsproj
index 778b9c2..3862aee 100644
--- a/src/CCVTAC.Tests/CCVTAC.Tests.fsproj
+++ b/src/CCVTAC.Tests/CCVTAC.Tests.fsproj
@@ -15,7 +15,7 @@
-
+
From e3aaaf21050058f21e99aea1585f52633dea03ff Mon Sep 17 00:00:00 2001
From: CodeConscious <50596087+codeconscious@users.noreply.github.com>
Date: Mon, 12 Jan 2026 21:54:03 +0900
Subject: [PATCH 04/12] Update func to library ones, fix tests, etc.
---
src/CCVTAC.Main/CCVTAC.Main.fsproj | 1 -
src/CCVTAC.Main/Commands.fs | 1 +
src/CCVTAC.Main/Downloading/Updater.fs | 3 +-
src/CCVTAC.Main/Extensions.fs | 167 ----------
src/CCVTAC.Main/ExternalTools/Runner.fs | 1 +
src/CCVTAC.Main/History.fs | 1 +
src/CCVTAC.Main/IoUtilities/Directories.fs | 5 +-
src/CCVTAC.Main/IoUtilities/Files.fs | 2 +-
src/CCVTAC.Main/Orchestrator.fs | 9 +-
src/CCVTAC.Main/PostProcessing/Deleter.fs | 5 +-
.../PostProcessing/MetadataUtilities.fs | 5 +-
src/CCVTAC.Main/PostProcessing/Mover.fs | 6 +-
.../PostProcessing/PostProcessing.fs | 1 +
src/CCVTAC.Main/PostProcessing/Renamer.fs | 13 +-
.../PostProcessing/Tagging/Tagger.fs | 5 +-
.../PostProcessing/Tagging/TaggingSet.fs | 3 +-
src/CCVTAC.Main/Printer.fs | 3 +-
src/CCVTAC.Main/Program.fs | 7 +-
src/CCVTAC.Main/ResultTracker.fs | 3 +-
src/CCVTAC.Main/Settings/Settings.fs | 5 +-
src/CCVTAC.Tests/CCVTAC.Tests.fsproj | 1 -
src/CCVTAC.Tests/ExtensionsTests.fs | 288 ------------------
src/CCVTAC.Tests/TagDetectionTests.fs | 2 +-
23 files changed, 49 insertions(+), 488 deletions(-)
delete mode 100644 src/CCVTAC.Main/Extensions.fs
delete mode 100644 src/CCVTAC.Tests/ExtensionsTests.fs
diff --git a/src/CCVTAC.Main/CCVTAC.Main.fsproj b/src/CCVTAC.Main/CCVTAC.Main.fsproj
index ab66807..2c32439 100644
--- a/src/CCVTAC.Main/CCVTAC.Main.fsproj
+++ b/src/CCVTAC.Main/CCVTAC.Main.fsproj
@@ -8,7 +8,6 @@
-
diff --git a/src/CCVTAC.Main/Commands.fs b/src/CCVTAC.Main/Commands.fs
index e55342a..8eb8013 100644
--- a/src/CCVTAC.Main/Commands.fs
+++ b/src/CCVTAC.Main/Commands.fs
@@ -1,5 +1,6 @@
namespace CCVTAC.Main
+open CCFSharpUtils.Library
open System
module Commands =
diff --git a/src/CCVTAC.Main/Downloading/Updater.fs b/src/CCVTAC.Main/Downloading/Updater.fs
index 596b179..7d90131 100644
--- a/src/CCVTAC.Main/Downloading/Updater.fs
+++ b/src/CCVTAC.Main/Downloading/Updater.fs
@@ -1,8 +1,9 @@
namespace CCVTAC.Main.Downloading
-open CCVTAC.Main.ExternalTools
open CCVTAC.Main
+open CCVTAC.Main.ExternalTools
open CCVTAC.Main.Settings.Settings
+open CCFSharpUtils.Library
module Updater =
diff --git a/src/CCVTAC.Main/Extensions.fs b/src/CCVTAC.Main/Extensions.fs
deleted file mode 100644
index e4e42bc..0000000
--- a/src/CCVTAC.Main/Extensions.fs
+++ /dev/null
@@ -1,167 +0,0 @@
-namespace CCVTAC.Main
-
-open System
-open System.Globalization
-open System.IO
-open System.Text
-
-type SB = StringBuilder
-
-module Numerics =
-
- let inline isZero (n: ^a) =
- n = LanguagePrimitives.GenericZero<'a>
-
- let inline isOne (n: ^a) =
- n = LanguagePrimitives.GenericOne<'a>
-
- /// Formats a number of any type to a comma-formatted string.
- let inline formatNumber (i: ^T) : string
- when ^T : (member ToString : string * IFormatProvider -> string) =
- (^T : (member ToString : string * IFormatProvider -> string) (i, "#,##0", CultureInfo.InvariantCulture))
-
-module String =
-
- let newLine = Environment.NewLine
-
- let hasNoText text =
- String.IsNullOrWhiteSpace text
-
- let hasText text =
- not (hasNoText text)
-
- let allHaveText xs =
- xs |> List.forall hasText
-
- let textOrFallback fallback text =
- if hasText text then text else fallback
-
- let textOrEmpty text =
- textOrFallback text String.Empty
-
- let equalIgnoringCase x y =
- String.Equals(x, y, StringComparison.OrdinalIgnoreCase)
-
- /// Checks whether a string begins with a specified substring. Case is ignored.
- let startsWith startText (text: string) =
- text.StartsWith(startText, StringComparison.InvariantCultureIgnoreCase)
-
- /// Checks whether a string ends with a specified substring. Case is ignored.
- let endsWith endText (text: string) =
- text.EndsWith(endText, StringComparison.InvariantCultureIgnoreCase)
-
- /// Pluralize text using a specified count.
- let inline pluralize ifOne ifNotOne count =
- if Numerics.isOne count then ifOne else ifNotOne
-
- /// Pluralize text including its count, such as "1 file", "30 URLs".
- let inline pluralizeWithCount ifOne ifNotOne count =
- sprintf "%d %s" count (pluralize ifOne ifNotOne count)
-
- let inline private fileLabeller descriptor (count: int) =
- match descriptor with
- | None -> $"""%s{Numerics.formatNumber count} %s{pluralize "file" "files" count}"""
- | Some d -> $"""%s{Numerics.formatNumber count} %s{d} {pluralize "file" "files" count}"""
-
- /// Returns a file-count string, such as "0 files" or 1 file" or "140 files".
- let fileLabel count =
- fileLabeller None count
-
- /// Returns a file-count string with a descriptor, such as "0 audio files" or "140 deleted files".
- let fileLabelWithDescriptor (descriptor: string) count =
- fileLabeller (Some (descriptor.Trim())) count
-
- /// Returns a new string in which all invalid path characters for the current OS
- /// have been replaced by the specified replacement character.
- /// Throws if the replacement character is an invalid path character.
- let replaceInvalidPathChars
- (replaceWith: char option)
- (customInvalidChars: char list option)
- (text: string)
- : string =
-
- let replaceWith = defaultArg replaceWith '_'
- let custom = defaultArg customInvalidChars []
-
- let invalidChars =
- seq {
- yield! Path.GetInvalidFileNameChars()
- yield! Path.GetInvalidPathChars()
- yield Path.PathSeparator
- yield Path.DirectorySeparatorChar
- yield Path.AltDirectorySeparatorChar
- yield Path.VolumeSeparatorChar
- yield! custom
- }
- |> Set.ofSeq
-
- if invalidChars |> Set.contains replaceWith then
- invalidArg "replaceWith" $"The replacement char ('%c{replaceWith}') must be a valid path character."
-
- Set.fold
- (fun (sb: SB) ch -> sb.Replace(ch, replaceWith))
- (SB text)
- invalidChars
- |> _.ToString()
-
- let trimTerminalLineBreak (text: string) =
- text.TrimEnd(newLine.ToCharArray())
-
-[]
-module Seq =
-
- let isNotEmpty seq = not (Seq.isEmpty seq)
-
- let doesNotContain x seq = not <| Seq.contains x seq
-
- let hasOne seq = seq |> Seq.length |> Numerics.isOne
-
- let hasMultiple seq = seq |> Seq.length |> (<) 1
-
- let caseInsensitiveContains text (xs: string seq) : bool =
- xs |> Seq.exists (fun x -> String.Equals(x, text, StringComparison.OrdinalIgnoreCase))
-
-[]
-module List =
-
- let isNotEmpty lst = not (List.isEmpty lst)
-
- let doesNotContain x lst = not <| List.contains x lst
-
- let hasOne lst = lst |> List.length |> Numerics.isOne
-
- let hasMultiple lst = lst |> List.length |> (<) 1
-
- let caseInsensitiveContains text (lst: string list) : bool =
- lst |> List.exists (fun x -> String.Equals(x, text, StringComparison.OrdinalIgnoreCase))
-
-[]
-module Array =
-
- let isNotEmpty arr = not <| Array.isEmpty arr
-
- let doesNotContain x arr = not <| Array.contains x arr
-
- let hasOne arr = arr |> Array.length |> Numerics.isOne
-
- let hasMultiple arr = arr |> Array.length |> (<) 1
-
- let caseInsensitiveContains text (arr: string array) : bool =
- arr |> Array.exists (fun x -> String.Equals(x, text, StringComparison.OrdinalIgnoreCase))
-
-/// Operations for regular expressions.
-[]
-module Rgx =
- open System.Text.RegularExpressions
-
- let trySuccessMatch (rgx: Regex) txt =
- let result = rgx.Match txt
- match result.Success with
- | false -> None
- | true -> Some result
-
- let capturesToSeq (m: Match) : Match seq =
- m.Captures |> Seq.cast
-
- let fstCapture (m: Match) =
- m |> capturesToSeq |> Seq.head
diff --git a/src/CCVTAC.Main/ExternalTools/Runner.fs b/src/CCVTAC.Main/ExternalTools/Runner.fs
index 5fd2a1a..d49b61d 100644
--- a/src/CCVTAC.Main/ExternalTools/Runner.fs
+++ b/src/CCVTAC.Main/ExternalTools/Runner.fs
@@ -1,6 +1,7 @@
namespace CCVTAC.Main.ExternalTools
open CCVTAC.Main
+open CCFSharpUtils.Library
open Startwatch.Library
open System
open System.Diagnostics
diff --git a/src/CCVTAC.Main/History.fs b/src/CCVTAC.Main/History.fs
index 80b084d..9af3e5a 100644
--- a/src/CCVTAC.Main/History.fs
+++ b/src/CCVTAC.Main/History.fs
@@ -1,6 +1,7 @@
namespace CCVTAC.Main
open CCVTAC.Main.IoUtilities.Files
+open CCFSharpUtils.Library
open System
open System.IO
open System.Text.Json
diff --git a/src/CCVTAC.Main/IoUtilities/Directories.fs b/src/CCVTAC.Main/IoUtilities/Directories.fs
index 2b1b42a..007a05d 100644
--- a/src/CCVTAC.Main/IoUtilities/Directories.fs
+++ b/src/CCVTAC.Main/IoUtilities/Directories.fs
@@ -3,6 +3,7 @@ namespace CCVTAC.Main.IoUtilities
open CCVTAC.Main
open CCFSharpUtils.Library
open System.IO
+open System.Text
module Directories =
@@ -12,7 +13,7 @@ module Directories =
/// Counts the number of audio files in a directory.
let audioFileCount (directory: string) (includedExtensions: string list) =
DirectoryInfo(directory).EnumerateFiles()
- |> Seq.filter (fun f -> List.caseInsensitiveContains f.Extension includedExtensions)
+ |> Seq.filter (fun f -> List.containsIgnoreCase f.Extension includedExtensions)
|> Seq.length
/// Returns the filenames in a given directory, optionally ignoring specific filenames.
@@ -72,7 +73,7 @@ module Directories =
if Array.isEmpty fileNames then
Ok ()
else
- SB($"Unexpectedly found {String.fileLabel fileNames.Length} in working directory \"{dirName}\":{String.newLine}")
+ StringBuilder($"Unexpectedly found {String.fileLabel fileNames.Length} in working directory \"{dirName}\":{String.newLine}")
.AppendLine
(fileNames
|> Array.truncate showMax
diff --git a/src/CCVTAC.Main/IoUtilities/Files.fs b/src/CCVTAC.Main/IoUtilities/Files.fs
index 7fe18ea..00d2e98 100644
--- a/src/CCVTAC.Main/IoUtilities/Files.fs
+++ b/src/CCVTAC.Main/IoUtilities/Files.fs
@@ -14,7 +14,7 @@ module Files =
let imageFileExts = [".jpg"; ".jpeg"]
- let filterByExt ext fs = fs |> List.filter (String.endsWith ext)
+ let filterByExt ext fs = fs |> List.filter (String.endsWithIgnoreCase ext)
let readAllText (filePath: string) : Result =
ofTry (fun _ -> File.ReadAllText filePath)
diff --git a/src/CCVTAC.Main/Orchestrator.fs b/src/CCVTAC.Main/Orchestrator.fs
index 833fd86..788cba1 100644
--- a/src/CCVTAC.Main/Orchestrator.fs
+++ b/src/CCVTAC.Main/Orchestrator.fs
@@ -7,6 +7,7 @@ open CCVTAC.Main.PostProcessing
open CCVTAC.Main.Settings
open CCVTAC.Main.Settings.Settings
open CCVTAC.Main.Settings.Settings.LiveUpdating
+open CCFSharpUtils.Library
open Startwatch.Library
open System
@@ -120,10 +121,10 @@ module Orchestrator =
(printer: Printer)
: Result =
- let checkCommand = List.caseInsensitiveContains command
+ let checkCommand = List.containsIgnoreCase command
// Help
- if String.equalIgnoringCase Commands.helpCommand command then
+ if String.equalIgnoreCase Commands.helpCommand command then
for kvp in Commands.summary do
printer.Info(kvp.Key)
printer.Info $" %s{kvp.Value}"
@@ -168,7 +169,7 @@ module Orchestrator =
Ok { NextAction = NextAction.Continue; UpdatedSettings = Some newSettings }
// Update audio formats
- elif command |> String.startsWith Commands.updateAudioFormatPrefix then
+ elif command |> String.startsWithIgnoreCase Commands.updateAudioFormatPrefix then
let format = command.Replace(Commands.updateAudioFormatPrefix, String.Empty).ToLowerInvariant()
if String.hasNoText format then
Error "You must append one or more supported audio formats separated by commas (e.g., \"m4a,opus,best\")."
@@ -181,7 +182,7 @@ module Orchestrator =
Ok { NextAction = NextAction.Continue; UpdatedSettings = Some newSettings }
// Update audio quality
- elif command |> String.startsWith Commands.updateAudioQualityPrefix then
+ elif command |> String.startsWithIgnoreCase Commands.updateAudioQualityPrefix then
let inputQuality = command.Replace(Commands.updateAudioQualityPrefix, String.Empty)
if String.hasNoText inputQuality then
Error "You must enter a number representing an audio quality between 10 (lowest) and 0 (highest)."
diff --git a/src/CCVTAC.Main/PostProcessing/Deleter.fs b/src/CCVTAC.Main/PostProcessing/Deleter.fs
index b850d6c..e6485c5 100644
--- a/src/CCVTAC.Main/PostProcessing/Deleter.fs
+++ b/src/CCVTAC.Main/PostProcessing/Deleter.fs
@@ -1,6 +1,7 @@
namespace CCVTAC.Main.PostProcessing
open CCVTAC.Main
+open CCFSharpUtils.Library
open System.IO
module Deleter =
@@ -35,7 +36,7 @@ module Deleter =
let collectionFileNames =
match getCollectionFiles collectionMetadata workingDirectory with
| Ok files ->
- printer.Debug $"""Found {String.fileLabelWithDescriptor "collection" files.Length}."""
+ printer.Debug $"""Found {String.fileLabelWithDesc "collection" files.Length}."""
files
| Error err ->
printer.Warning err
@@ -46,6 +47,6 @@ module Deleter =
if Array.isEmpty allFileNames then
printer.Warning "No files to delete were found."
else
- printer.Debug $"""Deleting {String.fileLabelWithDescriptor "temporary" allFileNames.Length}..."""
+ printer.Debug $"""Deleting {String.fileLabelWithDesc "temporary" allFileNames.Length}..."""
deleteAll allFileNames printer
printer.Info "Deleted temporary files."
diff --git a/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs b/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs
index e107389..087eb8a 100644
--- a/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs
+++ b/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs
@@ -1,8 +1,9 @@
namespace CCVTAC.Main.PostProcessing
+open CCVTAC.Main
+open CCFSharpUtils.Library
open System
open System.Text
-open CCVTAC.Main
module MetadataUtilities =
@@ -20,7 +21,7 @@ module MetadataUtilities =
sprintf "%s/%s/%s" m d y
let generateComment (v: VideoMetadata) (c: CollectionMetadata option) : string =
- let sb = SB()
+ let sb = StringBuilder()
sb.AppendLine("CCVTAC SOURCE DATA:") |> ignore
sb.AppendLine $"■ Downloaded: {DateTime.Now}" |> ignore
sb.AppendLine $"■ URL: %s{v.WebpageUrl}" |> ignore
diff --git a/src/CCVTAC.Main/PostProcessing/Mover.fs b/src/CCVTAC.Main/PostProcessing/Mover.fs
index bdbc62f..26a5187 100644
--- a/src/CCVTAC.Main/PostProcessing/Mover.fs
+++ b/src/CCVTAC.Main/PostProcessing/Mover.fs
@@ -1,3 +1,4 @@
+
namespace CCVTAC.Main.PostProcessing
open CCVTAC.Main
@@ -5,6 +6,7 @@ open CCVTAC.Main.IoUtilities
open CCVTAC.Main.PostProcessing
open CCVTAC.Main.PostProcessing.Tagging
open CCVTAC.Main.Settings.Settings
+open CCFSharpUtils.Library
open TaggingSet
open System
open System.IO
@@ -130,13 +132,13 @@ module Mover =
let audioFileNames =
workingDirInfo.EnumerateFiles()
- |> Seq.filter (fun f -> List.caseInsensitiveContains f.Extension Files.audioFileExts)
+ |> Seq.filter (fun f -> List.containsIgnoreCase f.Extension Files.audioFileExts)
|> List.ofSeq
if audioFileNames.IsEmpty then
printer.Error "No audio filenames to move were found."
else
- let fileCountMsg = String.fileLabelWithDescriptor "audio"
+ let fileCountMsg = String.fileLabelWithDesc "audio"
printer.Debug $"Moving %s{fileCountMsg audioFileNames.Length} to \"%s{fullMoveToDir}\"..."
diff --git a/src/CCVTAC.Main/PostProcessing/PostProcessing.fs b/src/CCVTAC.Main/PostProcessing/PostProcessing.fs
index 55788ce..3c938e7 100644
--- a/src/CCVTAC.Main/PostProcessing/PostProcessing.fs
+++ b/src/CCVTAC.Main/PostProcessing/PostProcessing.fs
@@ -4,6 +4,7 @@ open CCVTAC.Main
open CCVTAC.Main.IoUtilities
open CCVTAC.Main.PostProcessing.Tagging
open CCVTAC.Main.Settings.Settings
+open CCFSharpUtils.Library
open System.IO
open System.Linq
open System.Text.Json
diff --git a/src/CCVTAC.Main/PostProcessing/Renamer.fs b/src/CCVTAC.Main/PostProcessing/Renamer.fs
index 6d1d887..0c06de5 100644
--- a/src/CCVTAC.Main/PostProcessing/Renamer.fs
+++ b/src/CCVTAC.Main/PostProcessing/Renamer.fs
@@ -3,6 +3,7 @@ namespace CCVTAC.Main.PostProcessing
open CCVTAC.Main
open CCVTAC.Main.IoUtilities
open CCVTAC.Main.Settings.Settings
+open CCFSharpUtils.Library
open System
open System.IO
open System.Text
@@ -18,7 +19,7 @@ module Renamer =
| "KC" -> NormalizationForm.FormKC
| _ -> NormalizationForm.FormC
- let updateTextViaPatterns isQuietMode (printer: Printer) (sb: SB) (renamePattern: RenamePattern) =
+ let updateTextViaPatterns isQuietMode (printer: Printer) (sb: StringBuilder) (renamePattern: RenamePattern) =
let regex = Regex renamePattern.RegexPattern
let matches =
@@ -55,7 +56,7 @@ module Renamer =
then m.Groups[i + 1].Value.Trim()
else String.Empty
(searchFor, replaceWith))
- |> Seq.fold (fun (sb': SB) -> sb'.Replace) (SB renamePattern.ReplaceWithPattern)
+ |> Seq.fold (fun (sb': StringBuilder) -> sb'.Replace) (StringBuilder renamePattern.ReplaceWithPattern)
|> _.ToString()
sb.Insert(m.Index, replacementText) |> ignore
@@ -68,20 +69,20 @@ module Renamer =
let audioFiles =
workingDirInfo.EnumerateFiles()
- |> Seq.filter (fun f -> List.caseInsensitiveContains f.Extension Files.audioFileExts)
+ |> Seq.filter (fun f -> List.containsIgnoreCase f.Extension Files.audioFileExts)
|> List.ofSeq
if List.isEmpty audioFiles then
printer.Warning "No audio files to rename were found."
else
- printer.Debug $"""Renaming %s{String.fileLabelWithDescriptor "audio" audioFiles.Length}..."""
+ printer.Debug $"""Renaming %s{String.fileLabelWithDesc "audio" audioFiles.Length}..."""
for audioFile in audioFiles do
let newFileName =
userSettings.RenamePatterns
|> List.fold
- (fun (sb: SB) -> updateTextViaPatterns userSettings.QuietMode printer sb)
- (SB audioFile.Name)
+ (fun (sb: StringBuilder) -> updateTextViaPatterns userSettings.QuietMode printer sb)
+ (StringBuilder audioFile.Name)
|> _.ToString()
try
diff --git a/src/CCVTAC.Main/PostProcessing/Tagging/Tagger.fs b/src/CCVTAC.Main/PostProcessing/Tagging/Tagger.fs
index 9d4d2a6..8a905fc 100644
--- a/src/CCVTAC.Main/PostProcessing/Tagging/Tagger.fs
+++ b/src/CCVTAC.Main/PostProcessing/Tagging/Tagger.fs
@@ -5,6 +5,7 @@ open CCVTAC.Main.Settings.Settings
open CCVTAC.Main.PostProcessing
open CCVTAC.Main.PostProcessing.Tagging
open CCVTAC.Main.Downloading.Downloading
+open CCFSharpUtils.Library
open Startwatch.Library
open TaggingSet
open MetadataUtilities
@@ -60,7 +61,7 @@ module Tagger =
printer.Error $"Error writing image to the audio file: %s{exn.Message}"
let private releaseYear userSettings videoMetadata : uint32 option =
- if userSettings.IgnoreUploadYearUploaders |> List.caseInsensitiveContains videoMetadata.Uploader then
+ if userSettings.IgnoreUploadYearUploaders |> List.containsIgnoreCase videoMetadata.Uploader then
None
elif videoMetadata.UploadDate.Length <> 4 then
None
@@ -181,7 +182,7 @@ module Tagger =
(printer: Printer)
: unit =
- printer.Debug $"""Found %s{String.fileLabelWithDescriptor "audio" taggingSet.AudioFiles.Length} with resource ID %s{taggingSet.VideoId}."""
+ printer.Debug $"""Found %s{String.fileLabelWithDesc "audio" taggingSet.AudioFiles.Length} with resource ID %s{taggingSet.VideoId}."""
match parseVideoJson taggingSet with
| Ok videoData ->
diff --git a/src/CCVTAC.Main/PostProcessing/Tagging/TaggingSet.fs b/src/CCVTAC.Main/PostProcessing/Tagging/TaggingSet.fs
index e1e4de5..d7c170f 100644
--- a/src/CCVTAC.Main/PostProcessing/Tagging/TaggingSet.fs
+++ b/src/CCVTAC.Main/PostProcessing/Tagging/TaggingSet.fs
@@ -2,6 +2,7 @@ namespace CCVTAC.Main.PostProcessing.Tagging
open CCVTAC.Main
open CCVTAC.Main.IoUtilities
+open CCFSharpUtils.Library
open FsToolkit.ErrorHandling
open System.IO
open System.Text.RegularExpressions
@@ -46,7 +47,7 @@ module TaggingSet =
match Path.GetExtension fileName with
| Null -> false
| NonNull empty when String.hasNoText empty -> false
- | NonNull ext -> Files.audioFileExts |> List.caseInsensitiveContains ext
+ | NonNull ext -> Files.audioFileExts |> List.containsIgnoreCase ext
let audioFiles = files |> List.filter hasSupportedAudioExt
let jsonFiles = files |> Files.filterByExt Files.jsonFileExt
diff --git a/src/CCVTAC.Main/Printer.fs b/src/CCVTAC.Main/Printer.fs
index d73f12b..70b6395 100644
--- a/src/CCVTAC.Main/Printer.fs
+++ b/src/CCVTAC.Main/Printer.fs
@@ -1,5 +1,6 @@
namespace CCVTAC.Main
+open CCFSharpUtils.Library
open System
open System.Collections.Generic
open System.Linq
@@ -136,7 +137,7 @@ type Printer(showDebug: bool) =
/// Prints the requested number of blank lines.
static member EmptyLines(count: byte) =
- if Numerics.isZero count
+ if Num.isZero count
then ()
else
let repeats = int count - 1
diff --git a/src/CCVTAC.Main/Program.fs b/src/CCVTAC.Main/Program.fs
index dd1634b..50a6bfb 100644
--- a/src/CCVTAC.Main/Program.fs
+++ b/src/CCVTAC.Main/Program.fs
@@ -5,9 +5,10 @@ open CCVTAC.Main.IoUtilities
open CCVTAC.Main.Settings
open CCVTAC.Main.Settings.Settings
open Settings.IO
+open CCFSharpUtils.Library
+open Spectre.Console
open System
open System.IO
-open Spectre.Console
module Program =
@@ -24,13 +25,13 @@ module Program =
let main args : int =
let printer = Printer(showDebug = true)
- if Array.isNotEmpty args && Array.caseInsensitiveContains args[0] helpFlags then
+ if Array.isNotEmpty args && Array.containsIgnoreCase args[0] helpFlags then
printer.Info Help.helpText
int ExitCodes.Success
else
let settingsPath =
FileInfo <|
- if Array.hasMultiple args && Array.caseInsensitiveContains args[0] settingsFileFlags then
+ if Array.hasMultiple args && Array.containsIgnoreCase args[0] settingsFileFlags then
args[1] // Expected to be a settings file path.
else
defaultSettingsFileName
diff --git a/src/CCVTAC.Main/ResultTracker.fs b/src/CCVTAC.Main/ResultTracker.fs
index fdefdb2..2a9794e 100644
--- a/src/CCVTAC.Main/ResultTracker.fs
+++ b/src/CCVTAC.Main/ResultTracker.fs
@@ -1,5 +1,6 @@
namespace CCVTAC.Main
+open CCFSharpUtils.Library
open System
open System.Collections.Generic
@@ -39,7 +40,7 @@ type ResultTracker<'a>(printer: Printer) =
/// Prints any failures for the current batch.
member _.PrintBatchFailures() : unit =
- if Numerics.isZero failures.Count then
+ if Num.isZero failures.Count then
printer.Debug "No failures in batch."
else
let failureLabel = String.pluralize "failure" "failures" failures.Count
diff --git a/src/CCVTAC.Main/Settings/Settings.fs b/src/CCVTAC.Main/Settings/Settings.fs
index 23c8f4c..e0fa905 100644
--- a/src/CCVTAC.Main/Settings/Settings.fs
+++ b/src/CCVTAC.Main/Settings/Settings.fs
@@ -1,9 +1,10 @@
namespace CCVTAC.Main.Settings
-open System
-open System.Text.Json.Serialization
open CCVTAC.Main
+open CCFSharpUtils.Library
open Spectre.Console
+open System
+open System.Text.Json.Serialization
module Settings =
diff --git a/src/CCVTAC.Tests/CCVTAC.Tests.fsproj b/src/CCVTAC.Tests/CCVTAC.Tests.fsproj
index 3862aee..bbabcaa 100644
--- a/src/CCVTAC.Tests/CCVTAC.Tests.fsproj
+++ b/src/CCVTAC.Tests/CCVTAC.Tests.fsproj
@@ -9,7 +9,6 @@
-
diff --git a/src/CCVTAC.Tests/ExtensionsTests.fs b/src/CCVTAC.Tests/ExtensionsTests.fs
deleted file mode 100644
index 506958c..0000000
--- a/src/CCVTAC.Tests/ExtensionsTests.fs
+++ /dev/null
@@ -1,288 +0,0 @@
-module ExtensionsTests
-
-open CCVTAC.Main
-open Xunit
-open System
-
-module NumericsTests =
- open CCVTAC.Main.Numerics
-
- []
- let ``isZero returns true for any zero value`` () =
- Assert.True <| isZero 0
- Assert.True <| isZero 0u
- Assert.True <| isZero 0us
- Assert.True <| isZero 0.
- Assert.True <| isZero 0L
- Assert.True <| isZero 0m
- Assert.True <| isZero -0
- Assert.True <| isZero -0.
- Assert.True <| isZero -0L
- Assert.True <| isZero -0m
-
- []
- let ``isZero returns false for any non-zero value`` () =
- Assert.False <| isZero 1
- Assert.False <| isOne -1
- Assert.False <| isOne Int64.MinValue
- Assert.False <| isOne Int64.MaxValue
- Assert.False <| isOne 2
- Assert.False <| isZero 1u
- Assert.False <| isZero 1us
- Assert.False <| isZero -0.0000000000001
- Assert.False <| isZero 0.0000000000001
- Assert.False <| isZero 1.
- Assert.False <| isZero 1L
- Assert.False <| isZero 1m
-
- []
- let ``isOne returns true for any one value`` () =
- Assert.True <| isOne 1
- Assert.True <| isOne 1u
- Assert.True <| isOne 1us
- Assert.True <| isOne 1.
- Assert.True <| isOne 1L
- Assert.True <| isOne 1m
-
- []
- let ``isOne returns false for any non-one value`` () =
- Assert.False <| isOne 0
- Assert.False <| isOne -1
- Assert.False <| isOne Int64.MinValue
- Assert.False <| isOne Int64.MaxValue
- Assert.False <| isOne 2
- Assert.False <| isOne 0u
- Assert.False <| isOne 16u
- Assert.False <| isOne 0us
- Assert.False <| isOne -0.
- Assert.False <| isOne 0.001
- Assert.False <| isOne 0L
- Assert.False <| isOne 0m
-
- module FormatNumberTests =
-
- // A tiny custom type that implements the required ToString signature.
- type MyCustomNum(i: int) =
- member _.ToString(fmt: string, provider: IFormatProvider) =
- i.ToString(fmt, provider)
-
- []
- let ``format int`` () =
- let actual = formatNumber 123456
- Assert.Equal("123,456", actual)
-
- []
- let ``format negative int`` () =
- let actual = formatNumber -1234
- Assert.Equal("-1,234", actual)
-
- []
- let ``format zero`` () =
- let actual = formatNumber 0
- Assert.Equal("0", actual)
-
- []
- let ``format int64`` () =
- let actual = formatNumber 1234567890L
- Assert.Equal("1,234,567,890", actual)
-
- []
- let ``format decimal rounds to integer display`` () =
- let actual = formatNumber 123456.78M
- Assert.Equal("123,457", actual)
-
- []
- let ``format float rounds to integer display`` () =
- let actual = formatNumber 123456.78
- Assert.Equal("123,457", actual)
-
- []
- let ``format negative float rounds to integer display`` () =
- let actual = formatNumber -1234.56
- Assert.Equal("-1,235", actual)
-
- []
- let ``format custom numeric type`` () =
- let myNum = MyCustomNum 1234
- let actual = formatNumber myNum
- Assert.Equal("1,234", actual)
-
-module StringTests =
- open CCVTAC.Main.String
-
- []
- let ``fileLabel formats correctly`` () =
- Assert.True <| (fileLabel 0 = "0 files")
- Assert.True <| (fileLabel 1 = "1 file")
- Assert.True <| (fileLabel 2 = "2 files")
- Assert.True <| (fileLabel 1_000_000 = "1,000,000 files")
-
- []
- let ``fileLabelWithDescriptor formats correctly`` () =
- Assert.True <| (fileLabelWithDescriptor "audio" 0 = "0 audio files")
- Assert.True <| (fileLabelWithDescriptor " temporary " 1 = "1 temporary file")
- Assert.True <| (fileLabelWithDescriptor "deleted" 2 = "2 deleted files")
- Assert.True <| (fileLabelWithDescriptor "image" 1_000_000 = "1,000,000 image files")
-
- module ReplaceInvalidPathCharsTests =
- open System.IO
-
- []
- let ``Default replacement '_' replaces invalid path chars`` () =
- let invalids = Path.GetInvalidFileNameChars() |> Array.except ['\000']
- if Array.isEmpty invalids then
- Assert.True(false, "Unexpected environment: not enough invalid filename chars")
- let invalid = invalids[0]
- let input = $"start%c{invalid}end"
- let result = replaceInvalidPathChars None None input
-
- Assert.DoesNotContain(invalid.ToString(), result)
- Assert.Contains("_", result)
- Assert.StartsWith("start", result)
- Assert.EndsWith("end", result)
-
- []
- let ``Custom invalid chars are replaced with provided replacement`` () =
- let custom = ['#'; '%']
- let input = "abc#def%ghi"
- let result = replaceInvalidPathChars (Some '-') (Some custom) input
-
- Assert.DoesNotContain("#", result)
- Assert.DoesNotContain("%", result)
- Assert.Equal("abc-def-ghi", result)
-
- []
- let ``Throws when replacement char itself is invalid`` () =
- let invalidReplacement = Array.head <| Path.GetInvalidFileNameChars()
- let input = "has-no-invalid-chars"
- Assert.Throws(fun () ->
- replaceInvalidPathChars (Some invalidReplacement) None input |> ignore)
-
- []
- let ``No invalid chars returns identical string`` () =
- let input = "HelloWorld123"
- let result = replaceInvalidPathChars None None input
- Assert.Equal(input, result)
-
- []
- let ``All invalid path and filename chars are replaced`` () =
- let fileInvalid = Path.GetInvalidFileNameChars() |> Array.truncate 3
- let pathInvalid = Path.GetInvalidPathChars() |> Array.truncate 3
- let extras = [| Path.PathSeparator
- Path.DirectorySeparatorChar
- Path.AltDirectorySeparatorChar
- Path.VolumeSeparatorChar |]
- let charsToTest = Array.concat [ fileInvalid; pathInvalid; extras ]
- let input = String charsToTest
-
- let result = replaceInvalidPathChars None None input
-
- result.ToCharArray() |> Array.iter (fun ch -> Assert.Equal(ch, '_'))
- Assert.Equal(input.Length, result.Length)
-
-module SeqTests =
-
- []
- let ``caseInsensitiveContains returns true when exact match exists`` () =
- let input = ["Hello"; "World"; "Test"]
- Assert.True <| Seq.caseInsensitiveContains "Hello" input
- Assert.True <| Seq.caseInsensitiveContains "World" input
- Assert.True <| Seq.caseInsensitiveContains "Test" input
-
- []
- let ``caseInsensitiveContains returns true when exists but case differs`` () =
- let input = ["hello"; "WORLD"; "test"]
- Assert.True <| Seq.caseInsensitiveContains "Hello" input
- Assert.True <| Seq.caseInsensitiveContains "hello" input
- Assert.True <| Seq.caseInsensitiveContains "HELLO" input
- Assert.True <| Seq.caseInsensitiveContains "wOrLd" input
- Assert.True <| Seq.caseInsensitiveContains "tESt" input
- Assert.True <| Seq.caseInsensitiveContains "TEST" input
-
- []
- let ``caseInsensitiveContains returns false when text not in sequence`` () =
- let input = ["Hello"; "World"; "Test"]
- Assert.False <| Seq.caseInsensitiveContains "Missing" input
-
- []
- let ``caseInsensitiveContains works with empty sequence`` () =
- Assert.False <| Seq.caseInsensitiveContains "Any" []
-
- []
- let ``caseInsensitiveContains handles null or empty strings`` () =
- let input = [String.Empty; null; "Test"]
- Assert.True <| Seq.caseInsensitiveContains String.Empty input
- Assert.True <| Seq.caseInsensitiveContains null input
-
- []
- let ``caseInsensitiveContains handles Japanese strings`` () =
- let input = ["関数型プログラミング"; "楽しいぞ"]
- Assert.True <| Seq.caseInsensitiveContains "関数型プログラミング" input
- Assert.False <| Seq.caseInsensitiveContains "いや、楽しくないや" input
-
-module ListTests =
-
- []
- let ``caseInsensitiveContains returns true when exact match exists`` () =
- let input = ["Hello"; "World"; "Test"]
- Assert.True <| List.caseInsensitiveContains "Hello" input
- Assert.True <| List.caseInsensitiveContains "World" input
- Assert.True <| List.caseInsensitiveContains "Test" input
-
- []
- let ``caseInsensitiveContains returns true when exists but case differs`` () =
- let input = ["hello"; "WORLD"; "test"]
- Assert.True <| List.caseInsensitiveContains "Hello" input
- Assert.True <| List.caseInsensitiveContains "hello" input
- Assert.True <| List.caseInsensitiveContains "HELLO" input
- Assert.True <| List.caseInsensitiveContains "wOrLd" input
- Assert.True <| List.caseInsensitiveContains "tESt" input
- Assert.True <| List.caseInsensitiveContains "TEST" input
-
- []
- let ``caseInsensitiveContains returns false when text not in sequence`` () =
- let input = ["Hello"; "World"; "Test"]
- Assert.False <| List.caseInsensitiveContains "Missing" input
-
- []
- let ``caseInsensitiveContains works with empty sequence`` () =
- Assert.False <| List.caseInsensitiveContains "Any" []
-
- []
- let ``caseInsensitiveContains handles null or empty strings`` () =
- let input = [String.Empty; null; "Test"]
- Assert.True <| List.caseInsensitiveContains String.Empty input
- Assert.True <| List.caseInsensitiveContains null input
-
- []
- let ``caseInsensitiveContains handles Japanese strings`` () =
- let input = ["関数型プログラミング"; "楽しいぞ"]
- Assert.True <| List.caseInsensitiveContains "関数型プログラミング" input
- Assert.False <| List.caseInsensitiveContains "いや、楽しくないや" input
-
-module ArrayTests =
-
- module HasMultiple =
-
- []
- let ``hasMultiple returns true for array with more than one element`` () =
- Assert.True <| Array.hasMultiple [| 1; 2; 3 |]
-
- []
- let ``hasMultiple returns false for empty array`` () =
- Assert.False <| Array.hasMultiple [||]
-
- []
- let ``hasMultiple returns false for single-element array`` () =
- Assert.False <| Array.hasMultiple [| 0 |]
-
- []
- let ``hasMultiple works with different types of arrays`` () =
- Assert.True <| Array.hasMultiple [| "hello"; "world" |]
- Assert.True <| Array.hasMultiple [| 1.0; 2.0; 3.0 |]
- Assert.True <| Array.hasMultiple [| false; true; true |]
- Assert.True <| Array.hasMultiple [| Array.sum; Array.length |]
-
- []
- let ``hasMultiple handles large arrays`` () =
- Assert.True <| Array.hasMultiple (Array.init 100 id)
diff --git a/src/CCVTAC.Tests/TagDetectionTests.fs b/src/CCVTAC.Tests/TagDetectionTests.fs
index ed06004..ccf40e1 100644
--- a/src/CCVTAC.Tests/TagDetectionTests.fs
+++ b/src/CCVTAC.Tests/TagDetectionTests.fs
@@ -1,9 +1,9 @@
module TagDetectionTests
-open CCVTAC.Main
open CCVTAC.Main.PostProcessing.Tagging
open CCVTAC.Main.PostProcessing
open CCVTAC.Main.Settings.Settings
+open CCFSharpUtils.Library
open System
open Xunit
From 60863ee47dfdd58e43e842eba17b9e0162b5d65a Mon Sep 17 00:00:00 2001
From: CodeConscious <50596087+codeconscious@users.noreply.github.com>
Date: Tue, 13 Jan 2026 11:43:36 +0900
Subject: [PATCH 05/12] Use pattern matching over func
---
src/CCVTAC.Main/ResultTracker.fs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/CCVTAC.Main/ResultTracker.fs b/src/CCVTAC.Main/ResultTracker.fs
index 2a9794e..ff717b2 100644
--- a/src/CCVTAC.Main/ResultTracker.fs
+++ b/src/CCVTAC.Main/ResultTracker.fs
@@ -34,7 +34,7 @@ type ResultTracker<'a>(printer: Printer) =
| Ok _ ->
successCount <- successCount + 1UL
| Error e ->
- let msg = if e |> List.isNotEmpty then List.head e else String.Empty
+ let msg = match e with [] -> String.Empty | _ -> List.head e
if not (failures.TryAdd(input, msg)) then
failures[input] <- msg
From a5fc5bd81c63750e2cd0027740ac3b5301969464 Mon Sep 17 00:00:00 2001
From: CodeConscious <50596087+codeconscious@users.noreply.github.com>
Date: Wed, 14 Jan 2026 09:35:41 +0900
Subject: [PATCH 06/12] Use List.isNotEmpty
---
src/CCVTAC.Main/PostProcessing/Mover.fs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/CCVTAC.Main/PostProcessing/Mover.fs b/src/CCVTAC.Main/PostProcessing/Mover.fs
index 26a5187..abb3c7c 100644
--- a/src/CCVTAC.Main/PostProcessing/Mover.fs
+++ b/src/CCVTAC.Main/PostProcessing/Mover.fs
@@ -29,7 +29,7 @@ module Mover =
None
else
let playlistImages = images |> List.filter (fun i -> isPlaylistImage i.FullName)
- if not (List.isEmpty playlistImages)
+ if List.isNotEmpty playlistImages
then Some playlistImages[0]
elif audioFileCount > 1 && List.hasOne images
then Some images[0]
From a77181b76d0d9bd9e44433407593721cfa0d6f1b Mon Sep 17 00:00:00 2001
From: CodeConscious <50596087+codeconscious@users.noreply.github.com>
Date: Wed, 14 Jan 2026 10:53:21 +0900
Subject: [PATCH 07/12] Extra: Add playlist description conditionally
---
src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs b/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs
index 087eb8a..1be0291 100644
--- a/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs
+++ b/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs
@@ -54,7 +54,8 @@ module MetadataUtilities =
match v.PlaylistIndex with
| Some index -> if index > 0u then sb.AppendLine $"■ Playlist index: %d{index}" |> ignore
| None -> ()
- sb.AppendLine($"■ Playlist description: %s{String.textOrEmpty c'.Description}") |> ignore
+ if String.hasText c'.Description then
+ sb.AppendLine($"■ Playlist description: %s{c'.Description}") |> ignore
| None -> ()
sb.ToString()
From 2d8b899e28665d52a0b99e3f827e45a919005a98 Mon Sep 17 00:00:00 2001
From: CodeConscious <50596087+codeconscious@users.noreply.github.com>
Date: Wed, 14 Jan 2026 16:16:06 +0900
Subject: [PATCH 08/12] Tweak opens
---
src/CCVTAC.Main/PostProcessing/PostProcessing.fs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/CCVTAC.Main/PostProcessing/PostProcessing.fs b/src/CCVTAC.Main/PostProcessing/PostProcessing.fs
index 3c938e7..414b85d 100644
--- a/src/CCVTAC.Main/PostProcessing/PostProcessing.fs
+++ b/src/CCVTAC.Main/PostProcessing/PostProcessing.fs
@@ -3,14 +3,14 @@ namespace CCVTAC.Main.PostProcessing
open CCVTAC.Main
open CCVTAC.Main.IoUtilities
open CCVTAC.Main.PostProcessing.Tagging
+open CCVTAC.Main.PostProcessing.Tagging.TaggingSet
open CCVTAC.Main.Settings.Settings
open CCFSharpUtils.Library
+open Startwatch.Library
open System.IO
open System.Linq
open System.Text.Json
open System.Text.RegularExpressions
-open Startwatch.Library
-open TaggingSet
module PostProcessor =
From dc00c58d5dd2a2150845f408ead6ac451744040e Mon Sep 17 00:00:00 2001
From: CodeConscious <50596087+codeconscious@users.noreply.github.com>
Date: Wed, 14 Jan 2026 16:20:12 +0900
Subject: [PATCH 09/12] Update readme
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 7fda574..6ba778a 100644
--- a/README.md
+++ b/README.md
@@ -221,4 +221,4 @@ The first incarnation of this application was written in C#. However, after pick
As an experiment, I rewrote this application in OOP-style F#, using LLMs solely for the rough initial conversion (which greatly reduced the overall time and labor necessary at the cost of requiring a *lot* of manual cleanup). Ultimately, I was surprised how much I preferred the F# code over the C#, so I decided to keep this tool in F#.
-Due to this background, the code is not particularly idiomatic F#, but it is perfectly viable in its current blended-style form. That said, I'll probably tweak it over time to gradually to introduce more FP, mainly for practice.
+Due to this background, the code is not particularly idiomatic F#, but it is certainly perfectly viable in its current blended-style form. That said, I'll probably tweak it over time to gradually to introduce more idiomatic F# code.
From 05e50bf7d6793d0d40e3d853f63409cecb6e3b0a Mon Sep 17 00:00:00 2001
From: CodeConscious <50596087+codeconscious@users.noreply.github.com>
Date: Wed, 14 Jan 2026 17:42:47 +0900
Subject: [PATCH 10/12] Update CCFSharpUtils package
---
src/CCVTAC.Main/CCVTAC.Main.fsproj | 2 +-
src/CCVTAC.Tests/CCVTAC.Tests.fsproj | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/CCVTAC.Main/CCVTAC.Main.fsproj b/src/CCVTAC.Main/CCVTAC.Main.fsproj
index 2c32439..b9a83f0 100644
--- a/src/CCVTAC.Main/CCVTAC.Main.fsproj
+++ b/src/CCVTAC.Main/CCVTAC.Main.fsproj
@@ -40,7 +40,7 @@
-
+
diff --git a/src/CCVTAC.Tests/CCVTAC.Tests.fsproj b/src/CCVTAC.Tests/CCVTAC.Tests.fsproj
index bbabcaa..c972faf 100644
--- a/src/CCVTAC.Tests/CCVTAC.Tests.fsproj
+++ b/src/CCVTAC.Tests/CCVTAC.Tests.fsproj
@@ -14,7 +14,7 @@
-
+
From ab1c3bfac6c4b6452caed72bd2473d8fc14e206f Mon Sep 17 00:00:00 2001
From: CodeConscious <50596087+codeconscious@users.noreply.github.com>
Date: Wed, 14 Jan 2026 17:43:02 +0900
Subject: [PATCH 11/12] Change <=0 to <1
---
src/CCVTAC.Main/Printer.fs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/CCVTAC.Main/Printer.fs b/src/CCVTAC.Main/Printer.fs
index 70b6395..65f8a5c 100644
--- a/src/CCVTAC.Main/Printer.fs
+++ b/src/CCVTAC.Main/Printer.fs
@@ -141,7 +141,7 @@ type Printer(showDebug: bool) =
then ()
else
let repeats = int count - 1
- if repeats <= 0
+ if repeats < 1
then AnsiConsole.WriteLine()
else Enumerable.Repeat(String.newLine, repeats) |> String.Concat |> AnsiConsole.WriteLine
From 841d81de4ee6b0292ad54cb3e0f1db067ff8b34c Mon Sep 17 00:00:00 2001
From: CodeConscious <50596087+codeconscious@users.noreply.github.com>
Date: Thu, 15 Jan 2026 09:26:04 +0900
Subject: [PATCH 12/12] Rename func call
---
src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs b/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs
index 1be0291..a75c543 100644
--- a/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs
+++ b/src/CCVTAC.Main/PostProcessing/MetadataUtilities.fs
@@ -43,7 +43,7 @@ module MetadataUtilities =
if v.UploadDate.Length = 8 then
sb.AppendLine $"■ Uploaded: %s{formattedUploadDate v.UploadDate}" |> ignore
- let description = String.textOrFallback "None." v.Description
+ let description = String.textElse "None." v.Description
sb.AppendLine $"■ Video description: %s{description}" |> ignore
match c with