Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
2cc15f6
Update packages
codeconscious Mar 23, 2026
d936a64
Remove outdated operator
codeconscious Mar 25, 2026
704d076
Remove unused Shared.IO opens
codeconscious Mar 25, 2026
4205cfe
Update packages
codeconscious Mar 26, 2026
17e62eb
Place brackets on previous lines
codeconscious Mar 27, 2026
77f726e
Use library funcs; remove Shared.IO module
codeconscious Mar 27, 2026
69eefb9
Make timestamp format constant
codeconscious Mar 27, 2026
91f49e3
Use package Result operators
codeconscious Mar 27, 2026
52f3022
Use library copy func
codeconscious Mar 27, 2026
c1a124f
Move comment
codeconscious Mar 27, 2026
645b89d
Rename errors
codeconscious Mar 27, 2026
ca33f40
Remove now-redundant LibraryAnalysis.IO file
codeconscious Mar 28, 2026
ba1e9fe
Remove several Cacher.IO module funcs
codeconscious Mar 28, 2026
316b2b5
Remove DuplicateFinder.IO.readfile
codeconscious Mar 28, 2026
650efea
Remove superfluous GenreExtractor.IO funcs
codeconscious Mar 28, 2026
5c39506
Update package and 1 func name
codeconscious Mar 28, 2026
de70dee
Give error types unique names
codeconscious Mar 28, 2026
cc9d172
Split composed func
codeconscious Mar 28, 2026
3249586
Add and use flip func
codeconscious Mar 29, 2026
c6d1c98
Revert "Add and use flip func"
codeconscious Mar 29, 2026
21f377f
Update package
codeconscious Mar 29, 2026
5dfd007
Update package
codeconscious Mar 30, 2026
88d1f74
Pass FileInfo when writing files; etc.
codeconscious Mar 30, 2026
97109b0
Code layout
codeconscious Mar 30, 2026
b639f94
Add longest-filename analysis
codeconscious Mar 31, 2026
0df7113
Escape more text
codeconscious Mar 31, 2026
0b5ef15
Relocate comment
codeconscious Mar 31, 2026
36852a0
Rename binding
codeconscious Mar 31, 2026
fb309d2
Reorganize data layout
codeconscious Mar 31, 2026
ed04bd2
Show row separators
codeconscious Mar 31, 2026
9817690
Use String.nl
codeconscious Mar 31, 2026
0869847
Simplify logic
codeconscious Mar 31, 2026
4f94eb5
Code layout tweaks
codeconscious Mar 31, 2026
14f74c3
Add filename lengths
codeconscious Apr 1, 2026
db4f130
Merge branch 'main' into add-filename-analysis
codeconscious Apr 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/AudioTagTools.DuplicateFinder/Tags.fs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ let printDuplicates (groupedTracks: MultipleLibraryTags array option) =
printf $" • {artist}"
printfGray " — "
printf $"{title}"
printfGray $" [{duration} {extNoPeriod} {bitRate} {fileSize}]{String.newLine}"
printfGray $" [{duration} {extNoPeriod} {bitRate} {fileSize}]{String.nl}"

let printHeader () =
groupTracks
Expand All @@ -119,4 +119,3 @@ let printDuplicates (groupedTracks: MultipleLibraryTags array option) =
match groupedTracks with
| None -> printfn "No duplicates found."
| Some group -> group |> Array.iteri printGroup

9 changes: 9 additions & 0 deletions src/AudioTagTools.LibraryAnalysis/Analysis.fs
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,12 @@ let topQualityData count tags =
String.formatInt data.SampleRate
String.formatInt count |])

let longestFileNames count tags =
tags
|> Array.map (fun t -> t.FileName.Length, t)
|> Array.sortByDescending fst
|> Array.take count
|> Array.map (fun (count, t) ->
[| $"""{mainArtists "; " t}{String.nl}↪︎ {t.Title}"""
t.FileName
String.formatInt count |])
21 changes: 20 additions & 1 deletion src/AudioTagTools.LibraryAnalysis/Library.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ open Errors
open Shared
open Shared.TagLibrary
open CCFSharpUtils.Library
open FsToolkit.ErrorHandling
open Spectre.Console
open FsToolkit.ErrorHandling
open FsToolkit.ErrorHandling.Operator.Result

type QualityData =
Expand All @@ -29,76 +29,95 @@ let private run (args: string array) : Result<unit, AnalysisError> =
[| "Average file size"; String.formatBytes <| averageFileSize tags |]
[| "Album art percentage"; $"%s{albumArtPercentage tags}" |] |]
ColumnAlignments = [Justify.Left; Justify.Right]
ShowRowSeparators = false
}

printTable {
Title = Some "Top Artists"
Headers = Some ["Artist"; "Count"; "Ratio"]
Rows = topArtists 30 tags
ColumnAlignments = [Justify.Left; Justify.Right; Justify.Right]
ShowRowSeparators = false
}

