Native Instruments Kontakt Snapshots Helper

Post your working scripts, libraries and tools.
bmcclure
Posts: 31
Joined: 29 May 2018, 22:11
Location: West Virginia, USA
Contact:

Native Instruments Kontakt Snapshots Helper

Post by bmcclure » 04 Feb 2023, 22:01

I got tired of manually managing my Kontakt instrument snapshots and decided to automate a process that would be more maintainable long-term, so I wrote a script to scan all of my Kontakt library directories and create symlinks to the factory snapshots of each one.

One limitation of the script is that it relies on each Kontakt library having an "Instruments" folder in order to know what the library's main directory is, so in my case this required re-organizing some of my third-party libraries and batch-resaving them with Kontakt.

The script uses the "ln" command line tool. It can be installed via chocolatey with "choco install ln" or via winget with "winget install HermannSchinagl.ln".

How it works:
  • The script loops through the folders defined in "libraryDirs" recursively, skipping over folder names contained in "excludeFolders", looking for Kontakt library folders
  • For each Kontakt library folder, it looks for a file containing an "nicnt" extension and skips over that library if present (Kontakt manages those snapshots automatically)
  • If there is no nicnt file, the script checks for a Snapshots folder, and attempts to match up the snapshots with any .nki instruments within the library
  • The script then creates symlinks for each instrument snapshots folder at the location %A_MyDocuments%\Native Instruments\User Content\Kontakt\%InstrumentName%\Factory
The script is idempotent, each time you add a new library you can re-run the script and it will only add any missing symlinks. If you move existing libraries, you will need to delete the symlink manually before the script will re-add it.

This directory structure with the "Factory" symlink allows you to store your user-level snapshots outside of the "Factory" directory so that they are stored within My Documents and not within each library folder.

This was thrown together fairly quickly and could use improvements, but I've found it extremely useful already and thought I would share. I'll add version details and a changelog if/when I update this post.

The "libraryDirs" and "excludeFolders" variables should be updated for your setup, and the rest should probably work as-is.

Code: Select all

libraryDirs := [
    "C:\Music\Libraries",
    "D:\Music\Libraries",
    "E:\Music\Libraries",
]
excludeFolders := [
    "IK Multimedia",
    "MNTRA",
    "Native Instruments User Content",
    "Reaktor User Library",
]
includeExts := [
    "nkc",
    "nki",
    "nkm",
    "nkr",
]
excludeExts := [
    "nicnt",
]
snapshotFolders := [
    "Snapshots",
    "_Snapshots"
]
factoryFolders := [
    "Factory",
    "_Factory"
]
snapshotPattern := "*.nksn"
snapshotsDir := A_MyDocuments "\Native Instruments\User Content\Kontakt"

installed := 0

for , libraryDir in libraryDirs {
    installed += InstallChildLibrarySnapshots(libraryDir)
}

MsgBox("Installed " installed " snapshot folders.")

InstallChildLibrarySnapshots(parentDir) {
    global excludeFolders, excludeExts

    innerInstalled := 0

    loop files, parentDir "\*", "D" {
        for , excludeFolder in excludeFolders {
            if (A_LoopFileName == excludeFolder) {
                exclude := true
                continue 2
            }
        }

        if (IsLibrary(A_LoopFileFullPath)) {
            exclude := false

            loop files, A_LoopFileFullPath "\*", "F" {
                for , excludeExt in excludeExts {
                    if (A_LoopFileExt == excludeExt) {
                        exclude := true
                        break 2
                    }
                }
            }

            if (!exclude) {
                innerInstalled += InstallSnapshotsFromLibrary(A_LoopFileFullPath, A_LoopFileName)
            }
        } else {
            innerInstalled += InstallChildLibrarySnapshots(A_LoopFileFullPath)
        }
    }

    return innerInstalled
}

IsLibrary(libraryFolder) {
    isLibrary := false

    if (!isLibrary && DirExist(libraryFolder "\Instruments")) {
        isLibrary := DirHasLibraryFiles(libraryFolder "\Instruments")
    }

    return isLibrary
}

DirHasLibraryFiles(dir) {
    global includeExts

    hasLibraryFiles := false

    loop files, dir "\*", "F" {
        for , includeExt in includeExts {
            if (A_LoopFileExt == includeExt) {
                hasLibraryFiles := true
                break 2
            }
        }
    }

    return hasLibraryFiles
}

IsFactoryFolder(dir) {
    global factoryFolders

    SplitPath(dir, &dirName)

    isFactoryFolder := false

    for , factoryFolder in factoryFolders {
        if (dirName == factoryFolder) {
            isFactoryFolder := true
            break
        }
    }

    return isFactoryFolder
}

