# -*- coding: cp949 -*- import os, time import codeshift import id3tag_manip from api_album_v3 import api_album_v3 from api_search_v3 import api_search_v3 from parse_album_v3 import parse_album_v3 from parse_search_v3 import parse_search_v3 class data_elem: ''' 파일 목록 정보를 정의하는 클래스 ''' @property def modified(self): return self.__modified @modified.setter def modified(self, flag): self.__modified = flag @property def file_name(self): return self.__file_name @file_name.setter def file_name(self, name): return self.__file_name __file_name = u'' __modified = False class maniadb_core: ''' maniadb 태그 검색 에이전트의 코어 기능을 정의한 클래스. 모든 작업은 코어 클래스를 통해서만 가능하다. ''' def list_size(self): ''' 현재 파일 목록의 개수를 리턴한다. ''' return len(self.__data_list) def load(self, args): ''' 파일 목록을 불러온다. Args args: 파일 목록이 담긴 리스트. 리스트의 원소는 두 가지 타입이 있다. 하나는 단순 파일 이름, 다름 하나는 파일의 목록을 담은 텍스트 파일이다. 전자의 경우 단순히 MP3 파일 이름 문자열이다. 두 번째 또한 파일 이름의 문자열이지만, 문자열은 MP3 파일 경로가 아닌 텍스트 파일이다. 이 텍스트 파일 안에 MP3 파일의 경로가 한 줄에 하나씩 나열되어 있다. 만일 인자로 전달할 파일 이름이 후자의 형태인 경우, 파일 이름 앞에 반드시 '@' 기호를 붙여야 한다. Returns 읽어들인 목록의 개수 Raises 일반 예외: 목록으로 전달된 MP3 파일의 경로가 올바르지 않는 경우 발생된다. 하나라도 올바르지 않은 경로가 존재하면 명령은 전부 무시된다. ''' file_list = [] # filename checking for arg in args: if arg[0] == u'@': with open(arg[1:], u'r') as f: for line in f.xreadlines(): path = os.path.normpath(os.path.abspath(line.strip())) if os.path.exists(path): file_list.append(path) else: raise Exception(path+u' doesn\'t exitst') else: path = os.path.normpath(os.path.abspath(arg)) if os.path.exists(path): file_list.append(path) else: raise Exception(path+u' doesn\'t exitst') for f in file_list: buf = data_elem() buf.file_name = codeshift.loc2uni(f) buf.modified = False self.__data_list.append(buf) return len(file_list) def unload(self, nums): ''' 입력한 번호에 해당하는 엔트리를 파일 목록에서 지운다. Args nums: 지우려는 목록의 번호. 번호는 항상 1부터 시작한다. Returns 지워진 목록의 개수 Raises 일반 예외. 번호가 목록 범위를 벗어날 경우 발생한다. ''' for num in nums: if num < 1 or num > self.list_size(): raise Exception(u'Number exceeded the limit') for off, num in enumerate(nums): idx = num-off-1 del self.__data_list[idx] return len(nums) def clear_list(self): ''' 파일 목록을 완전히 지운다 ''' self.__data_list[:] = [] def song_query(self, keyword_song, keyword_artist, nresp): ''' 노래 검색 쿼리를 보내어 결과를 수신한다. 연결에 실패한 경우 3회까지 재시도한다. Args keyword_song: 노래 검색어 keyword_artist: 가수(아티스트) 검색어 nresp: 전달받을 응답의 최대 개수 Returns Maniadb 응답. 파싱된 콘텐츠 형태로 리턴된다. 파싱된 콘텐츠란 서버로부터 받은 XML의 구조를 파이썬 자료형인 딕셔너리와 리스트로 표현한 것을 말한다. Raises 일반 예외. 3회 연결 실패의 경우 발생한다. ''' # throwing query api = api_search_v3() # if some problem happens, xmldoc should be None retry = 3 xmldoc = None while True: if keyword_artist != u'': xmldoc = api.search(u'song', u'song', keyword_song, u'artist', keyword_artist, nresp) else: xmldoc = api.search(u'song', u'song', keyword_song, u'', u'', nresp) if xmldoc == None: if retry > 0: print u'Song info search failed. Retrying...', retry retry -= 1 time.sleep(3) continue else: raise Exception('Failed to connect to maniadb.com') else: break # write xml file to debug. with open(u'search_result.xml', u'w') as f: f.write(xmldoc) return parse_search_v3(u'song', xmldoc).result def album_query(self, album_id): ''' 앨범 상세 정보를 수신받는다. 연결에 실패한 경우 3회까지 재시도한다. Args album_id: Maniadb 내부 고유 앨범 ID Returns Maniadb 응답. 파싱된 콘텐츠 형태로 리턴된다. 파싱된 콘텐츠란 서버로부터 받은 XML의 구조를 파이썬 자료형인 딕셔너리와 리스트로 표현한 것을 말한다. Raises 일반 예외. 3회 연결 실패의 경우 발생한다. ''' api = api_album_v3() retry = 3 xmldoc = None while True: xmldoc = api.search(album_id) if xmldoc == None: if retry > 0: print u'Album id search failed. Retrying...', retry retry -= 1 time.sleep(3) continue else: raise Exception('Failed to connect to maniadb.com') else: break # debug with open(u'album_result.xml', u'w') as f: f.write(xmldoc) return parse_album_v3(xmldoc).result def get_all_files(self): ''' 모든 파일 이름 목록을 리턴한다. ''' all_files = [] for item in self.__data_list: all_files.append(item.file_name) return all_files def get_tagged_files(self): ''' 전체 파일 목록 중 현재 프로그램이 태그를 변경한 것들만 골라 그 이름의 목록을 리턴한다. ''' tagged = [] for item in self.__data_list: if item.modified == True: tagged.append(item.file_name) return tagged def get_untagged_files(self): ''' 전체 파일 목록 중 현재 프로그램이 변경하지 않은 것들만을 골라 그 이름의 목록을 리턴한다. ''' untagged = [] for item in self.__data_list: if item.modified == False: untagged.append(item.file_name) return untagged def get_file_name(self, num): ''' 파일 목록 번호에 해당하는 엔트리의 파일 이름을 리턴한다. ''' if 1 <= num and num <= self.list_size(): return self.__data_list[num-1].file_name else: raise Exception(u'number exceeded the limit') def is_tagged(self, num): ''' 파일 목록 번호에 해당하는 엔트리의 태그 내용이 현재 프로그램에 의해 수정되었는지를 리턴한다. ''' if 1 <= num and num <= self.list_size(): return self.__data_list[num-1].modified else: raise Exception(u'number exceeded the limit') def set_tagged(self, num, modified): ''' 파일 목록 번호에 해당하는 엔트리의 태그 내용 수정 여부를 조절한다. ''' if 1 <= num and num <= self.list_size(): self.__data_list[num-1].modified = modified else: raise Exception(u'number exceeded the limit') def update(self, listnum, song_info, song_selnum, album_info): ''' MP3 파일 정보를 업데이트한다. Args listnum: 업데이트할 파일 목록 번호 song_info: 노래 정보 검색 결과 (파싱된 콘텐츠) song_selnum: 노래 정보 중 취할 결과 번호 album_info: 앨범 정보 검색 결과 (파싱된 콘텐츠) Returns None Raises None ''' target_file_name = self.get_file_name(listnum) id3tag_manip.update_mp3_tag(target_file_name, song_info, song_selnum-1, album_info) self.set_tagged(listnum, True) ''' 파일 목록. data_elem 의 리스트이다. ''' __data_list = []