找回密码
 -注册-
查看: 1773|回复: 14
打印 上一主题 下一主题

使用 Roon 听音乐,搭建智能化资源管理系统的幸福体验

[复制链接]
跳转到指定楼层
1
发表于 2024-10-18 09:18 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式 来自 上海
本帖最后由 发烧的悟净 于 2024-10-18 09:39 编辑

        作为一名HIFI爱好者,每天打开Roon,看到最新下载的音乐自动整齐地加载到音乐库中,是一件无比美妙的事情。通过合理的自动化脚本,我们可以让Roon和NAS的搭配达到完美的效果,让每一天的音乐时光都变得更加愉快。本文将分享如何搭建一套智能化的音乐资源管理系统,包括自动化的资源下载、格式转换、文件整理以及最终入库。
自动化资源下载:轻松获取最新音乐系统的第一步是通过Transmission实现自动化音乐下载。我使用Python编写了一个脚本,定期抓取自己喜欢的音乐资源,并通过Transmission的API自动将它们加入下载队列。这一步彻底免除了手动寻找资源的麻烦,每天都有新的音乐自动到来,仿佛拥有了一个私人音乐管家。
  1. import requests

  2. # 通过Transmission API将抓取的种子文件加入下载队列
  3. def add_torrent(torrent_url):
  4.     session_id = get_transmission_session_id()  # 获取Transmission session id
  5.     headers = {'X-Transmission-Session-Id': session_id}
  6.     data = {"method": "torrent-add", "arguments": {"filename": torrent_url}}
  7.     response = requests.post(TRANSMISSION_URL, headers=headers, json=data)
  8.     print("下载任务已添加:", response.status_code)
复制代码



音乐文件整理与格式转换:为Roon优化在下载完成后,接下来的任务就是将音乐文件按照Roon的需求整理好。为保证最高的音质和播放兼容性,我的曲库中所有PCM格式的文件都统一转换为FLAC格式,而SACD文件则被提取成DSF格式。通过这些自动化的格式转换,不仅可以节省空间,还能提高Roon的解析效率。
1. WAV 转 FLAC所有从网上下载的WAV文件都会被自动转换成FLAC格式,因为FLAC不仅支持无损压缩,还能大幅减少存储空间。
  1. import subprocess

  2. def convert_wav_to_flac(wav_file):
  3.     flac_file = wav_file.with_suffix('.flac')
  4.     subprocess.run(['ffmpeg', '-i', str(wav_file), str(flac_file)])
  5.     print(f"已转换 {wav_file} 为 FLAC 文件.")
复制代码



2. SACD 转 DSFSACD资源往往是高音质音乐爱好者的珍藏。我使用一个Python脚本自动将SACD ISO文件提取成DSF文件,以便在Roon中更好地管理和播放这些音乐。
  1. def extract_sacd_to_dsf(iso_file):
  2.     output_dir = iso_file.with_suffix('')  # 将ISO提取到目录
  3.     subprocess.run(['sacd_extract', '-2', '-s', '-A', '-a', '-o', output_dir, '-i', iso_file])
  4.     print(f"已提取SACD {iso_file} 为DSF文件.")
复制代码



分轨与文件整理:精准管理3. CUE 提取分轨有些下载的音乐专辑是整轨文件,而通过CUE分轨,可以让每首歌曲在Roon中独立显示。自动化的CUE分轨脚本,让这一过程变得简单而高效。
  1. def split_cue(file, cue_file):
  2.     subprocess.run(['shnsplit', '-f', cue_file, '-o', 'flac', file])
  3.     print(f"已通过 {cue_file} 分割 {file} 文件.")
复制代码



4. 复制专辑封面图片为了让Roon能够正确识别专辑封面,我们需要确保封面图片与音乐文件在同一目录下。如果封面存放在子目录中,我的脚本会自动将封面图片复制到父目录,以便Roon快速抓取封面信息。
  1. def copy_album_cover(subdir):
  2.     for image_file in subdir.glob('*.jpg'):
  3.         destination = subdir.parent / image_file.name
  4.         image_file.replace(destination)
  5.         print(f"已将 {image_file} 移动至专辑目录 {destination}.")
