mirror of
https://github.com/spl0k/supysonic.git
synced 2024-12-22 08:56:17 +00:00
Media folder scanner
This commit is contained in:
parent
84bc079a72
commit
2cffe64946
46
db.py
46
db.py
@ -1,20 +1,23 @@
|
||||
# coding: utf-8
|
||||
|
||||
import config
|
||||
from sqlalchemy import create_engine, Column, Integer, String, Boolean
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||
|
||||
from sqlalchemy import create_engine, Column, ForeignKey
|
||||
from sqlalchemy import Integer, String, Boolean, Date, Time
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker, relationship, backref
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
from sqlalchemy import types
|
||||
from sqlalchemy.types import TypeDecorator
|
||||
from sqlalchemy import BINARY
|
||||
from sqlalchemy.schema import Column
|
||||
|
||||
import uuid
|
||||
|
||||
class UUID(types.TypeDecorator):
|
||||
class UUID(TypeDecorator):
|
||||
impl = BINARY
|
||||
|
||||
def __init__(self):
|
||||
self.impl.length = 16
|
||||
types.TypeDecorator.__init__(self, length = self.impl.length)
|
||||
TypeDecorator.__init__(self, length = self.impl.length)
|
||||
|
||||
def process_bind_param(self, value, dialect = None):
|
||||
if value and isinstance(value, uuid.UUID):
|
||||
@ -42,7 +45,7 @@ Base = declarative_base()
|
||||
Base.query = session.query_property()
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = 'users'
|
||||
__tablename__ = 'user'
|
||||
|
||||
id = UUID.gen_id_column()
|
||||
name = Column(String, unique = True)
|
||||
@ -52,11 +55,38 @@ class User(Base):
|
||||
admin = Column(Boolean)
|
||||
|
||||
class MusicFolder(Base):
|
||||
__tablename__ = 'folders'
|
||||
__tablename__ = 'folder'
|
||||
|
||||
id = UUID.gen_id_column()
|
||||
name = Column(String, unique = True)
|
||||
path = Column(String)
|
||||
last_scan = Column(Date, nullable = True)
|
||||
|
||||
class Artist(Base):
|
||||
__tablename__ = 'artist'
|
||||
|
||||
id = UUID.gen_id_column()
|
||||
name = Column(String)
|
||||
albums = relationship('Album', backref = 'artist', lazy = 'dynamic')
|
||||
|
||||
class Album(Base):
|
||||
__tablename__ = 'album'
|
||||
|
||||
id = UUID.gen_id_column()
|
||||
name = Column(String)
|
||||
artist_id = Column(UUID, ForeignKey('artist.id'))
|
||||
tracks = relationship('Track', backref = 'album', lazy = 'dynamic')
|
||||
|
||||
class Track(Base):
|
||||
__tablename__ = 'track'
|
||||
|
||||
id = UUID.gen_id_column()
|
||||
disc = Column(Integer)
|
||||
number = Column(Integer)
|
||||
title = Column(String)
|
||||
duration = Column(Time)
|
||||
album_id = Column(UUID, ForeignKey('album.id'))
|
||||
path = Column(String)
|
||||
|
||||
def init_db():
|
||||
Base.metadata.create_all(bind = engine)
|
||||
|
74
scanner.py
Executable file
74
scanner.py
Executable file
@ -0,0 +1,74 @@
|
||||
# coding: utf-8
|
||||
|
||||
import os.path
|
||||
import datetime
|
||||
import eyeD3
|
||||
|
||||
import db
|
||||
|
||||
def seconds_to_time(secs):
|
||||
th = secs / 3600
|
||||
tm = (secs % 3600) / 60
|
||||
ts = secs % 60
|
||||
return datetime.time(int(th), int(tm), int(ts))
|
||||
|
||||
class Scanner:
|
||||
def __init__(self, session):
|
||||
self.__session = session
|
||||
self.__added_artists = 0
|
||||
self.__added_albums = 0
|
||||
self.__added_tracks = 0
|
||||
self.__deleted_artists = 0
|
||||
self.__deleted_albums = 0
|
||||
self.__deleted_tracks = 0
|
||||
|
||||
def scan(self, folder):
|
||||
for root, subfolders, files in os.walk(folder.path):
|
||||
for f in files:
|
||||
if f.endswith('.mp3'):
|
||||
self.__scan_file(os.path.join(root, f))
|
||||
|
||||
def prune(self, folder):
|
||||
pass
|
||||
|
||||
def __scan_file(self, path):
|
||||
tag = eyeD3.Tag()
|
||||
tag.link(path)
|
||||
|
||||
al = self.__find_album(tag.getArtist(), tag.getAlbum())
|
||||
tr = al.tracks.filter(db.Track.path == path).first()
|
||||
if tr is None:
|
||||
tr = db.Track(path = path)
|
||||
self.__added_tracks += 1
|
||||
|
||||
tr.disc = (tag.getDiscNum() or (1, 1))[0]
|
||||
tr.number = tag.getTrackNum()[0]
|
||||
tr.title = tag.getTitle()
|
||||
tr.duration = seconds_to_time(eyeD3.Mp3AudioFile(path).getPlayTime())
|
||||
tr.album = al
|
||||
|
||||
def __find_album(self, artist, album):
|
||||
ar = self.__find_artist(artist)
|
||||
al = ar.albums.filter(db.Album.name == album).first()
|
||||
if not al is None:
|
||||
return al
|
||||
|
||||
al = db.Album(name = album, artist = ar)
|
||||
self.__added_albums += 1
|
||||
|
||||
return al
|
||||
|
||||
def __find_artist(self, artist):
|
||||
ar = self.__session.query(db.Artist).filter(db.Artist.name == artist).first()
|
||||
if not ar is None:
|
||||
return ar
|
||||
|
||||
ar = db.Artist(name = artist)
|
||||
self.__session.add(ar)
|
||||
self.__added_artists += 1
|
||||
|
||||
return ar
|
||||
|
||||
def stats(self):
|
||||
return (self.__added_artists, self.__added_albums, self.__added_tracks), (self.__deleted_artists, self.__deleted_albums, self.__deleted_tracks)
|
||||
|
@ -11,10 +11,39 @@
|
||||
|
||||
<h2>Music folders</h2>
|
||||
<table>
|
||||
<tr><th>Name</th><th>Path</th><th></th></tr>
|
||||
<tr><th>Name</th><th>Path</th><th></th><th></th></tr>
|
||||
{% for folder in folders %}
|
||||
<tr><td>{{ folder.name }}</td><td>{{ folder.path }}</td><td><a href="{{ url_for('del_folder', id = folder.id) }}">X</a></td></tr>
|
||||
<tr>
|
||||
<td>{{ folder.name }}</td><td>{{ folder.path }}</td><td><a href="{{ url_for('del_folder', id = folder.id) }}">X</a></td>
|
||||
<td><a href="{{ url_for('scan_folder', id = folder.id) }}">Scan</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<a href="{{ url_for('add_folder') }}">Add</a>
|
||||
<a href="{{ url_for('add_folder') }}">Add</a> <a href="{{ url_for('scan_folder') }}">Scan all</a>
|
||||
|
||||
<h2>Artists</h2>
|
||||
<table>
|
||||
<tr><th>Id</th><th>Name</th></tr>
|
||||
{% for artist in artists %}
|
||||
<tr><td>{{ artist.id }}</td><td>{{ artist.name }}</td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<h2>Albums</h2>
|
||||
<table>
|
||||
<tr><th>Artist</th><th>Album</th></tr>
|
||||
{% for album in albums %}
|
||||
<tr><td>{{ album.artist.name }}</td><td>{{ album.name }}</td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<h2>Tracks</h2>
|
||||
<table>
|
||||
<tr><th>Artist</th><th>Album</th><th>Disc</th><th>#</th><th>Title</th><th>Len</th><th>Path</th></tr>
|
||||
{% for track in tracks %}
|
||||
<tr><td>{{ track.album.artist.name }}</td><td>{{ track.album.name }}</td><td>{{ track.disc }}</td><td>{{ track.number }}</td><td>{{ track.title }}</td><td>{{ track.duration }}</td><td>{{ track.path }}</td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<p>{{ tracks|length }} tracks</p>
|
||||
|
||||
{% endblock %}
|
||||
|
67
web.py
67
web.py
@ -10,6 +10,7 @@ app = Flask(__name__)
|
||||
app.secret_key = '?9huDM\\H'
|
||||
|
||||
import db
|
||||
from scanner import Scanner
|
||||
|
||||
@app.teardown_request
|
||||
def teardown(exception):
|
||||
@ -22,7 +23,10 @@ def index():
|
||||
flash('Not configured. Please create the first admin user')
|
||||
return redirect(url_for('add_user'))
|
||||
"""
|
||||
return render_template('home.html', users = db.User.query.all(), folders = db.MusicFolder.query.all())
|
||||
return render_template('home.html', users = db.User.query.all(), folders = db.MusicFolder.query.all(),
|
||||
artists = db.Artist.query.order_by(db.Artist.name).all(),
|
||||
albums = db.Album.query.join(db.Album.artist).order_by(db.Artist.name, db.Album.name).all(),
|
||||
tracks = db.Track.query.join(db.Track.album, db.Album.artist).order_by(db.Artist.name, db.Album.name, db.Track.disc, db.Track.number).all())
|
||||
|
||||
@app.route('/resetdb')
|
||||
def reset_db():
|
||||
@ -64,14 +68,18 @@ def add_user():
|
||||
def del_user(id):
|
||||
try:
|
||||
idid = uuid.UUID(id)
|
||||
user = db.User.query.filter(db.User.id == uuid.UUID(id)).one()
|
||||
db.session.delete(user)
|
||||
db.session.commit()
|
||||
flash("Deleted user '%s'" % user.name)
|
||||
except ValueError:
|
||||
flash('Invalid user id')
|
||||
except NoResultFound:
|
||||
return redirect(url_for('index'))
|
||||
|
||||
user = db.User.query.get(idid)
|
||||
if user is None:
|
||||
flash('No such user')
|
||||
return redirect(url_for('index'))
|
||||
|
||||
db.session.delete(user)
|
||||
db.session.commit()
|
||||
flash("Deleted user '%s'" % user.name)
|
||||
|
||||
return redirect(url_for('index'))
|
||||
|
||||
@ -114,15 +122,50 @@ def add_folder():
|
||||
def del_folder(id):
|
||||
try:
|
||||
idid = uuid.UUID(id)
|
||||
folder = db.MusicFolder.query.filter(db.MusicFolder.id == uuid.UUID(id)).one()
|
||||
db.session.delete(folder)
|
||||
db.session.commit()
|
||||
flash("Deleted folder '%s'" % folder.name)
|
||||
except ValueError:
|
||||
flash('Invalid folder id')
|
||||
except NoResultFound:
|
||||
flash('No such folder')
|
||||
return redirect(url_for('index'))
|
||||
|
||||
folder = db.MusicFolder.query.get(idid)
|
||||
if folder is None:
|
||||
flash('No such folder')
|
||||
return redirect(url_for('index'))
|
||||
|
||||
db.session.delete(folder)
|
||||
# TODO delete associated tracks
|
||||
db.session.commit()
|
||||
flash("Deleted folder '%s'" % folder.name)
|
||||
|
||||
return redirect(url_for('index'))
|
||||
|
||||
@app.route('/scan')
|
||||
@app.route('/scan/<id>')
|
||||
def scan_folder(id = None):
|
||||
s = Scanner(db.session)
|
||||
if id is None:
|
||||
for folder in db.MusicFolder.query.all():
|
||||
s.scan(folder)
|
||||
s.prune(folder)
|
||||
else:
|
||||
try:
|
||||
idid = uuid.UUID(id)
|
||||
except ValueError:
|
||||
flash('Invalid folder id')
|
||||
return redirect(url_for('index'))
|
||||
|
||||
folder = db.MusicFolder.query.get(idid)
|
||||
if folder is None:
|
||||
flash('No such folder')
|
||||
return redirect(url_for('index'))
|
||||
|
||||
s.scan(folder)
|
||||
s.prune(folder)
|
||||
|
||||
added, deleted = s.stats()
|
||||
db.session.commit()
|
||||
|
||||
flash('Added: %i artists, %i albums, %i tracks' % (added[0], added[1], added[2]))
|
||||
flash('Deleted: %i artists, %i albums, %i tracks' % (deleted[0], deleted[1], deleted[2]))
|
||||
return redirect(url_for('index'))
|
||||
|
||||
import api.system
|
||||
|
Loading…
Reference in New Issue
Block a user