yt_common: zoning support
This commit is contained in:
parent
e7afd856df
commit
1a96b0f09e
@ -11,7 +11,7 @@ import tempfile
|
|||||||
|
|
||||||
from lvsfunc.render import clip_async_render
|
from lvsfunc.render import clip_async_render
|
||||||
|
|
||||||
from typing import Any, BinaryIO, Callable, List, Optional, Sequence, Set, Tuple, cast
|
from typing import Any, BinaryIO, Callable, List, NamedTuple, Optional, Sequence, Set, Tuple, cast
|
||||||
|
|
||||||
from .chapters import Chapter, Edition, make_chapters, make_qpfile
|
from .chapters import Chapter, Edition, make_chapters, make_qpfile
|
||||||
from .config import Config
|
from .config import Config
|
||||||
@ -39,6 +39,11 @@ def forward_signal(signum: int, frame: Any, process: Any) -> None:
|
|||||||
process.send_signal(signum)
|
process.send_signal(signum)
|
||||||
|
|
||||||
|
|
||||||
|
class Zone(NamedTuple):
|
||||||
|
r: Tuple[int, int]
|
||||||
|
b: float
|
||||||
|
|
||||||
|
|
||||||
class Encoder():
|
class Encoder():
|
||||||
clip: vs.VideoNode
|
clip: vs.VideoNode
|
||||||
|
|
||||||
@ -58,6 +63,7 @@ class Encoder():
|
|||||||
self._get_encoder_settings(settings_path)
|
self._get_encoder_settings(settings_path)
|
||||||
|
|
||||||
def encode(self, clip: vs.VideoNode, filename: str, start: int = 0, end: int = 0,
|
def encode(self, clip: vs.VideoNode, filename: str, start: int = 0, end: int = 0,
|
||||||
|
zones: Optional[List[Zone]] = None, qpfile: Optional[str] = None,
|
||||||
timecode_file: Optional[str] = None, want_timecodes: bool = False) -> Tuple[str, List[float]]:
|
timecode_file: Optional[str] = None, want_timecodes: bool = False) -> Tuple[str, List[float]]:
|
||||||
end = end if end != 0 else clip.num_frames
|
end = end if end != 0 else clip.num_frames
|
||||||
want_timecodes = True if timecode_file else want_timecodes
|
want_timecodes = True if timecode_file else want_timecodes
|
||||||
@ -68,7 +74,25 @@ class Encoder():
|
|||||||
log.warn("Existing output detected, skipping encode!")
|
log.warn("Existing output detected, skipping encode!")
|
||||||
return outfile, []
|
return outfile, []
|
||||||
|
|
||||||
params = [p.format(frames=end-start, filename=filename, qpfile="qpfile.txt") for p in self.params]
|
params: List[str] = []
|
||||||
|
for p in self.params:
|
||||||
|
if p == "$ZONES":
|
||||||
|
if zones:
|
||||||
|
zones.sort(key=lambda z: z.r[0])
|
||||||
|
params.append("--zones")
|
||||||
|
zargs: List[str] = []
|
||||||
|
for z in zones:
|
||||||
|
if z.r[0] - start >= 0 and z.r[0] < end:
|
||||||
|
s = z.r[0] - start
|
||||||
|
e = z.r[1] - start
|
||||||
|
e = e if e < end - start else end - start - 1
|
||||||
|
zargs.append(f"{s},{e},b={z.b}")
|
||||||
|
params.append("/".join(zargs))
|
||||||
|
elif p == "$QPFILE":
|
||||||
|
if qpfile:
|
||||||
|
params += ["--qpfile", qpfile]
|
||||||
|
else:
|
||||||
|
params.append(p.format(frames=end-start, filename=filename, qpfile="qpfile.txt"))
|
||||||
|
|
||||||
log.status("--- RUNNING ENCODE ---")
|
log.status("--- RUNNING ENCODE ---")
|
||||||
|
|
||||||
@ -171,6 +195,7 @@ class SelfRunner():
|
|||||||
audio_file: str
|
audio_file: str
|
||||||
timecodes: List[float]
|
timecodes: List[float]
|
||||||
timecode_file: Optional[str]
|
timecode_file: Optional[str]
|
||||||
|
qpfile: Optional[str]
|
||||||
|
|
||||||
encoder: Encoder
|
encoder: Encoder
|
||||||
audio: AudioGetter
|
audio: AudioGetter
|
||||||
@ -180,11 +205,13 @@ class SelfRunner():
|
|||||||
def __init__(self, config: Config, source: FileSource, final_filter: Callable[[], vs.VideoNode],
|
def __init__(self, config: Config, source: FileSource, final_filter: Callable[[], vs.VideoNode],
|
||||||
workraw_filter: Optional[Callable[[], vs.VideoNode]] = None,
|
workraw_filter: Optional[Callable[[], vs.VideoNode]] = None,
|
||||||
chapters: Optional[List[Chapter]] = None,
|
chapters: Optional[List[Chapter]] = None,
|
||||||
editions: Optional[List[Edition]] = None) -> None:
|
editions: Optional[List[Edition]] = None,
|
||||||
|
zones: Optional[List[Zone]] = None) -> None:
|
||||||
self.config = config
|
self.config = config
|
||||||
self.src = source
|
self.src = source
|
||||||
self.video_clean = False
|
self.video_clean = False
|
||||||
self.audio_clean = False
|
self.audio_clean = False
|
||||||
|
self.qpfile = None
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description=f"Encode {self.config.title} {self.config.desc}")
|
parser = argparse.ArgumentParser(description=f"Encode {self.config.title} {self.config.desc}")
|
||||||
if workraw_filter:
|
if workraw_filter:
|
||||||
@ -207,8 +234,10 @@ class SelfRunner():
|
|||||||
self.workraw = args.workraw if workraw_filter else False
|
self.workraw = args.workraw if workraw_filter else False
|
||||||
self.profile = "workraw" if self.workraw else "final"
|
self.profile = "workraw" if self.workraw else "final"
|
||||||
self.profile = args.profile or self.profile
|
self.profile = args.profile or self.profile
|
||||||
self.suffix = args.suffix if args.suffix is not None else "workraw" if self.workraw else "premux"
|
self.suffix = args.suffix if args.suffix is not None \
|
||||||
self.suffix = args.profile or self.suffix
|
else "workraw" if self.workraw \
|
||||||
|
else args.profile if args.profile \
|
||||||
|
else "premux"
|
||||||
|
|
||||||
self.clip = workraw_filter() if workraw_filter and self.workraw else final_filter()
|
self.clip = workraw_filter() if workraw_filter and self.workraw else final_filter()
|
||||||
|
|
||||||
@ -258,9 +287,9 @@ class SelfRunner():
|
|||||||
log.status("Comparison generated.")
|
log.status("Comparison generated.")
|
||||||
return
|
return
|
||||||
|
|
||||||
open("qpfile.txt", "w").close() # guarantee qpfile is created/cleared as appropriate
|
|
||||||
if (editions or chapters) and (start == 0 and end == self.clip.num_frames):
|
if (editions or chapters) and (start == 0 and end == self.clip.num_frames):
|
||||||
make_qpfile("qpfile.txt", chapters=chapters, editions=editions)
|
self.qpfile = "qpfile.txt"
|
||||||
|
make_qpfile(self.qpfile, chapters=chapters, editions=editions)
|
||||||
|
|
||||||
settings_path = os.path.join(self.config.datapath, f"{self.profile}-settings")
|
settings_path = os.path.join(self.config.datapath, f"{self.profile}-settings")
|
||||||
if not os.path.isfile(settings_path):
|
if not os.path.isfile(settings_path):
|
||||||
@ -272,7 +301,7 @@ class SelfRunner():
|
|||||||
if self.clip.fps_den == 0 else None
|
if self.clip.fps_den == 0 else None
|
||||||
self.video_file, self.timecodes = self.encoder.encode(self.clip,
|
self.video_file, self.timecodes = self.encoder.encode(self.clip,
|
||||||
f"{self.config.desc}_{self.suffix}_{start}_{end}",
|
f"{self.config.desc}_{self.suffix}_{start}_{end}",
|
||||||
start, end, self.timecode_file)
|
start, end, zones, self.qpfile, self.timecode_file)
|
||||||
|
|
||||||
# calculate timecodes if cfr and we didn't generate any (we shouldn'tve)
|
# calculate timecodes if cfr and we didn't generate any (we shouldn'tve)
|
||||||
self.timecodes = [round(float(1e9*f*(1/self.clip.fps)))/1e9 for f in range(0, self.clip.num_frames + 1)] \
|
self.timecodes = [round(float(1e9*f*(1/self.clip.fps)))/1e9 for f in range(0, self.clip.num_frames + 1)] \
|
||||||
|
@ -112,6 +112,11 @@ def make_qpfile(qpfile: str,
|
|||||||
chapters: Optional[List[Chapter]] = None,
|
chapters: Optional[List[Chapter]] = None,
|
||||||
editions: Optional[List[Edition]] = None) -> None:
|
editions: Optional[List[Edition]] = None) -> None:
|
||||||
editions = _to_edition(chapters=chapters, editions=editions)
|
editions = _to_edition(chapters=chapters, editions=editions)
|
||||||
frames = set(c.frame for e in editions for c in e.chapters)
|
frames = set()
|
||||||
|
for e in editions:
|
||||||
|
for c in e.chapters:
|
||||||
|
frames.add(c.frame)
|
||||||
|
if c.end_frame and c.end_frame > 0:
|
||||||
|
frames.add(c.end_frame)
|
||||||
with open(qpfile, "w", encoding="utf-8") as qp:
|
with open(qpfile, "w", encoding="utf-8") as qp:
|
||||||
qp.writelines([f"{f} I\n" for f in sorted(list(frames))])
|
qp.writelines([f"{f} I\n" for f in sorted(list(frames))])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user