返回列表 发布新帖

[存储使用与管理] 旧相册目录整理到新规则(YYYY/MM)+ 简单查重(按大小)|UGOS 教程

672 1
发表于 2025-12-24 14:05:53 | 查看全部 阅读模式 IP:–浙江–绍兴

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
背景与目标
从老系统/网盘恢复下来的照片视频,目录规则往往很乱(按应用、按批次、按来源)。我想把它们统一整理成新系统常见结构:
  • 归档到:相册根目录/YYYY/MM/原文件名
  • 如果取不到日期:放到 0000/00/
  • 跳过 AppleDouble 垃圾文件:以 ._ 开头的文件不处理
  • 简单查重:同名且大小一致 → 跳过;同名但大小不同 → 自动前缀 1_ / 2_ ...


⚠️重要提醒

  • 先做备份(或者至少先 dry-run + 小批量测试)。
  • “查重”这里只是 按文件大小判断,不是 hash/字节级校验(优点是快,缺点是可能误判)。
  • 跨存储池/跨磁盘移动会变成“复制+删除”,速度会慢很多;同一存储池内 move 通常很快。
  • 我用的是macos系统远程到ugos pro操作的,windows远程ssh的命令大家可以自行问AI



准备工作:明确目录与依赖

1)你需要确认的两个路径
  • 目标目录(相册根目录)示例:

DSTROOT="/home/DDD/Photos/MobileBackup/iPhone 15 Pro Max"
  • 来源目录(待整理的“旧相册”)示例:

SRC="/home/CAIT/restore/237555/我的相册"
2)工具依赖(新手最容易卡这一步)
脚本依赖这些命令:bash / awk / stat / mv / mkdir 以及 exiftool
先检查 exiftool 是否存在:
exiftool -ver
如果提示找不到,需要先安装 exiftool(ugos pro里面是自带有这个工具的,印象中我没有特地安装)。


第 1 部分:创建脚本(生成清单 + 按清单移动)
0)SSH 登录并切 root
ssh -p 22 用户名@192.168.xx.xx
输入密码
sudo -i
再次输入密码即可切换为root



脚本 1:生成清单脚本(plan)
作用:扫描来源目录 → 为每个文件计算“最早时间” → 输出两列 TSV:src<TAB>dst

cat >/root/make_plan_by_earliest_time.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

SRC="${1:?SRC missing}"
DSTROOT="${2:?DSTROOT missing}"
TS="$(date +%F_%H%M%S)"
PLAN="${3:-$DSTROOT/_plan_${TS}.tsv}"

mkdir -p "$DSTROOT"

exiftool -r -m -fast2 \
  -ext heic -ext heif -ext jpg -ext jpeg -ext png -ext mov -ext mp4 -ext m4v \
  -p '$FilePath|$DateTimeOriginal|$CreateDate|$MediaCreateDate|$FileModifyDate|$FileName' \
  "$SRC" \
| awk -F'|' -v dst="$DSTROOT" '
  BEGIN{OFS="\t"}
  function norm(t,   s){
    if (t=="") return ""
    s=t
    sub(/[+-][0-9:]+$/,"",s); sub(/Z$/,"",s)
    gsub(/[^0-9]/,"",s)
    if (length(s) < 14) return ""
    return substr(s,1,14)
  }
  function pick_earliest(a,b,c,d,   i,t,min){
    min=""
    for (i=1; i<=4; i++){
      t = (i==1?a:(i==2?b:(i==3?c:d)))
      if (t!=""){
        if (min=="" || t < min) min=t
      }
    }
    return min
  }
  {
    src=$1; dto=$2; cd=$3; mcd=$4; fmd=$5; name=$6
    if (src=="" || name=="") next
    if (name ~ /^\._/) next  # AppleDouble

    best = pick_earliest(norm(dto), norm(cd), norm(mcd), norm(fmd))
    if (best=="") ym="0000/00"
    else ym=substr(best,1,4)"/"substr(best,5,2)

    print src, dst"/"ym"/"name
  }
