Пример #1
0
def getMusic(where_clause='', where_values=None, tables=[],
             order_by=None, limit=None, metadata=False):
    # print(where_clause)
    c = MusicDatabase.getCursor()

    if 'songs' not in tables:
        tables.insert(0, 'songs')
    statement = ('SELECT id, root, path, mtime, title, artist, album, '
                 'albumArtist, track, date, genre, discNumber, '
                 'coverWidth, coverHeight, coverMD5 FROM %s %s' %
                 (','.join(tables), where_clause))
    if order_by:
        statement += ' ORDER BY %s' % order_by
    if limit:
        statement += ' LIMIT %d' % limit

    # print(statement, where_values)
    if where_values:
        result = c.execute(text(statement).bindparams(**where_values))
    else:
        result = c.execute(text(statement))
    r = [Song(x) for x in result.fetchall()]
    if metadata:
        MusicDatabase.updateSongsTags(r)
    return r
Пример #2
0
def album_info():
    if request.method != 'GET':
        return None
    MBD = MusicBrainzDatabase
    albumID = request.args.get('id', type=int)
    album_info = MBD.get_album_info(albumID)
    releaseID = album_info['release_id']
    rgID = album_info['release_group_id']
    release_group_info = MBD.get_release_group_info(rgID)
    secondary_types = MBD.get_release_group_secondary_types(rgID)
    release_events = MBD.get_release_events(releaseID)
    result = dict(album_info)
    result['covers_count'] = MusicDatabase.getAlbumCoversCount(albumID)
    if result['language']:
        result['language'] = LanguageEnum.name(result['language'])
    if result['release_status']:
        result['release_status'] = ReleaseStatusEnum.name(
            result['release_status'])
    result['release_group'] = dict(release_group_info)
    rg_type = result['release_group']['release_group_type']
    if rg_type:
        rg_type = ReleaseGroupTypeEnum.name(rg_type)
        result['release_group']['release_group_type'] = rg_type
    result['release_group_secondary_types'] = secondary_types
    result['release_events'] = [dict(x) for x in release_events]
    ratings = MusicDatabase.get_albums_ratings([albumID], current_user.userID)
    result['rating'] = ratings[albumID]
    return jsonify(result)
Пример #3
0
def album_cover():
    if request.method != 'GET':
        return None
    album_id = request.args.get('id', type=int)
    medium_number = request.args.get('medium_number', type=int, default=None)
    print(f'Delivering coverart of album {album_id} medium {medium_number}')

    path = MusicDatabase.getAlbumPath(album_id, medium_number)
    if not path:
        print('ERROR getting album image for album'
              f'{album_id}/{medium_number}')
        return ''
    coverfilename = coverAtPath(path)
    if not coverfilename and not medium_number:
        path = MusicDatabase.getAlbumPath(album_id, any_medium=True)
        if not path:
            print('ERROR getting album image for album'
                  f'{album_id}/{medium_number}')
            return ''
        coverfilename = coverAtPath(path)

    if coverfilename:
        return send_file(coverfilename)
    else:
        print(f'Error cover not found at {path}')

    return ''
Пример #4
0
    def append_song(self, song_id, mbid=None, *, connection=None):
        assert song_id, "song_id must be used"
        if not mbid:
            print(f'Obtaining MBID for song id {song_id}')
            mbid = MusicDatabase.getRecordingMBID(song_id)
        item = (song_id, mbid)
        self.songs.append(item)

        if self.id and self.store_songs_in_db:
            pos = len(self.songs) - 1
            c = connection or MusicDatabase.getCursor()
            entry = {
                'playlist_id': self.id,
                'song_id': song_id,
                'recording_mbid': mbid,
                'pos': pos,
            }

            pls = table('playlist_songs')
            MusicDatabase.insert_or_update(pls,
                                           entry,
                                           and_(pls.c.playlist_id == self.id,
                                                pls.c.pos == pos),
                                           connection=c)
            if not connection:
                c.commit()