复制代码



5. 清空空目录随着资源的下载和整理,可能会产生一些空文件夹。为了保持目录整洁并避免不必要的混乱,我的脚本会自动扫描并删除所有空目录。
  1. def remove_empty_directories(directory):
  2.     for dirpath in directory.rglob('*'):
  3.         if dirpath.is_dir() and not any(dirpath.iterdir()):
  4.             dirpath.rmdir()
  5.             print(f"已删除空目录 {dirpath}.")
复制代码



提交入库:Roon 的每日惊喜6. 提交入NAS库最后一步是将处理完的音乐文件自动同步到NAS,并确保Roon每天都能自动加载最新的音乐。这个步骤让我每天早上打开Roon时都充满期待,因为我知道,所有最新的音乐已经悄悄地躺在了我的音乐库里。
  1. def sync_to_nas(local_directory, nas_directory):
  2.     subprocess.run(['rsync', '-av', local_directory, nas_directory])
  3.     print(f"已将 {local_directory} 同步至 NAS 目录 {nas_directory}.")
复制代码



幸福体验:Roon 的音乐新发现通过这样一套自动化的资源管理系统,每天早晨我只需要打开Roon,今天的最新音乐便会自动出现在我的曲库中。无论是全新的SACD资源,还是从网上抓取的经典专辑,都能完美呈现。Roon自动扫描并识别封面和曲目信息,播放体验也始终流畅优雅。
拥有这套智能化的下载和整理系统,音乐的管理和欣赏变得如此简单而有趣。这不仅节省了大量时间,更让我体验到HIFI世界中的那份安静和专注,真正实现了听音乐的幸福感。
每天都有新发现,每次都有新惊喜。希望这篇文章也能带给你搭建这样一套系统的灵感,让Roon和音乐世界为你带来更多的美好体验。


2
 楼主| 发表于 2024-10-18 09:36 | 只看该作者 来自 上海
本帖最后由 发烧的悟净 于 2024-10-18 09:37 编辑

本周加入的古典专辑是这个样子的
回复

使用道具 举报

3
发表于 2024-10-19 15:28 来自手机 | 只看该作者 来自 宁夏石嘴山
楼主,roon里面的华语流行音乐多吗
回复

使用道具 举报

4
发表于 2024-10-19 18:11 | 只看该作者 来自 江苏徐州
高手,我开发的HI-Player也深度支持ROON
回复

使用道具 举报

5
 楼主| 发表于 2024-10-20 00:27 来自手机 | 只看该作者 来自 上海嘉定区
番茄炒蛋饭 发表于 2024-10-19 18:11
高手,我开发的HI-Player也深度支持ROON

有机会尝试一下
回复

使用道具 举报

6
 楼主| 发表于 2024-10-20 00:28 来自手机 | 只看该作者 来自 上海嘉定区
zhidongky2 发表于 2024-10-19 15:28
楼主,roon里面的华语流行音乐多吗

不算多
回复

使用道具 举报

7
发表于 2024-10-22 11:55 | 只看该作者 来自 广东深圳
本帖最后由 skychty 于 2024-10-22 11:56 编辑
番茄炒蛋饭 发表于 2024-10-19 18:11
高手,我开发的HI-Player也深度支持ROON

大神,大概有些什么功能,是手机上的app吗?
回复

使用道具 举报

8
发表于 2024-10-22 16:23 | 只看该作者 来自 湖北武汉
番茄炒蛋饭 发表于 2024-10-19 18:11
高手,我开发的HI-Player也深度支持ROON

有windows版本吗
回复

使用道具 举报

9
发表于 2024-10-22 17:17 来自手机 | 只看该作者 来自 中国
zhidongky2 发表于 2024-10-19 15:28
楼主,roon里面的华语流行音乐多吗

