Example #1
0
class Login(metaclass=Observable):
    on_start = signal()
    on_success = signal(str)
    on_failure = signal(Exception)
    on_connection_failed = signal()
    on_authentication_failed = signal()

    def __init__(self, session):
        self._session = session

    def authentication_started(self):
        self.on_start.emit()

    def authentication_succeeded(self, user_details):
        self._session.login_as(user_details["email"], user_details["token"],
                               user_details["permissions"])
        self.on_success.emit(user_details["email"])

    def authentication_failed(self, error):
        if isinstance(error, PlatformConnectionError):
            self.on_connection_failed.emit()

        if isinstance(error, AuthenticationError):
            self.on_authentication_failed.emit()

        self.on_failure.emit(error)
Example #2
0
class MediaPlayer(metaclass=Observable):
    class Error(Enum):
        none = QMediaPlayer.NoError
        unsupported_format = QMediaPlayer.FormatError
        access_denied = QMediaPlayer.AccessDeniedError

    closed = signal()
    playing = signal(Track)
    stopped = signal(Track)
    error_occurred = signal(Track)

    def __init__(self):
        self._media_library = create_media_library()
        self._playlist = []
        self._player = QMediaPlayer()
        self._player.stateChanged.connect(self._state_changed)
        self._player.mediaStatusChanged.connect(self._media_status_changed)

    def play(self, track):
        self._playlist.append(track)
        self._player.setMedia(self._media_library.fetch(track.filename))
        self._player.play()

    def stop(self):
        self._player.stop()

    def _media_status_changed(self, state):
        if state == QMediaPlayer.BufferedMedia:
            self._error = QMediaPlayer.NoError
            self.playing.emit(self._playlist[0])
        elif state == QMediaPlayer.InvalidMedia:
            # On windows 8, we only get an InvalidMedia status in case of error,
            # so we need to keep track of the error that occurred
            self._error = QMediaPlayer.FormatError

    @property
    def error(self):
        return MediaPlayer.Error(self._player.error() or self._error)

    def _state_changed(self, state):
        if state == QMediaPlayer.StoppedState and self.error is not MediaPlayer.Error.none:
            self.error_occurred.emit(self._playlist.pop(0), self.error)
        elif state == QMediaPlayer.StoppedState:
            self.stopped.emit(self._playlist.pop(0))

    def dispose(self):
        self._player.stop()
        # force the deletion of the player on windows because windows
        # doesn't release the handle and prevents further instanciations
        sip.delete(self._player)
        self._player = None

        self._media_library.dispose()
        self.closed.emit()
Example #3
0
class IdentitySelection(metaclass=Observable):
    on_identities_available = signal(Identities)
    on_failure = signal(Exception)
    on_connection_failed = signal()
    on_permission_denied = signal()
    on_success = signal()
    on_lookup_start = signal()
    on_assignation_start = signal()
    on_insufficient_information = signal()

    def __init__(self, project, person):
        self._person = self._query = person
        self._project = project

    @property
    def person(self):
        return self._person

    @property
    def query(self):
        return self._query

    @property
    def works(self):
        return [track.track_title for track in self._project.tracks]

    def query_changed(self, query):
        self._query = query

    def lookup_started(self):
        self.on_lookup_start.emit()

    def assignation_started(self):
        self.on_assignation_start.emit()

    def identities_found(self, identities):
        identity_cards = [
            IdentityCard(**identity) for identity in identities["identities"]
        ]
        self.on_identities_available.emit(
            Identities(identities["total_count"], identity_cards))

    def failed(self, error):
        if isinstance(error, PlatformConnectionError):
            self.on_connection_failed.emit()

        if isinstance(error, PermissionDeniedError):
            self.on_permission_denied.emit()

        if isinstance(error, InsufficientInformationError):
            self.on_insufficient_information.emit()

        self.on_failure.emit(error)

    def identity_selected(self, identity):
        self._project.add_isni(self._person, identity.id)
        self.on_success.emit()

    def identity_assigned(self, identity):
        self.identity_selected(IdentityCard(**identity))