Пример #5
0
    def create_in_db(self):
        if self.id:
            return
        c = MusicDatabase.getCursor()
        sql = text('INSERT INTO playlists (name, owner_id, playlist_type)'
                   'VALUES (:name, :owner_id, :playlist_type)')
        c.execute(sql.bindparams(name=self.name, owner_id=self.owner_id,
                                 playlist_type=self.playlist_type))

        try:
            sql = "SELECT currval(pg_get_serial_sequence('playlists','id'))"
            result = c.execute(sql)
        except exc.OperationalError:
            # Try the sqlite way
            sql = 'SELECT last_insert_rowid()'
            result = c.execute(sql)
        self.id = result.fetchone()[0]

        pls = table('playlist_songs')
        for idx, item in enumerate(self.songs):
            entry = {
                'playlist_id': self.id,
                'song_id': item[0],
                'recording_mbid': item[1],
                'pos': idx,
            }

            MusicDatabase.insert_or_update(pls, entry,
                                           and_(pls.c.playlist_id == self.id,
                                                pls.c.pos == idx),
                                           connection=c)
        c.commit()
Пример #6
0
    def insert_song(self, position, song_id=None, mbid=None, *,
                    connection=None):
        assert song_id, "song_id must be used"
        if position > len(self.songs):
            position = len(self.songs)

        if not mbid:
            mbid = MusicDatabase.getRecordingMBID(song_id)

        item = (song_id, mbid)

        self.songs.insert(position, item)

        if self.id:
            c = connection or MusicDatabase.getCursor()
            self._update_positions_in_db(len(self.songs) - 1, position, 1, c)

            entry = {
                'playlist_id': self.id,
                'pos': position,
                'song_id': song_id,
                'recording_mbid': mbid,
            }

            pls = table('playlist_songs')
            MusicDatabase.insert_or_update(pls, entry,
                                           and_(pls.c.playlist_id == self.id,
                                                pls.c.pos == position),
                                           connection=c)
            if not connection:
                c.commit()
Пример #7
0
 def insertMusicBrainzTags(song_id, mbIDs):
     MusicBrainzDatabase.insertMBArtistIDs(song_id, mbIDs.artistids)
     MusicBrainzDatabase.insertMBAlbumArtistIDs(song_id,
                                                mbIDs.albumartistids)
     MusicBrainzDatabase.insertMBWorkIDs(song_id, mbIDs.workids)
     songs_mb = table('songs_mb')
     mbTagRecord = songs_mb.select(songs_mb.c.song_id == song_id)
     mbTagRecord = MusicDatabase.execute(mbTagRecord).fetchone()
     if mbTagRecord:
         if (mbTagRecord['releasegroupid'] != mbIDs.releasegroupid or
             mbTagRecord['releaseid'] != mbIDs.releaseid or
             mbTagRecord['releasetrackid'] != mbIDs.releasetrackid or
             mbTagRecord['recordingid'] != mbIDs.recordingid or
                 mbTagRecord['confirmed'] != mbIDs.confirmed):
             print(f'update mb data for {song_id}')
             u = songs_mb.update() \
                         .where(songs_mb.c.song_id == song_id) \
                         .values(song_id=song_id,
                                 releasegroupid=mbIDs.releasegroupid,
                                 releaseid=mbIDs.releaseid,
                                 releasetrackid=mbIDs.releasetrackid,
                                 recordingid=mbIDs.recordingid,
                                 confirmed=mbIDs.confirmed)
             MusicDatabase.execute(u)
     else:
         print(f'insert mb data for {song_id}')
         i = songs_mb.insert().values(song_id=song_id,
                                      releasegroupid=mbIDs.releasegroupid,
                                      releaseid=mbIDs.releaseid,
                                      releasetrackid=mbIDs.releasetrackid,
                                      recordingid=mbIDs.recordingid,
                                      confirmed=mbIDs.confirmed)
         MusicDatabase.execute(i)
