예제 #1
0
    def __init__(self, configuration: Any):
        """load configuration file (defaults to $HOME/.transcode.yml)"""

        self.profiles = dict()
        self.rules = dict()
        yml = None
        if configuration is not None:
            if isinstance(configuration, Dict):
                yml = configuration
            else:
                if not os.path.exists(configuration):
                    print(f'Configuration file "{configuration}" not found')
                    exit(1)
                with open(configuration, 'r') as f:
                    yml = yaml.load(f, Loader=yaml.Loader)
            self.settings = yml['config']
            for name, profile in yml['profiles'].items():
                self.profiles[name] = Profile(name, profile)
                parent_names = self.profiles[name].include_profiles
                for parent_name in parent_names:
                    if parent_name not in self.profiles:
                        print(
                            f'Profile error ({name}: included "{parent_name}" not defined'
                        )
                        exit(1)
                    self.profiles[name].include(self.profiles[parent_name])

            for name, rule in yml['rules'].items():
                self.rules[name] = Rule(name, rule)

            if 'queues' in self.settings:
                self.queues = self.settings['queues']
            else:
                self.queues = dict()
예제 #2
0
 def test_profile_merge(self):
     p1 = Profile("p1", {"one": 1, "two": 2, "input_options": ["three", "four", "five"]})
     p2 = Profile("p2", {"one": 11, "six": 6, "input_options": ["seven", "four"]})
     p2.include(p1)
     self.assertEqual(p2.get("one"), 11, 'expected 11 for "one"')
     self.assertEqual(p2.get("two"), 2, 'expected 2 for "two"')
     self.assertEqual(p2.get("six"), 6, 'expected 6 for "six"')
     op2 = sorted(p2.input_options.as_list())
     expected = sorted(["three", "four", "five", "seven"])
     self.assertEqual(op2, expected, "Unexpected input_options merger")
예제 #3
0
    def ffmpeg_streams(self, profile: Profile) -> list:
        excl_audio = profile.excluded_audio()
        excl_subtitle = profile.excluded_subtitles()
        incl_audio = profile.included_audio()
        incl_subtitle = profile.included_subtitles()

        defl_audio = profile.default_audio()
        defl_subtitle = profile.default_subtitle()

        if excl_audio is None:
            excl_audio = []
        if excl_subtitle is None:
            excl_subtitle = []
        #
        # if no inclusions or exclusions just map everything
        #
        if len(incl_audio) == 0 and len(excl_audio) == 0 and len(
                incl_subtitle) == 0 and len(excl_subtitle) == 0:
            return ['-map', '0']

        seq_list = list()
        seq_list.append('-map')
        seq_list.append(f'0:{self.stream}')
        audio_streams = self._map_streams("a", self.audio, excl_audio,
                                          incl_audio, defl_audio)
        subtitle_streams = self._map_streams("s", self.subtitle, excl_subtitle,
                                             incl_subtitle, defl_subtitle)
        return seq_list + audio_streams + subtitle_streams
예제 #4
0
def rule_hook(mediainfo: MediaInfo) -> Optional[Profile]:

    #
    # skip of already hevc
    #
    if mediainfo.vcodec == "hevc":
        raise ProfileSKIP()

    #
    # small enough already
    #
    if mediainfo.filesize_mb < 2500 and 720 < mediainfo.res_height < 1081 and 30 < mediainfo.runtime < 65:
        raise ProfileSKIP()

    #
    # skip video if resolution < 700:  # don't re-encode something this small - no gain in it
    #
    if mediainfo.res_height < 700:
        raise ProfileSKIP()

    #
    # Here is a complex example you can only perform with a custom hook.
    #
    # strip redundant HD audio tracks from 4k to make it smaller
    #
    if mediainfo.vcodec == 'hevc' and mediainfo.res_height >= 2160 and len(
            mediainfo.audio) > 1:
        profile = Profile("strip-DTS")

        truehd_track = None
        ac3_track = None
        for track in mediainfo.audio:
            if track['lang'] != 'eng':
                continue
            if track['format'] == 'truehd':
                truehd_track = track['stream']
            elif track['format'] == 'ac3':
                ac3_track = track['stream']

        # if both English tracks exist, eliminate truehd and keep ac3 - don't transcode video
        if truehd_track and ac3_track:
            profile.automap = False  # override default
            profile.output_options.merge({
                '-map': ac3_track,
                '-c:v': 'copy',
                '-c:s': 'copy',
                '-c:a': 'copy',
                '-f': 'matroksa'
            })
            profile.extension = '.mkv'
            return profile

    #
    # anime
    #
    if '/anime/' in mediainfo.path:
        profile = Profile("anime")
        profile.include(common)  # include: common
        profile.output_options.merge([
            "-c:v hevc_nvenc", "-profile:v main", "-preset medium", "-crf 20"
        ])
        profile.queue_name = "cuda"  # queue: cuda
        profile.automap = True  # automap: yes
        return profile

    #
    # high frame rate
    #
    if mediainfo.fps > 30 and mediainfo.filesize_mb > 500:
        profile = Profile("high-frame-rate")
        profile.include(common)  # include: common
        profile.output_options.merge([
            "-c:v hevc_nvenc", "-profile:v main", "-preset medium", "-crf 20",
            "-r 30"
        ])
        profile.queue_name = "cuda"

    #
    # default to no profile to continue on and evaluate defined rules next (transcoder.yml)
    #
    return None
예제 #5
0
from typing import Optional

from pytranscoder.media import MediaInfo
from pytranscoder.profile import Profile, ProfileSKIP

common = Profile(
    "common",
    {
        'extension': '.mkv',
        'threshold':
        20,  # 20% minimum size reduction %, otherwise source is preserved as-is
        'threshold_check':
        60,  # start checking threshold at 60% done, kill job if threshold not met
        'input_options': ['-hwaccel cuvid'],
        'output_options': [
            "-f matroska",
            "-c:a copy",
            "-c:s copy",
        ]
    })


##
# Example hook that replicates functionality of rules in transcode.yml sample, only using pure Python.
# Here you have the full flexibility of Python for your rule tests and profile creation.
# Profile names here are more for your visual verification than anything functional.
#
# The result of calling rule_hook() should be one of:
#   - raise ProfileSKIP(), which raises an exception telling pytranscoder to skip the file
#   - return a valid Profile of your custom settings
#   - return None, indicating no match was made and to continue by evaluating the transcoder.yml rules.