| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- 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, file_utilities
- 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):
- # logger.debug("... indexation")
- 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)
- # music folder cant be found
- 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
- # music folder found
- 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)
- # walk files
- for filename in music_folder_path.walkfiles():
- # filename already seen
- if filename in buffer:
- continue
- # new file
- if filename not in index and is_media_file_ext(filename.ext):
- buffer.append(filename)
- # file already in db
- elif filename in index:
- track = index[filename]
- if track.status in (Track.STATUS_UNAVAILABLE, Track.STATUS_UNKNOWN):
- 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
- # NB: the tracks are treated from the end to the beginning, so missing files are treated before the new ones
- 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()
- title = vlc_media.get_meta(vlc.Meta.Title)
- if not title or title == '(null)' or title == filename.name:
- 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.duration = vlc_media.get_duration() // 1000
- 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()
|