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 21; grid-rows: 2 2 2 2 2 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.__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.__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() 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(" ", 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) 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) kwargs[TrackDescriptor.AUDIO_LAYOUT_KEY] = AudioLayout.LAYOUT_UNDEFINED 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.getSubIndex() != self.__subIndex] 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: self.__tc.addTrack(trackDescriptor) self.dismiss(trackDescriptor) else: track = self.__tc.getTrack(self.__pattern.getId(), self.__index) 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()