ff
This commit is contained in:
@@ -50,7 +50,7 @@
|
|||||||
## Data And Interface Notes
|
## Data And Interface Notes
|
||||||
|
|
||||||
- Key entities or records:
|
- Key entities or records:
|
||||||
- `Show`: canonical TV show metadata plus digit-formatting rules and an optional show-level encoding-quality fallback.
|
- `Show`: canonical TV show metadata plus digit-formatting rules, optional show-level notes, and an optional show-level encoding-quality fallback.
|
||||||
- `Pattern`: regex rule tying filenames to one show and one target media schema.
|
- `Pattern`: regex rule tying filenames to one show and one target media schema.
|
||||||
- `Track` and `TrackTag`: persisted target stream records, codec, dispositions, audio layout, and stream-level tags. Detailed source-to-target mapping rules live in `requirements/subtrack_mapping.md`.
|
- `Track` and `TrackTag`: persisted target stream records, codec, dispositions, audio layout, and stream-level tags. Detailed source-to-target mapping rules live in `requirements/subtrack_mapping.md`.
|
||||||
- `MediaTag`: persisted container-level metadata for a pattern.
|
- `MediaTag`: persisted container-level metadata for a pattern.
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
- The CLI command `ffx configure_workstation` shall act as a wrapper for the second-step preparation flow in `tools/configure_workstation.sh`.
|
- The CLI command `ffx configure_workstation` shall act as a wrapper for the second-step preparation flow in `tools/configure_workstation.sh`.
|
||||||
- The system shall persist reusable normalization rules in SQLite for:
|
- The system shall persist reusable normalization rules in SQLite for:
|
||||||
- shows and show formatting digits,
|
- shows and show formatting digits,
|
||||||
|
- optional show-level notes,
|
||||||
- optional show-level quality defaults,
|
- optional show-level quality defaults,
|
||||||
- regex-based filename patterns,
|
- regex-based filename patterns,
|
||||||
- per-pattern media tags,
|
- per-pattern media tags,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import os, shutil, click
|
import os, shutil, click
|
||||||
|
|
||||||
from sqlalchemy import create_engine, inspect
|
from sqlalchemy import create_engine, inspect, text
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
# Import the full model package so SQLAlchemy registers every mapped class
|
# Import the full model package so SQLAlchemy registers every mapped class
|
||||||
@@ -98,6 +98,30 @@ def ensureDatabaseVersion(databaseContext):
|
|||||||
f"Current database version ({currentDatabaseVersion}) does not match required ({DATABASE_VERSION})"
|
f"Current database version ({currentDatabaseVersion}) does not match required ({DATABASE_VERSION})"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ensureCurrentSchemaCompatibility(databaseContext)
|
||||||
|
|
||||||
|
|
||||||
|
def ensureCurrentSchemaCompatibility(databaseContext):
|
||||||
|
engine = databaseContext['engine']
|
||||||
|
inspector = inspect(engine)
|
||||||
|
showColumns = {
|
||||||
|
column['name']
|
||||||
|
for column in inspector.get_columns('shows')
|
||||||
|
}
|
||||||
|
|
||||||
|
alterStatements = []
|
||||||
|
if 'quality' not in showColumns:
|
||||||
|
alterStatements.append("ALTER TABLE shows ADD COLUMN quality INTEGER DEFAULT 0")
|
||||||
|
if 'notes' not in showColumns:
|
||||||
|
alterStatements.append("ALTER TABLE shows ADD COLUMN notes TEXT DEFAULT ''")
|
||||||
|
|
||||||
|
if not alterStatements:
|
||||||
|
return
|
||||||
|
|
||||||
|
with engine.begin() as connection:
|
||||||
|
for alterStatement in alterStatements:
|
||||||
|
connection.execute(text(alterStatement))
|
||||||
|
|
||||||
|
|
||||||
def promptForDatabaseMigration(databaseContext, currentDatabaseVersion: int, targetDatabaseVersion: int):
|
def promptForDatabaseMigration(databaseContext, currentDatabaseVersion: int, targetDatabaseVersion: int):
|
||||||
migrationPlan = getMigrationPlan(currentDatabaseVersion, targetDatabaseVersion)
|
migrationPlan = getMigrationPlan(currentDatabaseVersion, targetDatabaseVersion)
|
||||||
|
|||||||
@@ -78,3 +78,7 @@ def applyMigration(databaseContext):
|
|||||||
connection.execute(
|
connection.execute(
|
||||||
text("ALTER TABLE shows ADD COLUMN quality INTEGER DEFAULT 0")
|
text("ALTER TABLE shows ADD COLUMN quality INTEGER DEFAULT 0")
|
||||||
)
|
)
|
||||||
|
if 'notes' not in showColumns:
|
||||||
|
connection.execute(
|
||||||
|
text("ALTER TABLE shows ADD COLUMN notes TEXT DEFAULT ''")
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# from typing import List
|
# from typing import List
|
||||||
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
|
from sqlalchemy import create_engine, Column, Integer, String, Text, ForeignKey
|
||||||
from sqlalchemy.orm import relationship, declarative_base, sessionmaker
|
from sqlalchemy.orm import relationship, declarative_base, sessionmaker
|
||||||
|
|
||||||
from ffx.show_descriptor import ShowDescriptor
|
from ffx.show_descriptor import ShowDescriptor
|
||||||
@@ -46,6 +46,7 @@ class Show(Base):
|
|||||||
indicator_season_digits = Column(Integer, default=ShowDescriptor.DEFAULT_INDICATOR_SEASON_DIGITS)
|
indicator_season_digits = Column(Integer, default=ShowDescriptor.DEFAULT_INDICATOR_SEASON_DIGITS)
|
||||||
indicator_episode_digits = Column(Integer, default=ShowDescriptor.DEFAULT_INDICATOR_EPISODE_DIGITS)
|
indicator_episode_digits = Column(Integer, default=ShowDescriptor.DEFAULT_INDICATOR_EPISODE_DIGITS)
|
||||||
quality = Column(Integer, default=0)
|
quality = Column(Integer, default=0)
|
||||||
|
notes = Column(Text, default='')
|
||||||
|
|
||||||
|
|
||||||
def getDescriptor(self, context):
|
def getDescriptor(self, context):
|
||||||
@@ -60,5 +61,6 @@ class Show(Base):
|
|||||||
kwargs[ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY] = int(self.indicator_season_digits)
|
kwargs[ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY] = int(self.indicator_season_digits)
|
||||||
kwargs[ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY] = int(self.indicator_episode_digits)
|
kwargs[ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY] = int(self.indicator_episode_digits)
|
||||||
kwargs[ShowDescriptor.QUALITY_KEY] = int(self.quality or 0)
|
kwargs[ShowDescriptor.QUALITY_KEY] = int(self.quality or 0)
|
||||||
|
kwargs[ShowDescriptor.NOTES_KEY] = str(self.notes or '')
|
||||||
|
|
||||||
return ShowDescriptor(**kwargs)
|
return ShowDescriptor(**kwargs)
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ class ShowController():
|
|||||||
index_episode_digits = showDescriptor.getIndexEpisodeDigits(),
|
index_episode_digits = showDescriptor.getIndexEpisodeDigits(),
|
||||||
indicator_season_digits = showDescriptor.getIndicatorSeasonDigits(),
|
indicator_season_digits = showDescriptor.getIndicatorSeasonDigits(),
|
||||||
indicator_episode_digits = showDescriptor.getIndicatorEpisodeDigits(),
|
indicator_episode_digits = showDescriptor.getIndicatorEpisodeDigits(),
|
||||||
quality = showDescriptor.getQuality())
|
quality = showDescriptor.getQuality(),
|
||||||
|
notes = showDescriptor.getNotes())
|
||||||
|
|
||||||
s.add(show)
|
s.add(show)
|
||||||
s.commit()
|
s.commit()
|
||||||
@@ -92,6 +93,9 @@ class ShowController():
|
|||||||
if int(currentShow.quality or 0) != int(showDescriptor.getQuality()):
|
if int(currentShow.quality or 0) != int(showDescriptor.getQuality()):
|
||||||
currentShow.quality = int(showDescriptor.getQuality())
|
currentShow.quality = int(showDescriptor.getQuality())
|
||||||
changed = True
|
changed = True
|
||||||
|
if str(currentShow.notes or '') != str(showDescriptor.getNotes()):
|
||||||
|
currentShow.notes = str(showDescriptor.getNotes())
|
||||||
|
changed = True
|
||||||
|
|
||||||
if changed:
|
if changed:
|
||||||
s.commit()
|
s.commit()
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class ShowDescriptor():
|
|||||||
INDICATOR_SEASON_DIGITS_KEY = 'indicator_season_digits'
|
INDICATOR_SEASON_DIGITS_KEY = 'indicator_season_digits'
|
||||||
INDICATOR_EPISODE_DIGITS_KEY = 'indicator_episode_digits'
|
INDICATOR_EPISODE_DIGITS_KEY = 'indicator_episode_digits'
|
||||||
QUALITY_KEY = 'quality'
|
QUALITY_KEY = 'quality'
|
||||||
|
NOTES_KEY = 'notes'
|
||||||
|
|
||||||
DEFAULT_INDEX_SEASON_DIGITS = DEFAULT_SHOW_INDEX_SEASON_DIGITS
|
DEFAULT_INDEX_SEASON_DIGITS = DEFAULT_SHOW_INDEX_SEASON_DIGITS
|
||||||
DEFAULT_INDEX_EPISODE_DIGITS = DEFAULT_SHOW_INDEX_EPISODE_DIGITS
|
DEFAULT_INDEX_EPISODE_DIGITS = DEFAULT_SHOW_INDEX_EPISODE_DIGITS
|
||||||
@@ -132,6 +133,13 @@ class ShowDescriptor():
|
|||||||
else:
|
else:
|
||||||
self.__quality = 0
|
self.__quality = 0
|
||||||
|
|
||||||
|
if ShowDescriptor.NOTES_KEY in kwargs.keys():
|
||||||
|
if type(kwargs[ShowDescriptor.NOTES_KEY]) is not str:
|
||||||
|
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.NOTES_KEY} is required to be of type str")
|
||||||
|
self.__notes = kwargs[ShowDescriptor.NOTES_KEY]
|
||||||
|
else:
|
||||||
|
self.__notes = ''
|
||||||
|
|
||||||
|
|
||||||
def getId(self):
|
def getId(self):
|
||||||
return self.__showId
|
return self.__showId
|
||||||
@@ -150,6 +158,8 @@ class ShowDescriptor():
|
|||||||
return self.__indicatorEpisodeDigits
|
return self.__indicatorEpisodeDigits
|
||||||
def getQuality(self):
|
def getQuality(self):
|
||||||
return self.__quality
|
return self.__quality
|
||||||
|
def getNotes(self):
|
||||||
|
return self.__notes
|
||||||
|
|
||||||
def getFilenamePrefix(self):
|
def getFilenamePrefix(self):
|
||||||
return f"{self.__showName} ({str(self.__showYear)})"
|
return f"{self.__showName} ({str(self.__showYear)})"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import click
|
import click
|
||||||
|
|
||||||
from textual.screen import Screen
|
from textual.screen import Screen
|
||||||
from textual.widgets import Header, Footer, Static, Button, DataTable, Input
|
from textual.widgets import Header, Footer, Static, Button, DataTable, Input, TextArea
|
||||||
from textual.containers import Grid
|
from textual.containers import Grid
|
||||||
from textual.widgets._data_table import CellDoesNotExist
|
from textual.widgets._data_table import CellDoesNotExist
|
||||||
|
|
||||||
@@ -25,8 +25,8 @@ class ShowDetailsScreen(Screen):
|
|||||||
CSS = """
|
CSS = """
|
||||||
|
|
||||||
Grid {
|
Grid {
|
||||||
grid-size: 5 16;
|
grid-size: 5 18;
|
||||||
grid-rows: 2 2 2 2 2 2 2 2 2 2 2 9 2 9 2 2;
|
grid-rows: 2 2 2 2 2 2 6 2 2 2 2 2 9 2 9 2 2 2;
|
||||||
grid-columns: 30 30 30 30 30;
|
grid-columns: 30 30 30 30 30;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -77,6 +77,10 @@ class ShowDetailsScreen(Screen):
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
border: solid green;
|
border: solid green;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.note_box {
|
||||||
|
min-height: 6;
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
BINDINGS = [
|
BINDINGS = [
|
||||||
@@ -152,6 +156,8 @@ class ShowDetailsScreen(Screen):
|
|||||||
self.query_one("#indicator_episode_digits_input", Input).value = str(self.__showDescriptor.getIndicatorEpisodeDigits())
|
self.query_one("#indicator_episode_digits_input", Input).value = str(self.__showDescriptor.getIndicatorEpisodeDigits())
|
||||||
if self.__showDescriptor.getQuality():
|
if self.__showDescriptor.getQuality():
|
||||||
self.query_one("#quality_input", Input).value = str(self.__showDescriptor.getQuality())
|
self.query_one("#quality_input", Input).value = str(self.__showDescriptor.getQuality())
|
||||||
|
if self.__showDescriptor.getNotes():
|
||||||
|
self.query_one("#notes_textarea", TextArea).text = str(self.__showDescriptor.getNotes())
|
||||||
|
|
||||||
|
|
||||||
#raise click.ClickException(f"show_id {showId}")
|
#raise click.ClickException(f"show_id {showId}")
|
||||||
@@ -354,25 +360,32 @@ class ShowDetailsScreen(Screen):
|
|||||||
yield Input(type="integer", id="quality_input", classes="four")
|
yield Input(type="integer", id="quality_input", classes="four")
|
||||||
|
|
||||||
#6
|
#6
|
||||||
|
yield Static("Notes")
|
||||||
|
yield Static(" ", classes="four")
|
||||||
|
|
||||||
|
#7
|
||||||
|
yield TextArea(id="notes_textarea", classes="five note_box")
|
||||||
|
|
||||||
|
#8
|
||||||
yield Static("Index Season Digits")
|
yield Static("Index Season Digits")
|
||||||
yield Input(type="integer", id="index_season_digits_input", classes="four")
|
yield Input(type="integer", id="index_season_digits_input", classes="four")
|
||||||
|
|
||||||
#7
|
#9
|
||||||
yield Static("Index Episode Digits")
|
yield Static("Index Episode Digits")
|
||||||
yield Input(type="integer", id="index_episode_digits_input", classes="four")
|
yield Input(type="integer", id="index_episode_digits_input", classes="four")
|
||||||
|
|
||||||
#8
|
#10
|
||||||
yield Static("Indicator Season Digits")
|
yield Static("Indicator Season Digits")
|
||||||
yield Input(type="integer", id="indicator_season_digits_input", classes="four")
|
yield Input(type="integer", id="indicator_season_digits_input", classes="four")
|
||||||
|
|
||||||
#9
|
#11
|
||||||
yield Static("Indicator Edisode Digits")
|
yield Static("Indicator Edisode Digits")
|
||||||
yield Input(type="integer", id="indicator_episode_digits_input", classes="four")
|
yield Input(type="integer", id="indicator_episode_digits_input", classes="four")
|
||||||
|
|
||||||
# 10
|
# 12
|
||||||
yield Static(" ", classes="five")
|
yield Static(" ", classes="five")
|
||||||
|
|
||||||
# 11
|
# 13
|
||||||
yield Static("Shifted seasons", classes="two")
|
yield Static("Shifted seasons", classes="two")
|
||||||
|
|
||||||
if self.__showDescriptor is not None:
|
if self.__showDescriptor is not None:
|
||||||
@@ -384,18 +397,18 @@ class ShowDetailsScreen(Screen):
|
|||||||
yield Static(" ")
|
yield Static(" ")
|
||||||
yield Static(" ")
|
yield Static(" ")
|
||||||
|
|
||||||
# 12
|
# 14
|
||||||
yield self.shiftedSeasonsTable
|
yield self.shiftedSeasonsTable
|
||||||
|
|
||||||
# 13
|
# 15
|
||||||
yield Static("File patterns", classes="five")
|
yield Static("File patterns", classes="five")
|
||||||
# 14
|
# 16
|
||||||
yield self.patternTable
|
yield self.patternTable
|
||||||
|
|
||||||
# 15
|
# 17
|
||||||
yield Static(" ", classes="five")
|
yield Static(" ", classes="five")
|
||||||
|
|
||||||
# 16
|
# 18
|
||||||
yield Button("Save", id="save_button")
|
yield Button("Save", id="save_button")
|
||||||
yield Button("Cancel", id="cancel_button")
|
yield Button("Cancel", id="cancel_button")
|
||||||
|
|
||||||
@@ -445,6 +458,7 @@ class ShowDetailsScreen(Screen):
|
|||||||
kwargs[ShowDescriptor.QUALITY_KEY] = int(self.query_one("#quality_input", Input).value)
|
kwargs[ShowDescriptor.QUALITY_KEY] = int(self.query_one("#quality_input", Input).value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
kwargs[ShowDescriptor.NOTES_KEY] = str(self.query_one("#notes_textarea", TextArea).text)
|
||||||
|
|
||||||
return ShowDescriptor(**kwargs)
|
return ShowDescriptor(**kwargs)
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class DatabaseContextTests(unittest.TestCase):
|
|||||||
|
|
||||||
return shifted_season_id
|
return shifted_season_id
|
||||||
|
|
||||||
def rewrite_shows_table_without_quality(self, cursor):
|
def rewrite_shows_table_without_show_fields(self, cursor):
|
||||||
cursor.execute("ALTER TABLE shows RENAME TO shows_current")
|
cursor.execute("ALTER TABLE shows RENAME TO shows_current")
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""
|
"""
|
||||||
@@ -208,7 +208,7 @@ class DatabaseContextTests(unittest.TestCase):
|
|||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute("PRAGMA foreign_keys=OFF")
|
cursor.execute("PRAGMA foreign_keys=OFF")
|
||||||
self.rewrite_shifted_seasons_table_without_pattern_owner(cursor)
|
self.rewrite_shifted_seasons_table_without_pattern_owner(cursor)
|
||||||
self.rewrite_shows_table_without_quality(cursor)
|
self.rewrite_shows_table_without_show_fields(cursor)
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"UPDATE properties SET value = '2' WHERE key = ?",
|
"UPDATE properties SET value = '2' WHERE key = ?",
|
||||||
(DATABASE_VERSION_KEY,),
|
(DATABASE_VERSION_KEY,),
|
||||||
@@ -245,6 +245,7 @@ class DatabaseContextTests(unittest.TestCase):
|
|||||||
migrated_show = session.query(Show).filter(Show.id == 1).first()
|
migrated_show = session.query(Show).filter(Show.id == 1).first()
|
||||||
self.assertIsNotNone(migrated_show)
|
self.assertIsNotNone(migrated_show)
|
||||||
self.assertEqual(0, int(migrated_show.quality or 0))
|
self.assertEqual(0, int(migrated_show.quality or 0))
|
||||||
|
self.assertEqual('', str(migrated_show.notes or ''))
|
||||||
finally:
|
finally:
|
||||||
session.close()
|
session.close()
|
||||||
finally:
|
finally:
|
||||||
@@ -286,6 +287,40 @@ class DatabaseContextTests(unittest.TestCase):
|
|||||||
self.assertEqual("Database migration aborted by user.", str(raisedContext.exception))
|
self.assertEqual("Database migration aborted by user.", str(raisedContext.exception))
|
||||||
self.assertFalse(Path(f"{self.database_path}.v2-to-v{DATABASE_VERSION}.bak").exists())
|
self.assertFalse(Path(f"{self.database_path}.v2-to-v{DATABASE_VERSION}.bak").exists())
|
||||||
|
|
||||||
|
def test_database_context_repairs_current_show_schema_without_version_bump(self):
|
||||||
|
self.create_demo_show_with_shift()
|
||||||
|
|
||||||
|
connection = sqlite3.connect(self.database_path)
|
||||||
|
try:
|
||||||
|
cursor = connection.cursor()
|
||||||
|
cursor.execute("PRAGMA foreign_keys=OFF")
|
||||||
|
self.rewrite_shows_table_without_show_fields(cursor)
|
||||||
|
connection.commit()
|
||||||
|
finally:
|
||||||
|
connection.close()
|
||||||
|
|
||||||
|
with patch("ffx.database.click.confirm") as mocked_confirm, patch(
|
||||||
|
"ffx.database.click.echo"
|
||||||
|
) as mocked_echo:
|
||||||
|
reopened_context = databaseContext(str(self.database_path))
|
||||||
|
try:
|
||||||
|
self.assertEqual(DATABASE_VERSION, getDatabaseVersion(reopened_context))
|
||||||
|
|
||||||
|
Session = reopened_context["session"]
|
||||||
|
session = Session()
|
||||||
|
try:
|
||||||
|
repaired_show = session.query(Show).filter(Show.id == 1).first()
|
||||||
|
self.assertIsNotNone(repaired_show)
|
||||||
|
self.assertEqual(0, int(repaired_show.quality or 0))
|
||||||
|
self.assertEqual('', str(repaired_show.notes or ''))
|
||||||
|
finally:
|
||||||
|
session.close()
|
||||||
|
finally:
|
||||||
|
reopened_context["engine"].dispose()
|
||||||
|
|
||||||
|
mocked_confirm.assert_not_called()
|
||||||
|
mocked_echo.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ class ShowDescriptorDefaultTests(unittest.TestCase):
|
|||||||
self.assertEqual(3, descriptor.getIndicatorSeasonDigits())
|
self.assertEqual(3, descriptor.getIndicatorSeasonDigits())
|
||||||
self.assertEqual(4, descriptor.getIndicatorEpisodeDigits())
|
self.assertEqual(4, descriptor.getIndicatorEpisodeDigits())
|
||||||
self.assertEqual(0, descriptor.getQuality())
|
self.assertEqual(0, descriptor.getQuality())
|
||||||
|
self.assertEqual("", descriptor.getNotes())
|
||||||
|
|
||||||
def test_show_descriptor_without_context_uses_shared_constants(self):
|
def test_show_descriptor_without_context_uses_shared_constants(self):
|
||||||
descriptor = ShowDescriptor(id=1, name="Default Show", year=2024)
|
descriptor = ShowDescriptor(id=1, name="Default Show", year=2024)
|
||||||
@@ -72,12 +73,18 @@ class ShowDescriptorDefaultTests(unittest.TestCase):
|
|||||||
descriptor.getIndicatorEpisodeDigits(),
|
descriptor.getIndicatorEpisodeDigits(),
|
||||||
)
|
)
|
||||||
self.assertEqual(0, descriptor.getQuality())
|
self.assertEqual(0, descriptor.getQuality())
|
||||||
|
self.assertEqual("", descriptor.getNotes())
|
||||||
|
|
||||||
def test_show_descriptor_preserves_explicit_quality(self):
|
def test_show_descriptor_preserves_explicit_quality(self):
|
||||||
descriptor = ShowDescriptor(id=1, name="Quality Show", year=2024, quality=23)
|
descriptor = ShowDescriptor(id=1, name="Quality Show", year=2024, quality=23)
|
||||||
|
|
||||||
self.assertEqual(23, descriptor.getQuality())
|
self.assertEqual(23, descriptor.getQuality())
|
||||||
|
|
||||||
|
def test_show_descriptor_preserves_explicit_notes(self):
|
||||||
|
descriptor = ShowDescriptor(id=1, name="Notes Show", year=2024, notes="show notes")
|
||||||
|
|
||||||
|
self.assertEqual("show notes", descriptor.getNotes())
|
||||||
|
|
||||||
def test_episode_basename_uses_configured_digit_defaults_when_omitted(self):
|
def test_episode_basename_uses_configured_digit_defaults_when_omitted(self):
|
||||||
basename = getEpisodeFileBasename(
|
basename = getEpisodeFileBasename(
|
||||||
"Configured Show",
|
"Configured Show",
|
||||||
|
|||||||
Reference in New Issue
Block a user