click-textual
Maveno 1 year ago
parent 3008d66dfe
commit 1ae52399b9

@ -0,0 +1,113 @@
from enum import Enum
import difflib
class IsoLanguage(Enum):
AFRIKAANS = {"name": "Afrikaans", "iso639_1": "af", "iso639_2": "afr"}
ALBANIAN = {"name": "Albanian", "iso639_1": "sq", "iso639_2": "alb"}
ARABIC = {"name": "Arabic", "iso639_1": "ar", "iso639_2": "ara"}
ARMENIAN = {"name": "Armenian", "iso639_1": "hy", "iso639_2": "arm"}
AZERBAIJANI = {"name": "Azerbaijani", "iso639_1": "az", "iso639_2": "aze"}
BASQUE = {"name": "Basque", "iso639_1": "eu", "iso639_2": "baq"}
BELARUSIAN = {"name": "Belarusian", "iso639_1": "be", "iso639_2": "bel"}
BULGARIAN = {"name": "Bulgarian", "iso639_1": "bg", "iso639_2": "bul"}
CATALAN = {"name": "Catalan", "iso639_1": "ca", "iso639_2": "cat"}
CHINESE = {"name": "Chinese", "iso639_1": "zh", "iso639_2": "chi"}
CROATIAN = {"name": "Croatian", "iso639_1": "hr", "iso639_2": "hrv"}
CZECH = {"name": "Czech", "iso639_1": "cs", "iso639_2": "cze"}
DANISH = {"name": "Danish", "iso639_1": "da", "iso639_2": "dan"}
DUTCH = {"name": "Dutch", "iso639_1": "nl", "iso639_2": "dut"}
ENGLISH = {"name": "English", "iso639_1": "en", "iso639_2": "eng"}
ESTONIAN = {"name": "Estonian", "iso639_1": "et", "iso639_2": "est"}
FINNISH = {"name": "Finnish", "iso639_1": "fi", "iso639_2": "fin"}
FRENCH = {"name": "French", "iso639_1": "fr", "iso639_2": "fre"}
GEORGIAN = {"name": "Georgian", "iso639_1": "ka", "iso639_2": "geo"}
GERMAN = {"name": "German", "iso639_1": "de", "iso639_2": "ger"}
GREEK = {"name": "Greek", "iso639_1": "el", "iso639_2": "gre"}
HEBREW = {"name": "Hebrew", "iso639_1": "he", "iso639_2": "heb"}
HINDI = {"name": "Hindi", "iso639_1": "hi", "iso639_2": "hin"}
HUNGARIAN = {"name": "Hungarian", "iso639_1": "hu", "iso639_2": "hun"}
ICELANDIC = {"name": "Icelandic", "iso639_1": "is", "iso639_2": "ice"}
INDONESIAN = {"name": "Indonesian", "iso639_1": "id", "iso639_2": "ind"}
IRISH = {"name": "Irish", "iso639_1": "ga", "iso639_2": "gle"}
ITALIAN = {"name": "Italian", "iso639_1": "it", "iso639_2": "ita"}
JAPANESE = {"name": "Japanese", "iso639_1": "ja", "iso639_2": "jpn"}
KAZAKH = {"name": "Kazakh", "iso639_1": "kk", "iso639_2": "kaz"}
KOREAN = {"name": "Korean", "iso639_1": "ko", "iso639_2": "kor"}
LATIN = {"name": "Latin", "iso639_1": "la", "iso639_2": "lat"}
LATVIAN = {"name": "Latvian", "iso639_1": "lv", "iso639_2": "lav"}
LITHUANIAN = {"name": "Lithuanian", "iso639_1": "lt", "iso639_2": "lit"}
MACEDONIAN = {"name": "Macedonian", "iso639_1": "mk", "iso639_2": "mac"}
MALAY = {"name": "Malay", "iso639_1": "ms", "iso639_2": "may"}
MALTESE = {"name": "Maltese", "iso639_1": "mt", "iso639_2": "mlt"}
NORWEGIAN = {"name": "Norwegian", "iso639_1": "no", "iso639_2": "nor"}
PERSIAN = {"name": "Persian", "iso639_1": "fa", "iso639_2": "per"}
POLISH = {"name": "Polish", "iso639_1": "pl", "iso639_2": "pol"}
PORTUGUESE = {"name": "Portuguese", "iso639_1": "pt", "iso639_2": "por"}
ROMANIAN = {"name": "Romanian", "iso639_1": "ro", "iso639_2": "rum"}
RUSSIAN = {"name": "Russian", "iso639_1": "ru", "iso639_2": "rus"}
NORTHERN_SAMI = {"name": "Northern Sami", "iso639_1": "se", "iso639_2": "sme"}
SAMOAN = {"name": "Samoan", "iso639_1": "sm", "iso639_2": "smo"}
SANGO = {"name": "Sango", "iso639_1": "sg", "iso639_2": "sag"}
SANSKRIT = {"name": "Sanskrit", "iso639_1": "sa", "iso639_2": "san"}
SARDINIAN = {"name": "Sardinian", "iso639_1": "sc", "iso639_2": "srd"}
SERBIAN = {"name": "Serbian", "iso639_1": "sr", "iso639_2": "srp"}
SHONA = {"name": "Shona", "iso639_1": "sn", "iso639_2": "sna"}
SINDHI = {"name": "Sindhi", "iso639_1": "sd", "iso639_2": "snd"}
SINHALA = {"name": "Sinhala", "iso639_1": "si", "iso639_2": "sin"}
SLOVAK = {"name": "Slovak", "iso639_1": "sk", "iso639_2": "slk"}
SLOVENIAN = {"name": "Slovenian", "iso639_1": "sl", "iso639_2": "slv"}
SOMALI = {"name": "Somali", "iso639_1": "so", "iso639_2": "som"}
SOUTHERN_SOTHO = {"name": "Southern Sotho", "iso639_1": "st", "iso639_2": "sot"}
SPANISH = {"name": "Spanish", "iso639_1": "es", "iso639_2": "spa"}
SUNDANESE = {"name": "Sundanese", "iso639_1": "su", "iso639_2": "sun"}
SWAHILI = {"name": "Swahili", "iso639_1": "sw", "iso639_2": "swa"}
SWATI = {"name": "Swati", "iso639_1": "ss", "iso639_2": "ssw"}
SWEDISH = {"name": "Swedish", "iso639_1": "sv", "iso639_2": "swe"}
TAGALOG = {"name": "Tagalog", "iso639_1": "tl", "iso639_2": "tgl"}
TAMIL = {"name": "Tamil", "iso639_1": "ta", "iso639_2": "tam"}
THAI = {"name": "Thai", "iso639_1": "th", "iso639_2": "tha"}
TURKISH = {"name": "Turkish", "iso639_1": "tr", "iso639_2": "tur"}
UKRAINIAN = {"name": "Ukrainian", "iso639_1": "uk", "iso639_2": "ukr"}
URDU = {"name": "Urdu", "iso639_1": "ur", "iso639_2": "urd"}
VIETNAMESE = {"name": "Vietnamese", "iso639_1": "vi", "iso639_2": "vie"}
WELSH = {"name": "Welsh", "iso639_1": "cy", "iso639_2": "wel"}
@staticmethod
def find(label : str):
closestMatches = difflib.get_close_matches(label, [l.value["name"] for l in IsoLanguage], n=1)
if closestMatches:
foundLangs = [l for l in IsoLanguage if l.value['name'] == closestMatches[0]]
return foundLangs[0] if foundLangs else None
else:
return None
@staticmethod
def findThreeLetter(theeLetter : str):
foundLangs = [l for l in IsoLanguage if l.value['iso639_2'] == str(theeLetter)]
return foundLangs[0] if foundLangs else None
# def get(lang : str):
#
# selectedLangs = [l for l in IsoLanguage if l.value['iso639_2'] == lang]
#
# if selectedLangs:
# return selectedLangs[0]
# else:
# return None
def label(self):
return str(self.value['name'])
def twoLetter(self):
return str(self.value['iso639_1'])
def threeLetter(self):
return str(self.value['iso639_2'])

