boneyard 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- boneyard-0.1.0/PKG-INFO +26 -0
- boneyard-0.1.0/README.md +1 -0
- boneyard-0.1.0/pyproject.toml +55 -0
- boneyard-0.1.0/src/boneyard/__init__.py +315 -0
- boneyard-0.1.0/src/boneyard/py.typed +0 -0
boneyard-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: boneyard
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary:
|
|
5
|
+
Author: sgt0
|
|
6
|
+
Author-email: sgt0 <140186177+sgt0@users.noreply.github.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Classifier: Natural Language :: English
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Requires-Dist: ass~=1.0.3
|
|
17
|
+
Requires-Dist: jetpytools~=3.0.0
|
|
18
|
+
Requires-Dist: muxtools~=0.4.2
|
|
19
|
+
Requires-Dist: vapoursynth>=77
|
|
20
|
+
Requires-Dist: vapoursynth-scxvid~=3.0
|
|
21
|
+
Requires-Dist: vsjetpack~=2.0.0
|
|
22
|
+
Requires-Dist: vapoursynth-fmtconv>=31
|
|
23
|
+
Requires-Python: >=3.13
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# boneyard
|
boneyard-0.1.0/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# boneyard
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "boneyard"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = ""
|
|
5
|
+
license = "MIT"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "sgt0", email = "140186177+sgt0@users.noreply.github.com" }
|
|
9
|
+
]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Natural Language :: English",
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
14
|
+
"Programming Language :: Python :: 3.13",
|
|
15
|
+
"Programming Language :: Python :: 3.14",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Typing :: Typed",
|
|
19
|
+
]
|
|
20
|
+
requires-python = ">=3.13"
|
|
21
|
+
dependencies = [
|
|
22
|
+
"ass~=1.0.3",
|
|
23
|
+
"jetpytools~=3.0.0",
|
|
24
|
+
"muxtools~=0.4.2",
|
|
25
|
+
"vapoursynth>=77",
|
|
26
|
+
"vapoursynth-scxvid~=3.0",
|
|
27
|
+
"vsjetpack~=2.0.0",
|
|
28
|
+
|
|
29
|
+
# Explicitly specify these so they're properly resolved through `uv --index`.
|
|
30
|
+
"vapoursynth-fmtconv>=31",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[dependency-groups]
|
|
34
|
+
dev = [
|
|
35
|
+
"mypy==2.1.0",
|
|
36
|
+
"ruff==0.15.20",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[build-system]
|
|
40
|
+
requires = ["uv_build==0.11.7"]
|
|
41
|
+
build-backend = "uv_build"
|
|
42
|
+
|
|
43
|
+
[tool.uv.sources]
|
|
44
|
+
vapoursynth-fmtconv = { index = "vs-wheels" }
|
|
45
|
+
|
|
46
|
+
[[tool.uv.index]]
|
|
47
|
+
name = "vs-wheels"
|
|
48
|
+
url = "https://jaded-encoding-thaumaturgy.github.io/vs-wheels/simple"
|
|
49
|
+
explicit = true
|
|
50
|
+
|
|
51
|
+
[tool.basedpyright]
|
|
52
|
+
stubPath = "stubs"
|
|
53
|
+
|
|
54
|
+
[tool.mypy]
|
|
55
|
+
mypy_path = "$MYPY_CONFIG_FILE_DIR/stubs"
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Iterator
|
|
4
|
+
from statistics import fmean
|
|
5
|
+
from typing import Any, Literal, Self, override
|
|
6
|
+
|
|
7
|
+
from ass import Style
|
|
8
|
+
from ass.data import Color
|
|
9
|
+
from jetpytools import (
|
|
10
|
+
CustomIntEnum,
|
|
11
|
+
CustomRuntimeError,
|
|
12
|
+
Sentinel,
|
|
13
|
+
SentinelT,
|
|
14
|
+
SingleOrArr,
|
|
15
|
+
)
|
|
16
|
+
from muxtools import default_style_args, edit_style
|
|
17
|
+
from vsdenoise import MVToolsPreset, Prefilter, bm3d, mc_degrain, nl_means
|
|
18
|
+
from vsdenoise.blockmatch import BM3D
|
|
19
|
+
from vsdenoise.nlm import NLMeans
|
|
20
|
+
from vstools import (
|
|
21
|
+
ChromaLocation,
|
|
22
|
+
clip_async_render,
|
|
23
|
+
clip_data_gather,
|
|
24
|
+
core,
|
|
25
|
+
get_prop,
|
|
26
|
+
get_w,
|
|
27
|
+
merge_clip_props,
|
|
28
|
+
replace_ranges,
|
|
29
|
+
vs,
|
|
30
|
+
)
|
|
31
|
+
from vstools import Keyframes as JetpackKeyframes
|
|
32
|
+
from vstools.functions.timecodes import SceneBasedDynamicCache
|
|
33
|
+
|
|
34
|
+
__all__ = (
|
|
35
|
+
"LOFT_MOON_FIRA_DEFAULT",
|
|
36
|
+
"LOFT_MOON_FIRA_ALT",
|
|
37
|
+
"LOFT_MOON_SIGNS",
|
|
38
|
+
"LOFT_MOON_FIRA_PRESET",
|
|
39
|
+
"denoise",
|
|
40
|
+
"SceneBasedCambi",
|
|
41
|
+
"SceneChangeMode",
|
|
42
|
+
"Keyframes",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
LOFT_MOON_FIRA_DEFAULT = Style(
|
|
47
|
+
name="Default",
|
|
48
|
+
fontname="Fira Sans Medium",
|
|
49
|
+
fontsize=69.0,
|
|
50
|
+
outline=4.4,
|
|
51
|
+
shadow=2.2,
|
|
52
|
+
margin_l=275,
|
|
53
|
+
margin_r=275,
|
|
54
|
+
margin_v=60,
|
|
55
|
+
**(
|
|
56
|
+
default_style_args
|
|
57
|
+
| {
|
|
58
|
+
"bold": False,
|
|
59
|
+
"secondary_color": Color(r=0x00, g=0x00, b=0x00, a=0x00),
|
|
60
|
+
"outline_color": Color(r=0x1F, g=0x29, b=0x12, a=0x00),
|
|
61
|
+
"back_color": Color(r=0x1F, g=0x29, b=0x12, a=0xA0),
|
|
62
|
+
}
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
LOFT_MOON_FIRA_ALT = edit_style(
|
|
67
|
+
LOFT_MOON_FIRA_DEFAULT,
|
|
68
|
+
"Alt",
|
|
69
|
+
outline_color=Color(r=0x4C, g=0x21, b=0x48, a=0x00),
|
|
70
|
+
back_color=Color(r=0x4C, g=0x21, b=0x48, a=0xA0),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
LOFT_MOON_SIGNS = Style(
|
|
74
|
+
name="Signs",
|
|
75
|
+
fontname="Fira Sans Medium",
|
|
76
|
+
fontsize=80.0,
|
|
77
|
+
outline=0.0,
|
|
78
|
+
shadow=0.0,
|
|
79
|
+
margin_l=0,
|
|
80
|
+
margin_r=0,
|
|
81
|
+
margin_v=0,
|
|
82
|
+
**(
|
|
83
|
+
default_style_args
|
|
84
|
+
| {
|
|
85
|
+
"bold": False,
|
|
86
|
+
"alignment": 5,
|
|
87
|
+
"secondary_color": Color(r=0xFF, g=0xFF, b=0xFF, a=0x00),
|
|
88
|
+
"outline_color": Color(r=0x00, g=0x00, b=0x00, a=0x00),
|
|
89
|
+
"back_color": Color(r=0x00, g=0x00, b=0x00, a=0x00),
|
|
90
|
+
}
|
|
91
|
+
),
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
LOFT_MOON_FIRA_PRESET = [
|
|
95
|
+
LOFT_MOON_FIRA_DEFAULT,
|
|
96
|
+
LOFT_MOON_FIRA_ALT,
|
|
97
|
+
LOFT_MOON_SIGNS,
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def denoise(
|
|
102
|
+
clip: vs.VideoNode,
|
|
103
|
+
block_size: int = 64,
|
|
104
|
+
limit: int | tuple[int | None, int | None] | None = None,
|
|
105
|
+
refine: int = 3,
|
|
106
|
+
sigma: SingleOrArr[float] = 0.7,
|
|
107
|
+
sr: int = 2,
|
|
108
|
+
strength: float = 0.2,
|
|
109
|
+
thSAD: int | tuple[int, int] = 115, # noqa: N803
|
|
110
|
+
tr: int = 2,
|
|
111
|
+
) -> vs.VideoNode:
|
|
112
|
+
"""MVTools + BM3D + NLMeans denoise."""
|
|
113
|
+
|
|
114
|
+
ref = mc_degrain(
|
|
115
|
+
clip,
|
|
116
|
+
prefilter=Prefilter.DFTTEST,
|
|
117
|
+
preset=MVToolsPreset.HQ_SAD,
|
|
118
|
+
blksize=block_size,
|
|
119
|
+
thsad=thSAD,
|
|
120
|
+
limit=limit,
|
|
121
|
+
refine=refine,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
denoised_luma = bm3d(
|
|
125
|
+
clip, ref=ref, sigma=sigma, tr=tr, profile=BM3D.Profile.NORMAL, planes=0
|
|
126
|
+
)
|
|
127
|
+
denoised_luma = ChromaLocation.ensure_presence(
|
|
128
|
+
denoised_luma, ChromaLocation.from_video(clip, strict=True)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
return nl_means(
|
|
132
|
+
denoised_luma,
|
|
133
|
+
ref=ref,
|
|
134
|
+
h=strength,
|
|
135
|
+
tr=tr,
|
|
136
|
+
a=sr,
|
|
137
|
+
wmode=NLMeans.WeightMode.BISQUARE_HR, # wmode=3
|
|
138
|
+
planes=[1, 2],
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class SceneBasedCambi(SceneBasedDynamicCache):
|
|
143
|
+
def __init__(
|
|
144
|
+
self,
|
|
145
|
+
clip: vs.VideoNode,
|
|
146
|
+
keyframes: Keyframes | str,
|
|
147
|
+
cache_size: int = 5,
|
|
148
|
+
) -> None:
|
|
149
|
+
super().__init__(core.cambi.Cambi(clip), keyframes, cache_size)
|
|
150
|
+
|
|
151
|
+
@override
|
|
152
|
+
def get_clip(self, key: int) -> vs.VideoNode:
|
|
153
|
+
frame_range = self.keyframes.scenes[key]
|
|
154
|
+
cut = self.clip[frame_range.start : frame_range.stop]
|
|
155
|
+
frames_cambis = clip_data_gather(
|
|
156
|
+
cut, None, lambda _, f: get_prop(f, "CAMBI", float)
|
|
157
|
+
)
|
|
158
|
+
avg_cambi = fmean(frames_cambis)
|
|
159
|
+
return self.clip.std.SetFrameProps(Scene_Avg_CAMBI=avg_cambi)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class SceneChangeMode(CustomIntEnum):
|
|
163
|
+
"""Enum for various scene change modes."""
|
|
164
|
+
|
|
165
|
+
WWXD = 1
|
|
166
|
+
"""Get the scene changes using the vapoursynth-wwxd plugin."""
|
|
167
|
+
|
|
168
|
+
SCXVID = 2
|
|
169
|
+
"""Get the scene changes using the vapoursynth-scxvid plugin."""
|
|
170
|
+
|
|
171
|
+
WWXD_SCXVID_UNION = 3 # WWXD | SCXVID
|
|
172
|
+
"""Get every scene change detected by both wwxd or scxvid."""
|
|
173
|
+
|
|
174
|
+
WWXD_SCXVID_INTERSECTION = 0 # WWXD & SCXVID
|
|
175
|
+
"""
|
|
176
|
+
Only get the scene changes if both wwxd and scxvid mark a frame as being a
|
|
177
|
+
scene change.
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def is_WWXD(self) -> bool: # noqa: N802
|
|
182
|
+
"""Check whether a mode that uses wwxd is used."""
|
|
183
|
+
|
|
184
|
+
return self in (
|
|
185
|
+
SceneChangeMode.WWXD,
|
|
186
|
+
SceneChangeMode.WWXD_SCXVID_UNION,
|
|
187
|
+
SceneChangeMode.WWXD_SCXVID_INTERSECTION,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
def is_SCXVID(self) -> bool: # noqa: N802
|
|
192
|
+
"""Check whether a mode that uses scxvid is used."""
|
|
193
|
+
|
|
194
|
+
return self in (
|
|
195
|
+
SceneChangeMode.SCXVID,
|
|
196
|
+
SceneChangeMode.WWXD_SCXVID_UNION,
|
|
197
|
+
SceneChangeMode.WWXD_SCXVID_INTERSECTION,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def ensure_presence(self, clip: vs.VideoNode) -> vs.VideoNode:
|
|
201
|
+
"""
|
|
202
|
+
Ensures all the frame properties necessary for scene change detection
|
|
203
|
+
are created.
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
stats_clip = list[vs.VideoNode]()
|
|
207
|
+
|
|
208
|
+
if self.is_SCXVID:
|
|
209
|
+
if not hasattr(vs.core, "scxvid"):
|
|
210
|
+
raise CustomRuntimeError("Missing scxvid plugin.", self.ensure_presence)
|
|
211
|
+
stats_clip.append(clip.scxvid.Scxvid())
|
|
212
|
+
|
|
213
|
+
if self.is_WWXD:
|
|
214
|
+
if not hasattr(vs.core, "wwxd"):
|
|
215
|
+
raise CustomRuntimeError("Missing wwxd plugin.", self.ensure_presence)
|
|
216
|
+
stats_clip.append(clip.wwxd.WWXD())
|
|
217
|
+
|
|
218
|
+
keys = tuple(self.prop_keys)
|
|
219
|
+
|
|
220
|
+
expr = " ".join([f"x.{k}" for k in keys]) + (" and" * (len(keys) - 1))
|
|
221
|
+
|
|
222
|
+
blank = clip.std.BlankClip(1, 1, vs.GRAY8, keep=True)
|
|
223
|
+
|
|
224
|
+
if len(stats_clip) > 1:
|
|
225
|
+
return merge_clip_props(blank, *stats_clip).akarin.Expr(expr)
|
|
226
|
+
|
|
227
|
+
return blank.std.CopyFrameProps(stats_clip[0]).akarin.Expr(expr)
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def prop_keys(self) -> Iterator[str]:
|
|
231
|
+
if self.is_WWXD:
|
|
232
|
+
yield "Scenechange"
|
|
233
|
+
|
|
234
|
+
if self.is_SCXVID:
|
|
235
|
+
yield "_SceneChangePrev"
|
|
236
|
+
|
|
237
|
+
def lambda_cb(self) -> Callable[[int, vs.VideoFrame], SentinelT | int]:
|
|
238
|
+
return lambda n, f: Sentinel.check(n, bool(f[0][0, 0]))
|
|
239
|
+
|
|
240
|
+
def prepare_clip(
|
|
241
|
+
self, clip: vs.VideoNode, height: int | Literal[False] = 360
|
|
242
|
+
) -> vs.VideoNode:
|
|
243
|
+
"""
|
|
244
|
+
Prepare a clip for scene change metric calculations.
|
|
245
|
+
|
|
246
|
+
The clip will always be resampled to YUV420 8bit if it's not already,
|
|
247
|
+
as that's what the plugins support.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
clip: Clip to process.
|
|
251
|
+
height: Output height of the clip. Smaller frame sizes are faster to
|
|
252
|
+
process, but may miss more scene changes or introduce more false
|
|
253
|
+
positives. Width is automatically calculated. `False` means no
|
|
254
|
+
resizing operation is performed. Default: 360.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
A prepared clip for performing scene change metric calculations on.
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
if height:
|
|
261
|
+
clip = clip.resize.Bilinear(get_w(height, clip), height, vs.YUV420P8)
|
|
262
|
+
elif not clip.format or (clip.format and clip.format.id != vs.YUV420P8):
|
|
263
|
+
clip = clip.resize.Bilinear(format=vs.YUV420P8)
|
|
264
|
+
|
|
265
|
+
return self.ensure_presence(clip)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class Keyframes(JetpackKeyframes):
|
|
269
|
+
"""
|
|
270
|
+
Class representing keyframes, or scenechanges.
|
|
271
|
+
|
|
272
|
+
They follow the convention of signaling the start of the new scene.
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
@classmethod
|
|
276
|
+
@override
|
|
277
|
+
def from_clip(
|
|
278
|
+
cls,
|
|
279
|
+
clip: vs.VideoNode,
|
|
280
|
+
mode: SceneChangeMode = SceneChangeMode.SCXVID,
|
|
281
|
+
height: int | Literal[False] = 360,
|
|
282
|
+
**kwargs: Any,
|
|
283
|
+
) -> Self:
|
|
284
|
+
mode = SceneChangeMode(mode)
|
|
285
|
+
|
|
286
|
+
clip = mode.prepare_clip(clip, height)
|
|
287
|
+
|
|
288
|
+
frames = clip_async_render(
|
|
289
|
+
clip, None, "Detecting scene changes...", mode.lambda_cb(), **kwargs
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
return cls(Sentinel.filter(frames))
|
|
293
|
+
|
|
294
|
+
def to_clip(
|
|
295
|
+
self,
|
|
296
|
+
clip: vs.VideoNode,
|
|
297
|
+
*,
|
|
298
|
+
prop_key: str = next(iter(SceneChangeMode.SCXVID.prop_keys)),
|
|
299
|
+
scene_idx_prop: bool = False,
|
|
300
|
+
) -> vs.VideoNode:
|
|
301
|
+
propset_clip = clip.std.SetFrameProp(prop_key, True)
|
|
302
|
+
|
|
303
|
+
out = replace_ranges(clip, propset_clip, self)
|
|
304
|
+
|
|
305
|
+
if not scene_idx_prop:
|
|
306
|
+
return out
|
|
307
|
+
|
|
308
|
+
def _add_scene_idx(n: int, f: vs.VideoFrame) -> vs.VideoFrame:
|
|
309
|
+
f = f.copy()
|
|
310
|
+
|
|
311
|
+
f.props._SceneIdx = self.scenes.indices[n]
|
|
312
|
+
|
|
313
|
+
return f
|
|
314
|
+
|
|
315
|
+
return out.std.ModifyFrame(out, _add_scene_idx)
|
|
File without changes
|