summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichaël Ball <michael.ball@gmail.com>2014-12-13 16:41:35 +0000
committerMichaël Ball <michael.ball@gmail.com>2014-12-13 16:41:35 +0000
commitcd630834a985be4b39a673d022e180de3ff20517 (patch)
tree0cfdcba2570ca7925e3777104448e83501fa328c
parent86e68485b0848e25ef6d5b579c7d80f286d094a1 (diff)
Initial commit
-rw-r--r--.gitignore12
-rw-r--r--common/__init__.py0
-rw-r--r--common/utils.py57
-rw-r--r--db/__init__.py0
-rw-r--r--db/db_manager.py54
-rw-r--r--library.dbbin0 -> 17408 bytes
-rwxr-xr-xlibrary.py88
-rw-r--r--mach2.ini4
-rw-r--r--mach2.py95
-rw-r--r--models/__init__.py0
-rw-r--r--models/album.py114
-rw-r--r--models/artist.py115
-rw-r--r--models/track.py487
-rw-r--r--watcher.py66
14 files changed, 1092 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8374d58
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+# Development stuff
+.python-version
+tags
+GRTAGS
+GTAGS
+GPATH
+*.swp
+*.pyc
+library.db-journal
+
+public
+tmp
diff --git a/common/__init__.py b/common/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/common/__init__.py
diff --git a/common/utils.py b/common/utils.py
new file mode 100644
index 0000000..484179a
--- /dev/null
+++ b/common/utils.py
@@ -0,0 +1,57 @@
+def make_where_clause(params):
+ """Create a where clause for each key-value pair in a dict, joined
+ by AND.
+
+ Parameters
+ ----------
+ params : dict
+ A dict of keys and values
+ """
+
+ where_items = []
+ where_clause = None
+
+ try:
+ for key in params.keys():
+ where_items.append("%s=:%s" % (key, key))
+
+ where_statement = None
+ if len(where_items) > 1:
+ where_statement = " AND ".join(where_items)
+ else:
+ where_statement = where_items[0]
+
+ where_clause = " ".join(("WHERE", where_statement))
+ except AttributeError:
+ pass
+
+ return where_clause
+
+
+def update_clause_from_dict(data):
+ """Create an update clause from a dictionary
+
+ Parameters
+ __________
+ data: dict
+ A dict of the new value and the column name as key
+ """
+
+ update_items = []
+ set_statement = None
+ update_clause = None
+
+ try:
+ for key in data.keys():
+ update_items.append("%s = :%s", (key, key))
+
+ if len(update_items) > 1:
+ update_clause = ", ".join(update_items)
+ else:
+ update_clause = update_items[0]
+
+ set_statement = " ".join(("SET", update_clause))
+ except AttributeError:
+ pass
+
+ return set_statement
diff --git a/db/__init__.py b/db/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/db/__init__.py
diff --git a/db/db_manager.py b/db/db_manager.py
new file mode 100644
index 0000000..44d6b96
--- /dev/null
+++ b/db/db_manager.py
@@ -0,0 +1,54 @@
+import configparser
+import sqlite3
+
+
+class DbManager:
+ class __DbManager:
+ def __init__(self):
+ config = configparser.ConfigParser()
+ config.read("mach2.ini")
+ self.conn = sqlite3.connect(config["DEFAULT"]["database"])
+
+ def __str__(self):
+ return repr(self)
+
+ def execute(self, *args):
+ return self.conn.execute(*args)
+
+ def cursor(self):
+ return self.conn.cursor()
+
+ def commit(self):
+ return self.conn.commit()
+
+ def close(self):
+ return self.conn.close()
+
+ def rollback(self):
+ return self.conn.rollback()
+
+ def executemany(self, *args):
+ return self.conn.executemany(*args)
+
+ def executescript(self, *args):
+ return self.conn.executescript(*args)
+
+ def create_function(self, *args):
+ return self.conn.create_function(*args)
+
+ def create_aggregate(self, *args):
+ return self.conn.create_aggregate(*args)
+
+ def create_collation(self, *args):
+ return self.conn.create_collation(*args)
+
+ def interrupt(self):
+ return self.conn.interrupt()
+
+ instance = None
+
+ def __new__(cls):
+ if not DbManager.instance:
+ DbManager.instance = DbManager.__DbManager()
+
+ return DbManager.instance
diff --git a/library.db b/library.db
new file mode 100644
index 0000000..46011ca
--- /dev/null
+++ b/library.db
Binary files differ
diff --git a/library.py b/library.py
new file mode 100755
index 0000000..762b18b
--- /dev/null
+++ b/library.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+import configparser
+import mutagen
+import os
+
+from models.track import Track
+
+
+def run(path=None):
+ print("Scanning files")
+
+ if path is not None:
+ if os.path.isdir(path):
+ store_dir(path)
+ else:
+ store_file(path)
+ else:
+ store_dir("/media/Music")
+
+
+def store_file(path):
+ m = mutagen.File(path, easy=True)
+ if m:
+ if not Track.store(path, m):
+ print("Problem saving %s" % (path,))
+
+
+def store_dir(path):
+ file_store = []
+
+ for root, dirs, files in os.walk(path):
+ for name in files:
+ file_path = "".join([root, "/", name])
+ file_store.append(file_path)
+
+ file_store.sort()
+ j = 0
+ media_files = 0
+ print("Storing files")
+ for file_path in file_store:
+ j += 1
+ m = mutagen.File(file_path, easy=True)
+ if m:
+ if not Track.store(file_path, m):
+ print("Problem saving %s" % (file_path,))
+
+ media_files += 1
+ print(
+ "%d%% complete, (%d files)" % (((j / len(file_store)) * 100),
+ j)
+ )
+ print("Stored %d tracks" % (media_files,))
+
+
+def delete_file(path):
+ track = Track.find_by_path(path)
+
+ if track:
+ track_album = track.album
+ track_artists = track.artists
+
+ track.delete()
+
+ if track_album and len(track_album.tracks) == 0:
+ track_album.delete()
+
+ for artist in track_artists:
+ if len(artist.tracks) == 0:
+ artist.delete()
+
+
+def update_file(path):
+ m = mutagen.File(path, easy=True)
+ if m:
+ track = Track.find_by_path(path)
+ track.update(m)
+
+
+def update_track_filename(oldpath, newpath):
+ track = Track.find_by_path(oldpath)
+ track.filename = newpath
+ track.save()
+
+if __name__ == "__main__":
+ config = configparser.ConfigParser()
+ config.read("mach2.ini")
+
+ run(config["DEFAULT"]["media_dir"])
diff --git a/mach2.ini b/mach2.ini
new file mode 100644
index 0000000..ca71e7e
--- /dev/null
+++ b/mach2.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+database = library.db
+media_dir = /path/to/music/dir
+debug = True
diff --git a/mach2.py b/mach2.py
new file mode 100644
index 0000000..479c0ab
--- /dev/null
+++ b/mach2.py
@@ -0,0 +1,95 @@
+import configparser
+import json
+
+from flask import Flask
+
+from models.album import Album
+from models.artist import Artist
+from models.track import Track
+
+
+app = Flask(__name__)
+app.config.from_object(__name__)
+
+
+@app.route("/")
+def hello():
+ return "Hello world!"
+
+
+@app.route("/search/album/<albumname>")
+def album_search(albumname):
+ albums = []
+ for album in Album.search(name=albumname):
+ albums.append(album.__dict__)
+
+ return json.dumps(albums)
+
+
+@app.route("/search/artist/<artistname>")
+def artist_search(artistname):
+ artists = []
+ for artist in Artist.search(name=artistname):
+ artists.append(artist.__dict__)
+
+ return json.dumps(artists)
+
+
+@app.route("/search/track/<trackname>")
+def track_search(trackname):
+ tracks = []
+ for track in Track.search(name=trackname):
+ tracks.append(track.__dict__)
+
+ return json.dumps(tracks)
+
+
+@app.route("/artist/<int:artist_id>/tracks")
+def artist_tracks(artist_id):
+ tracks = []
+ artist = Artist(artist_id)
+
+ for track in artist.tracks:
+ tracks.append(track.__dict__)
+
+ return json.dumps(tracks)
+
+
+@app.route("/artist/<int:artist_id>/albums")
+def artist_albums(artist_id):
+ albums = []
+ artist = Artist(artist_id)
+
+ for album in artist.albums:
+ albums.append(album.__dict__)
+
+ return json.dumps(albums)
+
+
+@app.route("/album/<int:album_id>/tracks")
+def album_tracks(album_id):
+ tracks = []
+ album = Album(album_id)
+
+ for track in album.tracks:
+ tracks.append(track.__dict__)
+
+ return json.dumps(tracks)
+
+
+@app.route("/album/<int:album_id>/artists")
+def album_artists(album_id):
+ artists = []
+ album = Album(album_id)
+
+ for artist in album.artists:
+ artists.append(artist.__dict__)
+
+ return json.dumps(artists)
+
+
+if __name__ == "__main__":
+ config = configparser.ConfigParser()
+ config.read("mach2.ini")
+
+ app.run(debug=config["DEFAULT"]["debug"])
diff --git a/models/__init__.py b/models/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/models/__init__.py
diff --git a/models/album.py b/models/album.py
new file mode 100644
index 0000000..727156f
--- /dev/null
+++ b/models/album.py
@@ -0,0 +1,114 @@
+from common import utils
+from db.db_manager import DbManager
+
+
+class Album():
+ def __init__(self, id=None, **kwargs):
+ if id is not None:
+ db = DbManager()
+ for row in db.execute("""SELECT * FROM album WHERE id = ?""",
+ (id,)):
+ setattr(self, "id", id)
+ setattr(self, "name", row[1])
+ setattr(self, "date", row[2])
+ else:
+ for (key, value) in kwargs.items():
+ setattr(self, key, value)
+
+ def delete(self):
+ db = DbManager()
+
+ for track in self.tracks:
+ track.delete()
+
+ delete_sql = "DELETE FROM album WHERE id = ?"
+ db.execute(delete_sql, (self.id,))
+
+ delete_track_rel_sql = "DELETE FROM album_track WHERE album_id = ?"
+ db.execute(delete_track_rel_sql, (self.id,))
+
+ delete_artist_rel_sql = "DELETE FROM album_artist WHERE album_id = ?"
+ db.execute(delete_artist_rel_sql, (self.id,))
+
+ db.commit()
+
+ return True
+
+ @property
+ def artists(self):
+ from models.artist import Artist
+
+ if not hasattr(self, "_artists"):
+ setattr(self, "_artists", [])
+
+ db = DbManager()
+
+ for row in db.execute("""SELECT artist.* FROM artist INNER JOIN
+ album_artist ON artist.id =
+ album_artist.artist_id WHERE album_id = ?
+ ORDER BY name ASC""", (self.id,)):
+ artist = Artist(id=row[0], name=row[1], sortname=row[2],
+ musicbrainz_artistid=row[3])
+ self._artists.append(artist)
+
+ return self._artists
+
+ @property
+ def tracks(self):
+ from models.track import Track
+
+ if not hasattr(self, "_tracks"):
+ setattr(self, "_tracks", [])
+
+ db = DbManager()
+
+ for row in db.execute("""SELECT track.* FROM track
+ INNER JOIN album_track ON track.id =
+ album_track.track_id WHERE album_id = ?
+ ORDER BY tracknumber ASC""", (self.id,)):
+
+ track = Track(id=row[0], tracknumber=row[1], name=row[2],
+ grouping=row[3], filename=row[4])
+ self._tracks.append(track)
+
+ return self._tracks
+
+ def save(self):
+ dirty_attributes = {}
+
+ # check if the internal dict has been modified
+ for (attr, value) in self.__dict__.items():
+ if self.__data[attr] != getattr(self, attr):
+ dirty_attributes[attr] = value
+
+ if len(dirty_attributes) > 0:
+ db = DbManager()
+
+ set_clause = utils.update_clause_from_dict(dirty_attributes)
+
+ dirty_attributes[id] = self.id
+
+ sql = " ".join(("UPDATE album"), set_clause, "WHERE id = :id")
+ db.execute(sql, dirty_attributes)
+ db.commit()
+
+ def search(**search_params):
+ albums = []
+
+ db = DbManager()
+
+ where_clause = utils.make_where_clause(search_params)
+
+ result = None
+ if where_clause:
+ statement = " ".join(("SELECT * FROM album", where_clause))
+ result = db.execute(statement, search_params)
+ else:
+ result = db.execute("SELECT * FROM album")
+
+ for row in result:
+ albums.append(
+ Album(id=row[0], name=row[1], date=row[2])
+ )
+
+ return albums
diff --git a/models/artist.py b/models/artist.py
new file mode 100644
index 0000000..eaae27e
--- /dev/null
+++ b/models/artist.py
@@ -0,0 +1,115 @@
+from common import utils
+from db.db_manager import DbManager
+
+
+class Artist:
+ def __init__(self, id=None, **kwargs):
+ if id is not None:
+ db = DbManager()
+ for row in db.execute("""SELECT * FROM artist WHERE id = ?""",
+ (id,)):
+ setattr(self, "id", id)
+ setattr(self, "name", row[1])
+ setattr(self, "sortname", row[2])
+ setattr(self, "musicbrainz_artistid", row[3])
+ else:
+ for (key, value) in kwargs.items():
+ setattr(self, key, value)
+
+ def delete(self):
+ db = DbManager()
+
+ for album in self.albums:
+ album.delete()
+
+ delete_sql = "DELETE FROM artist WHERE id = ?"
+ db.execute(delete_sql, (self.id,))
+
+ delete_track_rel_sql = "DELETE FROM artist_track WHERE artist_id = ?"
+ db.execute(delete_track_rel_sql, (self.id,))
+
+ delete_album_rel_sql = "DELETE FROM album_artist WHERE artist_id = ?"
+ db.execute(delete_album_rel_sql, (self.id,))
+
+ db.commit()
+
+ return True
+
+ @property
+ def tracks(self):
+ from models.track import Track
+
+ if not hasattr(self, "_tracks"):
+ setattr(self, "_tracks", [])
+
+ db = DbManager()
+
+ for row in db.execute("""SELECT track.* FROM track
+ INNER JOIN artist_track ON track.id =
+ artist_track.track_id WHERE artist_id = ?
+ ORDER BY name ASC""", (self.id,)):
+
+ track = Track(id=row[0], tracknumber=row[1], name=row[2],
+ grouping=row[3], filename=row[4])
+ self._tracks.append(track)
+
+ return self._tracks
+
+ @property
+ def albums(self):
+ from models.album import Album
+
+ if not hasattr(self, "_albums"):
+ setattr(self, "_albums", [])
+
+ db = DbManager()
+
+ for row in db.execute("""SELECT album.* FROM album
+ INNER JOIN album_artist ON album.id =
+ album_artist.album_id WHERE artist_id = ?
+ ORDER BY date ASC""", (self.id,)):
+ album = Album(id=row[0], name=row[1], date=row[2])
+ self._albums.append(album)
+
+ return self._albums
+
+ def save(self):
+ dirty_attributes = {}
+
+ # check if the internal dict has been modified
+ for (attr, value) in self.__dict__.items():
+ if self.__data[attr] != getattr(self, attr):
+ dirty_attributes[attr] = value
+
+ if len(dirty_attributes) > 0:
+ db = DbManager()
+
+ set_clause = utils.update_clause_from_dict(dirty_attributes)
+
+ dirty_attributes[id] = self.id
+
+ sql = " ".join(("UPDATE artist"), set_clause, "WHERE id = :id")
+ db.execute(sql, dirty_attributes)
+ db.commit()
+
+ def search(**search_params):
+ artists = []
+
+ db = DbManager()
+
+ where_clause = utils.make_where_clause(search_params)
+
+ result = []
+ if where_clause:
+ statement = " ".join(("SELECT * FROM artist", where_clause))
+ result = db.execute(statement, search_params)
+ else:
+ result = db.execute("SELECT * FROM artist")
+
+ for row in result:
+ artists.append(
+ Artist(id=row[0], name=row[1], sortname=row[2],
+ musicbrainz_artistid=row[3])
+ )
+
+ return artists
diff --git a/models/track.py b/models/track.py
new file mode 100644
index 0000000..264db81
--- /dev/null
+++ b/models/track.py
@@ -0,0 +1,487 @@
+import sqlite3
+
+from common import utils
+from db.db_manager import DbManager
+from models.artist import Artist
+from models.album import Album
+
+
+class Track:
+
+ def __init__(self, id=None, **kwargs):
+
+ setattr(self, "__data", {})
+
+ if id is not None:
+ db = DbManager()
+ for row in db.execute("""SELECT * FROM track WHERE id = ?""",
+ (id,)):
+ setattr(self, "id", row[0])
+ setattr(self, "tracknumber", row[1])
+ setattr(self, "name", row[2])
+ setattr(self, "grouping", row[3])
+ setattr(self, "filename", row[4])
+ else:
+ for (key, value) in kwargs.items():
+ setattr(self, key, value)
+ self.__data[key] = value
+
+ def delete(self):
+ db = DbManager()
+
+ delete_sql = "DELETE FROM track WHERE id = ?"
+ db.execute(delete_sql, (self.id,))
+ db.commit()
+
+ return True
+
+ @property
+ def album(self):
+ if not hasattr(self, "_album"):
+ setattr(self, "_album", None)
+
+ db = DbManager()
+
+ for row in db.execute("""SELECT album.* FROM album INNER JOIN
+ album_track ON album.id =
+ album_track.album_id WHERE track_id = ?
+ LIMIT 1""", (self.id,)):
+ setattr(self, "_album", Album(row[0]))
+
+ return self._album
+
+ @property
+ def artists(self):
+ if not hasattr(self, "_artists"):
+ db = DbManager()
+
+ setattr(self, "_artists", [])
+ for row in db.execute("""SELECT artist.* FROM artist INNER JOIN
+ artist_track ON artist.id =
+ artist_track.artist_id WHERE
+ artist.id = ?""", (self.id,)):
+ self._artists.append(Artist(row[0]))
+
+ return self._artists
+
+ def update(self, metadata):
+ db = DbManager()
+ c = db.cursor()
+
+ artist_names = metadata["artist"]
+ musicbrainz_artist_ids = []
+ artistsorts = []
+ try:
+ musicbrainz_artist_ids = metadata["musicbrainz_artistid"]
+ except KeyError:
+ pass
+ try:
+ artistsorts = metadata["artistsort"]
+ except KeyError:
+ pass
+
+ i = 0
+ artists = []
+
+ for artist_name in artist_names:
+ musicbrainz_artistid = None
+ artistsort = None
+
+ try:
+ musicbrainz_artistid = musicbrainz_artist_ids[i]
+ except IndexError:
+ pass
+ try:
+ artistsort = artistsorts[i]
+ except IndexError:
+ pass
+
+ rows = None
+
+ if musicbrainz_artistid:
+ rows = c.execute("""SELECT * FROM artist WHERE
+ musicbrainz_artist_id = ?""",
+ (musicbrainz_artistid,))
+ else:
+ rows = c.execute("""SELECT * FROM artist WHERE
+ name = ?""", (artist_name,))
+
+ row = rows.fetchone()
+
+ if row:
+ artist = Artist(id=row[0], name=row[1],
+ sortname=row[2],
+ musicbrainz_artist_id=row[3])
+
+ if artist.name != artist_name:
+ c.execute("""UPDATE artist SET name = ? WHERE id = ?""",
+ (artist_name, artist.id))
+ artist.name = artist_name
+
+ if artist.sortname != artistsort:
+ c.execute("""UPDATE artist SET sortname = ? WHERE id =
+ ? """, (artistsort, id))
+ artist.sortname = artistsort
+
+ else:
+ c.execute("""INSERT INTO artist
+ (name, sortname, musicbrainz_artist_id) VALUES(
+ ?,?,?)""", (artist_name, artistsort,
+ musicbrainz_artistid))
+
+ artist = Artist(
+ id=c.lastrowid, name=artist_name,
+ sortname=artistsort,
+ musicbrainz_artist_id=musicbrainz_artistid
+ )
+
+ i += 1
+
+ artists.append(artist)
+
+ album_name = None
+ album_date = None
+ mb_albumid = None
+
+ album = None
+ try:
+ album_name = metadata["album"][0]
+ except KeyError:
+ pass
+ try:
+ album_date = metadata["date"][0]
+ except KeyError:
+ pass
+
+ try:
+ mb_albumid = metadata["musicbrainz_albumid"][0]
+ except KeyError:
+ pass
+
+ if mb_albumid:
+ rows = c.execute(
+ """SELECT * FROM album WHERE musicbrainz_albumid = ?""",
+ (mb_albumid,)
+ )
+
+ row = rows.fetchone()
+
+ if row:
+ album = Album(id=row[0], name=row[1], date=row[2],
+ mb_albumid=row[3])
+ else:
+ c.execute("""INSERT INTO album (name, `date`,
+ musicbrainz_albumid) VALUES (?,?,?)""",
+ (album_name, album_date, mb_albumid))
+
+ album = Album(id=c.lastrowid, name=album_name,
+ date=album_date, musicbrainz_albumid=mb_albumid)
+
+ elif album_name:
+ rows = c.execute(
+ """SELECT album.* FROM album INNER JOIN album_artist ON
+ album_artist.album_id = album.id WHERE album.name = ?
+ AND artist_id = ?""", (album_name, artist.id)
+ )
+ row = rows.fetchone()
+
+ if row:
+ album = Album(id=row[0], name=row[1], date=row[2])
+ else:
+ c.execute("""INSERT INTO album (name, `date`) VALUES
+ (?,?)""", (album_name, album_date))
+
+ album = Album(id=c.lastrowid, name=album_name,
+ date=album_date)
+
+ if album:
+ if album.name != album_name:
+ c.execute("""UPDATE album SET name = ? WHERE id = ?""",
+ (album_name, album.id))
+ album.name = album_name
+
+ if album.date != album_date:
+ c.execute("""UPDATE album SET date = ? WHERE id = ?""",
+ (album_date, album.id))
+ album.date = album_date
+
+ for artist in artists:
+ if album:
+ try:
+ c.execute(
+ """INSERT INTO album_artist (artist_id,
+ album_id) VALUES(?,?)""",
+ (artist.id, album.id)
+ )
+ except sqlite3.IntegrityError:
+ pass
+
+ track_number = None
+ track_name = None
+ track_grouping = None
+ try:
+ track_number = metadata["tracknumber"][0]
+ except KeyError:
+ pass
+ try:
+ track_name = metadata["title"][0]
+ except KeyError:
+ pass
+ try:
+ track_grouping = metadata["grouping"][0]
+ except KeyError:
+ pass
+
+ c.execute("""UPDATE track SET tracknumber = ?, name = ?,
+ grouping = ? WHERE id = ?""",
+ (track_number, track_name, track_grouping,
+ self.id))
+
+ if album:
+ try:
+ c.execute("""INSERT INTO album_track (album_id,
+ track_id) VALUES(?,?)""",
+ (album.id, self.id))
+ except sqlite3.IntegrityError:
+ pass
+
+ for artist in artists:
+ try:
+ c.execute("""INSERT INTO artist_track
+ (artist_id, track_id) VALUES(?,?)""",
+ (artist.id, self.id))
+ except sqlite3.IntegrityError:
+ pass
+
+ db.commit()
+ c.close()
+ return True
+
+ def save(self):
+ dirty_attributes = {}
+
+ # check if the internal dict has been modified
+ for (attr, value) in self.__dict__.items():
+ if self.__data[attr] != getattr(self, attr):
+ dirty_attributes[attr] = value
+
+ if len(dirty_attributes) > 0:
+ db = DbManager()
+
+ set_clause = utils.update_clause_from_dict(dirty_attributes)
+
+ dirty_attributes[id] = self.id
+
+ sql = " ".join(("UPDATE track"), set_clause, "WHERE id = :id")
+ db.execute(sql, dirty_attributes)
+ db.commit()
+
+ def search(**search_params):
+ db = DbManager()
+ tracks = []
+
+ where_clause = utils.make_where_clause(search_params)
+
+ result = None
+ if where_clause:
+ statement = " ".join(("SELECT * FROM track", where_clause))
+ result = db.execute(statement, search_params)
+ else:
+ result = db.execute("SELECT * FROM track")
+
+ for row in result:
+ tracks.append(
+ Track(id=row[0], tracknumber=row[1], name=row[3],
+ grouping=row[3], filename=row[4])
+ )
+
+ return tracks
+
+ def find_by_path(path):
+ db = DbManager()
+ track = None
+
+ for row in db.execute("SELECT * FROM track WHERE filename = ? LIMIT 1",
+ (path,)):
+ track = Track(row[0])
+
+ return track
+
+ def store(filename, metadata):
+ db = DbManager()
+ c = db.cursor()
+
+ artist_names = metadata["artist"]
+ musicbrainz_artist_ids = []
+ artistsorts = []
+ try:
+ musicbrainz_artist_ids = metadata["musicbrainz_artistid"]
+ except KeyError:
+ pass
+ try:
+ artistsorts = metadata["artistsort"]
+ except KeyError:
+ pass
+
+ i = 0
+ artists = []
+
+ for artist_name in artist_names:
+ musicbrainz_artistid = None
+ artistsort = None
+ try:
+ musicbrainz_artistid = musicbrainz_artist_ids[i]
+ except IndexError:
+ pass
+ try:
+ artistsort = artistsorts[i]
+ except IndexError:
+ pass
+
+ rows = None
+ if musicbrainz_artistid:
+ rows = c.execute("""SELECT * FROM artist WHERE
+ musicbrainz_artist_id = ?""",
+ (musicbrainz_artistid,))
+ else:
+ rows = c.execute("""SELECT * FROM artist WHERE
+ name = ?""", (artist_name,))
+ row = rows.fetchone()
+ if row:
+ artist = Artist(id=row[0], name=row[1],
+ sortname=row[2],
+ musicbrainz_artist_id=row[3])
+ else:
+ c.execute("""INSERT INTO artist
+ (name, sortname, musicbrainz_artist_id) VALUES(
+ ?,?,?)""", (artist_name, artistsort,
+ musicbrainz_artistid))
+
+ artist = Artist(
+ id=c.lastrowid, name=artist_name,
+ sortname=artistsort,
+ musicbrainz_artist_id=musicbrainz_artistid
+ )
+
+ i += 1
+
+ artists.append(artist)
+
+ album_name = None
+ album_date = None
+ mb_albumid = None
+
+ album = None
+ try:
+ album_name = metadata["album"][0]
+ except KeyError:
+ pass
+ try:
+ album_date = metadata["date"][0]
+ except KeyError:
+ pass
+
+ try:
+ mb_albumid = metadata["musicbrainz_albumid"][0]
+ except KeyError:
+ pass
+
+ if mb_albumid:
+ rows = c.execute(
+ """SELECT * FROM album WHERE musicbrainz_albumid = ?""",
+ (mb_albumid,)
+ )
+
+ row = rows.fetchone()
+
+ if row:
+ album = Album(id=row[0], name=row[1], date=row[2],
+ mb_albumid=row[3])
+ else:
+ c.execute("""INSERT INTO album (name, `date`,
+ musicbrainz_albumid) VALUES (?,?,?)""",
+ (album_name, album_date, mb_albumid))
+
+ album = Album(id=c.lastrowid, name=album_name,
+ date=album_date, musicbrainz_albumid=mb_albumid)
+
+ elif album_name:
+ rows = c.execute(
+ """SELECT album.* FROM album INNER JOIN album_artist ON
+ album_artist.album_id = album.id WHERE album.name = ?
+ AND artist_id = ?""", (album_name, artist.id)
+ )
+ row = rows.fetchone()
+
+ if row:
+ album = Album(id=row[0], name=row[1], date=row[2])
+ else:
+ c.execute("""INSERT INTO album (name, `date`) VALUES
+ (?,?)""", (album_name, album_date))
+
+ album = Album(id=c.lastrowid, name=album_name,
+ date=album_date)
+
+ for artist in artists:
+ if album:
+ try:
+ c.execute(
+ """INSERT INTO album_artist (artist_id,
+ album_id) VALUES(?,?)""",
+ (artist.id, album.id)
+ )
+ except sqlite3.IntegrityError:
+ pass
+
+ track_number = None
+ track_name = None
+ track_grouping = None
+ try:
+ track_number = metadata["tracknumber"][0]
+ except KeyError:
+ pass
+ try:
+ track_name = metadata["title"][0]
+ except KeyError:
+ pass
+ try:
+ track_grouping = metadata["grouping"][0]
+ except KeyError:
+ pass
+
+ track = None
+ rows = c.execute("""SELECT * FROM track WHERE filename = ?""",
+ (filename,))
+ row = rows.fetchone()
+ if row:
+ track = Track(id=row[0], tracknumber=row[1], name=row[2],
+ grouping=row[3], filename=row[4])
+ else:
+ c.execute("""INSERT INTO track (tracknumber, name,
+ grouping, filename) VALUES(?,?,?,?)""",
+ (track_number, track_name, track_grouping,
+ filename))
+
+ track = Track(id=c.lastrowid, tracknumber=track_number,
+ name=track_name, grouping=track_grouping,
+ filename=filename)
+
+ if album:
+ try:
+ c.execute("""INSERT INTO album_track (album_id,
+ track_id) VALUES(?,?)""",
+ (album.id, track.id))
+ except sqlite3.IntegrityError:
+ pass
+
+ for artist in artists:
+ try:
+ c.execute("""INSERT INTO artist_track
+ (artist_id, track_id) VALUES(?,?)""",
+ (artist.id, track.id))
+ except sqlite3.IntegrityError:
+ pass
+
+ db.commit()
+ c.close()
+ return True
diff --git a/watcher.py b/watcher.py
new file mode 100644
index 0000000..7dbd434
--- /dev/null
+++ b/watcher.py
@@ -0,0 +1,66 @@
+import atexit
+import configparser
+
+import pyinotify
+
+import library
+
+
+class EventHandler(pyinotify.ProcessEvent):
+ def process_IN_CREATE(self, event):
+ print("Creating:", event.pathname)
+ library.run(event.pathname)
+
+ def process_IN_DELETE(self, event):
+ print("Removing:", event.pathname)
+ library.delete_file(event.pathname)
+
+ def process_IN_MOVED_TO(self, event):
+ print("Moved to:", event.pathname)
+ library.update_track_filename(event.src_pathname, event.pathname)
+
+ def process_IN_MODIFY(self, event):
+ print("Modified:", event.pathname)
+ library.update_file(event.pathname)
+
+
+class LibraryWatcher:
+
+ def __init__(self, path):
+ print("Setting up library watcher")
+
+ if not hasattr(self, "path"):
+ setattr(self, "path", path)
+
+ if not hasattr(self, "wm"):
+ setattr(self, "wm", pyinotify.WatchManager())
+
+ mask = pyinotify.IN_DELETE | pyinotify.IN_CREATE | \
+ pyinotify.IN_MOVED_TO | pyinotify.IN_MOVED_FROM | \
+ pyinotify.IN_MODIFY
+
+ if not hasattr(self, "notifier"):
+ setattr(self,
+ "notifier",
+ pyinotify.ThreadedNotifier(self.wm, EventHandler()))
+
+ self.notifier.coalesce_events()
+ self.notifier.start()
+
+ if not hasattr(self, "wdd"):
+ setattr(self, "wdd", self.wm.add_watch(path, mask, rec=True,
+ auto_add=True))
+
+ print("Set up watch on ", path)
+
+ atexit.register(self.stop)
+
+ def stop(self):
+ if self.wdd[self.path] > 0:
+ self.wm.rm_watch(self.wdd[self.path], rec=True)
+
+if __name__ == "__main__":
+ config = configparser.ConfigParser()
+ config.read("mach2.ini")
+
+ watch = LibraryWatcher(config["DEFAULT"]["media_dir"])