videopython 0.55.1__tar.gz → 0.55.2__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.
- {videopython-0.55.1 → videopython-0.55.2}/PKG-INFO +1 -1
- {videopython-0.55.1 → videopython-0.55.2}/pyproject.toml +1 -1
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/audio/audio.py +106 -65
- {videopython-0.55.1 → videopython-0.55.2}/.gitignore +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/LICENSE +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/README.md +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/__init__.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/__init__.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/_device.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/_ollama.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/_optional.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/_predictor.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/_revisions.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/auto_edit/__init__.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/auto_edit/backend.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/auto_edit/catalog.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/auto_edit/editor.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/auto_edit/local.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/auto_edit/models.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/auto_edit/resolve.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/dubbing/__init__.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/dubbing/_tts_backend.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/dubbing/audio_ops.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/dubbing/config.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/dubbing/dubber.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/dubbing/models.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/dubbing/pipeline.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/dubbing/quality.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/dubbing/remux.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/dubbing/separation.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/dubbing/timing.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/dubbing/translation.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/dubbing/voice_sample.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/effects.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/errors.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/generation/__init__.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/generation/audio.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/generation/image.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/generation/video.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/keyframe.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/ops.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/transforms.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/understanding/__init__.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/understanding/_detector.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/understanding/audio.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/understanding/classification.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/understanding/faces.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/understanding/image.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/understanding/objects.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/understanding/temporal.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/video_analysis/__init__.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/video_analysis/analyzer.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/video_analysis/detectors.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/video_analysis/models.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/video_analysis/sampling.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/video_analysis/source_metadata.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/audio/__init__.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/audio/analysis.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/base/__init__.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/base/_dimensions.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/base/_ffmpeg.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/base/_video_io.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/base/description.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/base/draw_detections.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/base/exceptions.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/base/fonts/Anton-OFL.txt +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/base/fonts/Anton-Regular.ttf +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/base/fonts/BebasNeue-OFL.txt +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/base/fonts/BebasNeue-Regular.ttf +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/base/fonts/DejaVuSans.ttf +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/base/fonts/LICENSE_DEJAVU +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/base/fonts/Lato-Bold.ttf +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/base/fonts/Lato-OFL.txt +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/base/fonts/Poppins-Bold.ttf +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/base/fonts/Poppins-OFL.txt +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/base/fonts/__init__.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/base/transcription.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/base/video.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/editing/__init__.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/editing/_ass.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/editing/_easing.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/editing/_schema.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/editing/audio_ops.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/editing/effects.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/editing/operation.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/editing/streaming.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/editing/transcription_overlay.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/editing/transforms.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/editing/video_edit.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/mcp/__init__.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/mcp/server.py +0 -0
- {videopython-0.55.1 → videopython-0.55.2}/src/videopython/py.typed +0 -0
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import io
|
|
4
4
|
import subprocess
|
|
5
|
+
import tempfile
|
|
5
6
|
import wave
|
|
6
7
|
from dataclasses import dataclass
|
|
7
8
|
from pathlib import Path
|
|
@@ -16,6 +17,17 @@ from videopython.base.exceptions import AudioLoadError, FFmpegProbeError
|
|
|
16
17
|
if TYPE_CHECKING:
|
|
17
18
|
from videopython.audio.analysis import AudioLevels, AudioSegment, AudioSegmentType, SilentSegment
|
|
18
19
|
|
|
20
|
+
# What `Audio.from_path` asks ffmpeg for. Piped WAV comes back as pcm_s16le whatever
|
|
21
|
+
# the source's bit depth, so requesting raw s16le loses no fidelity that the WAV path
|
|
22
|
+
# was preserving, and there is no header to parse back.
|
|
23
|
+
_PCM_FORMAT = "s16le"
|
|
24
|
+
_PCM_DTYPE = np.int16
|
|
25
|
+
_PCM_SAMPLE_WIDTH = 2
|
|
26
|
+
|
|
27
|
+
# Read size when draining ffmpeg's stdout: big enough that a multi-GB decode is not
|
|
28
|
+
# millions of round trips, small enough to be irrelevant for a short clip.
|
|
29
|
+
_DECODE_CHUNK_BYTES = 8 << 20
|
|
30
|
+
|
|
19
31
|
|
|
20
32
|
def atempo_chain(speed: float) -> list[str]:
|
|
21
33
|
"""Build the ``atempo`` filter chain that time-stretches audio by ``speed``.
|
|
@@ -163,100 +175,131 @@ class Audio:
|
|
|
163
175
|
return cls(data, metadata)
|
|
164
176
|
|
|
165
177
|
@classmethod
|
|
166
|
-
def from_path(
|
|
178
|
+
def from_path(
|
|
179
|
+
cls,
|
|
180
|
+
file_path: str | Path,
|
|
181
|
+
*,
|
|
182
|
+
sample_rate: int | None = None,
|
|
183
|
+
channels: int | None = None,
|
|
184
|
+
) -> Audio:
|
|
167
185
|
"""
|
|
168
|
-
Load audio from a file using ffmpeg
|
|
186
|
+
Load audio from a file using ffmpeg.
|
|
187
|
+
|
|
188
|
+
``sample_rate`` and ``channels`` ask ffmpeg to convert *while decoding*
|
|
189
|
+
rather than loading the source in full and converting afterwards. For a
|
|
190
|
+
caller that only wants 16kHz mono -- speech recognition, diarization,
|
|
191
|
+
speaker embeddings -- that is the difference between holding the whole
|
|
192
|
+
source in memory and holding a twelfth of it: a 12-hour 48kHz stereo
|
|
193
|
+
recording is 16.5GB of float32 at source rate and 1.4GB at 16kHz mono.
|
|
194
|
+
Resampling uses soxr, the engine :meth:`resample` uses, so the result
|
|
195
|
+
tracks ``Audio.from_path(p).to_mono().resample(r)`` sample for sample at
|
|
196
|
+
an error RMS around one 16-bit LSB -- the two quantize at different points
|
|
197
|
+
in the chain, and neither is the more faithful for it.
|
|
169
198
|
|
|
170
199
|
Args:
|
|
171
200
|
file_path: Path to the audio file
|
|
201
|
+
sample_rate: Decode at this rate instead of the source's.
|
|
202
|
+
channels: Decode to this many channels instead of the source's.
|
|
203
|
+
``1`` downmixes to mono.
|
|
172
204
|
|
|
173
205
|
Returns:
|
|
174
206
|
Audio: New Audio instance
|
|
175
207
|
|
|
176
208
|
Raises:
|
|
177
209
|
FileNotFoundError: If the file doesn't exist
|
|
210
|
+
ValueError: If ``sample_rate`` or ``channels`` is not positive
|
|
178
211
|
AudioLoadError: If there's an error loading the audio
|
|
179
212
|
"""
|
|
180
213
|
file_path = Path(file_path)
|
|
181
214
|
if not file_path.exists():
|
|
182
215
|
raise FileNotFoundError(f"File not found: {file_path}")
|
|
216
|
+
if sample_rate is not None and sample_rate <= 0:
|
|
217
|
+
raise ValueError("Sample rate must be positive")
|
|
218
|
+
if channels is not None and channels <= 0:
|
|
219
|
+
raise ValueError("Channel count must be positive")
|
|
183
220
|
|
|
184
|
-
# Get audio info
|
|
185
221
|
info = cls._get_ffmpeg_info(file_path)
|
|
186
|
-
|
|
187
|
-
|
|
222
|
+
target_rate = info["sample_rate"] if sample_rate is None else sample_rate
|
|
223
|
+
target_channels = info["channels"] if channels is None else channels
|
|
224
|
+
|
|
225
|
+
# Raw PCM rather than a WAV round-trip. Piped WAV comes back as pcm_s16le
|
|
226
|
+
# whatever the source's bit depth -- `-bits_per_raw_sample` is a hint the
|
|
227
|
+
# WAV muxer does not act on -- and its header carries a placeholder length
|
|
228
|
+
# because a pipe is not seekable. So parsing it back told us only what we
|
|
229
|
+
# had already asked for, and cost two more full copies of the audio on the
|
|
230
|
+
# way: one for `BytesIO`, one for `readframes`.
|
|
188
231
|
cmd = [
|
|
189
232
|
"ffmpeg",
|
|
233
|
+
"-v",
|
|
234
|
+
"error",
|
|
190
235
|
"-i",
|
|
191
236
|
str(file_path),
|
|
192
237
|
"-f",
|
|
193
|
-
|
|
238
|
+
_PCM_FORMAT,
|
|
194
239
|
"-ar",
|
|
195
|
-
str(
|
|
240
|
+
str(target_rate),
|
|
196
241
|
"-ac",
|
|
197
|
-
str(
|
|
198
|
-
|
|
199
|
-
|
|
242
|
+
str(target_channels),
|
|
243
|
+
# soxr rather than ffmpeg's default resampler, so that decoding at a
|
|
244
|
+
# rate and resampling to it afterwards agree.
|
|
245
|
+
"-af",
|
|
246
|
+
"aresample=resampler=soxr",
|
|
200
247
|
"-", # Output to stdout
|
|
201
248
|
]
|
|
202
249
|
|
|
203
250
|
try:
|
|
204
|
-
|
|
205
|
-
|
|
251
|
+
# stderr to a file, not a pipe: stdout is drained to completion before
|
|
252
|
+
# stderr is read, and a full stderr pipe would deadlock ffmpeg partway
|
|
253
|
+
# through the audio.
|
|
254
|
+
with tempfile.TemporaryFile() as errors:
|
|
255
|
+
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=errors)
|
|
256
|
+
assert process.stdout is not None
|
|
257
|
+
# A bytearray rather than `communicate()`, which accumulates chunks
|
|
258
|
+
# in a list and joins them at the end -- holding the whole of a long
|
|
259
|
+
# decode twice at the moment it completes.
|
|
260
|
+
raw = bytearray()
|
|
261
|
+
try:
|
|
262
|
+
while chunk := process.stdout.read(_DECODE_CHUNK_BYTES):
|
|
263
|
+
raw += chunk
|
|
264
|
+
finally:
|
|
265
|
+
process.stdout.close()
|
|
266
|
+
if process.wait() != 0:
|
|
267
|
+
errors.seek(0)
|
|
268
|
+
raise AudioLoadError(f"FFmpeg error: {errors.read().decode(errors='replace')}")
|
|
269
|
+
except subprocess.CalledProcessError as e:
|
|
270
|
+
raise AudioLoadError(f"Error running ffmpeg: {e}")
|
|
206
271
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
with io.BytesIO(wav_data) as wav_io:
|
|
212
|
-
with wave.open(wav_io, "rb") as wav_file:
|
|
213
|
-
# Get WAV metadata
|
|
214
|
-
sample_width = wav_file.getsampwidth()
|
|
215
|
-
channels = wav_file.getnchannels()
|
|
216
|
-
sample_rate = wav_file.getframerate()
|
|
217
|
-
n_frames = wav_file.getnframes()
|
|
218
|
-
|
|
219
|
-
# Read raw audio data
|
|
220
|
-
raw_data = wav_file.readframes(n_frames)
|
|
221
|
-
|
|
222
|
-
# Convert bytes to numpy array based on sample width
|
|
223
|
-
dtype_map = {1: np.int8, 2: np.int16, 4: np.int32}
|
|
224
|
-
dtype = dtype_map.get(sample_width)
|
|
225
|
-
if dtype is None:
|
|
226
|
-
raise AudioLoadError(f"Unsupported sample width: {sample_width}")
|
|
227
|
-
|
|
228
|
-
# Explicitly annotated: numpy>=2.5 shape-types ndarray, so the
|
|
229
|
-
# 1-D frombuffer result cannot be rebound to a 2-D view below.
|
|
230
|
-
data: np.ndarray[Any, np.dtype[np.float32]]
|
|
231
|
-
data = np.frombuffer(raw_data, dtype=dtype).astype(np.float32)
|
|
232
|
-
|
|
233
|
-
# Reshape to (frames, channels) if stereo
|
|
234
|
-
if channels == 2:
|
|
235
|
-
data = data.reshape(-1, 2)
|
|
236
|
-
|
|
237
|
-
# Normalize to float between -1 and 1
|
|
238
|
-
max_value = float(np.iinfo(dtype).max) # type: ignore
|
|
239
|
-
data = data / max_value
|
|
240
|
-
|
|
241
|
-
# Ensure normalization is within bounds due to floating point precision
|
|
242
|
-
data = np.clip(data, -1.0, 1.0)
|
|
243
|
-
|
|
244
|
-
# Calculate frame count from actual data length
|
|
245
|
-
# For stereo, len(data) is already correct after reshape
|
|
246
|
-
frame_count = len(data)
|
|
247
|
-
|
|
248
|
-
metadata = AudioMetadata(
|
|
249
|
-
sample_rate=sample_rate,
|
|
250
|
-
channels=channels,
|
|
251
|
-
sample_width=sample_width,
|
|
252
|
-
duration_seconds=info["duration"],
|
|
253
|
-
frame_count=frame_count,
|
|
254
|
-
)
|
|
272
|
+
# A truncated final frame would otherwise make `frombuffer` raise on a file
|
|
273
|
+
# that is entirely usable up to that point.
|
|
274
|
+
frame_bytes = _PCM_SAMPLE_WIDTH * target_channels
|
|
275
|
+
usable = len(raw) - (len(raw) % frame_bytes)
|
|
255
276
|
|
|
256
|
-
|
|
277
|
+
# Explicitly annotated: numpy>=2.5 shape-types ndarray, so the
|
|
278
|
+
# 1-D frombuffer result cannot be rebound to a 2-D view below.
|
|
279
|
+
data: np.ndarray[Any, np.dtype[np.float32]]
|
|
280
|
+
data = np.frombuffer(memoryview(raw)[:usable], dtype=_PCM_DTYPE).astype(np.float32)
|
|
257
281
|
|
|
258
|
-
|
|
259
|
-
|
|
282
|
+
# Reshape to (frames, channels) if stereo
|
|
283
|
+
if target_channels == 2:
|
|
284
|
+
data = data.reshape(-1, 2)
|
|
285
|
+
|
|
286
|
+
# Normalize to float between -1 and 1, and clamp for floating-point
|
|
287
|
+
# precision. Both in place: at these sizes a copy per step is most of what
|
|
288
|
+
# makes decoding a long file expensive.
|
|
289
|
+
data /= float(np.iinfo(_PCM_DTYPE).max)
|
|
290
|
+
np.clip(data, -1.0, 1.0, out=data)
|
|
291
|
+
|
|
292
|
+
# Calculate frame count from actual data length
|
|
293
|
+
# For stereo, len(data) is already correct after reshape
|
|
294
|
+
metadata = AudioMetadata(
|
|
295
|
+
sample_rate=target_rate,
|
|
296
|
+
channels=target_channels,
|
|
297
|
+
sample_width=_PCM_SAMPLE_WIDTH,
|
|
298
|
+
duration_seconds=info["duration"],
|
|
299
|
+
frame_count=len(data),
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
return cls(data, metadata)
|
|
260
303
|
|
|
261
304
|
@classmethod
|
|
262
305
|
def from_file(cls, file_path: str | Path) -> Audio:
|
|
@@ -653,8 +696,6 @@ class Audio:
|
|
|
653
696
|
filter_str = ",".join(filters) if filters else "anull"
|
|
654
697
|
|
|
655
698
|
# Save current audio to temp WAV, process with ffmpeg, read back
|
|
656
|
-
import tempfile
|
|
657
|
-
|
|
658
699
|
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as input_file:
|
|
659
700
|
input_path = input_file.name
|
|
660
701
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/understanding/classification.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{videopython-0.55.1 → videopython-0.55.2}/src/videopython/ai/video_analysis/source_metadata.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|