GameSentenceMiner 2.14.16__py3-none-any.whl → 2.14.18__py3-none-any.whl

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.
@@ -27,6 +27,148 @@ supported_formats = {
27
27
  'm4a': 'aac',
28
28
  }
29
29
 
30
+ import subprocess
31
+ from pathlib import Path
32
+ import shutil
33
+
34
+
35
+ def video_to_anim(
36
+ input_path: str | Path,
37
+ output_path: str | Path = None,
38
+ codec: str = "webp", # "webp" or "avif" (ignored if audio=True)
39
+ start: str = None, # e.g. "00:00:12.5"
40
+ duration: float = None, # seconds
41
+ fps: int = 12,
42
+ max_width: int = 960,
43
+ max_height: int = None,
44
+ quality: int = 65, # 0..100, for avif: 0 (lossless) to 63 (worst), for webm: CRF value
45
+ compression_level: int = 6, # for webp: 0..6 (ignored for audio)
46
+ preset: str = "picture", # for webp (ignored for audio)
47
+ loop: int = 0, # for webp: 0=infinite (ignored for audio)
48
+ crop: str = None, # e.g. "1280:720:0:140"
49
+ extra_vf: list[str] = None,
50
+ audio: bool = None # whether to include audio, outputs WebM with VP9/Opus
51
+ ) -> Path:
52
+ """Convert video to efficient animated WebP/AVIF or WebM with audio using ffmpeg.
53
+
54
+ When audio=True, outputs WebM format with VP9 video codec and Opus audio codec.
55
+ The codec parameter is ignored when audio=True.
56
+ """
57
+
58
+ if shutil.which("ffmpeg") is None:
59
+ raise RuntimeError("ffmpeg not found on PATH.")
60
+
61
+ codec = codec.lower()
62
+ if codec not in {"webp", "avif"}:
63
+ raise ValueError("codec must be 'webp' or 'avif'")
64
+
65
+ input_path = Path(input_path)
66
+ if not input_path.exists():
67
+ raise FileNotFoundError(f"Input not found: {input_path}")
68
+
69
+ # Default output path
70
+ if output_path:
71
+ output_path = Path(output_path)
72
+ else:
73
+ if audio:
74
+ ext = ".webm"
75
+ else:
76
+ ext = ".webp" if codec == "webp" else ".avif"
77
+ output_path = input_path.with_suffix(ext)
78
+
79
+ # Ensure correct extension
80
+ if audio:
81
+ correct_ext = ".webm"
82
+ else:
83
+ correct_ext = ".webp" if codec == "webp" else ".avif"
84
+ if output_path.suffix.lower() != correct_ext:
85
+ output_path = output_path.with_suffix(correct_ext)
86
+
87
+ # Build filter chain
88
+ vf_parts = []
89
+ if fps:
90
+ vf_parts.append(f"fps={fps}")
91
+ if crop:
92
+ vf_parts.append(f"crop={crop}")
93
+ if max_width and max_height:
94
+ vf_parts.append(f"scale='min({max_width},iw)':min({max_height},ih):force_original_aspect_ratio=decrease")
95
+ elif max_width:
96
+ vf_parts.append(f"scale={max_width}:-1")
97
+ elif max_height:
98
+ vf_parts.append(f"scale=-1:{max_height}")
99
+ vf_parts.append("pad=ceil(iw/2)*2:ceil(ih/2)*2") # ensure even dimensions
100
+ if extra_vf:
101
+ vf_parts.extend(extra_vf)
102
+
103
+ # ffmpeg command base
104
+ cmd = ffmpeg_base_command_list.copy()
105
+ if start:
106
+ cmd += ["-ss", str(start)]
107
+ cmd += ["-i", str(input_path)]
108
+ if duration:
109
+ cmd += ["-t", str(duration)]
110
+
111
+ # Add video filters
112
+ cmd += ["-vf", ",".join(vf_parts)]
113
+
114
+ # Only add -an (no audio) if we're not including audio
115
+ if not audio:
116
+ cmd += ["-an"]
117
+
118
+ # Codec-specific settings
119
+ if audio:
120
+ # For WebM with audio, use VP9 for video and Opus for audio
121
+ # For WebM with audio, use AV1 (AVIF) for video and Opus for audio
122
+ cmd += [
123
+ "-c:v", "libaom-av1", # AV1 codec (used for AVIF images, but supported in WebM video)
124
+ "-crf", str(quality), # AV1 CRF scale (0 lossless - 63 worst)
125
+ "-pix_fmt", "yuv420p", # yuv420p for compatibility
126
+ "-cpu-used", "6", # speed/quality trade-off
127
+ "-c:a", "libopus", # use opus codec for audio
128
+ "-b:a", "128k", # audio bitrate
129
+ "-f", "webm", # output format webm
130
+ ]
131
+ elif codec == "webp":
132
+ cmd += [
133
+ "-c:v", "libwebp",
134
+ "-lossless", "0",
135
+ "-q:v", str(quality),
136
+ "-compression_level", str(compression_level),
137
+ "-preset", preset,
138
+ "-loop", str(loop),
139
+ "-threads", "0",
140
+ ]
141
+ elif codec == "avif":
142
+ cmd += [
143
+ "-c:v", "libaom-av1",
144
+ "-cpu-used", "6", # speed/quality trade-off
145
+ "-crf", str(quality), # AV1 CRF scale (0 lossless - 63 worst)
146
+ "-pix_fmt", "yuv420p", # yuv420p for better compatibility
147
+ ]
148
+
149
+ cmd.append(str(output_path))
150
+
151
+ subprocess.run(cmd, check=True)
152
+ return str(output_path)
153
+
154
+ def video_to_animation_with_start_end(video_path: str | Path, start: float, end: float, **kwargs) -> Path:
155
+ """Convert video to animation using start and end time strings."""
156
+ from datetime import datetime, timedelta
157
+
158
+ if end < start:
159
+ raise ValueError("end time must be after start time")
160
+ duration = end - start
161
+
162
+ return video_to_anim(
163
+ input_path=video_path,
164
+ start=start,
165
+ duration=duration,
166
+ **kwargs
167
+ )
168
+
169
+
170
+ # video_to_anim(r"C:\Users\Beangate\Videos\GSM\Output\ゴシップ\trimmed_GSM 2025-08-14 21-57-08_2025-08-14-21-57-12-654.mp4", codec="avif", quality=30, fps=30)
171
+
30
172
  def call_frame_extractor(video_path, timestamp):
31
173
  """
32
174
  Calls the video frame extractor script and captures the output.
@@ -64,6 +206,17 @@ def call_frame_extractor(video_path, timestamp):
64
206
  logger.error(f"An unexpected error occurred: {e}")
65
207
  return None
66
208
 
209
+ # def get_animated_screenshot(video_file, screenshot_timing, vad_start, vad_end):
210
+ # screenshot_timing = screenshot_timing if screenshot_timing else 1
211
+ # animated_ss = video_to_animation_with_start_end(video_file, screenshot_timing + vad_start, screenshot_timing + vad_end)
212
+ # return animated_ss
213
+
214
+ def get_anki_compatible_video(video_file, screenshot_timing, vad_start, vad_end, **kwargs):
215
+ screenshot_timing = screenshot_timing if screenshot_timing else 1
216
+ animated_ss = video_to_animation_with_start_end(video_file, screenshot_timing + vad_start, screenshot_timing + vad_end, **kwargs)
217
+ return animated_ss
218
+
219
+
67
220
  def get_screenshot(video_file, screenshot_timing, try_selector=False):
68
221
  screenshot_timing = screenshot_timing if screenshot_timing else 1
69
222
  if try_selector: