Last active
August 29, 2015 14:04
-
-
Save shuGH/12f4c1627ae46c079ea4 to your computer and use it in GitHub Desktop.
Script to add lyrics tag from text file in directories
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# coding: utf-8 | |
# -------------------------------------------------------------------------------- | |
# 音楽ファイルのタグ情報を元にテキスト形式の歌詞ファイルを検索し歌詞を書き込む | |
# -------------------------------------------------------------------------------- | |
# targets 対象の音楽ファイルorフォルダ(可変長) | |
# -d --dir 歌詞フォルダ(def:カレント以下) | |
# -l --matchlevel 文字列一致判定の類似度のレベル(1..3=def) | |
# -a --ischeckartist アーティストとフォルダ名の一致判定を行うか(def:False) | |
# -n --isnormalize 歌手表示を規格化するか(def:False) | |
# -w --isoverwrite 上書きするか(def:False) | |
# -f --format 歌手表示のフォーマット | |
# -t --istest 実際の書き込みは行わない(def:False) | |
# -------------------------------------------------------------------------------- | |
import os, sys, codecs | |
import glob | |
import difflib | |
import argparse | |
import traceback | |
import mutagen | |
from mutagen.id3 import USLT | |
from mutagen.mp3 import MP3 | |
from mutagen.oggvorbis import OggVorbis | |
from mutagen.easyid3 import EasyID3 | |
MP3_EXTENTION = ".mp3" | |
OGG_EXTENTION = ".ogg" | |
TXT_EXTENTION = ".txt" | |
DEFAULT_FORMAT = "" | |
IsTestMode = False | |
IsVerbose = False | |
# ---------------------------------------- | |
# コマンドラインの解析 | |
# ---------------------------------------- | |
def parseArgument(): | |
parser = argparse.ArgumentParser(description='') | |
parser.add_argument('--version', action='version', version='%(prog)s 1.0') | |
parser.add_argument('targets', nargs='*') | |
parser.add_argument('-d', '--dir', default='./', help='') | |
parser.add_argument('-l', '--matchlevel', type=int, default=3, choices=xrange(1, 4), help='') | |
parser.add_argument('-a', '--ischeckartist', default=False, action='store_true', help='') | |
parser.add_argument('-n', '--isnormalize', default=False, action='store_true', help='') | |
parser.add_argument('-w', '--isoverwrite', default=False, action='store_true', help='') | |
parser.add_argument('-f', '--format', type=str, default=DEFAULT_FORMAT, help='') | |
parser.add_argument('-t', '--istest', default=False, action='store_true', help='') | |
parser.add_argument('--verbose', default=False, action='store_true', help='') | |
args = parser.parse_args() | |
return args | |
# ---------------------------------------- | |
# 音楽ファイルの読み込み | |
# ---------------------------------------- | |
def loadAudio (fname): | |
if not os.path.exists(fname): | |
return None | |
ext = os.path.splitext(fname)[1] | |
if ext == MP3_EXTENTION: | |
return MP3(fname) | |
elif ext == OGG_EXTENTION: | |
return OggVorbis(fname) | |
return None | |
# ---------------------------------------- | |
# 一致強度の取得 | |
# ---------------------------------------- | |
def getMatchRatio (level): | |
ratio = 1.0 | |
if level >= 3: | |
ratio = 1.0 | |
elif level >= 2: | |
ratio = 0.8 | |
elif level >= 1: | |
ratio = 0.6 | |
return ratio | |
# ---------------------------------------- | |
# 曲名とファイル名との判定 | |
# ---------------------------------------- | |
def checkLyricsTitle (level, fname, title): | |
name = os.path.splitext(fname)[0] | |
match = difflib.SequenceMatcher(None, name, title) | |
ratio = match.ratio() | |
if ratio > 0.4: | |
printLog(u" Match ratio: "+name+u" - "+title+u" > "+str(ratio)) | |
return (ratio >= getMatchRatio(level)) | |
# ---------------------------------------- | |
# アーティスト名とディレクトリ名との判定 | |
# ---------------------------------------- | |
def checkLyricsArtist (level, dname, artist): | |
name = os.path.splitext(dname)[0] | |
match = difflib.SequenceMatcher(None, name, artist) | |
ratio = match.ratio() | |
if ratio > 0.4: | |
printLog(u" Match ratio: "+name+u" - "+artist+u" > "+str(ratio)) | |
return (ratio >= getMatchRatio(level)) | |
# ---------------------------------------- | |
# 歌詞の取得 | |
# ---------------------------------------- | |
def getLyrics (rpath, title, artist, album, isCheckArtist, stringMatchLevel): | |
# 走査 | |
for dpath, dnames, fnames in os.walk(rpath): | |
for fname in fnames: | |
ext = os.path.splitext(fname)[1] | |
if ext == TXT_EXTENTION: | |
# 曲名の判定 | |
if checkLyricsTitle(stringMatchLevel, fname.decode("mbcs"), title): | |
# アーティスト名の判定 | |
if isCheckArtist: | |
absPath = os.path.abspath(os.path.join(dpath, fname)) | |
dname = os.path.basename(os.path.dirname(absPath)) | |
if not checkLyricsArtist(stringMatchLevel, dname.decode("mbcs"), artist): | |
continue | |
# 取得 | |
f = codecs.open(os.path.join(dpath, fname), "r", "shift_jis") | |
buf = f.read() | |
f.close() | |
printLog(u" Get lyrics: "+fname.decode("mbcs")) | |
return buf | |
return None | |
# ---------------------------------------- | |
# 歌詞を正規化する | |
# ---------------------------------------- | |
def getNormalizedLyrics (lyrics, format): | |
# TODO | |
return lyrics | |
# ---------------------------------------- | |
# 歌詞をタグに書き込む | |
# ---------------------------------------- | |
def addLyrics (isOverWrite, audio, lyrics): | |
if 'audio/mp3' in audio.mime: | |
if not isOverWrite: | |
if len(audio.tags.getall('USLT')) > 0: | |
printLog(u" Already exists..") | |
return False | |
audio.tags.delall('USLT') | |
audio.tags.add(USLT(encoding=3, text=lyrics)) | |
if not IsTestMode: | |
audio.save() | |
printLog(u" Saved..") | |
return True | |
elif 'audio/vorbis' in audio.mime: | |
if not isOverWrite: | |
if "UNSYNCEDLYRICS" in audio.tags: | |
printLog(u" Already exists..") | |
return False | |
audio.tags["UNSYNCEDLYRICS"] = lyrics | |
if not IsTestMode: | |
audio.save() | |
printLog(u" Saved..") | |
return True | |
# ---------------------------------------- | |
# メインプロセス | |
# ---------------------------------------- | |
def mainProcess (fname, dpath, isOverWrite, isNormalize, isCheckArtist, stringMatchLevel, format): | |
printLog("Start: "+fname) | |
# 読み込み | |
audio = loadAudio(fname) | |
if not audio == None: | |
# 必要な情報の取得 | |
title = "" | |
artist = "" | |
album = "" | |
if 'audio/mp3' in audio.mime: | |
if "TIT2" in audio: | |
title = audio["TIT2"].text[0] | |
if "TPE1" in audio: | |
artist = audio["TPE1"].text[0] | |
if "TALB" in audio: | |
album = audio["TALB"].text[0] | |
if 'audio/vorbis' in audio.mime: | |
if "title" in audio: | |
title = audio["title"][0] | |
if "artist" in audio: | |
artist = audio["artist"][0] | |
if "album" in audio: | |
album = audio["album"][0] | |
printLog(u" Load audio: "+fname.decode("mbcs")+u" - "+title+u", "+artist+u", "+album) | |
# 歌詞ファイルの取得 | |
lyrics = getLyrics(dpath, title, artist, album, isCheckArtist, stringMatchLevel) | |
if not lyrics == None: | |
# 歌詞のフォーマット | |
if isNormalize: | |
lyrics = getNormalizedLyrics(lyrics, format) | |
# 保存 | |
isSaved = addLyrics(isOverWrite, audio, lyrics) | |
if isSaved: | |
print u"Done: saved lyrics.. "+fname.decode("mbcs") | |
else: | |
print u"Warn: already exists.. "+fname.decode("mbcs") | |
else: | |
print u"Warn: lyrics not found.. " + fname.decode("mbcs") | |
# ---------------------------------------- | |
# メイン | |
# ---------------------------------------- | |
def main(): | |
args = parseArgument() | |
targets = args.targets | |
dpath = args.dir | |
stringMatchLevel = args.matchlevel | |
isCheckArtist = args.ischeckartist | |
isOverWrite = args.isoverwrite | |
isNormalize = args.isnormalize | |
format = args.format | |
global IsTestMode | |
IsTestMode = args.istest | |
global IsVerbose | |
IsVerbose = args.verbose | |
printLog(args) | |
# 対象の走査 | |
for target in targets: | |
# フォルダ名の時 | |
fnames = glob.glob(target) | |
if len(fnames) > 0: | |
for fname in fnames: | |
mainProcess(fname, dpath, isOverWrite, isNormalize, isCheckArtist, stringMatchLevel, format) | |
# ファイルの時 | |
else: | |
mainProcess(target, dpath, isOverWrite, isNormalize, isCheckArtist, stringMatchLevel, format) | |
# ---------------------------------------- | |
# ログ出力 | |
# ---------------------------------------- | |
def printLog(mes): | |
if IsVerbose: print(mes) | |
# ---------------------------------------- | |
# スクリプト実行 | |
# ---------------------------------------- | |
if __name__ == "__main__": | |
try: | |
main() | |
except Exception as err: | |
print str(type(err)) | |
print err.message | |
print traceback.print_exc() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment