import time from collections import deque from queue import Queue from threading import Thread, Lock import vlc from path import Path 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 CyclicThread(Thread): DELAY = 0 def __init__(self): Thread.__init__(self) self.interrupted = False self.last_exec = 0 def act(self): raise NotImplementedError() def run(self): t = None while 1: if self.DELAY: t = time.time() if not self.DELAY or not self.last_exec or (t - self.last_exec) > self.DELAY: self.act() self.last_exec = t if self.interrupted: break def trigger(self): self.last_exec = 0 def stop(self): self.interrupted = True class Discoverer(CyclicThread): DELAY = 5 def __init__(self, indexer): CyclicThread.__init__(self) self.indexer = indexer def act(self): music_folder_repo = MusicFolderRepository() track_repo = TrackRepository() index = {t.path: t for t in track_repo.get_all()} for music_folder in music_folder_repo.get_all(): music_folder_path = Path(music_folder.path) for filename in music_folder_path.walkfiles(): if self.indexer.in_deque(filename): continue if filename not in index and is_media_file_ext(filename.ext): self.indexer.put(filename) elif filename in index: track = index[filename] if track.status == Track.STATUS_UNAVAILABLE: self.indexer.put(track.id) del index[filename] for filename, track in index.items(): if self.indexer.in_deque(track.id): continue filename = Path(filename) if not filename.exists(): self.indexer.put(track.id) class Indexer(CyclicThread): def __init__(self): CyclicThread.__init__(self) self.deque = deque() self.interrupted = False self.discoverer = Discoverer(self) def start(self): logger.info('** indexation thread started **') self.discoverer.start() super().start() def act(self): if self.deque: try: self.index(self.deque.pop()) except (FileNotFoundError, NotSupportedFile) as e: logger.warning("Error during indexation: %s" % e) def put(self, filename_or_track_id): self.deque.appendleft(filename_or_track_id) def in_deque(self, filename_or_track_id): return filename_or_track_id in self.deque @staticmethod def index(filename_or_track_id): """ index a media file from the filesystem or a track id """ track_repo = TrackRepository() 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(): logger.debug('Index - missing: %s' % filename) track.status = Track.STATUS_UNAVAILABLE track_repo.commit() return 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() vlc_media = vlc.Media(filename) vlc_media.parse() track_infos = vlc_media.get_tracks_info() track.title = vlc_media.get_meta(vlc.Meta.Title) or filename.stripext().name 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 if track.id is None: track_repo.create(track) logger.debug('Index - updated: %s' % filename) track_repo.commit() def stop(self): self.discoverer.stop() super().stop() logger.info('** indexation thread stopped **') if __name__ == '__main__': indexer = Indexer() indexer.start() try: indexer.join() except KeyboardInterrupt: indexer.stop()