A while ago (a long while ago now!) I played around with creating a database for storing my iTunes tracks. The reason behind it was simple: I wanted to be able to synchronize playlists and last-played data between copies on different computers.

More importantly, I wanted to learn more about working with external COM interfaces.

To that end, I built a Notes application and wrote the attached script library.

One should never apologize for their code, especially if it works, but I will point out that I never polished it up, so you may see some clunky bits. You’re welcome to play with it, adapt it to your own needs, or ignore it!

Anyway, attached is a LotusScript file that walks through your collection of tracks and creates or updates matching Notes documents. It requires the iTunes SDK version 7.7 or later (available from Apple),

Code is offered, of course, without warranty, guarantee, or support of any kind.

NOTE: For some reason I’m not allowed to upload text or lss or zip files, so the code is included in plain text below.

Option Public
Option Declare

Use "Custom Error Handling"
Use "Standard Functions v4"
Use "Cycle Value Handling v3"
Sub Initialize

 ' REQUIRES iTunes version 7.7 or later because of Persistent IDs

 On Error Goto errHandler

 Print "Started BuildEntriesFromiTunes()"

 Print "Launching iTunes (if not already running) ... this may take a moment ..."

 Call BuildEntriesFromiTunes()    

 Print "BuildEntriesFromiTunes() done"

 Print ""

 Goto Done

errHandler:
 Call ErrorMessage( Err, Erl, Error$, Lsi_info( 2 ), True )
 Resume Done
Done:

End Sub
Private Function GetLineFromTrack( _
iTunes As Variant, _
itTrack As Variant, _
varArray As Variant _
) As String

 ' build a pipe-separated string containing data of interest for a track

 ' if the track is invalid, exit with nothing in the string

 On Error Goto errHandler

 Dim i As Integer
 Dim intLen As Integer
 Dim strOut As String

 Const PIPE = "|"

 ' if we don't have a valid input track, bail
 If itTrack Is Nothing Then
 Print "Invalid track discovered"
 Exit Function
 End If

 Forall v In varArray
 strOut = strOut & GetTrackValue( iTunes, itTrack, Cstr( v ) ) & PIPE
 End Forall

 ' kill trailing PIPE symbol
 If Not Len( strOut ) = 0 Then
 strOut = Left$( strOut, Len( strOut ) - 1 )
 End If

 GetLineFromTrack = strOut

 Goto Done

errHandler:
 Call ErrorMessage( Err, Erl, Error$, Lsi_info( 2 ), False )
 Resume Done
Done:

End Function
Private Function GetTrackValue( iTunes As Variant, _
itTrack As Variant, strProperty As String ) As String

 ' given an iTunes Track input and the name of a track property, return
 ' the value for that property

 ' if property has no value, return empty string to save space

 On Error Goto errHandler

 Dim strTag As String
 Dim strValue As String
 Dim strCRLF As String

 Const LT = "<"
 Const LTSLASH = "</"
 Const GT = ">"
 Const COLON = ":"

 If itTrack Is Nothing Then
 GetTrackValue = ""
 Exit Function
 End If

 strCRLF = Chr$( 13 ) & Chr$( 10 )

 strTag = Lcase$( strProperty ) ' default

 ' case is root of field name before preprocessing
 ' ex:  "skip count" becomes field named "skip_count_tx"
 ' value is Track property

 Select Case Lcase$( strProperty )

 Case "name": strValue = itTrack.Name

 Case "composer": strValue = itTrack.Composer
 Case "album": strValue = itTrack.Album
 Case "grouping": strValue = itTrack.Grouping
 Case "genre": strValue = itTrack.Genre
 Case "size": strValue = itTrack.Size
 Case "time": strValue = itTrack.Time
 Case "disc number": strValue = itTrack.DiscNumber
 Case "disc count": strValue = itTrack.DiscCount        
 Case "track number": strValue = itTrack.TrackNumber
 Case "track count": strValue = itTrack.TrackCount
 Case "year": strValue = itTrack.Year
 Case "date modified": strValue = itTrack.ModificationDate
 Case "date added": strValue = itTrack.DateAdded
 Case "bit rate": strValue = itTrack.BitRate
 Case "sample rate": strValue = itTrack.SampleRate
 Case "volume adjustment": strValue = itTrack.VolumeAdjustment
 Case "kind": strValue = itTrack.KindAsString
 Case "equalizer": strValue = itTrack.EQ
 Case "comments": strValue = itTrack.Comment
 Case "play count": strValue = itTrack.PlayedCount
 Case "last played": strValue = itTrack.PlayedDate
 Case "skip count": strValue = itTrack.SkippedCount
 Case "last skipped": strValue = itTrack.SkippedDate
 Case "my rating": strValue = itTrack.Rating

 Case "location":
 strValue = itTrack.Location
 If InStr( strValue, COLON ) > 0 Then
 strValue = StrRight( strValue, COLON ) ' get rid of drive letter because of different storage paths
 End If

 Case "trackid": strValue = itTrack.TrackID
 Case "trackdatabaseid": strValue = itTrack.TrackDatabaseID
 Case "internal_index": strValue = itTrack.Index
 Case "playorder_index": strValue = itTrack.PlayOrderIndex        
 Case "artist": strValue = itTrack.Artist

 Case "persistent id low": strValue = Hex$( iTunes.ITObjectPersistentIDHigh( itTrack ) )
 Case "persistent id high": strValue = Hex$( iTunes.ITObjectPersistentIDLow( itTrack ) )

 Case "playlists": strValue = "" ' get this later

 Case Else:
 strValue = "Unrecognized Tag '" & strProperty & "'"
 End Select

