邪门,python寄了,被迫换用cpp

原来写了个py脚本,用于重命名从qq音乐下载的米哈游的音乐(ogg、mp3、flac均有)

Python
import os
import re

# QQ音乐
prog = re.compile(r"(.*\\)(.*) - (.*)( \[.*\])(\..*)")

# 网易云
# prog = re.compile(r"(.*) - (.*)")


def concat(*args, sep=""):
    return sep.join(args)


for curDir, dirs, files in os.walk(r"D:\LanguageLearning\python\music"):
    for file in files:
        # qq
        if file[-5:] == "].mp3" or file[-5:] == "].ogg" or file[-6:] == "].flac":
            # 网易
            # if (file[-3:] not in {"exe",".py","cmd","bat"}):
            name = os.path.join(curDir, file)
            # print(name)
            # print("")
            # QQ
            new_name_list = prog.split(name, 5)
            new_name = (
                '"' + concat(new_name_list[1], new_name_list[3], new_name_list[5]) + '"'
            )
            name = '"' + name + '"'
            print(new_name)
            # ---
            # 网易
            # new_name_list = prog.split(name, 2)
            # print(new_name_list)
            # new_name = os.path.split(os.path.realpath(__file__))[0] + "\\" + new_name_list[2]
            try:
                # qq
                os.renames(name, new_name)
                # 网易
                # os.renames(name, new_name)
            except:  # noqa: E722
                os.remove(name[1:-1])
            print("Success!", end="\n\n")

邪门玩意,明明py3.10都没问题的,也没看着py更新了与文件重命名相关的函数啊,怎么自从更新到py最新版本3.12突然就寄了……

典型表现就是运行到一半突然卡住,也不报错,也不提示,也不崩溃。任务管理器python占用cpu:0%-0.4%(在我的电脑上很正常),内存长久无变化,调试了一番硬是没发现问题出在哪里……明明一切都完美运行,然后突然毫无征兆的,进度就卡死在那里了……真是邪门……

正好我对Python的运行效率头大很久了,Rust又不太会用,于是直接决定用自己不怎么熟悉的cpp直接重写一个……

得益于Cpp糟糕max的UTF8(CJK)支持,我从昨晚12点写到3点,又从今天11点写到刚刚终于弄明白了……本来咱就不咋会cpp,还得想着做CJK兼容……真是服了cpp了

好在最后自己憋出来了……累死,AI用了吗?如用。解答的东西狗屁不通,思路都不对……

放小众留个痕迹吧,万一以后我手贱删了还有地方找,也懒得上传github了

cpp
#include "locale.h"
#include "wchar.h"
#include <filesystem>
#include <iostream>
#include <regex>
#include <string>
#include <windows.h>
using namespace std;
using namespace filesystem;

int main() {
  /*Set local of the rigion to avoid error caused by wrong coding*/
  std::setlocale(LC_ALL, "en_US.utf8");
  /*Init file path*/
  path file_dir = L"(D:\\LanguageLearning\\python\\music)";
  if (!exists(file_dir))
    exit(1);
  /*Start regex*/
  wsmatch regex_result_group;
  wregex reg(L"(.*\\\\)(.*) - (.*)( \\[.*\\])(\\.)(ogg|mp3|flac|wav)");
  /*Start to enum the dir*/
  directory_iterator list(file_dir);
  for (auto &it : list) {
    wstring temp = it.path().wstring();
    /*if path could match the regex, set the new_name*/
    if (regex_search(temp, regex_result_group, reg)) {
      wstring result = regex_result_group[1].str() +
                       regex_result_group[3].str() + L"." +
                       regex_result_group[6].str();
      wstring original_name = regex_result_group[0].str();
      wstring brief_name = regex_result_group[3].str();
      /*Try to rename*/
      if (0 != _wrename(original_name.c_str(), result.c_str())) {
        wcout << "Error" << brief_name << endl;
      } else {
        wcout << "Successfully! " << brief_name << endl << endl;
      }
    }
  }
  return 0;
}

(注:编译参数:-std=c++23 -Wall -Werror -O3 -pthread,请使用std23版本)


二编:

并且终于在大佬的分析下整明白了python为啥会崩(但是还是说上个版本没崩 :wl:),大抵就是错误的引号导致的rename和remove函数崩溃

在ai的帮助下改用rust全面重写,彻底解决了cpp的中文传参问题(我就没整明白过)


三编

Rust重写了一点点,增加了一点奇怪的路径支持功能

四编,改了rust的一点点冗余

main.rs
use regex::Regex;
use std::{env, fs, path::PathBuf};

