Skip to content

Powershell Movie Module

Movies module.

Keep in mind the below functions are intended to be working in a powershell module.

That means they might occasionally refer to their path as a module and might not work if copy pasted/run from a standalone script.

The below functions are commented inline.

One of the more interesting functions is "Get-MovieClassification" which connects to the movie db, the tv db and docuwiki.net in order to try and find the classification (documentary, western, etc) of a movie. It partially relies on xml objects from APIs and good old webscraping.

The function can be copied from here and is also available on Github

#
# This is a powershell module for managing movies and TV shows. To use it, you'll have to save it as a .psm1 file in your module folder.
#
$RedLine = $Null; $WindowWidth =(Get-Host).UI.RawUI.WindowSize.Width; $i = 1; do {$RedLine += "-"; $i++}while ($i -le $WindowWidth)
write-host "Loading Movies" -fore Yellow
write-host "get-help Convert-HandbrakeCommand -full" -fore cyan
write-host "get-help ConvertTo-x265 -full" -fore cyan
write-host "get-help Get-MovieClassification -full" -fore cyan
write-host "get-help Merge-Movies -full" -fore cyan
write-host "get-help New-Playlist -full" -fore cyan
write-host "get-help Rename-Movies -full" -fore cyan
write-host "get-help Rename-TVSeasons -full" -fore cyan
write-host "Get-MediaInfo -MovieFile `$File [-verbose]" -fore cyan
Write-host $RedLine -fore yellow -NoNewline; Write-host ""

#============================================================================================
# New-Playlist
#============================================================================================

