Skip to content

Instantly share code, notes, and snippets.

@shuGH
Last active August 29, 2015 14:04
Show Gist options
  • Save shuGH/12f4c1627ae46c079ea4 to your computer and use it in GitHub Desktop.
Save shuGH/12f4c1627ae46c079ea4 to your computer and use it in GitHub Desktop.
Script to add lyrics tag from text file in directories
# 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