Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions src/tagstudio/qt/mixed/file_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import typing
from dataclasses import dataclass
from datetime import datetime as dt
from datetime import timedelta
from pathlib import Path

import structlog
Expand Down Expand Up @@ -39,6 +38,17 @@ class FileAttributeData:
duration: int | None = None


def _format_duration(duration: int | float) -> str:
"""Format a duration in seconds as M:SS or H:MM:SS."""
try:
seconds = int(float(duration))
hours, seconds = divmod(seconds, 3600)
minutes, seconds = divmod(seconds, 60)
return f"{hours}:{minutes:02}:{seconds:02}" if hours else f"{minutes}:{seconds:02}"
except (OverflowError, ValueError):
return "-:--"


class FileAttributes(QWidget):
"""The Preview Panel Widget."""

Expand Down Expand Up @@ -224,15 +234,7 @@ def add_newline(stats_label_text: str) -> str:

if stats.duration is not None:
stats_label_text = add_newline(stats_label_text)
try:
dur_str = str(timedelta(seconds=float(stats.duration)))[:-7]
if dur_str.startswith("0:"):
dur_str = dur_str[2:]
if dur_str.startswith("0"):
dur_str = dur_str[1:]
except OverflowError:
dur_str = "-:--"
stats_label_text += f"{dur_str}"
stats_label_text += _format_duration(stats.duration)

if font_family:
stats_label_text = add_newline(stats_label_text)
Expand Down
30 changes: 30 additions & 0 deletions src/tagstudio/qt/views/preview_panel_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ def __init__(self, library: Library, driver: "QtDriver") -> None:
self._containers = FieldContainers(
self.lib, driver
) # TODO: this should be name mangled, but is still needed on the controller side atm
self.__current_stats: FileAttributeData | None = None
self.__current_stats_filepath: Path | None = None

preview_section = QWidget()
preview_layout = QVBoxLayout(preview_section)
Expand Down Expand Up @@ -132,13 +134,33 @@ def __init__(self, library: Library, driver: "QtDriver") -> None:
def __connect_callbacks(self) -> None:
self.__add_field_button.clicked.connect(self._add_field_button_callback)
self.__add_tag_button.clicked.connect(self._add_tag_button_callback)
self._thumb.stats_updated.connect(self.__thumb_stats_updated_callback)

def _add_field_button_callback(self) -> None:
raise NotImplementedError()

def _add_tag_button_callback(self) -> None:
raise NotImplementedError()

def __thumb_stats_updated_callback(self, filepath: Path, stats: FileAttributeData) -> None:
if len(self._selected) != 1:
return

if filepath != self.__current_stats_filepath:
return

if self.__current_stats is None:
self.__current_stats = FileAttributeData()

if stats.width is not None:
self.__current_stats.width = stats.width
if stats.height is not None:
self.__current_stats.height = stats.height
if stats.duration is not None:
self.__current_stats.duration = stats.duration

self._file_attrs.update_stats(filepath, self.__current_stats)

def _set_selection_callback(self) -> None:
raise NotImplementedError()

Expand All @@ -155,6 +177,8 @@ def set_selection(self, selected: list[int], update_preview: bool = True) -> Non
# No Items Selected
if len(selected) == 0:
self._thumb.hide_preview()
self.__current_stats = None
self.__current_stats_filepath = None
self._file_attrs.update_stats()
self._file_attrs.update_date_label()
self._containers.hide_containers()
Expand All @@ -167,9 +191,13 @@ def set_selection(self, selected: list[int], update_preview: bool = True) -> Non
entry: Entry = unwrap(self.lib.get_entry(entry_id))

filepath: Path = unwrap(self.lib.library_dir) / entry.path
if filepath != self.__current_stats_filepath:
self.__current_stats = None
self.__current_stats_filepath = filepath

if update_preview:
stats: FileAttributeData = self._thumb.display_file(filepath)
self.__current_stats = stats
self._file_attrs.update_stats(filepath, stats)
self._file_attrs.update_date_label(filepath)
self._containers.update_from_entry(entry_id)
Expand All @@ -182,6 +210,8 @@ def set_selection(self, selected: list[int], update_preview: bool = True) -> Non
elif len(selected) > 1:
# items: list[Entry] = [self.lib.get_entry_full(x) for x in self.driver.selected]
self._thumb.hide_preview() # TODO: Render mixed selection
self.__current_stats = None
self.__current_stats_filepath = None
self._file_attrs.update_multi_selection(len(selected))
self._file_attrs.update_date_label()
self._containers.hide_containers() # TODO: Allow for mixed editing
Expand Down
42 changes: 29 additions & 13 deletions src/tagstudio/qt/views/preview_thumb_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ class PreviewThumbView(QWidget):
"""The Preview Panel Widget."""

