From 0a026afba4ad26d6c2369ae85907a23ffd0ff70f Mon Sep 17 00:00:00 2001 From: Maveno Date: Sat, 16 Nov 2024 14:14:46 +0100 Subject: [PATCH] inc --- .gitignore | 1 + bin/ffx/database.py | 2 + bin/ffx/model/conversions/__init__.py | 0 bin/ffx/model/conversions/conversion.py | 47 ++ bin/ffx/model/conversions/conversion_2_3.py | 17 + bin/ffx/model/conversions/conversion_3_4.py | 7 + bin/ffx/model/pattern.py | 6 +- bin/ffx/model/shifted_season.py | 52 +++ bin/ffx/model/show.py | 5 +- bin/ffx/shifted_season_controller.py | 156 +++++++ bin/ffx/shifted_season_delete_screen.py | 111 +++++ bin/ffx/shifted_season_details_screen.py | 490 ++++++++++++++++++++ bin/ffx/show_details_screen.py | 60 ++- 13 files changed, 943 insertions(+), 11 deletions(-) create mode 100644 bin/ffx/model/conversions/__init__.py create mode 100644 bin/ffx/model/conversions/conversion.py create mode 100644 bin/ffx/model/conversions/conversion_2_3.py create mode 100644 bin/ffx/model/conversions/conversion_3_4.py create mode 100644 bin/ffx/model/shifted_season.py create mode 100644 bin/ffx/shifted_season_controller.py create mode 100644 bin/ffx/shifted_season_delete_screen.py create mode 100644 bin/ffx/shifted_season_details_screen.py diff --git a/.gitignore b/.gitignore index 4284f13..4577e72 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ junk/ ansible/inventory/hawaii.yml ansible/inventory/peppermint.yml ffx_test_report.log +bin/conversiontest.py diff --git a/bin/ffx/database.py b/bin/ffx/database.py index e1c39db..3391d70 100644 --- a/bin/ffx/database.py +++ b/bin/ffx/database.py @@ -4,6 +4,8 @@ from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from ffx.model.show import Base, Show + +from ffx.model.shifted_season import ShiftedSeason from ffx.model.pattern import Pattern from ffx.model.track import Track diff --git a/bin/ffx/model/conversions/__init__.py b/bin/ffx/model/conversions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bin/ffx/model/conversions/conversion.py b/bin/ffx/model/conversions/conversion.py new file mode 100644 index 0000000..927b2e8 --- /dev/null +++ b/bin/ffx/model/conversions/conversion.py @@ -0,0 +1,47 @@ +import os, sys, importlib, inspect, glob, re + +from ffx.configuration_controller import ConfigurationController +from ffx.database import databaseContext + +from sqlalchemy import Engine +from sqlalchemy.orm import sessionmaker + + +class Conversion(): + + def __init__(self): + + self._context = {} + self._context['config'] = ConfigurationController() + + self._context['database'] = databaseContext(databasePath=self._context['config'].getDatabaseFilePath()) + + self.__databaseSession: sessionmaker = self._context['database']['session'] + self.__databaseEngine: Engine = self._context['database']['engine'] + + + @staticmethod + def list(): + + basePath = os.path.dirname(__file__) + + filenamePattern = re.compile("conversion_([0-9]+)_([0-9]+)\\.py") + + filenameList = [os.path.basename(fp) for fp in glob.glob(f"{ basePath }/*.py") if fp != __file__] + + versionTupleList = [(fm.group(1), fm.group(2)) for fn in filenameList if (fm := filenamePattern.search(fn))] + + return versionTupleList + + + @staticmethod + def getClassReference(versionFrom, versionTo): + importlib.import_module(f"ffx.model.conversions.conversion_{ versionFrom }_{ versionTo }") + for name, obj in inspect.getmembers(sys.modules[f"ffx.model.conversions.conversion_{ versionFrom }_{ versionTo }"]): + #HINT: Excluding DispositionCombination as it seems to be included by import (?) + if inspect.isclass(obj) and name != 'Conversion' and name.startswith('Conversion'): + return obj + + @staticmethod + def getAllClassReferences(): + return [Conversion.getClassReference(verFrom, verTo) for verFrom, verTo in Conversion.list()] diff --git a/bin/ffx/model/conversions/conversion_2_3.py b/bin/ffx/model/conversions/conversion_2_3.py new file mode 100644 index 0000000..3661a23 --- /dev/null +++ b/bin/ffx/model/conversions/conversion_2_3.py @@ -0,0 +1,17 @@ +import os, sys, importlib, inspect, glob, re + +from .conversion import Conversion + + +class Conversion_2_3(Conversion): + + def __init__(self): + super().__init__() + + def applyConversion(self): + + s = self.__databaseSession() + e = self.__databaseEngine + + with e.connect() as c: + c.execute("ALTER TABLE user ADD COLUMN email VARCHAR(255)") diff --git a/bin/ffx/model/conversions/conversion_3_4.py b/bin/ffx/model/conversions/conversion_3_4.py new file mode 100644 index 0000000..f1c1541 --- /dev/null +++ b/bin/ffx/model/conversions/conversion_3_4.py @@ -0,0 +1,7 @@ +import os, sys, importlib, inspect, glob, re + +from .conversion import Conversion + + +class Conversion_3_4(Conversion): + pass diff --git a/bin/ffx/model/pattern.py b/bin/ffx/model/pattern.py index 92a7978..ac1584a 100644 --- a/bin/ffx/model/pattern.py +++ b/bin/ffx/model/pattern.py @@ -1,14 +1,14 @@ import click -from sqlalchemy import create_engine, Column, Integer, String, ForeignKey -from sqlalchemy.orm import relationship, sessionmaker, Mapped, backref +from sqlalchemy import Column, Integer, String, ForeignKey +from sqlalchemy.orm import relationship from .show import Base, Show -from .track import Track from ffx.media_descriptor import MediaDescriptor from ffx.show_descriptor import ShowDescriptor + class Pattern(Base): __tablename__ = 'patterns' diff --git a/bin/ffx/model/shifted_season.py b/bin/ffx/model/shifted_season.py new file mode 100644 index 0000000..ffead60 --- /dev/null +++ b/bin/ffx/model/shifted_season.py @@ -0,0 +1,52 @@ +import click + +from sqlalchemy import Column, Integer, ForeignKey +from sqlalchemy.orm import relationship + +from .show import Base, Show + + +class ShiftedSeason(Base): + + __tablename__ = 'shifted_seasons' + + # v1.x + id = Column(Integer, primary_key=True) + + + # v2.0 + # id: Mapped[int] = mapped_column(Integer, primary_key=True) + # pattern: Mapped[str] = mapped_column(String, nullable=False) + + # v1.x + show_id = Column(Integer, ForeignKey('shows.id', ondelete="CASCADE")) + show = relationship(Show, back_populates='shifted_seasons', lazy='joined') + + # v2.0 + # show_id: Mapped[int] = mapped_column(ForeignKey("shows.id", ondelete="CASCADE")) + # show: Mapped["Show"] = relationship(back_populates="patterns") + + + src_season = Column(Integer) + + first_episode = Column(Integer, default = -1) + last_episode = Column(Integer, default = -1) + + season_offset = Column(Integer, default = 0) + episode_offset = Column(Integer, default = 0) + + + def getSeasonOffset(self): + return self.season_offset + + def getEpisodeOffset(self): + return self.episode_offset + + + def getShifted(self, season, episode): + + if season == self.src_season and episode >= self.first_episode and episode <= self.last_episode: + return season + self.season_offset, episode + self.episode_offset + + else: + return season, episode diff --git a/bin/ffx/model/show.py b/bin/ffx/model/show.py index 7d82e39..af157f3 100644 --- a/bin/ffx/model/show.py +++ b/bin/ffx/model/show.py @@ -4,9 +4,9 @@ from sqlalchemy.orm import relationship, declarative_base, sessionmaker from ffx.show_descriptor import ShowDescriptor - Base = declarative_base() + class Show(Base): """ relationship(argument, opt1, opt2, ...) @@ -38,6 +38,9 @@ class Show(Base): # v2.0 # patterns: Mapped[List["Pattern"]] = relationship(back_populates="show", cascade="all, delete") + shifted_seasons = relationship('ShiftedSeason', back_populates='show', cascade="all, delete") + + index_season_digits = Column(Integer, default=ShowDescriptor.DEFAULT_INDEX_SEASON_DIGITS) index_episode_digits = Column(Integer, default=ShowDescriptor.DEFAULT_INDEX_EPISODE_DIGITS) indicator_season_digits = Column(Integer, default=ShowDescriptor.DEFAULT_INDICATOR_SEASON_DIGITS) diff --git a/bin/ffx/shifted_season_controller.py b/bin/ffx/shifted_season_controller.py new file mode 100644 index 0000000..c4192dc --- /dev/null +++ b/bin/ffx/shifted_season_controller.py @@ -0,0 +1,156 @@ +import click, re + +from ffx.model.shifted_season import ShiftedSeason + + +class ShiftedSeasonController(): + + def __init__(self, context): + + self.context = context + self.Session = self.context['database']['session'] # convenience + + + def addPattern(self, patternDescriptor): + + try: + + s = self.Session() + q = s.query(Pattern).filter(Pattern.show_id == int(patternDescriptor['show_id']), + Pattern.pattern == str(patternDescriptor['pattern'])) + + if not q.count(): + pattern = Pattern(show_id = int(patternDescriptor['show_id']), + pattern = str(patternDescriptor['pattern'])) + s.add(pattern) + s.commit() + return pattern.getId() + else: + return 0 + + except Exception as ex: + raise click.ClickException(f"PatternController.addPattern(): {repr(ex)}") + finally: + s.close() + + + def updatePattern(self, patternId, patternDescriptor): + + try: + s = self.Session() + q = s.query(Pattern).filter(Pattern.id == int(patternId)) + + if q.count(): + + pattern = q.first() + + pattern.show_id = int(patternDescriptor['show_id']) + pattern.pattern = str(patternDescriptor['pattern']) + + s.commit() + return True + + else: + return False + + except Exception as ex: + raise click.ClickException(f"PatternController.updatePattern(): {repr(ex)}") + finally: + s.close() + + + + def findPattern(self, patternDescriptor): + + try: + s = self.Session() + q = s.query(Pattern).filter(Pattern.show_id == int(patternDescriptor['show_id']), Pattern.pattern == str(patternDescriptor['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 getPattern(self, patternId : int): + + if type(patternId) is not int: + raise ValueError(f"PatternController.getPattern(): Argument patternId is required to be of type int") + + try: + s = self.Session() + q = s.query(Pattern).filter(Pattern.id == int(patternId)) + + return q.first() if q.count() else None + + except Exception as ex: + raise click.ClickException(f"PatternController.getPattern(): {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() + + + def matchFilename(self, filename : str) -> dict: + """Returns dict {'match': , 'pattern': } or empty dict of no pattern was found""" + + try: + s = self.Session() + q = s.query(Pattern) + + matchResult = {} + + for pattern in q.all(): + patternMatch = re.search(str(pattern.pattern), str(filename)) + if patternMatch is not None: + matchResult['match'] = patternMatch + matchResult['pattern'] = pattern + + return matchResult + + except Exception as ex: + raise click.ClickException(f"PatternController.matchFilename(): {repr(ex)}") + finally: + s.close() + +# def getMediaDescriptor(self, context, patternId): +# +# try: +# s = self.Session() +# q = s.query(Pattern).filter(Pattern.id == int(patternId)) +# +# if q.count(): +# return q.first().getMediaDescriptor(context) +# else: +# return None +# +# except Exception as ex: +# raise click.ClickException(f"PatternController.getMediaDescriptor(): {repr(ex)}") +# finally: +# s.close() \ No newline at end of file diff --git a/bin/ffx/shifted_season_delete_screen.py b/bin/ffx/shifted_season_delete_screen.py new file mode 100644 index 0000000..542fbf6 --- /dev/null +++ b/bin/ffx/shifted_season_delete_screen.py @@ -0,0 +1,111 @@ +import click + +from textual.screen import Screen +from textual.widgets import Header, Footer, Static, Button +from textual.containers import Grid + +from .show_controller import ShowController +from .pattern_controller import PatternController + +from ffx.model.pattern import Pattern + + +# Screen[dict[int, str, int]] +class ShiftedSeasonDeleteScreen(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.__patternId = patternId + self.__pattern: Pattern = self.__pc.getPattern(patternId) if patternId is not None else {} + self.__showDescriptor = self.__sc.getShowDescriptor(showId) if showId is not None else {} + + + def on_mount(self): + if self.__showDescriptor: + self.query_one("#showlabel", Static).update(f"{self.__showDescriptor.getId()} - {self.__showDescriptor.getName()} ({self.__showDescriptor.getYear()})") + if not self.__pattern is None: + self.query_one("#patternlabel", Static).update(str(self.__pattern.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.__patternId is None: + raise click.ClickException('PatternDeleteScreen.on_button_pressed(): pattern id is undefined') + + if self.__pc.deletePattern(self.__patternId): + self.dismiss(self.__pattern) + + else: + #TODO: Meldung + self.app.pop_screen() + + if event.button.id == "cancel_button": + self.app.pop_screen() + diff --git a/bin/ffx/shifted_season_details_screen.py b/bin/ffx/shifted_season_details_screen.py new file mode 100644 index 0000000..c173177 --- /dev/null +++ b/bin/ffx/shifted_season_details_screen.py @@ -0,0 +1,490 @@ +import click, re +from typing import List + +from textual import events +from textual.app import App, ComposeResult +from textual.screen import Screen +from textual.widgets import Header, Footer, Static, Button, Input, DataTable +from textual.containers import Grid + +from ffx.model.show import Show +from ffx.model.pattern import Pattern +from ffx.model.track import Track + +from .pattern_controller import PatternController +from .show_controller import ShowController +from .track_controller import TrackController +from .tag_controller import TagController + +from .track_details_screen import TrackDetailsScreen +from .track_delete_screen import TrackDeleteScreen + +from .tag_details_screen import TagDetailsScreen +from .tag_delete_screen import TagDeleteScreen + +from ffx.track_type import TrackType + +from ffx.track_disposition import TrackDisposition +from ffx.track_descriptor import TrackDescriptor + +from textual.widgets._data_table import CellDoesNotExist + +from ffx.file_properties import FileProperties +from ffx.iso_language import IsoLanguage +from ffx.audio_layout import AudioLayout + + +# Screen[dict[int, str, int]] +class ShiftedSeasonDetailsScreen(Screen): + + CSS = """ + + Grid { + grid-size: 3 10; + grid-rows: 2 2 2 2 2 2 2 2 2 2; + grid-columns: 40 40 40; + height: 100%; + width: 100%; + padding: 1; + } + + Input { + border: none; + } + Button { + border: none; + } + + DataTable { + min-height: 6; + } + + #toplabel { + height: 1; + + } + + + .two { + column-span: 3; + } + + .three { + column-span: 3; + } + + .four { + column-span: 4; + } + .five { + column-span: 5; + } + .six { + column-span: 6; + } + .seven { + column-span: 7; + } + + .box { + height: 100%; + border: solid green; + } + + .yellow { + tint: yellow 40%; + } + """ + + def __init__(self, patternId = None, showId = None): + super().__init__() + + self.context = self.app.getContext() + self.Session = self.context['database']['session'] # convenience + + self.__pc = PatternController(context = self.context) + self.__sc = ShowController(context = self.context) + self.__tc = TrackController(context = self.context) + self.__tac = TagController(context = self.context) + + self.__pattern : Pattern = self.__pc.getPattern(patternId) if patternId is not None else None + self.__showDescriptor = self.__sc.getShowDescriptor(showId) if showId is not None else None + + +# #TODO: per controller +# def loadTracks(self, show_id): +# +# try: +# +# tracks = {} +# tracks['audio'] = {} +# tracks['subtitle'] = {} +# +# s = self.Session() +# q = s.query(Pattern).filter(Pattern.show_id == int(show_id)) +# +# return [{'id': int(p.id), 'pattern': p.pattern} for p in q.all()] +# +# except Exception as ex: +# raise click.ClickException(f"loadTracks(): {repr(ex)}") +# finally: +# s.close() + + +# def updateTracks(self): +# +# self.tracksTable.clear() +# +# if self.__pattern is not None: +# +# tracks = self.__tc.findTracks(self.__pattern.getId()) +# +# typeCounter = {} +# +# tr: Track +# for tr in tracks: +# +# td : TrackDescriptor = tr.getDescriptor(self.context) +# +# trackType = td.getType() +# if not trackType in typeCounter.keys(): +# typeCounter[trackType] = 0 +# +# dispoSet = td.getDispositionSet() +# +# trackLanguage = td.getLanguage() +# audioLayout = td.getAudioLayout() +# row = (td.getIndex(), +# trackType.label(), +# typeCounter[trackType], +# td.getCodec(), +# audioLayout.label() if trackType == TrackType.AUDIO +# and audioLayout != AudioLayout.LAYOUT_UNDEFINED else ' ', +# trackLanguage.label() if trackLanguage != IsoLanguage.UNDEFINED else ' ', +# td.getTitle(), +# 'Yes' if TrackDisposition.DEFAULT in dispoSet else 'No', +# 'Yes' if TrackDisposition.FORCED in dispoSet else 'No', +# td.getSourceIndex()) +# +# self.tracksTable.add_row(*map(str, row)) +# +# typeCounter[trackType] += 1 +# +# +# def swapTracks(self, trackIndex1: int, trackIndex2: int): +# +# ti1 = int(trackIndex1) +# ti2 = int(trackIndex2) +# +# siblingDescriptors: List[TrackDescriptor] = self.__tc.findSiblingDescriptors(self.__pattern.getId()) +# +# numSiblings = len(siblingDescriptors) +# +# if ti1 < 0 or ti1 >= numSiblings: +# raise ValueError(f"PatternDetailsScreen.swapTracks(): trackIndex1 ({ti1}) is out of range ({numSiblings})") +# +# if ti2 < 0 or ti2 >= numSiblings: +# raise ValueError(f"PatternDetailsScreen.swapTracks(): trackIndex2 ({ti2}) is out of range ({numSiblings})") +# +# sibling1 = siblingDescriptors[trackIndex1] +# sibling2 = siblingDescriptors[trackIndex2] +# +# # raise click.ClickException(f"siblings id1={sibling1.getId()} id2={sibling2.getId()}") +# +# subIndex2 = sibling2.getSubIndex() +# +# sibling2.setIndex(sibling1.getIndex()) +# sibling2.setSubIndex(sibling1.getSubIndex()) +# +# sibling1.setIndex(trackIndex2) +# sibling1.setSubIndex(subIndex2) +# +# if not self.__tc.updateTrack(sibling1.getId(), sibling1): +# raise click.ClickException('Update sibling1 failed') +# if not self.__tc.updateTrack(sibling2.getId(), sibling2): +# raise click.ClickException('Update sibling2 failed') +# +# self.updateTracks() +# +# +# def updateTags(self): +# +# self.tagsTable.clear() +# +# if self.__pattern is not None: +# +# # raise click.ClickException(f"patternid={self.__pattern.getId()}") # 1 +# +# tags = self.__tac.findAllMediaTags(self.__pattern.getId()) +# +# #raise click.ClickException(f"tags={tags}") # encoder:blah +# +# for tagKey, tagValue in tags.items(): +# row = (tagKey, tagValue) +# self.tagsTable.add_row(*map(str, row)) + + +# def on_mount(self): +# +# if not self.__showDescriptor is None: +# self.query_one("#showlabel", Static).update(f"{self.__showDescriptor.getId()} - {self.__showDescriptor.getName()} ({self.__showDescriptor.getYear()})") +# +# if self.__pattern is not None: +# +# self.query_one("#pattern_input", Input).value = str(self.__pattern.getPattern()) +# +# self.updateTags() +# self.updateTracks() + + def compose(self): + + yield Header() + + with Grid(): + + # 1 + yield Static("Edit shifted season" if self.__pattern is not None else "New shifted season", id="toplabel", classes="three") + + # 2 + yield Static(" ", classes="three") + + # 3 + yield Static("Original season") + yield Input(id="input_original_season", classes="two") + + # 4 + yield Static("First Episode") + yield Input(id="input_first_episode", classes="two") + + # 5 + yield Static("Last Episode") + yield Input(id="imput_last_episode", classes="two") + + # 6 + yield Static("Season offset") + yield Input(id="input_season_offset", classes="two") + + # 7 + yield Static("Episode offset") + yield Input(id="input_episode_offset", classes="two") + + # 8 + yield Static(" ", classes="three") + + # 9 + yield Button("Save", id="save_button") + yield Button("Cancel", id="cancel_button") + yield Static(" ") + + # 10 + yield Static(" ", classes="three") + + yield Footer() + + +# def getPatternFromInput(self): +# return str(self.query_one("#pattern_input", Input).value) + + + +# def getSelectedTrackDescriptor(self): +# +# if not self.__pattern: +# return None +# +# try: +# +# # Fetch the currently selected row when 'Enter' is pressed +# #selected_row_index = self.table.cursor_row +# row_key, col_key = self.tracksTable.coordinate_to_cell_key(self.tracksTable.cursor_coordinate) +# +# if row_key is not None: +# selected_track_data = self.tracksTable.get_row(row_key) +# +# trackIndex = int(selected_track_data[0]) +# trackSubIndex = int(selected_track_data[2]) +# +# return self.__tc.getTrack(self.__pattern.getId(), trackIndex).getDescriptor(self.context, subIndex=trackSubIndex) +# +# else: +# return None +# +# except CellDoesNotExist: +# return None + + + +# def getSelectedTag(self): +# +# try: +# +# # Fetch the currently selected row when 'Enter' is pressed +# #selected_row_index = self.table.cursor_row +# row_key, col_key = self.tagsTable.coordinate_to_cell_key(self.tagsTable.cursor_coordinate) +# +# if row_key is not None: +# selected_tag_data = self.tagsTable.get_row(row_key) +# +# tagKey = str(selected_tag_data[0]) +# tagValue = str(selected_tag_data[1]) +# +# return tagKey, tagValue +# +# else: +# return None +# +# except CellDoesNotExist: +# return None + + + + # Event handler for button press + def on_button_pressed(self, event: Button.Pressed) -> None: + pass + + # Check if the button pressed is the one we are interested in +# if event.button.id == "save_button": +# +# patternDescriptor = {} +# patternDescriptor['show_id'] = self.__showDescriptor.getId() +# patternDescriptor['pattern'] = self.getPatternFromInput() +# +# if self.__pattern is not None: +# +# if self.__pc.updatePattern(self.__pattern.getId(), patternDescriptor): +# self.dismiss(patternDescriptor) +# else: +# #TODO: Meldung +# self.app.pop_screen() +# +# else: +# patternId = self.__pc.addPattern(patternDescriptor) +# if patternId: +# self.dismiss(patternDescriptor) +# else: +# #TODO: Meldung +# self.app.pop_screen() +# +# +# if event.button.id == "cancel_button": +# self.app.pop_screen() +# +# +# # Save pattern when just created before adding streams +# if self.__pattern is not None: +# +# numTracks = len(self.tracksTable.rows) +# +# if event.button.id == "button_add_track": +# self.app.push_screen(TrackDetailsScreen(patternId = self.__pattern.getId(), index = numTracks), self.handle_add_track) +# +# selectedTrack = self.getSelectedTrackDescriptor() +# if selectedTrack is not None: +# if event.button.id == "button_edit_track": +# self.app.push_screen(TrackDetailsScreen(trackDescriptor = selectedTrack), self.handle_edit_track) +# if event.button.id == "button_delete_track": +# self.app.push_screen(TrackDeleteScreen(trackDescriptor = selectedTrack), self.handle_delete_track) + + +# if event.button.id == "button_add_tag": +# if self.__pattern is not None: +# self.app.push_screen(TagDetailsScreen(), self.handle_update_tag) +# +# if event.button.id == "button_edit_tag": +# tagKey, tagValue = self.getSelectedTag() +# self.app.push_screen(TagDetailsScreen(key=tagKey, value=tagValue), self.handle_update_tag) +# +# if event.button.id == "button_delete_tag": +# tagKey, tagValue = self.getSelectedTag() +# self.app.push_screen(TagDeleteScreen(key=tagKey, value=tagValue), self.handle_delete_tag) + +# +# if event.button.id == "pattern_button": +# +# pattern = self.query_one("#pattern_input", Input).value +# +# patternMatch = re.search(FileProperties.SE_INDICATOR_PATTERN, pattern) +# +# if patternMatch: +# self.query_one("#pattern_input", Input).value = pattern.replace(patternMatch.group(1), +# FileProperties.SE_INDICATOR_PATTERN) +# +# +# if event.button.id == "button_track_up": +# +# selectedTrackDescriptor = self.getSelectedTrackDescriptor() +# selectedTrackIndex = selectedTrackDescriptor.getIndex() +# +# if selectedTrackIndex > 0 and selectedTrackIndex < self.tracksTable.row_count: +# correspondingTrackIndex = selectedTrackIndex - 1 +# self.swapTracks(selectedTrackIndex, correspondingTrackIndex) +# +# +# if event.button.id == "button_track_down": +# +# selectedTrackDescriptor = self.getSelectedTrackDescriptor() +# selectedTrackIndex = selectedTrackDescriptor.getIndex() +# +# if selectedTrackIndex >= 0 and selectedTrackIndex < (self.tracksTable.row_count - 1): +# correspondingTrackIndex = selectedTrackIndex + 1 +# self.swapTracks(selectedTrackIndex, correspondingTrackIndex) +# +# +# def handle_add_track(self, trackDescriptor : TrackDescriptor): +# +# dispoSet = trackDescriptor.getDispositionSet() +# trackType = trackDescriptor.getType() +# index = trackDescriptor.getIndex() +# subIndex = trackDescriptor.getSubIndex() +# language = trackDescriptor.getLanguage() +# title = trackDescriptor.getTitle() +# +# row = (index, +# trackType.label(), +# subIndex, +# " ", +# language.label(), +# title, +# 'Yes' if TrackDisposition.DEFAULT in dispoSet else 'No', +# 'Yes' if TrackDisposition.FORCED in dispoSet else 'No') +# +# self.tracksTable.add_row(*map(str, row)) +# +# +# def handle_edit_track(self, trackDescriptor : TrackDescriptor): +# +# try: +# +# row_key, col_key = self.tracksTable.coordinate_to_cell_key(self.tracksTable.cursor_coordinate) +# +# self.tracksTable.update_cell(row_key, self.column_key_track_audio_layout, trackDescriptor.getAudioLayout().label()) +# self.tracksTable.update_cell(row_key, self.column_key_track_language, trackDescriptor.getLanguage().label()) +# self.tracksTable.update_cell(row_key, self.column_key_track_title, trackDescriptor.getTitle()) +# self.tracksTable.update_cell(row_key, self.column_key_track_default, 'Yes' if TrackDisposition.DEFAULT in trackDescriptor.getDispositionSet() else 'No') +# self.tracksTable.update_cell(row_key, self.column_key_track_forced, 'Yes' if TrackDisposition.FORCED in trackDescriptor.getDispositionSet() else 'No') +# +# except CellDoesNotExist: +# pass +# +# +# def handle_delete_track(self, trackDescriptor : TrackDescriptor): +# self.updateTracks() +# +# +# +# def handle_update_tag(self, tag): +# +# if self.__pattern is None: +# raise click.ClickException(f"PatternDetailsScreen.handle_update_tag: pattern not set") +# +# if self.__tac.updateMediaTag(self.__pattern.getId(), tag[0], tag[1]) is not None: +# self.updateTags() +# +# def handle_delete_tag(self, tag): +# +# if self.__pattern is None: +# raise click.ClickException(f"PatternDetailsScreen.handle_delete_tag: pattern not set") +# +# if self.__tac.deleteMediaTagByKey(self.__pattern.getId(), tag[0]): +# self.updateTags() +# \ No newline at end of file diff --git a/bin/ffx/show_details_screen.py b/bin/ffx/show_details_screen.py index 3a38569..ffff715 100644 --- a/bin/ffx/show_details_screen.py +++ b/bin/ffx/show_details_screen.py @@ -19,6 +19,8 @@ from .tmdb_controller import TmdbController from .show_descriptor import ShowDescriptor +from .shifted_season_details_screen import ShiftedSeasonDetailsScreen + from .helper import filterFilename @@ -28,8 +30,8 @@ class ShowDetailsScreen(Screen): CSS = """ Grid { - grid-size: 5 14; - grid-rows: 2 2 2 2 2 2 2 2 2 2 2 6 2 2; + grid-size: 5 16; + grid-rows: 2 2 2 2 2 2 2 2 2 2 2 9 2 9 2 2; grid-columns: 30 30 30 30 30; height: 100%; width: 100%; @@ -44,7 +46,7 @@ class ShowDetailsScreen(Screen): } DataTable { column-span: 2; - min-height: 5; + min-height: 8; } #toplabel { @@ -160,7 +162,7 @@ class ShowDetailsScreen(Screen): def action_add_pattern(self): if not self.__showDescriptor is None: - self.app.push_screen(PatternDetailsScreen(showId = self.__showDescriptor.getId()), self.handle_add_pattern) # <- + self.app.push_screen(PatternDetailsScreen(showId = self.__showDescriptor.getId()), self.handle_add_pattern) def handle_add_pattern(self, screenResult): @@ -228,6 +230,18 @@ class ShowDetailsScreen(Screen): self.patternTable.cursor_type = 'row' + + self.shiftedSeasonsTable = DataTable(classes="five") + + self.column_key_source_season = self.shiftedSeasonsTable.add_column("Src Season", width=30) + self.column_key_first_episode = self.shiftedSeasonsTable.add_column("First Episode", width=30) + self.column_key_last_episode = self.shiftedSeasonsTable.add_column("Last Episode", width=30) + self.column_key_season_offset = self.shiftedSeasonsTable.add_column("Season Offset", width=30) + self.column_key_episode_offset = self.shiftedSeasonsTable.add_column("Episode Offset", width=30) + + self.shiftedSeasonsTable.cursor_type = 'row' + + yield Header() with Grid(): @@ -275,14 +289,29 @@ class ShowDetailsScreen(Screen): yield Static(" ", classes="five") # 11 - yield Static("File patterns", classes="five") + yield Static("Shifted seasons", classes="two") + + if self.__showDescriptor is not None: + yield Button("Add", id="button_add_shifted_season") + yield Button("Edit", id="button_edit_shifted_season") + yield Button("Delete", id="button_delete_shifted_season") + else: + yield Static(" ") + yield Static(" ") + yield Static(" ") + # 12 - yield self.patternTable + yield self.shiftedSeasonsTable # 13 + yield Static("File patterns", classes="five") + # 14 + yield self.patternTable + + # 15 yield Static(" ", classes="five") - # 14 + # 16 yield Button("Save", id="save_button") yield Button("Cancel", id="cancel_button") @@ -358,3 +387,20 @@ class ShowDetailsScreen(Screen): self.query_one("#name_input", Input).value = filterFilename(showName) self.query_one("#year_input", Input).value = str(showYear) + + + if event.button.id == "button_add_shifted_season": + if not self.__showDescriptor is None: + self.app.push_screen(ShiftedSeasonDetailsScreen(showId = self.__showDescriptor.getId()), self.handle_add_shifted_season) + + if event.button.id == "button_edit_shifted_season": + pass + + if event.button.id == "button_delete_shifted_season": + pass + + + def handle_add_shifted_season(self, screenResult): + pass + # pattern = (screenResult['pattern'],) + # self.patternTable.add_row(*map(str, pattern))