media/track comparison inc

click-textual
Maveno 12 months ago
parent aea8c7e9ea
commit aaa6b2cabc

@ -0,0 +1,6 @@
from ffx.helper import dictDiff
a = {'name': 'yolo', 'mass': 56}
b = {'name': 'zolo', 'mass': 58}
print(dictDiff(a, b))

@ -6,9 +6,6 @@ from ffx.ffx_app import FfxApp
from ffx.database import databaseContext from ffx.database import databaseContext
from ffx.media_descriptor import MediaDescriptor
from ffx.file_properties import FileProperties
VERSION='0.1.0' VERSION='0.1.0'
@ -251,32 +248,12 @@ def help():
@click.argument('filename', nargs=1) @click.argument('filename', nargs=1)
def inspect(ctx, filename): def inspect(ctx, filename):
fp = FileProperties(ctx.obj, filename) ctx.obj['command'] = 'inspect'
md = fp.getMediaDescriptor() ctx.obj['arguments'] = {}
ctx.obj['arguments']['filename'] = filename
click.echo('\nFile properties:\n')
click.echo(md.getTags())
for at in md.getAudioTracks():
click.echo(f"Audio: {at.getLanguage()} {'|'.join([f"{k}={v}" for (k,v) in at.getTags().items()])}")
for st in md.getSubtitleTracks():
click.echo(f"Subtitle: {st.getLanguage()} {'|'.join([f"{k}={v}" for (k,v) in st.getTags().items()])}")
click.echo('\nRecognized pattern:\n')
click.echo(f"Show Id: {fp.getShowId()}") app = FfxApp(ctx.obj)
app.run()
pattern = fp.getPattern()
click.echo(f"Pattern: {pattern} id={pattern.id} show={pattern.show.name} year={pattern.show.year}")
db_md = pattern.getMediaDescriptor()
click.echo(f"md from db: {db_md} tags={'|'.join([f"{k}={v}" for (k,v) in db_md.getTags().items()])} a_tracks={db_md.getAudioTracks()} s_tracks={db_md.getSubtitleTracks()}")
atrack0 = db_md.getAudioTracks()[0]
click.echo(f"a track0: lang={atrack0.getLanguage()} title={atrack0.getTitle()}")
@ -334,6 +311,8 @@ def shows(ctx):
# if 'database' not in ctx.obj.keys(): # if 'database' not in ctx.obj.keys():
# ctx.obj['database'] = databaseContext() # ctx.obj['database'] = databaseContext()
ctx.obj['command'] = 'shows'
app = FfxApp(ctx.obj) app = FfxApp(ctx.obj)
app.run() app.run()

@ -1,6 +1,7 @@
from textual.app import App from textual.app import App
from .shows_screen import ShowsScreen from .shows_screen import ShowsScreen
from .media_details_screen import MediaDetailsScreen
class FfxApp(App): class FfxApp(App):
@ -21,9 +22,17 @@ class FfxApp(App):
def on_mount(self) -> None: def on_mount(self) -> None:
self.push_screen(ShowsScreen())
if 'command' in self.context.keys():
if self.context['command'] == 'shows':
self.push_screen(ShowsScreen())
if self.context['command'] == 'inspect':
self.push_screen(MediaDetailsScreen())
def getContext(self): def getContext(self):
"""Data 'output' method""" """Data 'output' method"""
return self.context return self.context

@ -0,0 +1,36 @@
def dictDiff(a : dict, b : dict):
a_keys = set(a.keys())
b_keys = set(b.keys())
a_only = a_keys - b_keys
b_only = b_keys - a_keys
a_b = a_keys & b_keys
changed = {k for k in a_b if a[k] != b[k]}
diffResult = {}
if a_only:
diffResult['removed'] = a_only
if b_only:
diffResult['added'] = b_only
if changed:
diffResult['changed'] = changed
return diffResult
def setDiff(a : set, b : set) -> set:
a_only = a - b
b_only = b - a
diffResult = {}
if a_only:
diffResult['removed'] = a_only
if b_only:
diffResult['added'] = b_only
return diffResult

