Exemple #1
0
    def test_h265_1080p_5_1(self):
        length = 3
        with create_test_video(length=length,
                               video_def=VideoDefinition(
                                   Resolution.HIGH_DEF, VideoCodec.H265,
                                   VideoFileContainer.MKV),
                               audio_defs=[
                                   AudioDefition(AudioCodec.AAC,
                                                 AudioChannelName.SURROUND_5_1)
                               ]) as file:
            metadata = create_metadata_extractor().extract(file.name)
            self.assertEqual(1, len(metadata.video_streams))
            self.assertEqual(1, len(metadata.audio_streams))

            v = metadata.video_streams[0]
            a = metadata.audio_streams[0]

            self.assertEqual(VideoCodec.H265.ffmpeg_codec_name, v.codec)
            self.assertEqual(length, v.duration)
            self.assertEqual(Resolution.HIGH_DEF.width, v.width)
            self.assertEqual(Resolution.HIGH_DEF.height, v.height)

            self.assertEqual(AudioCodec.AAC.ffmpeg_codec_name, a.codec)
            assertAudioLength(length, a.duration)
            self.assertEqual(6, a.channels)
Exemple #2
0
    def process(self, wtv_file: str, com_file: str, srt_file: str):
        # Ensure TVDB client is authenticated
        self.tvdb.refresh()

        series, episode_name, season, episode_num, metadata = self.get_metadata(
            wtv_file)
        if series is not None and season is not None and episode_num is not None:
            # Detect interlace
            create_metadata_extractor().add_interlace_report(metadata)
            filename = os.path.basename(wtv_file)
            filename_wo_ext = os.path.splitext(filename)[0]
            out_video = os.path.join(
                self.out_dir,
                create_filename(self.template, series, season, episode_num,
                                episode_name, filename_wo_ext, 'mp4'))
            out_srt = os.path.join(
                self.out_dir,
                create_filename(self.template, series, season, episode_num,
                                episode_name, filename_wo_ext, 'eng.srt'))

            if not os.path.exists(os.path.dirname(out_video)):
                os.makedirs(os.path.dirname(out_video))
            if not os.path.exists(os.path.dirname(out_srt)):
                os.makedirs(os.path.dirname(out_srt))

            commercials = parse_commercial_file(com_file)
            split_subtitles(srt_file, invert_commercial(commercials), out_srt)
            successful = self.convert(wtv_file, out_video, commercials,
                                      metadata)
            if successful:
                # If we finished with the WTV, delete it
                if self.wtvdb.get_wtv(filename) is not None:
                    self.wtvdb.delete_wtv(filename)
                if not self.debug and self.delete_source:
                    os.remove(wtv_file)
                    os.remove(com_file)
                    os.remove(srt_file)
                logger.info('Completed {} => {}'.format(wtv_file, out_video))
            else:
                logger.warning('Failure to convert {}'.format(wtv_file))
        else:
            logger.warning(
                'Missing data for {}: series={}, episode_name={}, season={}, episode_num={}'
                .format(wtv_file, series, episode_name, season, episode_num))
def print_metadata(input, show_popup=False, interlace='none'):
    extractor = create_metadata_extractor()
    meta = extractor.extract(input, interlace != 'none')
    o = []

    o.append(os.path.basename(input))
    o.append(output('Directory: {}', os.path.dirname(input)))
    size = os.path.getsize(input)
    if meta.title:
        o.append(output('Title: {}', meta.title))
    o.append(output('Size: {}', sizeof_fmt(size)))
    o.append(output('Format: {}', meta.format))

    durations = [float(s.duration) for s in meta.streams if s.duration]
    if len(durations) > 0:
        o.append(output('Duration: {}', duration_to_str(max(durations))))

    o.append(output('Bitrate: {}', bitrate_to_str(meta.bit_rate)))
    for video in meta.video_streams:
        if video.bit_depth:
            o.append(output('Video: {} {} bit ({}x{})', video.codec,video.bit_depth, video.width, video.height))
        else:
            o.append(output('Video: {} ({}x{})', video.codec, video.width, video.height))

    audio_streams = []
    for audio in meta.audio_streams:
        audio_streams.append((audio.codec, audio.language, audio.channel_layout))
    audio_streams.sort()
    o.append(output('Audio:'))
    for a in audio_streams:
        o.append(output('  {} ({}, {})', *a))

    subtitles = [s.language for s in meta.subtitle_streams]
    if len(subtitles) == 0:
        subtitles = ['None']
    o.append(output('Subtitles: {}', ', '.join(subtitles)))
    o.append(output('Ripped: {}', meta.ripped))

    if meta.interlace_report:
        if interlace == 'summary':
            o.append(output('Interlaced: {}', meta.interlace_report.is_interlaced()))
        elif interlace == 'report':
            o.append(output('Interlaced:'))
            single = meta.interlace_report.single
            o.append(output('  Single: TFF={}, BFF={}, Progressive={}, Undetermined={} ({:.2f}%)', single.tff, single.bff,
                            single.progressive, single.undetermined, single.ratio * 100))
            multi = meta.interlace_report.multi
            o.append(output('  Multi: TFF={}, BFF={}, Progressive={}, Undetermined={} ({:.2f}%)', multi.tff, multi.bff,
                            multi.progressive, multi.undetermined, multi.ratio * 100))

    final = '\n'.join(o)
    if show_popup:
        popup(final)
    else:
        print(final)
