| import streamlit as st
|
| import tempfile
|
| import tempfile
|
| import os
|
| import time
|
| import ffmpeg
|
| import subprocess
|
| import platform
|
| from db import init_db, insert_record, get_records
|
|
|
|
|
| init_db()
|
|
|
| st.set_page_config(page_title="Video Compressor", layout="wide")
|
|
|
| def get_video_info(file_path):
|
| try:
|
| probe = ffmpeg.probe(file_path)
|
| video_stream = next((stream for stream in probe['streams'] if stream['codec_type'] == 'video'), None)
|
| audio_stream = next((stream for stream in probe['streams'] if stream['codec_type'] == 'audio'), None)
|
|
|
| duration = float(probe['format']['duration'])
|
| width = int(video_stream['width'])
|
| height = int(video_stream['height'])
|
|
|
|
|
| audio_bitrate = 128000
|
| if audio_stream and 'bit_rate' in audio_stream:
|
| audio_bitrate = int(audio_stream['bit_rate'])
|
| elif 'bit_rate' in probe['format']:
|
|
|
| audio_bitrate = 128000
|
|
|
| return {
|
| 'duration': duration,
|
| 'width': width,
|
| 'height': height,
|
| 'audio_bitrate': audio_bitrate
|
| }
|
| except Exception as e:
|
| st.error(f"Error probing video: {e}")
|
| return None
|
|
|
| def compress_video_2pass(input_path, output_path, start_time, end_time, target_size_mb, resolution, fps, progress_bar):
|
| info = get_video_info(input_path)
|
| if not info:
|
| return False
|
|
|
| duration = end_time - start_time
|
| if duration <= 0:
|
| st.error("Invalid trim times (End time must be greater than Start time).")
|
| return False
|
|
|
|
|
|
|
| target_size_bits = target_size_mb * 8388608
|
|
|
|
|
| audio_bitrate = info['audio_bitrate']
|
|
|
|
|
| total_bitrate = target_size_bits / duration
|
|
|
|
|
| video_bitrate = total_bitrate - audio_bitrate
|
|
|
|
|
| if video_bitrate < 10000:
|
| st.warning("Target size is too small for this duration. Video quality will be extremely poor or fail. Setting a minimum bitrate.")
|
| video_bitrate = 10000
|
|
|
| video_bitrate_k = int(video_bitrate / 1000)
|
| audio_bitrate_k = int(audio_bitrate / 1000)
|
|
|
|
|
| scale_param = ""
|
| if resolution != "Original":
|
| height = int(resolution.replace("p", ""))
|
| scale_param = f"scale=-2:{height}"
|
|
|
| devnull = os.devnull
|
|
|
| input_kwargs = {'ss': start_time, 'to': end_time}
|
|
|
|
|
| v_ext = ffmpeg.input(input_path, **input_kwargs)
|
| a_ext = v_ext.audio
|
| v = v_ext.video
|
|
|
| if scale_param:
|
| v = v.filter('scale', -2, int(resolution.replace('p', '')))
|
|
|
| if fps and fps != "Original":
|
| v = v.filter('fps', fps=int(fps))
|
|
|
|
|
| progress_bar.progress(20, text="Pass 1/2: Analyzing Video...")
|
| passlog_base = os.path.join(tempfile.gettempdir(), f"ffmpeg2pass_{int(time.time())}")
|
| try:
|
| pass1_args = {
|
| 'c:v': 'libx264',
|
| 'b:v': f'{video_bitrate_k}k',
|
| 'pass': 1,
|
| 'passlogfile': passlog_base,
|
| 'f': 'mp4',
|
| 'y': None
|
| }
|
|
|
|
|
|
|
| out1 = ffmpeg.output(v, devnull, **pass1_args)
|
| ffmpeg.run(out1, quiet=True, overwrite_output=True)
|
| except ffmpeg.Error as e:
|
| if e.stderr:
|
| st.error(f"Pass 1 Error: {e.stderr.decode('utf8')}")
|
| return False
|
|
|
|
|
| progress_bar.progress(60, text="Pass 2/2: Compressing Video...")
|
| try:
|
| pass2_args = {
|
| 'c:v': 'libx264',
|
| 'b:v': f'{video_bitrate_k}k',
|
| 'pass': 2,
|
| 'passlogfile': passlog_base,
|
| 'c:a': 'aac',
|
| 'b:a': f'{audio_bitrate_k}k',
|
| 'y': None
|
| }
|
| out2 = ffmpeg.output(v, a_ext, output_path, **pass2_args)
|
| ffmpeg.run(out2, quiet=True, overwrite_output=True)
|
|
|
| except ffmpeg.Error as e:
|
| if e.stderr:
|
| st.error(f"Pass 2 Error: {e.stderr.decode('utf8')}")
|
| return False
|
| finally:
|
|
|
| for ext in ['-0.log', '-0.log.mbtree']:
|
| logfile = f"{passlog_base}{ext}"
|
| if os.path.exists(logfile):
|
| try:
|
| os.remove(logfile)
|
| except:
|
| pass
|
|
|
| progress_bar.progress(100, text="Done!")
|
| return True
|
|
|
| st.title("Video Compressor & Trimmer")
|
| st.markdown("Upload a video, trim it, set a target size, and this tool will use **2-pass encoding** to perfectly hit your target size (MB).")
|
|
|
| tab1, tab2 = st.tabs(["Compressor", "History"])
|
|
|
| with tab1:
|
| uploaded_file = st.file_uploader("Upload Video", type=['mp4', 'mov', 'avi', 'mkv'])
|
|
|
| if uploaded_file is not None:
|
|
|
| with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as temp_input:
|
| temp_input.write(uploaded_file.read())
|
| temp_input_path = temp_input.name
|
|
|
| info = get_video_info(temp_input_path)
|
| if info:
|
| st.success(f"Video Loaded: {info['duration']:.2f} seconds | {info['width']}x{info['height']}")
|
|
|
| col1, col2 = st.columns(2)
|
| with col1:
|
| start_time = st.number_input("Start Time (seconds)", min_value=0.0, max_value=info['duration']-0.1, value=0.0, step=1.0)
|
| with col2:
|
| end_time = st.number_input("End Time (seconds)", min_value=0.1, max_value=info['duration'], value=info['duration'], step=1.0)
|
|
|
| col3, col4, col5 = st.columns(3)
|
| with col3:
|
| resolution = st.selectbox("Resolution", ["Original", "1080p", "720p", "480p", "360p"])
|
| with col4:
|
| fps = st.selectbox("FPS", ["Original", "60", "30", "24", "15"])
|
| with col5:
|
| target_size = st.number_input("Target Size (MB)", min_value=0.1, value=10.0, step=1.0)
|
|
|
| if st.button("Compress & Export"):
|
| st.info("Ensure you have `ffmpeg` installed on your system.")
|
| progress_bar = st.progress(0, text="Initializing...")
|
|
|
| with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as temp_output:
|
| temp_output_path = temp_output.name
|
|
|
| start_perf = time.time()
|
| success = compress_video_2pass(temp_input_path, temp_output_path, start_time, end_time, target_size, resolution, fps, progress_bar)
|
| end_perf = time.time()
|
|
|
| if success:
|
| final_size_bytes = os.path.getsize(temp_output_path)
|
| final_size_mb = final_size_bytes / (1024 * 1024)
|
| duration_sec = end_time - start_time
|
|
|
| st.success(f"Compression Successful! Time taken: {end_perf - start_perf:.2f}s")
|
| st.metric("Final Size", f"{final_size_mb:.2f} MB")
|
|
|
| with open(temp_output_path, "rb") as file:
|
| btn = st.download_button(
|
| label="Download Compressed Video",
|
| data=file,
|
| file_name=f"compressed_{uploaded_file.name}",
|
| mime="video/mp4"
|
| )
|
|
|
|
|
| insert_record(uploaded_file.name, target_size, final_size_mb, duration_sec, resolution, fps if fps != "Original" else info.get('r_frame_rate', 0))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| with tab2:
|
| st.header("Compression History")
|
| records = get_records()
|
| if not records:
|
| st.write("No compression history found.")
|
| else:
|
| import pandas as pd
|
| df = pd.DataFrame(records, columns=['ID', 'Original File', 'Target Size (MB)', 'Final Size (MB)', 'Duration (s)', 'Resolution', 'FPS', 'Timestamp'])
|
| st.dataframe(df, hide_index=True)
|
|
|