Add opt-in sample_videos cleanup rule for loose sample video files
This commit is contained in:
+48
-61
@@ -2,7 +2,7 @@
|
|||||||
#
|
#
|
||||||
# Configures what cleanup (-c) moves to a local trash folder. Nothing here
|
# Configures what cleanup (-c) moves to a local trash folder. Nothing here
|
||||||
# is deleted permanently -- matched items are moved to a .trash folder next
|
# is deleted permanently -- matched items are moved to a .trash folder next
|
||||||
# to the script, and every move can be undone. All three sections below are
|
# to the script, and every move can be undone. All sections below are
|
||||||
# optional and independent; enable only the ones you want.
|
# optional and independent; enable only the ones you want.
|
||||||
#
|
#
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -23,21 +23,22 @@
|
|||||||
# moved to trash, along with everything inside it. There is no
|
# moved to trash, along with everything inside it. There is no
|
||||||
# confirmation beyond the normal cleanup run prompts, so double check
|
# confirmation beyond the normal cleanup run prompts, so double check
|
||||||
# this list before running for real.
|
# this list before running for real.
|
||||||
#
|
|
||||||
# Uncomment and edit the list below to enable:
|
folders:
|
||||||
#
|
- Screens
|
||||||
# folders:
|
- Featurettes
|
||||||
# - Screens
|
- Extras
|
||||||
# - Featurettes
|
- Sample
|
||||||
# - Extras
|
- Samples
|
||||||
# - Sample
|
- Soundtrack
|
||||||
# - Samples
|
- Behind the Scenes
|
||||||
# - Behind the Scenes
|
- Deleted Scenes
|
||||||
# - Deleted Scenes
|
- Trailers
|
||||||
# - Trailers
|
- Interviews
|
||||||
# - Interviews
|
- Bonus
|
||||||
# - Bonus
|
- "* torrent"
|
||||||
#
|
- "* torrents"
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# files
|
# files
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -63,20 +64,39 @@
|
|||||||
# the "subtitles:" section below is the friendlier, language-agnostic
|
# the "subtitles:" section below is the friendlier, language-agnostic
|
||||||
# way to protect or target any language, including your own.
|
# way to protect or target any language, including your own.
|
||||||
# - Video file extensions (mp4, mkv, avi, etc.) are never deleted by
|
# - Video file extensions (mp4, mkv, avi, etc.) are never deleted by
|
||||||
# cleanup, even if a pattern here would otherwise match them.
|
# cleanup, even if a pattern here would otherwise match them. Use
|
||||||
|
# "sample_videos:" below for the one exception to that.
|
||||||
|
|
||||||
|
files:
|
||||||
|
- "*.txt"
|
||||||
|
- "*.nfo"
|
||||||
|
- "*.jpg"
|
||||||
|
- "*.jpeg"
|
||||||
|
- "*.png"
|
||||||
|
- "*.smi"
|
||||||
|
- "*.ico"
|
||||||
|
- "*.url"
|
||||||
|
- "*.sfv"
|
||||||
|
- "*.part"
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# sample_videos
|
||||||
|
# ============================================================================
|
||||||
#
|
#
|
||||||
# Uncomment and edit the list below to enable:
|
# Whether to move loose sample video files to trash during cleanup.
|
||||||
#
|
|
||||||
# files:
|
|
||||||
# - "*.txt"
|
|
||||||
# - "*.nfo"
|
|
||||||
# - "*.jpg"
|
|
||||||
# - "*.jpeg"
|
|
||||||
# - "*.png"
|
|
||||||
# - "*.ico"
|
|
||||||
# - "*.url"
|
|
||||||
# - "*.sfv"
|
|
||||||
#
|
#
|
||||||
|
# - Video file extensions (mp4, mkv, avi, etc.) are otherwise never touched
|
||||||
|
# by cleanup, even under "files:" above -- this is the one exception.
|
||||||
|
# - Only matches a video file with the whole word "sample" in its name
|
||||||
|
# (e.g. "Sample.mkv", "Movie-Sample.avi", "Movie.sample.mp4"), so it
|
||||||
|
# won't catch a real video that just happens to contain "sample" as part
|
||||||
|
# of a longer word.
|
||||||
|
# - This only catches loose sample files sitting next to the real video.
|
||||||
|
# A sample shipped inside its own "Sample" folder is already handled by
|
||||||
|
# adding "Sample" to the "folders:" list above.
|
||||||
|
|
||||||
|
sample_videos: true
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# subtitles
|
# subtitles
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -119,39 +139,6 @@
|
|||||||
# word frequency for Latin-script languages. This is a best-effort
|
# word frequency for Latin-script languages. This is a best-effort
|
||||||
# heuristic for .srt content, not a guarantee, and always errs toward
|
# heuristic for .srt content, not a guarantee, and always errs toward
|
||||||
# leaving a file alone if the result is unclear.
|
# leaving a file alone if the result is unclear.
|
||||||
#
|
|
||||||
# Uncomment and edit to enable (replace "Spanish" with your own primary
|
|
||||||
# language, or whichever language you want to keep):
|
|
||||||
#
|
|
||||||
# subtitles:
|
|
||||||
# - "!Spanish"
|
|
||||||
|
|
||||||
folders:
|
|
||||||
- Screens
|
|
||||||
- Featurettes
|
|
||||||
- Extras
|
|
||||||
- Sample
|
|
||||||
- Samples
|
|
||||||
- Soundtrack
|
|
||||||
- Behind the Scenes
|
|
||||||
- Deleted Scenes
|
|
||||||
- Trailers
|
|
||||||
- Interviews
|
|
||||||
- Bonus
|
|
||||||
- "* torrent"
|
|
||||||
- "* torrents"
|
|
||||||
|
|
||||||
files:
|
|
||||||
- "*.txt"
|
|
||||||
- "*.nfo"
|
|
||||||
- "*.jpg"
|
|
||||||
- "*.jpeg"
|
|
||||||
- "*.png"
|
|
||||||
- "*.smi"
|
|
||||||
- "*.ico"
|
|
||||||
- "*.url"
|
|
||||||
- "*.sfv"
|
|
||||||
- "*.part"
|
|
||||||
|
|
||||||
subtitles:
|
subtitles:
|
||||||
- "!English"
|
- "!English"
|
||||||
|
|||||||
+35
-5
@@ -57,6 +57,8 @@ VIDEO_EXTENSIONS = {
|
|||||||
"mp4", "mkv", "avi", "mov", "wmv", "m4v", "mpg", "mpeg", "flv", "ts", "m2ts", "webm",
|
"mp4", "mkv", "avi", "mov", "wmv", "m4v", "mpg", "mpeg", "flv", "ts", "m2ts", "webm",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SAMPLE_VIDEO_PATTERN = re.compile(r'(?<![A-Za-z0-9])sample(?![A-Za-z0-9])', re.IGNORECASE)
|
||||||
|
|
||||||
SUBTITLE_EXTENSIONS = {"srt", "sub"}
|
SUBTITLE_EXTENSIONS = {"srt", "sub"}
|
||||||
SUBTITLE_FOLDER_NAMES = {"subs", "subtitles"}
|
SUBTITLE_FOLDER_NAMES = {"subs", "subtitles"}
|
||||||
|
|
||||||
@@ -2853,7 +2855,7 @@ def save_scan_cache(cache):
|
|||||||
CACHE_FILE.write_text(json.dumps(cache, indent=2))
|
CACHE_FILE.write_text(json.dumps(cache, indent=2))
|
||||||
|
|
||||||
|
|
||||||
def compute_cleanup_rules_signature(delete_folder_names, delete_file_patterns, keep_languages, delete_languages):
|
def compute_cleanup_rules_signature(delete_folder_names, delete_file_patterns, keep_languages, delete_languages, sample_videos_enabled=False):
|
||||||
hasher = hashlib.sha256()
|
hasher = hashlib.sha256()
|
||||||
for name in sorted(delete_folder_names):
|
for name in sorted(delete_folder_names):
|
||||||
hasher.update(f"f:{name.lower()}\n".encode("utf-8"))
|
hasher.update(f"f:{name.lower()}\n".encode("utf-8"))
|
||||||
@@ -2863,6 +2865,7 @@ def compute_cleanup_rules_signature(delete_folder_names, delete_file_patterns, k
|
|||||||
hasher.update(f"k:{lang}\n".encode("utf-8"))
|
hasher.update(f"k:{lang}\n".encode("utf-8"))
|
||||||
for lang in sorted(delete_languages or []):
|
for lang in sorted(delete_languages or []):
|
||||||
hasher.update(f"d:{lang}\n".encode("utf-8"))
|
hasher.update(f"d:{lang}\n".encode("utf-8"))
|
||||||
|
hasher.update(f"s:{bool(sample_videos_enabled)}\n".encode("utf-8"))
|
||||||
return hasher.hexdigest()
|
return hasher.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
@@ -2893,6 +2896,23 @@ def load_delete_folder_names():
|
|||||||
return [name.lower() for name in parse_simple_yaml_list(DELETE_FILE, "folders")]
|
return [name.lower() for name in parse_simple_yaml_list(DELETE_FILE, "folders")]
|
||||||
|
|
||||||
|
|
||||||
|
def parse_simple_yaml_bool(path, key):
|
||||||
|
if not path.exists():
|
||||||
|
return False
|
||||||
|
for raw_line in path.read_text().splitlines():
|
||||||
|
line = raw_line.strip()
|
||||||
|
if not line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
if line.startswith(f"{key}:"):
|
||||||
|
value = line.split(":", 1)[1].strip().strip('"').strip("'").lower()
|
||||||
|
return value in ("true", "yes", "1", "on")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def load_delete_sample_videos_flag():
|
||||||
|
return parse_simple_yaml_bool(DELETE_FILE, "sample_videos")
|
||||||
|
|
||||||
|
|
||||||
TRAILING_WRAPPER_CHARS = ")]}'\""
|
TRAILING_WRAPPER_CHARS = ")]}'\""
|
||||||
|
|
||||||
|
|
||||||
@@ -3111,7 +3131,7 @@ def matches_file_name(name, patterns, path=None):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def find_cleanup_targets(share, delete_folder_names, delete_file_patterns, keep_languages=None, delete_languages=None, unchanged_folder_names=None):
|
def find_cleanup_targets(share, delete_folder_names, delete_file_patterns, keep_languages=None, delete_languages=None, unchanged_folder_names=None, sample_videos_enabled=False):
|
||||||
targets = []
|
targets = []
|
||||||
keep_languages = keep_languages or set()
|
keep_languages = keep_languages or set()
|
||||||
delete_languages = delete_languages or set()
|
delete_languages = delete_languages or set()
|
||||||
@@ -3146,6 +3166,10 @@ def find_cleanup_targets(share, delete_folder_names, delete_file_patterns, keep_
|
|||||||
elif entry.is_file():
|
elif entry.is_file():
|
||||||
ext = entry.suffix.lower().lstrip(".")
|
ext = entry.suffix.lower().lstrip(".")
|
||||||
if ext in VIDEO_EXTENSIONS:
|
if ext in VIDEO_EXTENSIONS:
|
||||||
|
if sample_videos_enabled and SAMPLE_VIDEO_PATTERN.search(entry.stem):
|
||||||
|
vprint(f" Match (sample video): {entry}")
|
||||||
|
targets.append(("file", entry))
|
||||||
|
continue
|
||||||
vprint(f" Skipping (protected video file): {entry}")
|
vprint(f" Skipping (protected video file): {entry}")
|
||||||
continue
|
continue
|
||||||
if matches_file_name(entry.name, delete_file_patterns, entry):
|
if matches_file_name(entry.name, delete_file_patterns, entry):
|
||||||
@@ -3224,19 +3248,24 @@ def run_cleanup(args, log):
|
|||||||
delete_folder_names = load_delete_folder_names()
|
delete_folder_names = load_delete_folder_names()
|
||||||
delete_file_patterns = load_delete_file_patterns()
|
delete_file_patterns = load_delete_file_patterns()
|
||||||
keep_languages, delete_languages = load_delete_subtitle_rules()
|
keep_languages, delete_languages = load_delete_subtitle_rules()
|
||||||
|
sample_videos_enabled = load_delete_sample_videos_flag()
|
||||||
vprint(f"Loaded {len(delete_folder_names)} folder pattern(s) from {DELETE_FILE.name}: {delete_folder_names}")
|
vprint(f"Loaded {len(delete_folder_names)} folder pattern(s) from {DELETE_FILE.name}: {delete_folder_names}")
|
||||||
vprint(f"Loaded {len(delete_file_patterns)} file pattern(s) from {DELETE_FILE.name}: {delete_file_patterns}")
|
vprint(f"Loaded {len(delete_file_patterns)} file pattern(s) from {DELETE_FILE.name}: {delete_file_patterns}")
|
||||||
vprint(f"Loaded subtitle rules: keep={keep_languages or 'none'}, delete={delete_languages or 'none'}")
|
vprint(f"Loaded subtitle rules: keep={keep_languages or 'none'}, delete={delete_languages or 'none'}")
|
||||||
|
vprint(f"Sample video cleanup enabled: {sample_videos_enabled}")
|
||||||
|
|
||||||
has_duplicates_folder = (Path(share) / DUPLICATES_FOLDER_NAME).is_dir()
|
has_duplicates_folder = (Path(share) / DUPLICATES_FOLDER_NAME).is_dir()
|
||||||
|
|
||||||
if not delete_folder_names and not delete_file_patterns and not keep_languages and not delete_languages and not has_duplicates_folder:
|
if (
|
||||||
|
not delete_folder_names and not delete_file_patterns and not keep_languages
|
||||||
|
and not delete_languages and not has_duplicates_folder and not sample_videos_enabled
|
||||||
|
):
|
||||||
print("No cleanup rules configured.")
|
print("No cleanup rules configured.")
|
||||||
print(f"Edit {DELETE_FILE.name} to enable cleanup.")
|
print(f"Edit {DELETE_FILE.name} to enable cleanup.")
|
||||||
return
|
return
|
||||||
|
|
||||||
rules_signature = compute_cleanup_rules_signature(
|
rules_signature = compute_cleanup_rules_signature(
|
||||||
delete_folder_names, delete_file_patterns, keep_languages, delete_languages
|
delete_folder_names, delete_file_patterns, keep_languages, delete_languages, sample_videos_enabled
|
||||||
)
|
)
|
||||||
cache = load_scan_cache()
|
cache = load_scan_cache()
|
||||||
share_key = str(Path(share).resolve())
|
share_key = str(Path(share).resolve())
|
||||||
@@ -3268,7 +3297,8 @@ def run_cleanup(args, log):
|
|||||||
print()
|
print()
|
||||||
|
|
||||||
targets = find_cleanup_targets(
|
targets = find_cleanup_targets(
|
||||||
share, delete_folder_names, delete_file_patterns, keep_languages, delete_languages, unchanged_folder_names
|
share, delete_folder_names, delete_file_patterns, keep_languages, delete_languages,
|
||||||
|
unchanged_folder_names, sample_videos_enabled
|
||||||
)
|
)
|
||||||
vprint(f"Scan complete. {len(targets)} item(s) matched.")
|
vprint(f"Scan complete. {len(targets)} item(s) matched.")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user