Пример #8
0
    def cacheMusicBrainzDB():
        artist = table('musicbrainz.artist')
        aa = table('musicbrainz.artist_alias')
        s = select([artist.c.id, artist.c.mbid, artist.c.name,
                    artist.c.sort_name, artist.c.artist_type, artist.c.area_id,
                    artist.c.gender, artist.c.disambiguation])
        locales = ['es', 'en']
        for a in MusicDatabase.execute(s).fetchall():
            s2 = (select([aa.c.name, aa.c.sort_name, aa.c.locale,
                         aa.c.artist_alias_type, aa.c.primary_for_locale])
                  .where(and_(aa.c.artist_id == a['id'],
                              aa.c.locale.in_(locales))))
            current = {}
            for x in MusicDatabase.execute(s2).fetchall():
                if MusicBrainzDatabase.is_better_alias(x, current):
                    current = {'locale_name': x['name'],
                               'locale_sort_name': x['sort_name'],
                               'locale': x['locale'],
                               'artist_alias_type': x['artist_alias_type']}
            if not current:
                current = {'locale_name': a['name'],
                           'locale_sort_name': a['sort_name'],
                           'locale': None,
                           'artist_alias_type': None}

            current['id'] = a['id']
            MusicDatabase.insert_or_update('artists_mb', current)
Пример #9
0
def album_set_ratings():
    if request.method != 'GET':
        return None
    album_id = request.args.get('id', type=int)
    rating = request.args.get('rating', type=int)
    MusicDatabase.set_album_rating(album_id, rating, current_user.userID)

    return ''
Пример #10
0
 def updateMusicBrainzIDs(songIDs=None):
     if not songIDs:
         return
     for song_id, tags in MusicBrainzDatabase.songTags(songIDs):
         mbIDs = getSongMusicBrainzIDs(song_id, tags)
         if any(mbIDs):
             MusicBrainzDatabase.insertMusicBrainzTags(song_id, mbIDs)
             MusicDatabase.commit()
Пример #11
0
def release_group_set_ratings():
    if request.method != 'GET':
        return None
    rgID = request.args.get('id', type=int)
    rating = request.args.get('rating', type=int)

    album_ids = MusicBrainzDatabase.get_release_group_albums(rgID)
    for album_id in album_ids:
        MusicDatabase.set_album_rating(album_id, rating, current_user.userID)

    return ''
Пример #12
0
def get_country_names():
    _ = MusicDatabase()
    c = MusicDatabase.getCursor()

    sql = text('SELECT name '
               '  FROM musicbrainz.area '
               ' WHERE area_type = 1 '
               '   ORDER BY name')

    r = c.execute(sql)
    return [x[0] for x in r.fetchall()]
Пример #13
0
    def create_in_db(self):
        super(GeneratedPlaylist, self).create_in_db()

        c = MusicDatabase.getCursor()
        t = table('playlist_generators')
        data = {'playlist_id': self.id,
                'generator': self.generator}

        MusicDatabase.insert_or_update(t, data,
                                       t.c.playlist_id == self.id,
                                       connection=c)
        c.commit()
Пример #14
0
def requestNewPassword(username):
    if not username:
        username = config['username']
    userID = MusicDatabase.getUserID(username)

    prompt = f'Enter the new password for user \'{username}\': '
    password = getpass.getpass(prompt)

    hashed = hashpw(password.encode('utf-8'), gensalt())
    if MusicDatabase.setUserPassword(userID, hashed):
        print('password changed successfully')
    else:
        print('Error changing password')
Пример #15
0
 def set_name(self, name):
     self.name = name
     if self.id:
         c = MusicDatabase.getCursor()
         sql = text('UPDATE playlists SET name = :name WHERE id = :id')
         c.execute(sql.bindparams(name=self.name, id=self.id))
         c.commit()
Пример #16
0
 def __init__(self, username=None):
     """Create a User object that can be used with Flask-Login."""
     self.username = username
     self.userID = MusicDatabase.getUserID(username)
     self.is_authenticated = True
     self.is_active = True
     self.is_anonymous = False