@ -3,6 +3,8 @@ from typing import List
from ffx.track_type import TrackType from ffx.track_type import TrackType
from ffx.track_descriptor import TrackDescriptor from ffx.track_descriptor import TrackDescriptor
from ffx.helper import dictDiff
class MediaDescriptor(): class MediaDescriptor():
"""This class represents the structural content of a media file including streams and metadata""" """This class represents the structural content of a media file including streams and metadata"""
@ -49,8 +51,6 @@ class MediaDescriptor():
@classmethod @classmethod
def fromFfprobe(cls, formatData, streamData): def fromFfprobe(cls, formatData, streamData):
#trackDescriptors = {}
kwargs = {} kwargs = {}
if MediaDescriptor.FFPROBE_TAGS_KEY in formatData.keys(): if MediaDescriptor.FFPROBE_TAGS_KEY in formatData.keys():
@ -59,22 +59,9 @@ class MediaDescriptor():
kwargs[MediaDescriptor.TRACK_DESCRIPTOR_LIST_KEY] = [] kwargs[MediaDescriptor.TRACK_DESCRIPTOR_LIST_KEY] = []
for streamObj in streamData: for streamObj in streamData:
#trackType = TrackType.fromLabel(streamObj[MediaDescriptor.FFPROBE_CODEC_TYPE_KEY])
# if trackType != TrackType.UNKNOWN:
#
# if trackType.label() not in trackDescriptors.keys():
# trackDescriptors[trackType.label()] = []
#
# trackDescriptors[trackType.label()].append(TrackDescriptor.fromFfprobe(streamObj))
if TrackType.fromLabel(streamObj[MediaDescriptor.FFPROBE_CODEC_TYPE_KEY]) != TrackType.UNKNOWN: if TrackType.fromLabel(streamObj[MediaDescriptor.FFPROBE_CODEC_TYPE_KEY]) != TrackType.UNKNOWN:
kwargs[MediaDescriptor.TRACK_DESCRIPTOR_LIST_KEY].append(TrackDescriptor.fromFfprobe(streamObj)) kwargs[MediaDescriptor.TRACK_DESCRIPTOR_LIST_KEY].append(TrackDescriptor.fromFfprobe(streamObj))
# kwargs[MediaDescriptor.TRACK_DESCRIPTOR_LIST_KEY] = trackDescriptors
return cls(**kwargs) return cls(**kwargs)
@ -82,10 +69,58 @@ class MediaDescriptor():
return self.__mediaTags return self.__mediaTags
def getAllTracks(self) -> List[TrackDescriptor]:
return self.__trackDescriptors
def getAudioTracks(self) -> List[TrackDescriptor]: def getAudioTracks(self) -> List[TrackDescriptor]:
#return self.__trackDescriptors[TrackType.AUDIO.label()] if TrackType.AUDIO.label() in self.__trackDescriptors.keys() else []
return [d for d in self.__trackDescriptors if d.getType() == TrackType.AUDIO] return [d for d in self.__trackDescriptors if d.getType() == TrackType.AUDIO]
def getSubtitleTracks(self) -> List[TrackDescriptor]: def getSubtitleTracks(self) -> List[TrackDescriptor]:
#return self.__trackDescriptors[TrackType.SUBTITLE.label()] if TrackType.SUBTITLE.label() in self.__trackDescriptors.keys() else []
return [d for d in self.__trackDescriptors if d.getType() == TrackType.SUBTITLE] return [d for d in self.__trackDescriptors if d.getType() == TrackType.SUBTITLE]
def compare(self, vsMediaDescriptor):
mediaTagsResult = dictDiff(vsMediaDescriptor.getTags(), self.getTags())
compareResult = {}
if mediaTagsResult:
compareResult['tags'] = mediaTagsResult
vsTracks = vsMediaDescriptor.getAllTracks()
tracks = self.getAllTracks()
numVsTracks = len(vsTracks)
numTracks = len(tracks)
maxNumOfTracks = max(numVsTracks, numTracks)
trackCompareResult = {}
for trackIndex in range(maxNumOfTracks):
if trackIndex > numVsTracks - 1:
if 'removed' not in trackCompareResult.keys():
trackCompareResult['removed'] = set()
trackCompareResult['removed'].add(trackIndex)
continue
if trackIndex > numTracks - 1:
if 'added' not in trackCompareResult.keys():
trackCompareResult['added'] = {}
trackCompareResult['added'][trackIndex] = vsTracks[trackIndex]
continue
trackResult = tracks[trackIndex].compare(vsTracks[trackIndex])
if trackResult:
if 'changed' not in trackCompareResult.keys():
trackCompareResult['changed'] = {}
trackCompareResult['changed'][trackIndex] = trackResult
if trackCompareResult:
compareResult['tracks'] = trackCompareResult
return compareResult

