yta-video-opengl 0.0.18__py3-none-any.whl → 0.0.19__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.
@@ -59,33 +59,14 @@ class Timeline:
59
59
  video_pixel_format: str = 'yuv420p',
60
60
  audio_codec: str = 'aac'
61
61
  ):
62
- # TODO: By now we are using just two video
63
- # tracks to test the composition
64
62
  # TODO: We need to be careful with the
65
63
  # priority, by now its defined by its
66
64
  # position in the array
67
- self.tracks: list[Track] = [
68
- Track(
69
- size = size,
70
- fps = fps,
71
- audio_fps = audio_fps,
72
- # TODO: I need more info about the audio
73
- # I think
74
- audio_samples_per_frame = audio_samples_per_frame
75
- ),
76
- Track(
77
- size = size,
78
- fps = fps,
79
- audio_fps = audio_fps,
80
- # TODO: I need more info about the audio
81
- # I think
82
- audio_samples_per_frame = audio_samples_per_frame
83
- )
84
- ]
65
+ self.tracks: list[Track] = []
85
66
  """
86
67
  All the video tracks we are handling.
87
68
  """
88
-
69
+
89
70
  self.size: tuple[int, int] = size
90
71
  """
91
72
  The size that the final video must have.
@@ -116,15 +97,60 @@ class Timeline:
116
97
  The audio codec for the audio exported.
117
98
  """
118
99
 
119
- # TODO: Create 'add_track' method, but by now
120
- # we hare handling only one
100
+ # We will have 2 tracks by now
101
+ self.add_track().add_track()
102
+
103
+ def add_track(
104
+ self,
105
+ index: Union[int, None] = None
106
+ ) -> 'Timeline':
107
+ """
108
+ Add a new track to the timeline, that will
109
+ be placed in the last position (last
110
+ priority).
111
+
112
+ It will be a video track unless you provide
113
+ 'is_audio_track' parameter as True.
114
+ """
115
+ index = (
116
+ index
117
+ if (
118
+ index is not None and
119
+ index <= len(self.tracks)
120
+ ) else
121
+ len(self.tracks)
122
+ )
123
+
124
+ # We need to change the index of the
125
+ # affected tracks (the ones that are
126
+ # in that index and after it)
127
+ if index < len(self.tracks):
128
+ for track in self.tracks:
129
+ if track.index >= index:
130
+ track.index += 1
131
+
132
+ self.tracks.append(Track(
133
+ size = self.size,
134
+ index = index,
135
+ fps = self.fps,
136
+ audio_fps = self.audio_fps,
137
+ # TODO: I need more info about the audio
138
+ # I think
139
+ audio_samples_per_frame = self.audio_samples_per_frame,
140
+ # TODO: Where do we obtain this from (?)
141
+ audio_layout = 'stereo',
142
+ audio_format = 'fltp'
143
+ ))
144
+
145
+ return self
146
+
147
+ # TODO: Create a 'remove_track'
148
+
121
149
  def add_video(
122
150
  self,
123
151
  video: Video,
124
152
  t: Union[int, float, Fraction],
125
- # TODO: This is for testing, it has to
126
- # disappear
127
- do_use_second_track: bool = False
153
+ track_index: int = 0
128
154
  ) -> 'Timeline':
129
155
  """
130
156
  Add the provided 'video' to the timeline,
@@ -133,16 +159,14 @@ class Timeline:
133
159
  TODO: The 'do_use_second_track' parameter
134
160
  is temporary.
135
161
  """
136
- # TODO: This is temporary logic by now
137
- # just to be able to test mixing frames
138
- # from 2 different tracks at the same
139
- # time
140
- index = 1 * do_use_second_track
162
+ ParameterValidator.validate_mandatory_number_between('track_index', track_index, 0, len(self.tracks))
141
163
 
142
- self.tracks[index].add_video(video, t)
164
+ self.tracks[track_index].add_video(video, t)
143
165
 
144
166
  return self
145
167
 
168
+ # TODO: Create a 'remove_video'
169
+
146
170
  # TODO: This method is not for the Track but
147
171
  # for the timeline, as one track can only
148
172
  # have consecutive elements
@@ -248,15 +272,15 @@ class Timeline:
248
272
  # We keep only the non-silent frames because
249
273
  # we will sum them after and keeping them
250
274
  # will change the results.
