3 Commits

Author SHA1 Message Date
Maveno
882d021bb6 RFC imports 2024-10-06 16:24:01 +02:00
Maveno
131cca2c53 tracktags UI mwe 2024-10-06 16:07:23 +02:00
Maveno
a03449a32b add/edit tag to ui 2024-10-06 12:21:27 +02:00
19 changed files with 522 additions and 354 deletions

View File

@@ -1,23 +1,7 @@
import os, time, sqlite3, sqlalchemy from textual.app import App
from textual.app import App, ComposeResult
from textual.screen import Screen
from textual.widgets import Header, Footer, Placeholder, Label
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 ffx.model.media_tag import MediaTag
# from ffx.model.track_tag import TrackTag
from .shows_screen import ShowsScreen from .shows_screen import ShowsScreen
from .warning_screen import WarningScreen
from .dashboard_screen import DashboardScreen
from .settings_screen import SettingsScreen
from .help_screen import HelpScreen
class FfxApp(App): class FfxApp(App):

View File

@@ -3,11 +3,6 @@ import os, re, click, json
from .media_descriptor import MediaDescriptor from .media_descriptor import MediaDescriptor
from .pattern_controller import PatternController from .pattern_controller import PatternController
#from .track_type import TrackType
#from .audio_layout import AudioLayout
#from .track_disposition import TrackDisposition
from .process import executeProcess from .process import executeProcess

View File

@@ -93,15 +93,6 @@ class IsoLanguage(Enum):
return foundLangs[0] if foundLangs else IsoLanguage.UNDEFINED return foundLangs[0] if foundLangs else IsoLanguage.UNDEFINED
# 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): def label(self):
return str(self.value['name']) return str(self.value['name'])

View File

@@ -155,6 +155,9 @@ class Track(Base):
def getType(self): def getType(self):
return TrackType.fromIndex(self.track_type) return TrackType.fromIndex(self.track_type)
# def getIndex(self):
# return int(self.index)
def getSubIndex(self): def getSubIndex(self):
return int(self.sub_index) return int(self.sub_index)
@@ -171,3 +174,20 @@ class Track(Base):
def getTags(self): def getTags(self):
return {str(t.key):str(t.value) for t in self.track_tags} return {str(t.key):str(t.value) for t in self.track_tags}
def getDescriptor(self) -> TrackDescriptor:
kwargs = {}
kwargs[TrackDescriptor.ID_KEY] = self.getId()
kwargs[TrackDescriptor.PATTERN_ID_KEY] = self.getPatternId()
#kwargs[TrackDescriptor.SUB_INDEX_KEY] = self.getIndex()
kwargs[TrackDescriptor.SUB_INDEX_KEY] = self.getSubIndex()
kwargs[TrackDescriptor.TRACK_TYPE_KEY] = self.getType()
kwargs[TrackDescriptor.DISPOSITION_SET_KEY] = self.getDispositionSet()
kwargs[TrackDescriptor.TAGS_KEY] = self.getTags()
return TrackDescriptor(**kwargs)

View File

@@ -2,8 +2,6 @@ import click, re
from ffx.model.pattern import Pattern from ffx.model.pattern import Pattern
from .media_descriptor import MediaDescriptor
class PatternController(): class PatternController():
@@ -88,12 +86,7 @@ class PatternController():
s = self.Session() s = self.Session()
q = s.query(Pattern).filter(Pattern.id == int(patternId)) q = s.query(Pattern).filter(Pattern.id == int(patternId))
if q.count(): return q.first() if q.count() else None
# pattern = q.first()
#return self.getPatternDict(pattern)
return q.first()
else:
return None
except Exception as ex: except Exception as ex:
raise click.ClickException(f"PatternController.getPattern(): {repr(ex)}") raise click.ClickException(f"PatternController.getPattern(): {repr(ex)}")
@@ -125,11 +118,6 @@ class PatternController():
def matchFilename(self, filename): def matchFilename(self, filename):
#SEASON_PATTERN = '[sS]([0-9]+)'
#EPISODE_PATTERN = '[eE]([0-9]+)'
#result = {}
try: try:
s = self.Session() s = self.Session()
q = s.query(Pattern) q = s.query(Pattern)
@@ -141,26 +129,6 @@ class PatternController():
else: else:
return None return None
# for pattern in q.all():
#
# match = re.search(pattern.pattern, filename)
#
# if match:
#
# result['pattern_id'] = pattern.id
# result['show_id'] = pattern.show_id
#
# result['indicator'] = match.group(1)
#
# seasonMatch = re.search(SEASON_PATTERN, result['indicator'])
# if seasonMatch:
# result['season'] = int(seasonMatch.group(1))
#
# episodeMatch = re.search(EPISODE_PATTERN, result['indicator'])
# if episodeMatch:
# result['episode'] = int(episodeMatch.group(1))
except Exception as ex: except Exception as ex:
raise click.ClickException(f"PatternController.findPattern(): {repr(ex)}") raise click.ClickException(f"PatternController.findPattern(): {repr(ex)}")
finally: finally:

View File

@@ -1,12 +1,8 @@
import click import click
from textual import events
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, Static, Button
from textual.containers import Grid, Horizontal from textual.containers import Grid
from ffx.model.pattern import Pattern
from .show_controller import ShowController from .show_controller import ShowController
from .pattern_controller import PatternController from .pattern_controller import PatternController

View File

@@ -3,8 +3,8 @@ import click, re
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, Static, Button, Input, DataTable
from textual.containers import Grid, Horizontal from textual.containers import Grid
from ffx.model.show import Show from ffx.model.show import Show
from ffx.model.pattern import Pattern from ffx.model.pattern import Pattern
@@ -80,7 +80,7 @@ class PatternDetailsScreen(Screen):
self.__sc = ShowController(context = self.context) self.__sc = ShowController(context = self.context)
self.__tc = TrackController(context = self.context) self.__tc = TrackController(context = self.context)
self.__pattern = self.__pc.getPattern(patternId) if patternId is not None else None self.__pattern : Pattern = self.__pc.getPattern(patternId) if patternId is not None else None
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 {}
@@ -270,18 +270,7 @@ class PatternDetailsScreen(Screen):
subIndex = int(selected_track_data[0]) subIndex = int(selected_track_data[0])
audioTrack = self.__tc.findTrack(self.__pattern.getId(), TrackType.AUDIO, subIndex) return self.__tc.findTrack(self.__pattern.getId(), TrackType.AUDIO, subIndex).getDescriptor()
kwargs = {}
kwargs[TrackDescriptor.PATTERN_ID_KEY] = self.__pattern.getId()
kwargs[TrackDescriptor.SUB_INDEX_KEY] = subIndex
kwargs[TrackDescriptor.TRACK_TYPE_KEY] = TrackType.AUDIO
kwargs[TrackDescriptor.DISPOSITION_SET_KEY] = audioTrack.getDispositionSet()
kwargs[TrackDescriptor.TAGS_KEY] = audioTrack.getTags()
return TrackDescriptor(**kwargs)
else: else:
return None return None
@@ -290,7 +279,7 @@ class PatternDetailsScreen(Screen):
return None return None
def getSelectedSubtitleTrackDescriptor(self): def getSelectedSubtitleTrackDescriptor(self) -> TrackDescriptor:
if not self.__pattern is None: if not self.__pattern is None:
return None return None
@@ -302,22 +291,11 @@ class PatternDetailsScreen(Screen):
row_key, col_key = self.subtitleStreamsTable.coordinate_to_cell_key(self.subtitleStreamsTable.cursor_coordinate) row_key, col_key = self.subtitleStreamsTable.coordinate_to_cell_key(self.subtitleStreamsTable.cursor_coordinate)
if row_key is not None: if row_key is not None:
selected_track_data = self.subtitleStreamsTable.get_row(row_key)
selected_track_data = self.subtitleStreamsTable.get_row(row_key)
subIndex = int(selected_track_data[0]) subIndex = int(selected_track_data[0])
subtitleTrack = self.__tc.findTrack(self.__pattern.getId(), TrackType.SUBTITLE, subIndex) return self.__tc.findTrack(self.__pattern.getId(), TrackType.SUBTITLE, subIndex).getDescriptor()
kwargs = {}
kwargs[TrackDescriptor.PATTERN_ID_KEY] = self.__pattern.getId()
kwargs[TrackDescriptor.SUB_INDEX_KEY] = subIndex
kwargs[TrackDescriptor.TRACK_TYPE_KEY] = TrackType.SUBTITLE
kwargs[TrackDescriptor.DISPOSITION_SET_KEY] = subtitleTrack.getDispositionSet()
kwargs[TrackDescriptor.TAGS_KEY] = subtitleTrack.getTags()
return TrackDescriptor(**kwargs)
else: else:
return None return None

View File

