Skip to content

Instantly share code, notes, and snippets.

@Venipa
Last active May 6, 2026 20:37
Show Gist options
  • Select an option

  • Save Venipa/0b02c79923014727859e23c06fe27c81 to your computer and use it in GitHub Desktop.

Select an option

Save Venipa/0b02c79923014727859e23c06fe27c81 to your computer and use it in GitHub Desktop.
script for organizing music files into [album name]
<#
MIT License
Copyright (c) 2026
Permission is hereby granted, free of charge, to any person obtaining a copy
of this script and associated documentation files (the "script"), to deal
in the script without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the script, and to permit persons to whom the script is
furnished to do so.
#>
function Get-PreferredBinaryPath {
param(
[Parameter(Mandatory = $true)]
[string]$BinaryName
)
$chocoInstall = $env:ChocolateyInstall
if (-not [string]::IsNullOrWhiteSpace($chocoInstall)) {
$candidatePath = Join-Path $chocoInstall "bin\$BinaryName.exe"
if (Test-Path -LiteralPath $candidatePath) {
return $candidatePath
}
}
return $BinaryName
}
function Exit-WithAnyKey {
Write-Host "Press any key to exit..."
[void][System.Console]::ReadKey($true)
exit
}
if ($args.Count -eq 0) {
Write-Host "No files provided. Exiting." -ForegroundColor Red
exit 1
}
$NO_ALBUM_FOLDER = "No Album"
$ffprobePath = Get-PreferredBinaryPath -BinaryName "ffprobe"
function ConvertTo-SafeFolderName {
param(
[Parameter(Mandatory = $true)]
[string]$Name
)
$safeName = $Name
foreach ($invalidChar in [System.IO.Path]::GetInvalidFileNameChars()) {
$safeName = $safeName.Replace($invalidChar, "_")
}
return $safeName.Trim()
}
function Get-MusicTagsFromMetadata {
param(
[Parameter(Mandatory = $true)]
[string]$FilePath,
[Parameter(Mandatory = $true)]
[string]$ProbePath
)
$tagMap = @{
artist = ""
album = ""
title = ""
}
try {
$tagOutput = & $ProbePath -v error -show_entries format_tags=artist,album,title -of default=noprint_wrappers=1:nokey=0 $FilePath
foreach ($line in $tagOutput) {
if ($line -match "^(?:TAG:)?(?<key>[^=]+)=(?<value>.*)$") {
$key = $matches["key"].Trim().ToLowerInvariant()
$value = $matches["value"].Trim()
if ($tagMap.ContainsKey($key) -and -not [string]::IsNullOrWhiteSpace($value)) {
$tagMap[$key] = $value
}
}
}
} catch {
Write-Warning "Failed reading metadata for '$FilePath'."
}
return $tagMap
}
function Get-UniqueDestinationPath {
param(
[Parameter(Mandatory = $true)]
[string]$DirectoryPath,
[Parameter(Mandatory = $true)]
[string]$FileName
)
$candidatePath = Join-Path $DirectoryPath $FileName
if (-not (Test-Path -LiteralPath $candidatePath)) {
return $candidatePath
}
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($FileName)
$extension = [System.IO.Path]::GetExtension($FileName)
$index = 1
do {
$candidateName = "$baseName ($index)$extension"
$candidatePath = Join-Path $DirectoryPath $candidateName
$index++
} while (Test-Path -LiteralPath $candidatePath)
return $candidatePath
}
foreach ($filepath in $args) {
if (-not (Test-Path -LiteralPath $filepath)) {
Write-Warning "File not found: $filepath"
continue
}
$file = Get-Item -LiteralPath $filepath
if ($file.PSIsContainer) {
Write-Warning "Skipping directory: $($file.FullName)"
continue
}
$tagMap = Get-MusicTagsFromMetadata -FilePath $file.FullName -ProbePath $ffprobePath
$rawAlbumName = [string]$tagMap["album"]
$safeAlbumName = ConvertTo-SafeFolderName -Name $rawAlbumName
if ([string]::IsNullOrWhiteSpace($safeAlbumName)) {
$safeAlbumName = $NO_ALBUM_FOLDER
}
$destinationDirectory = Join-Path $file.DirectoryName $safeAlbumName
if (-not (Test-Path -LiteralPath $destinationDirectory)) {
[void](New-Item -ItemType Directory -Path $destinationDirectory)
}
$destinationPath = Get-UniqueDestinationPath -DirectoryPath $destinationDirectory -FileName $file.Name
if ($file.FullName -eq $destinationPath) {
Write-Host "Already organized: $($file.FullName)" -ForegroundColor Yellow
continue
}
Move-Item -LiteralPath $file.FullName -Destination $destinationPath
Write-Host "Moved '$($file.Name)' -> '$safeAlbumName'" -ForegroundColor Green
}
Exit-WithAnyKey
@Venipa
Copy link
Copy Markdown
Author

Venipa commented May 6, 2026

usage: organize-albums.c.ps1 <file1> <file2> <...files>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment