#!/usr/bin/python
# -*- coding: utf-8 -*-

class pyTag:
    def testTestCase(self):
        return "maanu"

    def __init__(self,filepath):
        self.musicfile = open(filepath,"r+")
        type = self.detectType()
        if type == "ID3" :
            self.tag = id3Tag(self.musicfile)
        else :
            self.tag = None
            self.musicfile.close()

    def Tag(self):
        return self.tag

    def detectType(self):
        tagType = self.musicfile.read(3)
        return tagType

    def closeFile(self):
        self.musicfile.close()

class id3Tag:
    def __init__(self,fd):
        self.mp3file = fd
        self.tagSize = self.getTagSize()
        # 헤더의 크기가 10byte 이므로 0x0a를 더하면 전체 테그의 영역이 나온다.
        self.tagEnd = self.tagSize + 0x0a
        self.getAllTag()

    def getTagSize(self):
        header = self.mp3file.read(7)
        tagSize = (ord(header[3])<<24)+(ord(header[4])<<16)+(ord(header[5])<<8) + ord(header[6])
        return tagSize

    def parseHeader(self,header):
        if header == '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' :
            return None
        # 앞에서 4byte 가 ID
        id = header[:4]
        # 다음 4byte 가 프레임 사이즈
        size = (ord(header[4])<<24)+(ord(header[5])<<16)+(ord(header[6])<<8)+ord(header[7])

        # 인코더에 따라서 MCDI 의 사이즈를 아주 좆같이 설정하는 경우가 있다.
        # ID3v2.4 표준에 MCDI 크기의 최대 값은 804byte 이다.
        if id == "MCDI" :
            if size > 804 :
                size = 804

        return {"ID":id,"size":size}

    def getAllTag(self):
        result = {}
        while(1):
            # 프레임 헤더는 10byte
            frameHeader = self.mp3file.read(10)
            headerInfo = self.parseHeader(frameHeader)
            if headerInfo == None :
                break
            # 헤더의 사이즈 만큼 읽는다. 이만큼이 text 부분
            headerText = self.mp3file.read(headerInfo["size"])
            result[headerInfo["ID"]] = [headerInfo["size"],headerText]

        # Txxx 가 아니면 모두 없앤다.
        for i in result.keys():
            if i[0] != 'T' :
                result.pop(i)

        # 실제 테그가 끝나는 부분
        self.realTagEnd = self.mp3file.tell()
        self.allTags = result
        return result          
       
    def makeSizeArray(self,size):
        l = [0,0,0,0]
        l[0] = (size & 0xff000000) >> 24
        l[1] = (size & 0xff0000) >> 16
        l[2] = (size & 0xff00) >> 8
        l[3] = (size & 0xff) 

        result = ''
        for i in l:
            result += chr(i)
        return result

    def makeFrameArray(self):
        result = ''
        frameSize = ''
        for f in self.allTags.keys():
            frameSize = self.makeSizeArray(self.allTags[f][0])
            result += (f + frameSize + '\x00\x00' + self.allTags[f][1])
        return result

    def makeTagString(self,str):
        result = [0,'']
        # UTF-8 의 EDB 는 0x03 이다.
        setStr = '\x03' + str + '\x00'
        result[0] = len(setStr)
        result[1] = setStr
        return result

        

    def submitTag(self):
        tag = ''
        # 테그 헤더 : 식별자,버전,플래그
        tagHeader = "ID3\x04\x00\x00"   
        # 테그 헤더 : 사이즈
        tagSize = self.makeSizeArray(self.tagSize)        
        # 테그를 만든다.
        tag = tagHeader + tagSize
        tag += self.makeFrameArray()

        # 파일에 쓴다.        
        self.mp3file.seek(0)
        # 저장 영역을 청소한다.
        for i in range(self.realTagEnd):
            self.mp3file.write('\x00')
        # 다시 맨앞으로 간다음 쓴다.    
        self.mp3file.seek(0)
        self.mp3file.write(tag)

    def setTag(self,id,str):
        if self.allTags.has_key(id) == False :
            return None
        self.allTags[id] = self.makeTagString(str)
        return self.allTags


    def getTag(self,id):
        if self.allTags.has_key(id) == False :
            return None
        result = self.allTags[id][1][1:]
        return result

    def getAllTagId(self):
        return tuple(self.allTags.keys())

    def detectEDB(self,id):
        if self.allTags.has_key(id) == False :
            return None        
        return self.allTags[id][1][0]

    def addTag(self,id,text):
        if self.allTags.has_key(id) == True :
            return None
        self.allTags[id] = self.makeTagString(text)

    def deleteTag(self,id):
        if self.allTags.has_key(id) == False :
            return None        
        self.allTags.pop(id)

######################## 여기 까지 라이브러리 ##########################
    
import getopt
import sys

class Tagconv:
    def __init__(self,argv):
        self.argv = argv

    def startConv(self,filename):
        self.file = filename
        
        fileRef = pyTag(self.file)
        self.tag = fileRef.Tag()

        if self.tag == None :
            print "에러: 음악파일이 아닙니다."
            sys.exit()
        
        print "["+self.file + "] 에서 변환을 시작합니다. "

        for i in self.argv:
            op = i[0]
            arg = i[1]
            if op == '-a' or op == '--artist':
                self.setArtist(arg)
            elif op == '-g' or op == '--genre':
                self.setGenre(arg)
            elif op == '-A' or op == '--album':
                self.setAlbum(arg)
            elif op == '-t' or op == '--track':
                self.setTrack(arg)
            elif op == '-T' or op == '--title':
                self.setTitle(arg)
            elif op == '-c' or op == '--costum':
                self.setCostum(arg)
            elif op == '-f' or op == '--from':
                self.fromEncoding = i[1]
                self.converting()
        self.tag.submitTag() 
        fileRef.closeFile()

    def setTag(self,id,text):
        tag = self.tag.setTag(id,text)
        if tag == None :
            tag = sefl.tag.addTag(id,text)

    def setArtist(self,artist):
        self.setTag('TPE1',artist)

    def setGenre(self,genre):
        self.setTag('TCON',genre)
        
    def setAlbum(self,album):
        self.setTag('TALB',album)

    def setTrack(self,track):
        self.setTag('TRCK',track)

    def setTitle(self,title):
        self.setTag('TIT2',title)

    def setCostum(self,id):
        for i in self.argv:
            if i[0] == '-d' or i[0] == '--desc':
                desc = i[1]
        self.setTag(id,desc)

    def converting(self):
        tags = self.tag.getAllTagId()
        for i in tags:
            if self.tag.detectEDB(i) == '\x00':
                # 컨버팅
                oldText = self.tag.getTag(i)
                self.tag.setTag(i,unicode(oldText,self.fromEncoding).encode('utf-8'))
        
############################# Global Functions ##########################


def hasOpt(optlist,op):
    for i in optlist:
        if i[0] == op :
            return True
    return False

def checkError(opt,args):
    if args == [] :
        print "에러: 파일명을 반드시 입력하세요"
        return False
    
    # -f 옵션을 사용했을때는 다른 옵션을 사용을 금지 시킨다.
    isError = False
    if hasOpt(opt,'-f') == True or hasOpt(opt,'--from') == True:
        if hasOpt(opt,'-a') or hasOpt(opt,'--artist'):
            isError = True
        elif hasOpt(opt,'-g') or hasOpt(opt,'--genre'):
            isError = True
        elif hasOpt(opt,'-A') or hasOpt(opt,'--album'):
            isError = True
        elif hasOpt(opt,'-t') or hasOpt(opt,'--track'):
            isError = True
        elif hasOpt(opt,'-T') or hasOpt(opt,'--title'):
            isError = True
        elif hasOpt(opt,'-c') or hasOpt(opt,'--costum'):
            isError = True
    if isError :
        print "에러: -f (--from) 옵션은 다른 옵션과 같이 사용하면 안됩니다."
        return False    

    return True

def printHelp():
    helpString = '''
tagconv 2.0 
윈도우에서 인코딩이 이상하게 만들어진 음악파일의 테그 인코딩을 리눅스에서 읽을 수 있게끔 utf-8 로 변경해 줍니다.

이 프로그램은 navilera(이만우, navilera@gmail.com) 이 제작하였습니다.
이 프로그램은 GPL 을 따릅니다.
        -------- 사용법 -----------
1. 도움말 보기
    
    tagconv -h

2. 테그 내용 출력하기

    tagconv -p [음악 파일 이름]

3. 테그 인코딩 컨버팅

    tagconv -f [원래인코딩] [음악파일]
    혹은
    tagconv --from [원래인코딩] [음악파일]

4. 가수명 변경

    tagconv -a [가수이름] [음악파일]
    혹은
    tagconv --artist [가수이름] [음악파일]

5. 장르변경

    tagconv -g [장르] [음악파일]
    혹은
    tagconv --genre [장르] [음악파일]

6. 앨범명 변경

    tagconv -A [앨범명] [음악파일]
    혹은
    tagconv --album [앨범명] [음악파일]

7. 트랙번호변경

    tagconv -t [트랙번호] [음악파일]
    혹은
    tagconv --track [트랙번호] [음악파일]

8. 제목변경

    tagconv -T [제목] [음악파일]
    혹은
    tagconv --title [제목] [음악파일]

9. 사용자가 테그 추가

    tagconv -c [추가할프레임아이디] -d [프레임스트링] [음악파일]
    혹은
    tagconv --costum [추가할프레임아이디] --desc [프레임스트링] [음악파일]

    -------- 사용 예 ----------

1. 현재 디렉토리에 있는 한글 윈도우에서 만들어진 (인코딩이 cp949인) mp3 파일의 인코딩을 전부 utf8 로 변환한다.
    
    tagconv -fcp949 *.mp3
    혹은
    tagconv --from cp949 *.mp3

2. 현재 디렉토리에 있는 mp3 파일의 장르를 전부다 '가요' 로 변환한다.

    tagconv -g 가요 *.mp3

3. 현재 디렉토리에 있는 mp3 파일의 장르를 '가요' 로 가수를 '이만우' 로 변환한다.

    tagconv -g 가요 -a 이만우 *.mp3

4. mymusic.mp3 의 트랙번호를 10번으로 변환한다.

    tagconv -t 10 mymusic.mp3

5. 현재 디렉토리에 있는 mp3 파일의 테그 내용을 모두 출력한다.

    tagconv -p *.mp3
    '''
    print helpString

def printTags(filename):
    f = pyTag(filename)
    t = f.Tag()
    tagids = t.getAllTagId()
    
    str = ''
    for i in tagids:
        text = t.getTag(i)
        str += "[%s:%s] "%(i,text)
    print filename + " : " + str
    f.closeFile()
        
    
######################## entry point ###################################

if len(sys.argv) < 2 :
    printHelp()
    sys.exit()
    
if sys.argv[1] == '-h' or sys.argv[1] == '--help':
    printHelp()
    sys.exit()

optlist, args = getopt.getopt(sys.argv[1:],'f:a:g:A:t:T:c:d:p',['from=','artist=','genre=','album=','track=','title=','costum=','desc=','print'])


if checkError(optlist,args) == False:
    sys.exit()

if optlist == [] :
    printHelp()
    sys.exit()

tagconv = Tagconv(optlist)

for i in args:
    # -p 옵션이 있으면 아무것도 안하고 출력만 한다.
    if hasOpt(optlist,'-p') or hasOpt(optlist,'--print') :
        printTags(i)
        continue
    tagconv.startConv(i) 