@@ -1,16 +1,6 @@
import subprocess import subprocess
#class ProcessController():
#def __init__(self, commandSequence):
# self.__commandSequence = commandSequence
def executeProcess(commandSequence): def executeProcess(commandSequence):
process = subprocess.Popen(commandSequence, stdout=subprocess.PIPE, stderr=subprocess.PIPE) process = subprocess.Popen(commandSequence, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, error = process.communicate() output, error = process.communicate()
return output.decode('utf-8'), error.decode('utf-8'), process.returncode return output.decode('utf-8'), error.decode('utf-8'), process.returncode

View File

@@ -17,25 +17,10 @@ class ShowController():
s = self.Session() s = self.Session()
q = s.query(Show).filter(Show.id == showId) q = s.query(Show).filter(Show.id == showId)
showDescriptor = {}
if q.count(): if q.count():
show = q.first() show = q.first()
# showDescriptor['id'] = int(show.id)
# showDescriptor['name'] = str(show.name)
# showDescriptor['year'] = int(show.year)
#
# showDescriptor['index_season_digits'] = int(show.index_season_digits)
# showDescriptor['index_episode_digits'] = int(show.index_episode_digits)
# showDescriptor['indicator_season_digits'] = int(show.indicator_season_digits)
# showDescriptor['indicator_episode_digits'] = int(show.indicator_episode_digits)
#
# return showDescriptor
return show.getDesciptor() return show.getDesciptor()
except Exception as ex: except Exception as ex:
raise click.ClickException(f"ShowController.getShowDesciptor(): {repr(ex)}") raise click.ClickException(f"ShowController.getShowDesciptor(): {repr(ex)}")
finally: finally:

View File

@@ -1,12 +1,6 @@
import click
from textual import events
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, Static, Button
from textual.containers import Grid, Horizontal from textual.containers import Grid
from ffx.model.show import Show
from .show_controller import ShowController from .show_controller import ShowController

View File

@@ -1,10 +1,8 @@
import click import click
from textual import events
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, Static, Button, DataTable, Input
from textual.containers import Grid, Horizontal from textual.containers import Grid
from textual.widgets._data_table import CellDoesNotExist from textual.widgets._data_table import CellDoesNotExist
@@ -215,12 +213,9 @@ class ShowDetailsScreen(Screen):
# Define the columns with headers # Define the columns with headers
self.column_key_pattern = self.patternTable.add_column("Pattern", width=150) self.column_key_pattern = self.patternTable.add_column("Pattern", width=150)
#self.column_key_name = self.patternTable.add_column("Name", width=50)
#self.column_key_year = self.patternTable.add_column("Year", width=10)
self.patternTable.cursor_type = 'row' self.patternTable.cursor_type = 'row'
yield Header() yield Header()
with Grid(): with Grid():

View File

@@ -3,7 +3,7 @@ import click
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 from textual.widgets import Header, Footer, Placeholder, Label, ListView, ListItem, Static, DataTable, Button
from textual.containers import Grid, Horizontal from textual.containers import Grid
from ffx.model.show import Show from ffx.model.show import Show

View File

@@ -2,11 +2,6 @@ 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
from ffx.model.media_tag import MediaTag from ffx.model.media_tag import MediaTag
from ffx.model.track_tag import TrackTag from ffx.model.track_tag import TrackTag
@@ -19,175 +14,170 @@ class TagController():
self.Session = self.context['database']['session'] # convenience self.Session = self.context['database']['session'] # convenience
def addMediaTag(self, trackDescriptor): def updateMediaTag(self, trackId, tagKey, tagValue):
try: try:
s = self.Session() s = self.Session()
track = Track(pattern_id = int(trackDescriptor['pattern_id']), q = s.query(MediaTag).filter(MediaTag.track_id == int(trackId),
MediaTag.key == str(tagKey),
track_type = int(trackDescriptor['type'].value), MediaTag.value == str(tagValue))
tag = q.first()
sub_index = int(trackDescriptor['sub_index']), if tag:
tag.value = str(tagValue)
# language = str(trackDescriptor['language'].threeLetter()), else:
# title = str(trackDescriptor['title']), tag = MediaTag(track_id = int(trackId),
key = str(tagKey),
disposition_flags = int(TrackDisposition.toFlags(trackDescriptor['disposition_list']))) value = str(tagValue))
s.add(tag)
s.add(track)
s.commit() s.commit()
return int(tag.id)
except Exception as ex: except Exception as ex:
raise click.ClickException(f"TrackController.addTrack(): {repr(ex)}") raise click.ClickException(f"TagController.updateTrackTag(): {repr(ex)}")
finally: finally:
s.close() s.close()
def addTrackTag(self, trackDescriptor): def updateTrackTag(self, trackId, tagKey, tagValue):
try: try:
s = self.Session() s = self.Session()
track = Track(pattern_id = int(trackDescriptor['pattern_id']), q = s.query(TrackTag).filter(TrackTag.track_id == int(trackId),
TrackTag.key == str(tagKey),
track_type = int(trackDescriptor['type'].value), TrackTag.value == str(tagValue))
tag = q.first()
sub_index = int(trackDescriptor['sub_index']), if tag:
tag.value = str(tagValue)
# language = str(trackDescriptor['language'].threeLetter()), else:
# title = str(trackDescriptor['title']), tag = TrackTag(track_id = int(trackId),
key = str(tagKey),
disposition_flags = int(TrackDisposition.toFlags(trackDescriptor['disposition_list']))) value = str(tagValue))
s.add(tag)
s.add(track)
s.commit() s.commit()
return int(tag.id)
except Exception as ex: except Exception as ex:
raise click.ClickException(f"TrackController.addTrack(): {repr(ex)}") raise click.ClickException(f"TagController.updateTrackTag(): {repr(ex)}")
finally: finally:
s.close() s.close()
def findAllMediaTags(self, trackId) -> dict:
def updateTrack(self, trackId, trackDescriptor):
try: try:
s = self.Session() s = self.Session()
q = s.query(Track).filter(Track.id == int(trackId))
q = s.query(MediaTag).filter(MediaTag.track_id == int(trackId))
if q.count(): if q.count():
return {t.key:t.value for t in q.all()}
track = q.first()
track.sub_index = int(trackDescriptor['sub_index'])
# track.language = str(trackDescriptor['language'].threeLetter())
# track.title = str(trackDescriptor['title'])
track.disposition_flags = int(TrackDisposition.toFlags(trackDescriptor['disposition_list']))
s.commit()
return True
else:
return False
except Exception as ex:
raise click.ClickException(f"TrackController.addTrack(): {repr(ex)}")
finally:
s.close()
def findAllTracks(self, patternId):
try:
s = self.Session()
trackDescriptors = {}
trackDescriptors[TrackType.AUDIO.label()] = []
trackDescriptors[TrackType.SUBTITLE.label()] = []
q_audio = s.query(Track).filter(Track.pattern_id == int(patternId), Track.track_type == TrackType.AUDIO.index())
for audioTrack in q_audio.all():
trackDescriptors[TrackType.AUDIO.label()].append(audioTrack.id)
q_subtitle = s.query(Track).filter(Track.pattern_id == int(patternId), Track.track_type == TrackType.SUBTITLE.index())
for subtitleTrack in q_subtitle.all():
trackDescriptors[TrackType.SUBTITLE.label()].append(subtitleTrack.id)
return trackDescriptors
except Exception as ex:
raise click.ClickException(f"TrackController.findAllTracks(): {repr(ex)}")
finally:
s.close()
def findTrack(self, patternId, trackType : TrackType, subIndex):
try:
s = self.Session()
q = s.query(Track).filter(Track.pattern_id == int(patternId), Track.track_type == trackType.value, 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 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)
return track.getDescriptor()
else: else:
return {} return {}
except Exception as ex: except Exception as ex:
raise click.ClickException(f"TrackController.getTrackDescriptor(): {repr(ex)}") raise click.ClickException(f"TagController.findAllMediaTags(): {repr(ex)}")
finally: finally:
s.close() s.close()
def deleteTrack(self, trackId): def findAllTrackTags(self, trackId) -> dict:
try: try:
s = self.Session() s = self.Session()
q = s.query(Track).filter(Track.id == int(trackId))
q = s.query(TrackTag).filter(TrackTag.track_id == int(trackId))
if q.count():
return {t.key:t.value for t in q.all()}
else:
return {}
except Exception as ex:
raise click.ClickException(f"TagController.findAllTracks(): {repr(ex)}")
finally:
s.close()
def findMediaTag(self, trackId : int, trackKey : str) -> MediaTag:
try:
s = self.Session()
q = s.query(Track).filter(MediaTag.track_id == int(trackId), MediaTag.key == str(trackKey))
if q.count():
return q.first()
else:
return None
except Exception as ex:
raise click.ClickException(f"TagController.findMediaTag(): {repr(ex)}")
finally:
s.close()
def findTrackTag(self, trackId : int, tagKey : str) -> TrackTag:
try:
s = self.Session()
q = s.query(TrackTag).filter(TrackTag.track_id == int(trackId), TrackTag.key == str(tagKey))
if q.count():
return q.first()
else:
return None
except Exception as ex:
raise click.ClickException(f"TagController.findTrackTag(): {repr(ex)}")
finally:
s.close()
def deleteMediaTag(self, tagId) -> bool:
try:
s = self.Session()
q = s.query(MediaTag).filter(MediaTag.id == int(tagId))
if q.count(): if q.count():
trackDescriptor = self.getTrackDict(q.first()) tag = q.first()
s.delete(tag)
q_siblings = s.query(Track).filter(Track.pattern_id == int(trackDescriptor['pattern_id']), Track.track_type == trackDescriptor['type'].value).order_by(Track.sub_index)
subIndex = 0
for track in q_siblings.all():
if track.sub_index == trackDescriptor['sub_index']:
s.delete(track)
else:
track.sub_index = subIndex
subIndex += 1
s.commit() s.commit()
return True return True
return False
except Exception as ex:
raise click.ClickException(f"TagController.deleteMediaTag(): {repr(ex)}")
finally:
s.close()
def deleteTrackTag(self, tagId : int) -> bool:
if type(tagId) is not int:
raise TypeError('TagController.deleteTrackTag(): Argument tagId is required to be of type int')
try:
s = self.Session()
q = s.query(TrackTag).filter(TrackTag.id == int(tagId))
if q.count():
tag = q.first()
s.delete(tag)
s.commit()
return True
return False return False
except Exception as ex: except Exception as ex:
raise click.ClickException(f"TrackController.deleteTrack(): {repr(ex)}") raise click.ClickException(f"TagController.deleteTrackTag(): {repr(ex)}")
finally: finally:
s.close() s.close()

View File

@@ -0,0 +1,98 @@
from textual.screen import Screen
from textual.widgets import Header, Footer, Static, Button
from textual.containers import Grid
# Screen[dict[int, str, int]]
class TagDeleteScreen(Screen):
CSS = """
Grid {
grid-size: 4 9;
grid-rows: 2 2 2 2 2 2 2 2 2;
grid-columns: 30 30 30 30;
height: 100%;
width: 100%;
padding: 1;
}
Input {
border: none;
}
Button {
border: none;
}
#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;
}
"""
def __init__(self, key=None, value=None):
super().__init__()
self.__key = key
self.__value = value
def on_mount(self):
self.query_one("#keylabel", Static).update(str(self.__key))
self.query_one("#valuelabel", Static).update(str(self.__value))
def compose(self):
yield Header()
with Grid():
#1
yield Static(f"Are you sure to delete this tag ?", id="toplabel", classes="five")
#2
yield Static("Key")
yield Static(" ", id="keylabel", classes="four")
#3
yield Static("Value")
yield Static(" ", id="valuelabel", classes="four")
#4
yield Static(" ", classes="five")
#9
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":
tag = (self.__key, self.__value)
self.dismiss(tag)
if event.button.id == "cancel_button":
self.app.pop_screen()

View File

@@ -0,0 +1,121 @@
from textual.screen import Screen
from textual.widgets import Header, Footer, Static, Button, Input
from textual.containers import Grid
# Screen[dict[int, str, int]]
class TagDetailsScreen(Screen):
CSS = """
Grid {
grid-size: 5 20;
grid-rows: 2 2 2 2 2 3 2 2 2 2 2 6 2 2 6 2 2 2 2 6;
grid-columns: 25 25 25 25 225;
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;
}
"""
def __init__(self, key=None, value=None):
super().__init__()
self.__key = key
self.__value = value
def on_mount(self):
if self.__key is not None:
self.query_one("#key_input", Input).value = str(self.__key)
if self.__value is not None:
self.query_one("#value_input", Input).value = str(self.__value)
def compose(self):
yield Header()
with Grid():
# 8
yield Static("Key")
yield Input(id="key_input", classes="four")
yield Static("Value")
yield Input(id="value_input", classes="four")
# 17
yield Static(" ", classes="five")
# 18
yield Button("Save", id="save_button")
yield Button("Cancel", id="cancel_button")
# 19
yield Static(" ", classes="five")
# 20
yield Static(" ", classes="five", id="messagestatic")
yield Footer(id="footer")
def getTagFromInput(self):
tagKey = self.query_one("#key_input", Input).value
tagValue = self.query_one("#value_input", Input).value
return (tagKey, tagValue)
# 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":
self.dismiss(self.getTagFromInput())
if event.button.id == "cancel_button":
self.app.pop_screen()

