|
|
import gradio as gr |
|
|
import subprocess |
|
|
import os |
|
|
import re |
|
|
|
|
|
def compress_video(input_video, resolution, progress=gr.Progress()): |
|
|
if not input_video: |
|
|
raise gr.Error("Please upload a video file!") |
|
|
|
|
|
output_filename = "compressed_" + os.path.basename(input_video) |
|
|
output_path = os.path.join(os.path.dirname(input_video), output_filename) |
|
|
|
|
|
try: |
|
|
|
|
|
duration_cmd = [ |
|
|
"ffprobe", |
|
|
"-v", "error", |
|
|
"-show_entries", "format=duration", |
|
|
"-of", "default=noprint_wrappers=1:nokey=1", |
|
|
input_video |
|
|
] |
|
|
duration_output = subprocess.check_output(duration_cmd, text=True, stderr=subprocess.STDOUT) |
|
|
total_duration = float(duration_output.strip()) |
|
|
|
|
|
command = [ |
|
|
"ffmpeg", |
|
|
"-y", |
|
|
"-i", input_video, |
|
|
"-vf", f"scale=-2:{resolution}", |
|
|
"-c:v", "libx264", |
|
|
"-crf", "28", |
|
|
"-preset", "veryslow", |
|
|
"-c:a", "aac", |
|
|
"-b:a", "64k", |
|
|
"-movflags", "+faststart", |
|
|
output_path, |
|
|
] |
|
|
|
|
|
|
|
|
process = subprocess.Popen(command, stderr=subprocess.PIPE, text=True, encoding='utf-8') |
|
|
progress(0, desc="Starting compression...") |
|
|
|
|
|
for line in process.stderr: |
|
|
|
|
|
match = re.search(r"time=(\d+:\d+:\d+\.\d+)", line) |
|
|
if match: |
|
|
current_time_str = match.group(1) |
|
|
|
|
|
h, m, s = map(float, current_time_str.split(':')) |
|
|
current_time = h * 3600 + m * 60 + s |
|
|
|
|
|
percentage = min(current_time / total_duration, 1.0) |
|
|
progress(percentage, desc=f"Compressing: {int(percentage * 100)}%") |
|
|
|
|
|
process.stderr.close() |
|
|
return_code = process.wait() |
|
|
if return_code != 0: |
|
|
raise subprocess.CalledProcessError(return_code,command, process.stderr) |
|
|
|
|
|
|
|
|
except subprocess.CalledProcessError as e: |
|
|
error_message = e.stderr.decode('utf-8', 'ignore') |
|
|
if "width not divisible by 2" in error_message: |
|
|
error_message += "\n\nError: Video width is not divisible by 2. FFmpeg requires both width and height to be even numbers. Please try a different video or pre-process it with a video editing software." |
|
|
elif "No such file or directory" in error_message: |
|
|
error_message += "\n\nError: Input file or FFmpeg not found. Please check if the file path is correct and FFmpeg is installed." |
|
|
elif "Permission denied" in error_message: |
|
|
error_message +="\n\nError: Permission denied to access the file. Please check the file permissions." |
|
|
elif "Invalid data found when processing input" in error_message: |
|
|
error_message += "\n\nError: Input file is invalid or corrupted. Please check if the video file can be played correctly." |
|
|
else: |
|
|
error_message += "\n\nFFmpeg error output:\n" + error_message |
|
|
raise gr.Error(f"Video compression failed: {error_message}") |
|
|
|
|
|
except FileNotFoundError: |
|
|
raise gr.Error("FFmpeg or ffprobe not found. Please ensure FFmpeg is installed and added to your system's PATH environment variable.") |
|
|
except ValueError: |
|
|
raise gr.Error("Could not retrieve video duration. Please check if the video file is valid.") |
|
|
except Exception as e: |
|
|
raise gr.Error("An unknown error occurred: " + str(e)) |
|
|
|
|
|
return output_path |
|
|
|
|
|
|
|
|
|
|
|
css = """ |
|
|
.container { |
|
|
max-width: 800px; |
|
|
margin: auto; |
|
|
padding: 20px; |
|
|
border: 1px solid #ddd; |
|
|
border-radius: 5px; |
|
|
} |
|
|
h1 { |
|
|
text-align: center; |
|
|
color: #333; |
|
|
} |
|
|
.description { |
|
|
text-align: center; |
|
|
margin-bottom: 20px; |
|
|
color: #555; |
|
|
} |
|
|
.instructions { |
|
|
margin-top: 20px; |
|
|
padding: 10px; |
|
|
border: 1px dashed #aaa; |
|
|
border-radius: 5px; |
|
|
color: #444; |
|
|
} |
|
|
""" |
|
|
|
|
|
with gr.Blocks(css=css, title="Video Compressor") as demo: |
|
|
gr.HTML("<div class='container'>") |
|
|
gr.Markdown("# Video Compressor") |
|
|
gr.Markdown("## Easily compress videos and save storage space!", elem_classes="description") |
|
|
gr.Markdown( |
|
|
""" |
|
|
This tool uses FFmpeg to compress videos, supporting multiple resolution options. It significantly reduces video file size while maintaining clarity as much as possible. |
|
|
Ideal for: |
|
|
* **Saving storage space:** Reduce the disk space occupied by videos. |
|
|
* **Faster upload speeds:** Compressed videos are easier to upload to the internet. |
|
|
* **Optimizing web page loading:** Use smaller videos on web pages to improve page load speed. |
|
|
""", |
|
|
elem_classes="description" |
|
|
) |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(): |
|
|
input_video = gr.Video(label="Upload Video", interactive=True) |
|
|
resolution = gr.Dropdown( |
|
|
label="Select Resolution", |
|
|
choices=["240", "360", "480", "720"], |
|
|
value="360", |
|
|
interactive=True |
|
|
) |
|
|
compress_button = gr.Button("Compress Video") |
|
|
|
|
|
with gr.Column(): |
|
|
output_video = gr.Video(label="Compressed Video", interactive=False) |
|
|
|
|
|
gr.Markdown( |
|
|
""" |
|
|
## Instructions |
|
|
1. **Upload Video:** Click the "Upload Video" button and select the video file you want to compress. |
|
|
2. **Select Resolution:** Choose the target resolution from the dropdown list (e.g., 360p). Smaller numbers mean smaller files and lower quality. |
|
|
3. **Compress Video:** Click the "Compress Video" button. The compression process may take some time, depending on the video size and your computer's performance. |
|
|
4. **Download Video:** Once the compression is complete, you can preview and download the compressed video below. |
|
|
""", |
|
|
elem_classes="instructions" |
|
|
) |
|
|
gr.HTML("</div>") |
|
|
|
|
|
compress_button.click( |
|
|
compress_video, |
|
|
inputs=[input_video, resolution], |
|
|
outputs=[output_video] |
|
|
) |
|
|
|
|
|
demo.queue(default_concurrency_limit=3) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch(show_api=False,show_error=True) |