def test_insert_track_inserts_all_rows_expected(): from cozy.model.database_importer import DatabaseImporter, TrackInsertRequest from cozy.db.book import Book from cozy.db.file import File from cozy.db.track_to_file import TrackToFile database_importer = DatabaseImporter() file = File.create(path="New File", modified=1234567) track_data = { "name": "Test", "number": 2, "disk": 2, "book": Book.select().where(Book.name == "Test Book").get(), "length": 123, "position": 0 } track = TrackInsertRequest(track_data, file, 1234) database_importer._insert_tracks([track]) track_to_file_query = TrackToFile.select().join(File).where( TrackToFile.file == file.id) assert track_to_file_query.count() == 1 track_to_file: TrackToFile = track_to_file_query.get() assert track_to_file.track.name == track_data["name"] assert track_to_file.track.number == track_data["number"] assert track_to_file.track.disk == track_data["disk"] assert track_to_file.track.book.id == Book.select().where( Book.name == "Test Book").get().id assert track_to_file.track.length == track_data["length"] assert track_to_file.track.position == track_data["position"]
def books(): """ Find all books in the database :return: all books """ return Book.select()
def test_update_track_db_object_updates_object(): from cozy.model.library import Library from cozy.media.media_file import MediaFile from cozy.db.book import Book from cozy.media.chapter import Chapter from cozy.db.track import Track library = Library() chapter = Chapter("New Chapter", 0) media_file = MediaFile(book_name="New Book Name", author="New Author", reader="New Reader", disk=999, track_number=999, length=1234567, cover=b"cover", path="test.mp3", modified=1234567, chapters=[chapter]) book = Book.select().get() library._update_track_db_object(media_file, book) track_in_db: Track = Track.select().where(Track.file == "test.mp3").get() assert track_in_db.name == "New Chapter" assert track_in_db.disk == 999 assert track_in_db.number == 999 assert track_in_db.length == 1234567 assert track_in_db.modified == 1234567
def update_time(self): if self.book is None: return # update book object # TODO: optimize usage by only asking from the db on track change query = Book.select().where(Book.id == self.book.id) if (query.exists()): self.book = query.get() else: self.book = None return if self.ui.titlebar.current_book and self.book.id == self.ui.titlebar.current_book.id: progress = get_book_progress(self.book, False) progress += (player.get_current_duration() / 1000000000) remaining = (self.duration - progress) else: progress = get_book_progress(self.book) remaining = get_book_remaining(self.book) percentage = progress / self.duration self.total_label.set_text( tools.seconds_to_human_readable(self.duration / self.speed)) if percentage > 0.005: self.remaining_text.set_visible(True) self.remaining_label.set_visible(True) self.remaining_label.set_text( tools.seconds_to_human_readable(remaining / self.speed)) else: self.remaining_text.set_visible(False) self.remaining_label.set_visible(False) self.progress_bar.set_fraction(percentage)
def test_create_track_db_object_creates_object(): from cozy.model.library import Library from cozy.media.media_file import MediaFile from cozy.db.book import Book from cozy.media.chapter import Chapter library = Library() chapter = Chapter("New Chapter", 0) media_file = MediaFile(book_name="New Book Name", author="New Author", reader="New Reader", disk=999, track_number=999, length=1234567, cover=b"cover", path="New File", modified=1234567, chapters=[chapter]) book = Book.select().get() res_dict = library._get_track_dictionary_for_db(media_file, book) assert res_dict["name"] == "New Chapter" assert res_dict["disk"] == 999 assert res_dict["number"] == 999 assert res_dict["book"] == book assert res_dict["file"] == "New File" assert res_dict["length"] == 1234567 assert res_dict["modified"] == 1234567 assert res_dict["position"] == 0
def _load_all_books(self): with self._db: for book_db_obj in BookModel.select(BookModel.id): try: self._books.append(Book(self._db, book_db_obj.id)) except BookIsEmpty: pass
def test_create_book_db_object_creates_object(): from cozy.model.database_importer import DatabaseImporter from cozy.media.media_file import MediaFile from cozy.db.book import Book from cozy.media.chapter import Chapter database_importer = DatabaseImporter() chapter = Chapter("New Chapter", 0, 1234567, 999) media_file = MediaFile(book_name="New Book", author="New Author", reader="New Reader", disk=999, cover=b"cover", path="test.mp3", modified=1234567, chapters=[chapter]) database_importer._create_book_db_object(media_file) book_in_db: Book = Book.select().where(Book.name == "New Book").get() assert book_in_db.name == "New Book" assert book_in_db.author == "New Author" assert book_in_db.reader == "New Reader" assert book_in_db.cover == b"cover" assert book_in_db.position == 0 assert book_in_db.rating == -1
def test_create_track_db_object_creates_object(): from cozy.model.database_importer import DatabaseImporter from cozy.media.media_file import MediaFile from cozy.db.book import Book from cozy.media.chapter import Chapter database_importer = DatabaseImporter() chapter = Chapter("New Chapter", 0, 1234567, 999) media_file = MediaFile(book_name="New Book Name", author="New Author", reader="New Reader", disk=999, cover=b"cover", path="New File", modified=1234567, chapters=[chapter]) book = Book.select().get() res_dict = database_importer._get_track_list_for_db(media_file, book)[0] assert res_dict["name"] == "New Chapter" assert res_dict["disk"] == 999 assert res_dict["number"] == 999 assert res_dict["book"] == book assert res_dict["length"] == 1234567 assert res_dict["position"] == 0
def test_removing_book_with_missing_file_removes_all_traces_in_db( peewee_database): from cozy.model.book import Book from cozy.db.book import Book as BookDB from cozy.db.track import Track from cozy.db.file import File from cozy.db.track_to_file import TrackToFile book = Book(peewee_database, 1) track_ids = [chapter.id for chapter in book.chapters] track_to_file_ids = [ track_to_file.id for track_to_file in TrackToFile.select().join( Track).where(TrackToFile.track.id << track_ids) ] file_ids = [ track_to_file.file.id for track_to_file in TrackToFile.select().join( Track).where(TrackToFile.track.id << track_ids) ] assert len(track_ids) > 0 assert len(track_to_file_ids) > 0 assert len(file_ids) > 0 File.get_by_id(file_ids[0]).delete_instance() book.remove() assert BookDB.select().where(BookDB.id == 1).count() == 0 assert Track.select().where(Track.id << track_ids).count() == 0 assert TrackToFile.select().where( TrackToFile.id << track_to_file_ids).count() == 0 assert File.select().where(File.id << file_ids).count() == 0
def _import_or_update_book(self, media_file): if BookModel.select(BookModel.name).where( BookModel.name == media_file.book_name).count() < 1: book = self._create_book_db_object(media_file) else: book = self._update_book_db_object(media_file) return book
def test_create_book_db_object_creates_object(): from cozy.model.library import Library from cozy.media.media_file import MediaFile from cozy.db.book import Book from cozy.media.chapter import Chapter library = Library() chapter = Chapter("New Chapter", 0) media_file = MediaFile(book_name="New Book", author="New Author", reader="New Reader", disk=999, track_number=999, length=1234567, cover=b"cover", path="test.mp3", modified=1234567, chapters=[chapter]) library._create_book_db_object(media_file) book_in_db: Book = Book.select().where(Book.name == "New Book").get() assert book_in_db.name == "New Book" assert book_in_db.author == "New Author" assert book_in_db.reader == "New Reader" assert book_in_db.cover == b"cover" assert book_in_db.position == 0 assert book_in_db.rating == -1
def _load_all_books(self): for book_db_obj in BookModel.select(BookModel.id): try: book = Book(self._db, book_db_obj.id) book.add_listener(self._on_book_event) self._books.append(book) except BookIsEmpty: pass
def search_readers(search_string): """ Search all readers in the db with the given substring. This ignores upper/lowercase and returns each reader only once. :param search_string: substring to search for :return: readers matching the substring """ return Book.select(Book.reader).where(Book.reader.contains(search_string)).distinct().order_by(Book.reader)
def _update_book_db_object(self, media_file: MediaFile) -> BookModel: BookModel.update(name=media_file.book_name, author=media_file.author, reader=media_file.reader, cover=media_file.cover) \ .where(BookModel.name == media_file.book_name) \ .execute() return BookModel.select().where( BookModel.name == media_file.book_name).get()
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 and Settings.get().last_played_book.id == book.id: Settings.update(last_played_book=None).execute() book.delete_instance()
def jump_to_book(self, book): """ Open book overview with the given book. """ # first update track ui book = Book.select().where(Book.id == book.id).get() self.book_overview.set_book(book) # then switch the stacks self.main_stack.props.visible_child_name = "book_overview" self.toolbar_revealer.set_reveal_child(False)
def test_delete_deletes_book_from_db(peewee_database, mocker): from cozy.model.book import Book from cozy.db.book import Book as BookModel book = Book(peewee_database, 1) spy = mocker.spy(book, "emit_event") book._on_chapter_event("chapter-deleted", book.chapters[0]) assert BookModel.select().where(BookModel.id == 1).count() < 1 spy.assert_called_once_with("book-deleted", book) assert len(book._listeners) < 1
def search_books(search_string): """ Search all book names in the db with the given substring. This ignores upper/lowercase and returns each book name only once. :param search_string: substring to search for :return: book names matching the substring """ return Book.select(Book.name, Book.cover, Book.id).where(Book.name.contains(search_string) | Book.author.contains(search_string) | Book.reader.contains(search_string)).distinct().order_by( Book.name)
def test_authors_contains_every_author_from_db(peewee_database): from cozy.model.library import Library from cozy.db.book import Book library = Library(peewee_database) books = Book.select(Book.author).distinct().order_by(Book.author) authors_from_db = [book.author for book in books] # we cannot assert the same content as the library filters books without chapters assert len(library.authors) > 0 assert library.authors.issubset(authors_from_db)
def __ui_changed(self, event, message): """ Handler for events that occur in the main ui. """ if self.book is None or self.ui.titlebar.current_book is None or self.ui.titlebar.current_book.id != self.book.id: return if event == "playback-speed-changed": self.speed = Book.select().where( Book.id == self.book.id).get().playback_speed if self.ui.main_stack.props.visible_child_name == "book_overview": self.update_time()
def test_readers_contains_every_reader_from_db(): from cozy.model.library import Library from cozy.db.book import Book library = Library() books = Book.select(Book.reader).distinct().order_by(Book.reader) readers_from_db = [book.reader for book in books] readers_from_db = list(split_strings_to_set(set(readers_from_db))) # we cannot assert the same content as the library filters books without chapters assert len(library.readers) > 0 assert library.readers.issubset(readers_from_db)
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 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, media_type_human = 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) elif "ID3" in media_type_human: track_data = _get_mp3_tags(track, 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
def generate_artwork_cache(): """ Generates the default artwork cache for cover preview. """ for book in Book.select(): get_cover_pixbuf(book, 180)
def authors(): return Book.select(Book.author).distinct().order_by(Book.author)