Пример #17
0
 def get_release_label(releaseID):
     rl = table('musicbrainz.release_label')
     label = table('musicbrainz.label')
     s = (select([label.c.name.label('label_name'), rl.c.catalog_number])
          .where(and_(rl.c.label_id == label.c.id,
                      rl.c.release_id == releaseID)))
     return MusicDatabase.execute(s).fetchall()
Пример #18
0
    def load_from_db(where_clause='', where_values=None, tables=[],
                     order_by=None, limit=None):
        c = MusicDatabase.getCursor()

        if 'playlists' not in tables:
            tables.insert(0, 'playlists')

        statement = ('SELECT id, name, owner_id, playlist_type FROM %s %s' %
                     (','.join(tables), where_clause))

        if order_by:
            statement += ' ORDER BY %s' % order_by

        if limit:
            statement += ' LIMIT %d' % limit

        if where_values:
            print(statement)
            print(where_values)
            result = c.execute(text(statement).bindparams(**where_values))
        else:
            result = c.execute(text(statement))

        r = [Playlist(x) for x in result.fetchall()]
        return r
Пример #19
0
    def setSongRating(self, user_id, song_id, rating):
        try:
            self.ratings[user_id][song_id] = rating
        except KeyError:
            self.ratings[user_id] = {}
            self.ratings[user_id][song_id] = rating

        c = MusicDatabase.conn.cursor()
        sql = 'UPDATE ratings set rating = ? WHERE user_id = ? AND song_id = ?'
        c.execute(sql, (rating, user_id, song_id))
        if c.rowcount == 0:
            c.execute(
                'INSERT INTO ratings '
                '(user_id, song_id, rating) '
                'VALUES (?,?,?)', (user_id, song_id, rating))
        MusicDatabase.commit()
Пример #20
0
def api_v1_song_search():
    plman = app.bard.playlist_manager
    sq = SearchQuery.from_request(request, current_user.userID, plman)
    if not sq:
        raise ValueError('No SearchQuery!')
    pl = plman.get_search_result_playlist(sq)
    if not pl:
        pl = SearchPlaylist(sq)
        plman.add_search_playlist(pl)

    songs = MusicBrainzDatabase.search_songs_for_webui(sq.query, sq.offset,
                                                       sq.page_size)

    song_ids = [song['song_id'] for song in songs]
    for song_id in song_ids:
        pl.append_song(song_id)

    ratings = MusicDatabase.get_songs_ratings(song_ids, current_user.userID)
    songs = [{
        'rating': ratings[song['song_id']],
        **dict(song)
    } for song in songs]
    result = {
        'search_playlist_id': pl.searchPlaylistID,
        'search_query': sq.as_dict(),
        'songs': songs
    }
    return jsonify(result)
Пример #21
0
    def songsWithSameAudio(self, song, sftp):
        tgt_audio_sha256 = song.audioSha256sum()
        # self.remoteFileAudioSha256Sum(target)
        print(tgt_audio_sha256)

        song_ids = MusicDatabase.songsByAudioTrackSha256sum(tgt_audio_sha256)
        return [getSongsFromIDorPath(song_id)[0] for song_id in song_ids]
Пример #22
0
    def songTags(songIDs=None):
        c = MusicDatabase.getCursor()
        if songIDs:
            sql = text('SELECT song_id, name, value FROM tags '
                       'WHERE song_id IN :id_list '
                       'ORDER BY song_id, pos')
            result = c.execute(sql, {'id_list': tuple(songIDs)})
        else:
            sql = text('SELECT song_id, name, value FROM tags '
                       'ORDER BY song_id, pos')
            result = c.execute(sql)
        tags = {}
        row = result.fetchone()
        current_song_id = None
        while row:
            if row.song_id != current_song_id:
                if current_song_id:
                    yield current_song_id, tags
                    tags = {}
                current_song_id = row.song_id
            if row.name not in tags:
                tags[row.name] = [row.value]
            else:
                tags[row.name] += [row.value]

            row = result.fetchone()
        if current_song_id:
            yield current_song_id, tags
