def test_reuse_parser(verbose=False): parser0 = itunessmart.Parser(testdata[0]["info"], testdata[0]["criteria"]) parser1 = itunessmart.Parser(testdata[1]["info"], testdata[1]["criteria"]) # Reuse parser 0 with data[1] parser0.update_data_base64(testdata[1]["info"], testdata[1]["criteria"]) assert parser0.result.queryTree == parser1.result.queryTree # Reuse parser 0 with data[3] parser0.update_data_base64(testdata[3]["info"], testdata[3]["criteria"]) # Reuse parser 1 with data[3] parser1.update_data_base64(testdata[3]["info"], testdata[3]["criteria"]) assert parser0.result.queryTree == parser1.result.queryTree
def test_xsp_minimal(verbose=False): library = readLibrary("library_minimal.xml") playlist = library['Playlists'][1] assert playlist['Name'] == "Chip - League of My Own II" parser = itunessmart.Parser() parser.update_data_bytes(playlist['Smart Info'],playlist['Smart Criteria']) assert parser.result.query == "(lower(Album) = 'league of my own ii')" files = itunessmart.createXSPFile(directory=".", name=playlist['Name'], smartPlaylist=parser.result, createSubplaylists=True) assert len(files) == 1 with open(files[0], "r") as f: xsp = f.read() expected = """<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <smartplaylist type="songs"> <name>Chip - League of My Own II</name> <match>all</match> <rule field="album" operator="is"> <value>League of My Own II</value> </rule> </smartplaylist>""" assert xsp == expected # Clean up for file in files: if os.path.isfile(file): os.remove(file)
def test_double_parse(verbose=False): parser = itunessmart.Parser(testdata[0]["info"], testdata[0]["criteria"]) query = parser.result.query parser._parser.parse() assert query == parser.result.query parser._parser.parse() assert query == parser.result.query
def test_examples(verbose=False): for test in copy.deepcopy(testdata): parser = itunessmart.Parser(test["info"], test["criteria"]) result = parser.result if verbose: print("\n\nTest: %s" % test["desc"]) print("############") print(result.output) print("############") print(result.query) print("############") print(json.dumps(result.queryTree, indent=2)) print("############") print(result.ignore) if "expected" in test and test["expected"]: exp = test["expected"] if "output" in exp: assert exp.pop("output") == result.output if "query" in exp: assert exp.pop("query") == result.query if "queryTree" in exp: assert exp.pop("queryTree") == result.queryTree if "ignore" in exp: assert exp.pop("ignore") == result.ignore for key in exp: assert exp[key] == result.queryTree[key] else: raise NotImplementedError(test["desc"])
def test_xsp_errors(verbose=False): parser = itunessmart.Parser(testdata[13]["info"], testdata[13]["criteria"]) assert parser.result.query == "(MediaKind != 'Home Video')" try: files = itunessmart.createXSP(name="example", smartPlaylist=parser.result, createSubplaylists=False) except Exception as e: assert type(e) == itunessmart.xsp.PlaylistException
def test_library_minimal(verbose=False): library = readLibrary("library_minimal.xml") playlist = library['Playlists'][1] assert 'Name' in playlist and 'Smart Criteria' in playlist and 'Smart Info' in playlist and playlist['Smart Criteria'] assert playlist['Name'] == "Chip - League of My Own II" parser = itunessmart.Parser() parser.update_data_bytes(playlist['Smart Info'],playlist['Smart Criteria']) assert parser.result.query == "(lower(Album) = 'league of my own ii')" mapping = itunessmart.generatePersistentIDMapping(library) assert mapping['38822E892B8D332A'] == "Chip - League of My Own II"
def test_xsp_subplaylists(verbose=False): library = readLibrary("library_onlysmartplaylists.xml") persistentIDMapping = itunessmart.generatePersistentIDMapping(library) parser = itunessmart.Parser() files = [] for playlist in library['Playlists']: if 'Name' in playlist: if 'HipHop' in playlist['Name'] or 'rap' in playlist['Name']: parser.update_data_bytes(playlist['Smart Info'],playlist['Smart Criteria']) files += itunessmart.createXSPFile(directory='.', name=playlist['Name'], smartPlaylist=parser.result, createSubplaylists=True, persistentIDMapping=persistentIDMapping) with open("HipHop.xsp", "r") as f: xsp = f.read() # Rule: is playlist assert '''<rule field="playlist" operator="is"> <value>Deutschrap</value> </rule>''' in xsp # Subplaylists match: rules = xsp.split('<rule field="playlist"')[1:] for rule in rules: subname = rule.split('<value>')[1].split('</value>')[0] filename = "%s.xsp" % subname assert os.path.isfile(filename) # XML escaped character with open("HipHop2000.xsp", "r") as f: xsp = f.read() name = xsp.split("<name>")[1].split("</name>")[0].strip() assert name == "HipHop<2000" # Rule: global OR with open("Deutschrap.xsp", "r") as f: xsp = f.read() assert "<match>one</match>" in xsp # Clean up for file in files: if os.path.isfile(file): os.remove(file)
def main(iTunesLibraryFile=None, outputDirectory=None): if iTunesLibraryFile == "--help" or iTunesLibraryFile == "-h" or iTunesLibraryFile == "/?": print("""Export smart playlists from 'iTunes Music Library.xml' to .xsp files for Kodi. This interactive script uses the default iTunes library, but you may also specify a xml file: Optional arguments: python3 -m itunessmart {iTunesLibraryFile} {outputDirectory} {iTunesLibraryFile}\t - Default is ~\\Music\\iTunes\\iTunes Music Library.xml {outputDirectory}\t - Default is ./out/""") return export_sub_playlists = True if not iTunesLibraryFile: home = os.path.expanduser("~") folder = os.path.join(home, "Music/iTunes") iTunesLibraryFile = os.path.join(folder, "iTunes Music Library.xml") elif not os.path.isfile(iTunesLibraryFile) and os.path.isfile(os.path.join(iTunesLibraryFile, "iTunes Music Library.xml")): iTunesLibraryFile = os.path.join(iTunesLibraryFile, "iTunes Music Library.xml") while not os.path.isfile(iTunesLibraryFile): iTunesLibraryFile = input("# Please enter the path of your `iTunes Music Library.xml`: ") if os.path.isfile(iTunesLibraryFile): break elif os.path.isfile(os.path.join(iTunesLibraryFile, "iTunes Music Library.xml")): iTunesLibraryFile = os.path.join(iTunesLibraryFile, "iTunes Music Library.xml") break else: print("! Could not find file `%s`") print("# Reading %s . . . " % iTunesLibraryFile) with open(iTunesLibraryFile, "rb") as fs: # Read XML file library = itunessmart.readiTunesLibrary(fs) persistentIDMapping = itunessmart.generatePersistentIDMapping(library) print("# Library loaded!") userinput = input("# Do you want to convert a (single) or (all) playlists? ") export_all = True if userinput.lower() in ("single", "1", "one"): export_all = False userinput = input("# Do you want to export nested rules to sub-playlists? (yes/no) ") if userinput.lower() in ("n", "no", "0"): export_sub_playlists = False # Decode and export all smart playlists parser = itunessmart.Parser() if outputDirectory is None: outputDirectory = os.path.abspath("out") if not os.path.exists(outputDirectory): os.makedirs(outputDirectory) if export_all: print("# Converting playlists to %s" % outputDirectory) res = [] for playlist in library['Playlists']: if 'Name' in playlist and 'Smart Criteria' in playlist and 'Smart Info' in playlist and playlist['Smart Criteria']: try: parser.update_data_bytes(playlist['Smart Info'], playlist['Smart Criteria']) except Exception as e: print("! Failed to decode playlist:") try: print(traceback.format_exc()) printWithoutException(playlist['Name']) except KeyError: printWithoutException(playlist) if not parser.result: continue try: res.append((playlist['Name'], parser.result)) if export_all: itunessmart.createXSPFile(directory=outputDirectory, name=playlist['Name'], smartPlaylist=parser.result, createSubplaylists=export_sub_playlists, persistentIDMapping=persistentIDMapping) except itunessmart.EmptyPlaylistException as e: printWithoutException("! `%s` is empty." % playlist['Name']) except itunessmart.PlaylistException as e: printWithoutException("! Skipped `%s`: %s" % (playlist['Name'], str(e))) except Exception as e: print("! Failed to convert playlist:") try: print(traceback.format_exc()) printWithoutException(playlist['Name']) except KeyError: printWithoutException(playlist) if not export_all: i = 1 for p_name, _ in res: printWithoutException("# %02d: %s" % (i, p_name)) i += 1 while True: i = input("# Please give the number of the playlist (0 to exit): ") if i in ("0", ""): break i = int(i) p_name, p_result = res[i-1] printWithoutException("# Converting Playlist: %s" % p_name) try: itunessmart.createXSPFile(directory=outputDirectory, name=p_name, smartPlaylist=p_result, createSubplaylists=export_sub_playlists, persistentIDMapping=persistentIDMapping) except itunessmart.EmptyPlaylistException as e: printWithoutException("! `%s` is empty." % p_name) except itunessmart.PlaylistException as e: printWithoutException("! Skipped `%s`: %s" % (p_name, str(e))) except Exception as e: try: print("! Failed to convert playlist:") print(traceback.format_exc()) printWithoutException(p_name) except (UnicodeEncodeError, KeyError, TypeError) as e: print(p_result) print("# All done!") userdata = os.path.expandvars(r"%appdata%\kodi\userdata\playlists\music") if os.path.exists(os.path.expandvars(r"%appdata%\kodi\userdata")) else os.path.expanduser(r"~/Library/Application Support/Kodi/userdata/playlists/music") print("# You may copy the .xsp files to %s" % userdata)
#### Include modules from parent directory ### import sys import os include = os.path.relpath(os.path.join(os.path.dirname(__file__), "..")) sys.path.insert(0, include) #### Example: ### import itunessmart import json info = "AQEAAwAAAAIAAAAZAAAAAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" criteria = "U0xzdAABAAEAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAABAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/FNMc3QAAQABAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEQAAAAAAAAAEQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAEQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEAAAAAAAAABIAAAAAAAAAAAAAAAAAAAABAAAAAAAAABIAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARAAAAAAAAABZAAAAAAAAAAAAAAAAAAAAAQAAAAAAAABZAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAA" parser = itunessmart.Parser(info, criteria) result = parser.result print("############ Output:") print(result.output) print("############ Query:") print(result.query) print("############ JSON:") print(json.dumps(result.queryTree, indent=2)) print("############ Ignore:") print(result.ignore)