indexer.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import time
  2. from collections import deque
  3. from queue import Queue
  4. from threading import Thread, Lock
  5. import vlc
  6. from path import Path
  7. from core.exceptions import NotSupportedFile
  8. from core.file_utilities import is_media_file_ext, hash_file
  9. from core.logging_ import Logger
  10. from core.models import Track
  11. from core.repositories import MusicFolderRepository, TrackRepository
  12. logger = Logger.get()
  13. class CyclicThread(Thread):
  14. DELAY = 0
  15. def __init__(self):
  16. Thread.__init__(self)
  17. self.interrupted = False
  18. self.last_exec = 0
  19. def act(self):
  20. raise NotImplementedError()
  21. def run(self):
  22. t = None
  23. while 1:
  24. if self.DELAY:
  25. t = time.time()
  26. if not self.DELAY or not self.last_exec or (t - self.last_exec) > self.DELAY:
  27. self.act()
  28. self.last_exec = t
  29. if self.interrupted:
  30. break
  31. def trigger(self):
  32. self.last_exec = 0
  33. def stop(self):
  34. self.interrupted = True
  35. class Discoverer(CyclicThread):
  36. DELAY = 5
  37. def __init__(self, indexer):
  38. CyclicThread.__init__(self)
  39. self.indexer = indexer
  40. def act(self):
  41. music_folder_repo = MusicFolderRepository()
  42. track_repo = TrackRepository()
  43. index = {t.path: t for t in track_repo.get_all()}
  44. for music_folder in music_folder_repo.get_all():
  45. music_folder_path = Path(music_folder.path)
  46. for filename in music_folder_path.walkfiles():
  47. if self.indexer.in_deque(filename):
  48. continue
  49. if filename not in index and is_media_file_ext(filename.ext):
  50. self.indexer.put(filename)
  51. elif filename in index:
  52. track = index[filename]
  53. if track.status == Track.STATUS_UNAVAILABLE:
  54. self.indexer.put(track.id)
  55. del index[filename]
  56. for filename, track in index.items():
  57. if self.indexer.in_deque(track.id):
  58. continue
  59. filename = Path(filename)
  60. if not filename.exists():
  61. self.indexer.put(track.id)
  62. class Indexer(CyclicThread):
  63. def __init__(self):
  64. CyclicThread.__init__(self)
  65. self.deque = deque()
  66. self.interrupted = False
  67. self.discoverer = Discoverer(self)
  68. def start(self):
  69. logger.info('** indexation thread started **')
  70. self.discoverer.start()
  71. super().start()
  72. def act(self):
  73. if self.deque:
  74. try:
  75. self.index(self.deque.pop())
  76. except (FileNotFoundError, NotSupportedFile) as e:
  77. logger.warning("Error during indexation: %s" % e)
  78. def put(self, filename_or_track_id):
  79. self.deque.appendleft(filename_or_track_id)
  80. def in_deque(self, filename_or_track_id):
  81. return filename_or_track_id in self.deque
  82. @staticmethod
  83. def index(filename_or_track_id):
  84. """ index a media file from the filesystem or a track id """
  85. track_repo = TrackRepository()
  86. if type(filename_or_track_id) is int:
  87. track = track_repo.get_by_id(filename_or_track_id)
  88. filename = Path(track.path)
  89. track_hash = track.hash
  90. if not filename.exists():
  91. logger.debug('Index - missing: %s' % filename)
  92. track.status = Track.STATUS_UNAVAILABLE
  93. track_repo.commit()
  94. return
  95. else:
  96. filename = Path(filename_or_track_id)
  97. if not filename.exists():
  98. raise FileNotFoundError(f"File not found: {filename}")
  99. if not is_media_file_ext(filename.ext):
  100. raise NotSupportedFile(f"File's extension {filename.ext} is not supported")
  101. track_hash = hash_file(filename)
  102. track = track_repo.get_by_hash(track_hash)
  103. if not track:
  104. track = Track()
  105. vlc_media = vlc.Media(filename)
  106. vlc_media.parse()
  107. track_infos = vlc_media.get_tracks_info()
  108. track.title = vlc_media.get_meta(vlc.Meta.Title) or filename.stripext().name
  109. track.format = filename.ext
  110. track.artist = vlc_media.get_meta(vlc.Meta.AlbumArtist) or vlc_media.get_meta(vlc.Meta.Artist)
  111. track.album = vlc_media.get_meta(vlc.Meta.Album)
  112. track.track_num = vlc_media.get_meta(vlc.Meta.TrackNumber)
  113. # track.year = vlc_media.get_meta(vlc.Meta.Date)
  114. # track.duration = vlc_media.get_meta(vlc.Meta.Date)
  115. # track.size = 0
  116. track.note = ""
  117. track.status = Track.STATUS_FOUND
  118. track.path = filename
  119. track.hash = track_hash
  120. if track.id is None:
  121. track_repo.create(track)
  122. logger.debug('Index - updated: %s' % filename)
  123. track_repo.commit()
  124. def stop(self):
  125. self.discoverer.stop()
  126. super().stop()
  127. logger.info('** indexation thread stopped **')
  128. if __name__ == '__main__':
  129. indexer = Indexer()
  130. indexer.start()
  131. try:
  132. indexer.join()
  133. except KeyboardInterrupt:
  134. indexer.stop()