Пример #23
0
    def songAnalysis(song_id):
        tab_col_keys = {}
        for k, (t, c) in conversion_dict.items():
            tab_col_keys.setdefault(t, {})[c] = k

        c = MusicDatabase.getCursor()
        result = {}
        for t in tab_col_keys.keys():
            sql = text(f'SELECT * FROM analysis.{t} '
                       'WHERE song_id = :song_id ')
            values = c.execute(sql.bindparams(song_id=song_id)).fetchone()
            if not values:
                continue
            for col, key in tab_col_keys[t].items():
                if t == 'highlevel':
                    key = f'highlevel.{col}'
                parent = result
                parts = key.split('.')
                for part in parts[:-1]:
                    parent = parent.setdefault(part, {})

                # print(col)
                # print(values)
                # print(parts)
                parent[parts[-1]] = values[col]

                # result[key] = values[col]

        return result
Пример #24
0
    def compareAudioFiles(self, source, target, sftp):
        print('Comparing songs...')

        # src_song = getSongsAtPath(source, exact=True)[0]
        src_song = Song(source)
        tgt_song = remoteSong(target, sftp)
        basename = os.path.basename(source)
        colors = (Color.First, Color.Second)
        printSongsInfo(src_song, tgt_song, useColors=colors)
        if src_song.audioSha256sum() == tgt_song.audioSha256sum():
            print(f'{basename} has exactly the same audio in source and '
                  'target files')
        else:
            print(f'Audio tracks are different in local and remote files')
            comparison = src_song.audioCmp(tgt_song)
            print(comparison)
            tgt_audio_sha256 = tgt_song.audioSha256sum()
            song_ids = MusicDatabase.songsByAudioTrackSha256sum(
                tgt_audio_sha256)
            same_audio_songs = [
                getSongsFromIDorPath(songID)[0] for songID in song_ids
            ]

            if tgt_audio_sha256 and same_audio_songs:
                d = 'Files with exact same audio:\n'
                d += '\n'.join('      ' + Color.CantCompareSongs +
                               song.path() + Color.ENDC
                               for song in same_audio_songs)
                print(d)
            else:
                print(f'WARNING: The target file {target} will be overwritten '
                      'but has a unique audio track')
        return False
Пример #25
0
    def remove_analysis_for_song_id(self, song_id, *, connection=None):
        c = connection or MusicDatabase.getCursor()
        tables = list(set(x[0] for x in conversion_dict.values()))
        tables += [
            key.replace('.', '__').lower() + '_stats'
            for key in keys_with_stats
        ]
        tables += [
            key.replace('.', '__').lower() + '_stats'
            for key in keys_with_lists_stats
        ]
        tables += [key.replace('.', '__').lower() for key in keys_with_lists]
        tables += [key.replace('.', '__').lower() for key in fkeys_with_lists]
        tables += [
            key[0].replace('.', '__').lower()
            for key in fkeys_with_lists_of_lists
        ]

        for tablename in tables:
            sql = text(f'DELETE FROM analysis.{tablename} '
                       'WHERE song_id = :song_id')
            c.execute(sql.bindparams(song_id=song_id))

        if not connection:
            c.commit()
Пример #26
0
    def moveFrom(self, prevSong):
        """Move prevSong to the location and values of self."""
        print(f'Moving song from {prevSong.id}, {prevSong.path()}\n'
              f"              to {getattr(self, 'id', None)}, {self.path()}")

        if not self._path:
            return False
        r = MusicDatabase.moveSong(prevSong.id, self._path, self._root)
        if r:
            self.id = prevSong.id
            prevSong._path = self._path
            prevSong._root = self._root

            albumID = MusicDatabase.getAlbumID(albumPath(self._path))
            MusicDatabase.setSongInAlbum(self.id, albumID)
        return r
Пример #27
0
 def has_analysis_for_song_id(self, song_id, *, connection=None):
     if not connection:
         connection = MusicDatabase.getCursor()
     sql = text('select song_id from analysis.highlevel '
                f' where song_id = {song_id}')
     result = connection.execute(sql)
     return bool(result.fetchone())
