发布于 ,更新于 

Bad Apple:ALAC 音乐完整性验证与速查指南

背景

本文是对频道一段时间前的发送的这条推文的整理与补充,方便追踪、讨论与评论区拍砖:

在大约今年5月份之后,Apple Music 某个节点上的无损音乐编码器似乎坏掉了,导致他们提供的音乐源文件就是爆的,相比于其他来源会出现丢失采样的问题。根据社区的实验,内录官方客户端并分析采样,会发现问题依然存在,因此该问题似乎与社区中热门的第三方下载实现无关

叠甲在先:

  1. 尽管 Apple Music 的下载方案已从去年 DRM 被公开破拆之后成为马奇诺防线,并可在多个国内平台看到教程,但本文并不提供任何下载指导,也不鼓励任何形式的侵权行为。
  2. 本文的方法论建立在对几张典型专辑的实际测试之上,我并非 Apple Music 的利益相关者,我甚至并不知道当前的情况是 Apple Music 官方的编码器压坏了,还是 Apple 故意为之,推出了某个很新的妙妙 DRM 方案。众所周知,Apple Music 的编码器会对音频进行一些特殊处理(比如将部分数字母带™的响度调高),如果这次发现的音频问题在内录后依然存在,那么我认为它就不应被归类为 DRM,而是一种事实上的音频水印

现象

正文开始,以下是编写时发现仍有问题的专辑,你可以用来进行测试:

基于如上的叠甲,本文不会讨论更多的原理,只说对速查有用的现象:

  1. 出问题的音乐大多数是在今年(即 2025 年)之后被 Apple 重编码,通过一个现代的播放器(如 Potplayer)你可以在音频文件的 Encoded date 里找到 2025 年之后的日期。
  1. 如果使用带有校验功能的下载工具,会发现这些文件无法通过测试。使用 foobar2000 等第三方软件进行验证完整性,也无法通过:

使用 foobar2000 内置的 FLAC 转换也会报错:

  1. 使用 ffmpeg 进行转码时会报错,根据你不同的 ffmpeg 版本,报错通常会是如下的其中一种或多种:
  • Invalid data found when processing input
  • Not yet implemented in FFmpeg
  • Syntax element
  • patch welcome / patches welcome

其中,对于一大组文件,我认为使用 ffmpeg 进行检测是最为方便的方法,尤其是当你需要批量处理时。你可以使用以下命令来模拟:

1
ffmpeg -i "m4a文件" -f wav -hide_banner -loglevel info NUL 2>&1