Exemple #4
0
def _map_metadata(input_files, meta_shelve=None) -> Dict[str, Metadata]:
    extractor = create_metadata_extractor()
    ret = {}
    for file in input_files:
        if meta_shelve and file in meta_shelve:
            ret[file] = meta_shelve[file]
        elif meta_shelve:
            ret[file] = meta_shelve[file] = extractor.extract(file)
        else:
            ret[file] = extractor.extract(file)
    return ret
Exemple #5
0
    def test_metadata_in_file(self):
        meta = {'TestKey': 'test_value'}
        length = 2
        with create_test_video(length=length,
                               video_def=VideoDefinition(
                                   Resolution.HIGH_DEF, VideoCodec.H264,
                                   VideoFileContainer.WTV),
                               metadata=meta) as file:
            metadata = create_metadata_extractor().extract(file.name)
            print(metadata.tags)
            self.assertTrue('TestKey' in metadata.tags)
            self.assertEqual('test_value', metadata.tags['TestKey'])

        with create_test_video(length=length,
                               video_def=VideoDefinition(
                                   Resolution.HIGH_DEF, VideoCodec.H264,
                                   VideoFileContainer.MKV),
                               metadata=meta) as file:
            metadata = create_metadata_extractor().extract(file.name)
            print(metadata.tags)
            # MKV stores as uppercase
            self.assertTrue('TESTKEY' in metadata.tags)
            self.assertEqual('test_value', metadata.tags['TESTKEY'])
Exemple #6
0
    def test_2_files(self):
        with create_test_video(length=5, video_def=MP4_VIDEO_DEF) as first, \
                create_test_video(length=5, video_def=MP4_VIDEO_DEF) as second:
            with NamedTemporaryFile(suffix='.mp4') as output:
                concat_mp4(output.name,
                           files=[first.name, second.name],
                           overwrite=True)
                metadata = create_metadata_extractor().extract(output.name)
                self.assertEqual(1, len(metadata.video_streams))
                self.assertEqual(10, int(metadata.video_streams[0].duration))

                self.assertEqual(1, len(metadata.audio_streams))
                self.assertEqual('aac', metadata.audio_streams[0].codec)
                self.assertEqual(2, metadata.audio_streams[0].channels)
                assertAudioLength(10, metadata.audio_streams[0].duration)
Exemple #7
0
    def __init__(self, api_key=None, config_file=None):
        if api_key is not None:
            tmdb.API_KEY = api_key
        else:
            import configparser
            if config_file is None:
                config_file = os.path.expanduser(
                    '~/.config/moviedb/moviedb.ini')
                if not os.path.exists(config_file):
                    raise Exception(
                        'Must provide either api_key or config_file')
            config = configparser.ConfigParser()
            config.read(config_file)
            tmdb.API_KEY = config.get('moviedb', 'apikey')

        self.extractor = create_metadata_extractor()
