You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
412 lines
13 KiB
Python
412 lines
13 KiB
Python
import click, time
|
|
|
|
from textual import events
|
|
from textual.app import App, ComposeResult
|
|
from textual.screen import Screen
|
|
from textual.widgets import Header, Footer, Static, Button, SelectionList, Select, DataTable, Input
|
|
from textual.containers import Grid
|
|
|
|
from ffx.model.pattern import Pattern
|
|
from ffx.model.track import Track
|
|
|
|
from .track_controller import TrackController
|
|
from .pattern_controller import PatternController
|
|
from .tag_controller import TagController
|
|
|
|
from .track_type import TrackType
|
|
|
|
from .iso_language import IsoLanguage
|
|
from .track_disposition import TrackDisposition
|
|
from .audio_layout import AudioLayout
|
|
|
|
from .track_descriptor import TrackDescriptor
|
|
|
|
from .tag_details_screen import TagDetailsScreen
|
|
from .tag_delete_screen import TagDeleteScreen
|
|
|
|
from textual.widgets._data_table import CellDoesNotExist
|
|
|
|
|
|
# Screen[dict[int, str, int]]
|
|
class TrackDetailsScreen(Screen):
|
|
|
|
CSS = """
|
|
|
|
Grid {
|
|
grid-size: 5 24;
|
|
grid-rows: 2 2 2 2 2 3 3 2 2 3 2 2 2 2 2 6 2 2 6 2 2 2;
|
|
grid-columns: 25 25 25 25 125;
|
|
height: 100%;
|
|
width: 100%;
|
|
padding: 1;
|
|
}
|
|
|
|
Input {
|
|
border: none;
|
|
}
|
|
Button {
|
|
border: none;
|
|
}
|
|
SelectionList {
|
|
border: none;
|
|
min-height: 6;
|
|
}
|
|
Select {
|
|
border: none;
|
|
}
|
|
DataTable {
|
|
min-height: 6;
|
|
}
|
|
|
|
#toplabel {
|
|
height: 1;
|
|
}
|
|
|
|
.two {
|
|
column-span: 2;
|
|
}
|
|
.three {
|
|
column-span: 3;
|
|
}
|
|
|
|
.four {
|
|
column-span: 4;
|
|
}
|
|
.five {
|
|
column-span: 5;
|
|
}
|
|
|
|
.box {
|
|
height: 100%;
|
|
border: solid green;
|
|
}
|
|
|
|
.yellow {
|
|
tint: yellow 40%;
|
|
}
|
|
"""
|
|
|
|
def __init__(self, trackDescriptor : TrackDescriptor = None, patternId = None, trackType : TrackType = None, index = None, subIndex = None):
|
|
super().__init__()
|
|
|
|
self.context = self.app.getContext()
|
|
self.Session = self.context['database']['session'] # convenience
|
|
|
|
self.__tc = TrackController(context = self.context)
|
|
self.__pc = PatternController(context = self.context)
|
|
self.__tac = TagController(context = self.context)
|
|
|
|
self.__isNew = trackDescriptor is None
|
|
if self.__isNew:
|
|
self.__trackType = trackType
|
|
self.__audioLayout = AudioLayout.LAYOUT_UNDEFINED
|
|
self.__index = index
|
|
self.__subIndex = subIndex
|
|
self.__trackDescriptor : TrackDescriptor = None
|
|
self.__pattern : Pattern = self.__pc.getPattern(patternId) if patternId is not None else {}
|
|
else:
|
|
self.__trackType = trackDescriptor.getType()
|
|
self.__audioLayout = trackDescriptor.getAudioLayout()
|
|
self.__index = trackDescriptor.getIndex()
|
|
self.__subIndex = trackDescriptor.getSubIndex()
|
|
self.__trackDescriptor : TrackDescriptor = trackDescriptor
|
|
self.__pattern : Pattern = self.__pc.getPattern(self.__trackDescriptor.getPatternId())
|
|
|
|
|
|
|
|
def updateTags(self):
|
|
|
|
self.trackTagsTable.clear()
|
|
|
|
trackId = self.__trackDescriptor.getId()
|
|
|
|
if trackId != -1:
|
|
|
|
trackTags = self.__tac.findAllTrackTags(trackId)
|
|
|
|
for k,v in trackTags.items():
|
|
|
|
if k != 'language' and k != 'title':
|
|
row = (k,v)
|
|
self.trackTagsTable.add_row(*map(str, row))
|
|
|
|
|
|
def on_mount(self):
|
|
|
|
self.query_one("#index_label", Static).update(str(self.__index) if self.__index is not None else '-')
|
|
self.query_one("#subindex_label", Static).update(str(self.__subIndex)if self.__subIndex is not None else '-')
|
|
|
|
if self.__pattern is not None:
|
|
self.query_one("#pattern_label", Static).update(self.__pattern.getPattern())
|
|
|
|
if self.__trackType is not None:
|
|
self.query_one("#type_select", Select).value = self.__trackType.label()
|
|
if self.__trackType == TrackType.AUDIO:
|
|
self.query_one("#audio_layout_select", Select).value = self.__audioLayout.label()
|
|
|
|
for d in TrackDisposition:
|
|
|
|
dispositionIsSet = (self.__trackDescriptor is not None
|
|
and d in self.__trackDescriptor.getDispositionSet())
|
|
|
|
dispositionOption = (d.label(), d.index(), dispositionIsSet)
|
|
self.query_one("#dispositions_selection_list", SelectionList).add_option(dispositionOption)
|
|
|
|
if self.__trackDescriptor is not None:
|
|
|
|
self.query_one("#language_select", Select).value = self.__trackDescriptor.getLanguage().label()
|
|
self.query_one("#title_input", Input).value = self.__trackDescriptor.getTitle()
|
|
self.updateTags()
|
|
|
|
|
|
def compose(self):
|
|
|
|
self.trackTagsTable = DataTable(classes="five")
|
|
|
|
# Define the columns with headers
|
|
self.column_key_track_tag_key = self.trackTagsTable.add_column("Key", width=10)
|
|
self.column_key_track_tag_value = self.trackTagsTable.add_column("Value", width=125)
|
|
|
|
self.trackTagsTable.cursor_type = 'row'
|
|
|
|
|
|
languages = [l.label() for l in IsoLanguage]
|
|
|
|
yield Header()
|
|
|
|
with Grid():
|
|
|
|
# 1
|
|
yield Static(f"New stream" if self.__isNew else f"Edit stream", id="toplabel", classes="five")
|
|
|
|
# 2
|
|
yield Static("for pattern")
|
|
yield Static("", id="pattern_label", classes="four")
|
|
|
|
# 3
|
|
yield Static(" ", classes="five")
|
|
|
|
# 4
|
|
yield Static("Index / Subindex")
|
|
yield Static("", id="index_label", classes="two")
|
|
yield Static("", id="subindex_label", classes="two")
|
|
|
|
# 5
|
|
yield Static(" ", classes="five")
|
|
|
|
# 6
|
|
yield Static("Type")
|
|
yield Select.from_values([t.label() for t in TrackType], classes="four", id="type_select")
|
|
|
|
# 7
|
|
yield Static("Audio Layout")
|
|
yield Select.from_values([t.label() for t in AudioLayout], classes="four", id="audio_layout_select")
|
|
|
|
# 8
|
|
yield Static(" ", classes="five")
|
|
|
|
# 9
|
|
yield Static(" ", classes="five")
|
|
|
|
# 10
|
|
yield Static("Language")
|
|
yield Select.from_values(languages, classes="four", id="language_select")
|
|
# 11
|
|
yield Static(" ", classes="five")
|
|
|
|
# 12
|
|
yield Static("Title")
|
|
yield Input(id="title_input", classes="four")
|
|
|
|
# 13
|
|
yield Static(" ", classes="five")
|
|
|
|
# 14
|
|
yield Static(" ", classes="five")
|
|
|
|
# 15
|
|
yield Static("Stream tags")
|
|
yield Static(" ")
|
|
yield Button("Add", id="button_add_stream_tag")
|
|
yield Button("Edit", id="button_edit_stream_tag")
|
|
yield Button("Delete", id="button_delete_stream_tag")
|
|
# 16
|
|
yield self.trackTagsTable
|
|
|
|
# 17
|
|
yield Static(" ", classes="five")
|
|
|
|
# 18
|
|
yield Static("Stream dispositions", classes="five")
|
|
|
|
# 19
|
|
yield SelectionList[int](
|
|
classes="five",
|
|
id = "dispositions_selection_list"
|
|
)
|
|
|
|
# 20
|
|
yield Static(" ", classes="five")
|
|
# 21
|
|
yield Static(" ", classes="five")
|
|
|
|
# 22
|
|
yield Button("Save", id="save_button")
|
|
yield Button("Cancel", id="cancel_button")
|
|
|
|
# 23
|
|
yield Static(" ", classes="five")
|
|
|
|
# 24
|
|
yield Static(" ", classes="five", id="messagestatic")
|
|
|
|
|
|
yield Footer(id="footer")
|
|
|
|
|
|
def getTrackDescriptorFromInput(self):
|
|
|
|
kwargs = {}
|
|
|
|
kwargs[TrackDescriptor.PATTERN_ID_KEY] = int(self.__pattern.getId())
|
|
|
|
kwargs[TrackDescriptor.INDEX_KEY] = self.__index
|
|
kwargs[TrackDescriptor.SUB_INDEX_KEY] = self.__subIndex #!
|
|
|
|
kwargs[TrackDescriptor.TRACK_TYPE_KEY] = TrackType.fromLabel(self.query_one("#type_select", Select).value)
|
|
kwargs[TrackDescriptor.AUDIO_LAYOUT_KEY] = AudioLayout.fromLabel(self.query_one("#audio_layout_select", Select).value)
|
|
|
|
trackTags = {}
|
|
language = self.query_one("#language_select", Select).value
|
|
if language:
|
|
trackTags['language'] = IsoLanguage.find(language).threeLetter()
|
|
title = self.query_one("#title_input", Input).value
|
|
if title:
|
|
trackTags['title'] = title
|
|
|
|
tableTags = {row[0]:row[1] for r in self.trackTagsTable.rows if (row := self.trackTagsTable.get_row(r)) and row[0] != 'language' and row[0] != 'title'}
|
|
|
|
kwargs[TrackDescriptor.TAGS_KEY] = trackTags | tableTags
|
|
|
|
dispositionFlags = sum([2**f for f in self.query_one("#dispositions_selection_list", SelectionList).selected])
|
|
kwargs[TrackDescriptor.DISPOSITION_SET_KEY] = TrackDisposition.toSet(dispositionFlags)
|
|
|
|
return TrackDescriptor(**kwargs)
|
|
|
|
|
|
|
|
def getSelectedTag(self):
|
|
|
|
try:
|
|
|
|
# Fetch the currently selected row when 'Enter' is pressed
|
|
#selected_row_index = self.table.cursor_row
|
|
row_key, col_key = self.trackTagsTable.coordinate_to_cell_key(self.trackTagsTable.cursor_coordinate)
|
|
|
|
if row_key is not None:
|
|
selected_tag_data = self.trackTagsTable.get_row(row_key)
|
|
|
|
tagKey = str(selected_tag_data[0])
|
|
tagValue = str(selected_tag_data[1])
|
|
|
|
return tagKey, tagValue
|
|
|
|
else:
|
|
return None
|
|
|
|
except CellDoesNotExist:
|
|
return None
|
|
|
|
|
|
|
|
# Event handler for button press
|
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
|
|
# Check if the button pressed is the one we are interested in
|
|
if event.button.id == "save_button":
|
|
|
|
# Check for multiple default/forced disposition flags
|
|
|
|
if self.__trackType == TrackType.VIDEO:
|
|
trackList = self.__tc.findVideoTracks(self.__pattern.getId())
|
|
if self.__trackType == TrackType.AUDIO:
|
|
trackList = self.__tc.findAudioTracks(self.__pattern.getId())
|
|
elif self.__trackType == TrackType.SUBTITLE:
|
|
trackList = self.__tc.findSubtitleTracks(self.__pattern.getId())
|
|
else:
|
|
trackList = []
|
|
|
|
siblingTrackList = [t for t in trackList if t.getType() == self.__trackType and t.getIndex() != self.__index]
|
|
|
|
numDefaultTracks = len([t for t in siblingTrackList if TrackDisposition.DEFAULT in t.getDispositionSet()])
|
|
numForcedTracks = len([t for t in siblingTrackList if TrackDisposition.FORCED in t.getDispositionSet()])
|
|
|
|
self.__subIndex = len(trackList)
|
|
trackDescriptor = self.getTrackDescriptorFromInput()
|
|
|
|
if ((TrackDisposition.DEFAULT in trackDescriptor.getDispositionSet() and numDefaultTracks)
|
|
or (TrackDisposition.FORCED in trackDescriptor.getDispositionSet() and numForcedTracks)):
|
|
|
|
self.query_one("#messagestatic", Static).update("Cannot add another stream with disposition flag 'debug' or 'forced' set")
|
|
|
|
else:
|
|
|
|
self.query_one("#messagestatic", Static).update(" ")
|
|
|
|
if self.__isNew:
|
|
|
|
# Track per Screen hinzufügen
|
|
self.__tc.addTrack(trackDescriptor)
|
|
self.dismiss(trackDescriptor)
|
|
|
|
else:
|
|
|
|
track = self.__tc.getTrack(self.__pattern.getId(), self.__index)
|
|
|
|
# Track per details screen updaten
|
|
if self.__tc.updateTrack(track.getId(), trackDescriptor):
|
|
self.dismiss(trackDescriptor)
|
|
|
|
else:
|
|
self.app.pop_screen()
|
|
|
|
if event.button.id == "cancel_button":
|
|
self.app.pop_screen()
|
|
|
|
|
|
if event.button.id == "button_add_stream_tag":
|
|
if not self.__isNew:
|
|
self.app.push_screen(TagDetailsScreen(), self.handle_update_tag)
|
|
|
|
if event.button.id == "button_edit_stream_tag":
|
|
tagKey, tagValue = self.getSelectedTag()
|
|
self.app.push_screen(TagDetailsScreen(key=tagKey, value=tagValue), self.handle_update_tag)
|
|
|
|
if event.button.id == "button_delete_stream_tag":
|
|
tagKey, tagValue = self.getSelectedTag()
|
|
self.app.push_screen(TagDeleteScreen(key=tagKey, value=tagValue), self.handle_delete_tag)
|
|
|
|
|
|
def handle_update_tag(self, tag):
|
|
|
|
trackId = self.__trackDescriptor.getId()
|
|
|
|
if trackId == -1:
|
|
raise click.ClickException(f"TrackDetailsScreen.handle_update_tag: trackId not set (-1) trackDescriptor={self.__trackDescriptor}")
|
|
|
|
if self.__tac.updateTrackTag(trackId, tag[0], tag[1]) is not None:
|
|
self.updateTags()
|
|
|
|
def handle_delete_tag(self, trackTag):
|
|
|
|
trackId = self.__trackDescriptor.getId()
|
|
|
|
if trackId == -1:
|
|
raise click.ClickException(f"TrackDetailsScreen.handle_delete_tag: trackId not set (-1) trackDescriptor={self.__trackDescriptor}")
|
|
|
|
tag = self.__tac.findTrackTag(trackId, trackTag[0])
|
|
|
|
if tag is not None:
|
|
if self.__tac.deleteTrackTag(tag.id):
|
|
self.updateTags()
|