首页 > 编程语言 >[python]批量转换ncm格式文件

[python]批量转换ncm格式文件

时间:2024-12-07 17:11:38浏览次数:6  
标签:file str python 格式文件 ncm key import dir

前言

最近想换用本地其它播放器听音乐,但网易云音乐下载下来的文件格式是.ncm,不兼容其它播放器。网上找了下方案,参考网易云音乐ncm格式分析以及ncm与mp3格式转换实现了基本功能,在此基础上加了个多进程同时转换,以及通过命令行传一些参数,比如并发执行数、输入输出目录路径。

示例代码

其中有个非标准库依赖需要安装

python -m pip install pycryptodome

基本逻辑是参考原文的,修改处是添加了多进程执行、路径操作改用pathlib、支持命令行传参

import binascii
import struct
import base64
import json
import os
from Crypto.Cipher import AES
from pathlib import Path
from concurrent.futures import ProcessPoolExecutor
from time import time
import argparse


def get_args():
    parser = argparse.ArgumentParser(description="ncm file convertor")
    parser.add_argument("-j", type=int, default=None, help="Maximum number of concurrency, default is cpu cores count")
    parser.add_argument("-i", type=str, default=".", help="input dir path, default is current dir")
    parser.add_argument("-o", type=str, default=None, help="output dir path")
    return parser.parse_args()

def get_savepath(output_dir: str, file_name: str) -> Path:
    base_dir = Path(output_dir)
    if not base_dir.exists():
        base_dir.mkdir(parents=True, exist_ok=True)
    p = base_dir / file_name
    return p

def process(file_path: Path, output_dir: str):
    start = time()
    print(f"start convert {file_path}")
    core_key = binascii.a2b_hex("687A4852416D736F356B496E62617857")
    meta_key = binascii.a2b_hex("2331346C6A6B5F215C5D2630553C2728")
    unpad = lambda s: s[0 : -(s[-1] if type(s[-1]) == int else ord(s[-1]))]
    f = open(file_path, "rb")
    header = f.read(8)
    if binascii.b2a_hex(header) != b"4354454e4644414d":
        print("incorrect file header, skiped ...")
        return
    f.seek(2, 1)
    key_length = f.read(4)
    key_length = struct.unpack("<I", bytes(key_length))[0]
    key_data = f.read(key_length)
    key_data_array = bytearray(key_data)
    for i in range(0, len(key_data_array)):
        key_data_array[i] ^= 0x64
    key_data = bytes(key_data_array)
    cryptor = AES.new(core_key, AES.MODE_ECB)
    key_data = unpad(cryptor.decrypt(key_data))[17:]
    key_length = len(key_data)
    key_data = bytearray(key_data)
    key_box = bytearray(range(256))
    c = 0
    last_byte = 0
    key_offset = 0
    for i in range(256):
        swap = key_box[i]
        c = (swap + last_byte + key_data[key_offset]) & 0xFF
        key_offset += 1
        if key_offset >= key_length:
            key_offset = 0
        key_box[i] = key_box[c]
        key_box[c] = swap
        last_byte = c
    meta_length = f.read(4)
    meta_length = struct.unpack("<I", bytes(meta_length))[0]
    meta_data = f.read(meta_length)
    meta_data_array = bytearray(meta_data)
    for i in range(0, len(meta_data_array)):
        meta_data_array[i] ^= 0x63
    meta_data = bytes(meta_data_array)
    meta_data = base64.b64decode(meta_data[22:])
    cryptor = AES.new(meta_key, AES.MODE_ECB)
    meta_data = unpad(cryptor.decrypt(meta_data)).decode("utf-8")[6:]
    meta_data = json.loads(meta_data)
    crc32 = f.read(4)
    crc32 = struct.unpack("<I", bytes(crc32))[0]
    f.seek(5, 1)
    image_size = f.read(4)
    image_size = struct.unpack("<I", bytes(image_size))[0]

    file_name = f"{file_path.stem}.{meta_data['format']}"
    file_name = get_savepath(output_dir, file_name)
    m = open(file_name, "wb")
    chunk = bytearray()
    while True:
        chunk = bytearray(f.read(0x8000))
        chunk_length = len(chunk)
        if not chunk:
            break
        for i in range(1, chunk_length + 1):
            j = i & 0xFF
            chunk[i - 1] ^= key_box[
                (key_box[j] + key_box[(key_box[j] + j) & 0xFF]) & 0xFF
            ]
        m.write(chunk)
    m.close()
    f.close()
    end = time()
    print(f"convert {file_path.name} elapsed {end - start:.4f} seconds")


