记录一下我删除相机 SD 卡当中废片的工作流
采用 python 脚本实现(python3)。支持图片(JPG/ARW/HEIF 等)和视频(MP4 + 配套 XML 元数据)的筛选删除。
相关说明:
- Linux/mac 电脑,如果你是 windows 电脑那么脚本需要稍加改造
- 相机当中开启了拍摄的照片按照日期文件夹存储,这种功能,否则可能一个文件夹下的照片数量特别多,你需要维护一个特别长的 keep.txt
- keep.txt 当中的文件名可以是完整文件名,也可以不包含扩展名。
a. 如果是完整文件名,那脚本就根据完整文件名匹配去保留/删除文件
b. 如果只包含文件名 stem,没有扩展名,将匹配所有包含 stem 的文件名,进而执行保留/删除逻辑。比如 keep.txt 当中有这样一行 HDK09432 ,那这些文件 HDK09432.JPG, HDK09432.ARW, HDK09432.HEIF 都会被保留,这尤其适用于你开启了 JPEG/RAW 拍摄,那么一张图片会有两个文件 - 支持模拟运行,只需要在最后添加
--dry-run - 支持的扩展名:
.jpg.jpeg.png.arw.heic.heif.hif.dng.mp4.xml - 索尼视频的 XML 元数据文件会自动匹配对应的 MP4 主文件(如
C0054M01.XML跟随C0054.MP4同进退),无需在 keep.txt 中单独列出
照片工作流
索尼 JPEG 照片扩展名是 JPG,RAW 拍摄文件扩展名是 ARW,HEIF 拍摄扩展名是 HIF (Apple 的 HEIF 照片扩展名是 HEIC)
cd /run/media/dk/708D-3AF3/DCIM/15660613/
ls
HDK09429.JPG HDK09432.JPG HDK09435.JPG HDK09438.JPG HDK09441.JPG HDK09444.JPG HDK09447.JPG
HDK09430.JPG HDK09433.JPG HDK09436.JPG HDK09439.JPG HDK09442.JPG HDK09445.JPG HDK09448.JPG
HDK09431.JPG HDK09434.JPG HDK09437.JPG HDK09440.JPG HDK09443.JPG HDK09446.JPG HDK09449.JPG
$python3 ~/Downloads/myapps/delete_unwanted_media.py ./keep.txt --dry-run
========== 执行模式 ==========
DRY RUN
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09429.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09430.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09431.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09433.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09434.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09435.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09436.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09437.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09439.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09443.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09444.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09445.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09446.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09447.JPG
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09448.JPG
========== 保留文件 ==========
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09432.JPG
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09438.JPG
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09440.JPG
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09441.JPG
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09442.JPG
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09449.JPG
========== 统计 ==========
模式: DRY-RUN
计划删除文件数: 15
保留文件数: 6
========== 删除列表 ==========
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09429.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09430.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09431.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09433.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09434.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09435.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09436.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09437.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09439.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09443.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09444.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09445.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09446.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09447.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09448.JPG
========== 保留列表 ==========
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09432.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09438.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09440.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09441.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09442.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09449.JPG
$python3 ~/Downloads/myapps/delete_unwanted_media.py ./keep.txt
========== 执行模式 ==========
REAL DELETE
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09429.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09430.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09431.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09433.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09434.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09435.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09436.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09437.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09439.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09443.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09444.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09445.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09446.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09447.JPG
已删除: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09448.JPG
========== 保留文件 ==========
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09432.JPG
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09438.JPG
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09440.JPG
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09441.JPG
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09442.JPG
保留: /run/media/dk/708D-3AF3/DCIM/15660613/HDK09449.JPG
========== 统计 ==========
模式: DELETE
计划删除文件数: 15
保留文件数: 6
实际删除成功数: 15
========== 删除列表 ==========
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09429.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09430.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09431.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09433.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09434.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09435.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09436.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09437.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09439.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09443.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09444.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09445.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09446.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09447.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09448.JPG
========== 保留列表 ==========
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09432.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09438.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09440.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09441.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09442.JPG
/run/media/dk/708D-3AF3/DCIM/15660613/HDK09449.JPG
ls
HDK09432.JPG HDK09438.JPG HDK09440.JPG HDK09441.JPG HDK09442.JPG HDK09449.JPG keep.txt
视频工作流
索尼 A7C2 录制视频后,存储卡上的目录结构为 PRIVATE/M4ROOT/CLIP/。与照片不同,视频不按日期分子目录,所有日期的视频文件都平铺在同一个 CLIP/ 目录下,因此 keep.txt 会较长。不过首次维护好之后,每次拍摄后只需要在末尾追加新增的 clip 号即可。
每个视频片段会生成:
C0054.MP4— 视频主文件C0054M01.XML— 配套元数据文件(自动跟随 MP4 同进退)
cd /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/
ls
C0054.MP4 C0054M01.XML C0055.MP4 C0055M01.XML C0056.MP4 C0056M01.XML C0057.MP4 C0057M01.XML
C0061.MP4 C0061M01.XML C0062.MP4 C0062M01.XML C0063.MP4 C0063M01.XML C0068.MP4 C0068M01.XML
C0070.MP4 C0070M01.XML C0071.MP4 C0071M01.XML C0074.MP4 C0074M01.XML C0076.MP4 C0076M01.XML
C0077.MP4 C0077M01.XML C0079.MP4 C0079M01.XML C0080.MP4 C0080M01.XML C0083.MP4 C0083M01.XML
C0084.MP4 C0084M01.XML C0085.MP4 C0085M01.XML C0086.MP4 C0086M01.XML C0087.MP4 C0087M01.XML
C0088.MP4 C0088M01.XML C0089.MP4 C0089M01.XML C0090.MP4 C0090M01.XML C0091.MP4 C0091M01.XML
C0092.MP4 C0092M01.XML C0093.MP4 C0093M01.XML C0094.MP4 C0094M01.XML C0095.MP4 C0095M01.XML
C0096.MP4 C0096M01.XML
keep.txt 内容(只写 stem,不包含扩展名):
$cat keep.txt
# start to record keep.txt list
C0054
C0055
C0056
C0057
C0061
C0062
C0063
C0068
C0070
C0071
C0074
C0076
C0077
C0080
C0083
C0086
C0092
C0094
C0095
C0096
# 2026-07-04
先模拟运行确认预期:
$python3 ~/Downloads/myapps/delete_unwanted_media.py ./keep.txt --dry-run
========== 执行模式 ==========
DRY RUN
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0079M01.XML
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0079.MP4
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0084M01.XML
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0084.MP4
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0085M01.XML
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0085.MP4
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0087M01.XML
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0087.MP4
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0088M01.XML
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0088.MP4
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0089M01.XML
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0089.MP4
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0090M01.XML
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0090.MP4
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0091M01.XML
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0091.MP4
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0093M01.XML
[DRY-RUN] 将删除: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0093.MP4
========== 保留文件 ==========
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0054M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0054.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0055M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0055.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0056M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0056.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0057M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0057.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0061M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0061.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0062M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0062.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0063M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0063.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0068M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0068.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0071M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0071.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0070M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0070.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0074M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0074.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0077M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0077.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0076M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0076.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0080M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0080.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0083M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0083.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0086M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0086.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0092M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0092.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0094M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0094.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0095M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0095.MP4
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0096M01.XML
保留: /run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0096.MP4
========== 统计 ==========
模式: DRY-RUN
计划删除文件数: 18
保留文件数: 40
========== 删除列表 ==========
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0079M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0079.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0084M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0084.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0085M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0085.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0087M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0087.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0088M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0088.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0089M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0089.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0090M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0090.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0091M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0091.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0093M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0093.MP4
========== 保留列表 ==========
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0054M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0054.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0055M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0055.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0056M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0056.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0057M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0057.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0061M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0061.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0062M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0062.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0063M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0063.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0068M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0068.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0071M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0071.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0070M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0070.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0074M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0074.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0077M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0077.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0076M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0076.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0080M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0080.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0083M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0083.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0086M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0086.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0092M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0092.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0094M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0094.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0095M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0095.MP4
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0096M01.XML
/run/media/dk/708D-3AF3/PRIVATE/M4ROOT/CLIP/C0096.MP4
确认无误后去掉 --dry-run 执行实际删除。
脚本内容
$cat ~/Downloads/myapps/delete_unwanted_media.py
#!/usr/bin/env python3
import os
import sys
TARGET_EXTS = {".jpg", ".jpeg", ".png", ".arw", ".heic", ".heif", ".hif", ".dng", ".mp4", ".xml"}
def load_keep_list(path):
exact_keep = set()
stem_keep = set()
with open(path, "r", encoding="utf-8") as f:
for line in f:
raw = line.strip()
if not raw or raw.startswith("#"):
continue
stem, ext = os.path.splitext(raw)
if ext: # 有扩展名 → 精确匹配
exact_keep.add(raw)
else: # 无扩展名 → stem 匹配
stem_keep.add(stem)
return exact_keep, stem_keep
def get_effective_stem(filename, mp4_stems):
stem, ext = os.path.splitext(filename)
if ext.lower() == ".xml" and mp4_stems:
match = ""
for mp4_stem in mp4_stems:
if len(mp4_stem) > len(match) and stem.startswith(mp4_stem):
match = mp4_stem
if match:
return match
return stem
def is_target_file(filename):
ext = os.path.splitext(filename)[1].lower()
return ext in TARGET_EXTS
def main():
if len(sys.argv) < 2:
print("Usage: python3 delete_unwanted_media.py ./keep.txt [--dry-run]")
sys.exit(1)
keep_path = sys.argv[1]
dry_run = "--dry-run" in sys.argv[2:]
root_dir = os.getcwd()
if not os.path.exists(keep_path):
print(f"[ERROR] keep.txt not found: {keep_path}")
sys.exit(1)
exact_keep, stem_keep = load_keep_list(keep_path)
to_delete = []
to_keep = []
# 扫描文件
for root, dirs, files in os.walk(root_dir):
mp4_stems = [os.path.splitext(f)[0] for f in files if f.lower().endswith(".mp4")]
for file in files:
if not is_target_file(file):
continue
full_path = os.path.join(root, file)
stem = get_effective_stem(file, mp4_stems)
if (file in exact_keep) or (stem in stem_keep):
to_keep.append(full_path)
else:
to_delete.append(full_path)
deleted_count = 0
print("\n========== 执行模式 ==========")
print("DRY RUN" if dry_run else "REAL DELETE")
# 删除逻辑
for path in to_delete:
if dry_run:
print(f"[DRY-RUN] 将删除: {path}")
else:
try:
os.remove(path)
print(f"已删除: {path}")
deleted_count += 1
except Exception as e:
print(f"[ERROR] 删除失败 {path}: {e}")
# 保留输出
print("\n========== 保留文件 ==========")
for path in to_keep:
print(f"保留: {path}")
# 统计
print("\n========== 统计 ==========")
print(f"模式: {'DRY-RUN' if dry_run else 'DELETE'}")
print(f"计划删除文件数: {len(to_delete)}")
print(f"保留文件数: {len(to_keep)}")
if not dry_run:
print(f"实际删除成功数: {deleted_count}")
# 列表输出
print("\n========== 删除列表 ==========")
for path in to_delete:
print(path)
print("\n========== 保留列表 ==========")
for path in to_keep:
print(path)
if __name__ == "__main__":
main()
keep.txt 内容示例(照片):
$cat keep.txt
HDK09432
HDK09438
HDK09440
HDK09441
HDK09442
HDK09449
keep.txt 内容示例(视频,支持 # 注释行):
$cat keep.txt
# start to record keep.txt list
C0054
C0055
C0056
C0057
C0061
C0062
C0063
C0068
C0070
C0071
C0074
C0076
C0077
C0080
C0083
C0086
C0092
C0094
C0095
C0096
# 2026-07-04