InstallSnapshotsFromLibrary(libraryFolder, libraryName) {
    global snapshotsDir

    librarySnapshots := CollectSnapshotsLinks(libraryFolder, libraryName)
    innerInstalled := 0

    for instrumentName, factoryDir in librarySnapshots {
        instrumentDir := snapshotsDir "\" instrumentName
        instrumentLinks := Map()

        if (FileExist(instrumentDir) && !DirExist(instrumentDir)) {
            MsgBox(libraryName ": File '" instrumentDir "' already exists, but is not a directory. Skipping.")
            continue
        }

        factoryFolderName := "Factory"

        if (IsSnapshotFolder(factoryDir)) {
            SplitPath(factoryDir, &dirName)

            if (IsFactoryFolder(factoryDir)) {
                factoryFolderName := dirName
            }

            instrumentLinks[factoryFolderName] := factoryDir
        }

        for linkName, linkPath in instrumentLinks {
            link := instrumentDir "\" linkName

            if (!FileExist(linkPath)) {
                MsgBox(libraryName ": Dir '" linkPath "' does not exist. Skipping.")
                continue
            }

            if (!FileExist(link)) {
                msg := "Install " linkName "?"
                msg .= "`n`nLibrary: " libraryFolder
                msg .= "`nSource: " SubStr(linkPath, StrLen(libraryFolder) + 2)
                msg .= "`nDestination: " instrumentName "\" linkName

                result := MsgBox(msg, libraryName, "YesNo")

                if (result == "Yes") {
                    if (!FileExist(instrumentDir)) {
                        DirCreate(instrumentDir)
                    }

                    RunWait("ln --symbolic `"" linkPath "`" `"" link "`"",, "Hide")
                    innerInstalled += 1
                }
            }
        }
    }

    return innerInstalled
}

CollectInstrumentNames(libraryFolder) {
    global instrumentExts

    instrumentNames := []

    loop files, libraryFolder "\Instruments\*.nki", "FR" {
        instrumentName := SubStr(A_LoopFileName, 1, -(StrLen(A_LoopFileExt) + 1))

        exists := false

        for , existingName in instrumentNames {
            if (existingName == instrumentName) {
                exists := true
                break
            }
        }

        if (!exists) {
            instrumentNames.Push(instrumentName)
        }
    }

    return instrumentNames
}

IsInstrumentName(libraryDir, name) {
    instrumentNames := CollectInstrumentNames(libraryDir)

    exists := false

    for , instrumentName in instrumentNames {
        if (instrumentName == name) {
            exists := true
            break
        }
    }

    return exists
}

GetMainInstrument(libraryDir, libraryName) {
    if (IsInstrumentName(libraryDir, libraryName)) {
        return libraryName
    }

    instrumentNames := CollectInstrumentNames(libraryDir)

    if (instrumentNames.Length == 1) {
        return instrumentNames[1]
    }

    return ""
}

IsSnapshotFolder(folder) {
    global snapshotPattern

    isSnapshotFolder := false

    loop files, folder "\" snapshotPattern, "FR" {
        isSnapshotFolder := true
        break
    }

    return isSnapshotFolder
}

CollectSnapshotsLinks(libraryFolder, libraryName) {
    global snapshotFolders, snapshotPattern, snapshotsMap, snapshotsDir

    snapshotsPath := ""

    librarySnapshots := Map()
    librarySnapshots.CaseSense := false
    mainInstrument := GetMainInstrument(libraryFolder, libraryName)

    for , snapshotFolderName in snapshotFolders {
        loop files, libraryFolder "\" snapshotFolderName, "DR" {
            if (IsSnapshotFolder(A_LoopFileFullPath)) {
                snapshotFolder := A_LoopFileFullPath
                nonInstrumentDirs := []
                hasInstrumentDirs := false

                loop files, snapshotFolder "\*", "D" {
                    if (IsInstrumentName(libraryFolder, A_LoopFileName) && IsSnapshotFolder(A_LoopFileFullPath)) {
                        hasInstrumentDirs := true
                        librarySnapshots[A_LoopFileName] := A_LoopFileFullPath
                    } else {
                        nonInstrumentDirs.Push(A_LoopFileFullPath)
                    }
                }

                if (nonInstrumentDirs.Length) {
                    if (hasInstrumentDirs) {
                        MsgBox("Found mixed content in snapshot dir '" snapshotFolder "'.")
                    } else if (mainInstrument && !librarySnapshots.Has(mainInstrument)) {
                        librarySnapshots[mainInstrument] := snapshotFolder
                    }
                }
            }
        }
    }

    return librarySnapshots
}

Return to “Scripts and Functions (v2)”