Example #4
0
class ProjectHistory(metaclass=Observable):
    on_history_changed = signal()

    def __init__(self, *past_projects):
        self._history = deque(past_projects, maxlen=10)

    def project_opened(self, project):
        self._add_to_history(project)

    def project_saved(self, project):
        self._add_to_history(project)

    def _add_to_history(self, project):
        stale_entry = self._snapshot_for(project)
        if stale_entry is not None:
            self._history.remove(stale_entry)
        self._history.appendleft(ProjectSnapshot.of(project))
        self.on_history_changed.emit()

    def _snapshot_for(self, project):
        return next(
            filter(lambda entry: entry.path == project.filename,
                   self._history), None)

    def __getitem__(self, index):
        return self._history[index]

    def __len__(self):
        return len(self._history)
Example #5
0
    def __new__(mcs, clsname, bases, methods):
        methods["metadata_changed"] = signal(Signal.SELF)

        # Attach attribute names to the tags
        for key, value in methods.items():
            if isinstance(value, Tag):
                value.name = key
        return super().__new__(mcs, clsname, bases, methods)
Example #6
0
    def __new__(mcs, clsname, bases, methods):
        methods["metadata_changed"] = signal(Signal.SELF)

        # Attach attribute names to the tags
        for key, value in methods.items():
            if isinstance(value, Tag):
                value.name = key
        return super().__new__(mcs, clsname, bases, methods)
Example #7
0
class Track(metaclass=tag.Taggable):
    chain_of_title_changed = signal(ChainOfTitle)

    album = None

    track_title = tag.text()
    lead_performer = tag.text()
    version_info = tag.text()
    featured_guest = tag.text()
    comments = tag.text()
    publisher = tag.sequence()
    lyricist = tag.sequence()
    composer = tag.sequence()
    isrc = tag.text()
    iswc = tag.text()
    labels = tag.text()
    lyrics = tag.text()
    language = tag.text()
    tagger = tag.text()
    tagger_version = tag.text()
    tagging_time = tag.text()
    track_number = tag.numeric()
    total_tracks = tag.numeric()

    recording_time = tag.text()
    recording_studio = tag.text()
    recording_studio_region = tag.pairs()
    recording_studio_address = tag.text()
    production_company = tag.text()
    production_company_region = tag.pairs()
    music_producer = tag.text()
    mixer = tag.text()
    primary_style = tag.text()

    # todo Introduce Recording
    bitrate = tag.numeric()
    duration = tag.decimal()

    def __init__(self, filename, metadata=None, chain_of_title=None):
        self.filename = filename
        self.metadata = metadata or Metadata()
        self.chain_of_title = chain_of_title or ChainOfTitle.from_track(self)

    @property
    def type(self):
        return self.album.type if self.album is not None else None

    def __repr__(self):
        return "Track(filename={}, metadata={})".format(
            self.filename, self.metadata)

    def update(self, **metadata):
        for key, value in metadata.items():
            setattr(self, key, value)

        self.chain_of_title.update(lyricists=self.lyricist or [],
                                   composers=self.composer or [],
                                   publishers=self.publisher or [])
Example #8
0
class FakeAudioPlayer(metaclass=Observable):
    playing = signal(Track)
    stopped = signal(Track)
    error_occurred = signal(Track, int)

    track = None

    def play(self, track):
        self.track = track
        self.playing.emit(self.track)

    def stop(self):
        if self.track is not None:
            self.stopped.emit(self.track)

        self.track = None

    def error(self, error):
        self.error_occurred.emit(self.track, error)
Example #9
0
class AlbumPortfolio(metaclass=Observable):
    album_created = signal(Album)
    album_removed = signal(Album)

    def __init__(self):
        self._albums = []

    def add_album(self, album):
        self._albums.append(album)
        self.album_created.emit(album)

    def remove_album(self, album):
        self._albums.remove(album)
        self.album_removed.emit(album)

    def __getitem__(self, index):
        return self._albums[index]

    def __len__(self):
        return len(self._albums)
Example #10
0
class UserPreferences(metaclass=Observable):
    on_preferences_changed = signal(dict)

    artwork_location = locations.Pictures
    locale = "en"

    def __setattr__(self, name, value):
        super().__setattr__(name, value)
        self.on_preferences_changed.emit({name: value})

    def __repr__(self):
        return "UserPreferences(locale={})".format(self.locale)