Exemple #8
0
    def get_metadata(self, wtv_file: str) -> Tuple[str, str, int, int]:
        # Will detect deinterlace if we actually have to process this file
        metadata = create_metadata_extractor().extract(wtv_file,
                                                       detect_interlace=False)
        series = metadata.tags.get('Title', None)
        episode_name = metadata.tags.get('WM/SubTitle', None)
        # WM/SubTitleDescription

        filename = os.path.basename(wtv_file)
        wtv_obj = self.wtvdb.get_wtv(filename)
        if wtv_obj and wtv_obj.selected_episode:
            ep = wtv_obj.selected_episode.episode
            season = ep.season
            episode_num = ep.episode_num
            if episode_name is None:
                episode_name = ep.name
        elif series is not None:
            # Get season & episode number
            air_date = extract_original_air_date(wtv_file,
                                                 parse_from_filename=True,
                                                 metadata=metadata)
            episodes = self.tvdb.find_episode(series,
                                              episode=episode_name,
                                              air_date=air_date)
            if len(episodes) == 1:
                season, episode_num = tvdb_api.TVDB.season_number(episodes[0])
                if episodes[0]['episodeName'] is not None:
                    episode_name = episodes[0]['episodeName']
            else:
                # Handle multiple options
                self.wtvdb.store_candidates(self.tvdb, filename, metadata,
                                            episodes)
                season = None
                episode_num = None
        else:
            season = None
            episode_num = None

        # Try searching the description
        if season is None and episode_num is None and 'WM/SubTitleDescription' in metadata.tags:
            season, episode_num, _ = extract_season_ep(
                metadata.tags['WM/SubTitleDescription'])

        if episode_name is None and episode_num is not None:
            episode_name = 'Episode #{}'.format(episode_num)

        return series, episode_name, season, episode_num, metadata
    def subexecute(self, ns):
        from media_management_scripts.utils import create_metadata_extractor
        from media_management_scripts.support.files import get_input_output, list_files
        from media_management_scripts.support.formatting import bitrate_to_str

        src_dir = ns['source']
        dst_dir = ns['destination']
        dst_files = list(list_files(dst_dir))
        meta_db = ns.get('db', None)

        table = []
        extractor = create_metadata_extractor(meta_db)
        for src_file, dst_file in get_input_output(src_dir, dst_dir):
            row = []
            src_meta = extractor.extract(src_file)
            src_video = src_meta.video_streams[0]

            row.append(os.path.basename(src_file))
            row.append(src_video.codec)
            row.append('{}x{}'.format(src_video.width, src_video.height))
            row.append(bitrate_to_str(src_meta.bit_rate))
            #row.append(dst_file)

            if os.path.exists(dst_file):
                dst_meta = extractor.extract(dst_file)
                dst_video = dst_meta.video_streams[0]
                row.append(dst_video.codec)
                row.append('{}x{}'.format(dst_video.width, dst_video.height))
                row.append(bitrate_to_str(dst_meta.bit_rate))
            else:
                row.append('')
                row.append('')
                row.append('')
            table.append(tuple(row))

        columns = [
            'Source',
            'Src Codec',
            'Src Resolution',
            'Src Bitrate',  #'Destination',
            'Dest Codec',
            'Dest Resolution',
            'Dest Bitrate'
        ]
        self._bulk_print(table, columns)
Exemple #10
0
    def test_h264_stereo(self):
        length = 5
        with create_test_video(length=length) as file:
            metadata = create_metadata_extractor().extract(file.name)
            self.assertEqual(1, len(metadata.video_streams))
            self.assertEqual(1, len(metadata.audio_streams))

            v = metadata.video_streams[0]
            a = metadata.audio_streams[0]

            self.assertEqual(VideoCodec.H264.ffmpeg_codec_name, v.codec)
            self.assertEqual(length, v.duration)
            self.assertEqual(Resolution.LOW_DEF.width, v.width)
            self.assertEqual(Resolution.LOW_DEF.height, v.height)

            self.assertEqual(AudioCodec.AAC.ffmpeg_codec_name, a.codec)
            assertAudioLength(length, a.duration)
            self.assertEqual(2, a.channels)
Exemple #11
0
def search(input_dir: str, query: str, db_file: str = None, recursive=False):
    from media_management_scripts.support.search_parser import parse
    from media_management_scripts.utils import create_metadata_extractor
    from media_management_scripts.support.files import list_files
    query = parse(query)
    db_exists = os.path.exists(db_file) if db_file else False
    with create_metadata_extractor(db_file) as extractor:
        if recursive:
            files = list_files(input_dir, _filter)
        else:
            files = [x for x in os.listdir(input_dir) if _filter(os.path.join(input_dir, x))]
        for file in files:
            path = os.path.join(input_dir, file)
            if db_exists and os.path.samefile(db_file, path):
                # Skip if db file is in the same directory
                continue
            try:
                metadata = extractor.extract(path)
                context = {
                    'v': {
                        'codec': [v.codec for v in metadata.video_streams],
                        'width': [v.width for v in metadata.video_streams],
                        'height': [v.height for v in metadata.video_streams]
                    },
                    'a': {
                        'codec': [a.codec for a in metadata.audio_streams],
                        'channels': [a.channels for a in metadata.audio_streams],
                        'lang': [a.language for a in metadata.audio_streams],
                    },
                    's': {
                        'codec': [s.codec for s in metadata.subtitle_streams],
                        'lang': [s.language for s in metadata.subtitle_streams]
                    },
                    'ripped': metadata.ripped,
                    'bit_rate': metadata.bit_rate,
                    'resolution': metadata.resolution._name_,
                    'meta': metadata.to_dict()

                }
                if query.exec(context) is True:
                    yield file, metadata, True
            except Exception:
                yield file, None, False
