中兴F50装Openlist播放视频问题
中兴F50微NAS 解决macOS/iOS Safari无法播放视频 HEVC / HVC1 自动兼容优化脚本
介绍
这是一个面向 Root Android / 中兴 F50 存储版 / Android NAS 类设备 的视频自动优化脚本,主要解决部分 HEVC/H.265 视频在 iPhone Safari、Chrome 或 OpenList 网页端无法直接播放的问题。
仅能解决本机存储中的视频播放兼容性。
脚本会定时扫描指定存储目录中的视频文件,自动检测 HEVC 编码及封装格式。对于 hev1 MP4,会在不重新编码视频、不损失画质的前提下重新封装为 hvc1 并加入 faststart;对于 HEVC MKV,会自动生成更适合网页和 iPhone 播放的 MP4,优先保留 AAC 音轨,没有 AAC 时可自动转换为 AAC。
主要功能
自动扫描新增 MP4 / MKV 视频
HEVC hev1 → hvc1
自动添加 faststart
MKV → MP4 兼容封装
视频流默认不重新编码,避免画质损失
自动检测并跳过已经处理完成的视频
文件上传未完成时不会立即处理
转换完成后自动校验,失败时保留原文件
支持开机自启、定时后台扫描
运行环境
需要:
实现方式
HEVC MP4 + hev1→ HEVC MP4 + hvc1 + faststart
HEVC MP4 + hvc1→ 不处理
HEVC MP4 + PNG 封面流→ 只修改主视频流标签
HEVC MKV + AAC→ HEVC hvc1 MP4 + AAC
HEVC MKV + EAC3/AC3 等,没有 AAC→ HEVC hvc1 MP4 + AAC 192k
H.264 MP4→ 不处理
已经优化过的文件→ 跳过
也可以用于其他 Root 安卓设备、安卓盒子和部分 Android NAS;Linux NAS 经过路径和启动方式调整后也可以使用。
注意:它属于“封装兼容优化脚本”,不是万能转码器。DRM、损坏文件、特殊编码以及设备本身不支持的 HEVC Profile 仍可能无法播放。
脚本代码
mkdir -p /data/adb/hvc1-auto
cat > /data/adb/hvc1-auto/process.sh <<'EOF'
#!/system/bin/sh
# ============================================================
# Android HEVC / hvc1 自动兼容优化脚本
#
# 功能:
# 1. MP4:
# HEVC 且不是 hvc1 → 无损重新封装为 hvc1 + faststart
#
# 2. MKV:
# HEVC → 生成同名 MP4
# 优先复制 AAC 音轨;
# 没有 AAC 时,把第一条音轨转换成 AAC
#
# 3. 自动跳过:
# - 已经是 hvc1 的 MP4
# - 非 HEVC 视频
# - 正在写入/刚上传的文件
# - 临时文件
#
# 默认扫描目录:
# /storage/emulated/0/DCIM
#
# ============================================================
ROOT="/storage/emulated/0/DCIM"
WORK="/data/adb/hvc1-auto"
LOG="$WORK/hvc1-auto.log"
LOCK="$WORK/.lock"
# 文件最后修改至少超过多少秒后才处理
# 防止文件还没上传完就开始重新封装
MIN_AGE=120
# MKV 成功转换后是否删除原 MKV
#
# 0 = 保留原 MKV(推荐、安全)
# 1 = 校验成功后删除原 MKV
#
DELETE_MKV_AFTER_SUCCESS=0
mkdir -p "$WORK"
log()
{
echo "$(date '+%Y-%m-%d %H:%M:%S') $*" >> "$LOG"
}
# ------------------------------------------------------------
# 防止脚本重复运行
# ------------------------------------------------------------
if ! mkdir "$LOCK" 2>/dev/null; then
exit 0
fi
trap 'rmdir "$LOCK" 2>/dev/null' EXIT INT TERM
# ------------------------------------------------------------
# 检查 FFmpeg
# ------------------------------------------------------------
FFMPEG="$(command -v ffmpeg)"
FFPROBE="$(command -v ffprobe)"
if [ -z "$FFMPEG" ] || [ -z "$FFPROBE" ]; then
log "ERROR: ffmpeg / ffprobe not found"
exit 1
fi
# ------------------------------------------------------------
# 判断文件是否已经写入完成
# ------------------------------------------------------------
file_ready()
{
file="$1"
mtime="$(stat -c %Y "$file" 2>/dev/null)"
[ -z "$mtime" ] && return 1
now="$(date +%s)"
age=$((now - mtime))
[ "$age" -ge "$MIN_AGE" ]
}
# ------------------------------------------------------------
# 复制原文件权限/时间
# ------------------------------------------------------------
copy_attr()
{
src="$1"
dst="$2"
uid="$(stat -c %u "$src" 2>/dev/null)"
gid="$(stat -c %g "$src" 2>/dev/null)"
mode="$(stat -c %a "$src" 2>/dev/null)"
[ -n "$uid" ] && [ -n "$gid" ] && \
chown "$uid:$gid" "$dst" 2>/dev/null
[ -n "$mode" ] && \
chmod "$mode" "$dst" 2>/dev/null
touch -r "$src" "$dst" 2>/dev/null
}
# ------------------------------------------------------------
# MP4:hev1 / 非 hvc1 → hvc1
# ------------------------------------------------------------
process_mp4()
{
file="$1"
codec="$("$FFPROBE" \
-v error \
-select_streams v:0 \
-show_entries stream=codec_name \
-of default=noprint_wrappers=1:nokey=1 \
"$file" 2>/dev/null)"
[ "$codec" = "hevc" ] || return
tag="$("$FFPROBE" \
-v error \
-select_streams v:0 \
-show_entries stream=codec_tag_string \
-of default=noprint_wrappers=1:nokey=1 \
"$file" 2>/dev/null)"
# 已经兼容,无需再次处理
[ "$tag" = "hvc1" ] && return
tmp="${file%.*}.hvc1tmp.mp4"
rm -f "$tmp"
log "START MP4: $file"
log " codec=$codec tag=$tag"
"$FFMPEG" \
-nostdin \
-hide_banner \
-loglevel error \
-y \
-i "$file" \
-map 0 \
-c copy \
-tag:v:0 hvc1 \
-movflags +faststart \
"$tmp" >> "$LOG" 2>&1
if [ $? -ne 0 ]; then
log "FAIL MP4: ffmpeg failed: $file"
rm -f "$tmp"
return
fi
newtag="$("$FFPROBE" \
-v error \
-select_streams v:0 \
-show_entries stream=codec_tag_string \
-of default=noprint_wrappers=1:nokey=1 \
"$tmp" 2>/dev/null)"
if [ "$newtag" != "hvc1" ]; then
log "FAIL MP4: hvc1 validation failed: $file"
rm -f "$tmp"
return
fi
# 再确认文件本身可以正常读取
"$FFPROBE" \
-v error \
-show_entries format=duration \
-of default=noprint_wrappers=1:nokey=1 \
"$tmp" >/dev/null 2>&1
if [ $? -ne 0 ]; then
log "FAIL MP4: output verification failed: $file"
rm -f "$tmp"
return
fi
copy_attr "$file" "$tmp"
mv -f "$tmp" "$file"
log "OK MP4: converted to hvc1: $file"
}
# ------------------------------------------------------------
# MKV:HEVC → 浏览器兼容 MP4
# ------------------------------------------------------------
process_mkv()
{
file="$1"
codec="$("$FFPROBE" \
-v error \
-select_streams v:0 \
-show_entries stream=codec_name \
-of default=noprint_wrappers=1:nokey=1 \
"$file" 2>/dev/null)"
# 当前版本只自动处理 HEVC
[ "$codec" = "hevc" ] || return
base="${file%.*}"
out="${base}.mp4"
tmp="${base}.hvc1tmp.mp4"
# 已经有 MP4,则不覆盖
if [ -f "$out" ]; then
tag="$("$FFPROBE" \
-v error \
-select_streams v:0 \
-show_entries stream=codec_tag_string \
-of default=noprint_wrappers=1:nokey=1 \
"$out" 2>/dev/null)"
if [ "$tag" = "hvc1" ]; then
return
fi
log "SKIP MKV: target MP4 already exists: $out"
return
fi
rm -f "$tmp"
# 查找 AAC 音轨
aac_index="$("$FFPROBE" \
-v error \
-select_streams a \
-show_entries stream=index,codec_name \
-of csv=p=0 \
"$file" 2>/dev/null |
awk -F',' '$2=="aac"{print $1; exit}')"
# 查找第一条音轨
first_audio="$("$FFPROBE" \
-v error \
-select_streams a \
-show_entries stream=index \
-of csv=p=0 \
"$file" 2>/dev/null |
head -n 1)"
log "START MKV: $file"
# --------------------------------------------------------
# 情况 1:已有 AAC
# 视频和音频都直接复制,不重新编码
# --------------------------------------------------------
if [ -n "$aac_index" ]; then
log " AAC stream=$aac_index"
"$FFMPEG" \
-nostdin \
-hide_banner \
-loglevel error \
-y \
-i "$file" \
-map 0:v:0 \
-map 0:"$aac_index" \
-map_metadata 0 \
-map_chapters 0 \
-c:v copy \
-tag:v:0 hvc1 \
-c:a copy \
-movflags +faststart \
"$tmp" >> "$LOG" 2>&1
# --------------------------------------------------------
# 情况 2:没有 AAC,但存在音轨
# 视频不重编码,只把音频转成 AAC
# --------------------------------------------------------
elif [ -n "$first_audio" ]; then
log " No AAC; converting audio stream=$first_audio"
"$FFMPEG" \
-nostdin \
-hide_banner \
-loglevel error \
-y \
-i "$file" \
-map 0:v:0 \
-map 0:"$first_audio" \
-map_metadata 0 \
-map_chapters 0 \
-c:v copy \
-tag:v:0 hvc1 \
-c:a aac \
-b:a 192k \
-movflags +faststart \
"$tmp" >> "$LOG" 2>&1
# --------------------------------------------------------
# 情况 3:没有音频
# --------------------------------------------------------
else
log " No audio stream"
"$FFMPEG" \
-nostdin \
-hide_banner \
-loglevel error \
-y \
-i "$file" \
-map 0:v:0 \
-map_metadata 0 \
-map_chapters 0 \
-c:v copy \
-tag:v:0 hvc1 \
-an \
-movflags +faststart \
"$tmp" >> "$LOG" 2>&1
fi
if [ $? -ne 0 ]; then
log "FAIL MKV: ffmpeg failed: $file"
rm -f "$tmp"
return
fi
newtag="$("$FFPROBE" \
-v error \
-select_streams v:0 \
-show_entries stream=codec_tag_string \
-of default=noprint_wrappers=1:nokey=1 \
"$tmp" 2>/dev/null)"
if [ "$newtag" != "hvc1" ]; then
log "FAIL MKV: hvc1 validation failed: $file"
rm -f "$tmp"
return
fi
"$FFPROBE" \
-v error \
-show_entries format=duration \
-of default=noprint_wrappers=1:nokey=1 \
"$tmp" >/dev/null 2>&1
if [ $? -ne 0 ]; then
log "FAIL MKV: output verification failed: $file"
rm -f "$tmp"
return
fi
copy_attr "$file" "$tmp"
mv -f "$tmp" "$out"
log "OK MKV: created compatible MP4: $out"
if [ "$DELETE_MKV_AFTER_SUCCESS" = "1" ]; then
rm -f "$file"
log "DELETE MKV: $file"
fi
}
# ------------------------------------------------------------
# 主扫描
# ------------------------------------------------------------
log "===== SCAN START ====="
find "$ROOT" -type f \
\( -iname '*.mp4' -o -iname '*.mkv' \) \
-print0 2>/dev/null |
while IFS= read -r -d '' file
do
case "$file" in
*.hvc1tmp.mp4)
continue
;;
esac
file_ready "$file" || continue
lower="$(echo "$file" | tr '[:upper:]' '[:lower:]')"
case "$lower" in
*.mp4)
process_mp4 "$file"
;;
*.mkv)
process_mkv "$file"
;;
esac
done
log "===== SCAN END ====="
exit 0
EOF
chmod 755 /data/adb/hvc1-auto/process.sh创建 Magisk service.d
mkdir -p /data/adb/service.d
cat > /data/adb/service.d/hvc1-auto-service.sh <<'EOF'
#!/system/bin/sh
# 等待 Android 存储、Magisk 和 FFmpeg 初始化
sleep 120
while true
do
/data/adb/hvc1-auto/process.sh >/dev/null 2>&1
# 每 30 分钟扫描一次
sleep 1800
done
EOF
chmod 755 /data/adb/service.d/hvc1-auto-service.sh查看状态
tail -f /data/adb/hvc1-auto/hvc1-auto.log1 条公开评论
如果脚本执行有问题,多半是存储路径。将脚本给和存储路径给ai让它改。