Example #11
0
class Session(metaclass=Observable):
    user_signed_in = signal(User)
    user_signed_out = signal(User)

    _user = None

    def login_as(self, email, token, permissions):
        self._user = User.registered_as(email, token, permissions)
        self.user_signed_in.emit(self._user)

    def logout(self):
        logged_out, self._user = self._user, None
        self.user_signed_out.emit(logged_out)

    @property
    def opened(self):
        return self._user is not None

    @property
    def current_user(self):
        return self._user if self.opened else User.anonymous()
Example #12
0
class ProjectStudio(metaclass=Observable):
    on_project_opened = signal(Album)
    on_project_saved = signal(Album)
    on_project_closed = signal(Album)

    _current_project = None

    @property
    def current_project(self):
        return self._current_project

    def project_loaded(self, project):
        self._current_project = project
        self.on_project_opened.emit(project)

    project_created = project_loaded

    def project_saved(self, project):
        self.on_project_saved.emit(project)

    def project_closed(self, project):
        self._current_project = None
        self.on_project_closed.emit(project)
    class Name(QTableWidgetItem):
        on_name_changed = signal(str)

        def __init__(self, contributor):
            super().__init__()
            self.setData(Qt.UserRole, contributor)
            self.setText(contributor.name)

        def value_changed(self):
            name = self.text()
            contributor = self.data(Qt.UserRole)
            if contributor.name != name:
                contributor.name = name
                self.on_name_changed.emit(name)
    class Share(QTableWidgetItem):
        on_share_changed = signal(Contributor)

        def __init__(self, contributor):
            super().__init__()
            self.setData(Qt.UserRole, contributor)
            self.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
            self.setText(contributor.share)

        def value_changed(self):
            share = self.text()
            contributor = self.data(Qt.UserRole)
            if contributor.share != share:
                contributor.share = share
                self.on_share_changed.emit(contributor)
Example #15
0
class ContributorsTable(Table):
    on_contributor_changed = signal(dict)

    def __init__(self, table):
        super().__init__(table)
        self._contributors = []

    def _emit_contributor_changed(self, contributor):
        values = dict(name=contributor.name,
                      share=contributor.share,
                      affiliation=contributor.affiliation)
        if isinstance(contributor, AuthorComposer):
            values["publisher"] = contributor.publisher

        self.on_contributor_changed.emit(values)
    class IPI(QTableWidgetItem):
        on_ipi_changed = signal(str, str)

        def __init__(self, contributor, lookup_ipi):
            super().__init__()
            self._lookup_ipi = lookup_ipi
            self.setData(Qt.UserRole, contributor)
            self.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
            self.setText(contributor.ipi)

        def name_changed(self, name):
            contributor = self.data(Qt.UserRole)
            contributor.ipi = self._lookup_ipi(name)
            self.setText(contributor.ipi)

        def value_changed(self):
            ipi = self.text()
            contributor = self.data(Qt.UserRole)
            if contributor.ipi != ipi:
                contributor.ipi = ipi
                self.on_ipi_changed.emit(contributor.name, ipi)
