feat: add video support to history tab
- Video processing now saves to history database - History gallery displays both images and videos - Added type filter dropdown (All/Images/Videos) - Updated search and details to work with videos - Video entries show codec, frame count in details 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -11,17 +11,30 @@ from src.storage.history import history_manager, HistoryItem
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_history_gallery() -> list[tuple[str, str]]:
|
def get_history_gallery(type_filter: str = "All") -> list[tuple[str, str]]:
|
||||||
"""Get history items for gallery display."""
|
"""Get history items for gallery display."""
|
||||||
items = history_manager.get_recent(limit=100)
|
items = history_manager.get_recent(limit=100)
|
||||||
|
|
||||||
gallery_items = []
|
gallery_items = []
|
||||||
for item in items:
|
for item in items:
|
||||||
# Use output path for display
|
# Apply type filter
|
||||||
|
if type_filter == "Images" and item.type != "image":
|
||||||
|
continue
|
||||||
|
if type_filter == "Videos" and item.type != "video":
|
||||||
|
continue
|
||||||
|
|
||||||
output_path = Path(item.output_path)
|
output_path = Path(item.output_path)
|
||||||
if output_path.exists() and item.type == "image":
|
if not output_path.exists():
|
||||||
|
continue
|
||||||
|
|
||||||
|
# For images, show the image directly
|
||||||
|
if item.type == "image":
|
||||||
caption = f"{item.output_filename}\n{item.output_width}x{item.output_height}"
|
caption = f"{item.output_filename}\n{item.output_width}x{item.output_height}"
|
||||||
gallery_items.append((str(output_path), caption))
|
gallery_items.append((str(output_path), caption))
|
||||||
|
# For videos, show output path (Gradio Gallery will handle video thumbnails)
|
||||||
|
elif item.type == "video":
|
||||||
|
caption = f"🎬 {item.output_filename}\n{item.output_width}x{item.output_height}"
|
||||||
|
gallery_items.append((str(output_path), caption))
|
||||||
|
|
||||||
return gallery_items
|
return gallery_items
|
||||||
|
|
||||||
@@ -41,79 +54,111 @@ def get_history_stats() -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def search_history(query: str) -> list[tuple[str, str]]:
|
def search_history(query: str, type_filter: str = "All") -> list[tuple[str, str]]:
|
||||||
"""Search history by filename."""
|
"""Search history by filename."""
|
||||||
if not query:
|
if not query:
|
||||||
return get_history_gallery()
|
return get_history_gallery(type_filter)
|
||||||
|
|
||||||
items = history_manager.search(query)
|
items = history_manager.search(query)
|
||||||
|
|
||||||
gallery_items = []
|
gallery_items = []
|
||||||
for item in items:
|
for item in items:
|
||||||
|
# Apply type filter
|
||||||
|
if type_filter == "Images" and item.type != "image":
|
||||||
|
continue
|
||||||
|
if type_filter == "Videos" and item.type != "video":
|
||||||
|
continue
|
||||||
|
|
||||||
output_path = Path(item.output_path)
|
output_path = Path(item.output_path)
|
||||||
if output_path.exists() and item.type == "image":
|
if not output_path.exists():
|
||||||
|
continue
|
||||||
|
|
||||||
|
if item.type == "image":
|
||||||
caption = f"{item.output_filename}\n{item.output_width}x{item.output_height}"
|
caption = f"{item.output_filename}\n{item.output_width}x{item.output_height}"
|
||||||
gallery_items.append((str(output_path), caption))
|
gallery_items.append((str(output_path), caption))
|
||||||
|
elif item.type == "video":
|
||||||
|
caption = f"🎬 {item.output_filename}\n{item.output_width}x{item.output_height}"
|
||||||
|
gallery_items.append((str(output_path), caption))
|
||||||
|
|
||||||
return gallery_items
|
return gallery_items
|
||||||
|
|
||||||
|
|
||||||
def get_item_details(evt: gr.SelectData) -> tuple[Optional[tuple], str]:
|
def get_item_details(evt: gr.SelectData, type_filter: str = "All") -> tuple[Optional[tuple], str]:
|
||||||
"""Get details for selected history item."""
|
"""Get details for selected history item."""
|
||||||
if evt.index is None:
|
if evt.index is None:
|
||||||
return None, "Select an item to view details"
|
return None, "Select an item to view details"
|
||||||
|
|
||||||
items = history_manager.get_recent(limit=100)
|
items = history_manager.get_recent(limit=100)
|
||||||
|
|
||||||
# Filter to only images (same as gallery)
|
# Filter items same as gallery
|
||||||
image_items = [i for i in items if i.type == "image" and Path(i.output_path).exists()]
|
filtered_items = []
|
||||||
|
for item in items:
|
||||||
|
if type_filter == "Images" and item.type != "image":
|
||||||
|
continue
|
||||||
|
if type_filter == "Videos" and item.type != "video":
|
||||||
|
continue
|
||||||
|
if Path(item.output_path).exists():
|
||||||
|
filtered_items.append(item)
|
||||||
|
|
||||||
if evt.index >= len(image_items):
|
if evt.index >= len(filtered_items):
|
||||||
return None, "Item not found"
|
return None, "Item not found"
|
||||||
|
|
||||||
item = image_items[evt.index]
|
item = filtered_items[evt.index]
|
||||||
|
|
||||||
# Load images for slider
|
|
||||||
input_path = Path(item.input_path)
|
|
||||||
output_path = Path(item.output_path)
|
|
||||||
|
|
||||||
|
# Load images for slider (only for images)
|
||||||
slider_images = None
|
slider_images = None
|
||||||
if input_path.exists() and output_path.exists():
|
if item.type == "image":
|
||||||
slider_images = (str(input_path), str(output_path))
|
input_path = Path(item.input_path)
|
||||||
|
output_path = Path(item.output_path)
|
||||||
|
if input_path.exists() and output_path.exists():
|
||||||
|
slider_images = (str(input_path), str(output_path))
|
||||||
|
|
||||||
# Format details
|
# Format details
|
||||||
details = (
|
details = f"**{item.output_filename}**\n\n"
|
||||||
f"**{item.output_filename}**\n\n"
|
details += f"- **Type:** {item.type.title()}\n"
|
||||||
f"- **Type:** {item.type.title()}\n"
|
details += f"- **Model:** {item.model}\n"
|
||||||
f"- **Model:** {item.model}\n"
|
details += f"- **Scale:** {item.scale}x\n"
|
||||||
f"- **Scale:** {item.scale}x\n"
|
details += f"- **Face Enhancement:** {'Yes' if item.face_enhance else 'No'}\n"
|
||||||
f"- **Face Enhancement:** {'Yes' if item.face_enhance else 'No'}\n"
|
|
||||||
f"- **Input:** {item.input_width}x{item.input_height}\n"
|
if item.type == "video" and item.video_codec:
|
||||||
f"- **Output:** {item.output_width}x{item.output_height}\n"
|
details += f"- **Codec:** {item.video_codec}\n"
|
||||||
f"- **Processing Time:** {item.processing_time_seconds:.1f}s\n"
|
if item.frames_count:
|
||||||
f"- **Input Size:** {item.input_size_bytes/1024:.1f} KB\n"
|
details += f"- **Frames:** {item.frames_count}\n"
|
||||||
f"- **Output Size:** {item.output_size_bytes/1024:.1f} KB\n"
|
|
||||||
f"- **Created:** {item.created_at.strftime('%Y-%m-%d %H:%M')}"
|
details += f"- **Input:** {item.input_width}x{item.input_height}\n"
|
||||||
)
|
details += f"- **Output:** {item.output_width}x{item.output_height}\n"
|
||||||
|
details += f"- **Processing Time:** {item.processing_time_seconds:.1f}s\n"
|
||||||
|
details += f"- **Input Size:** {item.input_size_bytes/1024:.1f} KB\n"
|
||||||
|
details += f"- **Output Size:** {item.output_size_bytes/1024/1024:.1f} MB\n" if item.output_size_bytes > 1024*1024 else f"- **Output Size:** {item.output_size_bytes/1024:.1f} KB\n"
|
||||||
|
details += f"- **Created:** {item.created_at.strftime('%Y-%m-%d %H:%M')}"
|
||||||
|
|
||||||
return slider_images, details
|
return slider_images, details
|
||||||
|
|
||||||
|
|
||||||
def delete_selected(evt: gr.SelectData) -> tuple[list, str]:
|
def delete_selected(evt: gr.SelectData, type_filter: str = "All") -> tuple[list, str]:
|
||||||
"""Delete selected history item."""
|
"""Delete selected history item."""
|
||||||
if evt.index is None:
|
if evt.index is None:
|
||||||
return get_history_gallery(), "No item selected"
|
return get_history_gallery(type_filter), "No item selected"
|
||||||
|
|
||||||
items = history_manager.get_recent(limit=100)
|
items = history_manager.get_recent(limit=100)
|
||||||
image_items = [i for i in items if i.type == "image" and Path(i.output_path).exists()]
|
|
||||||
|
|
||||||
if evt.index >= len(image_items):
|
# Filter items same as gallery
|
||||||
return get_history_gallery(), "Item not found"
|
filtered_items = []
|
||||||
|
for item in items:
|
||||||
|
if type_filter == "Images" and item.type != "image":
|
||||||
|
continue
|
||||||
|
if type_filter == "Videos" and item.type != "video":
|
||||||
|
continue
|
||||||
|
if Path(item.output_path).exists():
|
||||||
|
filtered_items.append(item)
|
||||||
|
|
||||||
item = image_items[evt.index]
|
if evt.index >= len(filtered_items):
|
||||||
|
return get_history_gallery(type_filter), "Item not found"
|
||||||
|
|
||||||
|
item = filtered_items[evt.index]
|
||||||
history_manager.delete_item(item.id)
|
history_manager.delete_item(item.id)
|
||||||
|
|
||||||
return get_history_gallery(), f"Deleted: {item.output_filename}"
|
return get_history_gallery(type_filter), f"Deleted: {item.output_filename}"
|
||||||
|
|
||||||
|
|
||||||
def create_history_tab():
|
def create_history_tab():
|
||||||
@@ -186,17 +231,25 @@ def create_history_tab():
|
|||||||
# Event handlers
|
# Event handlers
|
||||||
search_box.change(
|
search_box.change(
|
||||||
fn=search_history,
|
fn=search_history,
|
||||||
inputs=[search_box],
|
inputs=[search_box, filter_dropdown],
|
||||||
|
outputs=[gallery],
|
||||||
|
)
|
||||||
|
|
||||||
|
filter_dropdown.change(
|
||||||
|
fn=get_history_gallery,
|
||||||
|
inputs=[filter_dropdown],
|
||||||
outputs=[gallery],
|
outputs=[gallery],
|
||||||
)
|
)
|
||||||
|
|
||||||
gallery.select(
|
gallery.select(
|
||||||
fn=get_item_details,
|
fn=get_item_details,
|
||||||
|
inputs=[filter_dropdown],
|
||||||
outputs=[comparison_slider, item_details],
|
outputs=[comparison_slider, item_details],
|
||||||
)
|
)
|
||||||
|
|
||||||
delete_btn.click(
|
delete_btn.click(
|
||||||
fn=lambda: (get_history_gallery(), get_history_stats()),
|
fn=lambda f: (get_history_gallery(f), get_history_stats()),
|
||||||
|
inputs=[filter_dropdown],
|
||||||
outputs=[gallery, stats_display],
|
outputs=[gallery, stats_display],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -205,4 +258,5 @@ def create_history_tab():
|
|||||||
"comparison_slider": comparison_slider,
|
"comparison_slider": comparison_slider,
|
||||||
"item_details": item_details,
|
"item_details": item_details,
|
||||||
"stats_display": stats_display,
|
"stats_display": stats_display,
|
||||||
|
"filter_dropdown": filter_dropdown,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from src.video.extractor import VideoExtractor, get_video_info
|
|||||||
from src.video.encoder import encode_video
|
from src.video.encoder import encode_video
|
||||||
from src.video.audio import extract_audio, has_audio_stream
|
from src.video.audio import extract_audio, has_audio_stream
|
||||||
from src.video.checkpoint import checkpoint_manager
|
from src.video.checkpoint import checkpoint_manager
|
||||||
|
from src.storage.history import history_manager
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -209,6 +210,23 @@ def process_video(
|
|||||||
output_size = output_path.stat().st_size / (1024 * 1024) # MB
|
output_size = output_path.stat().st_size / (1024 * 1024) # MB
|
||||||
fps_actual = metadata.total_frames / elapsed
|
fps_actual = metadata.total_frames / elapsed
|
||||||
|
|
||||||
|
# Save to history
|
||||||
|
history_manager.add_item(
|
||||||
|
type="video",
|
||||||
|
input_path=str(video_path),
|
||||||
|
output_path=str(output_path),
|
||||||
|
model=model_name,
|
||||||
|
scale=scale,
|
||||||
|
face_enhance=face_enhance,
|
||||||
|
video_codec=codec,
|
||||||
|
processing_time_seconds=elapsed,
|
||||||
|
input_width=metadata.width,
|
||||||
|
input_height=metadata.height,
|
||||||
|
output_width=metadata.width * scale,
|
||||||
|
output_height=metadata.height * scale,
|
||||||
|
frames_count=metadata.total_frames,
|
||||||
|
)
|
||||||
|
|
||||||
status = (
|
status = (
|
||||||
f"Completed in {elapsed/60:.1f} minutes ({fps_actual:.2f} fps)\n"
|
f"Completed in {elapsed/60:.1f} minutes ({fps_actual:.2f} fps)\n"
|
||||||
f"Output: {output_path.name} ({output_size:.1f} MB)"
|
f"Output: {output_path.name} ({output_size:.1f} MB)"
|
||||||
|
|||||||
Reference in New Issue
Block a user