def syncImage(img, current, session): """Add or updated the Image.""" def _img_str(i): return "%s - %s" % (i.type, i.description) for db_img in current.images: img_info = (img.type, img.md5, img.size) db_img_info = (db_img.type, db_img.md5, db_img.size) if db_img_info == img_info: img = None break elif (db_img.type == img.type and db_img.description == img.description): if img.md5 != db_img.md5: # Update image current.images.remove(db_img) current.images.append(img) session.add(current) pout(Fg.green("Updating image") + ": " + _img_str(img)) img = None break if img: # Add image current.images.append(img) session.add(current) pout(Fg.green("Adding image") + ": " + _img_str(img))
def _syncLib(lib): args._library = lib args.paths = [] for p in lib.paths: args.paths.append(str(p) if isinstance(p, Path) else p) pout("{}yncing library '{}': paths={}".format( "Force s" if args.force else "S", lib.name, lib.paths), log=log) return eyed3_main(args, None)
def test_plog(capfd): msg = "Xymox - Phoenix" log = logging.getLogger("test_plog") with patch.object(log, "info") as mock: pout(msg, log=log) out, _ = capfd.readouterr() assert out == msg + "\n" mock.assert_called_once_with(msg)
def deleteOrphans(session): num_orphaned_artists = 0 num_orphaned_albums = 0 num_orphaned_tracks = 0 found_ids = set() # Tracks for track in session.query(Track).all(): if not os.path.exists(track.path): pout(Fg.red("Removing track") + ": " + track.path) session.delete(track) num_orphaned_tracks += 1 log.warn("Deleting track: %s" % str(track)) session.flush() # Artists found_ids.clear() for artist in session.query(Artist).all(): if (artist.id == VARIOUS_ARTISTS_ID or artist.id in found_ids): continue any_track = session.query(Track).filter(Track.artist_id == artist.id) \ .first() any_album = session.query(Album).filter(Album.artist_id == artist.id) \ .first() if not any_track and not any_album: log.warn("Deleting artist: %s" % str(artist)) session.delete(artist) num_orphaned_artists += 1 else: found_ids.add(artist.id) session.flush() # Albums found_ids.clear() for album in session.query(Album).all(): if album.id in found_ids: continue any_track = session.query(Track).filter(Track.album_id == album.id) \ .first() if not any_track: log.warn("Deleting album: %s" % str(album)) session.delete(album) num_orphaned_albums += 1 else: found_ids.add(album.id) return (num_orphaned_tracks, num_orphaned_artists, num_orphaned_albums)
def deleteOrphans(session): num_orphaned_artists = 0 num_orphaned_albums = 0 num_orphaned_tracks = 0 found_ids = set() # Tracks for track in session.query(Track).all(): if not os.path.exists(track.path): pout(Fg.red("Removing track") + ": " + track.path) session.delete(track) num_orphaned_tracks += 1 log.warn("Deleting track: %s" % str(track)) session.flush() # Albums found_ids.clear() for album in session.query(Album).all(): if album.id in found_ids: continue any_track = session.query(Track).filter(Track.album_id == album.id).first() if not any_track: log.warn("Deleting album: %s" % str(album)) session.delete(album) num_orphaned_albums += 1 else: found_ids.add(album.id) session.flush() # Artists found_ids.clear() for artist in session.query(Artist).all(): if (artist.id == VARIOUS_ARTISTS_ID or artist.id in found_ids): continue any_track = session.query(Track).filter(Track.artist_id == artist.id) \ .first() any_album = session.query(Album).filter(Album.artist_id == artist.id) \ .first() if not any_track and (not any_album or not any_album.tracks): log.warn("Deleting artist: %s" % str(artist)) session.delete(artist) num_orphaned_artists += 1 else: found_ids.add(artist.id) session.flush() return (num_orphaned_tracks, num_orphaned_artists, num_orphaned_albums)
def handleDirectory(self, d, _): pout(Fg.blue("Syncing directory") + ": " + str(d)) audio_files = list(self._file_cache) self._file_cache = [] image_files = self._dir_images self._dir_images = [] if not audio_files: return d_datetime = datetime.fromtimestamp(getctime(d)) album_type = self._albumTypeHint(audio_files) or LP_TYPE album = None session = self._db_session for audio_file in audio_files: try: album = self._syncAudioFile(audio_file, album_type, d_datetime, session) except Exception as ex: # TODO: log and skip???? raise if album: # Directory images. for img_file in image_files: img_type = art.matchArtFile(img_file) if img_type is None: log.warn("Skipping unrecognized image file: %s" % img_file) continue new_img = Image.fromFile(img_file, img_type) if new_img: new_img.description = os.path.basename(img_file) syncImage( new_img, album if img_type in IMAGE_TYPES["album"] else album.artist, session) else: log.warn("Invalid image file: " + img_file) session.commit() if self.args.monitor: self._watchDir(d)
def _getArtist(self, session, name, resolved_artist): artist_rows = session.query(Artist).filter_by( name=name, lib_id=self._lib.id).all() if artist_rows: if len(artist_rows) > 1 and resolved_artist: # Use previously resolved artist for this directory. artist = resolved_artist elif len(artist_rows) > 1: # Resolve artist try: heading = "Multiple artists names '%s'" % \ artist_rows[0].name artist = console.selectArtist(Fg.blue(heading), choices=artist_rows, allow_create=True) except PromptExit: log.warn("Duplicate artist requires user " "intervention to resolve.") artist = None else: if artist not in artist_rows: session.add(artist) session.flush() pout(Fg.blue("Updating artist") + ": " + name) resolved_artist = artist else: # Artist match artist = artist_rows[0] else: # New artist artist = Artist(name=name, lib_id=self._lib.id) session.add(artist) session.flush() pout(Fg.green("Adding artist") + ": " + name) return artist, resolved_artist
{{ async }} def main(args): pout("\m/")
def _run(self, args=None): args = args or self.args args.plugin = self.plugin # FIXME # TODO: add CommandException to get rid of return 1 etc at this level libs = {lib.name: lib for lib in args.config.music_libs} if not libs and not args.paths: perr("\nMissing at least one path/library in which to sync!\n") self.parser.print_usage() return 1 sync_libs = [] if args.paths: file_paths = [] for arg in args.paths: if arg in libs: # Library name sync_libs.append(libs[arg]) else: # Path file_paths.append(arg) if file_paths: sync_libs.append(MusicLibrary(MAIN_LIB_NAME, paths=file_paths)) else: sync_libs = list(libs.values()) args.db_engine, args.db_session = self.db_engine, self.db_session def _syncLib(lib): args._library = lib args.paths = [] for p in lib.paths: args.paths.append(str(p) if isinstance(p, Path) else p) pout("{}yncing library '{}': paths={}".format( "Force s" if args.force else "S", lib.name, lib.paths), log=log) return eyed3_main(args, None) try: for lib in sync_libs: if not lib.sync and not args.force: pout("[{}] - sync=False".format(lib.name), log=log) continue result = _syncLib(lib) if result != 0: return result except IOError as err: perr(str(err)) return 1 if args.monitor: monitor = self.plugin.monitor_proc # Commit now, since we won't be returning self.db_session.commit() monitor.start() while True: if monitor.sync_queue.empty(): time.sleep(SYNC_INTERVAL / 2) continue sync_libs = {} for i in range(monitor.sync_queue.qsize()): lib, path = monitor.sync_queue.get_nowait() if lib not in sync_libs: sync_libs[lib] = set() sync_libs[lib].add(path) for lib, paths in list(sync_libs.items()): result = _syncLib(MusicLibrary(lib, paths=paths)) if result != 0: return result self.db_session.commit() monitor.join()
def handleDone(self): t = time.time() - self.start_time session = self._db_session session.query(Meta).one().last_sync = datetime.utcnow() self._lib.last_sync = datetime.utcnow() num_orphaned_artists = 0 num_orphaned_albums = 0 if not self.args.no_purge: log.debug( "Purging orphans (tracks, artists, albums) from database") (self._num_deleted, num_orphaned_artists, num_orphaned_albums) = deleteOrphans(session) if self._num_loaded or self._num_deleted: pout("") pout("== Library '{}' sync'd [ {:f}s time ({:f} files/s) ] ==". format(self._lib.name, t, self._num_loaded / t)) pout("%d files sync'd" % self._num_loaded) pout("%d tracks added" % self._num_added) pout("%d tracks modified" % self._num_modified) if not self.args.no_purge: pout("%d orphaned tracks deleted" % self._num_deleted) pout("%d orphaned artists deleted" % num_orphaned_artists) pout("%d orphaned albums deleted" % num_orphaned_albums) pout("")
def _syncAudioFile(self, audio_file, album_type, d_datetime, session): path = audio_file.path info = audio_file.info tag = audio_file.tag album = None is_various = (album_type == VARIOUS_TYPE) if not info or not tag: log.warn("File missing %s, skipping: %s" % ("audio" if not info else "tag/metadata", path)) return elif None in (tag.title, tag.artist): log.warn("File missing required artist and/or title " "metadata, skipping: %s" % path) return # Used when a duplicate artist is resolved for the entire directory. resolved_artist = None resolved_album_artist = None try: track = session.query(Track)\ .filter_by(path=path, lib_id=self._lib.id).one() except NoResultFound: track = None else: if datetime.fromtimestamp(getctime(path)) == track.ctime: # Track is in DB and the file is not modified. # stash the album though, we'll look for artwork # updates later album = track.album return # Either adding the track (track == None) # or modifying (track != None) artist, resolved_artist = self._getArtist(session, tag.artist, resolved_artist) if tag.album_type != SINGLE_TYPE: if tag.album_artist and tag.artist != tag.album_artist: album_artist, resolved_album_artist = \ self._getArtist(session, tag.album_artist, resolved_album_artist) else: album_artist = artist if artist is None: # see PromptExit return album_artist_id = album_artist.id if not is_various \ else VARIOUS_ARTISTS_ID album_rows = session.query(Album)\ .filter_by(title=tag.album, lib_id=self._lib.id, artist_id=album_artist_id).all() rel_date = tag.release_date rec_date = tag.recording_date or_date = tag.original_release_date if album_rows: if len(album_rows) > 1: # This artist has more than one album with the same # title. raise NotImplementedError("FIXME") album = album_rows[0] album.type = album_type album.release_date = rel_date album.original_release_date = or_date album.recording_date = rec_date pout(Fg.yellow("Updating album") + ": " + album.title) elif tag.album: album = Album(title=tag.album, lib_id=self._lib.id, artist_id=album_artist_id, type=album_type, release_date=rel_date, original_release_date=or_date, recording_date=rec_date, date_added=d_datetime) session.add(album) pout(Fg.green("Adding album") + ": " + album.title) session.flush() if not track: track = Track(audio_file=audio_file, lib_id=self._lib.id) self._num_added += 1 pout(Fg.green("Adding track") + ": " + path) else: track.update(audio_file) self._num_modified += 1 pout(Fg.yellow("Updating track") + ": " + path) genre = tag.genre genre_tag = None if genre: try: genre_tag = session.query(Tag)\ .filter_by(name=genre.name, lib_id=self._lib.id).one() except NoResultFound: genre_tag = Tag(name=genre.name, lib_id=self._lib.id) session.add(genre_tag) session.flush() track.artist_id = artist.id track.album_id = album.id if album else None if genre_tag: track.tags.append(genre_tag) session.add(track) if album: # Tag images for img in tag.images: for img_type in art.TO_ID3_ART_TYPES: if img.picture_type in \ art.TO_ID3_ART_TYPES[img_type]: break img_type = None if img_type is None: log.warn("Skipping unsupported image type: %s" % img.picture_type) continue new_img = Image.fromTagFrame(img, img_type) if new_img: syncImage( new_img, album if img_type in IMAGE_TYPES["album"] else album.artist, session) else: log.warn("Invalid image in tag") return album
def test_pout(capfd): msg = "There's a war outside!" pout(msg) out, _ = capfd.readouterr() assert out == msg + "\n"