' > "$PLAN"

echo "PLAN=$PLAN"
EOF

chmod +x /root/make_plan_by_earliest_time.sh
bash -n /root/make_plan_by_earliest_time.sh && echo "make_plan syntax OK"

补充说明:
  • plan 是 TSV(Tab 分隔),不是空格分隔。
  • 取日期优先级:DateTimeOriginal / CreateDate / MediaCreateDate / FileModifyDate,并取这几个里最早的一个。
  • 0000/00 通常代表:文件没有 EXIF、或者是下载/二次转存导致元数据缺失。




脚本 2:按清单移动脚本(带“按大小查重”和自动改名)

cat >/root/apply_plan_move_sizeprefix.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

DRYRUN=0
LIMIT=0

usage() {
  echo "Usage: $0 [--dry-run] [--limit N] plan1.tsv [plan2.tsv ...]"
  exit 1
}

PLANS=()
while [[ $# -gt 0 ]]; do
  case "$1" in
    --dry-run) DRYRUN=1; shift ;;
    --limit)   LIMIT="${2:-0}"; shift 2 ;;
    -h|--help) usage ;;
    *)         PLANS+=("$1"); shift ;;
  esac
done
[[ ${#PLANS[@]} -ge 1 ]] || usage

get_size() {
  stat -c %s -- "$1" 2>/dev/null || echo ""
}

for PLAN in "${PLANS[@]}"; do
  [[ -f "$PLAN" ]] || { echo "PLAN not found: $PLAN" >&2; exit 2; }

  BASE="${PLAN%.tsv}"
  LOG_MOV="${BASE}.move_ok.log"
  LOG_SKP="${BASE}.skip_same_size.log"
  LOG_RNM="${BASE}.renamed.log"
  LOG_ERR="${BASE}.error.log"
  RES_TSV="${BASE}.result.tsv"

  : >"$LOG_MOV"; : >"$LOG_SKP"; : >"$LOG_RNM"; : >"$LOG_ERR"
  echo -e "ACTION\tSRC\tDST_FINAL\tNOTE" >"$RES_TSV"

  n=0; moved=0; skipped=0; renamed=0; err=0

  while IFS=$'\t' read -r src dst; do
    [[ -z "${src:-}" || -z "${dst:-}" ]] && continue
    ((n++))

    # LIMIT:处理到 N 行就停(用于抽样/测试)
    if [[ "$LIMIT" -gt 0 && "$n" -gt "$LIMIT"; then
      echo "LIMIT reached: $LIMIT (stopped early) for $PLAN"
      break
    fi

    if [[ ! -e "$src"; then
      echo -e "ERROR\t$src\t$dst\tMISS_SRC" >>"$RES_TSV"
      echo "MISS_SRC | $src -> $dst" >>"$LOG_ERR"
      ((err++))
      continue
    fi

    mkdir -p "$(dirname -- "$dst")"
    dst_final="$dst"

    if [[ -e "$dst"; then
      s_src="$(get_size "$src")"
      s_dst="$(get_size "$dst")"

      if [[ -n "$s_src" && -n "$s_dst" && "$s_src" == "$s_dst"; then
        echo -e "SKIP\t$src\t$dst\tSAME_SIZE($s_src)" >>"$RES_TSV"
        echo "SAME_SIZE($s_src) | $src -> $dst" >>"$LOG_SKP"
        ((skipped++))
        continue
      fi

      # 同名但大小不同:前缀 1_,如仍冲突则 2_/3_...
      dir="$(dirname -- "$dst")"
      base="$(basename -- "$dst")"
      i=1
      while [[ -e "$dir/${i}_${base}"; do
        ((i++))
      done
      dst_final="$dir/${i}_${base}"
      echo -e "RENAMED\t$src\t$dst_final\tCONFLICT_WITH:$dst" >>"$RES_TSV"
      echo "CONFLICT | $src -> $dst_final (was $dst)" >>"$LOG_RNM"
      ((renamed++))
    fi

    if [[ "$DRYRUN"; then
      echo "DRYRUN mv -- '$src' '$dst_final'"
      echo -e "MOVED\t$src\t$dst_final\tDRYRUN" >>"$RES_TSV"
      ((moved++))
      continue
    fi

    if mv -n -- "$src" "$dst_final" 2>>"$LOG_ERR"; then
      echo -e "MOVED\t$src\t$dst_final\tOK" >>"$RES_TSV"
      echo "OK | $src -> $dst_final" >>"$LOG_MOV"
      ((moved++))
    else
      echo -e "ERROR\t$src\t$dst_final\tMV_FAILED" >>"$RES_TSV"
      echo "MV_FAILED | $src -> $dst_final" >>"$LOG_ERR"
      ((err++))
    fi
  done <"$PLAN"

  echo "DONE($PLAN) total=$n moved=$moved renamed=$renamed skipped=$skipped err=$err"
  echo "RESULT: $RES_TSV"
done
EOF

chmod +x /root/apply_plan_move_sizeprefix.sh
bash -n /root/apply_plan_move_sizeprefix.sh && echo "apply_plan syntax OK"



第 2 部分:生成清单(plan)

1)设定变量
DSTROOT="/home/DDD/Photos/MobileBackup/iPhone 15 Pro Max"SRC="/home/CAIT/restore/237555/我的相册"  (路径改为实际路径)
2)生成 plan(文件多会跑一段时间)
/root/make_plan_by_earliest_time.sh "$SRC" "$DSTROOT" \  "$DSTROOT/_plan_album_$(date +%F_%H%M%S).tsv"  (会在目标路径下生成)

第 3 部分:校验及移动
PLAN="刚才输出的PLAN路径"
1)必须两列(srcdst),bad_lines 必须=0
awk -F$'\t' 'NF!=2{bad++} END{print "bad_lines=",bad+0}' "$PLAN"
2)看前几行,确认是 Tab,且 dst 有 YYYY/MM 或 0000/00
head -n 5 "$PLAN" | sed -n 'l'
3)总行数(大致等于文件数)
wc -l "$PLAN"
4)统计 0000/00(不应该离谱多)
grep -c "/0000/00/" "$PLAN"
5)移动
/root/apply_plan_move_sizeprefix.sh "$PLAN" |& tee \
  "$DSTROOT/_apply_$(date +%F_%H%M%S).console.log"

动作解释:
  • MOVED:会移动(dry-run 时只是打印)
  • SKIP:目标已存在且大小一致(认为重复)
  • RENAMED:目标存在但大小不同 → 新文件改名为 1_XXX 再移动
  • ERROR:常见是 MISS_SRC(源文件已经被前一次 plan 搬走)

抛砖引玉,为大家提供一个思路,这只是我自己尝试过比较快捷可行的方案,如果大家有更好的工具和方法,欢迎分享。
如果中间出现什么不适配的,大家可以自行AI,上面的方法我已经验证过三个文件夹,还算成功,希望能帮到你。



评论1

绿联云的相册真的很烂Lv.1 发表于 2025-12-24 15:57:06 | 查看全部 IP:–广西–南宁
幸好我平时养成一个好的习惯,我从2014年就开始针对小孩的照片用工具批量按拍摄时间自动命名保存好,按不同的【小孩名】-【类型】-【年份】保存至今,就是目前绿联nas相册使用不给力,导致成千上万的照片和windows文件夹一样展示着,期待绿联nas相册2.0尽快上线!

评论

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

本版积分规则

Copyright © 2026 绿联NAS私有云社区 版权所有 All Rights Reserved. 粤公网安备44030002002555号| 粤ICP备12028978号
关灯 在本版发帖
联系技术支持
返回顶部
快速回复 返回顶部 返回列表