Exemple #12
0
def extract_original_air_date(wtv_file,
                              parse_from_filename=True,
                              metadata=None):
    if metadata is None:
        metadata = create_metadata_extractor().extract(wtv_file)
    # WM/MediaOriginalBroadcastDateTime=2012-10-13T04:00:00Z
    air_date = None

    if ORIGINAL_BROADCAST_DATE_KEY in metadata.tags:
        air_date = metadata.tags[ORIGINAL_BROADCAST_DATE_KEY]
    if air_date is None or air_date == '0001-01-01T00:00:00Z':
        # Extract from filename
        if parse_from_filename:
            split = os.path.basename(wtv_file).split('_')
            air_date = split[2] + '-' + split[3] + '-' + split[4]
        else:
            air_date = None
    else:
        air_date = air_date.split('T')[0]
    return air_date
def _get_new_name(input_to_cmd) -> NameInformation:
    metadata = create_metadata_extractor().extract(input_to_cmd)
    result = None
    title = metadata.title
    if title:
        code, result = _search(title, input_to_cmd, metadata)
        if code == CANCEL:
            return None
    while result is None:
        title = ''
        d = Dialog(autowidgetsize=True)
        exit_code, title = d.inputbox('No matches found. Try a different title?', init=title,
                                      title=os.path.basename(input_to_cmd))
        if exit_code == d.OK:
            code, result = _search(title, input_to_cmd, metadata)
            if code == CANCEL:
                return None
        else:
            return None

    return result
Exemple #14
0
def split_by_chapter(input, output_dir, chapters=4, initial_count=0):
    extractor = create_metadata_extractor()
    metadata = extractor.extract(input)

    num_chapters = len(metadata.chapters)
    if num_chapters % chapters != 0:
        raise Exception('Cannot evenly split {} by {} - {} chapters'.format(input, chapters, num_chapters))
    count = initial_count
    for i in range(0, num_chapters, chapters):
        if i != 0:
            start = metadata.chapters[i].start_time
        else:
            start = None
        if i + chapters < num_chapters:
            end = metadata.chapters[i + chapters - 1].end_time
        else:
            end = None
        output_file = os.path.join(output_dir, 'title{0:02d}.mkv'.format(count))
        count += 1
        cut(input, output_file, start, end)
    return num_chapters // chapters
Exemple #15
0
    def test_mpeg2(self):
        length = 5
        with create_test_video(
                length=length,
                video_def=VideoDefinition(codec=VideoCodec.MPEG2)) as file:
            metadata = create_metadata_extractor().extract(file.name, True)
            self.assertEqual(1, len(metadata.video_streams))
            self.assertEqual(1, len(metadata.audio_streams))
            self.assertFalse(metadata.interlace_report.is_interlaced())

            v = metadata.video_streams[0]
            a = metadata.audio_streams[0]

            self.assertEqual(VideoCodec.MPEG2.ffmpeg_codec_name, v.codec)
            self.assertEqual(length, v.duration)
            self.assertEqual(Resolution.LOW_DEF.width, v.width)
            self.assertEqual(Resolution.LOW_DEF.height, v.height)

            self.assertEqual(AudioCodec.AAC.ffmpeg_codec_name, a.codec)
            assertAudioLength(length, a.duration)
            self.assertEqual(2, a.channels)
def create_table_object(input_to_cmd, interlace='none'):
    from media_management_scripts.utils import create_metadata_extractor
    from media_management_scripts.support.formatting import sizeof_fmt, duration_to_str
    extractor = create_metadata_extractor()
    metadatas = [
        extractor.extract(i, interlace != 'none') for i in input_to_cmd
    ]
    header = [''] + [os.path.basename(f.file) for f in metadatas]
    num_audio = max([len(m.audio_streams) for m in metadatas])
    rows = [
        'Size', 'Duration', 'Bitrate (kb/s)', 'Video Codec', 'Resolution',
        'Audio'
    ]
    for i in range(1, num_audio):
        rows.append('')
    rows.append('Subtitles')
    file_columns = [rows]
    first_size = os.path.getsize(metadatas[0].file)
    for m in metadatas:
        data = []
        size = os.path.getsize(m.file)
        size_ratio = '{:.1f}%'.format(size / first_size * 100)
        data.append('{} ({})'.format(sizeof_fmt(size), size_ratio))
        data.append(
            duration_to_str(m.estimated_duration) if m.
            estimated_duration else '')
        data.append('{:.2f}'.format(m.bit_rate / 1024.0))
        video = m.video_streams[0]
        data.append(video.codec)
        data.append('{}x{}'.format(video.width, video.height))
        for a in m.audio_streams:
            data.append('{} ({}, {})'.format(a.codec, a.language,
                                             a.channel_layout))
        for i in range(len(m.audio_streams), num_audio):
            data.append('')
        data.append(','.join([s.language for s in m.subtitle_streams]))
        file_columns.append(data)
    table = list(map(list, zip(*file_columns)))
    return header, table
