Spaces:
Paused
Paused
Update stream_server.py
Browse files- stream_server.py +34 -97
stream_server.py
CHANGED
@@ -29,114 +29,51 @@ def is_valid_path(video_name):
|
|
29 |
video_path = (BASE_DIR / video_name).resolve()
|
30 |
return str(video_path).startswith(str(BASE_DIR))
|
31 |
|
32 |
-
def convert_to_hls(
|
33 |
"""
|
34 |
-
Converts an MP4
|
35 |
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
start_number (int): The starting number for HLS segments.
|
41 |
-
|
42 |
-
Returns:
|
43 |
-
int: Number of segments generated.
|
44 |
"""
|
45 |
-
|
46 |
-
|
|
|
|
|
|
|
|
|
|
|
47 |
'ffmpeg',
|
48 |
-
'-
|
49 |
-
'-
|
50 |
-
|
51 |
-
'-
|
52 |
-
'-
|
53 |
-
'-
|
54 |
-
'-
|
55 |
-
'-
|
56 |
-
'-
|
57 |
-
'-
|
58 |
-
'-
|
59 |
-
'-
|
60 |
-
|
61 |
-
os.path.join(output_dir, f'{video_name}.m3u8') # Output playlist
|
62 |
]
|
63 |
|
64 |
try:
|
65 |
-
#
|
66 |
-
subprocess.run(
|
|
|
67 |
except subprocess.CalledProcessError as e:
|
68 |
-
|
69 |
-
|
70 |
-
print(f"
|
71 |
-
raise HTTPException(status_code=500, detail="Video conversion failed")
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
async def add_video(video_name, output_path, audio_duration):
|
76 |
-
"""
|
77 |
-
Endpoint to add a video to the streaming queue.
|
78 |
-
|
79 |
-
Args:
|
80 |
-
video_name (str): Name of the video file to add.
|
81 |
-
request (Request): FastAPI request object to extract base URL.
|
82 |
|
83 |
-
Returns:
|
84 |
-
dict: Success message.
|
85 |
-
"""
|
86 |
-
global total_processed_duration
|
87 |
-
print((HLS_DIR / f'{video_name}.m3u8').exists())
|
88 |
-
if not (HLS_DIR / f'{video_name}.m3u8').exists():
|
89 |
-
async with segment_lock:
|
90 |
-
global segment_counter
|
91 |
-
current_start_number = segment_counter
|
92 |
-
num_segments_before = len([f for f in os.listdir(HLS_DIR) if f.startswith('video_stream') and f.endswith('.ts')])
|
93 |
-
await asyncio.get_event_loop().run_in_executor(
|
94 |
-
None, convert_to_hls, str(video_name), str(HLS_DIR), video_name, current_start_number
|
95 |
-
)
|
96 |
-
num_segments_after = len([f for f in os.listdir(HLS_DIR) if f.startswith('video_stream') and f.endswith('.ts')])
|
97 |
-
num_new_segments = num_segments_after - num_segments_before
|
98 |
-
# Update the global segment counter
|
99 |
-
segment_counter += num_new_segments
|
100 |
-
|
101 |
-
video_names.append(video_name)
|
102 |
-
segment_duration = 3
|
103 |
-
full_segments = int(audio_duration // segment_duration)
|
104 |
-
remaining_duration = audio_duration % segment_duration
|
105 |
-
|
106 |
-
existing_segments = set()
|
107 |
-
if output_path:
|
108 |
-
with open(output_path, 'r') as m3u8_file:
|
109 |
-
for line in m3u8_file:
|
110 |
-
if line.startswith('/live_stream/video_stream'):
|
111 |
-
existing_segments.add(line.strip())
|
112 |
-
|
113 |
-
new_segments = []
|
114 |
-
# Generate new segment paths
|
115 |
-
for i in range(1, full_segments + 1):
|
116 |
-
new_segment_path = f"/live_stream/video_stream{i}.ts"
|
117 |
-
if new_segment_path not in existing_segments:
|
118 |
-
new_segments.append(new_segment_path)
|
119 |
-
total_processed_duration += segment_duration
|
120 |
|
121 |
-
|
122 |
-
with open(output_path, 'a') as m3u8_file:
|
123 |
-
for segment in new_segments:
|
124 |
-
m3u8_file.write(f"#EXTINF:{segment_duration:.3f},\n")
|
125 |
-
m3u8_file.write(f"{segment}\n")
|
126 |
-
|
127 |
-
if remaining_duration > 0:
|
128 |
-
remaining_segment_path = f"/live_stream/video_stream{full_segments + 1}.ts"
|
129 |
-
if remaining_segment_path not in existing_segments:
|
130 |
-
m3u8_file.write(f"#EXTINF:{remaining_duration:.3f},\n")
|
131 |
-
m3u8_file.write(f"{remaining_segment_path}\n")
|
132 |
-
total_processed_duration += remaining_duration
|
133 |
-
|
134 |
-
# Add #EXT-X-ENDLIST only once after all segments have been added
|
135 |
-
if total_processed_duration == audio_duration:
|
136 |
-
with open(output_path, 'a') as m3u8_file:
|
137 |
-
m3u8_file.write("#EXT-X-ENDLIST\n")
|
138 |
-
total_processed_duration = 0
|
139 |
|
|
|
|
|
140 |
return {"message": f'"{video_name}" added to the streaming queue.'}
|
141 |
|
142 |
async def concatenate_playlists(video_names, base_dir):
|
|
|
29 |
video_path = (BASE_DIR / video_name).resolve()
|
30 |
return str(video_path).startswith(str(BASE_DIR))
|
31 |
|
32 |
+
def convert_to_hls(input_file, output_playlist, segment_prefix='segment', segment_duration=10):
|
33 |
"""
|
34 |
+
Converts an MP4 file to HLS .ts segments, maintaining continuity characteristics across segments.
|
35 |
|
36 |
+
:param input_file: Path to the input MP4 file.
|
37 |
+
:param output_playlist: Path to the output .m3u8 playlist file.
|
38 |
+
:param segment_prefix: Prefix for naming the .ts segments. Default is 'segment'.
|
39 |
+
:param segment_duration: Duration of each segment in seconds. Default is 10 seconds.
|
|
|
|
|
|
|
|
|
40 |
"""
|
41 |
+
if not os.path.exists(input_file):
|
42 |
+
raise FileNotFoundError(f"Input file '{input_file}' does not exist.")
|
43 |
+
|
44 |
+
# FFmpeg command to convert MP4 to HLS segments
|
45 |
+
os.chmod(input_file, 0o644) # Change permission to read/write for the user
|
46 |
+
|
47 |
+
command = [
|
48 |
'ffmpeg',
|
49 |
+
'-i', input_file, # Input MP4 file
|
50 |
+
'-c:v', 'libx264', # Video codec, consistent across all segments
|
51 |
+
'-c:a', 'aac', # Audio codec, consistent across all segments
|
52 |
+
'-strict', '-2', # Strict flag for AAC codec
|
53 |
+
'-flags', '-global_header', # Set global header flag for consistency
|
54 |
+
'-hls_time', str(segment_duration), # Segment duration in seconds
|
55 |
+
'-hls_list_size', '0', # Keep all segments in the playlist
|
56 |
+
'-hls_flags', 'append_list+omit_endlist+program_date_time', # Flags to maintain continuity
|
57 |
+
'-hls_segment_type', 'mpegts', # Ensure the segment type is TS
|
58 |
+
'-hls_segment_filename', f'{segment_prefix}%d.ts', # Naming pattern for the .ts segments
|
59 |
+
'-force_key_frames', f'expr:gte(t,n_forced*{segment_duration})', # Force keyframes for segment duration consistency
|
60 |
+
'-avoid_negative_ts', 'make_zero', # Avoid negative timestamps
|
61 |
+
output_playlist # Output .m3u8 playlist file
|
|
|
62 |
]
|
63 |
|
64 |
try:
|
65 |
+
# Run the FFmpeg command
|
66 |
+
subprocess.run(command, check=True)
|
67 |
+
print(f"Successfully converted '{input_file}' to HLS segments with playlist '{output_playlist}'.")
|
68 |
except subprocess.CalledProcessError as e:
|
69 |
+
print(f"Error during conversion: {e}")
|
70 |
+
except Exception as e:
|
71 |
+
print(f"An unexpected error occurred: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
|
75 |
+
def add_video(video_name, output_path, audio_duration):
|
76 |
+
convert_to_hls(video_name, output_path, segment_duration=audio_duration)
|
77 |
return {"message": f'"{video_name}" added to the streaming queue.'}
|
78 |
|
79 |
async def concatenate_playlists(video_names, base_dir):
|