check_ffmpeg = Signal(bool)
stats_updated = Signal(object, object)

__img_button_size: tuple[int, int]
__image_ratio: float

__filepath: Path | None
__thumb_filepath: Path | None
__media_filepath: Path | None
__rendered_res: tuple[int, int]

def __init__(self, library: Library, driver: "QtDriver") -> None:
Expand Down Expand Up @@ -92,6 +94,11 @@ def __init__(self, library: Library, driver: "QtDriver") -> None:
self.__media_player.addAction(open_file_action)
self.__media_player.addAction(open_explorer_action)
self.__media_player.addAction(delete_action)
self.__media_filepath = None
# QMediaPlayer loads duration asynchronously after setSource().
self.__media_player.player.durationChanged.connect(
self.__media_player_duration_changed_callback
)

# Need to watch for this to resize the player appropriately.
self.__media_player.player.hasVideoChanged.connect(
Expand Down Expand Up @@ -128,6 +135,15 @@ def _button_wrapper_callback(self):
def __media_player_video_changed_callback(self, video: bool) -> None:
self.__update_image_size((self.size().width(), self.size().height()))

def __media_player_duration_changed_callback(self, duration_ms: int) -> None:
if self.__media_filepath is None or duration_ms <= 0:
return

self.stats_updated.emit(
self.__media_filepath,
FileAttributeData(duration=duration_ms // 1000),
)

def __thumb_renderer_updated_callback(
self, _timestamp: float, img: QPixmap, _size: QSize, _path: Path
) -> None:
Expand Down Expand Up @@ -207,7 +223,7 @@ def __switch_preview(self, preview: MediaType | None) -> None:
self.__preview_gif.hide()

def __render_thumb(self, filepath: Path) -> None:
self.__filepath = filepath
self.__thumb_filepath = filepath
self.__rendered_res = (
math.ceil(self.__img_button_size[0] * THUMB_SIZE_FACTOR),
math.ceil(self.__img_button_size[1] * THUMB_SIZE_FACTOR),
Expand All @@ -221,17 +237,15 @@ def __render_thumb(self, filepath: Path) -> None:
update_on_ratio_change=True,
)

def __update_media_player(self, filepath: Path) -> int:
"""Display either audio or video.

Returns the duration of the audio / video.
"""
def __update_media_player(self, filepath: Path) -> None:
"""Display either audio or video."""
self.__media_filepath = filepath
self.__media_player.play(filepath)
return self.__media_player.player.duration() * 1000

def _display_video(self, filepath: Path, size: QSize | None) -> FileAttributeData:
self.__switch_preview(MediaType.VIDEO)
stats = FileAttributeData(duration=self.__update_media_player(filepath))
self.__update_media_player(filepath)
stats = FileAttributeData()

if size is not None:
stats.width = size.width()
Expand All @@ -250,7 +264,8 @@ def _display_video(self, filepath: Path, size: QSize | None) -> FileAttributeDat
def _display_audio(self, filepath: Path) -> FileAttributeData:
self.__switch_preview(MediaType.AUDIO)
self.__render_thumb(filepath)
return FileAttributeData(duration=self.__update_media_player(filepath))
self.__update_media_player(filepath)
return FileAttributeData()

def _display_gif(self, gif_data: bytes, size: tuple[int, int]) -> FileAttributeData | None:
"""Update the animated image preview from a filepath."""
Expand Down Expand Up @@ -296,14 +311,15 @@ def _display_image(self, filepath: Path):
def hide_preview(self) -> None:
"""Completely hide the file preview."""
self.__switch_preview(None)
self.__filepath = None
self.__thumb_filepath = None
self.__media_filepath = None
Comment thread
ludvig-sandh marked this conversation as resolved.
Outdated

@override
def resizeEvent(self, event: QResizeEvent) -> None:
self.__update_image_size((self.size().width(), self.size().height()))

if self.__filepath is not None and self.__rendered_res < self.__img_button_size:
self.__render_thumb(self.__filepath)
if self.__thumb_filepath is not None and self.__rendered_res < self.__img_button_size:
self.__render_thumb(self.__thumb_filepath)

return super().resizeEvent(event)

Expand Down
Loading