@ -0,0 +1,456 @@
import os, click, re
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 .pattern_controller import PatternController
from .show_controller import ShowController
from .track_controller import TrackController
from .track_details_screen import TrackDetailsScreen
from .track_delete_screen import TrackDeleteScreen
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.media_descriptor import MediaDescriptor
from ffx.file_properties import FileProperties
# Screen[dict[int, str, int]]
class MediaDetailsScreen(Screen):
CSS = """
Grid {
grid-size: 5 12;
grid-rows: 2 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;
}
"""
def __init__(self, patternId = None, showId = None):
super().__init__()
self.context = self.app.getContext()
self.Session = self.context['database']['session'] # convenience
if not 'command' in self.context.keys() or self.context['command'] != 'inspect':
raise click.ClickException(f"MediaDetailsScreen.__init__(): Can only perform command 'inspect'")
if not 'arguments' in self.context.keys() or not 'filename' in self.context['arguments'].keys() or not self.context['arguments']['filename']:
raise click.ClickException(f"MediaDetailsScreen.__init__(): Argument 'filename' is required to be provided for command 'inspect'")
self.__mediaFilename = self.context['arguments']['filename']
if not os.path.isfile(self.__mediaFilename):
raise click.ClickException(f"MediaDetailsScreen.__init__(): Media file {self.__mediaFilename} does not exist")
self.__mediaFileProperties = FileProperties(self.context, self.__mediaFilename)
self.__mediaDescriptor = self.__mediaFileProperties.getMediaDescriptor()
self.__mediaFilenamePattern = self.__mediaFileProperties.getPattern()
self.__storedMediaFilenamePattern = self.__mediaFilenamePattern.getMediaDescriptor()
raise click.ClickException(f"diff {self.__mediaDescriptor.compare(self.__storedMediaFilenamePattern)}")
# 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 updateAudioTracks(self):
#
# self.audioStreamsTable.clear()
#
# if self.__pattern is not None:
#
# audioTracks = self.__tc.findAudioTracks(self.__pattern.getId())
#
# 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):
#
# self.subtitleStreamsTable.clear()
#
# if self.__pattern is not None:
#
# subtitleTracks = self.__tc.findSubtitleTracks(self.__pattern.getId())
#
# 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 on_mount(self):
pass
# 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()
def compose(self):
# self.audioStreamsTable = DataTable(classes="five")
#
# # Define the columns with headers
# self.column_key_audio_subid = self.audioStreamsTable.add_column("Subindex", width=20)
# self.column_key_audio_layout = self.audioStreamsTable.add_column("Layout", width=20)
# self.column_key_audio_language = self.audioStreamsTable.add_column("Language", width=20)
# self.column_key_audio_title = self.audioStreamsTable.add_column("Title", width=30)
# self.column_key_audio_default = self.audioStreamsTable.add_column("Default", width=10)
# self.column_key_audio_forced = self.audioStreamsTable.add_column("Forced", 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=20)
# self.column_key_subtitle_spacer = self.subtitleStreamsTable.add_column(" ", width=20)
# self.column_key_subtitle_language = self.subtitleStreamsTable.add_column("Language", width=20)
# self.column_key_subtitle_title = self.subtitleStreamsTable.add_column("Title", width=30)
# self.column_key_subtitle_default = self.subtitleStreamsTable.add_column("Default", width=10)
# self.column_key_subtitle_forced = self.subtitleStreamsTable.add_column("Forced", width=10)
#
# self.subtitleStreamsTable.cursor_type = 'row'
yield Header()
# with Grid():
# 1
# yield Static("Edit filename pattern" if self.__pattern is not None else "New filename pattern", id="toplabel")
# yield Input(type="text", id="pattern_input", classes="four")
#
# # 2
# yield Static("from show")
# yield Static("", id="showlabel", classes="three")
# yield Button("Substitute pattern", id="patternbutton")
#
# # 3
# yield Static(" ", classes="five")
# # 4
# yield Static(" ", classes="five")
#
# # 5
# yield Static("Audio streams")
# yield Static(" ")
#
# if self.__pattern is not None:
# yield Button("Add", id="button_add_audio_stream")
# yield Button("Edit", id="button_edit_audio_stream")
# yield Button("Delete", id="button_delete_audio_stream")
# else:
# yield Static("")
# yield Static("")
# yield Static("")
# # 6
# yield self.audioStreamsTable
#
# # 7
# yield Static(" ", classes="five")
#
# # 8
# yield Static("Subtitle streams")
# yield Static(" ")
#
# if self.__pattern is not None:
# yield Button("Add", id="button_add_subtitle_stream")
# yield Button("Edit", id="button_edit_subtitle_stream")
# yield Button("Delete", id="button_delete_subtitle_stream")
# else:
# yield Static("")
# yield Static("")
# yield Static("")
# # 9
# 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)
# def getSelectedAudioTrackDescriptor(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.audioStreamsTable.coordinate_to_cell_key(self.audioStreamsTable.cursor_coordinate)
#
# if row_key is not None:
# selected_track_data = self.audioStreamsTable.get_row(row_key)
#
# subIndex = int(selected_track_data[0])
#
# return self.__tc.findTrack(self.__pattern.getId(), TrackType.AUDIO, subIndex).getDescriptor()
#
# else:
# return None
#
# except CellDoesNotExist:
# return None
#
# def getSelectedSubtitleTrackDescriptor(self) -> TrackDescriptor:
#
# if not self.__pattern is None:
# return None
#
# try:
#
# # Fetch the currently selected row when 'Enter' is pressed
# #selected_row_index = self.table.cursor_row
# row_key, col_key = self.subtitleStreamsTable.coordinate_to_cell_key(self.subtitleStreamsTable.cursor_coordinate)
#
# if row_key is not None:
#
# selected_track_data = self.subtitleStreamsTable.get_row(row_key)
# subIndex = int(selected_track_data[0])
#
# return self.__tc.findTrack(self.__pattern.getId(), TrackType.SUBTITLE, subIndex).getDescriptor()
#
# 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.show_obj['id']
# 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:
# if self.__pc.addPattern(patternDescriptor):
# 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:
#
# if event.button.id == "button_add_audio_stream":
# self.app.push_screen(TrackDetailsScreen(trackType = TrackType.AUDIO, patternId = self.__pattern.getId(), subIndex = len(self.audioStreamsTable.rows)), self.handle_add_track)
#
# selectedAudioTrack = self.getSelectedAudioTrackDescriptor()
# if selectedAudioTrack is not None:
# if event.button.id == "button_edit_audio_stream":
#
# self.app.push_screen(TrackDetailsScreen(trackDescriptor = selectedAudioTrack), self.handle_edit_track)
# if event.button.id == "button_delete_audio_stream":
# self.app.push_screen(TrackDeleteScreen(trackDescriptor = selectedAudioTrack), self.handle_delete_track)
#
# if event.button.id == "button_add_subtitle_stream":
# self.app.push_screen(TrackDetailsScreen(trackType = TrackType.SUBTITLE, patternId = self.__pattern.getId(), subIndex = len(self.subtitleStreamsTable.rows)), self.handle_add_track)
#
# selectedSubtitleTrack = self.getSelectedSubtitleTrackDescriptor()
# if selectedSubtitleTrack is not None:
# if event.button.id == "button_edit_subtitle_stream":
# self.app.push_screen(TrackDetailsScreen(trackDescriptor = selectedSubtitleTrack), self.handle_edit_track)
# if event.button.id == "button_delete_subtitle_stream":
# self.app.push_screen(TrackDeleteScreen(trackDescriptor = selectedSubtitleTrack), self.handle_delete_track)
#
# if event.button.id == "patternbutton":
#
# INDICATOR_PATTERN = '([sS][0-9]+[eE][0-9]+)'
#
# pattern = self.query_one("#pattern_input", Input).value
#
# patternMatch = re.search(INDICATOR_PATTERN, pattern)
#
# if patternMatch:
# self.query_one("#pattern_input", Input).value = pattern.replace(patternMatch.group(1), INDICATOR_PATTERN)
# def handle_add_track(self, trackDescriptor):
#
# dispoSet = trackDescriptor.getDispositionSet()
# trackType = trackDescriptor.getType()
# subIndex = trackDescriptor.getSubIndex()
# language = trackDescriptor.getLanguage()
# title = trackDescriptor.getTitle()
#
# if trackType == TrackType.AUDIO:
#
# row = (subIndex,
# " ",
# language.label(),
# title,
# 'Yes' if TrackDisposition.DEFAULT in dispoSet else 'No',
# 'Yes' if TrackDisposition.FORCED in dispoSet else 'No')
#
# self.audioStreamsTable.add_row(*map(str, row))
#
# if trackType == TrackType.SUBTITLE:
#
# row = (subIndex,
# " ",
# language.label(),
# title,
# 'Yes' if TrackDisposition.DEFAULT in dispoSet else 'No',
# 'Yes' if TrackDisposition.FORCED in dispoSet else 'No')
#
# self.subtitleStreamsTable.add_row(*map(str, row))
# def handle_edit_track(self, trackDescriptor : TrackDescriptor):
#
# try:
# if trackDescriptor.getType() == TrackType.AUDIO:
#
# row_key, col_key = self.audioStreamsTable.coordinate_to_cell_key(self.audioStreamsTable.cursor_coordinate)
#
# self.audioStreamsTable.update_cell(row_key, self.column_key_audio_language, trackDescriptor.getLanguage().label())
# self.audioStreamsTable.update_cell(row_key, self.column_key_audio_title, trackDescriptor.getTitle())
# self.audioStreamsTable.update_cell(row_key, self.column_key_audio_default, 'Yes' if TrackDisposition.DEFAULT in trackDescriptor.getDispositionSet() else 'No')
# self.audioStreamsTable.update_cell(row_key, self.column_key_audio_forced, 'Yes' if TrackDisposition.FORCED in trackDescriptor.getDispositionSet() else 'No')
#
# if trackDescriptor.getType() == TrackType.SUBTITLE:
#
# row_key, col_key = self.subtitleStreamsTable.coordinate_to_cell_key(self.subtitleStreamsTable.cursor_coordinate)
#
# self.subtitleStreamsTable.update_cell(row_key, self.column_key_subtitle_language, trackDescriptor.getLanguage().label())
# self.subtitleStreamsTable.update_cell(row_key, self.column_key_subtitle_title, trackDescriptor.getTitle())
# self.subtitleStreamsTable.update_cell(row_key, self.column_key_subtitle_default, 'Yes' if TrackDisposition.DEFAULT in trackDescriptor.getDispositionSet() else 'No')
# self.subtitleStreamsTable.update_cell(row_key, self.column_key_subtitle_forced, 'Yes' if TrackDisposition.FORCED in trackDescriptor.getDispositionSet() else 'No')
#
# except CellDoesNotExist:
# pass
# def handle_delete_track(self, trackDescriptor : TrackDescriptor):
#
# try:
# if trackDescriptor.getType() == TrackType.AUDIO:
# self.updateAudioTracks()
#
# if trackDescriptor.getType() == TrackType.SUBTITLE:
# self.updateSubtitleTracks()
#
# except CellDoesNotExist:
# pass

@ -3,6 +3,8 @@ from .track_type import TrackType
from .audio_layout import AudioLayout from .audio_layout import AudioLayout
from .track_disposition import TrackDisposition from .track_disposition import TrackDisposition
from .helper import dictDiff, setDiff
class TrackDescriptor(): class TrackDescriptor():
@ -179,3 +181,22 @@ class TrackDescriptor():
def getDispositionSet(self): def getDispositionSet(self):
return self.__dispositionSet return self.__dispositionSet
def compare(self, vsTrackDescriptor):
compareResult = {}
tagsDiffResult = dictDiff(vsTrackDescriptor.getTags(), self.getTags())
if tagsDiffResult:
compareResult['tags'] = tagsDiffResult
vsDispositions = vsTrackDescriptor.getDispositionSet()
dispositions = self.getDispositionSet()
dispositionDiffResult = setDiff(vsDispositions, dispositions)
if dispositionDiffResult:
compareResult['dispositions'] = dispositionDiffResult

Loading…
Cancel
Save