fn main() {
    let mut args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        println!(
            "\nRegex QQMusic Renamer    version 1.6\n\nUsage:\n{} <directory>\n{} <files>\n",
            &args[0], &args[0]
        );
        return;
    }

    let input_path: PathBuf = PathBuf::from(&args[1]);
    let (input_is_dir, checked_path) = check_path_dir(input_path);
    if input_is_dir {
        println!("Enum {:?} ...", checked_path);
        for files in fs::read_dir(&checked_path.as_path()).unwrap() {
            rename_files(&files.unwrap().path());
        }
    } else {
        args.remove(0);
        for i in args {
            rename_files(&PathBuf::from(&i));
        }
    }
}

fn check_path_dir(mut path_to_check: PathBuf) -> (bool, PathBuf) {
    path_to_check = if path_to_check.ends_with("\"") {
        PathBuf::from(path_to_check.to_string_lossy().strip_suffix("\"").unwrap())
    } else {
        path_to_check
    };

    if !path_to_check.exists() {
        panic!("{:?} does not exist.", path_to_check.to_str());
    }

    (path_to_check.is_dir(), path_to_check)
}

fn get_new_name(regex: Regex, original_name: &str) -> &str {
    regex
        .captures(original_name)
        .unwrap()
        .get(2)
        .unwrap()
        .as_str()
}

fn rename_files(each_path: &PathBuf) {
    let reg = Regex::new(r"(.*) - (.*)( \[.*)").unwrap();
    //File name example: "HOYO-MiX - 枫丹 Fontaine [qmmc2].flac"
    if !each_path.is_dir() {
        let ext_list = ["ogg", "mp3", "flv", "flac", "wav", "mgg2", "mgg", "mflac"];
        let file_name = each_path.file_stem().unwrap().to_str().unwrap();
        let file_ext = each_path.extension().unwrap().to_str().unwrap();
        let dir_path = each_path.parent().unwrap().to_str().unwrap();

        if ext_list.contains(&file_ext) && reg.is_match(file_name) {
            let new_file_name = get_new_name(reg, file_name);
            let new_file_path =
                PathBuf::from(dir_path).join(new_file_name.to_string() + "." + file_ext);
            fs::rename(each_path, new_file_path).unwrap();
            println!("Renamed {} to {}", file_name, new_file_name);
        }
    }
}
Cargo.toml
[package]
name = "_regex_qqmusic_renamer"
version = "1.3.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[profile.release]
lto = true
codegen-units = 1
opt-level = "z"
strip = true


[dependencies]
regex = { version = "1.10.2" }
winapi = { version = "0.3.9" }

如果有大神能明白python为啥寄了也行,疑惑很久很久了……

典型路径:
D:\LanguageLearning\python\music\HOYO-MiX - 枫丹 Fontaine [qmmc2].flac

你的 Python 脚本某种程度上来说有点难以形容,首先不要用 os.walk 方法了,和路径相关的方法应该用 Path 对象来处理,里面的 rglob 专用于搜索某个路径下的文件。