printTable {
Title = Some "Top Album Names"
Headers = Some ["Album"; "Count"; "Ratio"]
Rows = topAlbums 30 tags
ColumnAlignments = [Justify.Left; Justify.Right; Justify.Right]
ShowRowSeparators = false
}

printTable {
Title = Some "Top Titles"
Headers = Some ["Title"; "Count"]
Rows = topTitles 30 tags
ColumnAlignments = [Justify.Left; Justify.Right]
ShowRowSeparators = false
}

printTable {
Title = Some "Top Genres"
Headers = Some ["Genre"; "Count"; "Ratio"]
Rows = topGenres 30 tags
ColumnAlignments = [Justify.Left; Justify.Right; Justify.Right]
ShowRowSeparators = false
}

printTable {
Title = Some "Artists With The Most Genres"
Headers = Some ["Artist"; "Count"; "Genres"]
Rows = artistsWithMostGenres 20 tags
ColumnAlignments = [Justify.Left; Justify.Right; Justify.Left]
ShowRowSeparators = false
}

printTable {
Title = Some "Largest Files"
Headers = Some ["File Size"; "Artist & Title"]
Rows = largestFiles 20 tags |> Array.map Array.rev
ColumnAlignments = [Justify.Right; Justify.Left]
ShowRowSeparators = false
}

printTable {
Title = Some "Top Formats"
Headers = Some ["Extension"; "Count"]
Rows = topFormats 10 tags
ColumnAlignments = [Justify.Left; Justify.Right]
ShowRowSeparators = false
}

printTable {
Title = Some "Top Bitrates"
Headers = Some ["Bitrate"; "Count"]
Rows = topBitRates 10 tags
ColumnAlignments = [Justify.Right; Justify.Right]
ShowRowSeparators = false
}

printTable {
Title = Some "Top Sample Rates"
Headers = Some ["Sample Rate"; "Count"]
Rows = topSampleRates 10 tags
ColumnAlignments = [Justify.Right; Justify.Right]
ShowRowSeparators = false
}

printTable {
Title = Some "Top Quality Combinations"
Headers = Some ["Format"; "Bit Rate"; "Sample Rate"; "Count"]
Rows = topQualityData 10 tags
ColumnAlignments = [Justify.Left; Justify.Right; Justify.Right; Justify.Right]
ShowRowSeparators = false
}

printTable {
Title = Some "Longest File Names"
Headers = Some ["Artist & Title"; "Filename"; "Length"]
Rows = longestFileNames 5 tags
ColumnAlignments = [Justify.Left; Justify.Left; Justify.Right]
ShowRowSeparators = true
}
}

Expand Down
24 changes: 14 additions & 10 deletions src/AudioTagTools.Shared/Printing.fs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// Be careful with color because we don't know the user's terminal color scheme,
// so it's easy to unintentionally output invisible or hard-to-read text.
[<AutoOpen>]
module Shared.Printing

Expand All @@ -11,38 +9,44 @@ type TableData =
{ Title: string option
Headers: string list option
Rows: string array array
ColumnAlignments: Justify list }
ColumnAlignments: Justify list
ShowRowSeparators: bool }

// Be careful with color because we don't know the user's terminal color scheme,
// so it's easy to unintentionally output invisible or hard-to-read text.
let printfColor color msg =
Console.ForegroundColor <- color
printf $"%s{msg}"
printf $"%s{Markup.Escape msg}"
Console.ResetColor()

let printfnColor color msg =
printfColor color $"{msg}{String.newLine}"
printfColor color $"{msg}{String.nl}"

let printTable tableData =
let table = Table()
table.Border <- TableBorder.SimpleHeavy

if tableData.ShowRowSeparators then
table.ShowRowSeparators() |> ignore

match tableData.Headers with
| Some header ->
header |> List.iter (fun h -> table.AddColumn h |> ignore)
header
|> List.iter (fun h -> h |> Markup.Escape |> table.AddColumn |> ignore)
| None ->
tableData.Rows[0] |> Array.iter (fun _ -> table.AddColumn String.Empty |> ignore)
table.HideHeaders() |> ignore

tableData.ColumnAlignments
|> List.iteri (fun i alignment -> table.Columns[i].Alignment(alignment) |> ignore)
|> List.iteri (fun i alignment -> table.Columns[i].Alignment alignment |> ignore)

tableData.Rows
|> Array.iter (fun row -> table.AddRow row |> ignore)
|> Array.iter (fun rowItems -> rowItems |> Array.map Markup.Escape |> table.AddRow |> ignore)

match tableData.Title with
| Some tableTitle ->
let panel = Panel table
panel.Header <- PanelHeader $"| {tableTitle} |"
panel.Header <- PanelHeader $"| {Markup.Escape tableTitle} |"
AnsiConsole.Write panel
| None ->
AnsiConsole.Write table

Loading