yta-editor 0.0.1__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.
- yta_editor-0.0.1/LICENSE +19 -0
- yta_editor-0.0.1/PKG-INFO +22 -0
- yta_editor-0.0.1/README.md +3 -0
- yta_editor-0.0.1/pyproject.toml +32 -0
- yta_editor-0.0.1/src/yta_editor/__init__.py +41 -0
- yta_editor-0.0.1/src/yta_editor/decorators.py +33 -0
- yta_editor-0.0.1/src/yta_editor/media/__init__.py +16 -0
- yta_editor-0.0.1/src/yta_editor/media/abstract.py +160 -0
- yta_editor-0.0.1/src/yta_editor/media/audio/__init__.py +140 -0
- yta_editor-0.0.1/src/yta_editor/media/video/__init__.py +354 -0
- yta_editor-0.0.1/src/yta_editor/sources/__init__.py +0 -0
- yta_editor-0.0.1/src/yta_editor/sources/abstract.py +107 -0
- yta_editor-0.0.1/src/yta_editor/sources/audio/__init__.py +191 -0
- yta_editor-0.0.1/src/yta_editor/sources/video/__init__.py +457 -0
- yta_editor-0.0.1/src/yta_editor/tests.py +158 -0
- yta_editor-0.0.1/src/yta_editor/timeline.py +566 -0
- yta_editor-0.0.1/src/yta_editor/track/__init__.py +18 -0
- yta_editor-0.0.1/src/yta_editor/track/abstract.py +421 -0
- yta_editor-0.0.1/src/yta_editor/track/audio/__init__.py +44 -0
- yta_editor-0.0.1/src/yta_editor/track/media/__init__.py +11 -0
- yta_editor-0.0.1/src/yta_editor/track/media/abstract.py +174 -0
- yta_editor-0.0.1/src/yta_editor/track/media/audio.py +25 -0
- yta_editor-0.0.1/src/yta_editor/track/media/video.py +24 -0
- yta_editor-0.0.1/src/yta_editor/track/parts/__init__.py +21 -0
- yta_editor-0.0.1/src/yta_editor/track/parts/abstract.py +279 -0
- yta_editor-0.0.1/src/yta_editor/track/parts/audio.py +27 -0
- yta_editor-0.0.1/src/yta_editor/track/parts/video.py +27 -0
- yta_editor-0.0.1/src/yta_editor/track/video/__init__.py +61 -0
- yta_editor-0.0.1/src/yta_editor/utils/__init__.py +218 -0
- yta_editor-0.0.1/src/yta_editor/utils/frame_combinator.py +228 -0
- yta_editor-0.0.1/src/yta_editor/utils/frame_generator.py +319 -0
- yta_editor-0.0.1/src/yta_editor/utils/frame_wrapper.py +135 -0
yta_editor-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2018 The Python Packaging Authority
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: yta-editor
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Youtube Autonomous Main Editor
|
|
5
|
+
Author: danialcala94
|
|
6
|
+
Author-email: danielalcalavalera@gmail.com
|
|
7
|
+
Requires-Python: ==3.9
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Requires-Dist: av (>=0.0.1,<19.0.0)
|
|
11
|
+
Requires-Dist: numpy (>=0.0.1,<9.0.0)
|
|
12
|
+
Requires-Dist: pillow (>=0.0.1,<99.0.0)
|
|
13
|
+
Requires-Dist: quicktions (>=0.0.1,<9.0.0)
|
|
14
|
+
Requires-Dist: yta_validation (>=0.0.1,<1.0.0)
|
|
15
|
+
Requires-Dist: yta_video_frame_time (>=0.0.1,<1.0.0)
|
|
16
|
+
Requires-Dist: yta_video_opengl (>=0.0.1,<1.0.0)
|
|
17
|
+
Requires-Dist: yta_video_pyav (>=0.0.1,<1.0.0)
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# Youtube Autonomous Main Editor
|
|
21
|
+
|
|
22
|
+
The main Editor that works using PyAv and OpenGL.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "yta-editor"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "Youtube Autonomous Main Editor"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "danialcala94",email = "danielalcalavalera@gmail.com"}
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = "==3.9"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"yta_validation (>=0.0.1,<1.0.0)",
|
|
12
|
+
"yta_video_frame_time (>=0.0.1,<1.0.0)",
|
|
13
|
+
"yta_video_opengl (>=0.0.1,<1.0.0)",
|
|
14
|
+
"yta_video_pyav (>=0.0.1,<1.0.0)",
|
|
15
|
+
"av (>=0.0.1,<19.0.0)",
|
|
16
|
+
"numpy (>=0.0.1,<9.0.0)",
|
|
17
|
+
"quicktions (>=0.0.1,<9.0.0)",
|
|
18
|
+
# I need this 'pillow' just to read an image
|
|
19
|
+
# and transform into a numpy array to be able
|
|
20
|
+
# to handle the ImageMedia
|
|
21
|
+
"pillow (>=0.0.1,<99.0.0)",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[tool.poetry]
|
|
25
|
+
packages = [{include = "yta_editor", from = "src"}]
|
|
26
|
+
|
|
27
|
+
[tool.poetry.group.dev.dependencies]
|
|
28
|
+
pytest = "^8.3.5"
|
|
29
|
+
|
|
30
|
+
[build-system]
|
|
31
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
32
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Our awesome editor module in which we
|
|
3
|
+
have all the classes that interact with
|
|
4
|
+
it and make it possible.
|
|
5
|
+
|
|
6
|
+
An editor includes a single timeline,
|
|
7
|
+
built by tracks, in which we place media
|
|
8
|
+
elements and we are able to apply effects
|
|
9
|
+
to them.
|
|
10
|
+
|
|
11
|
+
Here is a brief explanation about the
|
|
12
|
+
hierarchy:
|
|
13
|
+
|
|
14
|
+
- The editor has a timeline.
|
|
15
|
+
- The timeline has audio and video tracks
|
|
16
|
+
(that can handle when we play or not the
|
|
17
|
+
audio and video).
|
|
18
|
+
- The tracks have parts, that are virtual
|
|
19
|
+
items to simplify the way we combine
|
|
20
|
+
tracks. Those parts include media
|
|
21
|
+
instances. The tracks have priorities
|
|
22
|
+
ones against the others.
|
|
23
|
+
- The media instances have a time range
|
|
24
|
+
in which they must be played within the
|
|
25
|
+
track (within the timeline). Those media
|
|
26
|
+
instances have the start and end time
|
|
27
|
+
range for the media source and can apply
|
|
28
|
+
effects to the frames.
|
|
29
|
+
- The media sources are just the way we
|
|
30
|
+
obtain the frames for the specific media
|
|
31
|
+
items (read a file, constant color, etc.).
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def main():
|
|
35
|
+
from yta_editor.tests import video_modified_stored
|
|
36
|
+
|
|
37
|
+
video_modified_stored()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == '__main__':
|
|
41
|
+
main()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def with_t_adjusted_to_media(
|
|
5
|
+
method
|
|
6
|
+
):
|
|
7
|
+
"""
|
|
8
|
+
Get the real 't' time moment based on the
|
|
9
|
+
video 'start' and 'end'. If they were
|
|
10
|
+
asking for the t=0.5s but our video was
|
|
11
|
+
subclipped to [1.0, 2.0), the 0.5s must be
|
|
12
|
+
actually the 1.5s of the video because of
|
|
13
|
+
the subclipped time range.
|
|
14
|
+
|
|
15
|
+
The formula:
|
|
16
|
+
- `t + self.start`
|
|
17
|
+
"""
|
|
18
|
+
@wraps(method)
|
|
19
|
+
def wrapper(
|
|
20
|
+
self,
|
|
21
|
+
t,
|
|
22
|
+
*args,
|
|
23
|
+
**kwargs
|
|
24
|
+
):
|
|
25
|
+
t += self.start
|
|
26
|
+
|
|
27
|
+
print(f'The video/audio real t is {str(float(t))}')
|
|
28
|
+
if t >= self.end:
|
|
29
|
+
raise Exception(f'The "t" ({str(t)}) provided is out of range. This video/audio lasts from [{str(self.start)}, {str(self.end)}).')
|
|
30
|
+
|
|
31
|
+
return method(self, t, *args, **kwargs)
|
|
32
|
+
|
|
33
|
+
return wrapper
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Media elements, that are the ones we will
|
|
3
|
+
use and place on the tracks, able to access
|
|
4
|
+
to the frames, using the different sources,
|
|
5
|
+
and to apply the effects we want.
|
|
6
|
+
"""
|
|
7
|
+
# from yta_editor.media.audio import AudioFileMedia
|
|
8
|
+
# from yta_editor.media.video import VideoFileMedia, VideoColorMedia, VideoImageMedia
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# __all__ = [
|
|
12
|
+
# 'AudioFileMedia',
|
|
13
|
+
# 'VideoFileMedia',
|
|
14
|
+
# 'VideoColorMedia',
|
|
15
|
+
# 'VideoImageMedia',
|
|
16
|
+
# ]
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from yta_video_opengl.effects import EffectsStack
|
|
2
|
+
from yta_validation.parameter import ParameterValidator
|
|
3
|
+
from quicktions import Fraction
|
|
4
|
+
from typing import Union
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _Media(ABC):
|
|
9
|
+
"""
|
|
10
|
+
Abstract class to be inherited by any
|
|
11
|
+
media element.
|
|
12
|
+
|
|
13
|
+
The media element is an element that
|
|
14
|
+
includes a source and a 'start' and
|
|
15
|
+
'end' values to be able to subclip that
|
|
16
|
+
media source and use only the part we
|
|
17
|
+
want to use.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def copy(
|
|
23
|
+
self
|
|
24
|
+
) -> '_Media':
|
|
25
|
+
"""
|
|
26
|
+
Get a copy of this instance with the same
|
|
27
|
+
source, time range and effects.
|
|
28
|
+
"""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def duration(
|
|
33
|
+
self
|
|
34
|
+
) -> Fraction:
|
|
35
|
+
"""
|
|
36
|
+
The duration of the media, that can be
|
|
37
|
+
shorter than the source duration if the
|
|
38
|
+
user requested it.
|
|
39
|
+
|
|
40
|
+
The formula:
|
|
41
|
+
- `self.end - self.start`
|
|
42
|
+
"""
|
|
43
|
+
return self.end - self.start
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
source: Union['AudioFileSource', 'AudioNumpySource', 'VideoFileSource', 'VideoColorSource', 'VideoImageSource', 'VideoNumpySource'],
|
|
48
|
+
start: Union[int, float, Fraction] = 0.0,
|
|
49
|
+
end: Union[int, float, Fraction, None] = None,
|
|
50
|
+
):
|
|
51
|
+
self.source: Union['AudioFileSource', 'AudioNumpySource', 'VideoFileSource', 'VideoColorSource', 'VideoImageSource', 'VideoNumpySource'] = source
|
|
52
|
+
"""
|
|
53
|
+
The source of this media element that
|
|
54
|
+
is the entity from which we can obtain
|
|
55
|
+
the frames.
|
|
56
|
+
"""
|
|
57
|
+
self._effects: EffectsStack = EffectsStack()
|
|
58
|
+
"""
|
|
59
|
+
The effects we want to apply on the
|
|
60
|
+
media.
|
|
61
|
+
"""
|
|
62
|
+
self.start: Fraction
|
|
63
|
+
"""
|
|
64
|
+
The time moment 't' in which the media
|
|
65
|
+
should start being played/displayed.
|
|
66
|
+
"""
|
|
67
|
+
self.end: Union[Fraction, None]
|
|
68
|
+
"""
|
|
69
|
+
The time moment 't' in which the media
|
|
70
|
+
should end being played/displayed.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
# Set 'start' and 'end'
|
|
74
|
+
self.set_time_range(start, end)
|
|
75
|
+
|
|
76
|
+
def set_time_range(
|
|
77
|
+
self,
|
|
78
|
+
start: Union[int, float, Fraction],
|
|
79
|
+
end: Union[int, float, Fraction, None] = None,
|
|
80
|
+
) -> '_Media':
|
|
81
|
+
"""
|
|
82
|
+
Set the media 'start' and 'end' time
|
|
83
|
+
moments range from the original source
|
|
84
|
+
that will be played/displayed.
|
|
85
|
+
|
|
86
|
+
- If `end = None`, the source duration
|
|
87
|
+
(if available) will be set.
|
|
88
|
+
- If `end > source.duration` it will be
|
|
89
|
+
replaced by the source duration value.
|
|
90
|
+
"""
|
|
91
|
+
ParameterValidator.validate_mandatory_positive_number('start', start, do_include_zero = True)
|
|
92
|
+
ParameterValidator.validate_positive_number('end', end, do_include_zero = False)
|
|
93
|
+
|
|
94
|
+
self.start: Fraction = Fraction(start)
|
|
95
|
+
self.end: Union[Fraction, None] = Fraction(
|
|
96
|
+
# TODO: Is this 'end' ok (?)
|
|
97
|
+
self.source.duration
|
|
98
|
+
if (
|
|
99
|
+
end is None or
|
|
100
|
+
end > self.source.duration
|
|
101
|
+
) else
|
|
102
|
+
end
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# If the source has a duration, the 'start'
|
|
106
|
+
# and 'end' must be valid
|
|
107
|
+
if self.source.duration is not None:
|
|
108
|
+
if (
|
|
109
|
+
self.start >= self.source.duration and
|
|
110
|
+
self.end >= self.source.duration
|
|
111
|
+
):
|
|
112
|
+
raise Exception(f'The provided "start" and "end" are invalid values considering the real media duration of {str(float(self.source.duration))}s')
|
|
113
|
+
|
|
114
|
+
if self.end <= self.start:
|
|
115
|
+
raise Exception('The "end" value cannot be equal or smaller than the "start" value.')
|
|
116
|
+
|
|
117
|
+
self.end = (
|
|
118
|
+
self.source.duration
|
|
119
|
+
if self.end > self.source.duration else
|
|
120
|
+
self.end
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return self
|
|
124
|
+
|
|
125
|
+
# TODO: This method has been created
|
|
126
|
+
# to be inherited by the other classes
|
|
127
|
+
# and being able to copy the instance
|
|
128
|
+
# properly by using the same 'source'
|
|
129
|
+
# reference and creating not a new one
|
|
130
|
+
@classmethod
|
|
131
|
+
def _init_with_source(
|
|
132
|
+
cls,
|
|
133
|
+
source: Union['AudioFileSource', 'AudioNumpySource', 'VideoFileSource', 'VideoColorSource', 'VideoImageSource', 'VideoNumpySource'],
|
|
134
|
+
start: Union[int, float, Fraction] = 0.0,
|
|
135
|
+
end: Union[int, float, Fraction, None] = None
|
|
136
|
+
):
|
|
137
|
+
"""
|
|
138
|
+
*For internal use only*
|
|
139
|
+
|
|
140
|
+
Alternative '__init__' to create the
|
|
141
|
+
instance from the 'source' directly. This
|
|
142
|
+
method must be called by the specific
|
|
143
|
+
implementations of this abstract class
|
|
144
|
+
to be able to instantiate them directly
|
|
145
|
+
from the 'source' to make copies.
|
|
146
|
+
|
|
147
|
+
We created this method to avoid generating
|
|
148
|
+
a new 'source' instance but preserving the
|
|
149
|
+
same reference.
|
|
150
|
+
"""
|
|
151
|
+
# Create new instance skipping '__init__'
|
|
152
|
+
instance = cls.__new__(cls)
|
|
153
|
+
super(cls, instance).__init__(
|
|
154
|
+
source = source,
|
|
155
|
+
start = start,
|
|
156
|
+
end = end
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
return instance
|
|
160
|
+
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from yta_editor.media.abstract import _Media
|
|
2
|
+
from yta_editor.sources.abstract import _AudioSource
|
|
3
|
+
from yta_editor.sources.audio import AudioFileSource, AudioNumpySource
|
|
4
|
+
from yta_editor.decorators import with_t_adjusted_to_media
|
|
5
|
+
from yta_editor.utils import apply_audio_effects_to_frame_at_t
|
|
6
|
+
from yta_video_opengl.nodes import TimedNode
|
|
7
|
+
from yta_validation.parameter import ParameterValidator
|
|
8
|
+
from quicktions import Fraction
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class _AudioMedia(_Media):
|
|
13
|
+
"""
|
|
14
|
+
Abstract class to be inherited by any
|
|
15
|
+
media element.
|
|
16
|
+
|
|
17
|
+
The media element is an element that
|
|
18
|
+
includes a source and a 'start' and
|
|
19
|
+
'end' values to be able to subclip that
|
|
20
|
+
media source and use only the part we
|
|
21
|
+
want to use.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
source: _AudioSource,
|
|
27
|
+
start: Union[int, float, Fraction] = 0.0,
|
|
28
|
+
end: Union[int, float, Fraction, None] = None,
|
|
29
|
+
):
|
|
30
|
+
super().__init__(
|
|
31
|
+
source = source,
|
|
32
|
+
start = start,
|
|
33
|
+
end = end
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def add_effect(
|
|
37
|
+
self,
|
|
38
|
+
effect: TimedNode
|
|
39
|
+
) -> '_AudioMedia':
|
|
40
|
+
"""
|
|
41
|
+
Add the provided 'effect' to the audio.
|
|
42
|
+
"""
|
|
43
|
+
ParameterValidator.validate_mandatory_instance_of('effect', effect, 'TimedNode')
|
|
44
|
+
|
|
45
|
+
if not effect.is_audio_node:
|
|
46
|
+
raise Exception('The provided "effect" is not an audio effect.')
|
|
47
|
+
|
|
48
|
+
self._effects.add_effect(effect)
|
|
49
|
+
|
|
50
|
+
return self
|
|
51
|
+
|
|
52
|
+
@with_t_adjusted_to_media
|
|
53
|
+
def get_audio_frames_at_t(
|
|
54
|
+
self,
|
|
55
|
+
t: Union[int, float, Fraction],
|
|
56
|
+
video_fps: Union[int, float, Fraction]
|
|
57
|
+
):
|
|
58
|
+
"""
|
|
59
|
+
Get the sequence of audio frames for a
|
|
60
|
+
given video 't' time moment, using the
|
|
61
|
+
audio cache system.
|
|
62
|
+
|
|
63
|
+
This is useful when we want to write a
|
|
64
|
+
video frame with its audio, so we obtain
|
|
65
|
+
all the audio frames associated to it
|
|
66
|
+
(remember that a video frame is associated
|
|
67
|
+
with more than 1 audio frame).
|
|
68
|
+
"""
|
|
69
|
+
print(f'Getting audio frames from {str(float(t + self.start))} that is actually {str(float(t))}')
|
|
70
|
+
for frame in self.source.get_audio_frames_at_t(t, video_fps):
|
|
71
|
+
yield apply_audio_effects_to_frame_at_t(
|
|
72
|
+
effects_stack = self._effects,
|
|
73
|
+
frame = frame,
|
|
74
|
+
t = t
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
class AudioFileMedia(_AudioMedia):
|
|
78
|
+
"""
|
|
79
|
+
An audio media that is read from an audio
|
|
80
|
+
file.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def copy(
|
|
85
|
+
self
|
|
86
|
+
) -> 'AudioFileMedia':
|
|
87
|
+
"""
|
|
88
|
+
Get a copy of this instance with the same
|
|
89
|
+
source, time range and effects.
|
|
90
|
+
"""
|
|
91
|
+
copy = AudioFileMedia._init_with_source(
|
|
92
|
+
source = self.source,
|
|
93
|
+
start = self.start,
|
|
94
|
+
end = self.end
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
copy._effects = self._effects.copy
|
|
98
|
+
|
|
99
|
+
return copy
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def duration(
|
|
103
|
+
self
|
|
104
|
+
) -> Fraction:
|
|
105
|
+
"""
|
|
106
|
+
The duration of the video.
|
|
107
|
+
"""
|
|
108
|
+
return self.end - self.start
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def audio_fps(
|
|
112
|
+
self
|
|
113
|
+
) -> Union[int, None]:
|
|
114
|
+
"""
|
|
115
|
+
The frames per second of the audio.
|
|
116
|
+
"""
|
|
117
|
+
return self.source.audio_fps
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def audio_time_base(
|
|
121
|
+
self
|
|
122
|
+
) -> Union[Fraction, None]:
|
|
123
|
+
"""
|
|
124
|
+
The time base of the audio.
|
|
125
|
+
"""
|
|
126
|
+
return self.source.audio_time_base
|
|
127
|
+
|
|
128
|
+
def __init__(
|
|
129
|
+
self,
|
|
130
|
+
filename: str,
|
|
131
|
+
start: Union[int, float, Fraction] = 0.0,
|
|
132
|
+
end: Union[int, float, Fraction, None] = None,
|
|
133
|
+
):
|
|
134
|
+
super().__init__(
|
|
135
|
+
source = AudioFileSource(filename),
|
|
136
|
+
start = start,
|
|
137
|
+
end = end
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# TODO: Create 'AudioNumpyMedia'
|