GetTrackValue = Trim$( strValue ) 

 Goto Done

errHandler:
 Print "Failed for property: " & strProperty
 Call LogErrorEx( "Failed for property: " & strProperty, "FAIL", Nothing )
 Print "Track name: " & itTrack.Name
 Call LogErrorEx( "Track name: " & itTrack.Name, "FAIL", Nothing )

 Print "Track location: " & itTrack.Location
 Call LogErrorEx( "Track location: " & itTrack.Location, "FAIL", Nothing )

 Call ErrorMessage( Err, Erl, Error$, Lsi_info( 2 ), False )
 Resume Done
Done:

End Function
Private Function BuildEntriesFromiTunes() As Boolean

 On Error Goto errHandler

 Const DEBUG = False

 Dim strMsgList List As String

 Dim strLine As String
 Dim strRaw As String
 Dim strRawInDoc As string
 Dim strLocation As String
 Dim strPlayLists As String

 Dim i As Long
 Dim j As Long
 Dim intTracks As Long
 Dim intPL As Integer

 Dim varArray As Variant ' array for holding individual track values
 Dim varRoots As Variant ' root names for iTunes property searches
 Dim varFields As Variant ' array for holding field names

 Dim lngCount As Long
 Dim lngCreated As Long    
 Dim lngUpdated As Long
 Dim strKey As String
 Dim boolCreated As Boolean
 Dim boolOneFound As boolean

 Dim session As New NotesSession
 Dim doc As notesdocument
 Dim db As NotesDatabase
 Dim view As NotesView
 Dim dc As NotesDocumentCollection

 Dim iTunes As Variant
 Dim itTracks As Variant
 Dim itTrack As Variant
 Dim itList As Variant
 Dim itSubList As Variant

 Dim itSources As Variant
 Dim itSource As Variant
 Dim itPlayLists As Variant

 Const FIELD_ROOTS = "Name,Artist,Composer,Album,Grouping," _
 & "Genre,Size,Time,Disc Number,Disc Count,Track Number," _
 & "Track Count,Year,Date Modified,Date Added,Bit Rate," _
 & "Sample Rate,Volume Adjustment,Kind,Equalizer,Comments," _
 & "Play Count,Last Played,Skip Count,Last Skipped," _
 & "My Rating,Location,Persistent ID Low,Persistent ID High,PlayLists"