Example #17
0
class ChainOfTitle:
    changed = signal(Signal.SELF)

    def __init__(self, **chain_of_title):
        self._publishers = chain_of_title.get("publishers", {})
        self._authors_composers = chain_of_title.get("authors_composers", {})

    @classmethod
    def from_track(cls, track):
        chain = cls()
        chain.update(lyricists=track.lyricist or [],
                     composers=track.composer or [],
                     publishers=track.publisher or [])
        return chain

    @property
    def contributors(self):
        return {
            "authors_composers": self._authors_composers,
            "publishers": self._publishers
        }

    def update(self, lyricists, composers, publishers):
        has_updated_authors_composers = self._update_authors_composers(
            composers, lyricists)
        has_updated_publishers = self._update_publishers(publishers)

        if has_updated_authors_composers or has_updated_publishers:
            self.changed.emit(self)

    def update_contributor(self, **contributor):
        self._try_update_contributor(contributor, self._authors_composers)
        self._try_update_contributor(contributor, self._publishers)

    def _update_publishers(self, publishers):
        has_updated = self._update_contributors(publishers, self._publishers)
        if has_updated:
            self._update_associated_publishers(publishers)

        return has_updated

    def _update_associated_publishers(self, publishers):
        def publisher_has_been_removed(author_composer):
            return "publisher" in author_composer.keys(
            ) and author_composer["publisher"] not in publishers

        for contributor in self._authors_composers.values():
            if publisher_has_been_removed(contributor):
                contributor["publisher"] = ""

    def _update_authors_composers(self, composers, lyricists):
        return self._update_contributors(lyricists + composers,
                                         self._authors_composers)

    @staticmethod
    def _update_contributors(new_contributors, contributors):
        to_remove = contributors.keys() - set(new_contributors)
        to_add = new_contributors - contributors.keys()
        for name in to_remove:
            del contributors[name]
        for name in to_add:
            contributors[name] = {"name": name}
        return len(to_remove) > 0 or len(to_add) > 0

    @staticmethod
    def _try_update_contributor(contributor, contributors):
        contributor_name = contributor["name"]
        if contributor_name in contributors:
            contributors[contributor_name] = contributor
Example #18
0
class Album(metaclass=tag.Taggable):
    track_inserted = signal(int, Track)
    track_removed = signal(int, Track)
    track_moved = signal(Track, int, int)

    # todo this should probably be in Track
    class Type:
        MP3 = "mp3"
        FLAC = "flac"

    release_name = tag.text()
    compilation = tag.flag()
    lead_performer = tag.text()
    lead_performer_region = tag.pairs()
    lead_performer_date_of_birth = tag.text()
    guest_performers = tag.pairs()
    label_name = tag.text()
    upc = tag.text()
    catalog_number = tag.text()
    release_time = tag.text()
    original_release_time = tag.text()
    contributors = tag.pairs()
    isnis = tag.map()
    ipis = tag.map()

    def __init__(self, metadata=None, of_type=Type.FLAC, filename=None):
        self.metadata = metadata.copy(
            *Album.tags()) if metadata is not None else Metadata()
        self.tracks = []
        self.type = of_type
        self.filename = filename

    @property
    def images(self):
        return self.metadata.images

    def images_of_type(self, type_):
        return self.metadata.imagesOfType(type_)

    @property
    def main_cover(self):
        if not self.images:
            return None

        if self.front_covers:
            return self.front_covers[0]

        return self.images[0]

    @property
    def front_covers(self):
        return self.images_of_type(Image.FRONT_COVER)

    def add_image(self, mime, data, type_=Image.OTHER, desc=""):
        self.metadata.addImage(mime, data, type_, desc)
        self.metadata_changed.emit(self)

    def add_front_cover(self, mime, data, desc="Front Cover"):
        self.add_image(mime, data, Image.FRONT_COVER, desc)

    def remove_images(self):
        self.metadata.removeImages()
        self.metadata_changed.emit(self)

    def add_isni(self, name, id_):
        if self.isnis is None:
            self.isnis = {name: id_}
        else:
            self.isnis[name] = id_
            self.metadata_changed.emit(self)

    def __len__(self):
        return len(self.tracks)

    def empty(self):
        return len(self) == 0

    def add_track(self, track):
        self._insert_track(len(self.tracks), track)

    def insert_track(self, track, position):
        self._insert_track(position, track)

    def _insert_track(self, position, track):
        track.album = self
        # todo move to Track
        if not self.compilation:
            track.lead_performer = self.lead_performer

        self.tracks.insert(position, track)
        self._renumber_tracks()
        self.track_inserted.emit(position, track)

    def remove_track(self, position):
        track = self.tracks.pop(position)
        self._renumber_tracks()
        self.track_removed.emit(position, track)
        return track

    def move_track(self, from_position, to_position):
        track = self.tracks.pop(from_position)
        self.tracks.insert(to_position, track)
        self._renumber_tracks()
        self.track_moved.emit(track, from_position, to_position)

    def _renumber_tracks(self):
        for index, track in enumerate(self.tracks):
            track.track_number = index + 1
            track.total_tracks = len(self.tracks)