roon只是播放平台,本身没有音乐资源。目前roon有内置qobuz,tidal,kkbox三家音乐网站的端口,但需要用户自己去这三家网站订阅,通过内置端口登录,才能调用资源进行播放

回复

使用道具 举报

10
发表于 2024-10-22 22:23 来自手机 | 只看该作者 来自 亚太地区
derekchen0866 发表于 2024-10-22 17:17
roon只是播放平台,本身没有音乐资源。目前roon有内置qobuz,tidal,kkbox三家音乐网站的端口,但需要用 ...

岂不是又要花钱?
回复

使用道具 举报

11
发表于 2024-10-23 11:14 | 只看该作者 来自 广东
看到高手的操作赏心悦目,FLOW
回复

使用道具 举报

12
发表于 2024-10-25 10:40 | 只看该作者 来自 中国
楼主可否给详细讲下cue,对脚本不是很懂
回复

使用道具 举报

13
 楼主| 发表于 2024-10-25 17:19 | 只看该作者 来自 上海
楼主可否给详细讲下cue,对脚本不是很懂
  1. import re
  2. from typing import List, Dict

  3. def parse_cue(file_path: str) -> Dict[str, List[Dict[str, str]]]:
  4.     """
  5.     解析CUE文件并提取信息,返回一个字典,其中包含专辑和音轨信息。
  6.    
  7.     Args:
  8.         file_path (str): CUE文件的路径。
  9.         
  10.     Returns:
  11.         Dict[str, List[Dict[str, str]]]: 包含专辑和音轨信息的字典。
  12.     """
  13.     # 用于存储专辑和音轨信息的字典
  14.     cue_data = {
  15.         "album": None,
  16.         "tracks": []
  17.     }
  18.    
  19.     with open(file_path, "r", encoding="utf-8") as file:
  20.         track_data = {}
  21.         
  22.         for line in file:
  23.             line = line.strip()
  24.             if line.startswith("TITLE"):
  25.                 title = re.findall(r'"(.+?)"', line)
  26.                 if "TRACK" not in line:
  27.                     cue_data["album"] = title[0] if title else ""
  28.                 else:
  29.                     track_data["title"] = title[0] if title else ""
  30.             
  31.             elif line.startswith("PERFORMER"):
  32.                 performer = re.findall(r'"(.+?)"', line)
  33.                 if "TRACK" not in line:
  34.                     cue_data["performer"] = performer[0] if performer else ""
  35.                 else:
  36.                     track_data["performer"] = performer[0] if performer else ""
  37.             
  38.             elif line.startswith("INDEX 01"):
  39.                 index_time = line.split()[-1]
  40.                 track_data["index"] = index_time
  41.                
  42.                 # 将该音轨添加到cue_data
  43.                 cue_data["tracks"].append(track_data)
  44.                 track_data = {}  # 重置track_data以便解析下一个音轨
  45.                
  46.     return cue_data

  47. # 示例使用
  48. cue_file = "sample.cue"
  49. parsed_data = parse_cue(cue_file)
  50. print(parsed_data)
复制代码

注:示例代码,不可执行

思路:
文件读取:打开指定的CUE文件,并逐行读取内容。

专辑信息提取:

检查每行是否以TITLE或PERFORMER开头来提取专辑的标题和演奏者信息。
如果TITLE或PERFORMER出现在非音轨的行中(即没有TRACK关键字),则将信息视为专辑信息而非音轨信息。
音轨信息提取:

遇到TRACK关键字时,准备解析一个新的音轨。
对于音轨的TITLE、PERFORMER和INDEX信息,分别提取标题、演奏者和开始时间(格式为mm:ss:ff)。
每个音轨信息保存在字典track_data中。
数据存储:

当解析到INDEX 01时,认为该音轨信息已完整,将track_data添加到主字典cue_data中的tracks列表中,并重置track_data字典以准备下一个音轨的解析。
返回结果:解析完成后,返回包含专辑和音轨信息的cue_data字典。

