Jan
23
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