@ -0,0 +1,30 @@
# from typing import List
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, Enum
from sqlalchemy.orm import relationship, declarative_base, sessionmaker
from .show import Base
from ffx.track_type import TrackType
class Tag(Base):
"""
relationship(argument, opt1, opt2, ...)
argument is string of class or Mapped class of the target entity
backref creates a bi-directional corresponding relationship (back_populates preferred)
back_populates points to the corresponding relationship (the actual class attribute identifier)
See: https://docs.sqlalchemy.org/en/(14|20)/orm/basic_relationships.html
"""
__tablename__ = 'tags'
# v1.x
id = Column(Integer, primary_key=True)
key = Column(String)
value = Column(String)
# v1.x
track_id = Column(Integer, ForeignKey('tracks.id', ondelete="CASCADE"))
track = relationship('Track', back_populates='tags')

@ -1,11 +1,16 @@
# from typing import List # from typing import List
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, Enum from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, declarative_base, sessionmaker from sqlalchemy.orm import relationship, declarative_base, sessionmaker
from .show import Base from .show import Base
from ffx.track_type import TrackType from ffx.track_type import TrackType
from ffx.iso_language import IsoLanguage
from ffx.model.tag import Tag
class Track(Base): class Track(Base):
""" """
@ -20,11 +25,21 @@ class Track(Base):
__tablename__ = 'tracks' __tablename__ = 'tracks'
# v1.x # v1.x
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True, autoincrement = True)
track_type = Column(Enum[TrackType]) # P=pattern_id+sub_index+track_type
track_type = Column(Integer) # TrackType
sub_index = Column(Integer)
# v1.x # v1.x
pattern_id = Column(Integer, ForeignKey('patterns.id', ondelete="CASCADE")) pattern_id = Column(Integer, ForeignKey('patterns.id', ondelete="CASCADE"))
pattern = relationship('Pattern', back_populates='tracks') pattern = relationship('Pattern', back_populates='tracks')
language = Column(String) # IsoLanguage threeLetter
title = Column(String)
tags = relationship('Tag', back_populates='track', cascade="all, delete")
disposition_flags = Column(Integer)

