|
|
import streamlit as st |
|
|
import random |
|
|
import tempfile |
|
|
import os |
|
|
import subprocess |
|
|
from datetime import datetime |
|
|
from dotenv import load_dotenv |
|
|
|
|
|
load_dotenv() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BASE_MODELS = [ |
|
|
"iPhone 11", "iPhone 11 Pro", "iPhone 12", "iPhone 12 Pro", |
|
|
"iPhone 13", "iPhone 13 Pro", "iPhone 13 Pro Max", |
|
|
"iPhone 14", "iPhone 14 Pro", "iPhone 14 Pro Max", |
|
|
"iPhone 15", "iPhone 15 Plus", "iPhone 15 Pro", "iPhone 15 Pro Max", |
|
|
"iPhone 16", "iPhone 16 Pro", "iPhone 16 Pro Max" |
|
|
] |
|
|
|
|
|
IOS_VERSIONS = [f"iOS {v}" for v in range(14, 21)] |
|
|
ISO_SPEEDS = [100, 200, 400, 800, 1600] |
|
|
F_NUMBERS = ["f/1.3", "f/1.4", "f/1.5", "f/1.6", "f/1.8"] |
|
|
FOCAL_LENGTHS = ["3.99 mm", "4.0 mm", "4.2 mm", "4.3 mm", "5.0 mm", "5.1 mm", "5.2 mm", "6.0 mm", "6.1 mm", "6.2 mm", "6.5 mm", "6.6 mm", "6.7 mm"] |
|
|
EXPOSURES = ["1/60", "1/70", "1/100", "1/110", "1/120", "1/130", "1/140", "1/200", "1/220", "1/240", "1/300", "1/320", "1/400", "1/500", "1/600", "1/700", "1/1000"] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_max_random_metadata(): |
|
|
"""Generate fully randomized video metadata simulating a random iPhone.""" |
|
|
model = random.choice(BASE_MODELS) |
|
|
f_number = random.choice(F_NUMBERS) |
|
|
focal_length = random.choice(FOCAL_LENGTHS) |
|
|
exposure = random.choice(EXPOSURES) |
|
|
iso_speed = random.choice(ISO_SPEEDS) |
|
|
ios_version = random.choice(IOS_VERSIONS) |
|
|
|
|
|
latitude = round(random.uniform(-90, 90), 6) |
|
|
longitude = round(random.uniform(-180, 180), 6) |
|
|
altitude = round(random.uniform(0, 100), 1) |
|
|
camera_id = f"{model}-back-{random.randint(1000,9999)}" |
|
|
|
|
|
metadata = { |
|
|
"com.apple.quicktime.make": "Apple", |
|
|
"com.apple.quicktime.model": model, |
|
|
"com.apple.quicktime.software": ios_version, |
|
|
"com.apple.quicktime.creationdate": datetime.now().strftime("%Y-%m-%dT%H:%M:%S"), |
|
|
"com.apple.quicktime.location.name": "Random Location", |
|
|
"com.apple.quicktime.location.ISO6709": f"{latitude}+{longitude}/", |
|
|
"com.apple.quicktime.lensmodel": f"{model} {focal_length} {f_number}", |
|
|
"com.apple.quicktime.make.identifier": "Apple iPhone Camera", |
|
|
"com.apple.quicktime.camera.identifier": camera_id, |
|
|
"com.apple.quicktime.exposuretime": exposure, |
|
|
"com.apple.quicktime.fnumber": f_number, |
|
|
"com.apple.quicktime.ISOSpeed": str(iso_speed), |
|
|
"com.apple.quicktime.focallength": focal_length, |
|
|
"com.apple.quicktime.gps.latitude": str(latitude), |
|
|
"com.apple.quicktime.gps.longitude": str(longitude), |
|
|
"com.apple.quicktime.gps.altitude": str(altitude) |
|
|
} |
|
|
return metadata |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def update_video_metadata(video_bytes: bytes) -> tuple[bytes, dict]: |
|
|
"""Apply maximal randomized metadata to a video.""" |
|
|
metadata = generate_max_random_metadata() |
|
|
|
|
|
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as temp_in: |
|
|
temp_in.write(video_bytes) |
|
|
temp_in_path = temp_in.name |
|
|
|
|
|
fd, temp_out_path = tempfile.mkstemp(suffix=".mp4") |
|
|
os.close(fd) |
|
|
|
|
|
metadata_args = [] |
|
|
for key, value in metadata.items(): |
|
|
safe_value = str(value).replace(":", "-") |
|
|
metadata_args.extend(["-metadata", f"{key}={safe_value}"]) |
|
|
|
|
|
cmd = [ |
|
|
"ffmpeg", "-y", "-i", temp_in_path, |
|
|
*metadata_args, |
|
|
"-movflags", "use_metadata_tags", |
|
|
"-map_metadata", "0", |
|
|
"-codec", "copy", |
|
|
temp_out_path |
|
|
] |
|
|
|
|
|
process = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
|
|
if process.returncode != 0: |
|
|
os.remove(temp_in_path) |
|
|
if os.path.exists(temp_out_path): |
|
|
os.remove(temp_out_path) |
|
|
raise RuntimeError(f"FFmpeg failed: {process.stderr.decode(errors='ignore')}") |
|
|
|
|
|
with open(temp_out_path, "rb") as f: |
|
|
updated_bytes = f.read() |
|
|
|
|
|
os.remove(temp_in_path) |
|
|
os.remove(temp_out_path) |
|
|
return updated_bytes, metadata |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ACCESS_TOKEN = os.getenv("ACCESS_TOKEN") |
|
|
|
|
|
|
|
|
if "authenticated" not in st.session_state: |
|
|
st.session_state.authenticated = False |
|
|
|
|
|
|
|
|
if not st.session_state.authenticated: |
|
|
st.title("π Video Metadata Editor") |
|
|
st.markdown("---") |
|
|
|
|
|
|
|
|
user_input = st.text_input( |
|
|
"Enter Access Token:", |
|
|
type="password", |
|
|
placeholder="Enter your access token" |
|
|
) |
|
|
|
|
|
|
|
|
login_button = st.button("Login", type="primary") |
|
|
|
|
|
if login_button: |
|
|
if user_input == ACCESS_TOKEN: |
|
|
st.session_state.authenticated = True |
|
|
st.rerun() |
|
|
else: |
|
|
st.error("β Invalid access token. Please try again.") |
|
|
|
|
|
st.stop() |
|
|
|
|
|
|
|
|
col1, col2 = st.columns([5, 1]) |
|
|
|
|
|
with col1: |
|
|
st.title("Video Metadata Editor") |
|
|
|
|
|
with col2: |
|
|
if st.button("Logout", type="secondary"): |
|
|
st.session_state.authenticated = False |
|
|
st.rerun() |
|
|
|
|
|
uploaded_video = st.file_uploader("Upload an MP4 video", type=["mp4"]) |
|
|
|
|
|
if uploaded_video is not None: |
|
|
st.subheader("Original Video") |
|
|
st.video(uploaded_video) |
|
|
|
|
|
if st.button("Change Metadata"): |
|
|
with st.spinner("Applying metadata..."): |
|
|
try: |
|
|
video_bytes = uploaded_video.read() |
|
|
updated_video, metadata = update_video_metadata(video_bytes) |
|
|
|
|
|
st.success(f"Metadata updated successfully!") |
|
|
|
|
|
st.subheader("Updated Video") |
|
|
st.video(updated_video) |
|
|
|
|
|
st.download_button( |
|
|
"Download Updated Video", |
|
|
updated_video, |
|
|
file_name="updated_metadata.mp4", |
|
|
mime="video/mp4" |
|
|
) |
|
|
|
|
|
except Exception as e: |
|
|
st.error(f"Error: {e}") |