Created
May 11, 2023 17:15
-
-
Save sbbosco/4877a303566916414c08db99dcd0313b to your computer and use it in GitHub Desktop.
Simple Pythonista script that read iOS Music Library
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys, os, time | |
import sqlite3 | |
from datetime import date | |
from threading import current_thread, main_thread | |
from objc_util import * | |
import ui | |
import dialogs | |
""" | |
Simple Pythonista script that reads iOS Music Library | |
""" | |
winx, winy = ui.get_window_size() | |
if winx > 850: | |
w, h = (540, 640) | |
else: | |
w, h = (winx, winy) | |
class MainViewer(ui.View): | |
def __init__(self): | |
self.name = 'My Music' | |
self.frame = (0, 0, w, h) | |
self.flex = 'WH' | |
self.background_color = 'gray' | |
btnplay = ui.ButtonItem(image=ui.Image.named('iob:ios7_play_24'), action=self.playall) | |
btnpause = ui.ButtonItem(image=ui.Image.named('iob:ios7_pause_24'), action=self.pause) | |
self.right_button_items = [btnplay, btnpause] | |
btnw = w/3 | |
btntitle = ui.Button(title='Title') | |
btntitle.background_color = 'gray' | |
btntitle.tint_color = 'white' | |
btntitle.border_width = 1 | |
btntitle.frame = (0, 0, btnw, 40) | |
btntitle.action = self.do_filter | |
self.btn1 = btntitle | |
self.add_subview(btntitle) | |
btnartist = ui.Button(title='Artist') | |
btnartist.background_color = 'gray' | |
btnartist.tint_color = 'white' | |
btnartist.border_width = 1 | |
btnartist.frame = (btnw, 0, btnw, 40) | |
btnartist.action = self.do_filter | |
self.btn2 = btnartist | |
self.add_subview(btnartist) | |
btnalbum = ui.Button(title='Album') | |
btnalbum.background_color = 'gray' | |
btnalbum.tint_color = 'white' | |
btnalbum.border_width = 1 | |
btnalbum.frame = (btnw*2, 0, btnw, 40) | |
btnalbum.action = self.do_filter | |
self.btn3 = btnalbum | |
self.add_subview(btnalbum) | |
self.tv = ui.TableView() | |
self.tv.frame = (0, 40, w, h - 40) | |
self.tv.flex = 'RB' | |
self.ds = ui.ListDataSource([]) | |
self.tv.data_source = self.tv.delegate = self.ds | |
self.ds.tableview_cell_for_row = self.table_cell_for_row | |
self.ds.tableview_accessory_button_tapped = self.tableview_accessory_button_tapped | |
self.add_subview(self.tv) | |
self.filters = { | |
'artist': None, | |
'album': None, | |
'title': None | |
} | |
self.use_db = True | |
if self.use_db: | |
self.db = SqliteDb() | |
self.imusic = {} | |
self.music = iMusic() | |
self.build_imusic() | |
def layout(self): | |
x, y, w, h = self.frame | |
btnw = w/3 | |
self.btn1.frame = (0, 0, btnw, 40) | |
self.btn2.frame = (btnw, 0, btnw, 40) | |
self.btn3.frame = (btnw * 2, 0, btnw, 40) | |
self.tv.frame = (0, 40, w, h - 40) | |
@ui.in_background | |
def build_imusic(self): | |
print(current_thread().name) | |
self.imusic['Songs'] = {} | |
for i, item in enumerate(self.music.songs): | |
#for item in music.songs: | |
id = str(item.persistentID()) | |
title = str(item.title()) | |
artist = str(item.artist()) | |
artistid = str(item.artistPersistentID()) | |
album = str(item.albumTitle()) | |
albumid = str(item.albumPersistentID()) | |
storeid = str(item.playbackStoreID()) | |
self.imusic['Songs'][id] = { | |
'id': id, | |
'title': title, | |
'artist': artist, | |
'artistid': artistid, | |
'album': album, | |
'albumid': albumid, | |
'index': i, | |
'storeid': storeid, | |
} | |
print('done') | |
self.reload_ds() | |
@ui.in_background | |
def playall(self, sender): | |
print('play') | |
songs = self.imusic['Songs'] | |
playlist = [] | |
for item in self.ds.items: | |
id = item[4] | |
mediaitem = songs.get(id, None) | |
if mediaitem: | |
playlist.append(self.music.songs[mediaitem['index']]) | |
print('playlist:', len(playlist)) | |
self.music.playsong(playlist) | |
@ui.in_background | |
def pause(self, sender): | |
print('pause') | |
self.music.pause() | |
def reload(self): | |
if self.use_db: | |
self.reload_db() | |
else: | |
self.reload_ds() | |
@ui.in_background | |
def reload_db(self): | |
rows = self.db.query('songs', | |
[ | |
self.filters['title'], | |
self.filters['artist'], | |
self.filters['album'] | |
] | |
) | |
print('reload db') | |
self.ds.items = rows | |
def reload_ds(self): | |
print('reload ds') | |
print(current_thread().name) | |
songs = self.imusic['Songs'] | |
dsitems = [] | |
for key in songs: | |
#print(songs[key]['title']) | |
idx = songs[key]['index'] | |
title = songs[key]['title'] | |
artist = songs[key]['artist'] | |
album = songs[key]['album'] | |
persistentid = songs[key]['id'] | |
if self.filters['title']: | |
if not self.filters['title'] in title.lower(): | |
continue | |
if self.filters['artist']: | |
if not self.filters['artist'] in artist.lower(): | |
continue | |
if self.filters['album']: | |
if not self.filters['album'] in album.lower(): | |
continue | |
dsitems.append([idx, title, artist, album, persistentid]) | |
#print(dsitems) | |
if self.use_db: | |
self.db.build_db(dsitems) | |
self.reload_db() | |
else: | |
self.ds.items = dsitems | |
def will_close(self): | |
@ui.in_background | |
def _close_db(): | |
self.db.close_db() | |
_close_db() | |
def table_cell_for_row(self, tableview, section, row): | |
item = self.ds.items[row] | |
cell = ui.TableViewCell('subtitle') | |
cell.text_label.text = str(item[1]) | |
cell.detail_text_label.text = ' ' + item[2] | |
""" | |
cell.detail_text_label.text = ' ' + \ | |
'{:{align}{width}s}'.format(str(item[2])[:30], width=34, align='<') + item[3] | |
""" | |
cell.accessory_type = 'detail_button' | |
cell.detail_text_label.font = ('Menlo', 14) | |
return cell | |
def tableview_accessory_button_tapped(self, tableview, section, row): | |
print('accessory: ', row) | |
print(current_thread().name) | |
@ui.in_background | |
def do_filter(self, sender): | |
print(sender.title) | |
print(current_thread().name) | |
filter_type = sender.title.lower() | |
#data = FilterDialog.display('filter_data') | |
if filter_type == 'artist': | |
print('artist') | |
value = self.filters['artist'] if self.filters['artist'] else '' | |
fields = [ | |
{'key': 'artist', 'title': 'Artist', 'type': 'text', 'value': value} | |
] | |
result = dialogs.form_dialog(title='Artist', fields=fields) | |
elif filter_type == 'album': | |
print('album') | |
value = self.filters['album'] if self.filters['album'] else '' | |
fields = [ | |
{'key': 'album', 'title': 'Album', 'type': 'text', 'value': value} | |
] | |
result = dialogs.form_dialog(title='Album', fields=fields) | |
elif filter_type == 'title': | |
print('title') | |
value = self.filters['title'] if self.filters['title'] else '' | |
fields = [ | |
{'key': 'title', 'title': 'Title', 'type': 'text', 'value': value} | |
] | |
result = dialogs.form_dialog(title='Title', fields=fields) | |
else: | |
result = None | |
#print('data', data) | |
data = None | |
self.apply_filter(filter_type, result) | |
def apply_filter(self, filter_type, result): | |
print('result', result) | |
print(current_thread().name) | |
if filter_type == 'artist': | |
if result: | |
self.filters['artist'] = result['artist'].lower() | |
else: | |
self.filters['artist'] = None | |
elif filter_type == 'album': | |
if result: | |
self.filters['album'] = result['album'].lower() | |
else: | |
self.filters['album'] = None | |
elif filter_type == 'title': | |
if result: | |
self.filters['title'] = result['title'].lower() | |
else: | |
self.filters['title'] = None | |
print(self.filters) | |
self.reload() | |
class SqliteDb: | |
def __init__(self, dbname=':memory:'): | |
self.dbname = dbname | |
self.open_db() | |
self.create_db() | |
def open_db(self): | |
self.con = None | |
if os.path.isfile(self.dbname) or self.dbname == ':memory:': | |
self.con = sqlite3.connect(self.dbname, | |
detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES, | |
check_same_thread=True) #allows us to use date and datetime | |
self.con.execute("PRAGMA foreign_keys = 1") # allows us to use foreign keys | |
else: | |
print("Cannot find DB") | |
def close_db(self): | |
print('close db') | |
self.con.close() | |
def create_db(self): | |
sql = """ | |
create table Songs (SongId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE, | |
Title TEXT, Artist TEXT, Album TEXT, PersistentId TEXT) | |
""" | |
self.con.execute(sql) | |
self.con.commit() | |
def build_db(self, rows): | |
sql = """ | |
insert into Songs (SongId, Title, Artist, Album, PersistentId) | |
values (?, ?, ?, ?, ?) | |
""" | |
for row in rows: | |
self.con.execute(sql, row) | |
self.con.commit() | |
def query(self, dataset, filters): | |
title = '%%' if filters[0] is None else '%' + filters[0] + '%' | |
artist = '%%' if filters[1] is None else '%' + filters[1] + '%' | |
album = '%%' if filters[2] is None else '%' + filters[2] + '%' | |
parms = [title, artist, album] | |
sql = """select SongId, Title, Artist, Album, PersistentId from Songs | |
where Title like ? and Artist like ? and Album like ? | |
order by Title | |
""" | |
cursor = self.con.execute(sql, parms) | |
rows = cursor.fetchall() | |
return rows | |
class iMusic: | |
def __init__(self): | |
MPMediaQuery = ObjCClass('MPMediaQuery') | |
self.MPMediaItemCollection = ObjCClass('MPMediaItemCollection') | |
MPMediaItem = ObjCClass('MPMediaItem') | |
MPMediaPropertyPredicate = ObjCClass('MPMediaPropertyPredicate') | |
MPMusicPlayerController = ObjCClass('MPMusicPlayerController') | |
self.player = MPMusicPlayerController.systemMusicPlayer() | |
NSBundle.bundleWithPath_('/System/Library/Frameworks/MediaPlayer.framework').load() | |
MPVolumeView = ObjCClass('MPVolumeView') | |
self.volume_view = MPVolumeView.new().autorelease() | |
self.firstrun = True | |
query = MPMediaQuery.songsQuery() | |
songs = list(query.items()) | |
self.songs = songs | |
def playsong(self, songitems): | |
collection = self.MPMediaItemCollection.collectionWithItems(songitems) | |
self.player.setQueueWithItemCollection(collection) | |
if self.firstrun: | |
time.sleep(2) | |
self.firstrun = False | |
self.player.play() | |
def pause(self): | |
print('pause player') | |
self.player.pause() | |
print('begin') | |
v = MainViewer() | |
if winx > 850: | |
v.present('sheet') | |
else: | |
v.present('fullscreen') | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment