作为某个 Project 的一部分~(暂时不透露是什么,嘻嘻(⁎⁍̴̛ᴗ⁍̴̛⁎) ) 需要把 iTunes 里面的所有音乐的 MetaInfo 和封面导出到 MongoDB 中
MongoDB 上次已经已经在 Raspberry Pi 4 上编译部署好了~在 Raspberry Pi 4 上安装 64-bit MongoDB Server 服务
然后再配合很久以前玩过的 在 Python 里使用 Scripting Bridge 与 iTunes 交互,就可以达到目标了233333
当然需要注意的是,这里要使用的是 macOS 自带的 Python 2.7,因为 ScriptingBridge
只安装在了自带的 Python 2.7 里
真正代码的话,其实整体来说很简单,需要考虑的点是如何做到不重复写封面,因为——
- 目前 Scripting Bridge 与 iTunes 交互时,只能一首音乐一首音乐的依次遍历,不能直接按照专辑遍历
- 同一张专辑里,有的音乐可能包含多张封面
- 不同的专辑可能被我 assgin 过相同的封面
综合这几点考虑的话,那就只能每次拿到有封面的音乐之后,对它的每一张封面都计算 SHA256 摘要(这里暂且认为 SHA256 的空间足够大,不会产生碰撞),并在放进 global_sha256
前,检查是否已经有相同的 SHA256 存在其中。如果没有的话,才保存图片到磁盘中,并放到那首歌的 MetaInfo 中;如果在 global_sha256
中有的话,那么就再看那首歌的 MetaInfo 中有没有这个 SHA256(因为也许有人不小心添加了两张一样的封面到音乐里)。
在遍历完所有音乐之后,把这些 MetaInfo 写入到 JSON 文件中~(就像下面这样
{ "album": "Cutie Panther", "name": "夏、終わらないで。", "artist": "BiBi (南條愛乃, Pile, 徳井青空)", "cover": [ "44f9b56091c7ca5b011cd9cb306eab21d4f854300c96347a0a7f3538cbeb9dcd-1" ], "composer": "渡辺和紀", "year": 0, "sha256": [ "44f9b56091c7ca5b011cd9cb306eab21d4f854300c96347a0a7f3538cbeb9dcd" ] }
最后再导进 MongoDB 数据库就可以啦(当然需要安装一下 pymongo
库)~主要的就分为 2 个 stage ♪(´ε` )
python2.7 -m pip install --user pymongo python2.7 iTunes.py -s 1 python2.7 iTunes.py --host raspberrypi.local -s 2
在传到 Raspberry Pi 的 MongoDB 之后验证一下~
#!/usr/bin/python # -*- coding: utf-8 -*- import argparse import json import hashlib import hmac import io from pymongo import MongoClient from ScriptingBridge import NSImage, SBApplication def cover_filename(cover_data): """Compute the filename for the given cover This function accepts a data buffer and computes its SHA256 Parameters ---------- cover_data : data buffer Data buffer of any cover Returns ------- signature_sha : str The SHA256 hex digest of the given input data """ signature_sha = hmac.new("".encode('utf-8'), cover_data, hashlib.sha256).hexdigest() return signature_sha def export_cover(save_at = "database.json"): """Export all covers exist in iTunes library Parameters ---------- save_at : str Save the metainfo to a JSON file (None indicates 'do not save') Returns ------- database : list A list that contains all metainfo of exported covers """ # 允许的文件后缀 allowed_suffixes = ["m4a", "mp3"] # 所有的信息 database = [] cover_info = {} # 从 iTunes 获取所有的项目 iTunes = SBApplication.applicationWithBundleIdentifier_("com.apple.iTunes") tracks = iTunes.sources()[0].playlists()[0].tracks() # 一共有多少项 tracks_amount = len(tracks) # 依次遍历 for index in range(tracks_amount): # 显示当前正在处理的 track = tracks[index] print(unicode("[{}/{}] {}").format(index + 1, tracks_amount, track.name().strip())) # 是否是音乐文件 if str(track.location())[-3:] not in allowed_suffixes: # 若不是则到下一项 continue # 获取封面 artworks = track.artworks() # 是否有至少一张封面图 if len(artworks) == 0: # 若不是则到下一项 continue # 该 track 所对应的信息 info = { "name": track.name().strip(), "artist": track.artist().strip(), "year": track.year(), "composer": track.composer().strip(), "album": track.album().strip() } # 计算对应的 SHA256 info["sha256"] = [] # 该 track 所对应的封面 for artwork in artworks: # 获取 NSImage 的数据 data = artwork.data() if isinstance(data, NSImage): data = data.TIFFRepresentation() # 获取该封面的 SHA256 cover_sha256 = cover_filename(data) # 如果这张封面已经有了 if cover_sha256 in info["sha256"]: # 就看下一个封面 continue # 否则就将当前封面的 SHA256 加入进去 info["sha256"].append(cover_sha256) # 否则生成文件名 filename = "{}-{}".format(cover_sha256, len(info["sha256"])) # 写入目录 data.writeToFile_atomically_("cover/{}.tiff".format(filename), True) # 是否确实读到并保存了一张或以上的封面 if len(info["sha256"]) == 0: # 没有则到下一项 continue # 将该 track 添加到数据库 database.append(info) # 保存 JSON if save_at is not None: with io.open(save_at, "w", encoding = "utf-8") as f: f.write(unicode(json.dumps(database, ensure_ascii = False, indent = 2))) # 返回数据库 return database def import_to_mongodb(data_source, host = "localhost", port = 27017, database = "animecover", collection = "songs",): """Import data to MongoDB Parameters ---------- data_source : list The metainfo of all exported covers host : str MongoDB host port : int MongoDB port Returns ------- result : InsertManyResult The result of insertion """ # 连接 MongoDB client = MongoClient(host, port) # 使用数据库 db = client[database] # 在 `collection` 中放入记录 result = db[collection].insert_many(data_source) return result def arg_parse(): """Parse command line arguments""" parser = argparse.ArgumentParser() parser.add_argument("-o", "--output", type = str, default = "database.json", help = "Save all metainfo of exported covers to file") parser.add_argument("--host", type = str, default = "localhost", help = "MongoDB host") parser.add_argument("-p", "--port", type = int, default = 27017, help = "MongoDB post") parser.add_argument("-d", "--database", type = str, default = "animecover", help = "Name of MongoDB database") parser.add_argument("-c", "--collection", type = str, default = "songs", help = "Name of collection in database") parser.add_argument("-s", "--stage", type = int, default = 1, help = "Stages 1/2") args = parser.parse_args() return args def main(): args = arg_parse() if args.stage == 1: # stage 1 # export all covers exist in iTunes library # and save metainfo export_cover(args.output) elif args.stage == 2: # stage 2 # import all metainfo to MongoDB with io.open(args.output, "r", encoding = "utf-8") as f: data = json.loads(f.read()) result = import_to_mongodb(data, args.host, args.port, args.database, args.collection) print("{} of {} successfully imported".format(len(result.inserted_ids), len(data))) if __name__ == "__main__": main()