' 
' This is a script to generate various statistics for your iTunes library.
' On average, it would take about 1 minute to run for every 1,000 songs. 
' It was created by Scott Yanoff (yanoff-NOSPAM@yahoo.com). Copyright 2010. 
' (Remove the "-NOSPAM" to successfully contact me) 
' 
' Update history: 
' 12-24-2008    Created this script
' 12-30-2008    Added the RightPad function for right-alignment of text
'               Added enumeration to the lists
'               Enhanced the information by artist to include average plays per artist 
'               Added message at start of execution so user knows the script is running
'               File that is generated is now automatically launched at the end
'               Added percentages to some of the statistics
' 01-01-2009    Corrected filename to include leading zero in month and day
'               Added total song time and total played time
' 01-03-2009    Corrected display of count of least-recently played tracks 
' 01-07-2009    Added average track duration, average play count per track
' 01-08-2009    Removed longest songs listing, it was too hard to sort by time for little value
'               Added play counts by hour of the day
' 01-10-2009    Limited the Top Genres list to 20 instead of displaying entire list
'               Most-played album listing now only shows albums with > 7 songs
' 01-11-2009    Limited the Artists list to 100 instead of displaying entire list
' 01-19-2009    Increased top albums display, corrected leading zero in seconds in average track duration
' 02-04-2009    Added count of unique artists
' 02-25-2009    Added artist name to all song lists
'               Now limits list of years to top 30 years, and numbers them
'               Now numbers genres and allows for more space for them
' 03-20-2009    Changed Tracks by Year to be Tracks by Decade
' 01-04-2010    Increased number of top albums, made min. # of songs on an album a variable
'               Minor cosmetic changes to the output
' 01-23-2010    Added total song play count for the top albums
' 
Const SORT_BY_NUMBER     = 1
Const SORT_BY_NUMBER_ASC = 2
Const SORT_BY_STRING     = 3

Dim songCount, playCount, intDisplayTopSongs, intDisplayTopAlbums, totalTime, songAndArtist
Dim totalPlayedTime, playedHour, intDIsplayTopGenres, i, intDisplayTopArtists, intDisplayTopYears
songCount            = 0 
playCount            = 0

' The following are constants that can be changed to affect the output. 
' These constants control how many items appear in the various lists that are output.
intDisplayTopSongs   = 100
intDisplayTopAlbums  = 51
intDisplayTopGenres  = 20
intDisplayTopArtists = 100
intDisplayTopYears   = 30
intMinSongsOnAlbum   = 7

Dim objApp, objLibrary, colTracks, intCount, strFileName, strSong, strArtist, strAlbum, strYear
Dim dictAlbums, dictArtistsSongs, dictRatingsSongs, dictGenreSongs, dictYearSongs, dictPlaysByHour
Dim dictPlayedCounts, dictPlayedDates, dictArtistPlaycounts, dictAlbumPlaycounts, dictAlbumTotalPlaycount
Set dictAlbums = CreateObject("Scripting.Dictionary") 
Set dictArtistsSongs = CreateObject("Scripting.Dictionary") 
Set dictRatingsSongs = CreateObject("Scripting.Dictionary") 
Set dictGenreSongs = CreateObject("Scripting.Dictionary") 
Set dictYearSongs = CreateObject("Scripting.Dictionary") 
Set dictPlaysByHour = CreateObject("Scripting.Dictionary")
Set dictPlayedCounts = CreateObject("Scripting.Dictionary") 
Set dictPlayedDates = CreateObject("Scripting.Dictionary")  
Set dictArtistPlaycounts = CreateObject("Scripting.Dictionary") 
Set dictAlbumPlaycounts = CreateObject("Scripting.Dictionary") 
Set dictAlbumTotalPlaycount = CreateObject("Scripting.Dictionary")

strFileName = "iTunesStats." & DatePart("yyyy",Now()) & Right("0" & Month(Now()),2) & Right("0" & Day(Now()),2) & ".txt" 
Wscript.Echo "iTunes Stats v2.13 by Scott Yanoff" & chr(13) & chr(13) & "iTunes Stats will take a few minutes to run. When it is done, it will show you the stats in a file called " & strFileName

' This function can be used to add spaces to the end of some text
Function Pad(ByRef Text, ByVal Length)
  Pad = Left(Text & Space(Length), Length)
End Function

' This function can be used to add spaces to the beginning of some text
Function RightPad(ByRef Text, ByVal Length) 
  RightPad = Right(Space(Length) & Text, Length) 
End Function 


 ' Description:
 '   Sorts a dictionary by either key or item
 ' Parameters:
 '   objDict - the dictionary to sort
 '   sortField - the field to sort (1=key, 2=item)
 '   sortBy - (1=sort by number, 2=sory by number ascending, 3=sort by string)
 ' Returns:
 '   A dictionary sorted by sortField
 '
Function SortDictionary(objDict, sortField, sortBy)

    ' declare constants
    Const dictKey  = 1
    Const dictItem = 2

    ' declare our variables
    Dim strDict()
    Dim objKey
    Dim strKey, strItem
    Dim X,Y,Z

    ' get the dictionary count
    Z = objDict.Count

    ' we need more than one item to warrant sorting
    If Z > 1 Then
      ' create an array to store dictionary information:
      ReDim strDict(Z,2)
      X = 0
      ' populate the string array:
      For Each objKey In objDict
          strDict(X,dictKey)  = objKey
          strDict(X,dictItem) = objDict(objKey)
          X = X + 1
      Next

      ' perform a a shell sort of the string array:
      For X = 0 To (Z - 2)
        For Y = X To (Z - 1)
         
          Select case sortBy
           case SORT_BY_NUMBER
            If strDict(X,sortField) < strDict(Y,sortField) Then
              strKey  = strDict(X,dictKey)
              strItem = strDict(X,dictItem)
              strDict(X,dictKey)  = strDict(Y,dictKey)
              strDict(X,dictItem) = strDict(Y,dictItem)
              strDict(Y,dictKey)  = strKey
              strDict(Y,dictItem) = strItem
            End If
            
           case SORT_BY_NUMBER_ASC
            If strDict(X,sortField) > strDict(Y,sortField) Then
              strKey  = strDict(X,dictKey)
              strItem = strDict(X,dictItem)
              strDict(X,dictKey)  = strDict(Y,dictKey)
              strDict(X,dictItem) = strDict(Y,dictItem)
              strDict(Y,dictKey)  = strKey
              strDict(Y,dictItem) = strItem
            End If
                                
           case SORT_BY_STRING
            If StrComp(strDict(X,sortField),strDict(Y,sortField),vbTextCompare) > 0 Then
              strKey  = strDict(X,dictKey)
              strItem = strDict(X,dictItem)
              strDict(X,dictKey)  = strDict(Y,dictKey)
              strDict(X,dictItem) = strDict(Y,dictItem)
              strDict(Y,dictKey)  = strKey
              strDict(Y,dictItem) = strItem
            End If

          End Select
        Next
      Next

      ' erase the contents of the dictionary object
      objDict.RemoveAll

      ' repopulate the dictionary with the sorted information
      For X = 0 To (Z - 1)
        objDict.Add strDict(X,dictKey), strDict(X,dictItem)
      Next

    End If
  End Function

Set objApp = CreateObject("iTunes.Application") 
Set objLibrary = objApp.LibraryPlaylist 
Set colTracks = objLibrary.Tracks 

