import time from collections import deque from threading import Thread, Timer, Event import vlc from PyQt5.QtCore import pyqtSignal, QObject from path import Path from core import db from core.exceptions import NotSupportedFile from core.file_utilities import is_media_file_ext, hash_file from core.logging_ import Logger from core.models import Track from core.repositories import MusicFolderRepository, TrackRepository logger = Logger.get() class AlreadyIndexed(Exception): pass class Emitter(QObject): filesIndexed = pyqtSignal(list) musicFolderStatusChanged = pyqtSignal(int) class Indexer(Thread): DELAY = 2 def __init__(self): Thread.__init__(self) self.stopped = Event() self.emitter = Emitter() self.timer = Timer(self.DELAY, self.act) def run(self): logger.info('** indexation thread started **') while not self.stopped.wait(self.DELAY): self.act() def act(self): # Initialize session = db.Session() music_folder_repo = MusicFolderRepository(session) track_repo = TrackRepository(session) # Get current data music_folders = music_folder_repo.get_all() tracks = track_repo.get_all() # Index existing index = {t.path: t for t in tracks} buffer = deque() # -- Walk through music folders # Index new files for music_folder in music_folders: music_folder_path = Path(music_folder.path) if not music_folder_path.exists(): if music_folder.status == music_folder.STATUS_FOUND: music_folder.status = music_folder.STATUS_UNAVAILABLE music_folder_repo.commit() self.emitter.musicFolderStatusChanged.emit(music_folder.id) continue if music_folder.status != music_folder.STATUS_FOUND: music_folder.status = music_folder.STATUS_FOUND music_folder_repo.commit() self.emitter.musicFolderStatusChanged.emit(music_folder.id) for filename in music_folder_path.walkfiles(): if filename in buffer: continue if filename not in index and is_media_file_ext(filename.ext): buffer.append(filename) elif filename in index: track = index[filename] if track.status == Track.STATUS_UNAVAILABLE: buffer.append(track.id) del index[filename] # Index missing files for filename, track in index.items(): if track.id in buffer: continue filename = Path(filename) if not filename.exists() and track.status != Track.STATUS_UNAVAILABLE: buffer.append(track.id) # Index buffered tracks tracks = [] while buffer: filename_or_id = buffer.pop() try: track = self.index(track_repo, filename_or_id) tracks.append(track) except AlreadyIndexed: pass except (FileNotFoundError, NotSupportedFile) as e: logger.warning("Error during indexation: %s" % e) continue except IndexError: break # Finalize if tracks: for track in tracks: if track.id is None: track_repo.create(track) track_repo.commit() self.emitter.filesIndexed.emit(tracks) logger.info(f"{len(tracks)} tracks indexed") @staticmethod def index(track_repo, filename_or_track_id): """ index a media file from the filesystem or a track id """ if type(filename_or_track_id) is int: track = track_repo.get_by_id(filename_or_track_id) filename = Path(track.path) track_hash = track.hash if not filename.exists() and track.status != Track.STATUS_UNAVAILABLE: logger.debug('Index - missing: %s' % filename) track.status = Track.STATUS_UNAVAILABLE return track else: filename = Path(filename_or_track_id) if not filename.exists(): raise FileNotFoundError(f"File not found: {filename}") if not is_media_file_ext(filename.ext): raise NotSupportedFile(f"File's extension {filename.ext} is not supported") track_hash = hash_file(filename) track = track_repo.get_by_hash(track_hash) if not track: track = Track() elif track.status == Track.STATUS_FOUND: raise AlreadyIndexed(f"File already indexed") vlc_media = vlc.Media(filename) vlc_media.parse() track_infos = vlc_media.get_tracks_info() title = vlc_media.get_meta(vlc.Meta.Title) if not title or title == '(null)': title = filename.stripext().name track.title = title track.format = filename.ext track.artist = vlc_media.get_meta(vlc.Meta.AlbumArtist) or vlc_media.get_meta(vlc.Meta.Artist) track.album = vlc_media.get_meta(vlc.Meta.Album) track.track_num = vlc_media.get_meta(vlc.Meta.TrackNumber) # track.year = vlc_media.get_meta(vlc.Meta.Date) # track.duration = vlc_media.get_meta(vlc.Meta.Date) # track.size = 0 track.note = "" track.status = Track.STATUS_FOUND track.path = filename track.hash = track_hash return track def stop(self): self.stopped.set() while self.is_alive(): time.sleep(0.1) logger.info('** indexation thread stopped **') if __name__ == '__main__': indexer = Indexer() indexer.start() try: indexer.join() except KeyboardInterrupt: indexer.stop()