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.
457 lines
14 KiB
Python
457 lines
14 KiB
Python
import click, re
|
|
|
|
from textual import events
|
|
from textual.app import App, ComposeResult
|
|
from textual.screen import Screen
|
|
from textual.widgets import Header, Footer, Static, Button, Input, DataTable
|
|
from textual.containers import Grid
|
|
|
|
from ffx.model.show import Show
|
|
from ffx.model.pattern import Pattern
|
|
from ffx.model.track import Track
|
|
|
|
from .pattern_controller import PatternController
|
|
from .show_controller import ShowController
|
|
from .track_controller import TrackController
|
|
from .tag_controller import TagController
|
|
|
|
from .track_details_screen import TrackDetailsScreen
|
|
from .track_delete_screen import TrackDeleteScreen
|
|
|
|
from .tag_details_screen import TagDetailsScreen
|
|
from .tag_delete_screen import TagDeleteScreen
|
|
|
|
from ffx.track_type import TrackType
|
|
|
|
from ffx.track_disposition import TrackDisposition
|
|
from ffx.track_descriptor import TrackDescriptor
|
|
|
|
from textual.widgets._data_table import CellDoesNotExist
|
|
|
|
from ffx.file_properties import FileProperties
|
|
|
|
|
|
# Screen[dict[int, str, int]]
|
|
class PatternDetailsScreen(Screen):
|
|
|
|
CSS = """
|
|
|
|
Grid {
|
|
grid-size: 5 13;
|
|
grid-rows: 2 2 2 2 2 8 2 2 8 2 2 2 2;
|
|
grid-columns: 25 25 25 25 25;
|
|
height: 100%;
|
|
width: 100%;
|
|
padding: 1;
|
|
}
|
|
|
|
Input {
|
|
border: none;
|
|
}
|
|
Button {
|
|
border: none;
|
|
}
|
|
|
|
DataTable {
|
|
min-height: 6;
|
|
}
|
|
|
|
#toplabel {
|
|
height: 1;
|
|
}
|
|
|
|
.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, patternId = None, showId = None):
|
|
super().__init__()
|
|
|
|
self.context = self.app.getContext()
|
|
self.Session = self.context['database']['session'] # convenience
|
|
|
|
self.__pc = PatternController(context = self.context)
|
|
self.__sc = ShowController(context = self.context)
|
|
self.__tc = TrackController(context = self.context)
|
|
self.__tac = TagController(context = self.context)
|
|
|
|
self.__pattern : Pattern = self.__pc.getPattern(patternId) if patternId is not None else None
|
|
self.__showDescriptor = self.__sc.getShowDescriptor(showId) if showId is not None else None
|
|
|
|
|
|
#TODO: per controller
|
|
def loadTracks(self, show_id):
|
|
|
|
try:
|
|
|
|
tracks = {}
|
|
tracks['audio'] = {}
|
|
tracks['subtitle'] = {}
|
|
|
|
s = self.Session()
|
|
q = s.query(Pattern).filter(Pattern.show_id == int(show_id))
|
|
|
|
return [{'id': int(p.id), 'pattern': p.pattern} for p in q.all()]
|
|
|
|
except Exception as ex:
|
|
raise click.ClickException(f"loadTracks(): {repr(ex)}")
|
|
finally:
|
|
s.close()
|
|
|
|
|
|
def updateTracks(self):
|
|
|
|
self.tracksTable.clear()
|
|
|
|
if self.__pattern is not None:
|
|
|
|
tracks = self.__tc.findTracks(self.__pattern.getId())
|
|
|
|
typeCounter = {}
|
|
|
|
for tr in tracks:
|
|
|
|
td : TrackDescriptor = tr.getDescriptor(self.context)
|
|
|
|
trackType = td.getType()
|
|
if not trackType in typeCounter.keys():
|
|
typeCounter[trackType] = 0
|
|
|
|
dispoSet = td.getDispositionSet()
|
|
|
|
row = (td.getIndex(),
|
|
trackType.label(),
|
|
typeCounter[trackType],
|
|
td.getAudioLayout().label() if trackType == TrackType.AUDIO else ' ',
|
|
td.getLanguage().label(),
|
|
td.getTitle(),
|
|
'Yes' if TrackDisposition.DEFAULT in dispoSet else 'No',
|
|
'Yes' if TrackDisposition.FORCED in dispoSet else 'No')
|
|
|
|
self.tracksTable.add_row(*map(str, row))
|
|
|
|
typeCounter[trackType] += 1
|
|
|
|
def updateTags(self):
|
|
|
|
self.tagsTable.clear()
|
|
|
|
if self.__pattern is not None:
|
|
|
|
# raise click.ClickException(f"patternid={self.__pattern.getId()}") # 1
|
|
|
|
tags = self.__tac.findAllMediaTags(self.__pattern.getId())
|
|
|
|
#raise click.ClickException(f"tags={tags}") # encoder:blah
|
|
|
|
for tagKey, tagValue in tags.items():
|
|
row = (tagKey, tagValue)
|
|
self.tagsTable.add_row(*map(str, row))
|
|
|
|
|
|
def on_mount(self):
|
|
|
|
if not self.__showDescriptor is None:
|
|
self.query_one("#showlabel", Static).update(f"{self.__showDescriptor.getId()} - {self.__showDescriptor.getName()} ({self.__showDescriptor.getYear()})")
|
|
|
|
if self.__pattern is not None:
|
|
|
|
self.query_one("#pattern_input", Input).value = str(self.__pattern.getPattern())
|
|
|
|
self.updateTags()
|
|
self.updateTracks()
|
|
|
|
def compose(self):
|
|
|
|
|
|
self.tagsTable = DataTable(classes="five")
|
|
|
|
# Define the columns with headers
|
|
self.column_key_tag_key = self.tagsTable.add_column("Key", width=10)
|
|
self.column_key_tag_value = self.tagsTable.add_column("Value", width=100)
|
|
|
|
self.tagsTable.cursor_type = 'row'
|
|
|
|
|
|
self.tracksTable = DataTable(id="tracks_table", classes="five")
|
|
|
|
self.column_key_track_index = self.tracksTable.add_column("Index", width=5)
|
|
self.column_key_track_type = self.tracksTable.add_column("Type", width=10)
|
|
self.column_key_track_sub_index = self.tracksTable.add_column("Subindex", width=5)
|
|
self.column_key_track_audio_layout = self.tracksTable.add_column("Layout", width=10)
|
|
self.column_key_track_language = self.tracksTable.add_column("Language", width=15)
|
|
self.column_key_track_title = self.tracksTable.add_column("Title", width=48)
|
|
self.column_key_track_default = self.tracksTable.add_column("Default", width=8)
|
|
self.column_key_track_forced = self.tracksTable.add_column("Forced", width=8)
|
|
|
|
self.tracksTable.cursor_type = 'row'
|
|
|
|
|
|
yield Header()
|
|
|
|
with Grid():
|
|
|
|
# 1
|
|
yield Static("Edit filename pattern" if self.__pattern is not None else "New filename pattern", id="toplabel")
|
|
yield Input(type="text", id="pattern_input", classes="four")
|
|
|
|
# 2
|
|
yield Static("from show")
|
|
yield Static("", id="showlabel", classes="three")
|
|
yield Button("Substitute pattern", id="pattern_button")
|
|
|
|
# 3
|
|
yield Static(" ", classes="five")
|
|
# 4
|
|
yield Static(" ", classes="five")
|
|
|
|
# 5
|
|
yield Static("Media Tags")
|
|
yield Static(" ")
|
|
|
|
if self.__pattern is not None:
|
|
yield Button("Add", id="button_add_tag")
|
|
yield Button("Edit", id="button_edit_tag")
|
|
yield Button("Delete", id="button_delete_tag")
|
|
else:
|
|
yield Static(" ")
|
|
yield Static(" ")
|
|
yield Static(" ")
|
|
# 6
|
|
yield self.tagsTable
|
|
|
|
# 7
|
|
yield Static(" ", classes="five")
|
|
|
|
# 8
|
|
yield Static("Streams")
|
|
yield Static(" ")
|
|
|
|
if self.__pattern is not None:
|
|
yield Button("Add", id="button_add_track")
|
|
yield Button("Edit", id="button_edit_track")
|
|
yield Button("Delete", id="button_delete_track")
|
|
else:
|
|
yield Static(" ")
|
|
yield Static(" ")
|
|
yield Static(" ")
|
|
# 9
|
|
yield self.tracksTable
|
|
|
|
# 10
|
|
yield Static(" ", classes="five")
|
|
|
|
# 11
|
|
yield Static(" ", classes="five")
|
|
|
|
# 12
|
|
yield Button("Save", id="save_button")
|
|
yield Button("Cancel", id="cancel_button")
|
|
yield Static(" ", classes="three")
|
|
|
|
# 13
|
|
yield Static(" ", classes="five")
|
|
|
|
yield Footer()
|
|
|
|
|
|
def getPatternFromInput(self):
|
|
return str(self.query_one("#pattern_input", Input).value)
|
|
|
|
|
|
|
|
def getSelectedTrackDescriptor(self):
|
|
|
|
if not self.__pattern:
|
|
return None
|
|
|
|
try:
|
|
|
|
# Fetch the currently selected row when 'Enter' is pressed
|
|
#selected_row_index = self.table.cursor_row
|
|
row_key, col_key = self.tracksTable.coordinate_to_cell_key(self.tracksTable.cursor_coordinate)
|
|
|
|
if row_key is not None:
|
|
selected_track_data = self.tracksTable.get_row(row_key)
|
|
|
|
trackIndex = int(selected_track_data[0])
|
|
trackSubIndex = int(selected_track_data[2])
|
|
|
|
return self.__tc.getTrack(self.__pattern.getId(), trackIndex).getDescriptor(self.context, subIndex=trackSubIndex)
|
|
|
|
else:
|
|
return None
|
|
|
|
except CellDoesNotExist:
|
|
return None
|
|
|
|
|
|
|
|
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.tagsTable.coordinate_to_cell_key(self.tagsTable.cursor_coordinate)
|
|
|
|
if row_key is not None:
|
|
selected_tag_data = self.tagsTable.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":
|
|
|
|
patternDescriptor = {}
|
|
patternDescriptor['show_id'] = self.__showDescriptor.getId()
|
|
patternDescriptor['pattern'] = self.getPatternFromInput()
|
|
|
|
if self.__pattern is not None:
|
|
|
|
if self.__pc.updatePattern(self.__pattern.getId(), patternDescriptor):
|
|
self.dismiss(patternDescriptor)
|
|
else:
|
|
#TODO: Meldung
|
|
self.app.pop_screen()
|
|
|
|
else:
|
|
patternId = self.__pc.addPattern(patternDescriptor)
|
|
if patternId:
|
|
self.dismiss(patternDescriptor)
|
|
else:
|
|
#TODO: Meldung
|
|
self.app.pop_screen()
|
|
|
|
|
|
if event.button.id == "cancel_button":
|
|
self.app.pop_screen()
|
|
|
|
|
|
# Save pattern when just created before adding streams
|
|
if self.__pattern is not None:
|
|
|
|
numTracks = len(self.tracksTable.rows)
|
|
|
|
if event.button.id == "button_add_track":
|
|
self.app.push_screen(TrackDetailsScreen(patternId = self.__pattern.getId(), index = numTracks), self.handle_add_track)
|
|
|
|
selectedTrack = self.getSelectedTrackDescriptor()
|
|
if selectedTrack is not None:
|
|
if event.button.id == "button_edit_track":
|
|
self.app.push_screen(TrackDetailsScreen(trackDescriptor = selectedTrack), self.handle_edit_track)
|
|
if event.button.id == "button_delete_track":
|
|
self.app.push_screen(TrackDeleteScreen(trackDescriptor = selectedTrack), self.handle_delete_track)
|
|
|
|
|
|
if event.button.id == "button_add_tag":
|
|
if self.__pattern is not None:
|
|
self.app.push_screen(TagDetailsScreen(), self.handle_update_tag)
|
|
|
|
if event.button.id == "button_edit_tag":
|
|
tagKey, tagValue = self.getSelectedTag()
|
|
self.app.push_screen(TagDetailsScreen(key=tagKey, value=tagValue), self.handle_update_tag)
|
|
|
|
if event.button.id == "button_delete_tag":
|
|
tagKey, tagValue = self.getSelectedTag()
|
|
self.app.push_screen(TagDeleteScreen(key=tagKey, value=tagValue), self.handle_delete_tag)
|
|
|
|
|
|
if event.button.id == "pattern_button":
|
|
|
|
pattern = self.query_one("#pattern_input", Input).value
|
|
|
|
patternMatch = re.search(FileProperties.SE_INDICATOR_PATTERN, pattern)
|
|
|
|
if patternMatch:
|
|
self.query_one("#pattern_input", Input).value = pattern.replace(patternMatch.group(1),
|
|
FileProperties.SE_INDICATOR_PATTERN)
|
|
|
|
def handle_add_track(self, trackDescriptor : TrackDescriptor):
|
|
|
|
dispoSet = trackDescriptor.getDispositionSet()
|
|
trackType = trackDescriptor.getType()
|
|
index = trackDescriptor.getIndex()
|
|
subIndex = trackDescriptor.getSubIndex()
|
|
language = trackDescriptor.getLanguage()
|
|
title = trackDescriptor.getTitle()
|
|
|
|
row = (index,
|
|
trackType.label(),
|
|
subIndex,
|
|
" ",
|
|
language.label(),
|
|
title,
|
|
'Yes' if TrackDisposition.DEFAULT in dispoSet else 'No',
|
|
'Yes' if TrackDisposition.FORCED in dispoSet else 'No')
|
|
|
|
self.tracksTable.add_row(*map(str, row))
|
|
|
|
|
|
def handle_edit_track(self, trackDescriptor : TrackDescriptor):
|
|
|
|
try:
|
|
|
|
row_key, col_key = self.tracksTable.coordinate_to_cell_key(self.tracksTable.cursor_coordinate)
|
|
|
|
self.tracksTable.update_cell(row_key, self.column_key_track_audio_layout, trackDescriptor.getAudioLayout().label())
|
|
self.tracksTable.update_cell(row_key, self.column_key_track_language, trackDescriptor.getLanguage().label())
|
|
self.tracksTable.update_cell(row_key, self.column_key_track_title, trackDescriptor.getTitle())
|
|
self.tracksTable.update_cell(row_key, self.column_key_track_default, 'Yes' if TrackDisposition.DEFAULT in trackDescriptor.getDispositionSet() else 'No')
|
|
self.tracksTable.update_cell(row_key, self.column_key_track_forced, 'Yes' if TrackDisposition.FORCED in trackDescriptor.getDispositionSet() else 'No')
|
|
|
|
except CellDoesNotExist:
|
|
pass
|
|
|
|
|
|
def handle_delete_track(self, trackDescriptor : TrackDescriptor):
|
|
self.updateTracks()
|
|
|
|
|
|
|
|
def handle_update_tag(self, tag):
|
|
|
|
if self.__pattern is None:
|
|
raise click.ClickException(f"PatternDetailsScreen.handle_update_tag: pattern not set")
|
|
|
|
if self.__tac.updateMediaTag(self.__pattern.getId(), tag[0], tag[1]) is not None:
|
|
self.updateTags()
|
|
|
|
def handle_delete_tag(self, tag):
|
|
|
|
if self.__pattern is None:
|
|
raise click.ClickException(f"PatternDetailsScreen.handle_delete_tag: pattern not set")
|
|
|
|
if self.__tac.deleteMediaTagByKey(self.__pattern.getId(), tag[0]):
|
|
self.updateTags()
|