' Use these 2 lines for testing across a small part of the collection INSTEAD of the For Each below
'For i = 1 to 200
'Set objTrack = colTracks.Item(i)
For Each objTrack in colTracks 
 Dim song, album, artist, rating, genre, year, playedCount, playedDate, albumAndArtist, trackTime, trackTimeInSeconds, dateAdded
 song   = objTrack.Name 
 album  = objTrack.Album 
 artist = objTrack.Artist 
 rating = objTrack.Rating 
 genre  = objTrack.Genre 
 year   = objTrack.Year 
 dateAdded = objTrack.DateAdded
 playedDate = objTrack.PlayedDate 
 playedCount = objTrack.PlayedCount 
 trackTime = objTrack.Time

 ' Calculate the total times:
 if isDate(trackTime) Then
  trackTimeInSeconds = (Hour(trackTime) * 60) + Minute(trackTime)
  totalTime = totalTime + trackTimeInSeconds 
  totalPlayedTime = totalPlayedTime + (trackTimeInSeconds * playedCount)
 end if
   
 ' Track the number of songs per artist: 
 if dictArtistsSongs.exists(artist) = true then 
  dictArtistsSongs(artist) = dictArtistsSongs(artist) + 1 
 else 
  dictArtistsSongs.Add artist,1 
 end if 

 ' Track the number of songs per rating:
 select case rating
  case 0
    rating = "None"
  case 20
    rating = "*"
  case 40
    rating = "**"
  case 60
    rating = "***"
  case 80
    rating = "****"
  case 100
    rating = "*****"
  case else
    rating = "None"
 end select
  
 if dictRatingsSongs.exists(rating) = true then 
  dictRatingsSongs(rating) = dictRatingsSongs(rating) + 1 
 else 
  dictRatingsSongs.Add rating,1 
 end if 
  
 ' Track the number of songs per genre: 
 if dictGenreSongs.exists(genre) = true then 
  dictGenreSongs(genre) = dictGenreSongs(genre) + 1 
 else 
  dictGenreSongs.Add genre,1 
 end if 

 ' Track the number of songs per year: 
 year = Left(year, 3) + "0s"
 if dictYearSongs.exists(year) = true then 
  dictYearSongs(year) = dictYearSongs(year) + 1 
 else 
  dictYearSongs.Add year,1 
 end if 

 ' Track the number of unique albums by combining album and artist name 
 ' This will ensure that albums such as "Greatest Hits" are tracked 
 ' individually by artist. We can also use this to track the song count
 ' per album, which will be used for the overall album play count later.
 albumAndArtist = album & "    (" & artist & ")" 
 if dictAlbums.exists(albumAndArtist) = true then 
  dictAlbums(albumAndArtist) = dictAlbums(albumAndArtist) + 1 
 else 
  dictAlbums.Add albumAndArtist,1 
 end if 

 ' Track the album play counts by storing lowest song play count per album:
 if dictAlbumPlaycounts.exists(albumAndArtist) = true then 
  if dictAlbumPlaycounts(albumAndArtist) > playedCount Then
   dictAlbumPlaycounts(albumAndArtist) = playedCount
  end if
 else 
  dictAlbumPlaycounts.Add albumAndArtist,playedCount 
 end if

 songAndArtist = song & "    (" & artist & ")" 

 ' Track the song counts:
 if dictPlayedCounts.exists(songAndArtist) = false then
  dictPlayedCounts.Add songAndArtist,playedCount
 end if 
 playCount = playCount + playedCount
 
 ' Track the song counts by album: 
 if dictAlbumTotalPlaycount.exists(albumAndArtist) = true then 
  dictAlbumTotalPlaycount(albumAndArtist) = dictAlbumTotalPlaycount(albumAndArtist) + playedCount 
 else 
  dictAlbumTotalPlaycount.Add albumAndArtist,playedCount 
 end if 

 ' Track the song counts by artist: 
 if dictArtistPlaycounts.exists(artist) = true then 
  dictArtistPlaycounts(artist) = dictArtistPlaycounts(artist) + playedCount 
 else 
  dictArtistPlaycounts.Add artist,playedCount 
 end if 
 
  ' Track the song played dates: 
 if dictPlayedDates.exists(songAndArtist) = false then
  dictPlayedDates.Add songAndArtist,playedDate
 end if 

 ' Track the plays by hour of the day: 
 playedHour = datePart("h",playedDate)
 'if Not IsEmpty(playedHour) and Not IsNull(playedHour) Then playedHour = CInt(playedHour)
 if dictPlaysByHour.exists(playedHour) = true then
  dictPlaysByHour(playedHour) = dictPlaysByHour(playedHour) + playedCount 
 else
  dictPlaysByHour.Add playedHour,playedCount
 end if 
 
 songCount = songCount + 1 
Next 


' Create the File System Object:
Set objFSO = CreateObject("Scripting.FileSystemObject")

' ForAppending = 8 ForReading = 1, ForWriting = 2
Set objTextFile = objFSO.OpenTextFile(strFileName, 2, True)

' Writes information to the file:
objTextFile.WriteLine("Number of unique tracks: " & FormatNumber(songCount,0))
objTextFile.WriteLine("Number of tracks played:  " & FormatNumber(playCount,0))
objTextFile.WriteLine("Average play count per track:  " & FormatNumber(playCount / songCount,1))
objTextFile.WriteLine("Number of unique albums: " & FormatNumber(dictAlbums.Count,0))
objTextFile.WriteLine("Number of unique artists: " & FormatNumber(dictArtistsSongs.Count,0))
objTextFile.WriteLine()

' Calculate and print various duration information:
Dim intDays, intHours, intMinutes, intSeconds, averageDuration

averageDuration = totalTime \ songCount
intMinutes = averageDuration \ 60
intSeconds = averageDuration Mod 60
objTextFile.WriteLine("Average track length: " & intMinutes & ":" & Right("0" & intSeconds,2))

intDays = totalTime \ 86400
intHours = (totalTime Mod 86400) \ 3600
intMinutes = (totalTime Mod 3600) \ 60
intSeconds = totalTime Mod 60
objTextFile.WriteLine("Total duration of all tracks: " & intDays & " days, " & intHours & " hours, " & intMinutes & " minutes, " & intSeconds & " seconds")

