indexer.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  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() and track.status != Track.STATUS_UNAVAILABLE:
  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. self.last_commit = None
  69. self.tracks = []
  70. def start(self):
  71. logger.info('** indexation thread started **')
  72. self.discoverer.start()
  73. super().start()
  74. def act(self):
  75. if self.deque:
  76. try:
  77. track = self.index(self.deque.pop())
  78. except (FileNotFoundError, NotSupportedFile) as e:
  79. logger.warning("Error during indexation: %s" % e)
  80. return
  81. def put(self, filename_or_track_id):
  82. self.deque.appendleft(filename_or_track_id)
  83. def in_deque(self, filename_or_track_id):
  84. return filename_or_track_id in self.deque
  85. @staticmethod
  86. def index(filename_or_track_id):
  87. """ index a media file from the filesystem or a track id """
  88. track_repo = TrackRepository()
  89. if type(filename_or_track_id) is int:
  90. track = track_repo.get_by_id(filename_or_track_id)
  91. filename = Path(track.path)
  92. track_hash = track.hash
  93. if not filename.exists() and track.status != Track.STATUS_UNAVAILABLE:
  94. logger.debug('Index - missing: %s' % filename)
  95. track.status = Track.STATUS_UNAVAILABLE
  96. track_repo.commit()
  97. return
  98. else:
  99. filename = Path(filename_or_track_id)
  100. if not filename.exists():
  101. raise FileNotFoundError(f"File not found: {filename}")
  102. if not is_media_file_ext(filename.ext):
  103. raise NotSupportedFile(f"File's extension {filename.ext} is not supported")
  104. track_hash = hash_file(filename)
  105. track = track_repo.get_by_hash(track_hash)
  106. if not track:
  107. track = Track()
  108. vlc_media = vlc.Media(filename)
  109. vlc_media.parse()
  110. track_infos = vlc_media.get_tracks_info()
  111. title = vlc_media.get_meta(vlc.Meta.Title)
  112. if not title or title == '(null)':
  113. title = filename.stripext().name
  114. track.title = title
  115. track.format = filename.ext
  116. track.artist = vlc_media.get_meta(vlc.Meta.AlbumArtist) or vlc_media.get_meta(vlc.Meta.Artist)
  117. track.album = vlc_media.get_meta(vlc.Meta.Album)
  118. track.track_num = vlc_media.get_meta(vlc.Meta.TrackNumber)
  119. # track.year = vlc_media.get_meta(vlc.Meta.Date)
  120. # track.duration = vlc_media.get_meta(vlc.Meta.Date)
  121. # track.size = 0
  122. track.note = ""
  123. track.status = Track.STATUS_FOUND
  124. track.path = filename
  125. track.hash = track_hash
  126. if track.id is None:
  127. track_repo.create(track)
  128. logger.debug('Index - updated: %s' % filename)
  129. track_repo.commit()
  130. return track
  131. def stop(self):
  132. self.discoverer.stop()
  133. super().stop()
  134. logger.info('** indexation thread stopped **')
  135. if __name__ == '__main__':
  136. indexer = Indexer()
  137. indexer.start()
  138. try:
  139. indexer.join()
  140. except KeyboardInterrupt:
  141. indexer.stop()