parse music file metadata with Easymp3 and return a song
    model.

def add_song(fpath, g_songs, g_artists, g_albums):
    """
    parse music file metadata with Easymp3 and return a song
    model.
    """
    try:
        if fpath.endswith('mp3') or fpath.endswith('ogg') or fpath.endswith('wma'):
            metadata = EasyMP3(fpath)
        elif fpath.endswith('m4a'):
            metadata = EasyMP4(fpath)
    except MutagenError as e:
        logger.exception('Mutagen parse metadata failed, ignore.')
        return None

    metadata_dict = dict(metadata)
    for key in metadata.keys():
        metadata_dict[key] = metadata_dict[key][0]
    if 'title' not in metadata_dict:
        title = fpath.rsplit('/')[-1].split('.')[0]
        metadata_dict['title'] = title
    metadata_dict.update(dict(
        url=fpath,
        duration=metadata.info.length * 1000  # milesecond
    ))
    schema = EasyMP3MetadataSongSchema(strict=True)
    try:
        data, _ = schema.load(metadata_dict)
    except ValidationError:
        logger.exeception('解析音乐文件({}) 元数据失败'.format(fpath))
        return

    # NOTE: use {title}-{artists_name}-{album_name} as song identifier
    title = data['title']
    album_name = data['album_name']
    artist_name_list = [
        name.strip()
        for name in re.split(r'[,&]', data['artists_name'])]
    artists_name = ','.join(artist_name_list)
    duration = data['duration']
    album_artist_name = data['album_artist_name']

    # 生成 song model
    # 用来生成 id 的字符串应该尽量减少无用信息，这样或许能减少 id 冲突概率
    song_id_str = ''.join([title, artists_name, album_name, str(int(duration))])
    song_id = gen_id(song_id_str)
    if song_id not in g_songs:
        # 剩下 album, lyric 三个字段没有初始化
        song = LSongModel(identifier=song_id,
                          artists=[],
                          title=title,
                          url=fpath,
                          duration=duration,
                          comments=[],
                          # 下面这些字段不向外暴露
                          genre=data['genre'],
                          cover=data['cover'],
                          date=data['date'],
                          desc=data['desc'],
                          disc=data['disc'],
                          track=data['track'])
        g_songs[song_id] = song
    else:
        song = g_songs[song_id]
        logger.debug('Duplicate song: %s %s', song.url, fpath)
        return

    # 生成 album artist model
    album_artist_id = gen_id(album_artist_name)
    if album_artist_id not in g_artists:
        album_artist = create_artist(album_artist_id, album_artist_name)
        g_artists[album_artist_id] = album_artist
    else:
        album_artist = g_artists[album_artist_id]

    # 生成 album model
    album_id_str = album_name + album_artist_name
    album_id = gen_id(album_id_str)
    if album_id not in g_albums:
        album = create_album(album_id, album_name)
        g_albums[album_id] = album
    else:
        album = g_albums[album_id]

    # 处理专辑的歌手信息和歌曲信息，专辑歌手的专辑列表信息
    if album not in album_artist.albums:
        album_artist.albums.append(album)
    if album_artist not in album.artists:
        album.artists.append(album_artist)
    if song not in album.songs:
        album.songs.append(song)

    # 处理歌曲的歌手和专辑信息，以及歌手的歌曲列表
    song.album = album
    for artist_name in artist_name_list:
        artist_id = gen_id(artist_name)
        if artist_id in g_artists:
            artist = g_artists[artist_id]
        else:
            artist = create_artist(identifier=artist_id, name=artist_name)
            g_artists[artist_id] = artist
        if artist not in song.artists:
            song.artists.append(artist)
        if song not in artist.songs:
            artist.songs.append(song)