'    & "My Rating,Location,TrackID,TrackDatabaseID,Internal_Index,PlayOrder_Index" 

 ' not used for array, hence wrapping commas
 Const SKIP_LISTS = ",Music,My Bulk Sort,Library,My Collections,"

 Const COMMA = ","
 Const PIPE = "|"
 Const COLON = ":"
 Const MAX_TRACKS = 30

 Set db = session.currentdatabase
 Set view = db.GetView( "Location" )

 varRoots = Split( FIELD_ROOTS, COMMA )
 varFields = Split( FIELD_ROOTS, COMMA )

 ' define field names
 For i = 0 To Ubound( varFields )
 varFields( i ) = _
 ReplaceSubstring( varFields( i ), " ", "_" ) & "_TX"
 Next

 Set iTunes = CreateObject( "iTunes.Application" )

 Set itSources = iTunes.Sources
 Set itSource = itSources.ItemByName( "Library" )
 Set itList = iTunes.LibraryPlayList()    

 intTracks = itList.Tracks.Count

 lngCount = intTracks

 For j = 1 To intTracks

 ' bail out quickly if we're debugging the code
 If DEBUG Then
 If j > MAX_TRACKS Then
 Print "[" & i & "] " _
 & itList.Name & " has more than " & MAX_TRACKS _
 & " tracks ... skipping the rest for now"
 Exit For
 End If
 End If

 Set itTrack = itList.Tracks.Item( j )

 Stop

 If itTrack Is Nothing Then

 Print "Track is 'Nothing' in BuildEntriesFromiTunes() function of ImportMusicCOM agent"
 strMsgList( lngCount ) = "Invalid track encountered - Track is 'Nothing'"

 Else        
 ' get pipe-separated string of info about the track
 ' if track is nothing, strLine is empty string
 strLine = GetLineFromTrack( iTunes, itTrack, varRoots )

 Stop

 boolCreated = False

 strRaw = strLine
 strRawInDoc = "Bogus Value to Start ||||||||||||||||||||||"

 varArray = Split( strLine, PIPE )

 strLocation = Lcase$( Trim$( itTrack.Location ) )

 If InStr( strLocation, COLON ) > 0 Then
 strLocation = StrRight( strLocation, COLON ) ' ignore drive letters
 End If

 ' if no location, delete from iTunes
 If strLocation = "" Then
 Print "DELETING missing track: " & itTrack.Name & " by " & itTrack.Artist
 strMsgList( lngCount ) = "DELETING missing track: " & itTrack.Name & " by " & itTrack.Artist

 Call itTrack.Delete

 Redim varArray( 0 ) ' force next part to get skipped

 Stop

 Set itPlayLists = itTrack.Playlists()
 strPlayLists = ""
 If itPlayLists.Count > 0 Then
 For intPL = 1 To itPlayLists.Count
 Set itSubList = itPlayLists( intPL )
 strKey = COMMA & itSubList.Name & COMMA

 ' look the other way!
 If Instr( SKIP_LISTS, strKey ) = 0 Then
 strPlayLists = strPlayLists & itSubList.Name & COMMA
 End If

 Next
 If strPlayLists <> "" Then            
 strPlayLists  = Left$( strPlayLists, Len( strPlayLists ) - 1 )
 strRaw = strRaw & PIPE & strPlayLists
 End If
 End If
 End If

 boolOneFound = False ' reset
 If Ubound( varArray ) = Ubound( varFields ) Then

 Set dc = view.GetAllDocumentsByKey( strLocation )

 If dc.Count = 1 Then

 boolOneFound = true
 Set doc = dc.GetFirstDocument()
 strRawInDoc = doc.RawData_TX( 0 )

 ' get rid of pers IDs and if found
 strRawInDoc = StrLeftBack( StrLeftBack( strRawInDoc, PIPE ), PIPE )
 strRawInDoc = strRawInDoc & PIPE & doc.Plalists_TX( 0 )
 strRaw = StrLeftBack( StrLeftBack( strRaw, PIPE ), PIPE )
 strRaw = strRaw & PIPE & strPlayLists

 Elseif dc.Count > 1 Then

 Print "ERROR: Multiple docs found for: "_
 & strLocation

 Call dc.RemoveAll( True ) ' get rid of all, create new

 Set doc = New NotesDocument( db )
 Call doc.ReplaceItemValue( "Form", "iTrack" )
 Call SetCycleValues( doc, "Created after erasing dupes" )

 Else
 Print "[" & lngCount & "] CREATING " & strRaw
 boolCreated = True
 lngCreated = lngCreated + 1
 Set doc = New NotesDocument( db )
 Call doc.ReplaceItemValue( "Form", "iTrack" )
 Call SetCycleValues( doc, "Created" )

 End If

 ' don't update unless something changed
 If strRawInDoc = strRaw Then
 Print "[" & lngCount & "] SKIPPING " & strRaw
 Else
 lngUpdated = lngUpdated + 1
 If Not boolCreated Then
 Print "[" & lngCount & "] UPDATING " & strRaw
 End If

 Call doc.ReplaceItemValue( "RawDataPrev_TX", doc.RawData_TX( 0 ) )
 Call doc.ReplaceItemValue( "RawData_TX", strRaw )

 For i = 0 To Ubound( varArray )
 Call doc.ReplaceItemValue( varFields( i ), _
 varArray( i ) )
 Next
 Call doc.ReplaceItemValue( "PlayLists_TX", strPlayLists )
 Call SetLastCycleValue( doc, "Last Updated" )

 Call doc.save( True, False )
 End If
 End If
 End If

 Stop

Next_Track:

 lngCount = lngCount - 1

 Next

 strRaw = ""
 Forall s In strMsgList
 strRaw = strRaw & s & Chr$( 10 )
 End Forall

 If strRaw <> "" Then
 Print strRaw
 Messagebox strRaw, 64, "iTunes"
 End If

 Print "Done: Created " & lngCreated & ", updated " & lngUpdated _
 & ", out of " & lngCount & " total tracks"

 Goto Done

errHandler:

 If Instr( Error$, "The track has been deleted." ) > 0 Then
 Print "THE TRACK HAS BEEN DELETED."        
 Resume Next_Track
 End If

 Call ErrorMessage( Err, Erl, Error$, Lsi_info( 2 ), False )
 Resume Done
Done:

End Function

No comments

Comments are closed.