iTunes & Traktor Collection

Patch's Avatar

Patch

11 Jan, 2018 06:24 PM

I’m using BeaTunes and love it.

I’ve been searching for a long time for a way to manipulate the metadata that Traktor creates and stores in iTunes.

For example - I’d love to create an iTunes smart playlist that is based on the number of times a track has been played in Traktor.

It would mean comparing the Traktor Collection .xml with the iTunes Library .xml, and updating the iTunes .nml with the Traktor playcount from the Collection .xml.

Could BeaTunes be the bridge that sits between Traktor and iTunes, to allow us to manipulate the Traktor metadata in iTunes???

Showing page 3 out of 4. View the first page

  1. Support Staff 61 Posted by hendrik on 16 May, 2019 06:28 AM

    hendrik's Avatar

    Do you know how XML is structured? The .nml file is written in XML.
    XML consists of tags also called elements. They follow the format:

    <NAME attributeName="attributeValue">potential text</NAME>
    

    An XML element starts with <NAME> and ends with </NAME>. It may have one or more attributes each consisting of attributeName and attributeValue. It's very similar to HTML.

    The code in the beaTlet is structured in functions. This particular beaTlet is written in the scripting language Groovy (there are other options, but let's stick to this one for now). Each function follows the form:

    def returnType functionName(ArgumentType argument, ...) {
        // function code <-- this is a comment
    }
    

    Note that anything after // is ignored by the program. It's just a comment.

    The given beaTlet already has some code in there to read the XML element <INFO>. This code is in the function importInfo. Here's the original function with additional comments that show how to add genre import:

    def boolean importInfo(StartElement element, String location) {
        AudioSong song = null
        try {
            // read some attributes from the <INFO> element
            // first read the attribute named PLAYCOUNT, i.e. <INFO PLAYCOUNT="someValue" ...>
            Attribute playcount = element.getAttributeByName(new QName("PLAYCOUNT"))
            // read the attribute named LAST_PLAYED, i.e. <INFO LAST_PLAYED="someValue" ...>
            Attribute lastPlayed = element.getAttributeByName(new QName("LAST_PLAYED"))
            // probably for historic reasons, Traktor calls comment 2 "rating"
            // in its .nml file.
            // we map its contents to the field custom1.
            // read the attribute named RATING, i.e. <INFO RATING="someValue" ...>
            Attribute rating = element.getAttributeByName(new QName("RATING"))
    
            // *** if you want to read additional attributes, do so here *** //
            // e.g. for genre something like:
            // Attribute genre = element.getAttributeByName(new QName("GENRE"))
    
            // the attributeValues for PLAYCOUNT, LAST_PLAYED, and RATING are now
            // stored in the variables named playcount, lastPlayed, and rating
            // sometimes <INFO ...> does not contain the desired attributes, because they are optional
            // if <INFO ...> does not contain PLAYCOUNT, the variable play count will be "null"
            // in case none of the desired attributes is present, skip further processing for efficiency.
    
            // *** if you want to add some new attribute, add it to the following list in the "if" statement *** //
            // i.e. add something like "|| genre != null"
            // note that the || means "or" (logical operator)
            if (playcount != null || lastPlayed != null || rating != null) {
                // at this point we are certain that at least ONE of the desired attributes is present
                // because we definitely are going to import something, we now need to look up
                // the song object in beaTunes. we can do that via its location (file path).
                song = getSong(location)
                // check, if we really found the song. if we can't find it, it's "null"
                if (song != null) {
                    // we have found the song object in beaTunes and can now proceed to manipulate it
                    // did we read a lastPlayed value? if so call the function setPlayDateUTC() on the song object
                    // calling song.setPlayDateUTC(...) effectively writes the value to beaTunes and if possible iTunes.
                    if (lastPlayed != null) {
                        log.debug("Setting playdate for " + song + ": " + lastPlayed.getValue())
                        song.setPlayDateUTC(new SimpleDateFormat("yyyy/M/d").parse(lastPlayed.getValue()))
                    }
                    // now check whether we have found a play count value
                    if (playcount != null) {
                        // apparently we found one. let's write it via song.setPlayCount()
                        log.debug("Setting playcount for " + song + ": " + playcount.getValue())
                        song.setPlayCount(Integer.valueOf(playcount.getValue()))
                    }
                    // now check whether we have found a rating/comment2 value (weird Traktor naming)
                    if (rating != null) {
                        // apparently we found one. let's write it via song.setCustom1()
                        log.debug("Setting rating/comment2/custom1 for " + song + ": " + rating.getValue())
                        song.setCustom1(rating.getValue())
                    }
                    // to add the genre import, do something like this:
                    // if (genre != null) {
                    //      // apparently we found one. let's write it via song.setGenre()
                    //     log.debug("Setting genre for " + song + ": " + genre.getValue())
                    //     song.setGenre(genre.getValue())
                    // }
                } else {
                    log.info("Failed to find song for location " + location)
                }
            }
        } catch(Exception e) {
            log.error("Failed to import info for " + song, e)
        }
    }
    

    So to add import capability for genre, you have to effectively add code in three places:

    1. read the attribute
    2. check whether at least one of the desired attributes was present (that if statement)
    3. check again, whether the specific attribute is present and then write it via some setXXX function

    Very important: The shown code depends on additional attributes being part of the <INFO> element. If they occur outside the <INFO> element, other changes are necessary.

    I can recommend using Atom for coding this, as it does syntax highlighting, which is helpful. For a syntax highlighted version of the code above, see https://gist.github.com/hendriks73/736be182cb221b75896898da7127295d It's much easier to read, as the (non-functional) comments are gray.

    Good luck!

    PS: If you encounter issues, please always attach your complete .groovy file to the message, so I can see what you have done.

  2. 62 Posted by Patch on 16 May, 2019 10:18 AM

    Patch's Avatar

    I had a go at coding this last night. I had some success - but I’ll revisit it using the info above.

    Thanks for the detailed explanation.

    I wanted to utilise the Tags “Programme” and “Sort Programme”, called “Series” and “Sort Series” in the iTunes Library respectively (according to Atom).

    Not entirely sure if these tags can be used for .mp3’s, since I can only add/edit them within iTunes (they don’t show up in MP3Tag, for example). If they can’t be saved to the file I’ll be stumped...

    Like I said - you’ve given me much more knowledge than I had last night, so I’ll have a go tonight.

  3. Support Staff 63 Posted by hendrik on 16 May, 2019 11:00 AM

    hendrik's Avatar

    so I’ll have a go tonight.

    Good luck! 🍀

  4. 64 Posted by Patch on 16 May, 2019 08:26 PM

    Patch's Avatar

    Woo-hoo!!! I think did it!!!

    I've got:

    (Traktor's) Mix tag showing up in the .mp3's Sort Programme/Sort Show in iTunes
    (Traktors) Remixer tag in the .mp3's Programme/Show tag in iTunes
    (Traktors) Comment2 tag in the .mp3's Movement tag in iTunes

    Honestly can't thank you enough for all of your help, Hendrik! This opens a LOT of possibilities with Smart Playlists in Traktor.

    Something did cross my mind here. Could we take existing tags from .mp3 files, and add them to the Traktor .nml using Beatlets???

  5. 65 Posted by Patch on 16 May, 2019 08:43 PM

    Patch's Avatar

    One other thing - the tags in the Sort Show column in BeaTunes are greyed out. What does that mean?

    Also, it looks like iTunes does not write the tags to the file, it must store them in it's own database. Because if I delete files from iTunes, then re-import them, the tags are gone.

    :-(

    Still very useful, though!

  6. Support Staff 66 Posted by hendrik on 17 May, 2019 11:59 AM

    hendrik's Avatar

    Woo-hoo!!! I think did it!!!

    Congrats! :-)

    Could we take existing tags from .mp3 files, and add them to the Traktor .nml using Beatlets???

    I am not sure whether Traktor ever reads from the .nml file and if so, when.

    One other thing - the tags in the Sort Show column in BeaTunes are greyed out. What does that mean?

    It's not usually mean to be edited in the table view. However, you can edit it in the Get Info dialog, just like in iTunes.

    Also, it looks like iTunes does not write the tags to the file,

    Really? That's odd. Perhaps not using the sort fields works better.

  7. 67 Posted by Patch on 17 May, 2019 01:52 PM

    Patch's Avatar

    Sort Programme & Programme/Sort Show and Show are .mp4 tags only. So you can use them for .mp3 files and write to them and read them in iTunes, but they’re stored in the iTunes Library file, not the .mp3 file.

    Can BeaTunes get info from files, or from the iTunes Library, and write them to the Traktor .nml? That would be amazing. The final piece of the puzzle for me!

  8. Support Staff 68 Posted by hendrik on 17 May, 2019 01:57 PM

    hendrik's Avatar

    Can BeaTunes get info from files, or from the iTunes Library, and write them to the Traktor .nml? That would be amazing. The final piece of the puzzle for me!

    In theory that's possible.

    But you assume that Traktor reads any information from the .nml file. Are you sure it does? What makes you so sure?

    iTunes for example, never reads from its iTunes Library.xml file.

  9. 69 Posted by Patch on 17 May, 2019 02:51 PM

    Patch's Avatar

    I get where you’re going, but how can we tell?

    So, does iTunes READ from its .itl file? That is to say, when we write a tag in iTunes it gets stored on its .xml file and it’s .itl file?

    Do we need to understand if Traktor has an equivalent of iTunes .itl file?

  10. Support Staff 70 Posted by hendrik on 17 May, 2019 02:53 PM

    hendrik's Avatar

    I get where you’re going, but how can we tell?

    You could modify the .nml file and see if Traktor picks it up somehow.

    So, does iTunes READ from its .itl file?

    The .itl file is iTunes' actually database. iTunes always stores data there and if possible in the audio file. If configured correctly, it dumps the XML file after each change.

  11. 71 Posted by Patch on 17 May, 2019 03:28 PM

    Patch's Avatar

    Right!!!

    Traktor DOES read from it's .nml file! I added a Tag (Comment) in MP3Tag ("Traktor READ nml Test") then opened the file in Traktor, and the Comment Tag got populated with that text.

    Then, I opened the Traktor .NML in Atom, and edited the entry next to Comment (Traktor WRITE nml Test"), then re-opened the file in Traktor, and presto! tag had been updated!

    After a check, I could see that the file Tag was not changed - so the file tag said READ, and the Traktor .nml Tag said WRITE.

    So - using BeaTunes, can we copy from iTunes, write to Traktor .nml?

  12. 72 Posted by Patch on 17 May, 2019 03:36 PM

    Patch's Avatar

    And I've just confirmed - once Traktor has an entry in the collection for an .mp3, it DOESN'T re-read the tags in the file the next time you play it!!! It takes the info from it's own Collection.nml.

    This is great!

  13. 73 Posted by Patch on 18 May, 2019 08:16 AM

    Patch's Avatar

    So, our original .groovy file takes text tags OUT of the Tractor Collection.nml, and moves it to iTunes which writes it to the .mp3 file. BeaTunes knows the location of the iTunes Library & .xml, and the .groovy asks for the Traktor Collection.nml

    Collection.nml = source
    iTunes/File Tags = destination

    How do we flip that so that iTunes/File is source, and Collection.nml is destination?

    I’m guessing it would need to be a separate Beatlet?

  14. Support Staff 74 Posted by hendrik on 18 May, 2019 08:37 AM

    hendrik's Avatar

    I’m guessing it would need to be a separate Beatlet?

    Yes, it could be.

    You'd have to iterate over the tracks in the .nml file and find the corresponding songs in beaTunes, just like the existing beaTlet does. Then you add the additional information to the .nml file (actually, I'd recommend writing to a different file first). To do so, first remember everything you want to export to .nml, then load the XML into memory (like a DOM), manipulate it, and serialize it. This is a bit of programming...

    BTW: I don't want to make my own product obsolete, but you don't need beaTunes for this. You just need to write a small script that reads the iTunes Library.xml file from beaTunes, matches it with the collection.nml file from Traktor, and then adds info to the Traktor file as needed. You could write this little program in pretty much any scripting language you want, e.g. Python, Ruby or JavaScript.

  15. 75 Posted by Patch on 18 May, 2019 10:56 AM

    Patch's Avatar

    Thanks Hendrik. I’d definitely like to keep this in BeaTunes. BeaTunes is a massive part of my preparation and keeping changes contained in your software is one of the things that make it indispensable to me!

    I’m gonna hit up a chap I know to see if he can code this for me, because, this is definitely outside of my coding ability.

  16. 76 Posted by Patch on 19 May, 2019 07:53 PM

    Patch's Avatar

    Back to the original Beatlet - is there a way to NOT over-write the iTunes field if there is already text in there, or if the text in there is different to rating field in the Traktor .nml? It would be around here in the code:

    }
    // now check whether we have found a rating/comment2 value (weird Traktor naming)
    if (rating != null) {
        // apparently we found one. let's write it via song.setCustom1()
        log.debug("Setting rating/comment2/Movement for " + song + ": " + rating.getValue())
        song.setMovement(rating.getValue())
    }
    

    It feels to me like we need to add an IF statement before song.setmovement (saying something like IF Movement is NOT null, do nothing, else song.setmovement(rating.getValue())

    Am I on the right lines here? Or am I waaaay off???

    Ideally, we'd have something like IF Movement is blank, set.movement(rating.getvalue(), IF movement is not equal to rating, set,movement(rating.getValue()

    I know the syntax above is WAAAY of, but can what I've described be done?

  17. Support Staff 77 Posted by hendrik on 20 May, 2019 07:33 AM

    hendrik's Avatar

    You'd do:

    }
    // now check whether we have found a rating/comment2 value (weird Traktor naming)
    if (rating != null && !rating.equals(song.getMovement())) {
        // apparently we found one. let's write it via song.setCustom1()
        log.debug("Setting rating/comment2/Movement for " + song + ": " + rating.getValue())
        song.setMovement(rating.getValue())
    }
    

    && means "and". The ! means "not". So if (rating != null && !rating.equals(song.getMovement())) { means:

    if rating exists und is not equal to song.getMovement(), then ...
    So this would only overwrite movement, if it is not already the same as rating.

    If you'd like to only overwrite it, when it's empty, you'd do:

    }
    // now check whether we have found a rating/comment2 value (weird Traktor naming)
    if (rating != null && (song.getMovement() == null || song.getMovement().isEmpty())) {
        // apparently we found one. let's write it via song.setCustom1()
        log.debug("Setting rating/comment2/Movement for " + song + ": " + rating.getValue())
        song.setMovement(rating.getValue())
    }
    

    || means "or". So this checks whether getMovement() has a value at all or whether the value it may have is empty, i.e. a string of length 0.

  18. 78 Posted by Patch on 20 May, 2019 11:58 AM

    Patch's Avatar

    How would we over-write Movement, but ONLY when it is empty, OR when it is populated but different to the text in rating?

    Reason for this, is that I will not type anything new directly into Movement, but I do already have a lot of files with Movement already populated. So I may update rating, and I'd like that to over-write existing text in Movement, or populate Movement if it is blank.

  19. Support Staff 79 Posted by hendrik on 22 May, 2019 09:24 AM

    hendrik's Avatar

    How would we over-write Movement, but ONLY when it is empty, OR when it is populated but different to the text in rating?

    Because we ONLY write the rating when it is not empty, it seems sufficient to check whether the current movement value is different from rating and rating is not empty, which is exactly the first example I gave:

    if (rating != null && !rating.equals(song.getMovement())) {
    

    Boolean logic is not that hard. Just use && and ||, set your parenthesis right, and you're golden! :-)

  20. 80 Posted by Patch on 22 May, 2019 03:25 PM

    Patch's Avatar

    You've been SO helpful, Hendrik.

    Finally - can we populate iTunes Rating (5 star scale) with info from Traktors Rating (also 5 star scale)?

  21. Support Staff 81 Posted by hendrik on 22 May, 2019 03:34 PM

    hendrik's Avatar

    Finally - can we populate iTunes Rating (5 star scale) with info from Traktors Rating (also 5 star scale)?

    How does Traktor expose its rating. Can you give an example from the .nml file?

  22. 82 Posted by Patch on 22 May, 2019 04:08 PM

    Patch's Avatar

    It's stored as an integer in RANKING:

    0* = 0
    1* = 51
    2* = 102
    3* = 153
    4* = 204
    5* = 255

    ex.: Ranking="153"

  23. Support Staff 83 Posted by hendrik on 23 May, 2019 07:17 AM

    hendrik's Avatar

    OK, that means you have to get the attribute like this (assuming, it is an <INFO> attribute):

    Attribute ranking = element.getAttributeByName(new QName("RANKING"))
    

    Add the variable ranking to this list:

    if (playcount != null || lastPlayed != null || rating != null || ranking != null) {
    

    and then set it like this:

    if (ranking != null) {
        log.debug("Setting rating/ranking for " + song + ": " + ranking.getValue())
        // convert string with 0-255 to integer with 0-100:
        int r = java.lang.Math.round(Integer.valueOf(ranking.getValue()) / 2.55f)
        song.setRating(r)
    }
    

    Disclaimer: the code is untested.

    This should lead to the same star ratings. however, it may be off a little, depending on how the mapping integer -> stars is implemented.

  24. 84 Posted by Patch on 23 May, 2019 07:57 AM

    Patch's Avatar

    I shall give it a try. 

  25. 85 Posted by Patch on 23 May, 2019 07:19 PM

    Patch's Avatar

    Simply put - you are THE MAN. :-)

  26. 86 Posted by Patch on 23 May, 2019 09:34 PM

    Patch's Avatar

    Hendrik - would you consider (as paid work) writing a Beatlet that would copy information from iTunes, and write it to the corresponding entries in the Traktor .nml?

    I really want to baseline the metadata in my music collection, and I'd really need all of the information currently held in the tags to be available in Traktor.

    We touched on it above, but it feels very much out of my ability to create it...

  27. Support Staff 87 Posted by hendrik on 25 May, 2019 08:53 AM

    hendrik's Avatar

    I'm sorry, but I will have virtually no time until July for such a thing and even then I might not get to it.

    So, if you want this quickly, you will probably be better off asking someone else. It's not rocket science (for a seasoned programmer).

  28. 88 Posted by Patch on 25 May, 2019 09:26 AM

    Patch's Avatar

    Understood.

    I've just spotted this discussion:

    http://help.beatunes.com/discussions/plugin/1837-embed-ratings-in-file-from-itunes

    So right now, the script that you've been helping me with in this thread takes the Star Rating (called Ranking) from the Traktor .nml, and copies that info to the itunes database (Star Rating). Great!

    Would using the EmbedRatings.groovy listed in the discussion above take that Star Rating (originally input in Traktor, then copied to iTunes) and embed it in the audio file???

  29. 89 Posted by Patch on 25 May, 2019 09:27 AM

    Patch's Avatar

    Sorry - and which tag in the audio file would it go to?

  30. 90 Posted by PAtch on 25 May, 2019 10:15 AM

    PAtch's Avatar

    IGNORE ME! Traktor actually writes its star rating directly to the file.

    Who knew?

Reply to this discussion

Internal reply

Formatting help / Preview (switch to plain text) No formatting (switch to Markdown)

Attaching KB article:

»

Attached Files

You can attach files up to 10MB

If you don't have an account yet, we need to confirm you're human and not a machine trying to post spam.

Keyboard shortcuts

Generic

? Show this help
ESC Blurs the current field

Comment Form

r Focus the comment reply box
^ + ↩ Submit the comment

You can use Command ⌘ instead of Control ^ on Mac