def write_tracks_to_db(tracks): """ """ if tracks is None or len(tracks) < 1: return if PeeweeVersion[0] == '2': data = list({ "name": t.name, "number": t.track_number, "disk": t.disk, "position": t.position, "book": t.book, "file": t.file, "length": t.length, "modified": t.modified } for t in tracks) Track.insert_many(data).execute() else: fields = [ Track.name, Track.number, Track.disk, Track.position, Track.book, Track.file, Track.length, Track.modified ] data = list((t.name, t.track_number, t.disk, t.position, t.book, t.file, t.length, t.modified) for t in tracks) Track.insert_many(data, fields=fields).execute()
def on_close(self, widget, data=None): """ Close and dispose everything that needs to be when window is closed. """ log.info("Closing.") self.titlebar.close() self.fs_monitor.close() if self.sleep_timer.is_running(): self.sleep_timer.stop() # save current position when still playing if player.get_gst_player_state() == Gst.State.PLAYING: Track.update(position=player.get_current_duration()).where( Track.id == player.get_current_track().id).execute() player.stop() player.dispose() close_db() report.close() log.info("Closing app.") self.app.quit() log.info("App closed.")
def play_pause(track, jump=False): """ Play a new file or pause/play if the file is already loaded. :param track: Track object that will be played/paused. """ global __current_track global __player global __wait_to_seek global __set_speed __wait_to_seek = jump if __current_track == track or track is None: # Track is already selected, only play/pause if get_gst_player_state() == Gst.State.PLAYING: __player.set_state(Gst.State.PAUSED) emit_event("pause") save_current_track_position() else: __player.set_state(Gst.State.PLAYING) emit_event("play", Track.get(Track.id == __current_track.id)) else: load_file(track) __player.set_state(Gst.State.PLAYING) emit_event("play", Track.get(Track.id == __current_track.id)) __set_speed = True
def save_current_track_position(pos=None, track=None): """ Saves the current track position to the cozy.db. """ if pos is None: pos = get_current_duration() if track is None: track = get_current_track() Track.update(position=pos).where(Track.id == track.id).execute()
def blacklist_book(book): """ Removes a book from the library and adds the path(s) to the track list. """ book_tracks = get_tracks(book) data = list((t.file, ) for t in book_tracks) chunks = [data[x:x + 500] for x in range(0, len(data), 500)] for chunk in chunks: StorageBlackList.insert_many(chunk, fields=[StorageBlackList.path]).execute() ids = list(t.id for t in book_tracks) Track.delete().where(Track.id << ids).execute() book.delete_instance()
def load_last_book(): """ Load the last played book into the player. """ global __current_track global __player last_book = Settings.get().last_played_book if last_book and last_book.position != 0: query = Track.select().where(Track.id == last_book.position) if query.exists(): last_track = query.get() if last_track: __player.set_state(Gst.State.NULL) if cozy.control.filesystem_monitor.FilesystemMonitor( ).is_track_online(last_track): path = last_track.file else: path = OfflineCache().get_cached_path(last_track) if not path: return __player.set_property("uri", "file://" + path) __player.set_state(Gst.State.PAUSED) __current_track = last_track Book.update(last_played=int(time.time())).where( Book.id == last_book.id).execute() emit_event("track-changed", last_track)
def _fetch_chapters(self): with self._db: tracks = TrackModel \ .select(TrackModel.id) \ .where(TrackModel.book == self._db_object) \ .order_by(TrackModel.disk, TrackModel.number, TrackModel.name) self._chapters = [Track(self._db, track.id) for track in tracks]
def is_external(book): """ Tests whether the given book is saved on external storage. """ return any(storage.path in Track.select().join(Book).where( Book.id == book.id).first().file for storage in Storage.select().where(Storage.external == True))
def __on_button_press(self, eventbox, event): """ Play the selected track. """ current_track = player.get_current_track() if current_track and current_track.id == self.track.id: player.play_pause(None) if player.get_gst_player_state() == Gst.State.PLAYING: player.jump_to_ns(Track.select().where( Track.id == self.track.id).get().position) else: player.load_file( Track.select().where(Track.id == self.track.id).get()) player.play_pause(None, True) Book.update(position=self.track).where( Book.id == self.track.book.id).execute()
def locate(self, button): """ Locate the file and update the database if the user selected one. """ directory, filename = os.path.split(self.missing_file) dialog = Gtk.FileChooserDialog( "Please locate the file " + filename, self.parent.window, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) filter = Gtk.FileFilter() filter.add_pattern(filename) filter.set_name(filename) dialog.add_filter(filter) path, file_extension = os.path.splitext(self.missing_file) filter = Gtk.FileFilter() filter.add_pattern("*" + file_extension) filter.set_name(file_extension + " files") dialog.add_filter(filter) filter = Gtk.FileFilter() filter.add_pattern("*") filter.set_name(_("All files")) dialog.add_filter(filter) dialog.set_local_only(False) response = dialog.run() if response == Gtk.ResponseType.OK: new_location = dialog.get_filename() Track.update(file=new_location).where( Track.file == self.missing_file).execute() directory, filename = os.path.split(new_location) importer.import_file(filename, directory, new_location, update=True) self.parent.refresh_content() self.dialog.destroy() self.parent.dialog_open = False player.load_file( Track.select().where(Track.file == new_location).get()) player.play_pause(None, True) dialog.destroy()
def _fetch_chapters(self): tracks = TrackModel \ .select(TrackModel.id) \ .where(TrackModel.book == self._db_object) \ .order_by(TrackModel.disk, TrackModel.number, TrackModel.name) self._chapters = [Track(self._db, track.id) for track in tracks] for chapter in self._chapters: chapter.add_listener(self._on_chapter_event)
def get_tracks(book): """ Find all tracks that belong to a given book :param book: the book object :return: all tracks belonging to the book object """ return Track.select().join(Book).where(Book.id == book.id).order_by( Track.disk, Track.number, Track.name)
def search_tracks(search_string): """ Search all tracks in the db with the given substring. This ignores upper/lowercase. :param search_string: substring to search for :return: tracks matching the substring """ return Track.select(Track.name).where( Track.name.contains(search_string)).order_by(Track.name)
def clean_db(): """ Delete everything from the database except settings. """ q = Track.delete() q.execute() q = Book.delete() q.execute() q = ArtworkCache.delete() q.execute()
def rebase_location(ui, oldPath, newPath): """ This gets called when a user changes the location of the audio book folder. Every file in the database updated with the new path. Note: This does not check for the existence of those files. """ trackCount = Track.select().count() currentTrackCount = 0 for track in Track.select(): newFilePath = track.file.replace(oldPath, newPath) Track.update(file=newFilePath).where(Track.id == track.id).execute() StorageBlackList.update(path=newFilePath).where( StorageBlackList.path == track.file).execute() Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, ui.titlebar.update_progress_bar.set_fraction, currentTrackCount / trackCount) currentTrackCount = currentTrackCount + 1 Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, ui.switch_to_playing)
def clean_books(): """ Remove all books that have no tracks """ for book in Book.select(): if not get_track_for_playback(book): Book.update(position=0).where(Book.id == book.id).execute() if Track.select().where(Track.book == book).count() < 1: if Settings.get().last_played_book.id == book.id: Settings.update(last_played_book=None).execute() book.delete_instance()
def remove_tracks_with_path(ui, path): """ Remove all tracks that contain the given path. """ if path == "": return for track in Track.select(): if path in track.file: track.delete_instance() clean_books() Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, ui.refresh_content)
def get_current_track(): """ Get the currently loaded track object. :return: currently loaded track object """ global __current_track if __current_track: query = Track.select().where(Track.id == __current_track.id) if query.exists(): return query.get() else: __current_track = None return None else: return None
def remove_invalid_entries(ui=None, refresh=False): """ Remove track entries from db that no longer exist in the filesystem. """ # remove entries from the db that are no longer existent for track in Track.select(): from cozy.control.filesystem_monitor import FilesystemMonitor if not os.path.isfile( track.file) and FilesystemMonitor().is_track_online(track): track.delete_instance() clean_books() if refresh: Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, ui.refresh_content)
def _fetch_chapters(self): tracks = TrackModel \ .select(TrackModel.id) \ .where(TrackModel.book == self._db_object) \ .order_by(TrackModel.disk, TrackModel.number, TrackModel.name) self._chapters = [] for track in tracks: try: track_model = Track(self._db, track.id) self._chapters.append(track_model) except TrackInconsistentData: log.warning("Skipping inconsistent model") except Exception as e: log.error("Could not create chapter object: {}".format(e)) for chapter in self._chapters: chapter.add_listener(self._on_chapter_event)
def get_track_for_playback(book): """ Finds the current track to playback for a given book. :param book: book which the next track is required from :return: current track position from book db """ book = Book.select().where(Book.id == book.id).get() query = Track.select().where(Track.id == book.position) if book.position < 1: track_items = get_tracks(book) if len(track_items) > 0: track = get_tracks(book)[0] else: track = None elif query.exists(): track = query.get() else: track = None return track
def Search(search): return Track.select().where(search in Track.name)
def update_database(ui, force=False): """ Scans the audio book directory for changes and new files. Also removes entries from the db that are no longer existent. """ paths = [] for location in Storage.select(): if os.path.exists(location.path): paths.append(location.path) # clean artwork cache artwork_cache.delete_artwork_cache() # are UI buttons currently blocked? player_blocked, importer_blocked = ui.get_ui_buttons_blocked() i = 0 percent_counter = 0 file_count = 0 for path in paths: file_count += sum([len(files) for r, d, files in os.walk(path)]) percent_threshold = file_count / 1000 failed = "" tracks_to_import = [] # Tracks which changed and need to be updated if they are cached tracks_cache_update = [] start = time.time() for path in paths: for directory, subdirectories, files in os.walk(path): for file in files: if file.lower().endswith( ('.mp3', '.ogg', '.flac', '.m4a', '.wav', '.opus')): path = os.path.join(directory, file) imported = True try: if force: imported, ignore = import_file( file, directory, path, True) tracks_cache_update.append(path) # Is the track already in the database? elif Track.select().where( Track.file == path).count() < 1: imported, track_data = import_file( file, directory, path) if track_data: tracks_to_import.append(track_data) # Has the modified date changed? elif (Track.select().where( Track.file == path).first().modified < os.path.getmtime(path)): imported, ignore = import_file(file, directory, path, update=True) tracks_cache_update.append(path) if not imported: failed += path + "\n" except UnicodeEncodeError as e: log.warning( "Could not import file because of invalid path or filename: " + path) reporter.exception("importer", e) failed += path + "\n" except Exception as e: log.warning("Could not import file: " + path) log.warning(traceback.format_exc()) reporter.exception("importer", e) failed += path + "\n" i = i + 1 if len(tracks_to_import) > 100: write_tracks_to_db(tracks_to_import) tracks_to_import = [] # don't flood gui updates if percent_counter < percent_threshold: percent_counter = percent_counter + 1 else: percent_counter = 1 Gdk.threads_add_idle( GLib.PRIORITY_DEFAULT_IDLE, ui.titlebar.progress_bar.set_fraction, i / file_count) Gdk.threads_add_idle( GLib.PRIORITY_DEFAULT_IDLE, ui.titlebar.update_progress_bar.set_fraction, i / file_count) write_tracks_to_db(tracks_to_import) end = time.time() log.info("Total import time: " + str(end - start)) # remove entries from the db that are no longer existent remove_invalid_entries() artwork_cache.generate_artwork_cache() Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, ui.refresh_content) Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, ui.switch_to_playing) Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, ui.check_for_tracks) if len(failed) > 0: Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, ui.display_failed_imports, failed) OfflineCache().update_cache(tracks_cache_update) OfflineCache()._process_queue()
def import_file(file, directory, path, update=False): """ Imports all information about a track into the database. Note: This creates also a new album object when it doesnt exist yet. Note: This does not check whether the file is already imported. :return: True if file was imported, otherwise False :return: Track object to be imported when everything passed successfully and track is not in the db already. """ if is_blacklisted(path): return True, None media_type = tools.__get_media_type(path) track = TrackContainer(None, path) cover = None reader = None track_number = None track_data = None # getting the some data is file specific ### MP3 ### if "audio/mpeg" in media_type: track_data = _get_mp3_tags(track, path) ### FLAC ### elif "audio/flac" in media_type or "audio/x-flac" in media_type: track_data = _get_flac_tags(track, path) ### OGG ### elif "audio/ogg" in media_type or "audio/x-ogg" in media_type: track_data = _get_ogg_tags(track, path) ### OPUS ### elif "audio/opus" in media_type or "audio/x-opus" in media_type or "codecs=opus" in media_type: track_data = _get_opus_tags(track, path) ### MP4 ### elif "audio/mp4" in media_type or "audio/x-m4a" in media_type: track_data = _get_mp4_tags(track, path) ### WAV ### elif "audio/wav" in media_type or "audio/x-wav" in media_type: track_data = TrackData(path) track_data.length = __get_wav_track_length(path) ### File will not be imported ### else: # don't use _ for ignored return value -> it is reserved for gettext ignore, file_extension = os.path.splitext(path) log.warning("Skipping file " + path + " because of mime type " + media_type + ".") reporter.error( "importer", "Mime type not detected as audio: " + media_type + " with file ending: " + file_extension) return False, None track_data.modified = __get_last_modified(path) # try to get all the remaining tags try: if track_data.track_number is None: # The track number can contain the total number of tracks track_text = str(__get_common_tag(track, "tracknumber")) track_data.track_number = int(track_text.split("/")[0]) except Exception as e: log.debug(e) track_data.track_number = 0 if track_data.book_name is None: track_data.book_name = __guess_book_name(directory) if track_data.author is None or track_data.author == "": if track_data.reader and len(track_data.reader) > 0: track_data.author = track_data.reader track_data.reader = "" else: track_data.author = _("Unknown Author") if track_data.reader is None or track_data.reader == "": track_data.reader = _("Unknown Reader") if track_data.name is None: track_data.name = __guess_title(file) if not track_data.disk: track_data.disk = 1 if not track_data.length: # Try to get the length by using gstreamer success, track_data.length = get_gstreamer_length(path) if not success: return False, None if update: if Book.select().where(Book.name == track_data.book_name).count() < 1: track_data.book = Book.create(name=track_data.book_name, author=track_data.author, reader=track_data.reader, position=0, rating=-1, cover=track_data.cover) else: track_data.book = Book.select().where( Book.name == track_data.book_name).get() Book.update(name=track_data.book_name, author=track_data.author, reader=track_data.reader, cover=track_data.cover).where( Book.id == track_data.book.id).execute() Track.update(name=track_data.name, number=track_data.track_number, book=track_data.book, disk=track_data.disk, length=track_data.length, modified=track_data.modified).where( Track.file == track_data.file).execute() else: # create database entries if Book.select().where(Book.name == track_data.book_name).count() < 1: track_data.book = Book.create(name=track_data.book_name, author=track_data.author, reader=track_data.reader, position=0, rating=-1, cover=track_data.cover) else: track_data.book = Book.select().where( Book.name == track_data.book_name).get() return True, track_data return True, None