@ -11,12 +11,16 @@ from ffx.model.pattern import Pattern
from .pattern_controller import PatternController from .pattern_controller import PatternController
from .show_controller import ShowController from .show_controller import ShowController
from .track_controller import TrackController
from .track_details_screen import TrackDetailsScreen from .track_details_screen import TrackDetailsScreen
from .track_delete_screen import TrackDeleteScreen from .track_delete_screen import TrackDeleteScreen
from ffx.track_type import TrackType from ffx.track_type import TrackType
from ffx.track_disposition import TrackDisposition
# Screen[dict[int, str, int]] # Screen[dict[int, str, int]]
class PatternDetailsScreen(Screen): class PatternDetailsScreen(Screen):
@ -71,23 +75,29 @@ class PatternDetailsScreen(Screen):
self.__pc = PatternController(context = self.context) self.__pc = PatternController(context = self.context)
self.__sc = ShowController(context = self.context) self.__sc = ShowController(context = self.context)
self.__tc = TrackController(context = self.context)
self.pattern_obj = self.__pc.getPatternDescriptor(patternId) if patternId is not None else {} self.pattern_obj = self.__pc.getPatternDescriptor(patternId) if patternId is not None else {}
self.show_obj = self.__sc.getShowDesciptor(showId) if showId is not None else {} self.show_obj = self.__sc.getShowDesciptor(showId) if showId is not None else {}
# def loadPatterns(self, show_id): def loadTracks(self, show_id):
#
# try: try:
# s = self.Session()
# q = s.query(Pattern).filter(Pattern.show_id == int(show_id)) tracks = {}
# tracks['audio'] = {}
# return [{'id': int(p.id), 'pattern': p.pattern} for p in q.all()] tracks['subtitle'] = {}
#
# except Exception as ex: s = self.Session()
# click.ClickException(f"loadPatterns(): {repr(ex)}") q = s.query(Pattern).filter(Pattern.show_id == int(show_id))
# finally:
# s.close() return [{'id': int(p.id), 'pattern': p.pattern} for p in q.all()]
except Exception as ex:
click.ClickException(f"loadPatterns(): {repr(ex)}")
finally:
s.close()
def on_mount(self): def on_mount(self):
@ -97,30 +107,52 @@ class PatternDetailsScreen(Screen):
if self.show_obj: if self.show_obj:
self.query_one("#showlabel", Static).update(f"{self.show_obj['id']} - {self.show_obj['name']} ({self.show_obj['year']})") self.query_one("#showlabel", Static).update(f"{self.show_obj['id']} - {self.show_obj['name']} ({self.show_obj['year']})")
# for pattern in self.loadPatterns(int(self.show_obj['id'])):
# row = (pattern['pattern'],)
# self.patternTable.add_row(*map(str, row))
if self.pattern_obj:
trackIds = self.__tc.findAllTracks(self.pattern_obj['id'])
for subIndex in range(3): for audioTrackId in trackIds['audio']:
row4 = (str(subIndex),str(subIndex),str(subIndex),str(subIndex),) ad = self.__tc.getTrackDescriptor(audioTrackId)
self.audioStreamsTable.add_row(*map(str, row4)) dispoList = ad['disposition_list']
row5 = (str(subIndex),str(subIndex),str(subIndex),str(subIndex),str(subIndex),) row = (ad['sub_index'],
self.subtitleStreamsTable.add_row(*map(str, row5)) " ",
ad['language'].label(),
ad['title'],
'Yes' if TrackDisposition.DEFAULT in dispoList else 'No',
'Yes' if TrackDisposition.FORCED in dispoList else 'No')
self.audioStreamsTable.add_row(*map(str, row))
for subtitleTrackId in trackIds['subtitle']:
sd = self.__tc.getTrackDescriptor(subtitleTrackId)
dispoList = sd['disposition_list']
row = (sd['sub_index'],
" ",
sd['language'].label(),
sd['title'],
'Yes' if TrackDisposition.DEFAULT in dispoList else 'No',
'Yes' if TrackDisposition.FORCED in dispoList else 'No')
self.subtitleStreamsTable.add_row(*map(str, row))
def compose(self): def compose(self):
self.audioStreamsTable = DataTable(classes="five") self.audioStreamsTable = DataTable(classes="five")
# Define the columns with headers # Define the columns with headers
self.column_key_audio_subid = self.audioStreamsTable.add_column("Subindex", width=10) self.column_key_audio_subid = self.audioStreamsTable.add_column("Subindex", width=20)
self.column_key_audio_layout = self.audioStreamsTable.add_column("Layout", width=10) self.column_key_audio_layout = self.audioStreamsTable.add_column("Layout", width=20)
self.column_key_audio_language = self.audioStreamsTable.add_column("Language", width=10) self.column_key_audio_language = self.audioStreamsTable.add_column("Language", width=20)
self.column_key_audio_title = self.audioStreamsTable.add_column("Title", width=10) self.column_key_audio_title = self.audioStreamsTable.add_column("Title", width=30)
self.column_key_subtitle_default = self.audioStreamsTable.add_column("Default", width=10)
self.column_key_subtitle_forced = self.audioStreamsTable.add_column("Forced", width=10)
self.audioStreamsTable.cursor_type = 'row' self.audioStreamsTable.cursor_type = 'row'
@ -128,9 +160,10 @@ class PatternDetailsScreen(Screen):
self.subtitleStreamsTable = DataTable(classes="five") self.subtitleStreamsTable = DataTable(classes="five")
# Define the columns with headers # Define the columns with headers
self.column_key_subtitle_subid = self.subtitleStreamsTable.add_column("Subindex", width=10) self.column_key_subtitle_subid = self.subtitleStreamsTable.add_column("Subindex", width=20)
self.column_key_subtitle_language = self.subtitleStreamsTable.add_column("Language", width=10) self.column_key_subtitle_spacer = self.subtitleStreamsTable.add_column(" ", width=20)
self.column_key_subtitle_title = self.subtitleStreamsTable.add_column("Title", width=10) self.column_key_subtitle_language = self.subtitleStreamsTable.add_column("Language", width=20)
self.column_key_subtitle_title = self.subtitleStreamsTable.add_column("Title", width=30)
self.column_key_subtitle_default = self.subtitleStreamsTable.add_column("Default", width=10) self.column_key_subtitle_default = self.subtitleStreamsTable.add_column("Default", width=10)
self.column_key_subtitle_forced = self.subtitleStreamsTable.add_column("Forced", width=10) self.column_key_subtitle_forced = self.subtitleStreamsTable.add_column("Forced", width=10)
@ -157,9 +190,15 @@ class PatternDetailsScreen(Screen):
# 5 # 5
yield Static("Audio streams") yield Static("Audio streams")
yield Static(" ") yield Static(" ")
yield Button("Add", id="button_add_audio_stream")
yield Button("Edit", id="button_edit_audio_stream") if self.pattern_obj:
yield Button("Delete", id="button_delete_audio_stream") yield Button("Add", id="button_add_audio_stream")
yield Button("Edit", id="button_edit_audio_stream")
yield Button("Delete", id="button_delete_audio_stream")
else:
yield Static("")
yield Static("")
yield Static("")
# 6 # 6
yield self.audioStreamsTable yield self.audioStreamsTable
@ -169,9 +208,15 @@ class PatternDetailsScreen(Screen):
# 8 # 8
yield Static("Subtitle streams") yield Static("Subtitle streams")
yield Static(" ") yield Static(" ")
yield Button("Add", id="button_add_subtitle_stream")
yield Button("Edit", id="button_edit_subtitle_stream") if self.pattern_obj:
yield Button("Delete", id="button_delete_subtitle_stream") yield Button("Add", id="button_add_subtitle_stream")
yield Button("Edit", id="button_edit_subtitle_stream")
yield Button("Delete", id="button_delete_subtitle_stream")
else:
yield Static("")
yield Static("")
yield Static("")
# 9 # 9
yield self.subtitleStreamsTable yield self.subtitleStreamsTable
@ -211,24 +256,51 @@ class PatternDetailsScreen(Screen):
self.app.pop_screen() self.app.pop_screen()
# Save pattern when just created before adding streams
if self.pattern_obj:
if event.button.id == "button_add_audio_stream": if event.button.id == "button_add_audio_stream":
self.app.push_screen(TrackDetailsScreen(TrackType.AUDIO), self.handle_add_stream) self.app.push_screen(TrackDetailsScreen(trackType = TrackType.AUDIO, patternId = self.pattern_obj['id'], subIndex = len(self.audioStreamsTable.rows)), self.handle_add_stream)
if event.button.id == "button_edit_audio_stream": if event.button.id == "button_edit_audio_stream":
self.app.push_screen(TrackDetailsScreen(TrackType.AUDIO), self.handle_edit_stream) self.app.push_screen(TrackDetailsScreen(trackType = TrackType.AUDIO, patternId = self.pattern_obj['id']), self.handle_edit_stream)
if event.button.id == "button_delete_audio_stream": if event.button.id == "button_delete_audio_stream":
self.app.push_screen(TrackDeleteScreen(TrackType.AUDIO), self.handle_delete_stream) self.app.push_screen(TrackDeleteScreen(trackType = TrackType.AUDIO, patternId = self.pattern_obj['id']), self.handle_delete_stream)
if event.button.id == "button_add_subtitle_stream": if event.button.id == "button_add_subtitle_stream":
self.app.push_screen(TrackDetailsScreen(TrackType.SUBTITLE), self.handle_add_stream) self.app.push_screen(TrackDetailsScreen(trackType = TrackType.SUBTITLE, patternId = self.pattern_obj['id'], subIndex = len(self.subtitleStreamsTable.rows)), self.handle_add_stream)
if event.button.id == "button_edit_subtitle_stream": if event.button.id == "button_edit_subtitle_stream":
self.app.push_screen(TrackDetailsScreen(TrackType.SUBTITLE), self.handle_edit_stream) self.app.push_screen(TrackDetailsScreen(trackType = TrackType.SUBTITLE, patternId = self.pattern_obj['id']), self.handle_edit_stream)
if event.button.id == "button_delete_subtitle_stream": if event.button.id == "button_delete_subtitle_stream":
self.app.push_screen(TrackDeleteScreen(TrackType.SUBTITLE), self.handle_delete_stream) self.app.push_screen(TrackDeleteScreen(trackType = TrackType.SUBTITLE, patternId = self.pattern_obj['id']), self.handle_delete_stream)
def handle_add_stream(self, trackDescriptor):
dispoList = trackDescriptor['disposition_list']
if trackDescriptor['type'] == TrackType.AUDIO:
row = (trackDescriptor['sub_index'],
" ",
trackDescriptor['language'].label(),
trackDescriptor['title'],
'Yes' if TrackDisposition.DEFAULT in dispoList else 'No',
'Yes' if TrackDisposition.FORCED in dispoList else 'No')
self.audioStreamsTable.add_row(*map(str, row))
if trackDescriptor['type'] == TrackType.SUBTITLE:
row = (trackDescriptor['sub_index'],
" ",
trackDescriptor['language'].label(),
trackDescriptor['title'],
'Yes' if TrackDisposition.DEFAULT in dispoList else 'No',
'Yes' if TrackDisposition.FORCED in dispoList else 'No')
self.subtitleStreamsTable.add_row(*map(str, row))
def handle_add_stream(self):
pass
def handle_edit_stream(self): def handle_edit_stream(self):
pass pass
def handle_delete_stream(self): def handle_delete_stream(self):

