Skip to content
This repository has been archived by the owner on Jun 30, 2019. It is now read-only.

Commit

Permalink
将Scanner类拆分成两个类分别处理扫描和歌曲映射两个功能
Browse files Browse the repository at this point in the history
  • Loading branch information
cyliuu committed Dec 4, 2018
1 parent c5629c6 commit 1357425
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 104 deletions.
8 changes: 4 additions & 4 deletions fuocore/local/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,27 +38,27 @@ class LSongModel(SongModel, LBaseModel):

@classmethod
def get(cls, identifier):
return cls.meta.provider.identifier_song_map.get(identifier)
return cls.meta.provider.library._songs.get(identifier)

@classmethod
def list(cls, identifier_list):
return map(cls.meta.provider.identifier_song_map.get, identifier_list)
return map(cls.meta.provider.library._songs.get, identifier_list)


class LAlbumModel(AlbumModel, LBaseModel):
_detail_fields = ('songs',)

@classmethod
def get(cls, identifier):
return cls.meta.provider.identifier_album_map.get(identifier)
return cls.meta.provider.library._albums.get(identifier)


class LArtistModel(ArtistModel, LBaseModel):
_detail_fields = ('songs',)

@classmethod
def get(cls, identifier):
return cls.meta.provider.identifier_artist_map.get(identifier)
return cls.meta.provider.library._artists.get(identifier)


