inc streams ui

click-textual
Javanaut 1 year ago
parent 73c957c9bb
commit a5d568ba34

@ -8,6 +8,7 @@ from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from ffx.model.show import Base, Show
from ffx.model.pattern import Pattern
from ffx.model.track import Track
from .shows_screen import ShowsScreen
from .warning_screen import WarningScreen

@ -2,6 +2,8 @@ from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, sessionmaker, Mapped, backref
from .show import Base
from .track import Track
class Pattern(Base):
@ -22,3 +24,5 @@ class Pattern(Base):
# v2.0
# show_id: Mapped[int] = mapped_column(ForeignKey("shows.id", ondelete="CASCADE"))
# show: Mapped["Show"] = relationship(back_populates="patterns")
tracks = relationship('Track', back_populates='pattern', cascade="all, delete")

@ -0,0 +1,28 @@
# from typing import List
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, declarative_base, sessionmaker
from .show import Base
class Track(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__ = 'tracks'
# v1.x
id = Column(Integer, primary_key=True)
track_type = Column(Integer)
# v1.x
pattern_id = Column(Integer, ForeignKey('patterns.id', ondelete="CASCADE"))
pattern = relationship('Pattern', back_populates='tracks')

@ -12,15 +12,19 @@ from ffx.model.pattern import Pattern
from .pattern_controller import PatternController
from .show_controller import ShowController
from .track_details_screen import TrackDetailsScreen
from .track_delete_screen import TrackDeleteScreen
# Screen[dict[int, str, int]]
class PatternDetailsScreen(Screen):
CSS = """
Grid {
grid-size: 2;
grid-rows: 2 auto;
grid-columns: 30 330;
grid-size: 5 11;
grid-rows: 2 2 2 2 6 2 2 6 2 2 2;
grid-columns: 25 25 25 25 25;
height: 100%;
width: 100%;
padding: 1;
@ -32,17 +36,24 @@ class PatternDetailsScreen(Screen):
Button {
border: none;
}
DataTable {
column-span: 2;
min-height: 5;
min-height: 6;
}
#toplabel {
height: 1;
}
.two {
column-span: 2;
.three {
column-span: 3;
}
.four {
column-span: 4;
}
.five {
column-span: 5;
}
.box {
@ -91,9 +102,18 @@ class PatternDetailsScreen(Screen):
# self.patternTable.add_row(*map(str, row))
for subIndex in range(3):
row4 = (str(subIndex),str(subIndex),str(subIndex),str(subIndex),)
self.audioStreamsTable.add_row(*map(str, row4))
row5 = (str(subIndex),str(subIndex),str(subIndex),str(subIndex),str(subIndex),)
self.subtitleStreamsTable.add_row(*map(str, row5))
def compose(self):
self.audioStreamsTable = DataTable()
self.audioStreamsTable = DataTable(classes="five")
# Define the columns with headers
self.column_key_audio_subid = self.audioStreamsTable.add_column("Subindex", width=10)
@ -104,7 +124,7 @@ class PatternDetailsScreen(Screen):
self.audioStreamsTable.cursor_type = 'row'
self.subtitleStreamsTable = DataTable()
self.subtitleStreamsTable = DataTable(classes="five")
# Define the columns with headers
self.column_key_subtitle_subid = self.subtitleStreamsTable.add_column("Subindex", width=10)
@ -116,31 +136,48 @@ class PatternDetailsScreen(Screen):
self.subtitleStreamsTable.cursor_type = 'row'
yield Header()
with Grid():
yield Static("Filename pattern" if self.pattern_obj else "New filename pattern", id="toplabel", classes="two")
# 1
yield Static("Edit filename pattern" if self.pattern_obj else "New filename pattern", id="toplabel")
yield Input(type="text", id="pattern_input", classes="four")
# 2
yield Static("from show")
yield Static("", id="showlabel")
yield Static("Pattern")
yield Input(type="text", id="pattern_input")
yield Static(" ", classes="two")
yield Static("Audio streams", classes="two")
# 3
yield Static(" ", classes="five")
# 4
yield Static(" ", classes="five")
# 5
yield Static("Audio streams")
yield Static(" ")
yield Button("Add", id="button_add_audio_stream")
yield Button("Edit", id="button_edit_audio_stream")
yield Button("Delete", id="button_delete_audio_stream")
# 6
yield self.audioStreamsTable
yield Static(" ", classes="two")
# 7
yield Static(" ", classes="five")
yield Static("Subtitle streams", classes="two")
# 8
yield Static("Subtitle streams")
yield Static(" ")
yield Button("Add", id="button_add_subtitle_stream")
yield Button("Edit", id="button_edit_subtitle_stream")
yield Button("Delete", id="button_delete_subtitle_stream")
# 9
yield self.subtitleStreamsTable
yield Static(" ", classes="two")
# 10
yield Static(" ", classes="five")
# 11
yield Button("Save", id="save_button")
yield Button("Cancel", id="cancel_button")
@ -172,3 +209,26 @@ class PatternDetailsScreen(Screen):
if event.button.id == "cancel_button":
self.app.pop_screen()
if event.button.id == "button_add_audio_stream":
self.app.push_screen(TrackDetailsScreen(2), self.handle_add_stream)
if event.button.id == "button_edit_audio_stream":
self.app.push_screen(TrackDetailsScreen(2), self.handle_edit_stream)
if event.button.id == "button_delete_audio_stream":
self.app.push_screen(TrackDeleteScreen(), self.handle_delete_stream)
if event.button.id == "button_add_subtitle_stream":
self.app.push_screen(TrackDetailsScreen(3), self.handle_add_stream)
if event.button.id == "button_edit_subtitle_stream":
self.app.push_screen(TrackDetailsScreen(3), self.handle_edit_stream)
if event.button.id == "button_delete_subtitle_stream":
self.app.push_screen(TrackDeleteScreen(), self.handle_delete_stream)
def handle_add_stream(self):
pass
def handle_edit_stream(self):
pass
def handle_delete_stream(self):
pass

@ -121,7 +121,7 @@ class ShowsScreen(Screen):
return [(int(s.id), s.name, s.year) for s in q.all()]
except Exception as ex:
click.ClickException(f"loadShows(): {repr(ex)}")
raise click.ClickException(f"ShowsScreen.loadShows(): {repr(ex)}")
finally:
s.close()
@ -131,7 +131,6 @@ class ShowsScreen(Screen):
self.table.add_row(*map(str, show)) # Convert each element to a string before adding
def compose(self):
# Create the DataTable widget

@ -0,0 +1,92 @@
import click
from ffx.model.track import Track
class TrackController():
def __init__(self, context):
self.context = context
self.Session = self.context['database_session'] # convenience
# def updatePattern(self, show_id, pattern):
#
# try:
# s = self.Session()
# q = s.query(Pattern).filter(Pattern.show_id == int(show_id), Pattern.pattern == str(pattern))
#
# if not q.count():
# pattern = Pattern(show_id = int(show_id), pattern = str(pattern))
# s.add(pattern)
# s.commit()
# return True
#
# except Exception as ex:
# raise click.ClickException(f"PatternController.updatePattern(): {repr(ex)}")
# finally:
# s.close()
#
#
#
# def findPattern(self, showId, pattern):
#
# try:
# s = self.Session()
# q = s.query(Pattern).filter(Pattern.show_id == int(showId), Pattern.pattern == str(pattern))
#
# if q.count():
# pattern = q.first()
# return int(pattern.id)
# else:
# return None
#
# except Exception as ex:
# raise click.ClickException(f"PatternController.findPattern(): {repr(ex)}")
# finally:
# s.close()
#
#
# def getPatternDescriptor(self, patternId):
#
# try:
# s = self.Session()
# q = s.query(Pattern).filter(Pattern.id == int(patternId))
#
# patternDescriptor = {}
# if q.count():
# pattern = q.first()
#
# patternDescriptor['id'] = pattern.id
# patternDescriptor['pattern'] = pattern.pattern
# patternDescriptor['show_id'] = pattern.show_id
#
# return patternDescriptor
#
# except Exception as ex:
# raise click.ClickException(f"PatternController.getPatternDescriptor(): {repr(ex)}")
# finally:
# s.close()
#
#
# def deletePattern(self, patternId):
# try:
# s = self.Session()
# q = s.query(Pattern).filter(Pattern.id == int(patternId))
#
# 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"PatternController.deletePattern(): {repr(ex)}")
# finally:
# s.close()

@ -0,0 +1,114 @@
import click
from textual import events
from textual.app import App, ComposeResult
from textual.screen import Screen
from textual.widgets import Header, Footer, Placeholder, Label, ListView, ListItem, Static, DataTable, Button, Input
from textual.containers import Grid, Horizontal
from ffx.model.pattern import Pattern
from .show_controller import ShowController
from .pattern_controller import PatternController
# Screen[dict[int, str, int]]
class TrackDeleteScreen(Screen):
CSS = """
Grid {
grid-size: 2;
grid-rows: 2 auto;
grid-columns: 30 330;
height: 100%;
width: 100%;
padding: 1;
}
Input {
border: none;
}
Button {
border: none;
}
#toplabel {
height: 1;
}
.two {
column-span: 2;
}
.box {
height: 100%;
border: solid green;
}
"""
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.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 {}
def on_mount(self):
if self.show_obj:
self.query_one("#showlabel", Static).update(f"{self.show_obj['id']} - {self.show_obj['name']} ({self.show_obj['year']})")
if self.pattern_obj:
self.query_one("#patternlabel", Static).update(str(self.pattern_obj['pattern']))
def compose(self):
yield Header()
with Grid():
yield Static("Are you sure to delete the following filename pattern?", id="toplabel", classes="two")
yield Static("", classes="two")
yield Static("Pattern")
yield Static("", id="patternlabel")
yield Static("", classes="two")
yield Static("from show")
yield Static("", id="showlabel")
yield Static("", classes="two")
yield Button("Delete", id="delete_button")
yield Button("Cancel", id="cancel_button")
yield Footer()
# Event handler for button press
def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "delete_button":
if self.__pc.deletePattern(self.pattern_obj['id']):
screenResult = {}
screenResult['show_id'] = self.show_obj['id']
screenResult['pattern'] = self.pattern_obj['pattern']
self.dismiss(screenResult)
else:
#TODO: Meldung
self.app.pop_screen()
if event.button.id == "cancel_button":
self.app.pop_screen()

@ -0,0 +1,222 @@
import click
from textual import events
from textual.app import App, ComposeResult
from textual.screen import Screen
from textual.widgets import Header, Footer, Placeholder, Label, ListView, ListItem, Static, DataTable, Button, Input
from textual.containers import Grid, Horizontal
from ffx.model.show import Show
from ffx.model.pattern import Pattern
from .track_controller import TrackController
# from .pattern_controller import PatternController
# from .show_controller import ShowController
from .track_type import TrackType
# Screen[dict[int, str, int]]
class TrackDetailsScreen(Screen):
CSS = """
Grid {
grid-size: 5 11;
grid-rows: 2 2 2 2 6 2 2 6 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;
}
"""
STREAM_TYPE_LABELS = [
'video',
'audio',
'subtitle'
]
def __init__(self, trackType : TrackType, streamId = None, patternId = None):
super().__init__()
self.context = self.app.getContext()
self.Session = self.context['database_session'] # convenience
self.trackType = trackType
self.__tc = TrackController(context = self.context)
#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):
#
# try:
# 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:
# click.ClickException(f"loadPatterns(): {repr(ex)}")
# finally:
# s.close()
def on_mount(self):
pass
# if self.pattern_obj:
# self.query_one("#pattern_input", Input).value = str(self.pattern_obj['pattern'])
# if self.show_obj:
# 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))
# for subIndex in range(3):
#
# row4 = (str(subIndex),str(subIndex),str(subIndex),str(subIndex),)
# self.audioStreamsTable.add_row(*map(str, row4))
#
# row5 = (str(subIndex),str(subIndex),str(subIndex),str(subIndex),str(subIndex),)
# self.subtitleStreamsTable.add_row(*map(str, row5))
def compose(self):
# self.audioStreamsTable = DataTable(classes="five")
#
# # Define the columns with headers
# self.column_key_audio_subid = self.audioStreamsTable.add_column("Subindex", width=10)
# self.column_key_audio_layout = self.audioStreamsTable.add_column("Layout", width=10)
# 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.audioStreamsTable.cursor_type = 'row'
#
#
# self.subtitleStreamsTable = DataTable(classes="five")
#
# # 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'
typeLabel = TrackDetailsScreen.STREAM_TYPE_LABELS[self.trackType-1]
yield Header()
with Grid():
# 1
yield Static(f"Edit {typeLabel} stream" if self.track_obj else f"New {typeLabel} stream", id="toplabel", classes="five")
# yield Input(type="text", id="pattern_input", classes="four")
# 2
# yield Static("from show")
# yield Static("", id="showlabel")
#
# # 3
# yield Static(" ", classes="five")
# # 4
# yield Static(" ", classes="five")
#
# # 5
# yield Static("Audio streams")
# yield Static(" ")
# yield Button("Add", id="button_add_audio_stream")
# yield Button("Edit", id="button_edit_audio_stream")
# yield Button("Delete", id="button_delete_audio_stream")
# # 6
# yield self.audioStreamsTable
#
# # 7
# yield Static(" ", classes="five")
#
# # 8
# yield Static("Subtitle streams")
# yield Static(" ")
# yield Button("Add", id="button_add_subtitle_stream")
# yield Button("Edit", id="button_edit_subtitle_stream")
# yield Button("Delete", id="button_delete_subtitle_stream")
# # 9
# yield self.subtitleStreamsTable
#
# 10
yield Static(" ", classes="five")
# 11
yield Button("Save", id="save_button")
yield Button("Cancel", id="cancel_button")
yield Footer()
# def getPatternFromInput(self):
# return str(self.query_one("#pattern_input", Input).value)
# 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":
#
# pattern = self.getPatternFromInput()
#
# if self.__pc.updatePattern(self.show_obj['id'], pattern):
#
# screenResult = {}
# screenResult['show_id'] = self.show_obj['id']
# screenResult['pattern'] = pattern
#
# self.dismiss(screenResult)
# else:
# #TODO: Meldung
# self.app.pop_screen()
if event.button.id == "cancel_button":
self.app.pop_screen()

@ -1,6 +1,6 @@
from enum import Enum
class StreamType(Enum):
class TrackType(Enum):
VIDEO = 1
AUDIO = 2
SUBTITLE = 3
Loading…
Cancel
Save