其次判断文件也应该是 Path.suffix == “.mp3” 这种模式,哪怕真的要按你的这个匹配模式也应该是正则表达式的 re.search(r’.mp3$') 的逻辑,或者字符串的 “.mp3” in file 的方式,总之完全不应该考虑字符串切片样式……

from pathlib2 import Path

呃,用其它语言也是条路子,起码如果你用 Python , 您应该过不了我的面试

1 个赞

大哥,我是个从未选修过计算机,从未接触过竞赛的普通医学专业学生啊……要是我能通过面试,那后面发生什么我都不敢想 :xk:

可以试试下面两个软件:

应该满足你的需求,就不用手撸脚本了……

这类需求其实用 py 就足够了,不用强行上 cpp 的,没有科班基础学 cpp 事倍功半……
然后 py 可以不用追更新,稳定用就一直用,建议一个功能脚本给它弄一个虚拟环境,杜绝系统环境带来的一些奇奇怪怪的问题
你正好稍微重新优化一下脚本应该也不用像用cpp花那么多时间

=====

我用你提供的典型路径试了试应该是没问题的
猜测可能是碰到哪个路径了没法处理吧…?按理说用 print 大法可以观察卡在哪个语句啊

因为非科班出身所以说也没看明白怎么优化py啊,大悲……这是一个死结

呃,你都更新 3.12 了…(不要随便更新啊,看看更新内容啊
几个点:

  1. 廖雪峰,面向小白基础的基础知识。在上面看看自己还有哪些不了解的语言特性,主要就是查阅起来比较方便,偶尔我有些记不太咋用了会去看看。

  2. cookbook,实用经验技巧。比较全面了,告诉你细节方面的最佳实践,自己编程时的逻辑功能等多参考上面的建议。不过这个是几年前写的了,有些内容随着版本更新会有更优实践。

  3. 官方文档,备查,主要看各种标准库的示例, 更新大版本时有什么新变化。有些版本更新会带来一些实用的新语法糖,写起来更优雅性能更好,比如 3.4 的 pathlib、3.6 的 f-string、3.7 的 dataclasses 等等,写新的脚本的时候就尽量用这些新语法。

  4. 大佬文章,我一般是更新的时候知乎V2EX这类地方随手搜一下看看有没有博客文章,学习的同时可以顺便关注一些比较优质的博客。

=========

举例来说:

if file[-5:] == "].mp3" or file[-5:] == "].ogg" or file[-6:] == "].flac",目的是判断文件类型,假如我这方面不熟(其实 glob 已经挺经典了,用过就会有印象),放狗搜索大概浏览一下各内容并综合分析,很容易就知道现在有 pathlib 标准库,然后就是fp = pathlib.Path(file)if fp.suffix in ['.mp3', '.ogg', '.flac'],就很简洁直观。包括后面用到的路径拼接、替换新文件名等等……

1 个赞

我也是自学py的,很早之前学的,基本也是要用了就现学,写东西时看到了新东西就看例子学着用,但始终感觉是代码的复制粘贴,没有长进。
话说有没有人推荐高质量的python教程,不用像字典一样解释所有概念、函数那样的,也不用太高深的知识,又有编程思维/规范/经验,这样实用的?又或者是分析高质量代码,说一说设计思路、优点这样的?

改名可以用我的改名小工具试试 :grinning:
https://abc100.net/106/

以前也有很长一段时间喜欢玩Python,后来吧,发现这玩意就是个可以闲暇之余玩的泥巴,不要因为用的人多就跟风,要有自己的判断。。。最大的问题就是写好了程序千万别更新环境或者版本。。。所以我现在写程序宁可用C#或者已经忘得差不多了的C++,反正大不了用ChatGTP4把C#转成C++,或者反过来转。。。

def log_info(msg: str):
    print(f"【{datetime.datetime.now()}】【日志】{msg}")


def trans_music_suffix(dir_path: Path, suffix: str):
    for _file in dir_path.glob(f"**/*{suffix}"):
        try:
            _new_name = "XXXXXXXXXXX"
            _file.rename(_new_name)
            # 如果想直接覆盖同名文件,使用以下代码
            # _file.replace(_new_name)
        except FileExistsError as er:
            log_info(f"{_file.name}重命名失败,因为目标文件已经存在")
        else:
            log_info(_file.name)


def main(dir_path: str):
    deal_suffix = (".mp3", ".ogg", ".flac")
    dir_path_obj = Path(dir_path)
    if not dir_path_obj.is_dir():
        raise NotADirectoryError(f"【错误】{dir_path}不是一个有效的目录")

    for _suffix in deal_suffix:
        log_info(f"开始处理{_suffix}格式")
        trans_music_suffix(dir_path_obj, _suffix)


if __name__ == '__main__':
    from argparse import ArgumentParser

    cmd = ArgumentParser(description="重命名工具")
    cmd.add_argument("dir", type=str, help="待处理的文件夹")
    args = cmd.parse_args()
    main(args.dir)

写了一个小脚本,供楼主参考一下。已经在3.11版本下正常运行通过,使用了pathlib来操作路径。不知道为啥有层主不推荐os.walk,但是在pathlib下确实没有找到直接与os.walk等价的方法。
另外,你的原脚本确实让人看不懂,写代码的一个重要点就是代码的可读和可维护。python的易读性算是很不错的,可以尝试再联系一下python基本功。

在 3.12 增加的 pathlib.Path.walk 功能
这样感觉基本完全替代 os.path 了

说实话,对非科班出身的我已经完全超纲了……至今我也没能整明白py的类到底是啥玩意,全都是函数实现……

以及,你似乎漏了最重要的正则(

令我奇怪的是rename那一步出的问题,遍历完全没问题,就在rename的时候奇怪的卡住了……

我就是因为看不懂你的正则在做什么,所以留了new_name给你发挥了

如果是在rename步骤卡住了,就需要debug一下,看看文件的状态了。

原来如此。python的标准库推陈出新,很多教程都还在用老的语法,当然也是最稳的语法。我在工作上还在用3.6你敢信?

巧了,我除了你的rename,其他基本都没看明白 :wl: 尤其不明白一堆以"_"开头的变量是要干啥……果然没经过正规教学还是不大行啊……

那估计就是遇到非法路径了吧……我比较纳闷的是你说没报错信息
不知道你是怎么调试的,如果在终端里执行出错就会马上打印的啊