File size: 7,162 Bytes
c93c8fd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eddd7de
c93c8fd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1f6e841
c93c8fd
1f6e841
c93c8fd
1f6e841
 
 
 
c93c8fd
1f6e841
 
 
 
 
 
 
c93c8fd
1f6e841
 
 
 
 
 
 
 
 
cc2a8df
1f6e841
 
 
c93c8fd
 
 
1f6e841
 
 
c93c8fd
1f6e841
 
 
c93c8fd
581abfa
7818ba9
1f6e841
 
c93c8fd
 
 
 
 
 
 
 
 
 
 
 
3de7ae7
c93c8fd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3de7ae7
c93c8fd
 
 
 
 
 
699d3d4
 
c93c8fd
699d3d4
 
 
c93c8fd
699d3d4
 
 
 
c93c8fd
699d3d4
 
 
 
c93c8fd
699d3d4
 
c93c8fd
 
 
841b38e
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import os
import subprocess
import pathlib
# from fastapi import HTTPException
import asyncio

# Define base directories
BASE_DIR = pathlib.Path('./videos').resolve()
HLS_DIR = pathlib.Path('./hls_videos').resolve()
HLS_DIR.mkdir(exist_ok=True)


# Keep track of video names added to the queue
video_names = []
segment_counter = 1
total_processed_duration = 0
segment_lock = asyncio.Lock()

def is_valid_path(video_name):
    """
    Validates the video path to prevent directory traversal attacks.

    Args:
        video_name (str): Name of the video file.

    Returns:
        bool: True if valid, False otherwise.
    """
    video_path = (BASE_DIR / video_name).resolve()
    return str(video_path).startswith(str(BASE_DIR))

def convert_to_hls(input_file, output_playlist, segment_prefix='segment', segment_duration=10):
    """
    Converts an MP4 file to HLS .ts segments, maintaining continuity characteristics across segments.

    :param input_file: Path to the input MP4 file.
    :param output_playlist: Path to the output .m3u8 playlist file.
    :param segment_prefix: Prefix for naming the .ts segments. Default is 'segment'.
    :param segment_duration: Duration of each segment in seconds. Default is 10 seconds.
    """
    if not os.path.exists(input_file):
        raise FileNotFoundError(f"Input file '{input_file}' does not exist.")

    # FFmpeg command to convert MP4 to HLS segments
    os.chmod(input_file, 0o644)  # Change permission to read/write for the user
    
    command = [
        'ffmpeg',
        '-i', input_file,                    # Input MP4 file
        '-c:v', 'libx264',                   # Video codec, consistent across all segments
        '-c:a', 'aac',                       # Audio codec, consistent across all segments
        '-strict', '-2',                     # Strict flag for AAC codec
        '-flags', '-global_header',          # Set global header flag for consistency
        '-hls_time', str(segment_duration),  # Segment duration in seconds
        '-hls_list_size', '0',               # Keep all segments in the playlist
        '-hls_flags', 'append_list+omit_endlist+program_date_time',  # Flags to maintain continuity
        '-hls_segment_type', 'mpegts',       # Ensure the segment type is TS
        '-hls_segment_filename', f'{HLS_DIR}/{segment_prefix}%d.ts',  # Naming pattern for the .ts segments
        '-force_key_frames', f'expr:gte(t,n_forced*{segment_duration})',  # Force keyframes for segment duration consistency
        '-avoid_negative_ts', 'make_zero',   # Avoid negative timestamps
        output_playlist                      # Output .m3u8 playlist file
    ]

    try:
        # Run the FFmpeg command
        subprocess.run(command, check=True)
        print(f"Successfully converted '{input_file}' to HLS segments with playlist '{output_playlist}'.")
    except subprocess.CalledProcessError as e:
        print(f"Error during conversion: {e}")
    except Exception as e:
        print(f"An unexpected error occurred: {e}")



def add_video(video_name, output_path, audio_duration):
    convert_to_hls(video_name, output_path, segment_duration=audio_duration)
    return {"message": f'"{video_name}" added to the streaming queue.'}