View File

@@ -10,6 +10,7 @@ from .iso_language import IsoLanguage
from .track_type import TrackType from .track_type import TrackType
from ffx.model.track_tag import TrackTag from ffx.model.track_tag import TrackTag
from ffx.track_descriptor import TrackDescriptor
class TrackController(): class TrackController():
@@ -24,7 +25,6 @@ class TrackController():
try: try:
s = self.Session() s = self.Session()
track = Track(pattern_id = int(trackDescriptor.getPatternId()), track = Track(pattern_id = int(trackDescriptor.getPatternId()),
track_type = int(trackDescriptor.getType().index()), track_type = int(trackDescriptor.getType().index()),
sub_index = int(trackDescriptor.getSubIndex()), sub_index = int(trackDescriptor.getSubIndex()),
@@ -47,7 +47,10 @@ class TrackController():
s.close() s.close()
def updateTrack(self, trackId, trackDescriptor): def updateTrack(self, trackId, trackDescriptor : TrackDescriptor):
if type(trackDescriptor) is not TrackDescriptor:
raise TypeError('TrackController.updateTrack(): Argument trackDescriptor is required to be of type TrackDescriptor')
try: try:
s = self.Session() s = self.Session()
@@ -55,50 +58,37 @@ class TrackController():
if q.count(): if q.count():
track = q.first() track : Track = q.first()
track.sub_index = int(trackDescriptor['sub_index']) track.sub_index = int(trackDescriptor.getSubIndex())
# track.language = str(trackDescriptor['language'].threeLetter())
# track.title = str(trackDescriptor['title'])
track.disposition_flags = int(TrackDisposition.toFlags(trackDescriptor.getDispositionSet())) track.disposition_flags = int(TrackDisposition.toFlags(trackDescriptor.getDispositionSet()))
s.commit() descriptorTagKeys = trackDescriptor.getTags()
tagKeysInDescriptor = set(descriptorTagKeys.keys())
tagKeysInDb = {t.key for t in track.track_tags}
for k in tagKeysInDescriptor & tagKeysInDb: # to update
tags = [t for t in track.track_tags if t.key == k]
tags[0].value = descriptorTagKeys[k]
for k in tagKeysInDescriptor - tagKeysInDb: # to add
tag = TrackTag(track_id=track.id, key=k, value=descriptorTagKeys[k])
s.add(tag)
for k in tagKeysInDb - tagKeysInDescriptor: # to remove
tags = [t for t in track.track_tags if t.key == k]
s.delete(tags[0])
s.commit()
return True return True
else: else:
return False return False
except Exception as ex: except Exception as ex:
raise click.ClickException(f"TrackController.addTrack(): {repr(ex)}") raise click.ClickException(f"TrackController.updateTrack(): {repr(ex)}")
finally: finally:
s.close() s.close()
#
# def findAllTracks(self, patternId):
#
# try:
# s = self.Session()
#
# trackDescriptors = {}
# trackDescriptors[TrackType.AUDIO.label()] = []
# trackDescriptors[TrackType.SUBTITLE.label()] = []
#
# q_audio = s.query(Track).filter(Track.pattern_id == int(patternId), Track.track_type == TrackType.AUDIO.index())
# for audioTrack in q_audio.all():
# trackDescriptors[TrackType.AUDIO.label()].append(audioTrack.id)
#
# q_subtitle = s.query(Track).filter(Track.pattern_id == int(patternId), Track.track_type == TrackType.SUBTITLE.index())
# for subtitleTrack in q_subtitle.all():
# trackDescriptors[TrackType.SUBTITLE.label()].append(subtitleTrack.id)
#
# return trackDescriptors
#
# except Exception as ex:
# raise click.ClickException(f"TrackController.findAllTracks(): {repr(ex)}")
# finally:
# s.close()
#
def findAudioTracks(self, patternId): def findAudioTracks(self, patternId):
try: try:
@@ -126,15 +116,13 @@ class TrackController():
s.close() s.close()
def findTrack(self, patternId : int, trackType : TrackType, subIndex : int): def findTrack(self, patternId : int, trackType : TrackType, subIndex : int) -> Track:
try: try:
s = self.Session() s = self.Session()
q = s.query(Track).filter(Track.pattern_id == int(patternId), Track.track_type == trackType.index(), Track.sub_index == int(subIndex)) q = s.query(Track).filter(Track.pattern_id == int(patternId), Track.track_type == trackType.index(), Track.sub_index == int(subIndex))
if q.count(): if q.count():
#track = q.first()
#return int(track.id)
return q.first() return q.first()
else: else:
return None return None
@@ -151,7 +139,6 @@ class TrackController():
if q.count(): if q.count():
#trackDescriptor = self.getTrackDict(q.first())
track = q.first() track = q.first()
q_siblings = s.query(Track).filter(Track.pattern_id == track.getPatternId(), Track.track_type == track.getType().index()).order_by(Track.sub_index) q_siblings = s.query(Track).filter(Track.pattern_id == track.getPatternId(), Track.track_type == track.getType().index()).order_by(Track.sub_index)

View File

@@ -3,14 +3,12 @@ import click
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, Static, Button
from textual.containers import Grid, Horizontal from textual.containers import Grid
from ffx.model.pattern import Pattern from ffx.model.pattern import Pattern
from ffx.track_descriptor import TrackDescriptor from ffx.track_descriptor import TrackDescriptor
# from .show_controller import ShowController
# from .pattern_controller import PatternController
from .track_controller import TrackController from .track_controller import TrackController
from .track_type import TrackType from .track_type import TrackType

View File

@@ -6,6 +6,7 @@ from .track_disposition import TrackDisposition
class TrackDescriptor(): class TrackDescriptor():
ID_KEY = 'id'
INDEX_KEY = 'index' INDEX_KEY = 'index'
SUB_INDEX_KEY = 'sub_index' SUB_INDEX_KEY = 'sub_index'
PATTERN_ID_KEY = 'pattern_id' PATTERN_ID_KEY = 'pattern_id'
@@ -20,6 +21,13 @@ class TrackDescriptor():
def __init__(self, **kwargs): def __init__(self, **kwargs):
if TrackDescriptor.ID_KEY in kwargs.keys():
if type(kwargs[TrackDescriptor.ID_KEY]) is not int:
raise TypeError(f"TrackDesciptor.__init__(): Argument {TrackDescriptor.ID_KEY} is required to be of type int")
self.__trackId = kwargs[TrackDescriptor.ID_KEY]
else:
self.__trackId = -1
if TrackDescriptor.PATTERN_ID_KEY in kwargs.keys(): if TrackDescriptor.PATTERN_ID_KEY in kwargs.keys():
if type(kwargs[TrackDescriptor.PATTERN_ID_KEY]) is not int: if type(kwargs[TrackDescriptor.PATTERN_ID_KEY]) is not int:
raise TypeError(f"TrackDesciptor.__init__(): Argument {TrackDescriptor.PATTERN_ID_KEY} is required to be of type int") raise TypeError(f"TrackDesciptor.__init__(): Argument {TrackDescriptor.PATTERN_ID_KEY} is required to be of type int")
@@ -134,6 +142,9 @@ class TrackDescriptor():
return None return None
def getId(self):
return self.__trackId
def getPatternId(self): def getPatternId(self):
return self.__patternId return self.__patternId

View File

@@ -3,16 +3,14 @@ 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, Checkbox, SelectionList, Select from textual.widgets import Header, Footer, Static, Button, SelectionList, Select, DataTable, Input
from textual.containers import Grid, Horizontal from textual.containers import Grid
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 .tag_controller import TagController
from .track_type import TrackType from .track_type import TrackType
@@ -22,6 +20,11 @@ from .audio_layout import AudioLayout
from .track_descriptor import TrackDescriptor 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]] # Screen[dict[int, str, int]]
class TrackDetailsScreen(Screen): class TrackDetailsScreen(Screen):
@@ -86,35 +89,37 @@ class TrackDetailsScreen(Screen):
self.__tc = TrackController(context = self.context) self.__tc = TrackController(context = self.context)
self.__pc = PatternController(context = self.context) self.__pc = PatternController(context = self.context)
self.__tac = TagController(context = self.context)
INDEX_KEY = 'index'
SUB_INDEX_KEY = 'sub_index'
PATTERN_ID_KEY = 'pattern_id'
TRACK_TYPE_KEY = 'track_type'
DISPOSITION_SET_KEY = 'disposition_set'
TAGS_KEY = 'tags'
AUDIO_LAYOUT_KEY = 'audio_layout'
# if trackDescriptor is None:
# self.__trackDescriptor = TrackDescriptor(index=,
# sub_index=
# pattern_id=patternId,
# track_type=trackType)
# else:
self.__isNew = trackDescriptor is None self.__isNew = trackDescriptor is None
if self.__isNew: if self.__isNew:
self.__trackType = trackType self.__trackType = trackType
self.__subIndex = subIndex self.__subIndex = subIndex
self.__trackDescriptor = None self.__trackDescriptor : TrackDescriptor = None
self.__pattern = self.__pc.getPattern(patternId) if patternId is not None else {} self.__pattern : Pattern = self.__pc.getPattern(patternId) if patternId is not None else {}
else: else:
self.__trackType = trackDescriptor.getType() self.__trackType = trackDescriptor.getType()
self.__subIndex = trackDescriptor.getSubIndex() self.__subIndex = trackDescriptor.getSubIndex()
self.__trackDescriptor = trackDescriptor self.__trackDescriptor : TrackDescriptor = trackDescriptor
self.__pattern = self.__pc.getPattern(self.__trackDescriptor.getPatternId()) 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): def on_mount(self):
@@ -122,12 +127,9 @@ class TrackDetailsScreen(Screen):
if self.__pattern is not None: if self.__pattern is not None:
self.query_one("#patternlabel", Static).update(self.__pattern.getPattern()) self.query_one("#patternlabel", Static).update(self.__pattern.getPattern())
if self.__subIndex is not None: if self.__subIndex is not None:
self.query_one("#subindexlabel", Static).update(str(self.__subIndex)) self.query_one("#subindexlabel", Static).update(str(self.__subIndex))
for d in TrackDisposition: for d in TrackDisposition:
dispositionIsSet = (self.__trackDescriptor is not None dispositionIsSet = (self.__trackDescriptor is not None
@@ -140,6 +142,7 @@ class TrackDetailsScreen(Screen):
self.query_one("#language_select", Select).value = self.__trackDescriptor.getLanguage().label() self.query_one("#language_select", Select).value = self.__trackDescriptor.getLanguage().label()
self.query_one("#title_input", Input).value = self.__trackDescriptor.getTitle() self.query_one("#title_input", Input).value = self.__trackDescriptor.getTitle()
self.updateTags()
def compose(self): def compose(self):
@@ -193,8 +196,9 @@ class TrackDetailsScreen(Screen):
# 11 # 11
yield Static("Stream tags") yield Static("Stream tags")
yield Static(" ", classes="two") yield Static(" ")
yield Button("Add", id="button_add_stream_tag") yield Button("Add", id="button_add_stream_tag")
yield Button("Edit", id="button_edit_stream_tag")
yield Button("Delete", id="button_delete_stream_tag") yield Button("Delete", id="button_delete_stream_tag")
# 12 # 12
yield self.trackTagsTable yield self.trackTagsTable
@@ -243,13 +247,15 @@ class TrackDetailsScreen(Screen):
trackTags = {} trackTags = {}
language = self.query_one("#language_select", Select).value language = self.query_one("#language_select", Select).value
# raise click.ClickException(f"language={language}")
if language: if language:
trackTags['language'] = IsoLanguage.find(language).threeLetter() trackTags['language'] = IsoLanguage.find(language).threeLetter()
title = self.query_one("#title_input", Input).value title = self.query_one("#title_input", Input).value
if title: if title:
trackTags['title'] = title trackTags['title'] = title
kwargs[TrackDescriptor.TAGS_KEY] = trackTags
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]) 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.DISPOSITION_SET_KEY] = TrackDisposition.toSet(dispositionFlags)
@@ -259,6 +265,31 @@ class TrackDetailsScreen(Screen):
return TrackDescriptor(**kwargs) 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 # Event handler for button press
def on_button_pressed(self, event: Button.Pressed) -> None: def on_button_pressed(self, event: Button.Pressed) -> None:
@@ -297,14 +328,50 @@ class TrackDetailsScreen(Screen):
else: else:
trackId = self.__tc.findTrack(self.__pattern.getId(), self.__trackType, self.__subIndex) track = self.__tc.findTrack(self.__pattern.getId(), self.__trackType, self.__subIndex)
if self.__tc.updateTrack(trackId, trackDescriptor): if self.__tc.updateTrack(track.getId(), trackDescriptor):
self.dismiss(trackDescriptor) self.dismiss(trackDescriptor)
else: else:
self.app.pop_screen() self.app.pop_screen()
if event.button.id == "cancel_button": if event.button.id == "cancel_button":
self.app.pop_screen() 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_add_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()