def create_task(input_dir: str, output_dir: str, jobs: int):
    current_dir = Path(input_dir)
    if not current_dir.exists():
        raise FileNotFoundError(f"{current_dir} not found")
    ncmfiles = current_dir.rglob("*.ncm")
    futures = []
    print(f"Maximum number of concurrency: {jobs}")
    with ProcessPoolExecutor(max_workers=jobs) as executor:
        for i in ncmfiles:
            futures.append(executor.submit(process, i, output_dir))


if __name__ == "__main__":
    args = get_args()
    if args.j is None or args.j < 1:
        jobs = os.cpu_count()
    else:
        jobs = args.j
    
    input_dir = args.i

    if args.o is None:
        output_dir = "output"
    else:
        output_dir = args.o
    try:
        create_task(input_dir, output_dir, jobs)
    except Exception as e:
        print(e)

使用。假设代码文件名为demo.py

# 指定最大进程数为 4, 源文件路径为 music, 转换后的文件目录为 result
python demo.py -j 4 -i music -o result

标签:file,str,python,格式文件,ncm,key,import,dir
From: https://www.cnblogs.com/XY-Heruo/p/18592412

相关文章

  • D90【python 接口自动化学习】- pytest基础用法
    day90pytest的setup,setdown详解(二)学习日期:20241206学习目标:pytest基础用法--pytest的setup,setdown详解(二)学习笔记:setup、teardown详解(二)函数级setup_function/teardown_function对每条函数用例生效(不在类中)importrequestsimportpytestdefsetup_function():......
  • D89【python 接口自动化学习】- pytest基础用法
    day89pytest的setup,setdown详解学习日期:20241205学习目标:pytest基础用法--pytest的setup,setdown详解学习笔记:setup、teardown详解模块级setup_module/teardown_module开始于模块始末,生效一次importpytestimportrequestsdefsetup_module():print("准备测......
  • python初学笔记
    1.python的安装通过csdn上的教程进行安装以及完成了环境的调配;2.python的基础认识Python是一种解释型、高级、通用的编程语言。它由GuidovanRossum于1989年发明,并于1991年首次发布。Python的设计哲学强调代码的可读性和简洁的语法,尤其是使用空格缩进来表示代码块,而非使用大......
  • Python语言基础(二):注释、保留字与标识符、变量
    前言:精通一门编程语言的不二法门,在于深刻理解并掌握其基础,并通过实践来不断磨砺技能,正如那句老话所说:“熟能生巧”。从本章起,我们将正式启程,踏上Python开发的奇妙之旅,一同感受Python带来的简洁与乐趣。在本章中,我们将逐步揭开Python的神秘面纱,内容包括但不限于:注释:了解如何在代......
  • python基于大数据的电商行业产品评价系统
    收藏关注不迷路!!......
  • 频谱分析—Python代码
    下面是一个用python进行频谱分析的代码案例。importmatplotlib.pyplotaspltimportnumpyasnpdefmyfft(signal,fs):'''FFT变换,用于频谱分析:paramsignal:type:ndarray,shape:(n,):paramfs:采样频率,Hz:paramone_side(default:......
  • 时频分析—连续小波变换python代码实现
    连续小波变换python代码实现:importmatplotlib.pyplotaspltimportnumpyasnpimportpywtdefMyCWT(y,fs,wavelet='cmor2-5',total_scal=512):'''连续小波变换CWT:paramy:信号,nnumpy,(n,):paramfs:采样频率:paramwavelet:复小......
  • Python+OpenCV系列:绘制图形和文字
    绘制图形和文字1.基本绘图函数简介2.绘制示例代码2.1创建一个空白图像2.2绘制基本图形1.绘制直线2.绘制矩形3.绘制圆4.绘制椭圆2.3添加文字3.显示与保存图像4.扩展应用总结在图像处理领域,绘制基本图形和文字是一个常见需求,例如标记对象、绘制边界框或添......
  • 基于微信平台健身小助手小程序的设计与实现【java或python】-计算机毕业设计源码+LW文
    选题的意义与目的网络和科技的进步以及人们生活条件的提高都让微信小程序越来越平民化,深入日常生活中。我国非常强调体育以及健身,需要不断的让更多人参与到健身中,因为健身不仅可以锻炼身体,也能锻炼意志,有了健康的身体,就可以好好的努力工作,努力学习,为国家做出应有的贡献。有一......
  • Python Tkinter制作恶作剧小程序(可以发给朋友)
    跟大家分享了游戏和教程,也该来点好玩的了,今天我就来和大家分享一个让朋友无法使用电脑的程序。这个程序运行之后,你会发现电脑的屏幕被蒙上了一层白布,除了ALT+F4以外都关不掉它,如果点击电脑屏幕还会弹出:程序:importtkinterastkfromtkinterimportmessageboxdefshow_......