async def concatenate_playlists(video_names, base_dir):
    """
    Concatenates multiple HLS playlists into a single playlist with unique segment numbering.

    Args:
        video_names (list): List of video names added to the queue.
        base_dir (str): Base directory where HLS files are stored.
        request (Request): FastAPI request object to extract base URL.
    """
    concatenated_playlist_path = os.path.join(base_dir, 'master.m3u8')
    max_segment_duration = 3  # Since we set hls_time to 3
    segment_lines = []  # To store segment lines

    # Construct base URL from the incoming request
    

    for video_name in video_names:
        video_playlist_path = os.path.join(base_dir, f'{video_name}.m3u8')
        if os.path.exists(video_playlist_path):
            with open(video_playlist_path, 'r') as infile:
                lines = infile.readlines()
                for line in lines:
                    line = line.strip()
                    if line.startswith('#EXTINF'):
                        # Append EXTINF line
                        segment_lines.append(line)
                    elif line.endswith('.ts'):
                        segment_file = line
                        # Update segment URI to include full URL
                        segment_path = f'{segment_file}'
                        segment_lines.append(segment_path)
                    elif line.startswith('#EXT-X-BYTERANGE'):
                        # Include byte range if present
                        segment_lines.append(line)
                    elif line.startswith('#EXT-X-ENDLIST'):
                        # Do not include this here; we'll add it at the end
                        continue
                    elif line.startswith('#EXTM3U') or line.startswith('#EXT-X-VERSION') or line.startswith('#EXT-X-PLAYLIST-TYPE') or line.startswith('#EXT-X-TARGETDURATION') or line.startswith('#EXT-X-MEDIA-SEQUENCE'):
                        # Skip these tags; they'll be added in the concatenated playlist
                        continue
                    else:
                        # Include any other necessary tags
                        segment_lines.append(line)

    # Write the concatenated playlist
    with open(concatenated_playlist_path, 'w') as outfile:
        outfile.write('#EXTM3U\n')
        outfile.write('#EXT-X-PLAYLIST-TYPE:VOD\n')
        outfile.write(f'#EXT-X-TARGETDURATION:{max_segment_duration}\n')
        outfile.write('#EXT-X-VERSION:4\n')
        outfile.write(f'#EXT-X-MEDIA-SEQUENCE:{1}\n')  # Starting from segment number 1
        for line in segment_lines:
            outfile.write(f'{line}\n')
        outfile.write('#EXT-X-ENDLIST\n')

def generate_m3u8(video_duration, output_path,segment_duration=3):
    # Initialize playlist content
    m3u8_content = "#EXTM3U\n"
    m3u8_content += "#EXT-X-PLAYLIST-TYPE:VOD\n"
    m3u8_content += f"#EXT-X-TARGETDURATION:{segment_duration}\n"
    m3u8_content += "#EXT-X-VERSION:4\n"
    m3u8_content += "#EXT-X-MEDIA-SEQUENCE:1\n"
    
    # # Calculate the number of full segments and the remaining duration

    # segment_duration = int(segment_duration)
    # full_segments = int(video_duration // segment_duration)
    # remaining_duration = video_duration % segment_duration

    # # Add full segments to the playlist
    # for i in range(full_segments):
    #     m3u8_content += f"#EXTINF:{segment_duration:.6f},\n"
    #     m3u8_content += f"/live_stream/video_stream{i + 1}.ts\n"

    # # Add the remaining segment if there's any leftover duration
    # if remaining_duration > 0:
    #     m3u8_content += f"#EXTINF:{remaining_duration:.6f},\n"
    #     m3u8_content += f"/live_stream/video_stream{full_segments + 1}.ts\n"

    # # End the playlist
    # m3u8_content += "#EXT-X-ENDLIST\n"
    with open(output_path, "w") as file:
        file.write(m3u8_content)    

    print(f"M3U8 playlist saved to {output_path}")