144 lines
5.3 KiB
Python
144 lines
5.3 KiB
Python
import os
|
|
import re
|
|
|
|
import bpy
|
|
|
|
from ... import config
|
|
from ...core import Check, Issue, Severity, contexts
|
|
|
|
|
|
def _sound_strips(scene):
|
|
editor = scene.sequence_editor
|
|
if editor is None:
|
|
return []
|
|
return [strip for strip in editor.strips_all if strip.type == "SOUND"]
|
|
|
|
|
|
def latest_version(path):
|
|
"""Return (current, latest, latest_path) for a versioned path, or None.
|
|
|
|
The version token (config VERSION_PATTERN) can be in the file name
|
|
(``dialog_v003.wav``) and/or in a folder (``.../v003/dialog.wav``). Every
|
|
token is tried; when a folder is bumped, the same token is updated in the
|
|
rest of the path.
|
|
"""
|
|
version_re = re.compile(config.VERSION_PATTERN)
|
|
parts = os.path.normpath(path).split(os.sep)
|
|
current = None
|
|
best, best_path = None, path
|
|
for index, part in enumerate(parts):
|
|
match = version_re.search(part)
|
|
if not match:
|
|
continue
|
|
current_text = match.group(1)
|
|
if current is None:
|
|
current = best = int(current_text)
|
|
parent = os.sep.join(parts[:index]) or os.sep
|
|
prefix, suffix = part[: match.start(1)], part[match.end(1):]
|
|
sibling_re = re.compile(re.escape(prefix) + r"(\d+)" + re.escape(suffix) + "$")
|
|
try:
|
|
siblings = os.listdir(parent)
|
|
except OSError:
|
|
continue
|
|
for name in siblings:
|
|
sibling = sibling_re.match(name)
|
|
if not sibling or int(sibling.group(1)) <= best:
|
|
continue
|
|
new_text = sibling.group(1)
|
|
rest = [p.replace(f"v{current_text}", f"v{new_text}") for p in parts[index + 1:]]
|
|
candidate = os.sep.join([parent, name, *rest])
|
|
if os.path.exists(candidate):
|
|
best, best_path = int(new_text), candidate
|
|
if current is None:
|
|
return None
|
|
return current, best, best_path
|
|
|
|
|
|
class SoundVersionCheck(Check):
|
|
id = "sound_versions"
|
|
label = "Sound versions"
|
|
description = "Sound strips exist on disk and use the latest published version."
|
|
category = "Sound"
|
|
contexts = frozenset({contexts.LAYOUT})
|
|
order = 10
|
|
fix_description = "Point the sounds to their latest version"
|
|
|
|
def run(self, ctx):
|
|
for strip in _sound_strips(ctx.scene):
|
|
sound = strip.sound
|
|
if sound is None:
|
|
yield Issue(f"Strip '{strip.name}' has no sound")
|
|
continue
|
|
if sound.packed_file is not None:
|
|
yield Issue.for_id(f"'{strip.name}': sound is packed, cannot check its version", sound)
|
|
continue
|
|
path = bpy.path.abspath(sound.filepath, library=sound.library)
|
|
if not os.path.isfile(path):
|
|
yield Issue.for_id(f"'{strip.name}': file not found {path}", sound)
|
|
continue
|
|
versions = latest_version(path)
|
|
if versions is None:
|
|
yield Issue.for_id(
|
|
f"'{strip.name}': no version number in {os.path.basename(path)}",
|
|
sound,
|
|
severity=Severity.WARNING,
|
|
)
|
|
continue
|
|
current, latest, latest_path = versions
|
|
if latest > current:
|
|
yield Issue.for_id(
|
|
f"'{strip.name}': v{current:03d} used, v{latest:03d} available",
|
|
sound,
|
|
data={"path": latest_path},
|
|
)
|
|
|
|
def fix(self, ctx, issues):
|
|
for issue in issues:
|
|
sound = issue.get_id()
|
|
path = issue.data.get("path")
|
|
if sound is None or not path:
|
|
continue
|
|
relative = sound.filepath.startswith("//")
|
|
# Setting the path reloads the sound.
|
|
sound.filepath = bpy.path.relpath(path) if relative and bpy.data.filepath else path
|
|
|
|
|
|
class SoundTrackActiveCheck(Check):
|
|
id = "sound_active"
|
|
label = "Sound track active"
|
|
description = "The scene has sound, audio is not muted, and no sound strip or channel is muted."
|
|
category = "Sound"
|
|
contexts = frozenset({contexts.LAYOUT})
|
|
order = 20
|
|
fix_description = "Unmute the scene audio, sound strips and their channels"
|
|
|
|
def run(self, ctx):
|
|
scene = ctx.scene
|
|
strips = _sound_strips(scene)
|
|
if not strips:
|
|
yield Issue("No sound strip in the sequencer")
|
|
return
|
|
if scene.use_audio:
|
|
yield Issue("Scene audio is muted (Scene > Audio > Mute)", data={"scene": True})
|
|
channels = scene.sequence_editor.channels
|
|
for strip in strips:
|
|
if strip.mute:
|
|
yield Issue(f"Sound strip '{strip.name}' is muted", data={"strip": strip.name})
|
|
if channels[strip.channel].mute:
|
|
yield Issue(
|
|
f"Channel {strip.channel} of '{strip.name}' is muted", data={"channel": strip.channel}
|
|
)
|
|
|
|
def fix(self, ctx, issues):
|
|
scene = ctx.scene
|
|
editor = scene.sequence_editor
|
|
for issue in issues:
|
|
if issue.data.get("scene"):
|
|
scene.use_audio = False
|
|
if "strip" in issue.data:
|
|
strip = editor.strips_all.get(issue.data["strip"])
|
|
if strip is not None:
|
|
strip.mute = False
|
|
if "channel" in issue.data:
|
|
editor.channels[issue.data["channel"]].mute = False
|