Skip to content

Commit

Permalink
Merge pull request #66 from sintc0nery/hotfix/deprecated_method
Browse files Browse the repository at this point in the history
I replaced the deprecated method readPlist
  • Loading branch information
liamks authored Sep 9, 2022
2 parents 0c734f3 + 62aec06 commit 36d16c5
Show file tree
Hide file tree
Showing 12 changed files with 447 additions and 143 deletions.
145 changes: 145 additions & 0 deletions build/lib/libpytunes/Library.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import logging
import plistlib
from six import PY2
from six.moves.urllib import parse as urlparse
import time

from libpytunes.Song import Song
from libpytunes.Playlist import Playlist


logger = logging.getLogger(__name__)

try:
import xspf
xspfAvailable = True
except ImportError:
xspfAvailable = False
pass


class Library:
def __init__(self, itunesxml, musicPathXML=None, musicPathSystem=None, filesOnly=False):
# musicPathXML and musicPathSystem will do path conversion
# when xml is being processed on different OS then iTunes
self.musicPathXML = musicPathXML
self.musicPathSystem = musicPathSystem
self.filesOnly = filesOnly
with open(itunesxml, 'rb') as f:
self.il = plistlib.load(f)
self.songs = {}
self.getSongs()

def getSongs(self):
format = "%Y-%m-%d %H:%M:%S"
for trackid, attributes in self.il['Tracks'].items():
s = Song()

s.name = attributes.get('Name')

# Support classical music naming (Work+Movement Number+Movement Name) since iTunes 12.5
s.work = attributes.get('Work')
s.movement_number = attributes.get('Movement Number')
s.movement_count = attributes.get('Movement Count')
s.movement_name = attributes.get('Movement Name')

s.track_id = int(attributes.get('Track ID')) if attributes.get('Track ID') else None
s.artist = attributes.get('Artist')
s.album_artist = attributes.get('Album Artist')
s.composer = attributes.get('Composer')
s.album = attributes.get('Album')
s.genre = attributes.get('Genre')
s.kind = attributes.get('Kind')
s.size = int(attributes.get('Size')) if attributes.get('Size') else None
s.total_time = attributes.get('Total Time')
s.track_number = attributes.get('Track Number')
s.track_count = int(attributes.get('Track Count')) if attributes.get('Track Count') else None
s.disc_number = int(attributes.get('Disc Number')) if attributes.get('Disc Number') else None
s.disc_count = int(attributes.get('Disc Count')) if attributes.get('Disc Count') else None
s.year = int(attributes.get('Year')) if attributes.get('Year') else None
s.date_modified = time.strptime(str(attributes.get('Date Modified')), format) if attributes.get('Date Modified') else None
s.date_added = time.strptime(str(attributes.get('Date Added')), format) if attributes.get('Date Added') else None
s.bit_rate = int(attributes.get('Bit Rate')) if attributes.get('Bit Rate') else None
s.sample_rate = int(attributes.get('Sample Rate')) if attributes.get('Sample Rate') else None
s.comments = attributes.get("Comments")
s.rating = int(attributes.get('Rating')) if attributes.get('Rating') else None
s.rating_computed = 'Rating Computed' in attributes
s.play_count = int(attributes.get('Play Count')) if attributes.get('Play Count') else None
s.album_rating = attributes.get('Album Rating')
s.album_rating_computed = 'Album Rating Computed' in attributes
s.persistent_id = attributes.get('Persistent ID')

if attributes.get('Location'):
s.location_escaped = attributes.get('Location')
s.location = s.location_escaped
s.location = urlparse.unquote(urlparse.urlparse(attributes.get('Location')).path[1:])
s.location = s.location.decode('utf-8') if PY2 else s.location # fixes bug #19
if (self.musicPathXML is not None and self.musicPathSystem is not None):
s.location = s.location.replace(self.musicPathXML, self.musicPathSystem)

s.compilation = 'Compilation' in attributes
s.lastplayed = time.strptime(str(attributes.get('Play Date UTC')), format) if attributes.get('Play Date UTC') else None
s.skip_count = int(attributes.get('Skip Count')) if attributes.get('Skip Count') else None
s.skip_date = time.strptime(str(attributes.get('Skip Date')), format) if attributes.get('Skip Date') else None
s.length = int(attributes.get('Total Time')) if attributes.get('Total Time') else None
s.track_type = attributes.get('Track Type')
s.grouping = attributes.get('Grouping')
s.podcast = 'Podcast' in attributes
s.movie = 'Movie' in attributes
s.has_video = 'Has Video' in attributes
s.loved = 'Loved' in attributes
s.album_loved = 'Album Loved' in attributes
s.playlist_only = 'Playlist Only' in attributes
s.apple_music = 'Apple Music' in attributes
s.protected = 'Protected' in attributes

self.songs[int(trackid)] = s

def getPlaylistNames(self, ignoreList=[
"Library", "Music", "Movies", "TV Shows", "Purchased", "iTunes DJ", "Podcasts"
]):

playlists = []
for playlist in self.il['Playlists']:
if playlist['Name'] not in ignoreList:
playlists.append(playlist['Name'])
return playlists

