label editor
This commit is contained in:
145
app/app.py
145
app/app.py
@@ -1,155 +1,38 @@
|
|||||||
import os
|
|
||||||
import base64
|
|
||||||
import sqlite3
|
|
||||||
import streamlit as st
|
import streamlit as st
|
||||||
import pandas as pd
|
import os
|
||||||
from glob import glob
|
import db
|
||||||
|
from views import show_video_thumbnail
|
||||||
|
from controllers import label_widget
|
||||||
|
|
||||||
# --- chemins ---
|
|
||||||
ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
||||||
MODEL_DIR = os.path.join(ROOT_DIR, "model")
|
|
||||||
DB_PATH = os.path.expanduser("~/Documents/.DanceVideos/db.sqlite")
|
|
||||||
VIDEO_DIR = os.path.expanduser("~/Documents/.DanceVideos/videos")
|
|
||||||
|
|
||||||
# --- initialisation de la base ---
|
|
||||||
def init_db():
|
|
||||||
if not os.path.exists(DB_PATH):
|
|
||||||
st.warning("⚠️ Base de données non trouvée, création d'une nouvelle...")
|
|
||||||
conn = sqlite3.connect(DB_PATH)
|
|
||||||
sql_files = sorted(glob(os.path.join(MODEL_DIR, "*.sql")))
|
|
||||||
for sql_file in sql_files:
|
|
||||||
with open(sql_file, "r", encoding="utf-8") as f:
|
|
||||||
sql = f.read()
|
|
||||||
try:
|
|
||||||
conn.executescript(sql)
|
|
||||||
# st.info(f"✅ Exécuté : {os.path.basename(sql_file)}")
|
|
||||||
except sqlite3.Error as e:
|
|
||||||
st.error(f"Erreur dans {sql_file} : {e}")
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
# --- utilitaires base ---
|
|
||||||
def get_conn():
|
|
||||||
return sqlite3.connect(DB_PATH, check_same_thread=False)
|
|
||||||
|
|
||||||
# --- chargement des vidéos ---
|
|
||||||
def load_videos():
|
|
||||||
conn = get_conn()
|
|
||||||
try:
|
|
||||||
return pd.read_sql_query("SELECT * FROM videos", conn)
|
|
||||||
except Exception as e:
|
|
||||||
st.error(f"Erreur chargement vidéos : {e}")
|
|
||||||
return pd.DataFrame()
|
|
||||||
finally:
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# --- interface principale ---
|
|
||||||
def main():
|
|
||||||
st.set_page_config(page_title="Dance Video Manager", layout="wide")
|
st.set_page_config(page_title="Dance Video Manager", layout="wide")
|
||||||
st.title("💃 Dance Video Manager")
|
st.title("💃 Dance Video Manager")
|
||||||
|
|
||||||
# --- Barre latérale : réglage utilisateur ---
|
# --- Barre latérale : hauteur max ---
|
||||||
st.sidebar.header("⚙️ Apparence")
|
st.sidebar.header("⚙️ Apparence")
|
||||||
max_height = st.sidebar.slider("Hauteur maximale (px)", 100, 800, 300, 50)
|
max_height = st.sidebar.slider("Hauteur maximale (px)", 100, 800, 300, 50)
|
||||||
st.markdown(
|
st.markdown(
|
||||||
f"""
|
f"""
|
||||||
<style>
|
<style>
|
||||||
/* Limite globale de hauteur */
|
|
||||||
img, video {{
|
img, video {{
|
||||||
max-height: {max_height}px !important;
|
max-height: {max_height}px !important;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
transition: all 0.3s ease-in-out;
|
transition: all 0.3s ease-in-out;
|
||||||
|
border-radius: 8px;
|
||||||
}}
|
}}
|
||||||
</style>
|
</style>
|
||||||
""",
|
""",
|
||||||
unsafe_allow_html=True
|
unsafe_allow_html=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# if st.button("🔄 Initialiser / synchroniser la base"):
|
# --- Charger données ---
|
||||||
init_db()
|
videos = db.load_videos()
|
||||||
|
labels = db.load_labels()
|
||||||
|
|
||||||
st.sidebar.header("Navigation")
|
|
||||||
page = st.sidebar.radio("Choisir une vue :", ["Vidéos", "Playlists", "Labels"])
|
|
||||||
|
|
||||||
videos = load_videos()
|
|
||||||
if videos.empty:
|
if videos.empty:
|
||||||
st.warning("Aucune vidéo trouvée dans la base.")
|
st.warning("Aucune vidéo trouvée dans la base.")
|
||||||
return
|
else:
|
||||||
|
for _, video in videos.iterrows():
|
||||||
if page == "Vidéos":
|
col3 = show_video_thumbnail(video)
|
||||||
conn = get_conn()
|
preselected = db.load_video_labels(video["mp4_file_name"])
|
||||||
try:
|
|
||||||
# Charger tous les labels existants
|
|
||||||
existing_labels = pd.read_sql_query("SELECT name FROM labels ORDER BY name", conn)["name"].tolist()
|
|
||||||
|
|
||||||
for _, row in videos.iterrows():
|
|
||||||
col1, col2, col3 = st.columns([1, 2, 1])
|
|
||||||
|
|
||||||
with col1:
|
|
||||||
thumb = row["thumbnail_file"]
|
|
||||||
if os.path.exists(thumb):
|
|
||||||
st.image(thumb, width="stretch")
|
|
||||||
st.caption(row["file_name"])
|
|
||||||
|
|
||||||
with col2:
|
|
||||||
mp4 = row["mp4_file"]
|
|
||||||
video_name = row["file_name"]
|
|
||||||
if os.path.exists(mp4):
|
|
||||||
if st.button(f"▶️ 📅 {row.get('record_datetime', '')} — 🕒 {row.get('day_of_week', '')}", key=f"play_{video_name}"):
|
|
||||||
st.video(mp4)
|
|
||||||
|
|
||||||
with col3:
|
with col3:
|
||||||
# Sélecteur de label
|
label_widget(video["mp4_file_name"], preselected=preselected)
|
||||||
label_selected = st.selectbox(
|
|
||||||
"Label",
|
|
||||||
options=existing_labels + ["Autre…"],
|
|
||||||
key=f"label_select_{video_name}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Création d’un label personnalisé
|
|
||||||
if label_selected == "Autre…":
|
|
||||||
label_selected = st.text_input(
|
|
||||||
"Entrer un label personnalisé",
|
|
||||||
value="",
|
|
||||||
key=f"label_input_{video_name}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Sauvegarde
|
|
||||||
if label_selected:
|
|
||||||
if st.button("💾 Sauvegarder", key=f"save_label_{video_name}"):
|
|
||||||
cursor = conn.cursor()
|
|
||||||
# Crée le label s'il n'existe pas
|
|
||||||
cursor.execute("INSERT OR IGNORE INTO labels (name) VALUES (?)", (label_selected,))
|
|
||||||
# Récupère l'ID
|
|
||||||
cursor.execute("SELECT id FROM labels WHERE name=?", (label_selected,))
|
|
||||||
label_id = cursor.fetchone()[0]
|
|
||||||
# Associe à la vidéo
|
|
||||||
cursor.execute("""
|
|
||||||
INSERT OR REPLACE INTO video_labels (video_file_name, label_id)
|
|
||||||
VALUES (?, ?)
|
|
||||||
""", (video_name, label_id))
|
|
||||||
conn.commit()
|
|
||||||
st.success(f"Label '{label_selected}' enregistré pour {video_name}")
|
|
||||||
|
|
||||||
finally:
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
elif page == "Playlists":
|
|
||||||
st.subheader("📜 Gestion des playlists")
|
|
||||||
conn = get_conn()
|
|
||||||
playlists = pd.read_sql_query("SELECT * FROM playlists", conn)
|
|
||||||
st.dataframe(playlists)
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
elif page == "Labels":
|
|
||||||
st.subheader("🏷️ Gestion des labels")
|
|
||||||
conn = get_conn()
|
|
||||||
labels = pd.read_sql_query("SELECT * FROM labels", conn)
|
|
||||||
st.dataframe(labels)
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|||||||
155
app/app.py.old
Normal file
155
app/app.py.old
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
import os
|
||||||
|
import base64
|
||||||
|
import sqlite3
|
||||||
|
import streamlit as st
|
||||||
|
import pandas as pd
|
||||||
|
from glob import glob
|
||||||
|
|
||||||
|
# --- chemins ---
|
||||||
|
ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||||
|
MODEL_DIR = os.path.join(ROOT_DIR, "model")
|
||||||
|
DB_PATH = os.path.expanduser("~/Documents/.DanceVideos/db.sqlite")
|
||||||
|
VIDEO_DIR = os.path.expanduser("~/Documents/.DanceVideos/videos")
|
||||||
|
|
||||||
|
# --- initialisation de la base ---
|
||||||
|
def init_db():
|
||||||
|
if not os.path.exists(DB_PATH):
|
||||||
|
st.warning("⚠️ Base de données non trouvée, création d'une nouvelle...")
|
||||||
|
conn = sqlite3.connect(DB_PATH)
|
||||||
|
sql_files = sorted(glob(os.path.join(MODEL_DIR, "*.sql")))
|
||||||
|
for sql_file in sql_files:
|
||||||
|
with open(sql_file, "r", encoding="utf-8") as f:
|
||||||
|
sql = f.read()
|
||||||
|
try:
|
||||||
|
conn.executescript(sql)
|
||||||
|
# st.info(f"✅ Exécuté : {os.path.basename(sql_file)}")
|
||||||
|
except sqlite3.Error as e:
|
||||||
|
st.error(f"Erreur dans {sql_file} : {e}")
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
# --- utilitaires base ---
|
||||||
|
def get_conn():
|
||||||
|
return sqlite3.connect(DB_PATH, check_same_thread=False)
|
||||||
|
|
||||||
|
# --- chargement des vidéos ---
|
||||||
|
def load_videos():
|
||||||
|
conn = get_conn()
|
||||||
|
try:
|
||||||
|
return pd.read_sql_query("SELECT * FROM videos", conn)
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Erreur chargement vidéos : {e}")
|
||||||
|
return pd.DataFrame()
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# --- interface principale ---
|
||||||
|
def main():
|
||||||
|
st.set_page_config(page_title="Dance Video Manager", layout="wide")
|
||||||
|
st.title("💃 Dance Video Manager")
|
||||||
|
|
||||||
|
# --- Barre latérale : réglage utilisateur ---
|
||||||
|
st.sidebar.header("⚙️ Apparence")
|
||||||
|
max_height = st.sidebar.slider("Hauteur maximale (px)", 100, 800, 300, 50)
|
||||||
|
st.markdown(
|
||||||
|
f"""
|
||||||
|
<style>
|
||||||
|
/* Limite globale de hauteur */
|
||||||
|
img, video {{
|
||||||
|
max-height: {max_height}px !important;
|
||||||
|
object-fit: contain;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
""",
|
||||||
|
unsafe_allow_html=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# if st.button("🔄 Initialiser / synchroniser la base"):
|
||||||
|
init_db()
|
||||||
|
|
||||||
|
st.sidebar.header("Navigation")
|
||||||
|
page = st.sidebar.radio("Choisir une vue :", ["Vidéos", "Playlists", "Labels"])
|
||||||
|
|
||||||
|
videos = load_videos()
|
||||||
|
if videos.empty:
|
||||||
|
st.warning("Aucune vidéo trouvée dans la base.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if page == "Vidéos":
|
||||||
|
conn = get_conn()
|
||||||
|
try:
|
||||||
|
# Charger tous les labels existants
|
||||||
|
existing_labels = pd.read_sql_query("SELECT name FROM labels ORDER BY name", conn)["name"].tolist()
|
||||||
|
|
||||||
|
for _, row in videos.iterrows():
|
||||||
|
col1, col2, col3 = st.columns([1, 2, 1])
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
thumb = row["thumbnail_file"]
|
||||||
|
if os.path.exists(thumb):
|
||||||
|
st.image(thumb, width="stretch")
|
||||||
|
st.caption(row["file_name"])
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
mp4 = row["mp4_file"]
|
||||||
|
video_name = row["file_name"]
|
||||||
|
if os.path.exists(mp4):
|
||||||
|
if st.button(f"▶️ 📅 {row.get('record_datetime', '')} — 🕒 {row.get('day_of_week', '')}", key=f"play_{video_name}"):
|
||||||
|
st.video(mp4)
|
||||||
|
|
||||||
|
with col3:
|
||||||
|
# Sélecteur de label
|
||||||
|
label_selected = st.selectbox(
|
||||||
|
"Label",
|
||||||
|
options=existing_labels + ["Autre…"],
|
||||||
|
key=f"label_select_{video_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Création d’un label personnalisé
|
||||||
|
if label_selected == "Autre…":
|
||||||
|
label_selected = st.text_input(
|
||||||
|
"Entrer un label personnalisé",
|
||||||
|
value="",
|
||||||
|
key=f"label_input_{video_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sauvegarde
|
||||||
|
if label_selected:
|
||||||
|
if st.button("💾 Sauvegarder", key=f"save_label_{video_name}"):
|
||||||
|
cursor = conn.cursor()
|
||||||
|
# Crée le label s'il n'existe pas
|
||||||
|
cursor.execute("INSERT OR IGNORE INTO labels (name) VALUES (?)", (label_selected,))
|
||||||
|
# Récupère l'ID
|
||||||
|
cursor.execute("SELECT id FROM labels WHERE name=?", (label_selected,))
|
||||||
|
label_id = cursor.fetchone()[0]
|
||||||
|
# Associe à la vidéo
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT OR REPLACE INTO video_labels (video_file_name, label_id)
|
||||||
|
VALUES (?, ?)
|
||||||
|
""", (video_name, label_id))
|
||||||
|
conn.commit()
|
||||||
|
st.success(f"Label '{label_selected}' enregistré pour {video_name}")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
elif page == "Playlists":
|
||||||
|
st.subheader("📜 Gestion des playlists")
|
||||||
|
conn = get_conn()
|
||||||
|
playlists = pd.read_sql_query("SELECT * FROM playlists", conn)
|
||||||
|
st.dataframe(playlists)
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
elif page == "Labels":
|
||||||
|
st.subheader("🏷️ Gestion des labels")
|
||||||
|
conn = get_conn()
|
||||||
|
labels = pd.read_sql_query("SELECT * FROM labels", conn)
|
||||||
|
st.dataframe(labels)
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
45
app/controllers.py
Normal file
45
app/controllers.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import streamlit as st
|
||||||
|
import db
|
||||||
|
|
||||||
|
def label_widget(video_name, preselected=None):
|
||||||
|
"""Widget multiselect labels avec création dynamique et réactivité."""
|
||||||
|
preselected = preselected or []
|
||||||
|
|
||||||
|
key_multiselect = f"labels_{video_name}"
|
||||||
|
key_input = f"new_label_{video_name}"
|
||||||
|
|
||||||
|
# --- Charger labels depuis DB ---
|
||||||
|
labels = db.load_labels()
|
||||||
|
|
||||||
|
# --- Initialisation session_state ---
|
||||||
|
if key_multiselect not in st.session_state:
|
||||||
|
st.session_state[key_multiselect] = preselected
|
||||||
|
|
||||||
|
# --- Si "Autre…" dans sélection, afficher input ---
|
||||||
|
current_selected = st.session_state[key_multiselect]
|
||||||
|
if "Autre…" in current_selected:
|
||||||
|
new_label = st.text_input("Entrer un label personnalisé", value="", key=key_input)
|
||||||
|
if new_label.strip():
|
||||||
|
# Ajouter le nouveau label à la DB
|
||||||
|
db.create_labels([new_label.strip()])
|
||||||
|
# Mettre à jour options avant de créer le multiselect
|
||||||
|
labels = db.load_labels()
|
||||||
|
# Remplacer "Autre…" par le nouveau label dans selection
|
||||||
|
current_selected = [l for l in current_selected if l != "Autre…"] + [new_label.strip()]
|
||||||
|
# MAINTENANT c'est sûr de passer current_selected comme default
|
||||||
|
st.session_state[key_multiselect] = current_selected
|
||||||
|
|
||||||
|
# --- Multiselect ---
|
||||||
|
selected = st.multiselect(
|
||||||
|
"Labels",
|
||||||
|
options=labels + ["Autre…"],
|
||||||
|
default=st.session_state[key_multiselect],
|
||||||
|
key=key_multiselect
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Sauvegarde ---
|
||||||
|
if st.button("💾 Sauvegarder labels", key=f"save_{video_name}"):
|
||||||
|
db.save_video_labels(video_name, selected)
|
||||||
|
st.success(f"{len(selected)} label(s) enregistré(s) pour {video_name}")
|
||||||
|
|
||||||
|
return selected
|
||||||
114
app/db.py
Normal file
114
app/db.py
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import sqlite3
|
||||||
|
from pathlib import Path
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
DB_PATH = Path.home() / "Documents/.DanceVideos/db.sqlite"
|
||||||
|
|
||||||
|
# --- Connexion SQLite avec timeout ---
|
||||||
|
def get_conn():
|
||||||
|
"""
|
||||||
|
Retourne une connexion SQLite avec timeout de 30s.
|
||||||
|
Utiliser un context manager pour garantir la fermeture.
|
||||||
|
"""
|
||||||
|
return sqlite3.connect(DB_PATH, timeout=30, check_same_thread=False)
|
||||||
|
|
||||||
|
# --- Vidéos ---
|
||||||
|
def load_videos():
|
||||||
|
"""
|
||||||
|
Retourne toutes les vidéos dans un DataFrame pandas.
|
||||||
|
"""
|
||||||
|
with get_conn() as conn:
|
||||||
|
return pd.read_sql_query("SELECT * FROM videos ORDER BY record_datetime DESC", conn)
|
||||||
|
|
||||||
|
# --- Labels ---
|
||||||
|
def load_labels():
|
||||||
|
"""
|
||||||
|
Retourne tous les labels existants dans une liste.
|
||||||
|
"""
|
||||||
|
with get_conn() as conn:
|
||||||
|
df = pd.read_sql_query("SELECT name FROM labels ORDER BY name", conn)
|
||||||
|
return df["name"].tolist()
|
||||||
|
|
||||||
|
def create_labels(label_names):
|
||||||
|
"""
|
||||||
|
Crée tous les labels de la liste s'ils n'existent pas.
|
||||||
|
"""
|
||||||
|
if not label_names:
|
||||||
|
return
|
||||||
|
|
||||||
|
with get_conn() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.executemany(
|
||||||
|
"INSERT OR IGNORE INTO labels (name) VALUES (?)",
|
||||||
|
[(name,) for name in label_names]
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
def get_label_ids(label_names):
|
||||||
|
"""
|
||||||
|
Retourne un dictionnaire {label_name: label_id}.
|
||||||
|
"""
|
||||||
|
label_ids = {}
|
||||||
|
with get_conn() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
for name in label_names:
|
||||||
|
cursor.execute("SELECT id FROM labels WHERE name=?", (name,))
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if row:
|
||||||
|
label_ids[name] = row[0]
|
||||||
|
return label_ids
|
||||||
|
|
||||||
|
# --- Vidéo / Labels ---
|
||||||
|
def load_video_labels(video_file_name):
|
||||||
|
"""
|
||||||
|
Retourne la liste des labels associés à une vidéo.
|
||||||
|
"""
|
||||||
|
with get_conn() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
query = """
|
||||||
|
SELECT l.name
|
||||||
|
FROM labels l
|
||||||
|
JOIN video_labels vl ON l.id = vl.label_id
|
||||||
|
WHERE vl.video_file_name = ?
|
||||||
|
"""
|
||||||
|
return [row[0] for row in cursor.execute(query, (video_file_name,))]
|
||||||
|
|
||||||
|
def save_video_labels(video_file_name, label_names):
|
||||||
|
"""
|
||||||
|
Associe une vidéo à plusieurs labels.
|
||||||
|
Supprime d'abord les labels précédents pour cette vidéo.
|
||||||
|
"""
|
||||||
|
if label_names is None:
|
||||||
|
label_names = []
|
||||||
|
|
||||||
|
# 1️⃣ Créer tous les labels
|
||||||
|
create_labels(label_names)
|
||||||
|
|
||||||
|
# 2️⃣ Récupérer les IDs
|
||||||
|
label_ids = get_label_ids(label_names)
|
||||||
|
|
||||||
|
# 3️⃣ Associer la vidéo aux labels dans une seule transaction
|
||||||
|
with get_conn() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# 🔹 Supprimer les anciennes associations
|
||||||
|
cursor.execute(
|
||||||
|
"DELETE FROM video_labels WHERE video_file_name = ?",
|
||||||
|
(video_file_name,)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 🔹 Ajouter les nouvelles associations
|
||||||
|
for lid in label_ids.values():
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT INTO video_labels (video_file_name, label_id)
|
||||||
|
VALUES (?, ?)
|
||||||
|
""", (video_file_name, lid))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
for lid in label_ids.values():
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT OR REPLACE INTO video_labels (video_file_name, label_id)
|
||||||
|
VALUES (?, ?)
|
||||||
|
""", (video_file_name, lid))
|
||||||
|
conn.commit()
|
||||||
20
app/views.py
Normal file
20
app/views.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import streamlit as st
|
||||||
|
import os
|
||||||
|
|
||||||
|
def show_video_thumbnail(video):
|
||||||
|
col1, col2, col3 = st.columns([1, 2, 1])
|
||||||
|
video_name = video["mp4_file_name"]
|
||||||
|
thumb = video["thumbnail_file"]
|
||||||
|
mp4 = video["mp4_file"]
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
if os.path.exists(thumb):
|
||||||
|
st.image(thumb, width="content")
|
||||||
|
st.caption(video_name)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
if os.path.exists(mp4):
|
||||||
|
if st.button(f"▶️ Lire 📅 {video.get('record_datetime', '')} — 🕒 {video.get('day_of_week', '')}", key=f"play_{video_name}"):
|
||||||
|
st.video(mp4)
|
||||||
|
|
||||||
|
return col3
|
||||||
Reference in New Issue
Block a user