因为个人原因, 我基本是下载歌曲并整理导入 iTunes,然后同步到各个设备上。 同时,我的设备语言是了英文,这就导致,所有英文歌曲按照 A-Z 排序,然后所有日文和中文歌曲在 # 的位置里。

搜索了下相关问题,知道 iTunes 有一个标签, sorting name, 即 iTunes 可以按照这个变量进行排序,比如将中文转换成拼音,或者日文转成罗马音。 这样就可以正常排序了。

解决方法是有了,但曲库都是以几百首计算的, 不可能一个一个的去改。 作为程序员,要遵守

宁愿写 1,2个小时的脚本,也不愿意花半小时手动完成任务

当然,写脚本是为了以后,新增歌曲的时候,可以自动化的完成编辑,可以省很多时间。

macOS (从 10.5 开始)提供了接口,叫做ScriptingBridge, 位于

/System/Library/Frameworks/Python.framework/Versions/Current/Extras/lib/python/PyObjC/

可以通过这个 bridge 获取 iTunes 的文件并对信息进行更改, 随后可以使用其他库或者脚本,转成拼音(或者罗马音)

大概代码就放在下面了, 有时间再上传到 Gist 上, 因此系统库的版本导致必须要用 Python2。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# -*- coding: utf-8 -*-
import itertools
from pypinyin import lazy_pinyin
from pykakasi import kakasi

# Load Bridge Lib
try:
import sys
FRAMEWORK_PATH = "/System/Library/Frameworks/Python.framework/Versions/Current"
sys.path.append(FRAMEWORK_PATH + "/Extras/lib/python/PyObjC/")
from ScriptingBridge import SBApplication
except ImportError:
print("Unable to find Bridge Lib")
exit()


class ITunes(object):

Languages = ["ja", "zh"]

def __init__(self):
self.app = SBApplication.applicationWithBundleIdentifier_('com.apple.iTunes')
self.app.setFixedIndexing_(True)
self.source = self.first(self.app.sources(), lambda x: x.name() == 'Library')
self.playlists = {}
self.process_funcs = {}
self.load_process_funcs()

@property
def tracks(self):
return self['Music'].tracks()

def __getitem__(self, name):
return self.first(self.source.userPlaylists(), lambda x: x.name() == name)

def first(self, iterable, predicate):
try:
return itertools.ifilter(predicate, iterable).next()
except StopIteration:
return None

def load_process_funcs(self):
k = kakasi()
k.setMode("H", "a")
k.setMode("K", "a")
k.setMode("r", "Hepburn")
self.process_funcs = {
"ja": k.getConverter().do,
"zh": lambda x: "".join(lazy_pinyin(x))
}

def before_process(self, lang):
if lang == "ja":
k = kakasi()
k.setMode("H", "a")
k.setMode("K", "a")
k.setMode("r", "Hepburn")
k.setMode("J", "a")
self.process_funcs["ja"] = k.getConverter().do

def check_language(self, track):
name_ja = self.process_funcs["ja"](track.name())
name_cn = lazy_pinyin(track.name())

# 歌名和转换后的中文,日文一样, 认为是英文,不处理
if track.name() == name_ja and track.name() == name_cn[0]:
return None

text = track.name() + "," + track.artist() + "," + track.album()
ja = self.process_funcs["ja"](text)
# 文本和转换后的日文一致
# 又因为设置不转换汉字, 因此文本内不包含平(片)假名
# 所以是中文
if text != ja:
return "ja"
return "zh"

def loadTracks(self):
# 清空播放列表
total = len(self.tracks)
numDigits = len(str(total))
for i, cur in enumerate(self.tracks, 1):
lang = self.check_language(cur)
if not lang:
continue
print("Adding track " + str(i).rjust(numDigits) + "/" + str(total) + ", Lang=" + lang)
cur.duplicateTo_(self.playlists[lang])

def convertTracks(self):
for lang in self.Languages:
self.before_process(lang)
print("Praseing " + lang.upper() + " playlist")
f = self.process_funcs[lang]
for cur in self.playlists[lang].tracks():
cur.setSortName_(f(cur.name()))
cur.setSortAlbum_(f(cur.album()))

def createTempPlaylist(self):
print("Creating all temp playlist")
pl = self.source.playlists()
plC = self.app.classForScriptingClass_("playlist")
for key in self.Languages:
playlist = plC.alloc().initWithProperties_({"name": key})
pl.insertObject_atIndex_(playlist, 0)
self.playlists[key] = playlist

def removeTempPlaylist(self):
print("Removing all temp playlist")
pl = self.source.playlists()
for key in self.Languages:
pl.removeObjectWithName_(key)

def run(self):
try:
self.createTempPlaylist()
self.loadTracks()
self.convertTracks()
except KeyboardInterrupt:
print("Pause")
finally:
self.removeTempPlaylist()


if __name__ == "__main__":
lib = ITunes()
lib.run()