diff --git a/transcoder/data/palette_0_edit_distance.pickle.bz2 b/transcoder/data/palette_0_edit_distance.pickle.bz2 new file mode 100644 index 0000000..89bd30f Binary files /dev/null and b/transcoder/data/palette_0_edit_distance.pickle.bz2 differ diff --git a/transcoder/edit_distance.pickle.bz2 b/transcoder/data/palette_5_edit_distance.pickle.bz2 similarity index 100% rename from transcoder/edit_distance.pickle.bz2 rename to transcoder/data/palette_5_edit_distance.pickle.bz2 diff --git a/transcoder/frame_grabber.py b/transcoder/frame_grabber.py index 5b568d6..8ef99b6 100644 --- a/transcoder/frame_grabber.py +++ b/transcoder/frame_grabber.py @@ -11,6 +11,7 @@ import skvideo.io from PIL import Image import screen +from palette import Palette from video_mode import VideoMode @@ -24,10 +25,11 @@ class FrameGrabber: class FileFrameGrabber(FrameGrabber): - def __init__(self, filename, mode: VideoMode): + def __init__(self, filename, mode: VideoMode, palette: Palette): super(FileFrameGrabber, self).__init__(mode) self.filename = filename # type: str + self.palette = palette # type: Palette self._reader = skvideo.io.FFmpegReader(filename) # Compute frame rate from input video @@ -43,8 +45,12 @@ class FileFrameGrabber(FrameGrabber): @staticmethod def _output_dir(filename) -> str: + # TODO: should include palette return ".".join(filename.split(".")[:-1]) + def _palette_arg(self) -> str: + return "P%d" % self.palette.value + def frames(self) -> Iterator[screen.MemoryMap]: """Encode frame to HGR using bmp2dhr. @@ -69,11 +75,9 @@ class FileFrameGrabber(FrameGrabber): _frame = _frame.resize((280, 192), resample=Image.LANCZOS) _frame.save(bmpfile) - # TODO: parametrize palette subprocess.call([ "/usr/local/bin/bmp2dhr", bmpfile, "hgr", - "P5", - # "P0", # Kegs32 RGB Color palette(for //gs playback) + self._palette_arg(), "D9" # Buckels dither ]) @@ -96,11 +100,9 @@ class FileFrameGrabber(FrameGrabber): _frame = _frame.resize((280, 192), resample=Image.LANCZOS) _frame.save(bmpfile) - # TODO: parametrize palette subprocess.call([ "/usr/local/bin/bmp2dhr", bmpfile, "dhgr", # "v", - "P5", # "P0", # Kegs32 RGB Color palette (for //gs - # playback) + self._palette_arg(), "A", # Output separate .BIN and .AUX files "D9" # Buckels dither ]) diff --git a/transcoder/main.py b/transcoder/main.py index bee3f11..8645237 100644 --- a/transcoder/main.py +++ b/transcoder/main.py @@ -3,6 +3,7 @@ import argparse import movie +import palette import video_mode parser = argparse.ArgumentParser( @@ -28,6 +29,10 @@ parser.add_argument( '--video_mode', type=str, choices=video_mode.VideoMode.__members__.keys(), help='Video display mode to encode for (HGR/DHGR)' ) +parser.add_argument( + '--palette', type=str, choices=palette.Palette.__members__.keys(), + help='Video palette to encode for (default=NTSC)' +) def main(args): @@ -37,9 +42,12 @@ def main(args): every_n_video_frames=args.every_n_video_frames, audio_normalization=args.audio_normalization, max_bytes_out=1024. * 1024 * args.max_output_mb, - video_mode=video_mode.VideoMode[args.video_mode] + video_mode=video_mode.VideoMode[args.video_mode], + palette=palette.Palette[args.palette], ) + print("Palette %s" % args.palette) + print("Input frame rate = %f" % m.frame_grabber.input_frame_rate) if args.output: diff --git a/transcoder/make_data_tables.py b/transcoder/make_data_tables.py index 1c7645e..05f8fbc 100644 --- a/transcoder/make_data_tables.py +++ b/transcoder/make_data_tables.py @@ -1,11 +1,16 @@ import bz2 import functools import pickle -from typing import Iterable +from typing import Dict, List, Iterable, Type + +import colormath.color_conversions +import colormath.color_diff +import colormath.color_objects import numpy as np import weighted_levenshtein +import colours import palette # The DHGR display encodes 7 pixels across interleaved 4-byte sequences @@ -65,7 +70,6 @@ import palette # contiguously into an array whose index is the (source, target) pair and # the value is the edit distance. - PIXEL_CHARS = "0123456789ABCDEF" @@ -74,14 +78,14 @@ def pixel_char(i: int) -> str: @functools.lru_cache(None) -def pixel_string(pixels: Iterable[palette.DHGRColours]) -> str: +def pixel_string(pixels: Iterable[colours.DHGRColours]) -> str: return "".join(pixel_char(p.value) for p in pixels) @functools.lru_cache(None) def pixels_influenced_by_byte_index( - pixels: Iterable[palette.DHGRColours], - idx: int) -> Iterable[palette.DHGRColours]: + pixels: str, + idx: int) -> str: """Return subset of pixels that are influenced by given byte index (0..4)""" start, end = { 0: (0, 1), @@ -93,52 +97,6 @@ def pixels_influenced_by_byte_index( return pixels[start:end + 1] -# Don't even consider insertions and deletions into the string, they don't -# make sense for comparing pixel strings -insert_costs = np.ones(128, dtype=np.float64) * 100000 -delete_costs = np.ones(128, dtype=np.float64) * 100000 - -# Smallest substitution value is ~20 from palette.diff_matrix, i.e. -# we always prefer to transpose 2 pixels rather than substituting colours. -transpose_costs = np.ones((128, 128), dtype=np.float64) * 10 - -substitute_costs = np.zeros((128, 128), dtype=np.float64) - -# Substitution costs to use when evaluating other potential offsets at which -# to store a content byte. We penalize more harshly for introducing -# errors that alter pixel colours, since these tend to be very -# noticeable as visual noise. -error_substitute_costs = np.zeros((128, 128), dtype=np.float64) - - -def make_substitute_costs(): - # Penalty for changing colour - for i, c in enumerate(PIXEL_CHARS): - for j, d in enumerate(PIXEL_CHARS): - cost = palette.diff_matrix[i, j] - substitute_costs[(ord(c), ord(d))] = cost # / 20 - substitute_costs[(ord(d), ord(c))] = cost # / 20 - error_substitute_costs[(ord(c), ord(d))] = 5 * cost # / 4 - error_substitute_costs[(ord(d), ord(c))] = 5 * cost # / 4 - - -make_substitute_costs() - - -@functools.lru_cache(None) -def edit_distance(a, b, error: bool): - res = weighted_levenshtein.dam_lev( - a, b, - - insert_costs=insert_costs, - delete_costs=delete_costs, - substitute_costs=error_substitute_costs if error else substitute_costs, - ) - - assert res == 0 or (1 <= res < 2 ** 16), res - return res - - @functools.lru_cache(None) def int28_to_pixels(int28): return tuple( @@ -170,7 +128,77 @@ def map_int8_to_mask32_3(int8): return int8 << 20 -def make_edit_distance(): +class EditDistanceParams: + # Don't even consider insertions and deletions into the string, they don't + # make sense for comparing pixel strings + insert_costs = np.ones(128, dtype=np.float64) * 100000 + delete_costs = np.ones(128, dtype=np.float64) * 100000 + + # Smallest substitution value is ~20 from palette.diff_matrices, i.e. + # we always prefer to transpose 2 pixels rather than substituting colours. + transpose_costs = np.ones((128, 128), dtype=np.float64) * 10 + + substitute_costs = np.zeros((128, 128), dtype=np.float64) + + # Substitution costs to use when evaluating other potential offsets at which + # to store a content byte. We penalize more harshly for introducing + # errors that alter pixel colours, since these tend to be very + # noticeable as visual noise. + error_substitute_costs = np.zeros((128, 128), dtype=np.float64) + + +def compute_diff_matrix(pal: Type[palette.BasePalette]): + # Compute matrix of CIE2000 delta values for this pal, representing + # perceptual distance between colours. + dm = np.ndarray(shape=(16, 16), dtype=np.int) + + for colour1, a in pal.RGB.items(): + alab = colormath.color_conversions.convert_color( + a, colormath.color_objects.LabColor) + for colour2, b in pal.RGB.items(): + blab = colormath.color_conversions.convert_color( + b, colormath.color_objects.LabColor) + dm[colour1.value, colour2.value] = int( + colormath.color_diff.delta_e_cie2000(alab, blab)) + return dm + + +def make_substitute_costs(pal: Type[palette.BasePalette]): + edp = EditDistanceParams() + + diff_matrix = compute_diff_matrix(pal) + + # Penalty for changing colour + for i, c in enumerate(PIXEL_CHARS): + for j, d in enumerate(PIXEL_CHARS): + cost = diff_matrix[i, j] + edp.substitute_costs[(ord(c), ord(d))] = cost # / 20 + edp.substitute_costs[(ord(d), ord(c))] = cost # / 20 + edp.error_substitute_costs[(ord(c), ord(d))] = 5 * cost # / 4 + edp.error_substitute_costs[(ord(d), ord(c))] = 5 * cost # / 4 + + return edp + + +@functools.lru_cache(None) +def edit_distance( + edp: EditDistanceParams, + a: str, + b: str, + error: bool) -> np.float64: + res = weighted_levenshtein.dam_lev( + a, b, + insert_costs=edp.insert_costs, + delete_costs=edp.delete_costs, + substitute_costs=( + edp.error_substitute_costs if error else edp.substitute_costs), + ) + + assert res == 0 or (1 <= res < 2 ** 16), res + return res + + +def make_edit_distance(edp: EditDistanceParams): edit = [ np.zeros(shape=(2 ** 16), dtype=np.int16), np.zeros(shape=(2 ** 24), dtype=np.int16), @@ -191,8 +219,8 @@ def make_edit_distance(): second_pixels = pixels_influenced_by_byte_index( pixel_string(int28_to_pixels(second)), 0) - edit[0][pair] = edit_distance(first_pixels, second_pixels, - error=False) + edit[0][pair] = edit_distance( + edp, first_pixels, second_pixels, error=False) first = map_int8_to_mask32_3(i) second = map_int8_to_mask32_3(j) @@ -202,8 +230,8 @@ def make_edit_distance(): second_pixels = pixels_influenced_by_byte_index( pixel_string(int28_to_pixels(second)), 3) - edit[3][pair] = edit_distance(first_pixels, second_pixels, - error=False) + edit[3][pair] = edit_distance( + edp, first_pixels, second_pixels, error=False) for i in range(2 ** 12): print(i) @@ -218,8 +246,8 @@ def make_edit_distance(): second_pixels = pixels_influenced_by_byte_index( pixel_string(int28_to_pixels(second)), 1) - edit[1][pair] = edit_distance(first_pixels, second_pixels, - error=False) + edit[1][pair] = edit_distance( + edp, first_pixels, second_pixels, error=False) first = map_int12_to_mask32_2(i) second = map_int12_to_mask32_2(j) @@ -229,22 +257,23 @@ def make_edit_distance(): second_pixels = pixels_influenced_by_byte_index( pixel_string(int28_to_pixels(second)), 2) - edit[2][pair] = edit_distance(first_pixels, second_pixels, - error=False) + edit[2][pair] = edit_distance( + edp, first_pixels, second_pixels, error=False) return edit def main(): - edit = make_edit_distance() + for p in palette.PALETTES.values(): + print("Processing palette %s" % p) + edp = make_substitute_costs(p) + edit = make_edit_distance(edp) - # TODO: error distance matrices - - with bz2.open( - "transcoder/edit_distance.pickle.bz2", "wb", - compresslevel=9) as out: - pickle.dump( - edit, out, protocol=pickle.HIGHEST_PROTOCOL) + # TODO: error distance matrices + data = "transcoder/data/palette_%d_edit_distance.pickle" \ + ".bz2" % p.ID.value + with bz2.open(data, "wb", compresslevel=9) as out: + pickle.dump(edit, out, protocol=pickle.HIGHEST_PROTOCOL) if __name__ == "__main__": diff --git a/transcoder/make_data_tables_test.py b/transcoder/make_data_tables_test.py index af4b9ae..b8d0d27 100644 --- a/transcoder/make_data_tables_test.py +++ b/transcoder/make_data_tables_test.py @@ -10,31 +10,15 @@ class TestMakeDataTables(unittest.TestCase): self.assertEqual("0FC", make_data_tables.pixel_string(pixels)) def test_pixels_influenced_by_byte_index(self): - pixels = ( - DHGRColours.ORANGE, - DHGRColours.GREEN, - DHGRColours.BLACK, - DHGRColours.BLACK, - DHGRColours.BLACK, - DHGRColours.BLACK, - DHGRColours.BLACK, - ) + pixels = "CB00000" self.assertEqual( - (DHGRColours.ORANGE, DHGRColours.GREEN), + "CB", make_data_tables.pixels_influenced_by_byte_index(pixels, 0) ) - pixels = ( - DHGRColours.BLACK, - DHGRColours.BROWN, - DHGRColours.YELLOW, - DHGRColours.GREY1, - DHGRColours.BLACK, - DHGRColours.BLACK, - DHGRColours.BLACK, - ) + pixels = "CBA9000" self.assertEqual( - (DHGRColours.BROWN, DHGRColours.YELLOW, DHGRColours.GREY1), + "BA9", make_data_tables.pixels_influenced_by_byte_index(pixels, 1) ) diff --git a/transcoder/movie.py b/transcoder/movie.py index ec7e261..f8c3589 100644 --- a/transcoder/movie.py +++ b/transcoder/movie.py @@ -7,6 +7,7 @@ import frame_grabber import machine import opcodes import video +from palette import Palette from video_mode import VideoMode @@ -17,19 +18,22 @@ class Movie: audio_normalization: float = None, max_bytes_out: int = None, video_mode: VideoMode = VideoMode.HGR, + palette: Palette = Palette.NTSC, ): self.filename = filename # type: str self.every_n_video_frames = every_n_video_frames # type: int self.max_bytes_out = max_bytes_out # type: int self.video_mode = video_mode # type: VideoMode + self.palette = palette # type: Palette self.audio = audio.Audio( filename, normalization=audio_normalization) # type: audio.Audio self.frame_grabber = frame_grabber.FileFrameGrabber( - filename, mode=video_mode) + filename, mode=video_mode, palette=self.palette) self.video = video.Video( self.frame_sequencer, mode=video_mode, + palette=self.palette, ticks_per_second=self.audio.sample_rate ) # type: video.Video diff --git a/transcoder/palette.py b/transcoder/palette.py index 1eed896..a3d33e6 100644 --- a/transcoder/palette.py +++ b/transcoder/palette.py @@ -1,51 +1,81 @@ +import enum +from typing import Dict, Type + import colormath.color_objects -import colormath.color_diff -import colormath.color_conversions -import numpy as np from colours import DHGRColours +# Type annotation +RGB = colormath.color_objects.sRGBColor + def rgb(r, g, b): - return colormath.color_objects.sRGBColor(r, g, b, is_upscaled=True) + return RGB(r, g, b, is_upscaled=True) -# Palette RGB values taken from BMP2DHGR's default NTSC palette -# TODO: support other palettes as well, e.g. //gs RGB -palette = { - DHGRColours.BLACK: rgb(0, 0, 0), - DHGRColours.MAGENTA: rgb(148, 12, 125), - DHGRColours.BROWN: rgb(99, 77, 0), - DHGRColours.ORANGE: rgb(249, 86, 29), - DHGRColours.DARK_GREEN: rgb(51, 111, 0), - DHGRColours.GREY1: rgb(126, 126, 126), - DHGRColours.GREEN: rgb(67, 200, 0), - DHGRColours.YELLOW: rgb(221, 206, 23), - DHGRColours.DARK_BLUE: rgb(32, 54, 212), - DHGRColours.VIOLET: rgb(188, 55, 255), - DHGRColours.GREY2: rgb(126, 126, 126), - DHGRColours.PINK: rgb(255, 129, 236), - DHGRColours.MED_BLUE: rgb(7, 168, 225), - DHGRColours.LIGHT_BLUE: rgb(158, 172, 255), - DHGRColours.AQUA: rgb(93, 248, 133), - DHGRColours.WHITE: rgb(255, 255, 255) -} +class Palette(enum.Enum): + """BMP2DHR palette numbers""" + UNKNOWN = -1 + IIGS = 0 + NTSC = 5 -def compute_diff_matrix(): - # Compute matrix of CIE2000 delta values for this palette, representing - # perceptual distance between colours. - dm = np.ndarray(shape=(16, 16), dtype=np.int) +class BasePalette: + ID = Palette.UNKNOWN # type: Palette - for colour1, a in palette.items(): - alab = colormath.color_conversions.convert_color( - a, colormath.color_objects.LabColor) - for colour2, b in palette.items(): - blab = colormath.color_conversions.convert_color( - b, colormath.color_objects.LabColor) - dm[colour1.value, colour2.value] = int( - colormath.color_diff.delta_e_cie2000(alab, blab)) - return dm + # Palette RGB map + RGB = {} # type: Dict[DHGRColours: RGB] -diff_matrix = compute_diff_matrix() +class NTSCPalette(BasePalette): + ID = Palette.NTSC + + # Palette RGB values taken from BMP2DHGR's default NTSC palette + RGB = { + DHGRColours.BLACK: rgb(0, 0, 0), + DHGRColours.MAGENTA: rgb(148, 12, 125), + DHGRColours.BROWN: rgb(99, 77, 0), + DHGRColours.ORANGE: rgb(249, 86, 29), + DHGRColours.DARK_GREEN: rgb(51, 111, 0), + DHGRColours.GREY1: rgb(126, 126, 126), + DHGRColours.GREEN: rgb(67, 200, 0), + DHGRColours.YELLOW: rgb(221, 206, 23), + DHGRColours.DARK_BLUE: rgb(32, 54, 212), + DHGRColours.VIOLET: rgb(188, 55, 255), + DHGRColours.GREY2: rgb(126, 126, 126), + DHGRColours.PINK: rgb(255, 129, 236), + DHGRColours.MED_BLUE: rgb(7, 168, 225), + DHGRColours.LIGHT_BLUE: rgb(158, 172, 255), + DHGRColours.AQUA: rgb(93, 248, 133), + DHGRColours.WHITE: rgb(255, 255, 255) + } + + +class IIGSPalette(BasePalette): + ID = Palette.IIGS + + # Palette RGB values taken from BMP2DHGR's KEGS32 palette + RGB = { + DHGRColours.BLACK: rgb(0, 0, 0), + DHGRColours.MAGENTA: rgb(221, 0, 51), + DHGRColours.BROWN: rgb(136, 85, 34), + DHGRColours.ORANGE: rgb(255, 102, 0), + DHGRColours.DARK_GREEN: rgb(0, 119, 0), + DHGRColours.GREY1: rgb(85, 85, 85), + DHGRColours.GREEN: rgb(0, 221, 0), + DHGRColours.YELLOW: rgb(255, 255, 0), + DHGRColours.DARK_BLUE: rgb(0, 0, 153), + DHGRColours.VIOLET: rgb(221, 0, 221), + DHGRColours.GREY2: rgb(170, 170, 170), + DHGRColours.PINK: rgb(255, 153, 136), + DHGRColours.MED_BLUE: rgb(34, 34, 255), + DHGRColours.LIGHT_BLUE: rgb(102, 170, 255), + DHGRColours.AQUA: rgb(0, 255, 153), + DHGRColours.WHITE: rgb(255, 255, 255) + } + + +PALETTES = { + Palette.IIGS: IIGSPalette, + Palette.NTSC: NTSCPalette +} # type: Dict[Palette, Type[BasePalette]] diff --git a/transcoder/screen.py b/transcoder/screen.py index abf1974..845cd04 100644 --- a/transcoder/screen.py +++ b/transcoder/screen.py @@ -3,9 +3,10 @@ import bz2 import functools import pickle -from typing import Union +from typing import Union, List import numpy as np +import palette # Type annotation for cases where we may process either an int or a numpy array. IntOrArray = Union[int, np.ndarray] @@ -140,10 +141,15 @@ class DHGRBitmap: # How much to right-shift bits after masking to bring into int8/int12 range BYTE_SHIFTS = [0, 4, 12, 20] - # Load edit distance matrices for masked, shifted byte 0..3 values - # TODO: should go somewhere else since we don't use it here at all - with bz2.open("transcoder/edit_distance.pickle.bz2", "rb") as ed: - edit_distances = pickle.load(ed) + @staticmethod + @functools.lru_cache(None) + def edit_distances(palette_id: palette.Palette) -> List[np.ndarray]: + """Load edit distance matrices for masked, shifted byte 0..3 values.""" + data = "transcoder/data/palette_%d_edit_distance.pickle.bz2" % ( + palette_id.value + ) + with bz2.open(data, "rb") as ed: + return pickle.load(ed) # type: List[np.ndarray] def __init__(self, main_memory: MemoryMap, aux_memory: MemoryMap): self.main_memory = main_memory diff --git a/transcoder/video.py b/transcoder/video.py index 26652a8..7878668 100644 --- a/transcoder/video.py +++ b/transcoder/video.py @@ -10,6 +10,7 @@ import numpy as np import opcodes import screen from frame_grabber import FrameGrabber +from palette import Palette from video_mode import VideoMode @@ -21,13 +22,17 @@ class Video: def __init__( self, frame_grabber: FrameGrabber, - mode: VideoMode = VideoMode.HGR + mode: VideoMode = VideoMode.HGR, + palette: Palette = Palette.NTSC, + ticks_per_second: int, ): self.mode = mode # type: VideoMode self.frame_grabber = frame_grabber # type: FrameGrabber + self.ticks_per_second = float(ticks_per_second) # type: float self.ticks_per_frame = ( self.ticks_per_second / self.input_frame_rate) # type: float self.frame_number = 0 # type: int + self.palette = palette # type: Palette # Initialize empty screen self.memory_map = screen.MemoryMap( @@ -213,8 +218,8 @@ class Video: heapq.heapify(priorities) return priorities - @staticmethod def _diff_weights( + self, source: screen.DHGRBitmap, target: screen.DHGRBitmap, is_aux: bool @@ -228,14 +233,16 @@ class Video: # Concatenate 8-bit source and target into 16-bit values pair0 = (source_pixels0 << 8) + target_pixels0 - dist0 = source.edit_distances[0][pair0].reshape(pair0.shape) + dist0 = source.edit_distances(self.palette)[0][pair0].reshape( + pair0.shape) # Pixels influenced by byte offset 2 source_pixels2 = source.mask_and_shift_data(source.packed, 2) target_pixels2 = target.mask_and_shift_data(target.packed, 2) # Concatenate 12-bit source and target into 24-bit values pair2 = (source_pixels2 << 12) + target_pixels2 - dist2 = source.edit_distances[2][pair2].reshape(pair2.shape) + dist2 = source.edit_distances(self.palette)[2][pair2].reshape( + pair2.shape) diff[:, 0::2] = dist0 diff[:, 1::2] = dist2 @@ -245,13 +252,15 @@ class Video: source_pixels1 = source.mask_and_shift_data(source.packed, 1) target_pixels1 = target.mask_and_shift_data(target.packed, 1) pair1 = (source_pixels1 << 12) + target_pixels1 - dist1 = source.edit_distances[1][pair1].reshape(pair1.shape) + dist1 = source.edit_distances(self.palette)[1][pair1].reshape( + pair1.shape) # Pixels influenced by byte offset 3 source_pixels3 = source.mask_and_shift_data(source.packed, 3) target_pixels3 = target.mask_and_shift_data(target.packed, 3) pair3 = (source_pixels3 << 8) + target_pixels3 - dist3 = source.edit_distances[3][pair3].reshape(pair3.shape) + dist3 = source.edit_distances(self.palette)[3][pair3].reshape( + pair3.shape) diff[:, 0::2] = dist1 diff[:, 1::2] = dist3 @@ -278,12 +287,12 @@ class Video: else: pair = (old_pixels << 12) + new_pixels - p = target_pixelmap.edit_distances[byte_offset][pair] + p = target_pixelmap.edit_distances(self.palette)[byte_offset][pair] return p - @staticmethod def _compute_delta( + self, content: int, target: screen.DHGRBitmap, old, @@ -301,7 +310,8 @@ class Video: # Concatenate 8-bit source and target into 16-bit values pair0 = (source_pixels0 << 8) + target_pixels0 - dist0 = target.edit_distances[0][pair0].reshape(pair0.shape) + dist0 = target.edit_distances(self.palette)[0][pair0].reshape( + pair0.shape) # Pixels influenced by byte offset 2 source_pixels2 = target.mask_and_shift_data( @@ -309,7 +319,8 @@ class Video: target_pixels2 = target.mask_and_shift_data(target.packed, 2) # Concatenate 12-bit source and target into 24-bit values pair2 = (source_pixels2 << 12) + target_pixels2 - dist2 = target.edit_distances[2][pair2].reshape(pair2.shape) + dist2 = target.edit_distances(self.palette)[2][pair2].reshape( + pair2.shape) diff[:, 0::2] = dist0 diff[:, 1::2] = dist2 @@ -320,14 +331,16 @@ class Video: target.masked_update(1, target.packed, content), 1) target_pixels1 = target.mask_and_shift_data(target.packed, 1) pair1 = (source_pixels1 << 12) + target_pixels1 - dist1 = target.edit_distances[1][pair1].reshape(pair1.shape) + dist1 = target.edit_distances(self.palette)[1][pair1].reshape( + pair1.shape) # Pixels influenced by byte offset 3 source_pixels3 = target.mask_and_shift_data( target.masked_update(3, target.packed, content), 3) target_pixels3 = target.mask_and_shift_data(target.packed, 3) pair3 = (source_pixels3 << 8) + target_pixels3 - dist3 = target.edit_distances[3][pair3].reshape(pair3.shape) + dist3 = target.edit_distances(self.palette)[3][pair3].reshape( + pair3.shape) diff[:, 0::2] = dist1 diff[:, 1::2] = dist3 diff --git a/transcoder/video_test.py b/transcoder/video_test.py index a47633e..239016d 100644 --- a/transcoder/video_test.py +++ b/transcoder/video_test.py @@ -3,6 +3,7 @@ import unittest import frame_grabber +import palette import screen import video import video_mode @@ -27,11 +28,13 @@ class TestVideo(unittest.TestCase): diff = v._diff_weights(v.pixelmap, target_pixelmap, is_aux=True) + pal = palette.NTSCPalette + # Expect byte 0 to map to 0b00000000 01111111 - expect0 = target_pixelmap.edit_distances[0][0b0000000001111111] + expect0 = target_pixelmap.edit_distances(pal.ID)[0][0b0000000001111111] # Expect byte 2 to map to 0b000000000000 000101010100 - expect2 = target_pixelmap.edit_distances[2][0b000101010100] + expect2 = target_pixelmap.edit_distances(pal.ID)[2][0b000101010100] self.assertEqual(expect0, diff[0, 0]) self.assertEqual(expect2, diff[0, 1]) @@ -61,10 +64,11 @@ class TestVideo(unittest.TestCase): diff = v._diff_weights(v.pixelmap, target_pixelmap, is_aux=True) # Expect byte 0 to map to 0b01111111 01101101 - expect0 = target_pixelmap.edit_distances[0][0b0111111101101101] + expect0 = target_pixelmap.edit_distances(pal.ID)[0][0b0111111101101101] # Expect byte 2 to map to 0b000101010100 000011011000 - expect2 = target_pixelmap.edit_distances[2][0b0000101010100000011011000] + expect2 = target_pixelmap.edit_distances(pal.ID)[2][ + 0b0000101010100000011011000] self.assertEqual(expect0, diff[0, 0]) self.assertEqual(expect2, diff[0, 1])