diff --git a/bin/ffx/media_descriptor.py b/bin/ffx/media_descriptor.py index 9f4af71..634f388 100644 --- a/bin/ffx/media_descriptor.py +++ b/bin/ffx/media_descriptor.py @@ -58,9 +58,21 @@ class MediaDescriptor(): kwargs[MediaDescriptor.TRACK_DESCRIPTOR_LIST_KEY] = [] + subIndexCounters = {} + for streamObj in streamData: - if TrackType.fromLabel(streamObj[MediaDescriptor.FFPROBE_CODEC_TYPE_KEY]) != TrackType.UNKNOWN: - kwargs[MediaDescriptor.TRACK_DESCRIPTOR_LIST_KEY].append(TrackDescriptor.fromFfprobe(streamObj)) + + ffprobeCodecType = streamObj[MediaDescriptor.FFPROBE_CODEC_TYPE_KEY] + trackType = TrackType.fromLabel(ffprobeCodecType) + + if trackType != TrackType.UNKNOWN: + + if trackType not in subIndexCounters.keys(): + subIndexCounters[trackType] = 0 + + kwargs[MediaDescriptor.TRACK_DESCRIPTOR_LIST_KEY].append(TrackDescriptor.fromFfprobe(streamObj, subIndex=subIndexCounters[trackType])) + subIndexCounters[trackType] += 1 + return cls(**kwargs) diff --git a/bin/ffx/media_details_screen.py b/bin/ffx/media_details_screen.py index 331f459..a487ddf 100644 --- a/bin/ffx/media_details_screen.py +++ b/bin/ffx/media_details_screen.py @@ -17,6 +17,7 @@ from .track_details_screen import TrackDetailsScreen from .track_delete_screen import TrackDeleteScreen from ffx.track_type import TrackType +from ffx.model.track import Track from ffx.track_disposition import TrackDisposition from ffx.track_descriptor import TrackDescriptor @@ -33,8 +34,8 @@ class MediaDetailsScreen(Screen): CSS = """ Grid { - grid-size: 4 8; - grid-rows: 8 2 2 8 2 8 2 8; + grid-size: 4 9; + grid-rows: 8 2 2 2 8 2 8 2 8; grid-columns: 25 125 10 75; height: 100%; width: 100%; @@ -84,6 +85,10 @@ class MediaDetailsScreen(Screen): row-span: 8; /* tint: magenta 40%; */ } + + #pattern_input { + tint: red 40%; + } """ def __init__(self, patternId = None, showId = None): @@ -111,7 +116,8 @@ class MediaDetailsScreen(Screen): self.__mediaDescriptor = self.__mediaFileProperties.getMediaDescriptor() self.__mediaFilenamePattern = self.__mediaFileProperties.getPattern() - self.__storedMediaFilenamePattern = self.__mediaFilenamePattern.getMediaDescriptor() + + self.__storedMediaFilenamePattern = self.__mediaFilenamePattern.getMediaDescriptor() if self.__mediaFilenamePattern is not None else None # raise click.ClickException(f"diff {self.__mediaDescriptor.compare(self.__storedMediaFilenamePattern)}") @@ -178,6 +184,22 @@ class MediaDetailsScreen(Screen): # self.subtitleStreamsTable.add_row(*map(str, row)) + def getRowIndexFromShowId(self, showId : int) -> int: + """Find the index of the row where the value in the specified column matches the target_value.""" + + for rowKey, row in self.showsTable.rows.items(): # dict[RowKey, Row] + + rowData = self.showsTable.get_row(rowKey) + + try: + if showId == int(rowData[0]): + return int(self.showsTable.get_row_index(rowKey)) + except: + continue + + return None + + def on_mount(self): row = (' ', '', ' ') # Convert each element to a string before adding @@ -187,15 +209,87 @@ class MediaDetailsScreen(Screen): row = (int(show.id), show.name, show.year) # Convert each element to a string before adding self.showsTable.add_row(*map(str, row)) -# 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 is not None: -# -# self.query_one("#pattern_input", Input).value = str(self.__pattern.getPattern()) -# -# self.updateAudioTracks() -# self.updateSubtitleTracks() + for mediaTagKey, mediaTagValue in self.__mediaDescriptor.getTags().items(): + row = (mediaTagKey, mediaTagValue) # Convert each element to a string before adding + self.mediaTagsTable.add_row(*map(str, row)) + + self.updateAudioTracks(self.__mediaDescriptor.getAudioTracks()) + self.updateSubtitleTracks(self.__mediaDescriptor.getSubtitleTracks()) + + if self.__mediaFilenamePattern is not None: + + showIdentifier = self.__mediaFilenamePattern.getShowId() + showRowIndex = self.getRowIndexFromShowId(showIdentifier) + if showRowIndex is not None: + self.showsTable.move_cursor(row=showRowIndex) + + + self.query_one("#pattern_input", Input).value = self.__mediaFilenamePattern.getPattern() + + mediaDifferences = self.__mediaDescriptor.compare(self.__mediaFilenamePattern.getMediaDescriptor()) + + if 'tags' in mediaDifferences.keys(): + + mediaTags = self.__mediaDescriptor.getTags() + + if 'added' in mediaDifferences['tags'].keys(): + + for addedTagKey in mediaDifferences['tags']['added']: + + row = (f"added media tag: key='{addedTagKey}' value='{mediaTags[addedTagKey]}'",) + self.differencesTable.add_row(*map(str, row)) + + if 'tracks' in mediaDifferences.keys(): + + tracks = self.__mediaDescriptor.getAllTracks() + + if 'removed' in mediaDifferences['tracks'].keys(): + + for removedTrackIndex in mediaDifferences['tracks']['removed']: + + removedTrack : Track = tracks[removedTrackIndex] + + row = (f"removed {removedTrack.getType().label()} track: index={removedTrackIndex} subIndex={removedTrack.getSubIndex()} lang={removedTrack.getLanguage().threeLetter()}",) + self.differencesTable.add_row(*map(str, row)) + + + + def updateAudioTracks(self, audioTracks): + + self.audioStreamsTable.clear() + + for at in audioTracks: + + dispoSet = at.getDispositionSet() + + row = (at.getSubIndex(), + " ", + at.getLanguage().label(), + at.getTitle(), + 'Yes' if TrackDisposition.DEFAULT in dispoSet else 'No', + 'Yes' if TrackDisposition.FORCED in dispoSet else 'No') + + self.audioStreamsTable.add_row(*map(str, row)) + + def updateSubtitleTracks(self, subtitleTracks): + + self.subtitleStreamsTable.clear() + + for st in subtitleTracks: + + dispoSet = st.getDispositionSet() + + row = (st.getSubIndex(), + " ", + st.getLanguage().label(), + st.getTitle(), + 'Yes' if TrackDisposition.DEFAULT in dispoSet else 'No', + 'Yes' if TrackDisposition.FORCED in dispoSet else 'No') + + self.subtitleStreamsTable.add_row(*map(str, row)) + + + def compose(self): @@ -205,21 +299,21 @@ class MediaDetailsScreen(Screen): self.showsTable = DataTable() # Define the columns with headers - self.column_key_id = self.showsTable.add_column("ID", width=10) - self.column_key_name = self.showsTable.add_column("Name", width=50) - self.column_key_year = self.showsTable.add_column("Year", width=10) + self.column_key_show_id = self.showsTable.add_column("ID", width=10) + self.column_key_show_name = self.showsTable.add_column("Name", width=50) + self.column_key_show_year = self.showsTable.add_column("Year", width=10) self.showsTable.cursor_type = 'row' - self.trackTagsTable = DataTable() + self.mediaTagsTable = DataTable() # Define the columns with headers - self.column_key_track_tag_key = self.trackTagsTable.add_column("Key", width=10) - self.column_key_track_tag_value = self.trackTagsTable.add_column("Value", width=30) + self.column_key_track_tag_key = self.mediaTagsTable.add_column("Key", width=20) + self.column_key_track_tag_value = self.mediaTagsTable.add_column("Value", width=90) - self.trackTagsTable.cursor_type = 'row' + self.mediaTagsTable.cursor_type = 'row' @@ -250,12 +344,12 @@ class MediaDetailsScreen(Screen): # Create the DataTable widget - self.diffsTable = DataTable(id='differences-table') # classes="triple" + self.differencesTable = DataTable(id='differences-table') # classes="triple" # Define the columns with headers - self.column_key_differences = self.diffsTable.add_column("Differences", width=70) + self.column_key_differences = self.differencesTable.add_column("Differences", width=70) - self.diffsTable.cursor_type = 'row' + self.differencesTable.cursor_type = 'row' @@ -267,34 +361,39 @@ class MediaDetailsScreen(Screen): yield Static("Show") yield self.showsTable yield Static(" ") - yield self.diffsTable + yield self.differencesTable # 2 + yield Static(" ") + yield Button("Substitute") + yield Static(" ") + + # 3 yield Static("Pattern") - yield Input(type="text") + yield Input(type="text", id='pattern_input') yield Static(" ") - # 3 + # 4 yield Static(" ", classes="three") - # 4 + # 5 yield Static("Media Tags") - yield self.trackTagsTable + yield self.mediaTagsTable yield Static(" ") - # 5 + # 6 yield Static(" ", classes="three") - # 6 + # 7 yield Static("Audio Streams") yield self.audioStreamsTable yield Static(" ") - # 7 + # 8 yield Static(" ", classes="three") - # 8 + # 9 yield Static("Subtitle Streams") yield self.subtitleStreamsTable yield Static(" ") diff --git a/bin/ffx/model/track.py b/bin/ffx/model/track.py index 49d405b..d94f23b 100644 --- a/bin/ffx/model/track.py +++ b/bin/ffx/model/track.py @@ -38,11 +38,6 @@ class Track(Base): pattern_id = Column(Integer, ForeignKey('patterns.id', ondelete="CASCADE")) pattern = relationship('Pattern', back_populates='tracks') - - # language = Column(String) # IsoLanguage threeLetter - # title = Column(String) - - track_tags = relationship('TrackTag', back_populates='track', cascade="all, delete", lazy="joined") @@ -55,10 +50,6 @@ class Track(Base): if trackType is not None: self.track_type = int(trackType) - # language = kwargs.pop('language', None) - # if language is not None: - # self.language = str(language.threeLetter()) - dispositionSet = kwargs.pop(TrackDescriptor.DISPOSITION_SET_KEY, set()) self.disposition_flags = int(TrackDisposition.toFlags(dispositionSet)) @@ -155,12 +146,12 @@ class Track(Base): def getType(self): return TrackType.fromIndex(self.track_type) - # def getIndex(self): - # return int(self.index) + def getIndex(self): + return int(self.index) if self.index is not None else -1 def getSubIndex(self): - return int(self.sub_index) - + return int(self.sub_index) if self.sub_index is not None else -1 + def getLanguage(self): tags = {t.key:t.value for t in self.track_tags} return IsoLanguage.findThreeLetter(tags['language']) if 'language' in tags.keys() else IsoLanguage.UNDEFINED @@ -183,7 +174,7 @@ class Track(Base): 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.getIndex() kwargs[TrackDescriptor.SUB_INDEX_KEY] = self.getSubIndex() kwargs[TrackDescriptor.TRACK_TYPE_KEY] = self.getType() diff --git a/bin/ffx/track_descriptor.py b/bin/ffx/track_descriptor.py index 51e63f4..3796957 100644 --- a/bin/ffx/track_descriptor.py +++ b/bin/ffx/track_descriptor.py @@ -18,6 +18,7 @@ class TrackDescriptor(): TAGS_KEY = 'tags' AUDIO_LAYOUT_KEY = 'audio_layout' + FFPROBE_INDEX_KEY = 'index' FFPROBE_DISPOSITION_KEY = 'disposition' FFPROBE_TAGS_KEY = 'tags' @@ -85,7 +86,7 @@ class TrackDescriptor(): @classmethod - def fromFfprobe(cls, streamObj): + def fromFfprobe(cls, streamObj, subIndex : int = -1): """Processes ffprobe stream data as array with elements according to the following example { "index": 4, @@ -133,6 +134,10 @@ class TrackDescriptor(): if trackType != TrackType.UNKNOWN: kwargs = {} + + kwargs[TrackDescriptor.INDEX_KEY] = streamObj[TrackDescriptor.FFPROBE_INDEX_KEY] if TrackDescriptor.FFPROBE_INDEX_KEY in streamObj.keys() else -1 + kwargs[TrackDescriptor.SUB_INDEX_KEY] = subIndex + kwargs[TrackDescriptor.TRACK_TYPE_KEY] = trackType kwargs[TrackDescriptor.DISPOSITION_SET_KEY] = {t for d in (k for (k,v) in streamObj[TrackDescriptor.FFPROBE_DISPOSITION_KEY].items() if v) if (t := TrackDisposition.find(d)) if t is not None} if TrackDescriptor.FFPROBE_DISPOSITION_KEY in streamObj.keys() else set()