Пример #28
0
    def setSongRating(self, user_id, song_id, rating):
        try:
            self.ratings[user_id][song_id] = rating
        except KeyError:
            self.ratings[user_id] = {}
            self.ratings[user_id][song_id] = rating

        c = MusicDatabase.conn.cursor()
        sql = 'UPDATE ratings set rating = ? WHERE user_id = ? AND song_id = ?'
        c.execute(sql, (rating, user_id, song_id))
        if c.rowcount == 0:
            c.execute('INSERT INTO ratings '
                      '(user_id, song_id, rating) '
                      'VALUES (?,?,?)',
                      (user_id, song_id, rating))
        MusicDatabase.commit()
Пример #29
0
def findPairs(songs1, songs2):
    pairs = {}
    songsRemainingFrom1 = songs1.copy()
    songsRemainingFrom2 = songs2.copy()
    print('Getting pairs from', songs1)
    print(' and              ', songs2)
    for song1 in songs1:
        similarSongsIn2 = []
        for song2 in songs2:
            similarity = MusicDatabase.songsSimilarity(song1.id, song2.id)
            # print(song1.id, song2.id, similarity)
            if similarity >= 0.85:
                similarSongsIn2.append((song2, similarity))
                with suppress(ValueError):
                    songsRemainingFrom1.remove(song1)
                with suppress(ValueError):
                    songsRemainingFrom2.remove(song2)
        if similarSongsIn2:
            pairs[song1] = similarSongsIn2
            if len([x for x in similarSongsIn2 if x[1] == 1.0]) > 1:
                colors = (TerminalColors.Warning, TerminalColors.ENDC)
                print('%sWARNING: Repeated songs in the same directory!%s' %
                      colors)
                for x in similarSongsIn2:
                    print(x)

    return pairs, songsRemainingFrom1, songsRemainingFrom2
Пример #30
0
 def lastSongIDWithAnalysis():
     c = MusicDatabase.getCursor()
     sel = select([func.max(Highlevel.c.song_id)])
     result = c.execute(sel)
     x = result.fetchone()
     if x:
         return x[0]
     return 0
Пример #31
0
 def move_song(self, from_position, to_position, *, connection=None):
     c = connection or MusicDatabase.getCursor()
     item = self.remove_song(from_position, connection=c)
     if to_position > from_position:
         to_position -= 1
     self.insert_song(to_position, item=item, connection=c)
     if not connection:
         c.commit()
Пример #32
0
    def loadMetadataInfo(self):
        if getattr(self, 'metadata', None) is None:
            self.loadMetadata()
        elif getattr(self.metadata, 'info', None) is not None:
            return

        (self._format, self.metadata.info, self._audioSha256sum, silences) = \
            MusicDatabase.getSongProperties(self.id)
        self._silenceAtStart = silences[0]
        self._silenceAtEnd = silences[1]
Пример #33
0
    def loadMetadata(self):
        if getattr(self, 'metadata', None) is not None:
            return

        if getattr(self, 'id', None) is not None:
            self.metadata = type('info', (dict,), {})()
            self.metadata.update(MusicDatabase.getSongTags(self.id))
            return

        self.loadFile(self._path)
        if self.metadata is None:
            raise Exception("Couldn't load metadata!")
Пример #34
0
    def loadCoverImageData(self, path):
        self._coverWidth, self._coverHeight = 0, 0
        self._coverMD5 = ''
        random_number = random.randint(0, 100000)
        coverfilename = os.path.join(config['tmpdir'],
                                     '/cover-%d.jpg' % random_number)

        MusicDatabase.addCover(path, coverfilename)
        # c = self.conn.cursor()

        # values = [ ( path, coverfilename), ]
        # c.executemany('''INSERT INTO covers(path, cover) VALUES (?,?)''',
        #               values)
        command = ['ffmpeg', '-i', path, '-map', '0:m:comment:Cover (front)',
                   '-c', 'copy', coverfilename]

        process = subprocess.run(command, stdout=subprocess.DEVNULL,
                                 stderr=subprocess.DEVNULL)
        if process.returncode != 0:
            # try with any image in the file
            process = subprocess.run(['ffmpeg', '-i', path, '-c', 'copy',
                                      coverfilename],
                                     stdout=subprocess.DEVNULL,
                                     stderr=subprocess.DEVNULL)
            if process.returncode != 0:
                return

        try:
            image = Image.open(coverfilename)
            self._coverWidth, self._coverHeight = image.size
            self._coverMD5 = md5(coverfilename)
        except IOError:
            print('Error reading cover file from %s' % path)
            return

        os.unlink(coverfilename)