@ -5,6 +5,8 @@ from textual.app import App, ComposeResult
from textual.screen import Screen from textual.screen import Screen
from textual.widgets import Header, Footer, Placeholder, Label, ListView, ListItem, Static, DataTable, Button, Input from textual.widgets import Header, Footer, Placeholder, Label, ListView, ListItem, Static, DataTable, Button, Input
from textual.containers import Grid, Horizontal from textual.containers import Grid, Horizontal
from textual.widgets._data_table import CellDoesNotExist
from ffx.model.show import Show from ffx.model.show import Show
from ffx.model.pattern import Pattern from ffx.model.pattern import Pattern
@ -21,9 +23,9 @@ class ShowDetailsScreen(Screen):
CSS = """ CSS = """
Grid { Grid {
grid-size: 2; grid-size: 2 14;
grid-rows: 2 auto; grid-rows: 2 2 2 2 2 2 2 2 2 2 2 6 2 2;
grid-columns: 30 330; grid-columns: 30 90;
height: 100%; height: 100%;
width: 100%; width: 100%;
padding: 1; padding: 1;
@ -114,22 +116,30 @@ class ShowDetailsScreen(Screen):
def getSelectedPattern(self): def getSelectedPattern(self):
# Fetch the currently selected row when 'Enter' is pressed
#selected_row_index = self.table.cursor_row
row_key, col_key = self.patternTable.coordinate_to_cell_key(self.patternTable.cursor_coordinate)
selectedPattern = {} selectedPattern = {}
if row_key is not None: try:
selected_row_data = self.patternTable.get_row(row_key)
# Fetch the currently selected row when 'Enter' is pressed
selectedPattern['pattern'] = str(selected_row_data[0]) #selected_row_index = self.table.cursor_row
#selectedPattern['pattern'] = selected_row_data[1] row_key, col_key = self.patternTable.coordinate_to_cell_key(self.patternTable.cursor_coordinate)
if row_key is not None:
selected_row_data = self.patternTable.get_row(row_key)
selectedPattern['pattern'] = str(selected_row_data[0])
#selectedPattern['pattern'] = selected_row_data[1]
except CellDoesNotExist:
pass
return selectedPattern return selectedPattern
def action_add_pattern(self): def action_add_pattern(self):
if self.show_obj: if self.show_obj:
self.app.push_screen(PatternDetailsScreen(showId = self.show_obj['id']), self.handle_add_pattern) self.app.push_screen(PatternDetailsScreen(showId = self.show_obj['id']), self.handle_add_pattern)
@ -186,7 +196,7 @@ class ShowDetailsScreen(Screen):
self.patternTable = DataTable(classes="two") self.patternTable = DataTable(classes="two")
# Define the columns with headers # Define the columns with headers
self.column_key_id = self.patternTable.add_column("Pattern", width=60) self.column_key_id = self.patternTable.add_column("Pattern", width=90)
#self.column_key_name = self.patternTable.add_column("Name", width=50) #self.column_key_name = self.patternTable.add_column("Name", width=50)
#self.column_key_year = self.patternTable.add_column("Year", width=10) #self.column_key_year = self.patternTable.add_column("Year", width=10)
@ -197,36 +207,55 @@ class ShowDetailsScreen(Screen):
with Grid(): with Grid():
# 1
yield Static("Show" if self.show_obj else "New Show", id="toplabel", classes="two") yield Static("Show" if self.show_obj else "New Show", id="toplabel", classes="two")
# 2
yield Static("ID") yield Static("ID")
if self.show_obj: if self.show_obj:
yield Static("", id="id_wdg") yield Static("", id="id_wdg")
else: else:
yield Input(type="integer", id="id_wdg") yield Input(type="integer", id="id_wdg")
# 3
yield Static("Name") yield Static("Name")
yield Input(type="text", id="name_input") yield Input(type="text", id="name_input")
# 4
yield Static("Year") yield Static("Year")
yield Input(type="integer", id="year_input") yield Input(type="integer", id="year_input")
#5
yield Static(" ", classes="two") yield Static(" ", classes="two")
#6
yield Static("Index Season Digits") yield Static("Index Season Digits")
yield Input(type="integer", id="index_season_digits_input") yield Input(type="integer", id="index_season_digits_input")
#7
yield Static("Index Episode Digits") yield Static("Index Episode Digits")
yield Input(type="integer", id="index_episode_digits_input") yield Input(type="integer", id="index_episode_digits_input")
#8
yield Static("Indicator Season Digits") yield Static("Indicator Season Digits")
yield Input(type="integer", id="indicator_season_digits_input") yield Input(type="integer", id="indicator_season_digits_input")
#9
yield Static("Indicator Edisode Digits") yield Static("Indicator Edisode Digits")
yield Input(type="integer", id="indicator_episode_digits_input") yield Input(type="integer", id="indicator_episode_digits_input")
# 10
yield Static(" ", classes="two") yield Static(" ", classes="two")
# 11
yield Static("File patterns", classes="two")
# 12
yield self.patternTable yield self.patternTable
# 13
yield Static(" ", classes="two") yield Static(" ", classes="two")
# 14
yield Button("Save", id="save_button") yield Button("Save", id="save_button")
yield Button("Cancel", id="cancel_button") yield Button("Cancel", id="cancel_button")

@ -2,6 +2,11 @@ import click
from ffx.model.track import Track from ffx.model.track import Track
from .track_type import TrackType
from .track_disposition import TrackDisposition
from .iso_language import IsoLanguage
class TrackController(): class TrackController():
@ -11,82 +16,151 @@ class TrackController():
self.Session = self.context['database_session'] # convenience self.Session = self.context['database_session'] # convenience
# def updatePattern(self, show_id, pattern): def addTrack(self, trackDescriptor):
#
# try: try:
# s = self.Session() s = self.Session()
# q = s.query(Pattern).filter(Pattern.show_id == int(show_id), Pattern.pattern == str(pattern))
# track = Track(pattern_id = int(trackDescriptor['pattern_id']),
# if not q.count():
# pattern = Pattern(show_id = int(show_id), pattern = str(pattern)) track_type = int(trackDescriptor['type'].value),
# s.add(pattern)
# s.commit() sub_index = int(trackDescriptor['sub_index']),
# return True
# language = str(trackDescriptor['language'].threeLetter()),
# except Exception as ex:
# raise click.ClickException(f"PatternController.updatePattern(): {repr(ex)}") title = str(trackDescriptor['title']),
# finally:
# s.close() disposition_flags = int(TrackDisposition.toFlags(trackDescriptor['disposition_list'])))
#
# s.add(track)
# s.commit()
# def findPattern(self, showId, pattern): return True
#
# try:
# s = self.Session() except Exception as ex:
# q = s.query(Pattern).filter(Pattern.show_id == int(showId), Pattern.pattern == str(pattern)) raise click.ClickException(f"TrackController.addTrack(): {repr(ex)}")
# finally:
# if q.count(): s.close()
# pattern = q.first()
# return int(pattern.id)
# else: # def updateTrack(self, trackDescriptor):
# return None #
# # try:
# except Exception as ex: # s = self.Session()
# raise click.ClickException(f"PatternController.findPattern(): {repr(ex)}") # q = s.query(Track).filter(Track.id == int(trackId))
# finally: #
# s.close() # if not q.count():
# # track = Track(pattern_id = int(trackDescriptor['pattern_id']),
# # track_type = TrackType(trackDescriptor['type']),
# def getPatternDescriptor(self, patternId): # sub_index = int(trackDescriptor['sub_index']),
# # language = IsoLanguage(trackDescriptor['language']),
# try: # title = str(trackDescriptor['title']),
# s = self.Session() # disposition_flags = TrackDisposition.toFlags(trackDescriptor['disposition_list']))
# q = s.query(Pattern).filter(Pattern.id == int(patternId)) # s.add(track)
# # s.commit()
# patternDescriptor = {} # return True
# if q.count(): # else:
# pattern = q.first() # return False
# #
# patternDescriptor['id'] = pattern.id # except Exception as ex:
# patternDescriptor['pattern'] = pattern.pattern # raise click.ClickException(f"TrackController.updateTrack(): {repr(ex)}")
# patternDescriptor['show_id'] = pattern.show_id # finally:
# # s.close()
# return patternDescriptor
#
# except Exception as ex: def findAllTracks(self, patternId):
# raise click.ClickException(f"PatternController.getPatternDescriptor(): {repr(ex)}")
# finally: try:
# s.close() s = self.Session()
#
# trackDescriptors = {}
# def deletePattern(self, patternId): trackDescriptors['audio'] = []
# try: trackDescriptors['subtitle'] = []
# s = self.Session()
# q = s.query(Pattern).filter(Pattern.id == int(patternId)) q_audio = s.query(Track).filter(Track.pattern_id == int(patternId), Track.track_type == TrackType.AUDIO.value)
#
# if q.count(): for audioTrack in q_audio.all():
# trackDescriptors['audio'].append(audioTrack.id)
# #DAFUQ: https://stackoverflow.com/a/19245058
# # q.delete() q_subtitle = s.query(Track).filter(Track.pattern_id == int(patternId), Track.track_type == TrackType.SUBTITLE.value)
# pattern = q.first() for subtitleTrack in q_subtitle.all():
# s.delete(pattern) trackDescriptors['subtitle'].append(subtitleTrack.id)
#
# s.commit()
# return True return trackDescriptors
# return False
#
# except Exception as ex: except Exception as ex:
# raise click.ClickException(f"PatternController.deletePattern(): {repr(ex)}") raise click.ClickException(f"TrackController.findAllTracks(): {repr(ex)}")
# finally: finally:
# s.close() s.close()
def findTrack(self, patternId, trackType, subIndex):
try:
s = self.Session()
q = s.query(Track).filter(Track.pattern_id == int(patternId), Track.track_type == trackType, Track.sub_index == int(subIndex))
if q.count():
track = q.first()
return int(track.id)
else:
return None
except Exception as ex:
raise click.ClickException(f"TrackController.findTrack(): {repr(ex)}")
finally:
s.close()
def getTrackDict(self, track):
trackDescriptor = {}
trackDescriptor['pattern_id'] = int(track.pattern_id)
trackDescriptor['type'] = TrackType(track.track_type)
trackDescriptor['sub_index'] = int(track.sub_index)
trackDescriptor['language'] = IsoLanguage.findThreeLetter(track.language)
trackDescriptor['title'] = str(track.title)
trackDescriptor['disposition_list'] = TrackDisposition.toList(track.disposition_flags)
return trackDescriptor
def getTrackDescriptor(self, trackId):
try:
s = self.Session()
q = s.query(Track).filter(Track.id == int(trackId))
if q.count():
track = q.first()
return self.getTrackDict(track)
else:
return {}
except Exception as ex:
raise click.ClickException(f"TrackController.getTrackDescriptor(): {repr(ex)}")
finally:
s.close()
def deleteTrack(self, trackId):
try:
s = self.Session()
q = s.query(Track).filter(Track.id == int(trackId))
if q.count():
#DAFUQ: https://stackoverflow.com/a/19245058
# q.delete()
pattern = q.first()
s.delete(pattern)
s.commit()
return True
return False
except Exception as ex:
raise click.ClickException(f"TrackController.deleteTrack(): {repr(ex)}")
finally:
s.close()

@ -1,20 +1,24 @@
import click import click, time
from textual import events from textual import events
from textual.app import App, ComposeResult from textual.app import App, ComposeResult
from textual.screen import Screen from textual.screen import Screen
from textual.widgets import Header, Footer, Placeholder, Label, ListView, ListItem, Static, DataTable, Button, Input from textual.widgets import Header, Footer, Placeholder, Label, ListView, ListItem, Static, DataTable, Button, Input, Checkbox, SelectionList, Select
from textual.containers import Grid, Horizontal from textual.containers import Grid, Horizontal
from ffx.model.show import Show from ffx.model.show import Show
from ffx.model.pattern import Pattern from ffx.model.pattern import Pattern
from .track_controller import TrackController from .track_controller import TrackController
# from .pattern_controller import PatternController from .pattern_controller import PatternController
# from .show_controller import ShowController # from .show_controller import ShowController
from .track_type import TrackType from .track_type import TrackType
from .iso_language import IsoLanguage
from .track_disposition import TrackDisposition
# Screen[dict[int, str, int]] # Screen[dict[int, str, int]]
class TrackDetailsScreen(Screen): class TrackDetailsScreen(Screen):
@ -22,8 +26,8 @@ class TrackDetailsScreen(Screen):
CSS = """ CSS = """
Grid { Grid {
grid-size: 5 11; grid-size: 5 18;
grid-rows: 2 2 2 2 6 2 2 6 2 2 2; grid-rows: 2 2 2 2 2 3 2 2 2 2 2 6 2 2 6 2 2 2;
grid-columns: 25 25 25 25 25; grid-columns: 25 25 25 25 25;
height: 100%; height: 100%;
width: 100%; width: 100%;
@ -36,7 +40,13 @@ class TrackDetailsScreen(Screen):
Button { Button {
border: none; border: none;
} }
SelectionList {
border: none;
min-height: 6;
}
Select {
border: none;
}
DataTable { DataTable {
min-height: 6; min-height: 6;
} }
@ -44,7 +54,10 @@ class TrackDetailsScreen(Screen):
#toplabel { #toplabel {
height: 1; height: 1;
} }
.two {
column-span: 2;
}
.three { .three {
column-span: 3; column-span: 3;
} }
@ -62,21 +75,25 @@ class TrackDetailsScreen(Screen):
} }
""" """
def __init__(self, trackType : TrackType, streamId = None, patternId = None): def __init__(self, trackId = None, patternId = None, trackType : TrackType = None, subIndex = None):
super().__init__() super().__init__()
self.context = self.app.getContext() self.context = self.app.getContext()
self.Session = self.context['database_session'] # convenience self.Session = self.context['database_session'] # convenience
self.__tc = TrackController(context = self.context)
self.__pc = PatternController(context = self.context)
self.track_obj = self.__tc.getTrackDescriptor(trackId) if trackId is not None else {} #TODO: Overwriting alternative values if set
if trackType is None:
raise click.ClickException('Track type is required to be set')
self.trackType = trackType self.trackType = trackType
self.subIndex = subIndex
self.__tc = TrackController(context = self.context) self.pattern_obj = self.__pc.getPatternDescriptor(patternId) if patternId is not None else {}
#self.__pc = PatternController(context = self.context)
#self.__sc = ShowController(context = self.context)
# self.pattern_obj = self.__pc.getPatternDescriptor(patternId) if patternId is not None else {}
# self.show_obj = self.__sc.getShowDesciptor(showId) if showId is not None else {}
self.track_obj = {}
# def loadPatterns(self, show_id): # def loadPatterns(self, show_id):
# #
@ -93,9 +110,12 @@ class TrackDetailsScreen(Screen):
def on_mount(self): def on_mount(self):
pass
# if self.pattern_obj: if self.pattern_obj:
# self.query_one("#pattern_input", Input).value = str(self.pattern_obj['pattern']) self.query_one("#patternlabel", Static).update(str(self.pattern_obj['pattern']))
if self.subIndex is not None:
self.query_one("#subindexlabel", Static).update(str(self.subIndex))
# if self.show_obj: # if self.show_obj:
# self.query_one("#showlabel", Static).update(f"{self.show_obj['id']} - {self.show_obj['name']} ({self.show_obj['year']})") # self.query_one("#showlabel", Static).update(f"{self.show_obj['id']} - {self.show_obj['name']} ({self.show_obj['year']})")
@ -116,27 +136,19 @@ class TrackDetailsScreen(Screen):
def compose(self): def compose(self):
# self.audioStreamsTable = DataTable(classes="five") self.trackTagsTable = DataTable(classes="five")
#
# # Define the columns with headers # Define the columns with headers
# self.column_key_audio_subid = self.audioStreamsTable.add_column("Subindex", width=10) self.column_key_track_tag_key = self.trackTagsTable.add_column("Key", width=10)
# self.column_key_audio_layout = self.audioStreamsTable.add_column("Layout", width=10) self.column_key_track_tag_value = self.trackTagsTable.add_column("Value", width=30)
# self.column_key_audio_language = self.audioStreamsTable.add_column("Language", width=10)
# self.column_key_audio_title = self.audioStreamsTable.add_column("Title", width=10) self.trackTagsTable.cursor_type = 'row'
#
# self.audioStreamsTable.cursor_type = 'row'
# languages = [l.label() for l in IsoLanguage]
#
# self.subtitleStreamsTable = DataTable(classes="five") dispositions = [(d.label(), d.index(), False) for d in TrackDisposition]
#
# # Define the columns with headers
# self.column_key_subtitle_subid = self.subtitleStreamsTable.add_column("Subindex", width=10)
# self.column_key_subtitle_language = self.subtitleStreamsTable.add_column("Language", width=10)
# self.column_key_subtitle_title = self.subtitleStreamsTable.add_column("Title", width=10)
# self.column_key_subtitle_default = self.subtitleStreamsTable.add_column("Default", width=10)
# self.column_key_subtitle_forced = self.subtitleStreamsTable.add_column("Forced", width=10)
#
# self.subtitleStreamsTable.cursor_type = 'row'
yield Header() yield Header()
@ -144,69 +156,102 @@ class TrackDetailsScreen(Screen):
# 1 # 1
yield Static(f"Edit {self.trackType.label()} stream" if self.track_obj else f"New {self.trackType.label()} stream", id="toplabel", classes="five") yield Static(f"Edit {self.trackType.label()} stream" if self.track_obj else f"New {self.trackType.label()} stream", id="toplabel", classes="five")
# yield Input(type="text", id="pattern_input", classes="four")
# 2 # 2
# yield Static("from show") yield Static("for pattern")
# yield Static("", id="showlabel") yield Static("", id="patternlabel", classes="four")
#
# # 3 # 3
# yield Static(" ", classes="five") yield Static("sub index")
# # 4 yield Static("", id="subindexlabel", classes="four")
# yield Static(" ", classes="five")
# # 4
# # 5 yield Static(" ", classes="five")
# yield Static("Audio streams") # 5
# yield Static(" ") yield Static(" ", classes="five")
# yield Button("Add", id="button_add_audio_stream")
# yield Button("Edit", id="button_edit_audio_stream") # 6
# yield Button("Delete", id="button_delete_audio_stream") yield Static("Language")
# # 6 yield Select.from_values(languages, classes="four", id="language_select")
# yield self.audioStreamsTable # 7
# yield Static(" ", classes="five")
# # 7
# yield Static(" ", classes="five") # 8
# yield Static("Title")
# # 8 yield Input(id="title_input", classes="four")
# yield Static("Subtitle streams")
# yield Static(" ") # 9
# yield Button("Add", id="button_add_subtitle_stream") yield Static(" ", classes="five")
# yield Button("Edit", id="button_edit_subtitle_stream")
# yield Button("Delete", id="button_delete_subtitle_stream")
# # 9
# yield self.subtitleStreamsTable
#
# 10 # 10
yield Static(" ", classes="five") yield Static(" ", classes="five")
# 11 # 11
yield Static("Stream tags")
yield Static(" ", classes="two")
yield Button("Add", id="button_add_stream_tag")
yield Button("Delete", id="button_delete_stream_tag")
# 12
yield self.trackTagsTable
# 13
yield Static(" ", classes="five")
# 14
yield Static("Stream dispositions", classes="five")
# 15
yield SelectionList[int](
*dispositions,
classes="five",
id = "dispositions_selection_list"
)
# 16
yield Static(" ", classes="five")
# 17
yield Static(" ", classes="five")
# 18
yield Button("Save", id="save_button") yield Button("Save", id="save_button")
yield Button("Cancel", id="cancel_button") yield Button("Cancel", id="cancel_button")
yield Footer() yield Footer()
# def getPatternFromInput(self): def getTrackDescriptorFromInput(self):
# return str(self.query_one("#pattern_input", Input).value)
trackDescriptor = {}
trackDescriptor['pattern_id'] = int(self.pattern_obj['id'])
trackDescriptor['type'] = TrackType(self.trackType)
trackDescriptor['sub_index'] = self.subIndex
trackDescriptor['language'] = IsoLanguage.find(str(self.query_one("#language_select", Select).value))
trackDescriptor['title'] = str(self.query_one("#title_input", Input).value)
disposition_flags = sum([2**f for f in self.query_one("#dispositions_selection_list", SelectionList).selected])
trackDescriptor['disposition_list'] = TrackDisposition.toList(disposition_flags)
return trackDescriptor
# Event handler for button press # Event handler for button press
def on_button_pressed(self, event: Button.Pressed) -> None: def on_button_pressed(self, event: Button.Pressed) -> None:
# Check if the button pressed is the one we are interested in # Check if the button pressed is the one we are interested in
# if event.button.id == "save_button": if event.button.id == "save_button":
#
# pattern = self.getPatternFromInput() trackDescriptor = self.getTrackDescriptorFromInput()
#
# if self.__pc.updatePattern(self.show_obj['id'], pattern): if self.__tc.addTrack(trackDescriptor): #!
# self.dismiss(trackDescriptor)
# screenResult = {} else:
# screenResult['show_id'] = self.show_obj['id'] #TODO: Meldung
# screenResult['pattern'] = pattern self.app.pop_screen()
#
# self.dismiss(screenResult)
# else:
# #TODO: Meldung
# self.app.pop_screen()
if event.button.id == "cancel_button": if event.button.id == "cancel_button":
self.app.pop_screen() self.app.pop_screen()

