File size: 6,002 Bytes
c16a803
 
5296733
c16a803
40446f2
e4ed31e
6c154f8
c16a803
 
f34662a
 
 
b9f7ba4
a6be34b
f34662a
 
 
a6be34b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f34662a
 
 
 
 
 
 
30622c8
a6be34b
 
 
 
 
f34662a
 
b1ae044
b9f7ba4
 
 
 
 
c16a803
40446f2
6c154f8
 
 
c16a803
6c154f8
 
79683ca
6c154f8
 
79683ca
c047e75
e4ed31e
cbd85fc
 
6c154f8
 
 
 
 
3c753ee
712c716
6c154f8
712c716
6c154f8
79683ca
6c154f8
 
 
 
 
 
6f1f7b0
30f78b0
 
 
 
 
 
 
 
 
 
1229389
f34662a
 
 
 
5a36eaa
f34662a
 
0212a08
f34662a
e280197
1794bc0
 
 
 
bd750f5
1794bc0
 
5a36eaa
1794bc0
 
 
 
 
 
30f78b0
1794bc0
 
1229389
1794bc0
1229389
1794bc0
 
30f78b0
b9f7ba4
0e31770
 
c358ea4
6b59874
a25ec23
 
 
 
 
 
 
 
 
 
6d80bec
6b59874
6c154f8
6d80bec
 
 
 
 
 
68299e3
 
 
0e31770
68299e3
 
 
1229389
68299e3
b9f7ba4
6d80bec
 
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
164
165
166
167
168
import logging
import shutil
import tempfile
import subprocess
from pathlib import Path
from moviepy.editor import VideoFileClip
import gradio as gr
import requests
from urllib.parse import urlparse
import http.server
import socketserver
import threading
import atexit
import socket

logging.basicConfig(level=logging.INFO)

PORT = 8000  # Define PORT here

def get_available_port(start_port):
    while True:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            try:
                s.bind(("", start_port))
                return start_port
            except OSError as e:
                if e.errno == 98:  # errno 98 means address already in use
                    start_port += 1  # If address in use, increment port and try again
                    continue
                else:
                    raise

temp_dir = Path(tempfile.mkdtemp())  # Create a temporary directory using mkdtemp() instead of TemporaryDirectory()

class Handler(http.server.SimpleHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, directory=str(temp_dir), **kwargs)

def start_server():
    global PORT  # Make sure to define PORT as global
    PORT = get_available_port(PORT)
    with socketserver.TCPServer(("", PORT), Handler) as httpd:
        print(f"Serving at port {PORT}")
        httpd.serve_forever()
       
t = threading.Thread(target=start_server)
t.start()

# Cleanup function to remove the temporary directory when the script is exited
@atexit.register
def cleanup():
    shutil.rmtree(temp_dir)

logging.basicConfig(level=logging.INFO)

def download_file(url, destination):
    """Downloads a file from a url to a destination."""
    response = requests.get(url)
    response.raise_for_status()
    with open(destination, 'wb') as f:
        f.write(response.content)

def get_input_path(video_file, video_url, temp_dir):
    """Returns the path to the video file, downloading it if necessary."""
    if video_file is not None:
        return Path(video_file.name)
    elif video_url:
        url_path = urlparse(video_url).path
        file_name = Path(url_path).name
        destination = temp_dir / file_name
        download_file(video_url, destination)
        return destination
    else:
        raise ValueError("No input was provided.")

def get_output_path(input_path, temp_dir, res):
    """Returns the path to the output file, creating it if necessary."""
    output_path = temp_dir / (Path(input_path).stem + f"_{res}.m3u8")
    return output_path

def get_aspect_ratio(input_path, aspect_ratio):
    """Returns the aspect ratio of the video, calculating it if necessary."""
    if aspect_ratio is not None:
        return aspect_ratio
    video = VideoFileClip(str(input_path))
    return f"{video.size[0]}:{video.size[1]}"

def create_master_playlist(output_paths, temp_dir):
    """Creates a master playlist .m3u8 file that includes all other .m3u8 files."""
    master_playlist_path = temp_dir / "master_playlist.m3u8"
    with open(master_playlist_path, 'w') as f:
        f.write("#EXTM3U\n")
        for path in output_paths:
            f.write(f"#EXT-X-STREAM-INF:BANDWIDTH={1000*1000},RESOLUTION={path.stem.split('_')[-1]}\n")
            f.write(f"{path.name}\n")
    return master_playlist_path

def convert_video(video_file, quality, aspect_ratio, video_url):
    # Use the already-created temp_dir instead of creating a new one
    # temp_dir = Path(tempfile.mkdtemp())
    input_path = get_input_path(video_file, video_url, temp_dir)
    aspect_ratio = get_aspect_ratio(input_path, aspect_ratio)

    video = VideoFileClip(str(input_path))
    original_height = video.size[1]

    output_paths = []  # Define output_paths as an empty list

    for res in standard_resolutions:
        # Skip if resolution is higher than original
        if res > original_height:
            continue

        scale = "-1:" + str(res)  # we scale the height to res and keep aspect ratio
        output_path = get_output_path(input_path, temp_dir, str(res) + 'p')  # pass the resolution to create a unique output file

        ffmpeg_command = [
            "ffmpeg", "-i", str(input_path), "-c:v", "libx264", "-crf", str(quality),
            "-vf", f"scale={scale}:force_original_aspect_ratio=decrease,pad=ceil(iw/2)*2:ceil(ih/2)*2",
            "-hls_time", "10", "-hls_playlist_type", "vod", "-hls_segment_filename",
            str(temp_dir / f"{output_path.stem}_%03d.ts"), str(output_path)
        ]

        logging.info("Running ffmpeg command: " + ' '.join(ffmpeg_command))
        subprocess.run(ffmpeg_command, check=True)

        output_paths.append(output_path)  # Append each completed output file to the output_paths list

    master_playlist_path = create_master_playlist(output_paths, temp_dir)
    output_paths.append(master_playlist_path)

    # Convert Path objects to URLs before returning
    return format_output([{'name': path.stem, 'url': f"http://localhost:{PORT}/{path.name}"} for path in output_paths])


video_file = gr.File(label="Video File")
quality = gr.inputs.Dropdown(
    choices=["18", "23", "27", "28", "32"], 
    default="27", 
    label="Quality"
)
aspect_ratio = gr.inputs.Dropdown(
    choices=["16:9",  "1:1", "4:3", "3:2", "5:4", "21:9", "1.85:1", "2.35:1", "3:1", "360", "9:16", "2:1", "1:2", "9:1"], 
    default="16:9", 
    label="Aspect ratio (width:height)"
)
standard_resolutions = [4320, 2160, 1440, 1080, 720, 480, 360, 240, 144]  # 8K, 4K, 2K, Full HD, HD, SD in pixels
video_url = gr.Textbox(label="Or enter video URL")

def format_output(output):
    html = ""
    for file in output:
        html += f'<p><a href="{file["url"]}">{file["name"]}</a></p>'
    return html

interface = gr.Interface(
    fn=convert_video, 
    inputs=[video_file, quality, aspect_ratio, video_url],
    outputs=gr.outputs.HTML(label="Download Links"),
    title="Video Converter",
    description="A simple video converter app",
    allow_flagging=False, 
)
interface.launch()