From cfc21957850a6ab63603760fdf46ee7b3974b478 Mon Sep 17 00:00:00 2001 From: jecaro Date: Sat, 8 Aug 2026 22:29:19 +0200 Subject: [PATCH] Add `number-tracks` command --- app/Main.hs | 3 +++ app/Options.hs | 21 +++++++++++++++++++ lib/Commands.hs | 15 ++++++++++++++ tests/Tests/Commands.hs | 45 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/app/Main.hs b/app/Main.hs index 65d8c71..b131891 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -178,6 +178,9 @@ main = do Conduit.mapM_C $ \file -> liftIO $ do track <- AudioTrack.getTags file fixFilePath fixFilePathOptions track + Options.NumberTracks Options.NumberTracksOptions {..} files -> do + allFiles <- ConduitUtils.runConduitWithProgress files Conduit.sinkList + Commands.numberTracks nuReverse allFiles Options.Search options -> do case options of Options.SeSearchMany (Options.SearchMany {..}) -> diff --git a/app/Options.hs b/app/Options.hs index ce56eb9..d9bffec 100644 --- a/app/Options.hs +++ b/app/Options.hs @@ -3,6 +3,7 @@ module Options Command (..), Files (..), FixFilePathsOptions (..), + NumberTracksOptions (..), SearchMany (..), SearchManySource (..), SearchOne (..), @@ -75,10 +76,16 @@ data SetTagsOptions | SetTagsFromId UUID.UUID deriving (Show) +newtype NumberTracksOptions = NumberTracksOptions + { nuReverse :: Bool + } + deriving (Show) + data Command = CreateConfig | GetTags Files | SetTags SetTagsOptions Files + | NumberTracks NumberTracksOptions Files | Edit Files | Check CheckOptions Files | FixFilePaths FixFilePathsOptions Files @@ -122,6 +129,14 @@ checksP = <*> optional albumSameTagsP <*> artistSameGenreP +numberTracksOptionsP :: Options.Parser NumberTracksOptions +numberTracksOptionsP = + NumberTracksOptions + <$> Options.switch + ( Options.long "reverse" + <> Options.help "Sort in reverse alphabetical order" + ) + fixFilePathsOptionsP :: Options.Parser FixFilePathsOptions fixFilePathsOptionsP = FixFilePathsOptions @@ -470,6 +485,12 @@ optionsP = (SetTags <$> setTagsOptionsP <*> filesP) (Options.progDesc "Set tags") ) + <> Options.command + "number-tracks" + ( Options.info + (NumberTracks <$> numberTracksOptionsP <*> filesP) + (Options.progDesc "Set track numbers based on filename sort order") + ) <> Options.command "edit" ( Options.info diff --git a/lib/Commands.hs b/lib/Commands.hs index cdf6944..b935b72 100644 --- a/lib/Commands.hs +++ b/lib/Commands.hs @@ -1,6 +1,7 @@ module Commands ( getTags, setTags, + numberTracks, checkTrack, checkDisc, checkAlbum, @@ -21,6 +22,7 @@ import Check.Artist qualified as Artist import Check.Disc qualified as Disc import Check.Track qualified as Track import Commands.FileSystem qualified as FileSystem +import Data.List qualified as List import Model.Album qualified as Album import Model.Artist qualified as Artist import Model.AudioTrack qualified as AudioTrack @@ -58,6 +60,19 @@ setTags options filename = Nothing (SetTags.setter options) +numberTracks :: (MonadIO m) => Bool -> [Path.Path Path.Abs Path.File] -> m () +numberTracks isReverse files = + traverse_ (uncurry setTrackNumber) $ zip [1 ..] ordered + where + sorted = List.sort files + ordered = if isReverse then reverse sorted else sorted + setTrackNumber n filename = + HTagLib.setTags + (Path.toFilePath filename) + Nothing + $ HTagLib.trackNumberSetter + $ HTagLib.mkTrackNumber n + countTrues :: [Bool] -> Int countTrues = length . filter id diff --git a/tests/Tests/Commands.hs b/tests/Tests/Commands.hs index af9508d..91d8a36 100644 --- a/tests/Tests/Commands.hs +++ b/tests/Tests/Commands.hs @@ -11,6 +11,7 @@ import Path (reldir, relfile, ()) import Path qualified import Path.IO qualified as Path import Relude.Unsafe qualified as Unsafe +import Sound.HTagLib qualified as HTagLib import System.IO qualified as System import Test.Hspec.Expectations (shouldBe) import Test.Tasty qualified as Tasty @@ -19,7 +20,7 @@ import Tests.Common qualified as Common import UnliftIO.Exception qualified as Exception test :: Tasty.TestTree -test = Tasty.testGroup "Commands" [testFixFilePaths] +test = Tasty.testGroup "Commands" [testFixFilePaths, testNumberTracks] testFixFilePaths :: Tasty.TestTree testFixFilePaths = @@ -118,6 +119,48 @@ testFixFilePaths = null coverFiles `shouldBe` False ] +testNumberTracks :: Tasty.TestTree +testNumberTracks = + Tasty.testGroup + "numberTracks" + [ Tasty.testCase "ascending order" $ + withThreeFiles $ \(a, b, c) -> do + Commands.numberTracks False [c, a, b] + getTrack a >>= (`shouldBe` HTagLib.mkTrackNumber 1) + getTrack b >>= (`shouldBe` HTagLib.mkTrackNumber 2) + getTrack c >>= (`shouldBe` HTagLib.mkTrackNumber 3), + Tasty.testCase "descending order" $ + withThreeFiles $ \(a, b, c) -> do + Commands.numberTracks True [c, a, b] + getTrack a >>= (`shouldBe` HTagLib.mkTrackNumber 3) + getTrack b >>= (`shouldBe` HTagLib.mkTrackNumber 2) + getTrack c >>= (`shouldBe` HTagLib.mkTrackNumber 1) + ] + +withThreeFiles :: + ( ( Path.Path Path.Abs Path.File, + Path.Path Path.Abs Path.File, + Path.Path Path.Abs Path.File + ) -> + IO () + ) -> + Tasty.Assertion +withThreeFiles action = + Path.withSystemTempDir "htagcli" $ \dir -> do + let mkFile name = do + file <- Path.parseRelFile name + let absFile = dir file + Path.copyFile [relfile|./data/sample.mp3|] absFile + pure absFile + a <- mkFile "a.mp3" + b <- mkFile "b.mp3" + c <- mkFile "c.mp3" + action (a, b, c) + +getTrack :: + (MonadIO m) => Path.Path Path.Abs Path.File -> m (Maybe HTagLib.TrackNumber) +getTrack file = AudioTrack.atTrack <$> AudioTrack.getTags file + testTargetAlreadyExists :: Bool -> Tasty.Assertion testTargetAlreadyExists dryRun = Common.withOneTrackFile $ \dir file -> do