import time from collections import deque from queue import Queue from threading import Thread, Lock import vlc 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 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 time.sleep(0.1) 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): session = db.Session() music_folder_repo = MusicFolderRepository(session) music_folders = music_folder_repo.get_all() track_repo = TrackRepository(session) tracks = track_repo.get_all() index = {t.path: t for t in tracks} for music_folder in music_folders: 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() and track.status != Track.STATUS_UNAVAILABLE: self.indexer.put(track.id) class Indexer(CyclicThread): DELAY = 2 BUFFER_SIZE = 100 def __init__(self): CyclicThread.__init__(self) self.deque = deque() self.interrupted = False self.discoverer = Discoverer(self) self.last_commit = None self.tracks = [] def start(self): logger.info('** indexation thread started **') self.discoverer.start() super().start() def act(self): buffer = [] session = db.Session() track_repo = TrackRepository(session) for _ in range(self.BUFFER_SIZE): try: track = self.index(track_repo, self.deque.pop()) buffer.append(track) except (FileNotFoundError, NotSupportedFile) as e: logger.warning("Error during indexation: %s" % e) continue except IndexError: break if buffer: for track in buffer: if track.id is None: track_repo.create(track) logger.info(f"{len(buffer)} tracks indexed") track_repo.commit() 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(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() 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.discoverer.stop() super().stop() logger.info('** indexation thread stopped **') if __name__ == '__main__': indexer = Indexer() indexer.start() try: indexer.join() except KeyboardInterrupt: indexer.stop()