def print_metadata_json(input, interlace='none'):
    extractor = create_metadata_extractor()
    meta = extractor.extract(input, interlace != 'none')
    print(json.dumps(meta, cls=Encoder))
def convert_with_config(input,
                        output,
                        config: ConvertConfig,
                        print_output=True,
                        overwrite=False,
                        metadata=None,
                        mappings=None,
                        use_nice=True):
    """

    :param input:
    :param output:
    :param config:
    :param print_output:
    :param overwrite:
    :param metadata:
    :param mappings: List of mappings (for example ['0:0', '0:1'])
    :return:
    """
    if not overwrite and check_exists(output):
        return -1
    if print_output:
        print('Converting {} -> {}'.format(input, output))
        print('Using config: {}'.format(config))

    if not metadata:
        metadata = create_metadata_extractor().extract(
            input, detect_interlace=config.deinterlace)
    elif config.deinterlace and not metadata.interlace_report:
        raise Exception(
            'Metadata provided without interlace report, but convert requires deinterlace checks'
        )

    if metadata.resolution not in (Resolution.LOW_DEF, Resolution.STANDARD_DEF,
                                   Resolution.MEDIUM_DEF,
                                   Resolution.HIGH_DEF) and not config.scale:
        print('{}: Resolution not supported for conversion: {}'.format(
            input, metadata.resolution))
        # TODO Handle converting 4k content in H.265/HVEC
        return -2
    if use_nice and nice_exe:
        args = [nice_exe, ffmpeg()]
    else:
        args = [ffmpeg()]
    if overwrite:
        args.append('-y')
    args.extend(['-i', input])

    if config.scale:
        args.extend(['-vf', 'scale=-1:{}'.format(config.scale)])

    args.extend(['-c:v', config.video_codec])
    crf = config.crf
    bitrate = config.bitrate
    if VideoCodec.H264.equals(
            config.video_codec
    ) and config.bitrate is not None and config.bitrate != 'disabled':
        crf = 1
        # -x264-params vbv-maxrate=1666:vbv-bufsize=3332:crf-max=22:qpmax=34
        if config.bitrate == 'auto':
            bitrate = auto_bitrate_from_config(metadata.resolution, config)
        params = 'vbv-maxrate={}:vbv-bufsize={}:crf-max=25:qpmax=34'.format(
            str(bitrate), str(bitrate * 2))
        args.extend(['-x264-params', params])
    elif VideoCodec.H265.equals(
            config.video_codec
    ) and config.bitrate is not None and config.bitrate != 'disabled':
        raise Exception('Avg Bitrate not supported for H265')

    args.extend(['-crf', str(crf), '-preset', config.preset])
    if config.deinterlace:
        is_interlaced = metadata.interlace_report.is_interlaced(
            config.deinterlace_threshold)
        if print_output:
            print('{} - Interlaced: {}'.format(metadata.interlace_report,
                                               is_interlaced))
        if is_interlaced:
            # Video is interlaced, so add the deinterlace filter
            args.extend(['-vf', 'yadif'])

    args.extend(['-c:a', config.audio_codec])

    index = 0
    for audio in metadata.audio_streams:
        if audio.channels == 7:
            # 6.1 sound, so mix it up to 7.1
            args.extend(['-ac:a:{}'.format(index), '8'])
        index += 1

    if config.include_subtitles:
        args.extend(['-c:s', 'copy'])

    if not mappings:
        args.extend(['-map', '0'])
    else:
        for m in mappings:
            if type(m) == int:
                args.extend(['-map', '0:{}'.format(m)])
            else:
                args.extend(['-map', m])

    if config.include_meta:
        args.extend(['-metadata', 'ripped=true'])
        args.extend(['-metadata:s:v:0', 'ripped=true'])
    args.append(output)

    return execute(args, print_output)