@ -0,0 +1,49 @@
from enum import Enum
import difflib
class TrackDisposition(Enum):
DEFAULT = {"name": "default", "index": 0}
FORCED = {"name": "forced", "index": 1}
DUB = {"name": "dub", "index": 2}
ORIGINAL = {"name": "original", "index": 3}
COMMENT = {"name": "comment", "index": 4}
LYRICS = {"name": "lyrics", "index": 5}
KARAOKE = {"name": "karaoke", "index": 6}
HEARING_IMPAIRED = {"name": "hearing_impaired", "index": 7}
VISUAL_IMPAIRED = {"name": "visual_impaired", "index": 8}
CLEAN_EFFECTS = {"name": "clean_effects", "index": 9}
ATTACHED_PIC = {"name": "attached_pic", "index": 10}
TIMED_THUMBNAILS = {"name": "timed_thumbnails", "index": 11}
NON_DIEGETICS = {"name": "non_diegetic", "index": 12}
CAPTIONS = {"name": "captions", "index": 13}
DESCRIPTIONS = {"name": "descriptions", "index": 14}
METADATA = {"name": "metadata", "index": 15}
DEPENDENT = {"name": "dependent", "index": 16}
STILL_IMAGE = {"name": "still_image", "index": 17}
def label(self):
return str(self.value['name'])
def index(self):
return int(self.value['index'])
@staticmethod
def toFlags(dispositionList):
"""Flags stored in integer bits (2**index)"""
flags = 0
for d in dispositionList:
flags += 2 ** d.index()
return flags
@staticmethod
def toList(flags):
dispositionList = []
for d in TrackDisposition:
if flags & int(2 ** d.index()):
dispositionList += [d]
return dispositionList
Loading…
Cancel
Save