class LSearchModel(SearchModel, LBaseModel):
Expand Down
170 changes: 89 additions & 81 deletions fuocore/local/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
这些小部分理论都可以从中拆除。
"""

import copy
import logging
import pickle
import os

from fuzzywuzzy import process
Expand All @@ -21,6 +21,7 @@

logger = logging.getLogger(__name__)
MUSIC_LIBRARY_PATH = os.path.expanduser('~') + '/Music'
CACHE_DIR = os.path.expanduser('~') + '/.FeelUOwn/data'


def scan_directory(directory, exts=None, depth=2):
Expand Down Expand Up @@ -73,32 +74,56 @@ def create_song(fpath):
return song


@log_exectime
def scan(paths, depth=2):
"""scan media files in all paths
"""
song_exts = ['mp3', 'ogg', 'wma', 'm4a']
exts = song_exts
depth = depth if depth <= 3 else 3
media_files = []
for directory in paths:
logger.debug('正在扫描目录(%s)...', directory)
media_files.extend(scan_directory(directory, exts, depth))
songs = []
for fpath in media_files:
song = create_song(fpath)
if song is not None:
songs.append(song)
else:
logger.warning('%s can not be recognized', fpath)
logger.debug('扫描到 %d 首歌曲', len(songs))
return songs


class Scanner:
def __init__(self, paths=None, depth=2):
self.__songs = []
self._songs = []

#: music resource paths to be scanned, list
self.depth = depth
self.paths = paths or [MUSIC_LIBRARY_PATH]

@property
def songs(self):
return self._songs

@log_exectime
def run(self):
"""scan media files in all paths
"""
song_exts = ['mp3', 'ogg', 'wma', 'm4a']
exts = song_exts
depth = self.depth if self.depth <= 3 else 3
media_files = []
for directory in self.paths:
logger.debug('正在扫描目录(%s)...', directory)
media_files.extend(scan_directory(directory, exts, depth))

# db_name = CACHE_DIR + '/local_song_info.db'
# try:
# with open(db_name, 'rb') as file_object:
# historty_media_files, songs = pickle.load(file_object)
# if (set(historty_media_files) == set(media_files)):
# self._songs = songs
# return
# except Exception as e:
# logger.warning(str(e))

self._songs = []
for fpath in media_files:
song = create_song(fpath)
if song is not None:
self._songs.append(song)
else:
logger.warning('%s can not be recognized', fpath)
logger.debug('扫描到 %d 首歌曲', len(self._songs))

# with open(db_name, 'wb') as file_object:
# if media_files:
# pickle.dump((media_files, self._songs), file_object)


class DataBase:
def __init__(self):
#: identifier song map: {id: song, ...}
self._songs = dict()

Expand All @@ -108,10 +133,6 @@ def __init__(self, paths=None, depth=2):
#: identifier artist map: {id: artist, ...}
self._artists = dict()

#: music resource paths to be scanned, list
self.depth = depth
self.paths = paths or [MUSIC_LIBRARY_PATH]

@property
def songs(self):
return self._songs.values()
Expand All @@ -124,52 +145,53 @@ def albums(self):
def artists(self):
return self._artists.values()

def run(self):
self.__songs = scan(self.paths, self.depth)
self.setup_library()

@log_exectime
def setup_library(self):
# FIXME: 函数太长,请重构我!
def run(self, songs):
self._songs.clear()
self._albums.clear()
self._artists.clear()

for song in self.__songs:
self.setup_library(songs)
self.analyze_library()

def setup_library(self, scanner_songs):
for song in scanner_songs:
if song.identifier in self._songs:
continue
self._songs[song.identifier] = song
# 增加ablum.songs的信息

if song.album is not None:
album = song.album
if album.identifier not in self._albums:
import copy
self._albums[album.identifier] = copy.deepcopy(album)
self._albums[album.identifier].songs = [song]
else:
self._albums[album.identifier].songs.append(song)
# 增加artist.songs的信息
album_data = {'identifier': album.identifier,
'name': album.name,
'songs': []}
if album.artists:
album_data['artists'] = [{'identifier': album.artists[0].identifier,
'name': album.artists[0].name}]
self._albums[album.identifier], _ = LocalAlbumSchema(strict=True).load(album_data)
self._albums[album.identifier].songs.append(song)

if song.artists is not None:
for artist in song.artists:
if artist.identifier not in self._artists:
self._artists[artist.identifier] = copy.deepcopy(artist)
self._artists[artist.identifier].albums = []
self._artists[artist.identifier].songs = [song]
else:
self._artists[artist.identifier].songs.append(song)

# 更新专辑歌曲排序,更新艺术家专辑信息,更新歌曲专辑信息
artist_data = {'identifier': artist.identifier,
'name': artist.name,
'songs': [],
'albums': []}
self._artists[artist.identifier], _ = LocalArtistSchema(strict=True).load(artist_data)
self._artists[artist.identifier].songs.append(song)

def analyze_library(self):
for album in self._albums.values():
# 增加artists.albums, 必须在这里进行(如果在song中进行会导致artist.albums重复)
if album.artists:
# 专辑艺术家只能有一个!
album_artist = album.artists[0]
if album_artist.identifier not in self._artists:
self._artists[album_artist.identifier] = copy.deepcopy(album_artist)
self._artists[album_artist.identifier].albums = [album]
self._artists[album_artist.identifier].songs = []
else:
self._artists[album_artist.identifier].albums.append(album)
album_artist_data = {'identifier': album_artist.identifier,
'name': album_artist.name,
'songs': [],
'albums': []}
self._artists[album_artist.identifier], _ = LocalArtistSchema(strict=True).load(album_artist_data)
self._artists[album_artist.identifier].albums.append(album)
for artist in self._artists.values():
# if artist.albums:
# artist.albums.sort(key=lambda x: (x.songs[0].date is None, x.songs[0].date), reverse=True)
Expand All @@ -182,22 +204,19 @@ class LocalProvider(AbstractProvider):
def __init__(self):
super().__init__()

self._identifier_song_map = dict()
self._identifier_album_map = dict()
self._identifier_artist_map = dict()
self.library = DataBase()
self._songs = []
self._albums = []
self._artists = []

def scan(self, paths=None, depth=2):
def scan(self, paths=None, depth=3):
scanner = Scanner(paths or [], depth=depth)
scanner.run()
self._identifier_song_map = scanner._songs
self._identifier_album_map = scanner._albums
self._identifier_artist_map = scanner._artists
self._songs = list(scanner.songs)
self._albums = list(scanner.albums)
self._artists = list(scanner.artists)

self.library.run(scanner.songs)
self._songs = list(self.library.songs)
self._albums = list(self.library.albums)
self._artists = list(self.library.artists)

@property
def identifier(self):
Expand All @@ -207,18 +226,6 @@ def identifier(self):
def name(self):
return '本地音乐'

@property
def identifier_song_map(self):
return self._identifier_song_map

@property
def identifier_artist_map(self):
return self._identifier_artist_map

@property
def identifier_album_map(self):
return self._identifier_album_map

@property
def songs(self):
return self._songs
Expand Down Expand Up @@ -250,6 +257,7 @@ def search(self, keyword, **kwargs):

provider = LocalProvider()


from fuocore.local.schemas import EasyMP3MetadataSongSchema
from .schemas import LocalAlbumSchema
from .schemas import LocalArtistSchema
from .schemas import EasyMP3MetadataSongSchema
from .models import LSearchModel
32 changes: 13 additions & 19 deletions fuocore/local/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,59 +12,58 @@ class BaseSchema(Schema):
desc = fields.Str()


class ArtistSchema(BaseSchema):
class LocalArtistSchema(BaseSchema):
# TODO: 添加一个 alias 字段?
name = fields.Str(required=True)
cover = fields.Str() # NOTE: 可能需要单独一个 Schema
songs = fields.List(fields.Nested('SongSchema'), missing=None)
albums = fields.List(fields.Nested('AlbumSchema'), missing=None)
songs = fields.List(fields.Nested('LocalSongSchema'), missing=None)
albums = fields.List(fields.Nested('LocalAlbumSchema'), missing=None)

@post_load
def create_model(self, data):
return LArtistModel(**data)


class AlbumSchema(BaseSchema):
class LocalAlbumSchema(BaseSchema):
name = fields.Str(required=True)
img = fields.Str()
songs = fields.List(fields.Nested('SongSchema'), missing=None)
artists = fields.List(fields.Nested(ArtistSchema), missing=[])
songs = fields.List(fields.Nested('LocalSongSchema'), missing=None)
artists = fields.List(fields.Nested(LocalArtistSchema), missing=[])

@post_load
def create_model(self, data):
return LAlbumModel(**data)


class SongSchema(BaseSchema):
class LocalSongSchema(BaseSchema):
title = fields.Str(required=True)
url = fields.Str(required=True)
duration = fields.Float(required=True) # mileseconds
album = fields.Nested(AlbumSchema, missing=None)
artists = fields.List(fields.Nested(ArtistSchema), missing=[])
album = fields.Nested(LocalAlbumSchema, missing=None)
artists = fields.List(fields.Nested(LocalArtistSchema), missing=[])

@post_load
def create_model(self, data):
return LSongModel(**data)


class EasyMP3MetadataSongSchema(Schema):
"""EasyMP3 metadata"""
url = fields.Str(required=True)
title_list = fields.List(fields.Str(), load_from='title', required=True)
duration = fields.Float(required=True)
artist_list = fields.List(fields.Str(), load_from='artist')
genre_list = fields.List(fields.Str(), load_from='genre')
# genre_list = fields.List(fields.Str(), load_from='genre')
album_list = fields.List(fields.Str(), load_from='album')
album_artist_list = fields.List(fields.Str(), load_from='albumartist')

@post_load
def create_song_model(self, data):
def create_model(self, data):
# FIXME: 逻辑太多,请重构我,重构之前这里不应该添加新功能
title = data['title_list'][0] if data.get('title_list') else 'Unknown'
artists_name = data['artist_list'][0] if data.get('artist_list') else ''
album_name = data['album_list'][0] if data.get('album_list') else ''
# NOTE: use {title}-{artists_name}-{album_name} as song identifier
song_identifier_str = '{} - {} - {}'.format(title, artists_name, album_name)
song_identifier_str = '{} - {} - {} - {}'.format(title, artists_name, album_name, data['duration'])
song_identifier = str(elfhash(base64.b64encode(bytes(song_identifier_str, 'utf-8'))))
song_data = {
'identifier': song_identifier,
Expand All @@ -73,7 +72,6 @@ def create_song_model(self, data):
'url': data['url']
}

# album.songs需要统计之后统计
if album_name:
album_artist_name = data['album_artist_list'][0] if data.get('album_artist_list') else ''
album_identifier_str = '{} - {}'.format(album_name, album_artist_name)
Expand All @@ -85,19 +83,15 @@ def create_song_model(self, data):
song_data['album']['artists'] = [{'identifier': album_artist_identifier,
'name': album_artist_name}]

# artist.songs和artist.albums需要之后统计
if artists_name:
song_data['artists'] = []
# Lin-Manuel Miranda, Leslie Odom Jr., Anthony Ramos, Daveed Diggs & Okieriete Onaodowan
# 将其分解为'Lin-Manuel Miranda','Leslie Odom Jr.','Anthony Ramos','Daveed Diggs','Okieriete Onaodowan'
artist_names = [artist.strip() for artist in re.split(r'[,&]', artists_name)]
# TO DO: 特殊情况'陳樂基 & Killersoap殺手鐧'/'Macklemore & Ryan Lewis'/'Leslie Odom, Jr.'时不应该拆分
for artist_name in artist_names:
artist_identifier = str(elfhash(base64.b64encode(bytes(artist_name, 'utf-8'))))
song_data['artists'].append({'identifier': artist_identifier,
'name': artist_name})

song, _ = SongSchema(strict=True).load(song_data)
song, _ = LocalSongSchema(strict=True).load(song_data)
return song


Expand Down

0 comments on commit 1357425

Please sign in to comment.