function New-Playlist
{
<#

.SYNOPSIS
This commandlet makes a new M3U8 playlist, that can be used by programs that support them,
Like: Winamp, VLC and SMPlayer

.DESCRIPTION
Creates a new playlist based on a provided SearchString. The SearchString can contain wild cards:

* matches any set of characters.
? matches a single character.
\w matches any word character, meaning letters and numbers.
\s matches any white space character, such as tabs, spaces, and so forth.
\d matches any digit character.

\W (capital W) any non-word character
\S any non-white space.
\D any non digit. 

.EXAMPLE

New-Playlist (get-childitem d:\movies | Where-Object {($_.Name -match "Dora") -and ($_.Name -match "Dutch")} | foreach-Object {$_.FullName}) -verbose

.EXAMPLE
New-Playlist (get-childitem \\Uchuujin-san\movies | Where-Object {(($_.Name -match "Dora") -or `
            ($_.Name -match "Diego")) -and ($_.Name -match "Dutch")} | foreach-Object `
            {$_.FullName}) -M3U8File "DoraDiego" -verbose
.EXAMPLE
New-Playlist (get-childitem \\Uchuujin-san\movies | Where-Object {($_.Name -match "- Dutch")} | foreach-Object `
            {$_.FullName}) -M3U8File "Dutch" -verbose
.EXAMPLE
New-Playlist (get-childitem \\Uchuujin-san\movies | Where-Object {($_.Name -match "- Spanish") `
-and ($_.Name -ne "The Rosetta Stone - Spanish")} | foreach-Object `
{$_.FullName}) -M3U8File "Spanish" -verbose

.NOTES
M3U is for Playlists, M3U8 is for UTF-8 playlists.
UTF supports additional characters which are frequently used for foreign languages.

.PARAMETER Folders

Folders (or files for that matter) to add to the M3U8 file.

.PARAMETER M3U8File

Name of the file to save to. If a path is missing, current path is used.
If a dot is missing, m3u8 is assumed.

.LINK

http://en.wikipedia.org/wiki/M3U
http://www.computerperformance.co.uk/powershell/powershell_conditional_operators.htm

#>
param(
[Parameter(Position=0, Mandatory=$true)]$Folders,
[Parameter(Position=1, Mandatory=$false)]$M3U8File
)
    if(!$M3U8File){$M3U8File = "Playlist.m3u8"}
    if(!($M3U8File.contains(".")))
    {
        #Missing . so, adding extention.
        $M3U8File = $M3U8File + ".m3u8"
    }
    if(split-path $M3U8File)
    {
        #There's a path in M3U8File
    }else{
        #There's no path in M3U8File, use current folder.
        $M3U8File = Join-path (Get-Location -PSProvider FileSystem).ProviderPath $M3U8File
    }
    $ExtensionsArray = ".aac", ".ac3", ".aifc", ".aiff", ".ape", ".asf", ".au", ".avi", ".avr", ".dat", ".divx", ".dts", ".dvd", ".flac", ".fli", ".flv", ".iff", ".ifo", ".irca", ".m1v", ".m2v", ".m4a", ".mac", ".mat", ".mka", ".mks", ".mkv", ".mov", ".mp2", ".mp3", ".mp4", ".mpeg", ".mpeg1", ".mpeg2", ".mpeg4", ".mpg", ".mpgv", ".mpv", ".ogg", ".ogm", ".paf", ".pvf", ".qt", ".ra", ".rm", ".rmvb", ".sd2", ".sds", ".sw", ".vob", ".w64", ".wav", ".wma", ".wmv", ".xi", ".xvid"
    $M3U8Content = @()
    $M3U8Content += "#EXTM3U"

    foreach($Folder in $Folders)
    {
        $MovieFileObjects = (get-childitem $Folder | Where-Object {$ExtensionsArray -eq $_.Extension.ToString().tolower()})
        foreach($MovieFileObject in $MovieFileObjects)
        {
            write-Verbose "$($MovieFileObject.fullname)"
            $Duration = (Get-MediaInfo "$($MovieFileObject.fullname)" -verbose:$verbose)[0].Duration
            if($Duration)
            {
                # This can probably be done better by a regular expression, but this will work for now.
                if($Duration.split(" ")[0].contains("h"))
                {
                    # There's an hour mark in there (I'm not checking for days, if that happens, the script will break).
                    # Not sure what it does when a movie is less than 1 minute.
                    $DurationInSeconds = (([int]$Duration.split(" ")[0].replace("h","") * 60) + ([int]$Duration.split(" ")[1].replace("mn",""))) * 60
                }else{
                    # There's only minutes in there.
                    $DurationInSeconds = ([int]$Duration.split(" ")[0].replace("mn","")) * 60
                }
            }else{
                $DurationInSeconds = 1
            }
            # Have to replace dashes, because VLC interprets them as Artist/Title
            $M3U8Content += "#EXTINF:$DurationInSeconds,$(($MovieFileObject.directory.name).Replace("-"," ")) $($MovieFileObject.BaseName)"
            $M3U8Content += "$($MovieFileObject.fullname)"
        }
        write-Verbose "Saving to $M3U8File"
        $M3U8Content | out-file $M3U8File -Encoding "UTF8"
    }
}

#============================================================================================
# Rename-Movies
#============================================================================================
Function Rename-Movies
{
param(
<#

.SYNOPSIS
Renames all similar files in a folder also removes underscores and dots and removes leading/trailing spaces.

.DESCRIPTION
will remove [ and ] and everything in between them.
needs get-LongestCommonSubstring and get-LongestCommonSubstringArray

.EXAMPLE
Rename-Movies "K:\Movie7\D.Gray-man - Season 2 (2007)"
.NOTES

.LINK


#>
[Parameter(Position=0, Mandatory=$True)]$MovieFolder
)
    if(!(test-path $MovieFolder))
    {
        Break
    }
    Push-Location
    set-location $MovieFolder
    # First remove square brackets.
    if(Get-ChildItem '*``[*``]*')
    {
        do
        {
            $SquareBracketFiles = Get-ChildItem '*``[*``]*' | Where-Object { $_.Attributes -notlike "Directory" }
            foreach($SquareBracketFile in $SquareBracketFiles)
            {
                $SquareBracketOpen = ($SquareBracketFile.Name).IndexOf("[")
                $SquareBracketClose =($SquareBracketFile.Name).IndexOf("]")
                $ReplaceThis = ($SquareBracketFile.Name).Substring($SquareBracketOpen, $SquareBracketClose - $SquareBracketOpen + 1)
                Move-Item -literalpath $SquareBracketFile.Name $SquareBracketFile.Name.Replace($ReplaceThis, "")
            }
        }while(Get-ChildItem '*``[*``]*')
    }
    #Let's get rid of the _ and .
    # Find common string.
    $LongestCommonSubstring = get-LongestCommonSubstringArray (get-childitem $MovieFolder  | Where-Object { $_.Attributes -notlike "Directory" } | ForEach-Object{$_.BaseName})
    if($LongestCommonSubstring -and ($LongestCommonSubstring.length -gt 2))
    {
        $NewFileName = $Null
        Foreach($File in (get-childitem $MovieFolder | Where-Object { $_.Attributes -notlike "Directory" }))
        {
            $NewFileName = $File.BaseName.Replace($LongestCommonSubstring, "")
            $NewFileName = $NewFileName.Replace("_", " ")
            $NewFileName = $NewFileName.Replace(".", " ")
            $NewFileName = $NewFileName.Trim()
            $NewFileName = $NewFileName + $File.Extension
            Move-Item -literalpath $File.Name $NewFileName
        }
    }
    # Trim spaces
    Pop-Location
}

#============================================================================================
# get-LongestCommonSubstring
#============================================================================================
Function get-LongestCommonSubstring
{
Param(
[string]$String1, 
[string]$String2
)
    if((!$String1) -or (!$String2)){Break}
    # .Net Two dimensional Array:
    $Num = New-Object 'object[,]' $String1.Length, $String2.Length
    [int]$maxlen = 0
    [int]$lastSubsBegin = 0
    $sequenceBuilder = New-Object -TypeName "System.Text.StringBuilder"

    for ([int]$i = 0; $i -lt $String1.Length; $i++)
    {
        for ([int]$j = 0; $j -lt $String2.Length; $j++)
        {
            if ($String1[$i] -ne $String2[$j])
            {
                    $Num[$i, $j] = 0
            }else{
                if (($i -eq 0) -or ($j -eq 0))
                {
                        $Num[$i, $j] = 1
                }else{
                        $Num[$i, $j] = 1 + $Num[($i - 1), ($j - 1)]
                }
                if ($Num[$i, $j] -gt $maxlen)
                {
                    $maxlen = $Num[$i, $j]
                    [int]$thisSubsBegin = $i - $Num[$i, $j] + 1
                    if($lastSubsBegin -eq $thisSubsBegin)
                    {#if the current LCS is the same as the last time this block ran
                            [void]$sequenceBuilder.Append($String1[$i]);
                    }else{ #this block resets the string builder if a different LCS is found
                        $lastSubsBegin = $thisSubsBegin
                        $sequenceBuilder.Length = 0 #clear it
                        [void]$sequenceBuilder.Append($String1.Substring($lastSubsBegin, (($i + 1) - $lastSubsBegin)))
                    }
                }
            }
        }
    }
    return $sequenceBuilder.ToString()
}
#============================================================================================
# get-LongestCommonSubstringArray
#============================================================================================
Function get-LongestCommonSubstringArray
{
Param(
[Parameter(Position=0, Mandatory=$True)][Array]$Array
)
    $PreviousSubString = $Null
    $LongestCommonSubstring = $Null
    foreach($SubString in $Array)
    {
        if($LongestCommonSubstring)
        {
            $LongestCommonSubstring = get-LongestCommonSubstring $SubString $LongestCommonSubstring
            write-verbose "Consequtive diff: $SubString - $LongestCommonSubstring = $LongestCommonSubstring"
        }else{
            if($PreviousSubString)
            {
                $LongestCommonSubstring = get-LongestCommonSubstring $SubString $PreviousSubString
                write-verbose "first diff: $SubString - $PreviousSubString = $LongestCommonSubstring"
            }else{
                $PreviousSubString = $SubString
                write-verbose "No PreviousSubstring yet, setting it to: $PreviousSubString"
            }
        }
    }
    Return $LongestCommonSubstring
}

#============================================================================================
# Get-MovieClassification
#============================================================================================
$apikeys = get-content (join-path $PsScriptRoot apikeys.txt).tostring()
$TheMovieDBapikey = $apikeys | Where-Object {$_ -match "TheMovieDBapikey"} | ForEach-Object {$_ -replace "TheMovieDBapikey="}
$TheTVDBAuthentication = @{
    "apikey" = $apikeys | Where-Object {$_ -match "TVDBapikey"} | ForEach-Object {$_ -replace "TVDBapikey="}
    "userkey" = $apikeys | Where-Object {$_ -match "TVDBuserkey"} | ForEach-Object {$_ -replace "TVDBuserkey="}
    "username" = $apikeys | Where-Object {$_ -match "TVDBusername"} | ForEach-Object {$_ -replace "TVDBusername="}
}
Function Get-MovieClassification
{
<#

.SYNOPSIS
    Will classify a movie based on the name and year. Uses themoviedb.org and thetvdb.com
.DESCRIPTION

.EXAMPLE
    Get-MovieClassification -MovieName "Am I Normal (2007)"
.EXAMPLE
    Get-MovieClassification -MovieName "American Photography - A Century of Images (1999)" -Verbose
    Get-MovieClassification -MovieName "Andy Hamilton's Search For Satan (2011)" -Verbose
    Get-MovieClassification -MovieName "Animals In Love (2007)" -Verbose
    Get-MovieClassification -MovieName "De Helaasheid Der Dingen (2009)" -Verbose
    Get-MovieClassification -MovieName "Brass Eye (1997)" -Verbose
    Get-MovieClassification -MovieName "Building the Biggest (2006)" -Verbose
    Get-MovieClassification -MovieName "Colour Me Kubrick - A True...Ish Story (2005)" -Verbose
    Get-MovieClassification -MovieName "Baukunst (2001)" -Verbose

.EXAMPLE
    remove-module movies
    IPMO C:\movies\Movies.psm1
    $Unknown = @()
    $Documentaries = @()
    $Movies = @()
    $Anime = @()
    $TV = @()
    $Dutch = @()
    $MovieNames = get-content C:\movies\movienames.txt
    $i = 0
    foreach($MovieName in $MovieNames)
    {
        write-host "$i $MovieName" -fore yellow
        $Result = $Null
        $Result = Get-MovieClassification -MovieName $MovieName
        if($Result -eq "Documentary")
        {
            $Documentaries += $MovieName
        }elseif($Result -eq "Movie"){
            $Movies += $MovieName
        }elseif($Result -eq "Anime"){
            $Anime += $MovieName
        }elseif($Result -eq "TV"){
            $TV += $MovieName
        }elseif($Result -eq "Dutch"){
            $Dutch += $MovieName
        }else{
            $Unknown += $MovieName
        }
        start-sleep -Milliseconds 500
        $i++
    }
.NOTES
    apikeys are stored in a file called apikeys.txt in the module folder and will have to be set before this works.
.LINK


#>
param(
$MovieName,
[switch]$TitleSplit,
[switch]$Verbose
)
    # $Genres = (Invoke-RestMethod -Uri "https://api.themoviedb.org/3/genre/movie/list?api_key=apikeyhere&language=en-US").genres
    #$Genres = @{
    #   Action=28
    #   Adventure=12
    #   Animation=16
    #   Comedy=35
    #   Crime=80
    #   Documentary=99
    #   Drama=18
    #   Family=10751
    #   Fantasy=14
    #   History=36
    #   Horror=27
    #   Music=10402
    #   Mystery=9648
    #   Romance=10749
    #   "Science Fiction"=878
    #   "TV Movie"=10770
    #   Thriller=53
    #   War=10752
    #   Western=37
    #}

    $MovieName = $MovieName.tolower().Replace(" -",":")
    [int]$MovieYear = $Null
    if($MovieName -match "\([0-9][0-9][0-9][0-9]\)")
    {
        $MovieYear = $MovieName.split("(")[1].Replace(")","")
        $MovieName = $MovieName.split("(")[0].Trim()
        $MovieYears = (($MovieYear + 1),$MovieYear,($MovieYear -1))
    }
    $TheMovieDBMatch = $Null
    Add-Type -AssemblyName System.Web
    $URLMovieName = [System.Web.HttpUtility]::UrlEncode($MovieName)
    $SearchResult = $Null
    $SearchResult = Invoke-RestMethod -Uri "https://api.themoviedb.org/3/search/movie?api_key=$($TheMovieDBapikey)&query=$URLMovieName"
    $DutchLanguage = $Null

    if($SearchResult)
    {
        if($SearchResult.total_results -ge 1)
        {
            $PossibleVideoObjects = @()
            foreach($VideoObject in $SearchResult.Results)
            {
                $NameMatchFound = $Null
                if($VideoObject.title.tolower().StartsWith($MovieName))
                {
                    if($Verbose)
                    {
                        write-host "TheMovieDB Title match: $MovieName - $($VideoObject.title)" -fore green
                    }
                    $NameMatchFound = $True
                }else{
                    if($VideoObject.original_title.tolower().StartsWith($MovieName))
                    {
                        if($Verbose)
                        {
                            write-host "TheMovieDB Title match on original_title: $MovieName - $($VideoObject.original_title)" -fore green

                        }
                        $NameMatchFound = $True
                        if($VideoObject.original_language.tolower() -eq "nl")
                        {
                            $DutchLanguage = $True
                            if($Verbose)
                            {
                                write-host "TheMovieDB Language is Dutch: $MovieName - $($VideoObject.title)" -fore green

                            }
                        }
                    }else{
                        write-host "TheMovieDB - mismatching on title: $MovieName - $($VideoObject.title.tolower())" -for red
                    }
                }
                if($NameMatchFound)
                {
                    if($VideoObject.release_date)
                    {
                        $VideoObjectReleaseYear = $VideoObject.release_date.split("-")[0]
                        if($VideoObjectReleaseYear -and $MovieYear)
                        {
                            if($MovieYears -eq $VideoObjectReleaseYear)
                            {
                                if($Verbose)
                                {
                                    write-host "TheMovieDB Year match: $MovieYear - $VideoObjectReleaseYear" -fore green
                                }
                                $TheMovieDBMatch = $True

                                break
                            }else{
                                if($Verbose)
                                {
                                    write-host "TheMovieDB - No match on year: $MovieName $MovieYears" -for red
                                }
                            }
                        }else{
                            if(!$MovieYear)
                            {
                                write-host "TheMovieDB - Year is missing but we have a potential match: $MovieName - $($VideoObject.release_date.split("-")[0])" -for red
                            }
                        }
                    }

                }
            }
        }else{
            if($Verbose)
            {
                write-host "TheMovieDB - Nothing matched on title: $MovieName" -for red
            }
        }
    }else{
        if($Verbose)
        {
            write-host "TheMovieDB - Nothing returned at all?" -for red
        }
    }
    $theTVDBMatch = $Null
    if(!$TheMovieDBMatch)
    {
        $TheTVDBToken = (Invoke-RestMethod -Uri "https://api.thetvdb.com/login" -Method Post -Body ($TheTVDBAuthentication | ConvertTo-Json) -ContentType 'application/json').token
        $TVDBHeaders = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
        $TVDBHeaders.Add("Accept", "application/json")
        $TVDBHeaders.Add("Authorization", "Bearer $TheTVDBToken")
        $SearchResult = $Null
        try
        {
            $SearchResult = (Invoke-RestMethod -Uri "https://api.thetvdb.com/search/series?name=$URLMovieName" -Headers $TVDBHeaders)
        }catch [System.Net.WebException]
        {
            # Looks like it wasn't found
        }
        if($SearchResult)
        {
            $PossibleVideoObjects = @()
            foreach($VideoObject in $SearchResult.data)
            {
                if($VideoObject.seriesName.tolower().StartsWith($MovieName))
                {
                    if($Verbose)
                    {
                        write-host "theTVDB Title match: $MovieName - $($VideoObject.seriesName)" -fore green
                    }
                    if($VideoObject.firstAired)
                    {
                        $VideoObjectReleaseYear = $VideoObject.firstAired.split("-")[0]
                        if($VideoObjectReleaseYear -and $MovieYear)
                        {
                            if($MovieYears -eq $VideoObjectReleaseYear)
                            {
                                if($Verbose)
                                {
                                    write-host "theTVDB Year match: $MovieYear - $VideoObjectReleaseYear" -fore green
                                }
                                $theTVDBMatch = $True

                                break
                            }else{
                                if($Verbose)
                                {
                                    write-host "theTVDB - No match on year: $MovieName $MovieYears" -for red
                                }
                            }
                        }else{
                            if($Verbose)
                            {
                                write-host "theTVDB $MovieName - Something wrong with Year: $MovieYears -eq $VideoObjectReleaseYear" -fore magenta
                            }
                        }
                    }else{
                        if($Verbose)
                        {
                            write-host "theTVDB Title match: $MovieName - No year in TVDB" -fore magenta
                        }
                        $PossibleVideoObjects += $VideoObject
                    }
                }else{
                    write-host "theTVDB - mismatching on title: $MovieName - $($VideoObject.seriesName.tolower())" -for red
                }
            }
            if((!$theTVDBMatch) -and $PossibleVideoObjects)
            {
                if($PossibleVideoObjects.count -eq 1)
                {
                    $theTVDBMatch = $True
                    $VideoObject = $PossibleVideoObjects
                    write-host "theTVDB - matched on title missing year: $MovieName - $($VideoObject.seriesName.tolower())" -for green
                }else{
                    write-host "theTVDB - multiple potentials found without year: $MovieName - $($PossibleVideoObjects.seriesName.tolower())" -for red
                }
            }
        }else{
            if($Verbose)
            {
                write-host "theTVDB - Nothing returned on title: $MovieName" -for red
            }
        }
    }
    $DocuWikiNotFound = $Null
    $DocuWikiFound = $Null
    $WebPageYear = $Null
    $DocuWikiMatch = $Null
    if(!$theTVDBMatch -and !$TheMovieDBMatch)
    {
        $URL = "https://docuwiki.net/index.php?title=$URLMovieName"
        $WebPage = Invoke-WebRequest -uri $URL 

        if(!($WebPage.StatusCode -eq "200"))
        {
            Write-host "Couldn't query docuwiki.net" -fore red
        }else{
            foreach($Line in $WebPage.Content.split("`n"))
            {
                if(!$DocuWikiNotFound)
                {
                    if($DocuWikiFound)
                    {
                        if($Line.StartsWith('<a href="/index.php?title=Category:Year" title="Category:Year">Year</a> &gt; <a href="/index.php?title=Category:'))
                        {
                            $Line = $Line.Replace('<a href="/index.php?title=Category:Year" title="Category:Year">Year</a> &gt; <a href="/index.php?title=Category:',"")
                            $WebPageYear = $Line.Split('"')[0]
                        }
                    }else{
                        if($line.trim().tolower() -eq '<meta name="robots" content="noindex,nofollow" />')
                        {
                            if($Verbose)
                            {
                                write-host "docuwiki - $MovieName not found" -fore red
                            }
                            $DocuWikiNotFound = $True
                        }elseif($line.trim().tolower() -eq "<h1 class=`"firstheading`">$MovieName</h1>")
                        {
                            if($Verbose)
                            {
                                write-host "docuwiki Title match: $MovieName" -fore green
                            }
                            $DocuWikiFound = $True
                        }elseif($line.trim().tolower().StartsWith("<h1 class=`"firstheading`">"))
                        {
                            $DocuWikiFound = $True
                            if($line.trim().tolower() -eq "<h1 class=`"firstheading`">$MovieName</h1>")
                            {
                                if($Verbose)
                                {
                                    write-host "docuwiki Title match: $MovieName" -fore green
                                }
                            }else{
                                $TranslatedName = $Null
                                $TranslatedName = $line.trim().tolower().Split(">")[1].Replace("</h1>","")
                                write-host "docuwiki Title match under translated Name: $MovieName - $TranslatedName" -fore green
                            }
                        }
                    }
                }
            }
        }
        if($DocuWikiFound)
        {
            if($MovieYear)
            {
                if($WebPageYear)
                {
                    if($MovieYears -eq $WebPageYear)
                    {
                        $DocuWikiMatch = $True
                    }else{
                        if($Verbose)
                        {
                            write-host "docuwiki - $MovieName found, but years mismatch: $WebPageYear - $MovieYears" -fore red
                        }
                    }
                }
            }else{
                $DocuWikiMatch = $True
            }
        }
    }
    if(($theTVDBMatch -or $TheMovieDBMatch -or $DocuWikiMatch) -and $TitleSplit)
    {
        write-host "$MovieName Split title found! Update name" -fore yellow
    }
    if($theTVDBMatch)
    {
        $SeriesResult = (Invoke-RestMethod -Uri "https://api.thetvdb.com/series/$($VideoObject.id)" -Headers $TVDBHeaders)
        $SeriesObject = $SeriesResult.data
        $TVGenres = "Reality", "Comedy"
        if(($SeriesObject.genre -eq "Documentary") -or ($SeriesObject.genre -eq "News"))
        {
            return "Documentary"
        }else{
            if(($SeriesObject.genre -eq "Anime") -or ($SeriesObject.genre -eq "Animation"))
            {
                return "Anime"
            }elseif($TVGenres -eq $SeriesObject.genre){
                return "TV"
            }else{
                write-host "uncategorized genres theTVDBMatch"
                write-host "$($SeriesObject.genre)" -fore magenta
            }
        }
    }
    if($TheMovieDBMatch)
    {
        if($DutchLanguage)
        {
            return "Dutch"
        }
        if($VideoObject.genre_ids -eq 99)
        {
            return "Documentary"
        }else{
            if($Verbose)
            {
                write-host "uncategorized genre_ids TheMovieDBMatch"
                write-host "$($VideoObject.genre_ids)" -fore magenta
            }
            return "Movie"
        }
    }
    if($DocuWikiMatch)
    {
        return "Documentary"
    }

    if(!($theTVDBMatch) -and !($TheMovieDBMatch) -and !($DocuWikiMatch) -and ($MovieName -match ":"))
    {
        $NewMovieName = $Null
        if($MovieYear)
        {
            $NewMovieName = "$($MovieName.split(":")[0].trim()) ($MovieYear)"
        }else{
            $NewMovieName = $MovieName.split(":")[0].trim()
        }
        if($Verbose)
        {
            write-host "Trying with a split name for $MovieName |now trying: $NewMovieName" -fore Magenta
        }
        Get-MovieClassification -MovieName $NewMovieName -TitleSplit -Verbose:$Verbose
    }
}

#============================================================================================
# Rename-TVSeasons
#============================================================================================
Function Rename-TVSeasons
{
<#

.SYNOPSIS
    Will change the way seasons are sorted. Creates a top folder without -Season,
    then adds the -season folders into it.
.DESCRIPTION

.EXAMPLE
    Rename-TVSeasons -Folder "F:\TV"
.NOTES

.LINK

#>
param(
$Folder
)
    $DutchFolders = get-childitem $Folder | Where-Object {$_.name -match "  "}
    foreach($Folder in $DutchFolders)
    {
        Rename-Item -Path $Folder.fullname -NewName $Folder.fullname.Replace("  "," ")
    }
    if($Folder -match "Dutch")
    {
        $DutchFolders = get-childitem $Folder | Where-Object {$_.name -match "- Dutch "}
        foreach($Folder in $DutchFolders)
        {
            Rename-Item -Path $Folder.fullname -NewName $Folder.fullname.Replace("- Dutch","")
        }
    }

    $SeasonFolders = get-childitem $Folder | Where-Object {$_.name -match "- Season "}

    foreach($SubFolder in $SeasonFolders)
    {
        $SplitFolderNameArray = $SubFolder.Name.split("-").Trim()
        $i = 0
        $TopLevelFolderName = ""
        Foreach($NamePart in $SplitFolderNameArray)
        {
            if($NamePart -match "Season ")
            {
                break
            }else{
                if($i -gt 0)
                {
                    $TopLevelFolderName += " - $($SplitFolderNameArray[$i])"
                }else{
                    $TopLevelFolderName += "$($SplitFolderNameArray[$i])"
                }
            }
            $i++
        }
        if(!(Test-path "$($Folder)\$($TopLevelFolderName)"))
        {
            mkdir "$($Folder)\$($TopLevelFolderName)"
        }
        Move-Item -Path $SubFolder.FullName -Destination "$($Folder)\$($TopLevelFolderName)"
    }
}

#============================================================================================
# Group-Movies
#============================================================================================
Function Group-Movies
{
<#

.SYNOPSIS

.DESCRIPTION

.EXAMPLE
    Group-Movies -Folder "G:\Movie1"
.EXAMPLE
    Group-Movies -Folder "G:\Movie1" -Verbose
.NOTES

.LINK

#>
param(
$Folder,
[switch]$Verbose
)
    $Drive = $Folder.split("\")[0] + "\"

    # Exclude all seasons (going to be TV and anime) and dutch (will make a separate step for these)
    $SubFolders = $Null
    $SubFolders = get-childitem $Folder
    Foreach($SubFolder in $SubFolders)
    {
        $Unknown = $Null
        $MovieName = $SubFolder.Name
        $MovieName = $MovieName.Tolower().Replace("- Season ","")
        write-host "$MovieName" -fore yellow
        $Result = $Null
        if($MovieName -match " - Dutch ")
        {
            $NewFolderPath = Join-Path $Drive Dutch
        }else{
            $Result = Get-MovieClassification -MovieName $MovieName -verbose:$Verbose
            if($Result -eq "Documentary")
            {
                $NewFolderPath = Join-Path $Drive Documentaries
            }elseif($Result -eq "Movie"){
                $NewFolderPath = Join-Path $Drive Movies
            }elseif($Result -eq "Anime"){
                $NewFolderPath = Join-Path $Drive Anime
            }elseif($Result -eq "TV"){
                $NewFolderPath = Join-Path $Drive TV
            }elseif($Result -eq "Dutch"){
                $NewFolderPath = Join-Path $Drive Dutch
            }else{
                $Unknown += $MovieName
                $Unknown = $True
            }
        }
        if(!$Unknown)
        {
            if(!(test-path $NewFolderPath))
            {
                write-host "making $NewFolderPath"
                start-sleep 1
                mkdir $NewFolderPath
            }
            $NewFolderName = Join-Path $NewFolderPath $MovieName
            if(test-path $NewFolderName)
            {
                write-host "=== $NewFolderName already exists! ===" -fore red
            }else{
                Move-Item -Path $SubFolder.FullName -Destination $NewFolderPath
            }
        }
        start-sleep -Milliseconds 500
    }
    if(Join-Path $Drive Anime)
    {
        Rename-TVSeasons -Folder (Join-Path $Drive Anime)
    }
    if(Join-Path $Drive TV)
    {
        Rename-TVSeasons -Folder (Join-Path $Drive TV)
    }
    if(Join-Path $Drive Documentaries)
    {
        Rename-TVSeasons -Folder (Join-Path $Drive Documentaries)
    }
    if(Join-Path $Drive Dutch)
    {
        Rename-TVSeasons -Folder (Join-Path $Drive Dutch)
    }
}


#============================================================================================
# ConvertTo-x265
#============================================================================================
Function ConvertTo-x265
{
<#

.SYNOPSIS
    Copiess a moviefile to \\ferb\Video\ToBeRemoved, then makes a new x265 file.
.DESCRIPTION

.EXAMPLE
    ConvertTo-x265 -FolderName "\\ferb\Video\Anime1\One-Punch Man"
    ConvertTo-x265 -FolderName "\\ferb\Video\Anime1\One-Punch Man" -onlyover1gb
.EXAMPLE
    ConvertTo-x265 -FolderName "\\ferb\Video\Anime1\One-Punch Man\Season 2\One-Punch Man - S02E01 - Return of the Hero - HDTV-1080p - x265 Opus.mkv"
.NOTES
    Will only copy back the x265 file if it is at least 5% smaller than the old file.
.LINK

#>
param(
$FolderName,
[switch]$onlyover1gb,
$Quality=22,
[switch]$To1080
)
    $Folder = get-item $FolderName
    $ItsaFile = $False
    if($Folder.gettype().Name -eq "FileInfo")
    {
        $ItsaFile = $True
        $File = get-item $FolderName
        $Folder = get-item $File.Directory.FullName
    }
    if($env:computername -eq "phineas")
    {
        $driveletter = "E:\"
        $ToBeRemovedFolder = Join-path "\\ferb\Video\ToBeRemoved2" $Folder.Name
    }elseif($env:computername -eq "ender"){
        $driveletter = "K:\"
        $ToBeRemovedFolder = Join-path "Z:\ToBeRemoved2" $Folder.Name
    }elseif($env:computername -eq "candace"){
        $driveletter = "H:\"
        $ToBeRemovedFolder = Join-path "Z:\ToBeRemoved2" $Folder.Name
    }


    $ConversionToFolder = Join-path ($driveletter + "Handbrake\ConversionTo") $Folder.Name
    $ConversionFromFolder = Join-path ($driveletter + "Handbrake\ConversionFrom") $Folder.Name

    if(!(test-path $ToBeRemovedFolder)){$Null = mkdir $ToBeRemovedFolder}
    if(!(test-path $ConversionToFolder)){$Null = mkdir $ConversionToFolder}
    if(!(test-path $ConversionFromFolder)){$Null = mkdir $ConversionFromFolder}
    if($ItsaFile)
    {

        copy-item $File.Fullname $ConversionFromFolder
        move-item $File.Fullname $ToBeRemovedFolder
        $OldFileName = Join-path $ConversionFromFolder $File.Name
        $NewFileName = Join-path $ConversionToFolder $File.Name
        Convert-HandbrakeCommandx265  -From "$OldFileName" -To "$NewFileName" -Quality $Quality -To1080 $To1080
        if((get-item $NewFileName).length -lt ((get-item $OldFileName).length - ((get-item $OldFileName).length/20)))
        {
            write-host "NewFileName size: $((get-item $NewFileName).length) - OldFileName $((get-item $OldFileName).length)" -fore green
            write-host "ItsaFile move-item $NewFileName $($Folder.FullName)" -fore green
            move-item $NewFileName $Folder.FullName
            write-host "ItsaFile remove-item $OldFileName" -fore green
            remove-item $OldFileName
        }else{
            write-host "newfile size: $((get-item $NewFileName).length)) - old file size: $((get-item $OldFileName).length))" -fore red
            write-host "ItsaFile move-item $OldFileName $($Folder.FullName)" -fore green
            move-item $OldFileName $Folder.FullName
            write-host "new file is larger!" -fore red
        }

    }else{
        $SubFolders = get-childitem $FolderName -Directory
        if(!$SubFolders)
        {
            $SubFolders = $Folder
        }
        Foreach($SubFolder in $SubFolders)
        {
            $ToBeRemovedSubFolder = Join-path $ToBeRemovedFolder $SubFolder.Name
            $ConversionFromFolderSubFolder = Join-path $ConversionFromFolder $SubFolder.Name
            $ConversionToFolderSubFolder = Join-path $ConversionToFolder $SubFolder.Name
            if(!(test-path $ToBeRemovedSubFolder)){$Null = mkdir $ToBeRemovedSubFolder}
            if(!(test-path $ConversionFromFolderSubFolder)){$Null = mkdir $ConversionFromFolderSubFolder}
            if(!(test-path $ConversionToFolderSubFolder)){$Null = mkdir $ConversionToFolderSubFolder}
            $Files = get-childitem $SubFolder.FullName -File | where-object {!(($_.name.endswith(".srt")) -or ($_.name -match "265"))}
            $i = 0
            Foreach($File in $Files)
            {
                $OldFileName = Join-path $ConversionFromFolderSubFolder $File
                $NewFileName = Join-path $ConversionToFolderSubFolder $File
                if($onlyover1gb)
                {   
                    if($File.length -le 1073741824)
                    {
                        write-host "$File is less than 1GB, skip."
                        continue
                    }
                }

                if(test-path $OldFileName)
                {
                    write-host "$OldFileName already exists" -fore red
                    return
                }
                write-host "copy-item $($File.Fullname) $ConversionFromFolderSubFolder" -fore green
                copy-item $File.Fullname $ConversionFromFolderSubFolder
                write-host "move-item $($File.Fullname) $ToBeRemovedSubFolder)" -fore green
                move-item $File.Fullname $ToBeRemovedSubFolder

                if(!(test-path $NewFileName))
                {
                    write-host "$NewFileName doesn't exist"
                    Convert-HandbrakeCommandx265  -From "$OldFileName" -To "$NewFileName" -Quality $Quality -To1080 $To1080
                    if((get-item $NewFileName).length -lt ((get-item $OldFileName).length - ((get-item $OldFileName).length/20)))
                    {
                        write-host "NewFileName size: $((get-item $NewFileName).length) - OldFileName $((get-item $OldFileName).length)" -fore green
                        write-host "move-item $NewFileName $($SubFolder.FullName)" -fore green
                        move-item $NewFileName $SubFolder.FullName
                        write-host "remove-item $OldFileName" -fore green
                        remove-item $OldFileName
                        $i = 0
                    }else{
                        write-host "newfile size: $((get-item $NewFileName).length)) - old file size: $((get-item $OldFileName).length))" -fore red
                        $i++
                        write-host "move-item $OldFileName $($SubFolder.FullName)" -fore green
                        move-item $OldFileName $SubFolder.FullName
                        if($i -gt 4)
                        {
                            Start-Pause -message "new file was larger 5x in a row! (might as well stop here)"
                        }
                    }   
                }else{
                    write-host "$NewFileName already exists?" -fore red
                    Start-Pause -message "already exists?"
                }
            }
        }
    }
}

Function Convert-HandbrakeCommand
{
param(
$From,
$To,
$Quality
)
    HandBrakeCLI --subtitle 0-99 --quality $quality --two-pass --encoder-preset medium --all-audio --encoder x264 --subtitle-burned=none --output "$To" --input "$From"
    # this one is faster for testing purposes
    #HandBrakeCLI --subtitle 0-99 --quality 19 --two-pass --encoder-preset superfast --all-audio --encoder x264 --subtitle-burned=none --output "$To" --input "$From"
}

Function Convert-HandbrakeCommandx265
{
<#

.SYNOPSIS
    uses HandBrakeCLI to convert a video file to x265. HandBrakeCLI has to be in the path variable.
.DESCRIPTION

.EXAMPLE
    Convert-HandbrakeCommandx265 -From x -To x -Quality 24
.EXAMPLE

.NOTES
    used by ConvertTo-x265. Not really intended to be used as stand alone. 
.LINK

#>
param(
$From,
$To,
$Quality,
[switch]$To1080
)
    if($To1080){
        HandBrakeCLI --subtitle 0-99 --quality $quality --two-pass --encoder-preset slower --all-audio --encoder nvenc_h265 --subtitle-burned=none --output "$To" --input "$From" --width 1920 --height 1080
    }else{
        HandBrakeCLI --subtitle 0-99 --quality $quality --two-pass --encoder-preset slower --all-audio --encoder nvenc_h265 --subtitle-burned=none --output "$To" --input "$From"
    }
}

Function Merge-Movies
{
<#

.SYNOPSIS
    merges two or more movie files into one if they are in the same directory.

    Names it after the directory.
.DESCRIPTION

.EXAMPLE
    Merge-Movies "\\ferb\Video\Movies\UFC 11 - The Proving Ground (1996)"
.EXAMPLE

    # lines held a copy paste from the radarr table.
    # pretty easy and effective.

    foreach($line in $Lines)
    {
        if($line.startswith("/"))
        {
            $Directory = "\\ferb" + $line.replace("/","\")
            Merge-Movies "$Directory"
        }
    }

.NOTES

.LINK

#>
param($Directory)

    Set-Location $Directory

    $Files = get-childitem $Directory

    if ($files  -is [array])
    {
        $ConcatString = "`"concat:"
        $i = 0
        $TotalLength = 0
        foreach($File in $Files)
        {
            $ConcatString = $ConcatString + $File.Name
            $i = $i + 1
            if($i -lt $Files.Count)
            {$ConcatString = $ConcatString + "|"}else{$ConcatString = $ConcatString + "`""}
            $TotalLength = $TotalLength + $File.Length
        }

        $outputName = $directory.split("\")[-1] + ".avi"


        ffmpeg -i $ConcatString -c copy "$outputName"

        $NewFiles = get-childitem $Directory
        $TotalNewLength = 0
        foreach($File in $NewFiles)
        {
            $TotalNewLength = $TotalNewLength + $File.Length
        }

        if ($TotalNewLength -ge ($TotalLength * 2))
        {
            foreach($File in $Files)
            {
                remove-item $File
            }
        }else{
            write-host "$TotalNewLength is not twice $TotalLength"
        }
    }
}

#=====================================================================
# Get-MediaInfo
#=====================================================================
Function Get-MediaInfo
{
<#

.SYNOPSIS

 Returns an array of objects, consisting of the Audio and Video tracks from a media container file.

 Usage: Get-MediaInfo -MovieFile `$MovieFile [-Verbose]

 Dependencies: MediaInfo.exe and MediaInfo.dll 
 (files should be located in the Module Folder)

 Media Info supplies technical and tag information about a video or audio file. 

.EXAMPLE

$MovieObject = Get-MediaInfo "Path\Movie.mkv"

$MovieObject[0]

Complete_name       : Path\Movie.mkv
Duration            : 1h 50mn
File_size           : 700 MiB
Format              : AVI
Format_Info         : Audio Video Interleave
Overall_bit_rate    : 886 Kbps
type                : General
Writing_application : Nandub v1.0rc2
Writing_library     : Nandub build 1852/release

The other tracks are audio and video and can be queried like this:

(Get-MediaInfo "Path\Movie.mkv") | where {$_.type -eq "Audio"}
(Get-MediaInfo "Path\Movie.mkv") | where {$_.type -eq "Video"}

Keep in mind that multiple audio and/or video tracks can be returned (but only one General)

.NOTES

 Supported formats: 
 Video : MKV, OGM, MP4, AVI, MPG, VOB, MPEG1, MPEG2, MPEG4, 
 DVD, WMV, ASF, DivX, XviD, MOV (Quicktime), SWF(Flash), FLV, FLI, RM/RMVB. 
 Audio : OGG, MP3, WAV, RA, AC3, DTS, AAC, M4A, AU, AIFF, WMA. 

Uses the 0.7.60 version. https://mediaarea.net/en/MediaInfo/Download/Windows

.LINK

http://mediainfo.sourceforge.net/en

#>
param(
[Parameter(Position=0, Mandatory=$true)]$MovieFile
)
    if(!($MovieFile)){get-help Get-MediaInfo; Break}
    if(test-path $MovieFile)
    {
        $Executable = (join-path $PsScriptRoot MediaInfo.exe).tostring()
        $xmldata = new-object "System.Xml.XmlDocument"
        $xmldata.LoadXml((Invoke-Expression "$Executable --Output=XML `"$MovieFile`""))
        $Collection = @()
        foreach($Track in $xmldata.Mediainfo.File.Track)
        {
            $myobj = new-object object
            foreach($Attribute in ($Track | get-member -MemberType properties))
            {
                write-Verbose "$($Attribute.Name) - $($Track.($Attribute.Name))"
                $myobj | add-member -membertype NoteProperty -Name ($Attribute.Name) -value ($Track.($Attribute.Name))
            }
            $Collection += $myobj
        }
        return $Collection
    }else{
        Write-host "$MovieFile Not Found" -fore Red
    }
}