251
- collapsed_frames = [
275
+ non_empty_collapsed_frames = [
252
276
  frame._frame
253
277
  for frame in collapsed_frames
254
278
  if not frame.is_from_empty_part
255
279
  ]
256
280
 
257
- if len(collapsed_frames) == 0:
281
+ if len(non_empty_collapsed_frames) == 0:
258
282
  # If they were all silent, just keep one
259
- collapsed_frames = [collapsed_frames[0]._frame]
283
+ non_empty_collapsed_frames = [collapsed_frames[0]._frame]
260
284
 
261
285
  # Now, mix column by column (track by track)
262
286
  # TODO: I do this to have an iterator, but
@@ -264,7 +288,7 @@ class Timeline:
264
288
  # frame because of the size at the original
265
289
  # video or something...
266
290
  frames = [
267
- AudioFrameCombinator.sum_tracks_frames(collapsed_frames, self.audio_fps)
291
+ AudioFrameCombinator.sum_tracks_frames(non_empty_collapsed_frames, self.audio_fps)
268
292
  ]
269
293
 
270
294
  for audio_frame in frames:
@@ -139,62 +139,19 @@ class _Part:
139
139
  is_from_empty_part = False
140
140
  )
141
141
  else:
142
- # TODO: Transform this below to a utils in
143
- # which I obtain the array directly
144
- # Check many full and partial silent frames we need
145
- number_of_frames, number_of_remaining_samples = audio_frames_and_remainder_per_video_frame(
146
- video_fps = self._track.fps,
147
- sample_rate = self._track.audio_fps,
148
- number_of_samples_per_audio_frame = self._track.audio_samples_per_frame
142
+ frames = generate_silent_frames(
143
+ fps = self._track.fps,
144
+ audio_fps = self._track.audio_fps,
145
+ audio_samples_per_frame = self._track.audio_samples_per_frame,
146
+ # TODO: Where do this 2 formats come from (?)
147
+ layout = self._track.audio_layout,
148
+ format = self._track.audio_format
149
149
  )
150
150
 
151
- # TODO: I need to set the pts, but here (?)
152
- # The complete silent frames we need
153
- silent_frame = self._audio_frame_generator.silent(
154
- sample_rate = self._track.audio_fps,
155
- # TODO: Check where do we get this value from
156
- layout = 'stereo',
157
- number_of_samples = self._track.audio_samples_per_frame,
158
- # TODO: Check where do we get this value from
159
- format = 'fltp',
160
- pts = None,
161
- time_base = None
162
- )
163
-
164
- frames = (
165
- [
166
- AudioFrameWrapped(
167
- frame = silent_frame,
168
- is_from_empty_part = True
169
- )
170
- ] * number_of_frames
171
- if number_of_frames > 0 else
172
- []
173
- )
174
-
175
- # The remaining partial silent frames we need
176
- if number_of_remaining_samples > 0:
177
- silent_frame = self._audio_frame_generator.silent(
178
- sample_rate = self._track.audio_fps,
179
- # TODO: Check where do we get this value from
180
- layout = 'stereo',
181
- number_of_samples = number_of_remaining_samples,
182
- # TODO: Check where do we get this value from
183
- format = 'fltp',
184
- pts = None,
185
- time_base = None
186
- )
187
-
188
- frames.append(
189
- AudioFrameWrapped(
190
- frame = silent_frame,
191
- is_from_empty_part = True
192
- )
193
- )
194
-
195
151
  for frame in frames:
196
152
  yield frame
197
153
 
154
+
198
155
  # TODO: I don't like using t as float,
199
156
  # we need to implement fractions.Fraction
200
157
  # TODO: This is called Track but it is
@@ -256,27 +213,50 @@ class Track:
256
213
  from first to last.
257
214
  """
258
215
  return sorted(self._videos, key = lambda video: video.start)
216
+
217
+ @property
218
+ def is_muted(
219
+ self
220
+ ) -> bool:
221
+ """
222
+ Flag to indicate if the track is muted or
223
+ not. Being muted means that no audio frames
224
+ will be retured from this track.
225
+ """
226
+ return self._is_muted
259
227
 
260
228
  def __init__(
261
229
  self,
262
230
  # TODO: I need the general settings of the
263
231
  # project to be able to make audio also, not
264
232
  # only the empty frames
233
+ index: int,
265
234
  size: tuple[int, int],
266
235
  fps: float,
267
236
  audio_fps: float,
268
237
  # TODO: Where does it come from (?)
269
- audio_samples_per_frame: int
238
+ audio_samples_per_frame: int,
239
+ audio_layout: str = 'stereo',
240
+ audio_format: str = 'fltp'
270
241
  ):
271
242
  self._videos: list[VideoOnTrack] = []
272
243
  """
273
244
  The list of 'VideoOnTrack' instances that
274
245
  must play on this track.
275
246
  """
247
+ self._is_muted: bool = False
248
+ """
249
+ Internal flag to indicate if the track is
250
+ muted or not.
251
+ """
276
252
  self.size: tuple[int, int] = size
277
253
  """
278
254
  The size of the videos of this track.
279
255
  """
256
+ self.index: int = index
257
+ """
258
+ The index of the track within the timeline.
259
+ """
280
260
  self.fps: float = float(fps)
281
261
  """
282
262
  The fps of the track, needed to calculate
@@ -292,6 +272,16 @@ class Track:
292
272
  """
293
273
  The number of samples per audio frame.
294
274
  """
275
+ self.audio_layout: str = audio_layout
276
+ """
277
+ The layout of the audio, that can be 'mono'
278
+ or 'stereo'.
279
+ """
280
+ self.audio_format: str = audio_format
281
+ """
282
+ The format of the audio, that can be 's16',
283
+ 'flt', 'fltp', etc.
284
+ """
295
285
 
296
286
  def _is_free(
297
287
  self,
@@ -305,8 +295,8 @@ class Track:
305
295
  """
306
296
  return not any(
307
297
  (
308
- video.video.start < end and
309
- video.video.end > start
298
+ video.start < end and
299
+ video.end > start
310
300
  )
311
301
  for video in self.videos
312
302
  )
@@ -332,6 +322,24 @@ class Track:
332
322
  raise Exception('NON_LIMITED_EMPTY_PART_END exceeded.')
333
323
  return None
334
324
 
325
+ def mute(
326
+ self
327
+ ) -> 'Track':
328
+ """
329
+ Set the track as muted so no audio frame will
330
+ be played from this track.
331
+ """
332
+ self._is_muted = True
333
+
334
+ def unmute(
335
+ self
336
+ ) -> 'Track':
337
+ """
338
+ Set the track as unmuted so the audio frames
339
+ will be played as normal.
340
+ """
341
+ self._is_muted = False
342
+
335
343
  def get_frame_at(
336
344
  self,
337
345
  t: Union[int, float, Fraction]
@@ -375,7 +383,19 @@ class Track:
375
383
  (remember that a video frame is associated
376
384
  with more than 1 audio frame).
377
385
  """
378
- for frame in self._get_part_at_t(t).get_audio_frames_at(t):
386
+ frames = (
387
+ generate_silent_frames(
388
+ fps = self.fps,
389
+ audio_fps = self.audio_fps,
390
+ audio_samples_per_frame = self.audio_samples_per_frame,
391
+ layout = self.audio_layout,
392
+ format = self.audio_format
393
+ )
394
+ if self.is_muted else
395
+ self._get_part_at_t(t).get_audio_frames_at(t)
396
+ )
397
+
398
+ for frame in frames:
379
399
  yield frame
380
400
 
381
401
  def add_video(
@@ -404,7 +424,8 @@ class Track:
404
424
  # TODO: We can have many different strategies
405
425
  # that we could define in the '__init__' maybe
406
426
  t: T = T.from_fps(t, self.fps)
407
- if not self._is_free(t.truncated, t.next(1).truncated):
427
+ #if not self._is_free(t.truncated, t.next(1).truncated):
428
+ if not self._is_free(t.truncated, t.truncated + video.duration):
408
429
  raise Exception('The video cannot be added at the "t" time moment, something blocks it.')
409
430
  t = t.truncated
410
431
  else:
@@ -464,4 +485,78 @@ class Track:
464
485
 
465
486
  self._parts = parts
466
487
 
467
- return self
488
+ return self
489
+
490
+ # TODO: Is this method here ok (?)
491
+ def generate_silent_frames(
492
+ fps: int,
493
+ audio_fps: int,
494
+ audio_samples_per_frame: int,
495
+ layout: str = 'stereo',
496
+ format: str = 'fltp'
497
+ ) -> list[AudioFrameWrapped]:
498
+ """
499
+ Get the audio silent frames we need for
500
+ a video with the given 'fps', 'audio_fps'
501
+ and 'audio_samples_per_frame', using the
502
+ also provided 'layout' and 'format' for
503
+ the audio frames.
504
+
505
+ This method is used when we have empty
506
+ parts on our tracks and we need to
507
+ provide the frames, that are passed as
508
+ AudioFrameWrapped instances and tagged as
509
+ coming from empty parts.
510
+ """
511
+ audio_frame_generator: AudioFrameGenerator = AudioFrameGenerator()
512
+
513
+ # Check how many full and partial silent
514
+ # audio frames we need
515
+ number_of_frames, number_of_remaining_samples = audio_frames_and_remainder_per_video_frame(
516
+ video_fps = fps,
517
+ sample_rate = audio_fps,
518
+ number_of_samples_per_audio_frame = audio_samples_per_frame
519
+ )
520
+
521
+ # The complete silent frames we need
522
+ silent_frame = audio_frame_generator.silent(
523
+ sample_rate = audio_fps,
524
+ layout = layout,
525
+ number_of_samples = audio_samples_per_frame,
526
+ format = format,
527
+ pts = None,
528
+ time_base = None
529
+ )
530
+
531
+ frames = (
532
+ [
533
+ AudioFrameWrapped(
534
+ frame = silent_frame,
535
+ is_from_empty_part = True
536
+ )
537
+ ] * number_of_frames
538
+ if number_of_frames > 0 else
539
+ []
540
+ )
541
+
542
+ # The remaining partial silent frames we need
543
+ if number_of_remaining_samples > 0:
544
+ silent_frame = audio_frame_generator.silent(
545
+ sample_rate = audio_fps,
546
+ # TODO: Check where do we get this value from
547
+ layout = layout,
548
+ number_of_samples = number_of_remaining_samples,
549
+ # TODO: Check where do we get this value from
550
+ format = format,
551
+ pts = None,
552
+ time_base = None
553
+ )
554
+
555
+ frames.append(
556
+ AudioFrameWrapped(
557
+ frame = silent_frame,
558
+ is_from_empty_part = True
559
+ )
560
+ )
561
+
562
+ return frames
yta_video_opengl/tests.py CHANGED
@@ -601,15 +601,25 @@ def video_modified_stored():
601
601
  # must be played at the same time
602
602
  video = Video(VIDEO_PATH, 0.25, 0.75)
603
603
  timeline = Timeline()
604
- timeline.add_video(Video(VIDEO_PATH, 0.25, 1.0), 0.75)
605
- timeline.add_video(Video('C:/Users/dania/Downloads/Y2meta.app-10 Smooth Transitions Green Screen Template For Kinemaster, Alight Motion, Filmora, premiere pro-(1080p).mp4', 1.5, 2.0), 3.0)
606
- timeline.add_video(Video(VIDEO_PATH, 0.5, 1.0), 2.0)
607
- timeline.add_video(Video(VIDEO_PATH, 0.5, 1.0), 2.1, do_use_second_track = True)
604
+
605
+ transitions_30fps = 'C:/Users/dania/Downloads/Y2meta.app-10 Smooth Transitions Green Screen Template For Kinemaster, Alight Motion, Filmora, premiere pro-(1080p).mp4'
606
+ simpsons_60fps = 'C:/Users/dania/Downloads/Y_una_porra_los_simpsons_castellano_60fps.mp4'
607
+
608
+ # Track 1
609
+ timeline.add_video(Video(VIDEO_PATH, 0.25, 1.0), 0.75, track_index = 0)
610
+ timeline.add_video(Video(simpsons_60fps, 1.5, 2.0), 3.0, track_index = 0)
611
+ timeline.add_video(Video(VIDEO_PATH, 0.5, 1.0), 2.0, track_index = 0)
612
+
613
+ timeline.tracks[0].mute()
614
+
615
+ # Track 2
616
+ timeline.add_video(Video(VIDEO_PATH, 0.5, 1.0), 2.7, track_index = 1)
617
+ timeline.add_video(Video(simpsons_60fps, 5.8, 7.8), 0.6, track_index = 1)
608
618
  # 30fps
609
619
  # timeline.add_video(Video('C:/Users/dania/Downloads/Y2meta.app-TOP 12 SIMPLE LIQUID TRANSITION _ GREEN SCREEN TRANSITION PACK-(1080p60).mp4', 0.25, 1.5), 0.25, do_use_second_track = True)
610
620
  # 29.97fps
611
621
  # timeline.add_video(Video('C:/Users/dania/Downloads/Y_una_porra_los_simpsons_castellano.mp4', 5.8, 6.8), 3.6, do_use_second_track = True)
612
- timeline.add_video(Video('C:/Users/dania/Downloads/Y_una_porra_los_simpsons_castellano_60fps.mp4', 5.8, 7.8), 3.6, do_use_second_track = True)
622
+
613
623
  timeline.render(OUTPUT_PATH)
614
624
 
615
625
  return
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: yta-video-opengl
3
- Version: 0.0.18
3
+ Version: 0.0.19
4
4
  Summary: Youtube Autonomous Video OpenGL Module
5
5
  Author: danialcala94
6
6
  Author-email: danielalcalavalera@gmail.com
@@ -4,8 +4,8 @@ yta_video_opengl/complete/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
4
4
  yta_video_opengl/complete/frame_combinator.py,sha256=uYg7907knjBlmZUZCCzkxDcj0Nown0muvL5PNVS707A,9413
5
5
  yta_video_opengl/complete/frame_generator.py,sha256=VRcPgpqfxQWMeLzgEJObbM0xu7_85I1y_YyQVhcEswc,7853
6
6
  yta_video_opengl/complete/frame_wrapper.py,sha256=g0aTcUVmF5uQtxs95_XsxlwL0QUj-fNOSRHvK4ENqg4,3347
7
- yta_video_opengl/complete/timeline.py,sha256=haUTJoGv15Xqt0G1E52St_PmYrzoqwPF_k88FWBIfyA,16188
8
- yta_video_opengl/complete/track.py,sha256=kLd8iUUPKVbCvk0nRnL630lagjh-8QengcZU65xa21c,15706
7
+ yta_video_opengl/complete/timeline.py,sha256=d6_5Yd5n6TTjwo0ozKNDsd3GAfL12ATx0w-TVkIr0Eo,16767
8
+ yta_video_opengl/complete/track.py,sha256=NCfNLOjg_7AWX5g5moYgAjmgAJ22Hp92TLa5pdoW6RM,17910
9
9
  yta_video_opengl/complete/video_on_track.py,sha256=laxDvMr1rrmnDfHpk825j42f4pj9I1X8vOFmzwteuM8,4824
10
10
  yta_video_opengl/nodes/__init__.py,sha256=TZ-ZO05PZ0_ABq675E22_PngLWOe-_w5s1cLlV3NbWM,3469
11
11
  yta_video_opengl/nodes/audio/__init__.py,sha256=4nKkC70k1UgLcCSPqFWm3cKdaJM0KUmQTwGWv1xFarQ,2926
@@ -17,11 +17,11 @@ yta_video_opengl/reader/cache/audio.py,sha256=cm_1D5f5RnmJgaidA1pnEhTPF8DE0mU2Mo
17
17
  yta_video_opengl/reader/cache/utils.py,sha256=9aJ6qyUFRvoh2jRbIvtF_-1MOm_sgQtPiy0WXLCZYcA,1402
18
18
  yta_video_opengl/reader/cache/video.py,sha256=3sT9cE0sdTty5AE9yFAPJrJNxCX5vWVATK8OeJInr8I,3496
19
19
  yta_video_opengl/t.py,sha256=xOhT1xBEwChlXf-Tuy-WxA_08iRJWVlnL_Hyzr-9-sk,6633
20
- yta_video_opengl/tests.py,sha256=6s5x-RReIZ38d1c4gB7aMoDLHLrC0LWEttOYyixhUV0,27838
20
+ yta_video_opengl/tests.py,sha256=eyFnz7yBDJyIoti-cV7Dz1bMTeF61Z4_JrFkLXuZQl4,28019
21
21
  yta_video_opengl/utils.py,sha256=yUi17EjNR4SVpvdDUwUaKl4mBCb1uyFCSGoIX3Zr2F0,15586
22
22
  yta_video_opengl/video.py,sha256=Fgu_BzuDlMbfl1Hwjk8Yzo3ZxO73wPuyTUjTbf9OSLw,8951
23
23
  yta_video_opengl/writer.py,sha256=QwvjQcEkzn1WAVqVTFiI6tYIXJO67LKKUTJGO_eflFM,8893
24
- yta_video_opengl-0.0.18.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
25
- yta_video_opengl-0.0.18.dist-info/METADATA,sha256=3sGbbiucD3tIj8QtHNmXKfEK8JW6ZjBWo09xKXmBplY,714
26
- yta_video_opengl-0.0.18.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
27
- yta_video_opengl-0.0.18.dist-info/RECORD,,
24
+ yta_video_opengl-0.0.19.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
25
+ yta_video_opengl-0.0.19.dist-info/METADATA,sha256=wLhgCKzFD4EJiPuuCnNv40dimACB-Zkg8A7otla6ins,714
26
+ yta_video_opengl-0.0.19.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
27
+ yta_video_opengl-0.0.19.dist-info/RECORD,,