这个解析器能够提取常见的专辑和音轨信息,适用于CUE文件的基础解析。
回复

使用道具 举报

14
 楼主| 发表于 2024-10-25 17:22 | 只看该作者 来自 上海
pp46318 发表于 2024-10-25 10:40
楼主可否给详细讲下cue,对脚本不是很懂
楼主可否给详细讲下cue,对脚本不是很懂
  1. import re
  2. from typing import List, Dict

  3. def parse_cue(file_path: str) -> Dict[str, List[Dict[str, str]]]:
  4.     """
  5.     解析CUE文件并提取信息,返回一个字典,其中包含专辑和音轨信息。
  6.    
  7.     Args:
  8.         file_path (str): CUE文件的路径。
  9.         
  10.     Returns:
  11.         Dict[str, List[Dict[str, str]]]: 包含专辑和音轨信息的字典。
  12.     """
  13.     # 用于存储专辑和音轨信息的字典
  14.     cue_data = {
  15.         "album": None,
  16.         "tracks": []
  17.     }
  18.    
  19.     with open(file_path, "r", encoding="utf-8") as file:
  20.         track_data = {}
  21.         
  22.         for line in file:
  23.             line = line.strip()
  24.             if line.startswith("TITLE"):
  25.                 title = re.findall(r'"(.+?)"', line)
  26.                 if "TRACK" not in line:
  27.                     cue_data["album"] = title[0] if title else ""
  28.                 else:
  29.                     track_data["title"] = title[0] if title else ""
  30.             
  31.             elif line.startswith("PERFORMER"):
  32.                 performer = re.findall(r'"(.+?)"', line)
  33.                 if "TRACK" not in line:
  34.                     cue_data["performer"] = performer[0] if performer else ""
  35.                 else:
  36.                     track_data["performer"] = performer[0] if performer else ""
  37.             
  38.             elif line.startswith("INDEX 01"):
  39.                 index_time = line.split()[-1]
  40.                 track_data["index"] = index_time
  41.                
  42.                 # 将该音轨添加到cue_data
  43.                 cue_data["tracks"].append(track_data)
  44.                 track_data = {}  # 重置track_data以便解析下一个音轨
  45.                
  46.     return cue_data

  47. # 示例使用
  48. cue_file = "sample.cue"
  49. parsed_data = parse_cue(cue_file)
  50. print(parsed_data)
复制代码

注:示例代码,不可执行

思路:
文件读取:打开指定的CUE文件,并逐行读取内容。

专辑信息提取:

检查每行是否以TITLE或PERFORMER开头来提取专辑的标题和演奏者信息。
如果TITLE或PERFORMER出现在非音轨的行中(即没有TRACK关键字),则将信息视为专辑信息而非音轨信息。
音轨信息提取:

遇到TRACK关键字时,准备解析一个新的音轨。
对于音轨的TITLE、PERFORMER和INDEX信息,分别提取标题、演奏者和开始时间(格式为mm:ss:ff)。
每个音轨信息保存在字典track_data中。
数据存储:

当解析到INDEX 01时,认为该音轨信息已完整,将track_data添加到主字典cue_data中的tracks列表中,并重置track_data字典以准备下一个音轨的解析。
返回结果:解析完成后,返回包含专辑和音轨信息的cue_data字典。

这个解析器能够提取常见的专辑和音轨信息,适用于CUE文件的基础解析。
回复

使用道具 举报

15
发表于 2024-10-26 09:06 | 只看该作者 来自 中国
发烧的悟净 发表于 2024-10-25 17:22
注:示例代码,不可执行

思路:

非常感谢楼主耐心详细的解答。不过还是有个问题,这个具体要怎么操作呢?
回复

使用道具 举报

您需要登录后才可以回帖 登录 | -注册-

本版积分规则

Archiver|手机版|粤icp备09046054号|耳机网-耳机大家坛

粤公网安备 44030602000598号 耳机大家坛、www.erji.net、网站LOGO图形均为注册商标

GMT+8, 2024-11-25 15:18

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表