MetaSync: minor improvements for iTunes source

- Use path as an key to find items (over artist/title/album tuples)
- Sensible default library location
This commit is contained in:
Tom Jaspers 2015-05-13 20:02:34 +02:00
parent 02bec1bdd7
commit 94edc7a2a4
4 changed files with 265 additions and 61 deletions

View file

@ -19,6 +19,8 @@ import os
import shutil
import tempfile
import plistlib
import urllib
from urlparse import urlparse
from time import mktime
from beets import util
@ -39,6 +41,21 @@ def create_temporary_copy(path):
shutil.rmtree(temp_dir)
def _norm_itunes_path(path):
# Itunes prepends the location with 'file://' on posix systems,
# and with 'file://localhost/' on Windows systems.
# The actual path to the file is always saved as posix form
# E.g., 'file://Users/Music/bar' or 'file://localhost/G:/Music/bar'
# The entire path will also be capitalized (e.g., '/Music/Alt-J')
# Note that this means the path will always have a leading separator,
# which is unwanted in the case of Windows systems.
# E.g., '\\G:\\Music\\bar' needs to be stripped to 'G:\\Music\\bar'
return util.bytestring_path(os.path.normpath(
urllib.unquote(urlparse(path).path)).lstrip('\\')).lower()
class Itunes(MetaSource):
item_types = {
@ -52,8 +69,12 @@ class Itunes(MetaSource):
def __init__(self, config, log):
super(Itunes, self).__init__(config, log)
config.add({'itunes': {
'library': '~/Music/iTunes/iTunes Library.xml'
}})
# Load the iTunes library, which has to be the .xml one (not the .itl)
library_path = util.normpath(config['itunes']['library'].get(str))
library_path = config['itunes']['library'].as_filename()
try:
self._log.debug(
@ -71,16 +92,14 @@ class Itunes(MetaSource):
hint = ''
raise ConfigValueError(u'invalid iTunes library' + hint)
# Convert the library in to something we can query more easily
self.collection = {
(track['Name'], track['Album'], track['Album Artist']): track
for track in raw_library['Tracks'].values()}
# Make the iTunes library queryable using the path
self.collection = {_norm_itunes_path(track['Location']): track
for track in raw_library['Tracks'].values()}
def sync_from_source(self, item):
key = (item.title, item.album, item.albumartist)
result = self.collection.get(key)
result = self.collection.get(util.bytestring_path(item.path).lower())
if not all(key) or not result:
if not result:
self._log.warning(u'no iTunes match found for {0}'.format(item))
return

View file

@ -8,7 +8,7 @@
<key>Application Version</key><string>12.1.2.27</string>
<key>Features</key><integer>5</integer>
<key>Show Content Ratings</key><true/>
<key>Music Folder</key><string>file:///beetstests/Music/iTunes/iTunes%20Media/</string>
<key>Music Folder</key><string>file:////Music/</string>
<key>Library Persistent ID</key><string>1ABA8417E4946A32</string>
<key>Tracks</key>
<dict>
@ -44,7 +44,7 @@
<key>Sort Artist</key><string>alt-J</string>
<key>Persistent ID</key><string>20E89D1580C31363</string>
<key>Track Type</key><string>File</string>
<key>Location</key><string>file:///beetstests/Music/Music/Alt-J/An%20Awesome%20Wave/03%20Tessellate.mp3</string>
<key>Location</key><string>file:///Music/Alt-J/An%20Awesome%20Wave/03%20Tessellate.mp3</string>
<key>File Folder Count</key><integer>4</integer>
<key>Library Folder Count</key><integer>2</integer>
</dict>
@ -80,7 +80,42 @@
<key>Sort Artist</key><string>alt-J</string>
<key>Persistent ID</key><string>D7017B127B983D38</string>
<key>Track Type</key><string>File</string>
<key>Location</key><string>file:///beetstests/Music/Music/Alt-J/An%20Awesome%20Wave/04%20Breezeblocks.mp3</string>
<key>Location</key><string>file://localhost/Music/Alt-J/An%20Awesome%20Wave/04%20Breezeblocks.mp3</string>
<key>File Folder Count</key><integer>4</integer>
<key>Library Folder Count</key><integer>2</integer>
</dict>
<key>638</key>
<dict>
<key>Track ID</key><integer>638</integer>
<key>Name</key><string>❦ (Ripe &#38; Ruin)</string>
<key>Artist</key><string>alt-J</string>
<key>Album Artist</key><string>alt-J</string>
<key>Album</key><string>An Awesome Wave</string>
<key>Kind</key><string>MPEG audio file</string>
<key>Size</key><integer>2173293</integer>
<key>Total Time</key><integer>72097</integer>
<key>Disc Number</key><integer>1</integer>
<key>Disc Count</key><integer>1</integer>
<key>Track Number</key><integer>2</integer>
<key>Track Count</key><integer>13</integer>
<key>Year</key><integer>2012</integer>
<key>Date Modified</key><date>2015-05-09T17:04:53Z</date>
<key>Date Added</key><date>2015-02-02T15:28:39Z</date>
<key>Bit Rate</key><integer>233</integer>
<key>Sample Rate</key><integer>44100</integer>
<key>Play Count</key><integer>8</integer>
<key>Play Date</key><integer>3514109973</integer>
<key>Play Date UTC</key><date>2015-05-10T11:39:33Z</date>
<key>Skip Count</key><integer>1</integer>
<key>Skip Date</key><date>2015-02-02T15:29:10Z</date>
<key>Album Rating</key><integer>80</integer>
<key>Album Rating Computed</key><true/>
<key>Artwork Count</key><integer>1</integer>
<key>Sort Album</key><string>Awesome Wave</string>
<key>Sort Artist</key><string>alt-J</string>
<key>Persistent ID</key><string>183699FA0554D0E6</string>
<key>Track Type</key><string>File</string>
<key>Location</key><string>file:///Music/Alt-J/An%20Awesome%20Wave/02%20%E2%9D%A6%20(Ripe%20&#38;%20Ruin).mp3</string>
<key>File Folder Count</key><integer>4</integer>
<key>Library Folder Count</key><integer>2</integer>
</dict>
@ -102,6 +137,9 @@
<dict>
<key>Track ID</key><integer>636</integer>
</dict>
<dict>
<key>Track ID</key><integer>638</integer>
</dict>
</array>
</dict>
<dict>
@ -119,55 +157,11 @@
<dict>
<key>Track ID</key><integer>636</integer>
</dict>
<dict>
<key>Track ID</key><integer>638</integer>
</dict>
</array>
</dict>
<dict>
<key>Name</key><string>Movies</string>
<key>Playlist ID</key><integer>22338</integer>
<key>Playlist Persistent ID</key><string>ED848683ABD912C5</string>
<key>Distinguished Kind</key><integer>2</integer>
<key>Movies</key><true/>
<key>All Items</key><true/>
</dict>
<dict>
<key>Name</key><string>TV Shows</string>
<key>Playlist ID</key><integer>22344</integer>
<key>Playlist Persistent ID</key><string>030882163A22E881</string>
<key>Distinguished Kind</key><integer>3</integer>
<key>TV Shows</key><true/>
<key>All Items</key><true/>
</dict>
<dict>
<key>Name</key><string>Podcasts</string>
<key>Playlist ID</key><integer>22347</integer>
<key>Playlist Persistent ID</key><string>8A8C2A6F094235CF</string>
<key>Distinguished Kind</key><integer>10</integer>
<key>Podcasts</key><true/>
<key>All Items</key><true/>
</dict>
<dict>
<key>Name</key><string>iTunes U</string>
<key>Playlist ID</key><integer>22354</integer>
<key>Playlist Persistent ID</key><string>571BAA51CE17C191</string>
<key>Distinguished Kind</key><integer>31</integer>
<key>iTunesU</key><true/>
<key>All Items</key><true/>
</dict>
<dict>
<key>Name</key><string>Audiobooks</string>
<key>Playlist ID</key><integer>22357</integer>
<key>Playlist Persistent ID</key><string>2D2BE73BF9612562</string>
<key>Distinguished Kind</key><integer>5</integer>
<key>Audiobooks</key><true/>
<key>All Items</key><true/>
</dict>
<dict>
<key>Name</key><string>Genius</string>
<key>Playlist ID</key><integer>22372</integer>
<key>Playlist Persistent ID</key><string>F35301460DED0A7A</string>
<key>Distinguished Kind</key><integer>26</integer>
<key>All Items</key><true/>
</dict>
</array>
</dict>
</plist>

View file

@ -0,0 +1,167 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Major Version</key><integer>1</integer>
<key>Minor Version</key><integer>1</integer>
<key>Date</key><date>2015-05-11T15:27:14Z</date>
<key>Application Version</key><string>12.1.2.27</string>
<key>Features</key><integer>5</integer>
<key>Show Content Ratings</key><true/>
<key>Music Folder</key><string>file://localhost/C:/Documents%20and%20Settings/Owner/My%20Documents/My%20Music/iTunes/iTunes%20Media/</string>
<key>Library Persistent ID</key><string>B4C9F3EE26EFAF78</string>
<key>Tracks</key>
<dict>
<key>180</key>
<dict>
<key>Track ID</key><integer>180</integer>
<key>Name</key><string>Tessellate</string>
<key>Artist</key><string>alt-J</string>
<key>Album Artist</key><string>alt-J</string>
<key>Album</key><string>An Awesome Wave</string>
<key>Genre</key><string>Alternative</string>
<key>Kind</key><string>MPEG audio file</string>
<key>Size</key><integer>5525212</integer>
<key>Total Time</key><integer>182674</integer>
<key>Disc Number</key><integer>1</integer>
<key>Disc Count</key><integer>1</integer>
<key>Track Number</key><integer>3</integer>
<key>Track Count</key><integer>13</integer>
<key>Year</key><integer>2012</integer>
<key>Date Modified</key><date>2015-02-02T15:23:08Z</date>
<key>Date Added</key><date>2014-04-24T09:28:38Z</date>
<key>Bit Rate</key><integer>238</integer>
<key>Sample Rate</key><integer>44100</integer>
<key>Play Count</key><integer>0</integer>
<key>Play Date</key><integer>3513593824</integer>
<key>Skip Count</key><integer>3</integer>
<key>Skip Date</key><date>2015-02-05T15:41:04Z</date>
<key>Rating</key><integer>80</integer>
<key>Album Rating</key><integer>80</integer>
<key>Album Rating Computed</key><true/>
<key>Artwork Count</key><integer>1</integer>
<key>Sort Album</key><string>Awesome Wave</string>
<key>Sort Artist</key><string>alt-J</string>
<key>Persistent ID</key><string>20E89D1580C31363</string>
<key>Track Type</key><string>File</string>
<key>Location</key><string>file://localhost/G:/Music/Alt-J/An%20Awesome%20Wave/03%20Tessellate.mp3</string>
<key>File Folder Count</key><integer>-1</integer>
<key>Library Folder Count</key><integer>-1</integer>
</dict>
<key>183</key>
<dict>
<key>Track ID</key><integer>183</integer>
<key>Name</key><string>Breezeblocks</string>
<key>Artist</key><string>alt-J</string>
<key>Album Artist</key><string>alt-J</string>
<key>Album</key><string>An Awesome Wave</string>
<key>Genre</key><string>Alternative</string>
<key>Kind</key><string>MPEG audio file</string>
<key>Size</key><integer>6827195</integer>
<key>Total Time</key><integer>227082</integer>
<key>Disc Number</key><integer>1</integer>
<key>Disc Count</key><integer>1</integer>
<key>Track Number</key><integer>4</integer>
<key>Track Count</key><integer>13</integer>
<key>Year</key><integer>2012</integer>
<key>Date Modified</key><date>2015-02-02T15:23:08Z</date>
<key>Date Added</key><date>2014-04-24T09:28:38Z</date>
<key>Bit Rate</key><integer>237</integer>
<key>Sample Rate</key><integer>44100</integer>
<key>Play Count</key><integer>31</integer>
<key>Play Date</key><integer>3513594051</integer>
<key>Play Date UTC</key><date>2015-05-04T12:20:51Z</date>
<key>Skip Count</key><integer>0</integer>
<key>Rating</key><integer>100</integer>
<key>Album Rating</key><integer>80</integer>
<key>Album Rating Computed</key><true/>
<key>Artwork Count</key><integer>1</integer>
<key>Sort Album</key><string>Awesome Wave</string>
<key>Sort Artist</key><string>alt-J</string>
<key>Persistent ID</key><string>D7017B127B983D38</string>
<key>Track Type</key><string>File</string>
<key>Location</key><string>file://localhost/G:/Music/Alt-J/An%20Awesome%20Wave/04%20Breezeblocks.mp3</string>
<key>File Folder Count</key><integer>-1</integer>
<key>Library Folder Count</key><integer>-1</integer>
</dict>
<key>638</key>
<dict>
<key>Track ID</key><integer>638</integer>
<key>Name</key><string>❦ (Ripe &#38; Ruin)</string>
<key>Artist</key><string>alt-J</string>
<key>Album Artist</key><string>alt-J</string>
<key>Album</key><string>An Awesome Wave</string>
<key>Kind</key><string>MPEG audio file</string>
<key>Size</key><integer>2173293</integer>
<key>Total Time</key><integer>72097</integer>
<key>Disc Number</key><integer>1</integer>
<key>Disc Count</key><integer>1</integer>
<key>Track Number</key><integer>2</integer>
<key>Track Count</key><integer>13</integer>
<key>Year</key><integer>2012</integer>
<key>Date Modified</key><date>2015-05-09T17:04:53Z</date>
<key>Date Added</key><date>2015-02-02T15:28:39Z</date>
<key>Bit Rate</key><integer>233</integer>
<key>Sample Rate</key><integer>44100</integer>
<key>Play Count</key><integer>8</integer>
<key>Play Date</key><integer>3514109973</integer>
<key>Play Date UTC</key><date>2015-05-10T11:39:33Z</date>
<key>Skip Count</key><integer>1</integer>
<key>Skip Date</key><date>2015-02-02T15:29:10Z</date>
<key>Album Rating</key><integer>80</integer>
<key>Album Rating Computed</key><true/>
<key>Artwork Count</key><integer>1</integer>
<key>Sort Album</key><string>Awesome Wave</string>
<key>Sort Artist</key><string>alt-J</string>
<key>Persistent ID</key><string>183699FA0554D0E6</string>
<key>Track Type</key><string>File</string>
<key>Location</key><string>file://localhost/G:/Experiments/Alt-J/An%20Awesome%20Wave/02%20%E2%9D%A6%20(Ripe%20&#38;%20Ruin).mp3</string>
<key>File Folder Count</key><integer>4</integer>
<key>Library Folder Count</key><integer>2</integer>
</dict>
</dict>
<key>Playlists</key>
<array>
<dict>
<key>Name</key><string>Bibliotheek</string>
<key>Master</key><true/>
<key>Playlist ID</key><integer>72</integer>
<key>Playlist Persistent ID</key><string>728AA5B1D00ED23B</string>
<key>Visible</key><false/>
<key>All Items</key><true/>
<key>Playlist Items</key>
<array>
<dict>
<key>Track ID</key><integer>180</integer>
</dict>
<dict>
<key>Track ID</key><integer>183</integer>
</dict>
<dict>
<key>Track ID</key><integer>638</integer>
</dict>
</array>
</dict>
<dict>
<key>Name</key><string>Muziek</string>
<key>Playlist ID</key><integer>103</integer>
<key>Playlist Persistent ID</key><string>8120A002B0486AD7</string>
<key>Distinguished Kind</key><integer>4</integer>
<key>Music</key><true/>
<key>All Items</key><true/>
<key>Playlist Items</key>
<array>
<dict>
<key>Track ID</key><integer>180</integer>
</dict>
<dict>
<key>Track ID</key><integer>183</integer>
</dict>
<dict>
<key>Track ID</key><integer>638</integer>
</dict>
</array>
</dict>
</array>
</dict>
</plist>

View file

@ -12,6 +12,7 @@
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
import os
import platform
import time
from datetime import datetime
from beets.library import Item
@ -25,20 +26,34 @@ def _parsetime(s):
return time.mktime(datetime.strptime(s, '%Y-%m-%d %H:%M:%S').timetuple())
def _is_windows():
return platform.system() == "Windows"
class MetaSyncTest(_common.TestCase, TestHelper):
itunes_library = os.path.join(_common.RSRC, 'itunes_library.xml')
itunes_library_unix = os.path.join(_common.RSRC,
'itunes_library_unix.xml')
itunes_library_windows = os.path.join(_common.RSRC,
'itunes_library_windows.xml')
def setUp(self):
self.setup_beets()
self.load_plugins('metasync')
self.config['metasync']['source'] = 'itunes'
self.config['metasync']['itunes']['library'] = self.itunes_library
if _is_windows():
self.config['metasync']['itunes']['library'] = \
self.itunes_library_windows
else:
self.config['metasync']['itunes']['library'] = \
self.itunes_library_unix
self._set_up_data()
def _set_up_data(self):
items = [_common.item() for _ in range(2)]
items[0].title = 'Tessellate'
items[0].artist = 'alt-J'
items[0].albumartist = 'alt-J'
@ -50,6 +65,15 @@ class MetaSyncTest(_common.TestCase, TestHelper):
items[1].albumartist = 'alt-J'
items[1].album = 'An Awesome Wave'
if _is_windows():
items[0].path = \
u'G:\\Music\\Alt-J\\An Awesome Wave\\03 Tessellate.mp3'
items[1].path = \
u'G:\\Music\\Alt-J\\An Awesome Wave\\04 Breezeblocks.mp3'
else:
items[0].path = u'/Music/Alt-J/An Awesome Wave/03 Tessellate.mp3'
items[1].path = u'/Music/Alt-J/An Awesome Wave/04 Breezeblocks.mp3'
for item in items:
self.lib.add(item)
@ -71,7 +95,6 @@ class MetaSyncTest(_common.TestCase, TestHelper):
self.assertIn('itunes_skipcount: 3', out)
self.assertIn('itunes_lastplayed: 2015-05-04 12:20:51', out)
self.assertIn('itunes_lastskipped: 2015-02-05 15:41:04', out)
self.assertEqual(self.lib.items()[0].itunes_rating, 60)
def test_sync_from_itunes(self):
@ -95,5 +118,6 @@ class MetaSyncTest(_common.TestCase, TestHelper):
def suite():
return unittest.TestLoader().loadTestsFromName(__name__)
if __name__ == b'__main__':
unittest.main(defaultTest='suite')