以如上的 TOGENASHI TOGEARI - Fragile Violet - Single 为例,输出会是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Stream #0:0(und): Audio: pcm_s16le ([1][0][0][0] / 0x0001), 44100 Hz, stereo, s16, 1411 kb/s (default)
Metadata:
encoder : Lavc62.13.101 pcm_s16le
creation_time : 2025-06-23T04:06:21.000000Z
handler_name : Fragile Violet
vendor_id : [0][0][0][0]
[alac @ 00000289a4f48580] invalid element channel count
[aist#0:0/alac @ 00000289a2cc8fc0] [dec:alac @ 00000289a2cc9180] Error submitting packet to decoder: Invalid data found when processing input
[alac @ 00000289a2cee6c0] invalid element channel count
[alac @ 00000289a4f48580] invalid element channel count
[aist#0:0/alac @ 00000289a2cc8fc0] [dec:alac @ 00000289a2cc9180] Error submitting packet to decoder: Invalid data found when processing input
Last message repeated 1 times
[alac @ 00000289a4f79a40] Syntax element 4 is not implemented. Update your FFmpeg version to the newest one from Git. If the problem still occurs, it means that your file has a feature which has not been implemented.
[aist#0:0/alac @ 00000289a2cc8fc0] [dec:alac @ 00000289a2cc9180] Error submitting packet to decoder: Not yet implemented in FFmpeg, patches welcome
[out#0/wav @ 00000289a2c23080] video:0KiB audio:32168KiB subtitle:0KiB other streams:0KiB global headers:0KiB muxing overhead: 0.000929%
size= 32168KiB time=00:03:07.10 bitrate=1408.4kbits/s speed=3.54e+03x elapsed=0:00:00.05

速查工具

出于验证的需求,我找 Gemini 生了个批处理脚本并改了改,可以用于批量校验这些文件。 你可以直接下载使用,或是在附录找到源代码,自己看一眼再跑。在运行脚本时请确保您的环境中已安装并配置好 ffmpeg,并且可以在命令行中直接调用。

验证

此前的发现在社区内引发了广泛讨论,其中不乏有观点认为这是 Apple Music 的一种防盗版机制。为了厘清这一问题,本段将提供一份客户端内录的验证记录,让每位读者都能亲手检验——这些音频瑕疵究竟是源于 DRM 保护,还是 Apple 自身的编码器缺陷。
在动手前,请务必再次阅读以下声明:首先,出于版权考量,本文不会涉及任何下载教学,我们假设您已合法拥有需要验证的音乐。其次,我们判断的逻辑很简单,也已在上文中叙述:如果所谓的“问题”在内录后仍然存在,那它就和 DRM 无关了,而是一种被编码进音频里的“事实水印”,是该和国内的流媒体坐一桌的。

此处非常感谢 @TheM14 老师进行了严谨的测试,为本文提供了非常重要的参考依据。

准备工作

首先,我们需要证明内录的结果是正确的,这里需要提前进行以下处理:

  1. 寻找一个内录通道,这里使用的是 Minifuse 声卡驱动自带的内录通道(主要是方便),像 VoiceMeeter 的虚拟内录通道也是可以的。
  2. 将输出的采样率和位深设置成和原曲相同。
  3. 干掉 Windows 自带的 CAudioLimiter。 这是 Windows 操作系统音频引擎中一个内置的音频处理组件。它的主要功能是作为一个峰值限制器(limiter),防止音频信号在输出时超过最大电平(0 dBFS),从而避免产生削波(clipping)失真。但为了忠实地还原音频,我们需要确保录制的音频不经过任何额外处理。你可以参考 B 站上的 BV1GPNezjEsM 来干掉它。(在进行任何有关注册表的操作前,请手动备份注册表)

内录与验证

接下来进行内录,此处以「結束バンド」的「僕と三原色」这首歌为例。

  1. 先在 Apple Music 的 Windows 客户端中寻找对应的歌曲并进行下载(以免网络不良的情况下播放成 lossy 的 AAC).
  2. 使用内录通道配合 Reaper 内录(录制的时候要设置好工程的采样率),之后导出 48/24 (与已知采样率一致)的 WAV .

在进行后续对比之前,我们必须首先确保录音能够精确还原 Apple Music 输出的原始音频流。我们可以使用 foobar2000 的 Binary Comparator 组件(下称 bitcompare)来进行对比,你可以在 foobar2000 的组件库 中找到它并安装,请注意一下这个组件只有 32 位格式。
此处,我们手里有三份音源,一份是通过工具下载解密 DRM 获得的 ALAC(decrypted.m4a),一份是内录的 WAV(recorded.wav),另一份是通过 MORA 自购下载的无损 FLAC(mora.flac)。

首先将 decrypted.m4amora.flac 进行比较:

此处 bitcompare 没有找到差别,证明了下载解密工具本身没有问题,两个来源的音源是一致的。

接下来对比 mora.flacrecorded.wav

注意到 bitcompare 的提示:
Differences found in compared tracks; the tracks became identical after applying offset and truncating first/last samples.
Extra leading/trailing sections contained only null samples.

我们显然不能在内录的时候做到和原曲完全同步,因此 bitcompare 通过对齐和裁剪的方式来忽略掉了前后多余的采样点(事实上全是空的),然后发现中间的部分是完全一致的
通过如上的对比,我们可以得出结论:内录是没问题的,解密也是没问题的。因为可以和 MORA 自购下载的文件对齐每个采样点的数值,如果不是完美解密这根本不可能。

问题专辑

接下来,我们再来对比一下已知有问题的蔡依林「Pleasure」专辑里的 Safari,首先我们使用解密工具下载一版,然后尝试使用 foobar2000 来进行完整性验证:

为了排除发行方对不同渠道的影响,此处从 Tidal 和 Amazon Music HD 也分别购买下载了同一首歌的同音质音频,并进行 bitcompare 测试:

结果是一模一样且无错误的,可以证明并非发行商的问题。

然后参照上述的步骤对这首歌进行内录,然后直接使用 Adobe Audition 与来自 Amazon/Tidal 的音源进行反相相消比较,可以发现右声道似乎出现了一个孤零零的音频信号

这里就是 Apple 经过内录和 Amazon/Tidal 的音源不同的地方了,此处出现了一个采样值的错误,这个错误很有可能由于解码器的不支持,导致直接丢弃这个数据块,从而从一个采样点的错误衍生到 4096 个采样点的错误(一个 block)。

验证就到这里,我们可以得出结论:Apple Music 的无损音源确实存在问题,这个问题在内录后依然存在。出于版权考量,本文不会提供相关的源文件下载,读者可以参考上述的步骤自行实验。

结语

所以从音乐收藏和发布的角度来说,我们可以得出什么结论?

  1. Apple Music 的无损音源现在已经不再能和 Amazon 和 Qobuz 一样可信(这应该会给各路喜欢用 Apple Music 搞视听的 HiFi 爱好者一记闷棍),从各路来源获取的时候应该格外谨慎。(实际上真正应该得到的教训是从多个来源交叉验证)
  2. 无论来源如何,任何 ALAC 文件,尤其是已被转码为 FLAC 的文件,都应被视为潜在的污染源尤其是已被转码过的 FLAC 文件,因为在转码完毕后他们的编码错误被掩盖了。
  3. 务必优先采用亲手获取、来源清晰的音频。切勿信赖第三方的链接解析站或下载机器人,因为它们分发的文件缺乏来源保证(有的甚至在静默转码到 FLAC 的时候忽略了上述问题),可能继承了原始文件存在的所有风险。
  4. 如果你正在使用社区里比较流行的下载实现,请务必把他们提供的完整性验证功能打开(原理实际上和本文是类似的)。在排除了网络、账户等个人因素后,若校验持续失败,则应判定 Apple Music 的官方音源本身已损坏。此时,请果断放弃该来源,以避免收藏或传播有问题的音频。

本文提供的方法与结论仅供参考。此方法可有效识别并排除部分存在问题的专辑,但无法保证通过验证的专辑绝对无误。若您获取了 Apple Music 的 ALAC 音频文件,最可靠的验证方式仍是与其他无损且已知无音频水印与后处理的音源(如通过对比未压缩音轨的 MD5 哈希值)进行交叉比对。在撰写本文时,我对一些文件进行了此类交叉验证,目前尚未发现响度匹配而 MD5 值不同的案例,欢迎评论区补充拍砖。  

附录

以下是速查脚本的源代码,理论上与可以直接下载的版本一致,你可以自行验证与修改:

Windows 批处理脚本

请注意:因为批处理中使用 CHCP 65001 设置了 UTF-8 代码页,请保证你在保存时选择使用 UTF-8 编码。如果你不知道这是什么且在运行的时候遇见了编码错误(比如乱码),请把下列代码中的 CHCP 65001 >NUL 这一行删除。

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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION

REM https://blog.hanlin.press/2025/09/ALAC-Verification-Guide/
REM --- 脚本初始化 ---
REM 切换到 UTF-8 代码页(65001)以正确显示和处理中文字符
CHCP 65001 >NUL

REM --- 获取当前日期和时间 (格式: YYYY-MM-DD 和 HH-MM-SS),使用 WMIC 以避免区域设置问题 ---
FOR /F "tokens=2 delims==" %%I IN ('wmic os get LocalDateTime /value') DO SET "dt=%%I"
SET "CURRENT_DATE=%dt:~0,4%-%dt:~4,2%-%dt:~6,2%"
SET "CURRENT_TIME=%dt:~8,2%-%dt:~10,2%-%dt:~12,2%"

REM 定义报告文件的基础名和完整路径
SET "report_basename=ALAC_Scan_Report_%CURRENT_DATE%_%CURRENT_TIME%.txt"
SET "report_file=%~dp0%report_basename%"
SET "temp_log=%TEMP%\ffmpeg_scan_error.log"

REM 初始化计数器
SET /A problem_count=0
SET /A total_files_scanned=0

ECHO.
ECHO =======================================================
ECHO == ALAC 编码问题扫描工具 20250911 ==
ECHO =======================================================
ECHO.

REM --- 环境检查: FFMPEG ---
WHERE ffmpeg >NUL 2>NUL
IF %ERRORLEVEL% NEQ 0 (
ECHO [错误] 未在您的 PATH 中找到 FFMPEG。
ECHO 请先安装 FFMPEG 并确保可以从命令行调用它。
PAUSE
GOTO :EOF
)

REM --- 准备报告文件 ---
IF EXIST "%report_file%" DEL "%report_file%"
ECHO ALAC 文件扫描报告 > "%report_file%"
ECHO 生成时间: %DATE% %TIME% >> "%report_file%"
ECHO ====================================================== >> "%report_file%"

REM --- 判断执行模式 (拖放或直接运行) ---
IF "%~1"=="" (
ECHO [模式] 未拖放文件或文件夹,将递归扫描当前目录。
ECHO.
CALL :ScanDirectory "."
) ELSE (
ECHO [模式] 检测到拖放操作,将处理指定的文件或文件夹。
ECHO.
FOR %%A IN (%*) DO (
IF EXIST "%%~A\" (
CALL :ScanDirectory "%%~A"
) ELSE (
IF /I "%%~xA"==".m4a" (
CALL :ProcessFile "%%~A"
) ELSE (
ECHO [跳过] "%%~nxA" 不是 M4A 文件。
)
)
)
)

REM --- 扫描结束,生成总结 ---
ECHO.
ECHO --------------------------------------------------
ECHO.
ECHO 扫描完成!

ECHO 总共扫描了 !total_files_scanned! 个文件。

SET "summary="
IF %problem_count% == 0 (
SET "summary=结果: 未发现任何存在编码问题的文件。"
ECHO !summary!
) ELSE (
SET "summary=结果: 共发现 !problem_count! 个文件存在编码问题。"
ECHO !summary!
)

REM 将总结也写入报告文件底部
ECHO. >> "%report_file%"
ECHO ====================================================== >> "%report_file%"
ECHO 扫描总结: >> "%report_file%"
ECHO 总共扫描了 %total_files_scanned% 个 M4A 文件。 >> "%report_file%"
ECHO %summary% >> "%report_file%"
ECHO ====================================================== >> "%report_file%"

:AfterChecksum
ECHO 详细的综合报告已生成:
ECHO "%report_file%"
IF %problem_count% GTR 0 (
ECHO.
ECHO 因为发现了问题文件,正在为您自动打开报告...
START "" "%report_file%"
)

ECHO.
PAUSE
GOTO :EOF


REM --- 子程序: 扫描整个目录 (递归) ---
:ScanDirectory
SET "target_dir=%~1"
ECHO --- 正在递归扫描文件夹: "%target_dir%" ---
FOR /R "%target_dir%" %%F IN (*.m4a) DO (
CALL :ProcessFile "%%F"
)
GOTO :EOF


REM --- 子程序: 处理单个文件 ---
:ProcessFile
SET "current_file=%~1"
SET /A total_files_scanned+=1
ECHO.
ECHO [!total_files_scanned!] 正在检查: "!current_file!"

REM 核心逻辑: 运行 ffmpeg -i,将所有输出(包括分析和错误)都重定向到临时文件
ffmpeg -i "!current_file!" -f wav -hide_banner -loglevel info NUL 2> "%temp_log%"

REM 在临时文件中查找指定的错误字符串
findstr /C:"Invalid data found when processing input" /C:"Not yet implemented in FFmpeg" /C:"Syntax element" /C:"patch welcome" /C:"patches welcome" "%temp_log%" >NUL

ECHO. >> "%report_file%"
ECHO ---------------------------------------- >> "%report_file%"
ECHO [文件] "!current_file!" >> "%report_file%"

IF !ERRORLEVEL! == 0 (
REM 发现问题
SET /A problem_count+=1
ECHO [状态] 发现问题!

REM --- 将详细错误信息写入报告文件 ---
ECHO [状态] 发现问题! >> "%report_file%"
ECHO [详情] FFmpeg 完整输出: >> "%report_file%"
TYPE "%temp_log%" >> "%report_file%"

) ELSE (
REM 文件正常
ECHO [状态] 正常
ECHO [状态] 正常 >> "%report_file%"

REM --- 提取音频流信息并输出 ---
FOR /F "tokens=*" %%L IN ('findstr " Audio:" "%temp_log%"') DO (
REM 在控制台输出单行分析
ECHO [信息] %%L
REM 将单行分析写入报告
ECHO [信息] %%L >> "%report_file%"
)
)

REM 清理临时日志文件
IF EXIST "%temp_log%" DEL "%temp_log%"
GOTO :EOF

Bash 脚本

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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#!/usr/bin/env bash

# https://blog.hanlin.press/2025/09/ALAC-Verification-Guide/
set -o pipefail
export LC_ALL=${LC_ALL:-C.UTF-8}
export LANG=${LANG:-C.UTF-8}
CURRENT_DATE=$(date +%Y-%m-%d)
CURRENT_TIME=$(date +%H-%M-%S)
script_dir=$(cd "$(dirname "$0")" && pwd)
report_basename="ALAC_Scan_Report_${CURRENT_DATE}_${CURRENT_TIME}.txt"
report_file="${script_dir}/${report_basename}"
temp_log=$(mktemp -t ffmpeg_scan_error.XXXXXX.log)

problem_count=0
total_files_scanned=0

echo
echo "======================================================="
echo "== ALAC 编码问题扫描工具 20250911 =="
echo "======================================================="
echo

if ! command -v ffmpeg >/dev/null 2>&1; then
echo "[错误] 未在 PATH 中找到 ffmpeg"
exit 1
fi

: > "$report_file"
{
echo "ALAC 文件扫描报告"
echo "生成时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "======================================================"
} >> "$report_file"

append_report() {
printf '%s\n' "$@" >> "$report_file"
}

process_file() {
local file="$1"
total_files_scanned=$((total_files_scanned + 1))
echo
echo "[$total_files_scanned] 正在检查: \"$file\""
: > "$temp_log"
ffmpeg -i "$file" -f wav -hide_banner -loglevel info -y /dev/null 2> "$temp_log"

if grep -F -e "Invalid data found when processing input" \
-e "Not yet implemented in FFmpeg" \
-e "patch welcome" \
-e "patches welcome" \
-e "Syntax element" "$temp_log" >/dev/null 2>&1; then
problem=true
else
problem=false
fi

append_report ""
append_report "----------------------------------------"
append_report "[文件] \"$file\""

if $problem; then
problem_count=$((problem_count + 1))
echo " [状态] 发现问题!"
append_report "[状态] 发现问题!"
append_report "[详情] FFmpeg 完整输出:"
cat "$temp_log" >> "$report_file"
else
echo " [状态] 正常"
append_report "[状态] 正常"
if grep -F " Audio:" "$temp_log" >/dev/null 2>&1; then
while IFS= read -r line; do
echo " [信息] $line"
append_report "[信息] $line"
done < <(grep -F " Audio:" "$temp_log")
fi
fi
}

scan_directory() {
local target_dir="$1"
echo "--- 正在递归扫描文件夹: \"$target_dir\" ---"
while IFS= read -r -d '' f; do
process_file "$f"
done < <(find "$target_dir" -type f -iname '*.m4a' -print0)
}

if [[ $# -eq 0 ]]; then
echo "[模式] 未提供参数,将递归扫描当前目录。"
echo
scan_directory "."
else
echo "[模式] 检测到传入参数,将处理指定的文件或文件夹。"
echo
for path in "$@"; do
if [[ -d "$path" ]]; then
scan_directory "$path"
elif [[ -f "$path" ]]; then
ext="${path##*.}"
ext="${ext,,}"
if [[ "$ext" == "m4a" ]]; then
process_file "$path"
else
echo "[跳过] \"$(basename "$path")\" 不是 M4A 文件。"
fi
else
echo "[跳过] \"$path\" 无法访问。"
fi
done
fi

echo
echo "--------------------------------------------------"
echo
echo "扫描完成!"
echo "总共扫描了 $total_files_scanned 个文件。"

if [[ $problem_count -eq 0 ]]; then
summary="结果: 未发现任何存在编码问题的文件。"
else
summary="结果: 共发现 $problem_count 个文件存在编码问题。"
fi
echo "$summary"

append_report ""
append_report "======================================================"
append_report "扫描总结:"
append_report "总共扫描了 ${total_files_scanned} 个 M4A 文件。"
append_report "$summary"
append_report "======================================================"

echo "详细的综合报告已生成:"
echo "\"$report_file\""

if [[ $problem_count -gt 0 ]]; then
if command -v xdg-open >/dev/null 2>&1; then
xdg-open "$report_file" >/dev/null 2>&1 &
elif command -v open >/dev/null 2>&1; then
open "$report_file" >/dev/null 2>&1 &
fi
fi

rm -f "$temp_log"
echo

PowerShell 7 脚本

Nullpointer 提供,支持多线程处理,适合大批量文件的扫描。

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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#requires -Version 7.0
param(
[Parameter(Position = 0)]
[string[]]$Path
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

if (-not (Get-Command ffmpeg -ErrorAction SilentlyContinue)) {
Write-Host "[错误] 未在 PATH 中找到 ffmpeg,请先安装。" -ForegroundColor Red
exit 1
}

function Get-M4AFiles {
param([string[]]$Paths)

if (-not $Paths -or @($Paths).Count -eq 0) {
Write-Host "[模式] 未提供参数,将递归扫描当前目录。"
return Get-ChildItem -Path '.' -Filter '*.m4a' -File -Recurse -ErrorAction SilentlyContinue
}

Write-Host "[模式] 处理指定的文件/文件夹。"
$list = New-Object System.Collections.Generic.List[System.IO.FileInfo]
foreach ($p in $Paths) {
$resolved = (Resolve-Path -LiteralPath $p -ErrorAction SilentlyContinue)?.Path
if (-not $resolved) { Write-Host "[跳过] 找不到路径:$p"; continue }
if (Test-Path -LiteralPath $resolved -PathType Container) {
Get-ChildItem -LiteralPath $resolved -Filter '*.m4a' -File -Recurse -ErrorAction SilentlyContinue |
ForEach-Object { [void]$list.Add($_) }
}
elseif (Test-Path -LiteralPath $resolved -PathType Leaf) {
if ([IO.Path]::GetExtension($resolved).ToLowerInvariant() -eq '.m4a') {
[void]$list.Add([IO.FileInfo]$resolved)
}
else {
Write-Host "[跳过] `"$([IO.Path]::GetFileName($resolved))`" 不是 M4A 文件。"
}
}
}
return $list
}

$targets = @( Get-M4AFiles -Paths $Path | Sort-Object FullName )
if ($targets.Count -eq 0) {
Write-Host "未找到任何 .m4a 文件。"
exit 0
}

$stamp = (Get-Date).ToString('yyyy-MM-dd_HH-mm-ss')
$report = Join-Path $PSScriptRoot "ALAC_Scan_Report_$stamp.txt"

$ErrorPatterns = @(
'Invalid data found when processing input',
'Not yet implemented in FFmpeg',
'Syntax element',
'patch welcome',
'patches welcome'
)

"ALAC 文件扫描报告" | Out-File -LiteralPath $report -Encoding UTF8
"生成时间: $(Get-Date -Format 'yyyy/MM/dd HH:mm:ss')" | Out-File -LiteralPath $report -Encoding UTF8 -Append
"=====================================================" | Out-File -LiteralPath $report -Encoding UTF8 -Append

$maxParallel = [Math]::Max(2, [Environment]::ProcessorCount)
$total = $targets.Count
$problemCount = 0

Write-Host ""
Write-Host "======================================================="
Write-Host "== ALAC 编码问题扫描工具"
Write-Host "======================================================="
Write-Host ""

$sw = [System.Diagnostics.Stopwatch]::StartNew()

$results = $targets | ForEach-Object -Parallel {
$filePath = $_.FullName
$ErrorPatterns = $using:ErrorPatterns

$log = [System.IO.Path]::GetTempFileName()
try {
& ffmpeg -v info -hide_banner -i $filePath -f null - 1>$null 2> $log
$text = if (Test-Path $log) { Get-Content $log -Raw -Encoding UTF8 } else { "" }
$hasProblem = $false
foreach ($p in $ErrorPatterns) {
if ($text -match [Regex]::Escape($p)) { $hasProblem = $true; break }
}
$audio = @()
if ($text) {
$audio = ($text -split "`r?`n") | Where-Object { $_ -match '\bAudio:' }
}

$fileName = [System.IO.Path]::GetFileName($filePath)
Write-Host "处理完成: $fileName" -ForegroundColor Green

[pscustomobject]@{
File = $filePath
Problem = $hasProblem
Log = $text
Audio = $audio
}
}
finally {
if (Test-Path $log) { Remove-Item $log -Force -ErrorAction SilentlyContinue }
}
} -ThrottleLimit $maxParallel

$sw.Stop()

foreach ($r in $results) {
Add-Content $report ""
Add-Content $report "----------------------------------------"
Add-Content $report "[文件] `"$($r.File)`""
if ($r.Problem) {
$problemCount++
Add-Content $report "[状态] 发现问题!"
Add-Content $report "[详情] FFmpeg 完整输出:"
Add-Content $report $r.Log
}
else {
Add-Content $report "[状态] 正常"
foreach ($line in $r.Audio) {
Add-Content $report "[信息] $line"
}
}
}

Add-Content $report ""
Add-Content $report "====================================================="
Add-Content $report "扫描总结:"
Add-Content $report "总共扫描了 $total 个 M4A 文件。"
Add-Content $report ("耗时: {0:N1} 秒" -f $sw.Elapsed.TotalSeconds)
if ($problemCount -eq 0) {
Add-Content $report "结果: 未发现任何存在编码问题的文件。"
}
else {
Add-Content $report "结果: 共发现 $problemCount 个文件存在编码问题。"
}

Write-Host ""
Write-Host ("扫描完成!共 {0} 个文件,{1} 个有问题。" -f $total, $problemCount) `
-ForegroundColor ($problemCount -eq 0 ? 'Green' : 'Yellow')
Write-Host "报告位置:$report"
if ($problemCount -gt 0) {
Start-Process -FilePath $report
}