intDays = totalPlayedTime \ 86400
intHours = (totalPlayedTime Mod 86400) \ 3600
intMinutes = (totalPlayedTime Mod 3600) \ 60
intSeconds = totalPlayedTime Mod 60
objTextFile.WriteLine("Total played time of all tracks played: " & intDays & " days, " & intHours & " hours, " & intMinutes & " minutes, " & intSeconds & " seconds")
objTextFile.WriteLine()

if Int(intDisplayTopSongs) > Int(songCount) Then
 intDisplayTopSongs = songCount
End If

if Int(intDisplayTopAlbums) > Int(dictAlbums.Count) Then
 intDisplayTopAlbums = dictAlbums.Count
End If

if Int(intDisplayTopGenres) > Int(dictGenreSongs.Count) Then
 intDisplayTopGenres = dictGenreSongs.Count
End If

if Int(intDisplayTopArtists) > Int(dictArtistPlaycounts.Count) Then
 intDisplayTopArtists = dictArtistPlaycounts.Count
End If

if Int(intDisplayTopYears) > Int(dictYearSongs.Count) Then
 intDisplayTopYears = dictYearSongs.Count
End If

' Print the rating information:
objTextFile.WriteLine("Total number of unique ratings: " & dictRatingsSongs.Count)
objTextFile.WriteLine("Rating:         Number of Tracks:")
objTextFile.WriteLine("---------------------------------------------")
Dim strRating 
SortDictionary dictRatingsSongs,1,SORT_BY_STRING
strRating = dictRatingsSongs.keys 
intCount = dictRatingsSongs.Items 
for i = 0 To dictRatingsSongs.Count - 1
  objTextFile.WriteLine(Pad(strRating(i),12) & RightPad(FormatNumber(intCount(i),0),8) & " (" & FormatPercent((intCount(i) / songCount),1) & ")") 
next 
objTextFile.WriteLine()

' Print the genre information:
objTextFile.WriteLine("Total number of unique genres: " & dictGenreSongs.Count)
objTextFile.WriteLine("Genre:                              Number of Tracks:")
objTextFile.WriteLine("---------------------------------------------------------")
Dim strGenre 
SortDictionary dictGenreSongs,2,SORT_BY_NUMBER
strGenre = dictGenreSongs.keys 
intCount = dictGenreSongs.Items 
for i = 0 To intDisplayTopGenres - 1
  objTextFile.WriteLine(RightPad(i+1,3) & ". " & Pad(strGenre(i),30) & RightPad(FormatNumber(intCount(i),0),8) & " (" & FormatPercent((intCount(i) / songCount),1) & ")") 
next 
objTextFile.WriteLine()

' Print the year information:
objTextFile.WriteLine("Decade:           Number of Tracks:")
objTextFile.WriteLine("---------------------------------------------")
SortDictionary dictYearSongs,2,SORT_BY_NUMBER
strYear = dictYearSongs.keys 
intCount = dictYearSongs.Items 
for i = 0 To intDisplayTopYears - 1 
  objTextFile.WriteLine(RightPad(i+1,3) & ". " & Pad(strYear(i),12) & RightPad(FormatNumber(intCount(i),0),8) & " (" & FormatPercent((intCount(i) / songCount),1) & ")") 
next 
objTextFile.WriteLine()

' Print the most-played songs information:
objTextFile.WriteLine("                                     * MOST-PLAYED TRACKS *")
objTextFile.WriteLine("Song:                                                           Play Count:  Last Played:")
objTextFile.WriteLine("---------------------------------------------------------------------------------------------------")
SortDictionary dictPlayedCounts,2,SORT_BY_NUMBER
strSong = dictPlayedCounts.keys 
intCount = dictPlayedCounts.Items 

for i = 0 To intDisplayTopSongs - 1
  objTextFile.WriteLine(RightPad(i+1,3) & ".  " & Pad(strSong(i),60) & RightPad(FormatNumber(intCount(i),0),8) & RightPad(dictPlayedDates.Item(strSong(i)),24)) 
next 
objTextFile.WriteLine()