def getPlaylist(self, playlistName):
for playlist in self.il['Playlists']:
if playlist['Name'] == playlistName:
# id playlist_id track_num url title album artist length uniqueid
p = Playlist(playlistName)
p.playlist_id = playlist['Playlist ID']
p.is_folder = playlist.get('Folder', False)
p.playlist_persistent_id = playlist.get('Playlist Persistent ID')
p.parent_persistent_id = playlist.get('Parent Persistent ID')
p.distinguished_kind = playlist.get('Distinguished Kind')
p.is_genius_playlist = True if playlist.get('Genius Track ID') else False
p.is_smart_playlist = True if playlist.get('Smart Info') and not playlist.get('Folder', False) else False
tracknum = 1
# Make sure playlist was not empty
if 'Playlist Items' in playlist:
for track in playlist['Playlist Items']:
id = int(track['Track ID'])
t = self.songs[id]
t.playlist_order = tracknum
tracknum += 1
p.tracks.append(t)
return p

def getPlaylistxspf(self, playlistName):
global xspfAvailable
if (xspfAvailable):
x = xspf.Xspf()
for playlist in self.il['Playlists']:
if playlist['Name'] == playlistName:
x.title = playlistName
x.info = ""
for track in playlist['Playlist Items']:
id = int(track['Track ID'])
x.add_track(title=self.songs[id].name, creator="", location=self.songs[id].location)
return x.toXml()
else:
logger.warning("xspf library missing, go to https://github.com/alastair/xspf to install.")
return None
19 changes: 19 additions & 0 deletions build/lib/libpytunes/Playlist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from six import iteritems

class Playlist:
is_folder = False
playlist_persistent_id = None
parent_persistent_id = None
distinguished_kind = None
playlist_id = None

def __init__(self, playListName=None):
self.name = playListName
self.tracks = []

def __iter__(self):
for attr, value in iteritems(self.__dict__):
yield attr, value

def ToDict(self):
return {key: value for (key, value) in self}
96 changes: 96 additions & 0 deletions build/lib/libpytunes/Song.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from six import iteritems


class Song:
"""
Song Attributes:
name (String)
track_id (Integer)
artist (String)
album_artist (String)
composer = None (String)
album = None (String)
genre = None (String)
kind = None (String)
size = None (Integer)
total_time = None (Integer)
track_number = None (Integer)
track_count = None (Integer)
disc_number = None (Integer)
disc_count = None (Integer)
year = None (Integer)
date_modified = None (Time)
date_added = None (Time)
bit_rate = None (Integer)
sample_rate = None (Integer)
comments = None (String)
rating = None (Integer)
rating_computed = False (Boolean)
album_rating = None (Integer)
play_count = None (Integer)
location = None (String)
location_escaped = None (String)
compilation = False (Boolean)
grouping = None (String)
lastplayed = None (Time)
skip_count = None (Integer)
skip_date = None (Time)
length = None (Integer)
persistent_id = None (String)
album_rating_computed = False (Boolean)
work = None (String)
movement_name = None (String)
movement_number = None (Integer)
movement_count = None (Integer)
playlist_only = None (Bool)
apple_music = None (Bool)
protected = None (Bool)
"""
name = None
track_id = None
artist = None
album_artist = None
composer = None
album = None
genre = None
kind = None
size = None
total_time = None
track_number = None
track_count = None
disc_number = None
disc_count = None
year = None
date_modified = None
date_added = None
bit_rate = None
sample_rate = None
comments = None
rating = None
rating_computed = None
album_rating = None
play_count = None
skip_count = None
skip_date = None
location = None
location_escaped = None
compilation = None
grouping = None
lastplayed = None
length = None
persistent_id = None
album_rating_computed = None
work = None
movement_name = None
movement_number = None
movement_count = None
playlist_only = None
apple_music = None
protected = None

def __iter__(self):
for attr, value in iteritems(self.__dict__):
yield attr, value

def ToDict(self):
return {key: value for (key, value) in self}
3 changes: 3 additions & 0 deletions build/lib/libpytunes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from libpytunes.Library import Library
from libpytunes.Song import Song
from libpytunes.Playlist import Playlist
Empty file.
25 changes: 25 additions & 0 deletions build/lib/libpytunes/tests/test_library.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import unittest
from libpytunes import Library
import os

class TestLibrary(unittest.TestCase):

def setUp(self):
self.it_library = Library(os.path.join(os.path.dirname(__file__), "Test Library.xml"))

def test_songs(self):

for id, song in self.it_library.songs.items():
assert(hasattr(song, 'name') == True)

def test_playlists(self):

playlists = self.it_library.getPlaylistNames()

for song in self.it_library.getPlaylist(playlists[0]).tracks:
assert(hasattr(song, 'track_number'))
assert(hasattr(song, 'artist'))
assert(hasattr(song, 'name'))

if __name__ == '__main__':
unittest.main()
Binary file added dist/libpytunes-1.5.2-py3.10.egg
Binary file not shown.
Loading

0 comments on commit 36d16c5

Please sign in to comment.