Пример #35
0
    def audioCmp(self, other, forceSimilar=False, interactive=True,
                 useColors=None, printSongsInfoCallback=None,
                 forceInteractive=False):
        """Compare the audio of this object with the audio of other.

        Returns -1 if self has better audio than other,
        1 if other has better audio than self and 0 if they have
        audio of the same characteristics. Also, it can raise
        a SongsNotComparableException exception if audio has
        different length or it's not similar according to
        chromaprint fingerprints
        """
        self.loadMetadataInfo()
        other.loadMetadataInfo()
        if self._audioSha256sum == other._audioSha256sum:
            return 0

        if (not forceSimilar and getattr(self, 'id', None) and
                getattr(other, 'id', None) and
                not MusicDatabase.areSongsSimilar(self.id, other.id)):
            raise DifferentSongsException(
                'Trying to compare different songs (%d and %d)'
                % (self.id, other.id))

        len_diff = abs(self.durationWithoutSilences() -
                       other.durationWithoutSilences())
        if len_diff > 30:
            raise DifferentLengthException(
                'Songs duration is too different (%f and %f seconds / %f and %f seconds)'
                % (self.durationWithoutSilences(), other.durationWithoutSilences(),
                   self.metadata.info.length, other.metadata.info.length))

        if len_diff > 5:
            print(self.duration(), self.durationWithoutSilences(), self.silenceAtStart(), self.silenceAtEnd())
            raise SlightlyDifferentLengthException(
                'Songs duration is slightly different (%f and %f seconds / %f and %f seconds)'
                % (self.durationWithoutSilences(), other.durationWithoutSilences(),
                   self.metadata.info.length, other.metadata.info.length))

        if not forceInteractive:
            if self.isLossless() and not other.isLossless():
                return -1
            if other.isLossless() and not self.isLossless():
                return 1

        si = self.metadata.info
        oi = other.metadata.info

        # Be sure the self.metadata.info structure contains all information
        sbps = self.bits_per_sample()
        self.bitrate()
        obps = other.bits_per_sample()
        other.bitrate()

        if not forceInteractive:
            if si.bitrate > oi.bitrate * 1.12 \
               and ((sbps and obps and sbps >= obps) or
                    (not sbps and not obps)) \
               and si.channels >= oi.channels \
               and si.sample_rate >= oi.sample_rate:
                return -1

            if oi.bitrate > si.bitrate * 1.12 \
               and ((sbps and obps and obps >= sbps) or
                    (not sbps and not obps)) \
               and oi.channels >= si.channels \
               and oi.sample_rate >= si.sample_rate:
                return 1

#        if self.completeness > other.completeness:
#            return -1
#
#        if other.completeness > self.completeness:
#            return 1

            if oi.bitrate//1000 == si.bitrate//1000 \
               and ((sbps and obps and obps == sbps) or
                    (not sbps and not obps)) \
               and oi.channels == si.channels:
                if oi.sample_rate > si.sample_rate:
                    return 1
                elif si.sample_rate > oi.sample_rate:
                    return -1
                else:
                    return 0

        if interactive or forceInteractive:
            if printSongsInfoCallback:
                printSongsInfoCallback(self, other)
            filename1 = '/tmp/1'
            filename2 = '/tmp/2'
            shutil.copyfile(self.path(), filename1)
            shutil.copyfile(other.path(), filename2)
            result = manualAudioCmp(filename1, filename2, useColors=useColors)
            os.unlink(filename1)
            os.unlink(filename2)
            if result or result == 0:
                return result

        raise CantCompareSongsException('Not sure how to compare songs')