Music Video Tagging

I've tried quite a few bits of music tagging software that utilise song lookup services, and many of these are great ... for audio files like .mp3s.

They generally suck for music videos, and when you have a very large and diverse collection of music videos like me, that these taggers suck at it sucks big time.

So I built my own in PowerShell by automating Google searches and utilising the handy taglib-sharp. Let's face it, Google do know a lot of stuff, after all.

If you've not come across taglib-sharp you can find it at https://github.com/mono/taglib-sharp/. Some rather simple compilation with Visual Studio will result in (among other things) the file taglib-sharp.dll, which I use in the PowerShell below. Drop the .dll in the same directory as the script.

The results are not perfect, with the metadata of some music videos not able to be easily looked up, but I think on the whole it's pretty bloody good. For me, this meant still manually touching some files, but it was much better than needing to do thousands.

Script is designed to do just genre and release year. I get title and artist from the file name and bulk tag them using the very good (and free) Mp3tag (https://www.mp3tag.de/en/) before using this script.

Enjoy, and as always be careful: take a backup because tagging does modify files (and 'last modified' time).


$ie = New-Object -com InternetExplorer.Application -ErrorAction Stop
$ie.Visible = $false

$path = "Z:\My Videos"

# Tracks are processed alphabetically. For script re-start after interruption...
# $restart_from = "Catherine Britt - Too Far Gone [video].mp4"

Get-ChildItem $path | Sort-Object |
Foreach-Object {
    $file = $_.Name
    
    if ($file -ge $restart_from) {
        # Load up the song file and its metadata
        $song = [TagLib.File]::Create((Resolve-Path -LiteralPath (Join-Path $path $file)))

        $artist_title = "$($song.Tag.Artists[0]) - $($song.Tag.Title)"

        if ( $song.Tag.Artists[0] -and $song.Tag.Title ) {
            $url = "https://www.google.com/search?q=$($song.Tag.Artists[0].Replace(" ","+"))+$($song.Tag.Title.Replace(" ","+"))+release+and+genre"

            $ie.Navigate($url)
            while ($ie.Busy -eq $true -Or $ie.ReadyState -ne 4) { Start-Sleep -Milliseconds 10 }
            $doc = $ie.Document

            $found_released = @($doc.IHTMLDocument2_body.InnerText.Split([Environment]::NewLine) | ForEach-Object {if ($_ -cmatch "Released.{0,1}:.{0,20}(\d\d\d\d)") {$Matches[1]}})
            $found_released_date = @($doc.IHTMLDocument2_body.InnerText.Split([Environment]::NewLine) | ForEach-Object {if ($_ -cmatch "Release date: \d{1,2} [A-Z,a-z]+ (\d\d\d\d)") {$Matches[1]}})
            $found_genre = @($doc.IHTMLDocument2_body.InnerText.Split([Environment]::NewLine) | ForEach-Object {if ($_ -cmatch "Genres{0,1}.{0,1}:.{0,1}(.+)") {$Matches[1]}})

            # Try and detect whether the song is a modern House track that night normally be considered 'Dance'
            $found_beatport = @($doc.IHTMLDocument2_body.InnerText.Split([Environment]::NewLine) | ForEach-Object {if ($_ -cmatch "Length(.*)Released(.*)BPM(.*)Genre (.+);(.*)") {$matches[4]}})
            $house = if ($found_beatport) { $found_beatport[0].ToLower() -like "*house*" } else { $false }

            $released = $found_released[0]
            $released_date = $found_released_date[0]

            $genre = $found_genre[0] -replace ",","/" -split "/"

            if ($house -and (-not ($genre -contains "House"))) {
                if ($genre -contains "Dance") { $genre = $genre -replace "Dance","House" } else { $genre += "House" }
            }
            if ($genre -contains "Country music") { $genre = $genre -replace "Country music","Country" }
            if ($genre -contains "Electro-Disco") { $genre = $genre -replace "Electro-Disco","Electronic" }
            if ($genre -contains "Dance-Pop") { $genre = $genre -replace "Dance-Pop","Dance" }
            for($i = 0; $i -lt $genre.Count; $i++) {
                if ($genre[$i].Contains(".")) { $genre[$i] = $genre[$i].Substring(0, $genre[$i].IndexOf(".")) }
                if ($genre[$i].Contains(";")) { $genre[$i] = $genre[$i].Substring(0, $genre[$i].IndexOf(";")) }
                if ($genre[$i].Contains(" · Add to Wishlist")) { $genre[$i] = $genre[$i].Substring(0, $genre[$i].IndexOf(" · Add to Wishlist")) }
                $genre[$i] = $genre[$i].Trim()
            }
            $genre = $genre.Where({ -not [string]::IsNullOrWhiteSpace($_) })

            if ($released -match "(19|20)[0-9][0-9]") {
                $year = $released -as [int]
            } elseif ($released_date -match "(19|20)[0-9][0-9]") {
                $year = $released_date -as [int]
            } else { $year = 0 }

            Write-Host "$($artist_title.PadRight(80))$($song.Tag.Year)`t$($song.Tag.Genres)`t|`t$($year)`t$($genre)"

            $modify = $false

            if (($song.Tag.Year -ne $year) -and ($year -gt 0)) {
                $modify = $true
                $song.Tag.Year = $year
                "Will update year"
            }

            if ((-not ($song.Tag.Genres)) -and ($genre)) {
                $modify = $true
                $song.Tag.Genres = $genre
                "Will update genre"
            }

            # Save the metadata 
            if ($modify) { $song.Save() }
        }
    }
}

$ie.Quit()
[void][Runtime.Interopservices.Marshal]::ReleaseComObject($ie)