-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathrandom_clip_generator.py
148 lines (117 loc) · 5.28 KB
/
random_clip_generator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
""" Random video clips player for Win10. """
import os
import random
import subprocess
import sys
import xml.etree.ElementTree as ET
from subprocess import Popen
XML_PLAYLIST_FILE = 'clips.xspf'
SUBFOLDER = 'videos'
NUMBER_OF_CLIPS = 5
INTERVAL_MIN = 4
INTERVAL_MAX = 8
CURRENT_DIRECTORY = os.path.dirname( os.path.abspath(__file__) )
def prepend_line(filename: str, line: str) -> None:
""" Append line to beginning of file. """
assert filename, f"Cannot prepend line: '{line}' to invalid {filename}. "
if line is not None and len(line) > 0:
with open(filename, 'r+', encoding='utf-8') as file:
content = file.read()
file.seek(0,0)
file.write( line.rstrip("\r\n") + "\n" + content )
def list_files_subfolder() -> list:
""" Create a list of all files in SUBFOLDER (videos). """
subfolder_contents = os.listdir(SUBFOLDER)
if subfolder_contents is None or len(subfolder_contents) < 1:
print(f"There are no files under {SUBFOLDER}. ")
sys.exit()
return subfolder_contents
def select_video_at_random(list_of_files: list) -> str:
""" Choose a video. :return: Video filename with Win full path. """
assert list_of_files and SUBFOLDER
subpath = os.path.join(CURRENT_DIRECTORY, SUBFOLDER)
selected = random.randint(0, len(list_of_files) - 1)
return ( os.path.join(subpath, list_of_files[selected]) ).replace("\\", "/")
def get_video_duration(num_to_log: int, video: str) -> int:
""" Extract video duration with ffprobe and subprocess.Popen.
:return: Video duration in seconds. """
assert video
ffprobe_str = "ffprobe -v error -select_streams v:0 \
-show_entries stream=duration -of default=noprint_wrappers=1:nokey=1"
command = f"{ffprobe_str} {video} "
with Popen(command, stdout=subprocess.PIPE) as process:
(out, err) = process.communicate()
if err:
print(f"Process error: [{str(err)}]. Iteration: {num_to_log}. ")
result = ( ( ( ( str(out) ).split('.', maxsplit=1) )[0] ).split("b'") )[1]
seconds = int(result)
assert INTERVAL_MIN < seconds > 0, f"Video too short: {video} "
return seconds
def choose_starting_point(video_length: int) -> int:
""" Choose beginning of clip.
:return: Starting point from beginning of video to end of video - max. """
assert video_length > 0
return random.randint(0, video_length - INTERVAL_MAX)
def add_clip_to_tracklist(track_list: ET.Element, \
video: str, start: int, end: int) -> None:
""" Add clip (track) to playlist.trackList sub element tree and mute.
:param: track_list: Contains the clips.
:param: video: The name of the video file to be cut.
:param: start: Begin clip from.
:param: end: Stop clip at. """
assert track_list is not None and video and start >= 0
track = ET.SubElement(track_list, 'track')
ET.SubElement(track, 'location').text = f"file:///{video}"
extension = ET.SubElement(track, 'extension', \
application='http://www.videolan.org/vlc/playlist/0')
ET.SubElement(extension, 'vlc:option').text = f"start-time={start}"
ET.SubElement(extension, 'vlc:option').text = f"stop-time={end}"
ET.SubElement(extension, 'vlc:option').text = 'no-audio'
def create_xml_file(playlist_et: ET.Element) -> None:
""" Finally write the playlist tree element as an xspf file to disk. """
ET.ElementTree(playlist_et).write(XML_PLAYLIST_FILE)
prepend_line(XML_PLAYLIST_FILE, '<?xml version="1.0" encoding="UTF-8"?>')
def generate_random_video_clips_playlist(video_list: list) -> ET.Element:
"""
* Create playlist as an xml element tree.
* Create tracklist as subelement of playlist. This contains the clips.
* For each clip to be generated:
+ Select a video at random.
+ Choose beginning and end of clip from selected video.
+ Add clip to playlist.
"""
assert video_list
playlist = ET.Element("playlist", version="1", xmlns="http://xspf.org/ns/0/")
tracks = ET.SubElement(playlist, "trackList")
assert 1 <= NUMBER_OF_CLIPS < sys.maxsize, \
"Invalid number of clips: {NUMBER_OF_CLIPS} "
for iteration in range(NUMBER_OF_CLIPS):
video_file = select_video_at_random(video_list)
duration = get_video_duration(iteration, video_file)
begin_at = choose_starting_point(duration)
clip_length = random.randint(INTERVAL_MIN, INTERVAL_MAX)
play_to = begin_at + clip_length
add_clip_to_tracklist(tracks, video_file, begin_at, play_to)
return playlist
def execute_vlc() -> None:
""" Call VLC only once and pass it the xspf playlist. """
executable = os.path.join(CURRENT_DIRECTORY, 'exevlc.bat')
with Popen([executable, f"{CURRENT_DIRECTORY}/{XML_PLAYLIST_FILE}"]):
pass
def verify_intervals_valid() -> None:
""" Music video clips should be between at most 15 to 25 seconds long. """
assert 15 >= INTERVAL_MIN >= 1
assert 25 >= INTERVAL_MAX >= 1
def main():
"""
* Get list of videos.
* Generate an xml playlist with random clips from those videos.
* Run VLC with that playlist.
"""
verify_intervals_valid()
files = list_files_subfolder()
top_element = generate_random_video_clips_playlist(files)
create_xml_file(top_element)
execute_vlc()
if __name__ == "__main__":
main()