' Print the most-played songs information:
objTextFile.WriteLine("                    * MOST-PLAYED ENTIRE ALBUMS (MINIMUM " & intMinSongsOnAlbum & " SONGS PER ALBUM) *")
objTextFile.WriteLine("Album:                                                           Album Plays:  Number of Songs:  Total Song Play Count:")
objTextFile.WriteLine("-----------------------------------------------------------------------------------------------------------------------")
SortDictionary dictAlbumPlaycounts,2,SORT_BY_NUMBER
strAlbum = dictAlbumPlaycounts.keys 
intCount = dictAlbumPlaycounts.Items 
Dim songsOnAlbum, topAlbumCount, totalSongPlayCount
topAlbumCount = 1
i = 0
Do Until topAlbumCount = intDisplayTopAlbums
  songsOnAlbum = dictAlbums.Item(strAlbum(i))
  totalSongPlayCount = dictAlbumTotalPlaycount.Item(strAlbum(i))

  ' Display only albums with a specific number of songs on them:
  If songsOnAlbum >= intMinSongsOnAlbum Then
   objTextFile.WriteLine(RightPad(topAlbumCount,3) & ".  " & Pad(strAlbum(i),60) & RightPad(FormatNumber(intCount(i),0),8) & RightPad(FormatNumber(songsOnAlbum,0),12)) & RightPad(FormatNumber(totalSongPlayCount,0),28)
    topAlbumCount = topAlbumCount + 1
  End If 
  
  i = i + 1
  If topAlbumCount > dictAlbums.Count Then Exit Do
  If i = dictAlbums.Count Then Exit Do
Loop 
objTextFile.WriteLine()

' Print the play counts, songs, and average per artist:
objTextFile.WriteLine("                            * PLAY COUNTS / TRACKS PER ARTIST *")
objTextFile.WriteLine("Artist:                                            Play Count:    Songs:   Average:")
objTextFile.WriteLine("-----------------------------------------------------------------------------------")
SortDictionary dictArtistPlaycounts,2,SORT_BY_NUMBER
Dim avgPerArtist
strArtist = dictArtistPlaycounts.keys 
intCount = dictArtistPlaycounts.Items 
for i = 0 To intDisplayTopArtists - 1

 if dictArtistsSongs.Item(strArtist(i)) > 0 then
  avgPerArtist = intCount(i)/dictArtistsSongs.Item(strArtist(i))
 else
  avgPerArtist = 0
 end if

  objTextFile.WriteLine(RightPad(i+1,3) & ".  " & Pad(strArtist(i),45) & RightPad(FormatNumber(intCount(i),0),8) & RightPad(FormatNumber(dictArtistsSongs.Item(strArtist(i)),0),12) & RightPad(FormatNumber(avgPerArtist,1),12)) 
next 
objTextFile.WriteLine()

' Print the distribution of plays by hour:
objTextFile.WriteLine("Hour:    Play Count:")
objTextFile.WriteLine("----------------------")
SortDictionary dictPlaysByHour,1,SORT_BY_NUMBER_ASC
playedHour = dictPlaysByHour.keys 
intCount = dictPlaysByHour.Items
Dim playedHourFormatted 
for i = 0 To dictPlaysByHour.Count - 1

 If (playedHour(i) = 0) Then
  playedHourFormatted = "12 AM"
 ElseIf playedHour(i) < 12 Then
  playedHourFormatted = CStr(playedHour(i)) + " AM"
 ElseIf playedHour(i) = 12 Then
  playedHourFormatted = "12 PM"
 Else
  playedHourFormatted = CStr(playedHour(i) Mod 12) + " PM"
 End If 

 objTextFile.WriteLine(RightPad(playedHourFormatted,5) & "    " & RightPad(FormatNumber(intCount(i),0),6) & " (" & FormatPercent((intCount(i) / playCount),1) & ")") 
next 
objTextFile.WriteLine()

' Print the most-recently added songs information:
objTextFile.WriteLine("                             * MOST-RECENTLY-PLAYED TRACKS *")
objTextFile.WriteLine("Song:                                                             Play Date:")
objTextFile.WriteLine("-----------------------------------------------------------------------------------------")
SortDictionary dictPlayedDates,2,SORT_BY_NUMBER
strSong = dictPlayedDates.keys 
intCount = dictPlayedDates.Items 
for i = 0 To intDisplayTopSongs - 1
  objTextFile.WriteLine(RightPad(i+1,3) & ".  " & Pad(strSong(i),60) & intCount(i)) 
next 
objTextFile.WriteLine()

' Print the most-recently added songs information:
objTextFile.WriteLine("                            * LEAST-RECENTLY-PLAYED TRACKS *")
objTextFile.WriteLine("Song:                                                             Play Date:")
objTextFile.WriteLine("-----------------------------------------------------------------------------------------")
for i = (dictPlayedDates.Count - 1) To (dictPlayedDates.Count - intDisplayTopSongs) Step -1
  objTextFile.WriteLine(RightPad(i+1,5) & ".  " & Pad(strSong(i),60) & intCount(i)) 
next 

objTextFile.Close
'Wscript.Echo "Information is now in " & strFileName

' Automatically launch the file when it is done:
Set objApp = CreateObject("WScript.Shell")
objApp.Run strFileName 

' Sort the arrays: 

WScript.Quit
