Exemple #1
0
    def __init__(self, ffmpeg_path=None, ffprobe_path=None):
        """
        Initialize a new Converter object.
        """

        self.ffmpeg = FFMpeg(ffmpeg_path=ffmpeg_path,
                             ffprobe_path=ffprobe_path)
        self.video_codecs = {}
        self.audio_codecs = {}
        self.subtitle_codecs = {}
        self.attachment_codecs = {}
        self.formats = {}

        for cls in audio_codec_list:
            name = cls.codec_name
            self.audio_codecs[name] = cls

        for cls in video_codec_list:
            name = cls.codec_name
            self.video_codecs[name] = cls

        for cls in subtitle_codec_list:
            name = cls.codec_name
            self.subtitle_codecs[name] = cls

        for cls in attachment_codec_list:
            name = cls.codec_name
            self.attachment_codecs[name] = cls

        for cls in format_list:
            name = cls.format_name
            self.formats[name] = cls
    def __init__(self, ffmpeg_path=None, ffprobe_path=None):
        """
        Initialize a new Converter object.
        """

        self.ffmpeg = FFMpeg(ffmpeg_path=ffmpeg_path,
                             ffprobe_path=ffprobe_path)
        self.video_codecs = {}
        self.audio_codecs = {}
        self.subtitle_codecs = {}
        self.formats = {}

        for cls in audio_codec_list:
            name = cls.codec_name
            self.audio_codecs[name] = cls

        for cls in video_codec_list:
            name = cls.codec_name
            self.video_codecs[name] = cls

        for cls in subtitle_codec_list:
            name = cls.codec_name
            self.subtitle_codecs[name] = cls

        for cls in format_list:
            name = cls.format_name
            self.formats[name] = cls
Exemple #3
0
    def start(self):
        """
        Start encoding.

        :raises: :py:exc:`~encode.EncodeError` if something goes wrong
            during encoding.
        """
        command = shlex.split(self.profile.command)

        try:
            ffmpeg = FFMpeg(self.profile.encoder.path)
            job = ffmpeg.convert(self.input_path, self.output_path, command)
            for timecode in job:
                logger.debug("Encoding (time: %f)...\r" % timecode)

        except FFMpegError as error:
            exc = self._build_exception(error, self.profile.command)
            raise exc

        except FFMpegConvertError as error:
            exc = self._build_exception(error.details, self.profile.command)
            raise exc
Exemple #4
0
    def __init__(self, ffmpeg_path=None, ffprobe_path=None):
        '''Initialize a new Converter object.'''
        self.ffmpeg = FFMpeg(ffmpeg_path=ffmpeg_path,
                             ffprobe_path=ffprobe_path)
        self.video_codecs = {}
        self.audio_codecs = {}
        self.subtitle_codecs = {}
        self.formats = {}

        for cls in codec_lists['audio']:
            name = cls.codec_name
            self.audio_codecs[name] = cls

        for cls in codec_lists['video']:
            name = cls.codec_name
            self.video_codecs[name] = cls

        for cls in codec_lists['subtitle']:
            name = cls.codec_name
            self.subtitle_codecs[name] = cls

        for cls in format_list:
            name = cls.format_name
            self.formats[name] = cls
Exemple #5
0
class Converter(object):
    """
    Converter class, encapsulates formats and codecs.

    >>> c = Converter()
    """
    def __init__(self, ffmpeg_path=None, ffprobe_path=None):
        """
        Initialize a new Converter object.
        """

        self.ffmpeg = FFMpeg(ffmpeg_path=ffmpeg_path,
                             ffprobe_path=ffprobe_path)
        self.video_codecs = {}
        self.audio_codecs = {}
        self.subtitle_codecs = {}
        self.attachment_codecs = {}
        self.formats = {}

        for cls in audio_codec_list:
            name = cls.codec_name
            self.audio_codecs[name] = cls

        for cls in video_codec_list:
            name = cls.codec_name
            self.video_codecs[name] = cls

        for cls in subtitle_codec_list:
            name = cls.codec_name
            self.subtitle_codecs[name] = cls

        for cls in attachment_codec_list:
            name = cls.codec_name
            self.attachment_codecs[name] = cls

        for cls in format_list:
            name = cls.format_name
            self.formats[name] = cls

    def ffmpeg_codec_name_to_codec_name(self, type, ffmpeg_codec_name):
        if type == 'video':
            return next((x.codec_name for x in video_codec_list
                         if x.ffmpeg_codec_name == ffmpeg_codec_name), None)
        elif type == 'audio':
            return next((x.codec_name for x in audio_codec_list
                         if x.ffmpeg_codec_name == ffmpeg_codec_name), None)
        elif type == 'subtitle':
            return next((x.codec_name for x in subtitle_codec_list
                         if x.ffmpeg_codec_name == ffmpeg_codec_name), None)
        elif type == 'attachment':
            return next((x.codec_name for x in attachment_codec_list
                         if x.ffmpeg_codec_name == ffmpeg_codec_name), None)
        return None

    def parse_options(self, opt, twopass=None):
        """
        Parse format/codec options and prepare raw ffmpeg option list.
        """
        format_options = None
        audio_options = []
        video_options = []
        subtitle_options = []
        attachment_options = []
        source_options = []

        if not isinstance(opt, dict):
            raise ConverterError('Invalid output specification')

        if 'format' not in opt:
            raise ConverterError('Format not specified')

        f = opt['format']
        if f not in self.formats:
            raise ConverterError('Requested unknown format: ' + str(f))

        format_options = self.formats[f]().parse_options(opt)
        if format_options is None:
            raise ConverterError('Unknown container format error')

        if 'source' not in opt or len(opt['source']) < 1:
            raise ConverterError('No source file provided')

        if 'audio' not in opt and 'video' not in opt and 'subtitle' not in opt:
            raise ConverterError(
                'Neither audio nor video nor subtitle streams requested')

        # Sources
        if 'source' in opt:
            y = opt['source']

            if isinstance(y, str):
                y = [y]

            for x in y:
                if not os.path.exists(x):
                    raise ConverterError('Souce file does not exist')
                source_options.extend(['-i', x])

        # Audio
        if 'audio' in opt:
            y = opt['audio']

            # Creates the new nested dictionary to preserve backwards compatability
            if isinstance(y, dict):
                y = [y]

            for x in y:
                if not isinstance(x, dict) or 'codec' not in x:
                    raise ConverterError('Invalid audio codec specification')

                c = x['codec']
                if c not in self.audio_codecs:
                    raise ConverterError('Requested unknown audio codec ' +
                                         str(c))

                audio_options.extend(self.audio_codecs[c]().parse_options(
                    x, y.index(x)))
                if audio_options is None:
                    raise ConverterError('Unknown audio codec error')

        # Subtitle
        if 'subtitle' in opt:
            y = opt['subtitle']

            # Creates the new nested dictionary to preserve backwards compatability
            if isinstance(y, dict):
                y = [y]

            for x in y:
                if not isinstance(x, dict) or 'codec' not in x:
                    raise ConverterError(
                        'Invalid subtitle codec specification')

                c = x['codec']
                if c not in self.subtitle_codecs:
                    raise ConverterError('Requested unknown subtitle codec ' +
                                         str(c))

                subtitle_options.extend(
                    self.subtitle_codecs[c]().parse_options(x, y.index(x)))
                if subtitle_options is None:
                    raise ConverterError('Unknown subtitle codec error')

        # Attachments
        if 'attachment' in opt:
            y = opt['attachment']

            # Creates the new nested dictionary to preserve backwards compatability
            if isinstance(y, dict):
                y = [y]

            for x in y:
                if not isinstance(x, dict) or 'codec' not in x:
                    raise ConverterError(
                        'Invalid attachment codec specification')

                c = x['codec']
                if c not in self.attachment_codecs:
                    raise ConverterError(
                        'Requested unknown attachment codec ' + str(c))

                attachment_options.extend(
                    self.attachment_codecs[c]().parse_options(x, y.index(x)))
                if attachment_options is None:
                    raise ConverterError('Unknown attachment codec error')

        if 'video' in opt:
            x = opt['video']
            if not isinstance(x, dict) or 'codec' not in x:
                raise ConverterError('Invalid video codec specification')

            c = x['codec']
            if c not in self.video_codecs:
                raise ConverterError('Requested unknown video codec ' + str(c))

            video_options = self.video_codecs[c]().parse_options(x)
            if video_options is None:
                raise ConverterError('Unknown video codec error')

        # aggregate all options
        optlist = source_options + video_options + audio_options + subtitle_options + attachment_options + format_options

        if twopass == 1:
            optlist.extend(['-pass', '1'])
        elif twopass == 2:
            optlist.extend(['-pass', '2'])

        return optlist

    def convert(self,
                outfile,
                options,
                twopass=False,
                timeout=10,
                preopts=None,
                postopts=None):
        """
        Convert media file (infile) according to specified options, and
        save it to outfile. For two-pass encoding, specify the pass (1 or 2)
        in the twopass parameter.

        Options should be passed as a dictionary. The keys are:
            * format (mandatory, string) - container format; see
              formats.BaseFormat for list of supported formats
            * audio (optional, dict) - audio codec and options; see
              avcodecs.AudioCodec for list of supported options
            * video (optional, dict) - video codec and options; see
              avcodecs.VideoCodec for list of supported options
            * map (optional, int) - can be used to map all content of stream 0

        Multiple audio/video streams are not supported. The output has to
        have at least an audio or a video stream (or both).

        Convert returns a generator that needs to be iterated to drive the
        conversion process. The generator will periodically yield timecode
        of currently processed part of the file (ie. at which second in the
        content is the conversion process currently).

        The optional timeout argument specifies how long should the operation
        be blocked in case ffmpeg gets stuck and doesn't report back. This
        doesn't limit the total conversion time, just the amount of time
        Converter will wait for each update from ffmpeg. As it's usually
        less than a second, the default of 10 is a reasonable default. To
        disable the timeout, set it to None. You may need to do this if
        using Converter in a threading environment, since the way the
        timeout is handled (using signals) has special restriction when
        using threads.

        >>> conv = Converter().convert('test1.ogg', '/tmp/output.mkv', {
        ...    'format': 'mkv',
        ...    'audio': { 'codec': 'aac' },
        ...    'video': { 'codec': 'h264' }
        ... })

        >>> for timecode in conv:
        ...   pass # can be used to inform the user about the progress
        """

        if not isinstance(options, dict):
            raise ConverterError('Invalid options')

        if 'source' not in options:
            raise ConverterError('No source specified')

        infile = options['source'][0]

        info = self.ffmpeg.probe(infile)
        if info is None:
            raise ConverterError("Can't get information about source file")

        if not info.video and not info.audio and not info.subtitle:
            raise ConverterError(
                'Source file has no audio, video, or subtitle streams')

        if info.video and 'video' in options:
            options = options.copy()
            v = options['video'] = options['video'].copy()
            v['src_width'] = info.video.video_width
            v['src_height'] = info.video.video_height

        if not info.format.duration:
            info.format.duration = 0.01

        if info.video and info.format.duration < 0.01:
            raise ConverterError('Zero-length media')

        if twopass:
            optlist1 = self.parse_options(options, 1)
            for timecode in self.ffmpeg.convert(outfile,
                                                optlist1,
                                                timeout=timeout,
                                                preopts=preopts,
                                                postopts=postopts):
                yield int((50.0 * timecode) / info.format.duration)

            optlist2 = self.parse_options(options, 2)
            for timecode in self.ffmpeg.convert(outfile,
                                                optlist2,
                                                timeout=timeout,
                                                preopts=preopts,
                                                postopts=postopts):
                yield int(50.0 + (50.0 * timecode) / info.format.duration)
        else:
            optlist = self.parse_options(options, twopass)
            for timecode in self.ffmpeg.convert(outfile,
                                                optlist,
                                                timeout=timeout,
                                                preopts=preopts,
                                                postopts=postopts):
                yield int((100.0 * timecode) / info.format.duration)

    def probe(self, fname, posters_as_video=True):
        """
        Examine the media file. See the documentation of
        converter.FFMpeg.probe() for details.

        :param posters_as_video: Take poster images (mainly for audio files) as
            A video stream, defaults to True
        """
        return self.ffmpeg.probe(fname, posters_as_video)

    def thumbnail(self,
                  fname,
                  time,
                  outfile,
                  size=None,
                  quality=FFMpeg.DEFAULT_JPEG_QUALITY):
        """
        Create a thumbnail of the media file. See the documentation of
        converter.FFMpeg.thumbnail() for details.
        """
        return self.ffmpeg.thumbnail(fname, time, outfile, size, quality)

    def thumbnails(self, fname, option_list):
        """
        Create one or more thumbnail of the media file. See the documentation
        of converter.FFMpeg.thumbnails() for details.
        """
        return self.ffmpeg.thumbnails(fname, option_list)
class Converter(object):
    """
    Converter class, encapsulates formats and codecs.

    >>> c = Converter()
    """

    def __init__(self, ffmpeg_path=None, ffprobe_path=None):
        """
        Initialize a new Converter object.
        """

        self.ffmpeg = FFMpeg(ffmpeg_path=ffmpeg_path,
                             ffprobe_path=ffprobe_path)
        self.video_codecs = {}
        self.audio_codecs = {}
        self.subtitle_codecs = {}
        self.formats = {}

        for cls in audio_codec_list:
            name = cls.codec_name
            self.audio_codecs[name] = cls

        for cls in video_codec_list:
            name = cls.codec_name
            self.video_codecs[name] = cls

        for cls in subtitle_codec_list:
            name = cls.codec_name
            self.subtitle_codecs[name] = cls

        for cls in format_list:
            name = cls.format_name
            self.formats[name] = cls

    def parse_options(self, opt, twopass=None):
        """
        Parse format/codec options and prepare raw ffmpeg option list.
        """
        format_options = None
        audio_options = []
        video_options = []
        subtitle_options = []

        if not isinstance(opt, dict):
            raise ConverterError('Invalid output specification')

        if 'format' not in opt:
            raise ConverterError('Format not specified')

        f = opt['format']
        if f not in self.formats:
            raise ConverterError('Requested unknown format: ' + str(f))

        format_options = self.formats[f]().parse_options(opt)
        if format_options is None:
            raise ConverterError('Unknown container format error')

        if 'audio' not in opt and 'video' not in opt and 'subtitle' not in opt:
            raise ConverterError('Neither audio nor video nor subtitle streams requested')

        if 'audio' not in opt:
            opt['audio'] = {'codec': None}

        if 'subtitle' not in opt:
            opt['subtitle'] = {'codec': None}

        # Audio
        y = opt['audio']

        # Creates the new nested dictionary to preserve backwards compatibility
        try:
            first = list(y.values())[0]
            if not isinstance(first, dict):
                y = {0: y}
        except IndexError:
            pass

        audioFlag = 0
        for n in y:
            x = y[n]

            if not isinstance(x, dict) or 'codec' not in x:
                raise ConverterError('Invalid audio codec specification')

            if 'path' in x and 'source' not in x:
                raise ConverterError('Cannot specify audio path without FFMPEG source number')

            if 'source' in x and 'path' not in x:
                raise ConverterError('Cannot specify alternate input source without a path')

            c = x['codec']
            if c not in self.audio_codecs:
                raise ConverterError('Requested unknown audio codec ' + str(c))

            if audioFlag == 0:
                audio_options.extend(self.audio_codecs[c]().parse_options(x, n))
            if audio_options is None:
                raise ConverterError('Unknown audio codec error')
            audioFlag = 1

        # Subtitle
        y = opt['subtitle']

        # Creates the new nested dictionary to preserve backwards compatibility
        try:
            first = list(y.values())[0]
            if not isinstance(first, dict):
                y = {0: y}
        except IndexError:
            pass

        for n in y:
            x = y[n]
            if not isinstance(x, dict) or 'codec' not in x:
                raise ConverterError('Invalid subtitle codec specification')

            if 'path' in x and 'source' not in x:
                raise ConverterError('Cannot specify subtitle path without FFMPEG source number')

            if 'source' in x and 'path' not in x:
                raise ConverterError('Cannot specify alternate input source without a path')

            c = x['codec']
            if c not in self.subtitle_codecs:
                raise ConverterError('Requested unknown subtitle codec ' + str(c))

            subtitle_options.extend(self.subtitle_codecs[c]().parse_options(x, n))
            if subtitle_options is None:
                raise ConverterError('Unknown subtitle codec error')

        if 'video' in opt:
            x = opt['video']
            if not isinstance(x, dict) or 'codec' not in x:
                raise ConverterError('Invalid video codec specification')

            c = x['codec']
            if c not in self.video_codecs:
                raise ConverterError('Requested unknown video codec ' + str(c))

            video_options = self.video_codecs[c]().parse_options(x)
            if video_options is None:
                raise ConverterError('Unknown video codec error')

        # aggregate all options
        optlist = video_options + audio_options + subtitle_options + format_options

        if twopass == 1:
            optlist.extend(['-pass', '1'])
        elif twopass == 2:
            optlist.extend(['-pass', '2'])

        return optlist

    def convert(self, infile, outfile, options, twopass=False, timeout=10, preopts=None, postopts=None):
        """
        Convert media file (infile) according to specified options, and
        save it to outfile. For two-pass encoding, specify the pass (1 or 2)
        in the twopass parameter.

        Options should be passed as a dictionary. The keys are:
            * format (mandatory, string) - container format; see
              formats.BaseFormat for list of supported formats
            * audio (optional, dict) - audio codec and options; see
              avcodecs.AudioCodec for list of supported options
            * video (optional, dict) - video codec and options; see
              avcodecs.VideoCodec for list of supported options
            * map (optional, int) - can be used to map all content of stream 0

        Multiple audio/video streams are not supported. The output has to
        have at least an audio or a video stream (or both).

        Convert returns a generator that needs to be iterated to drive the
        conversion process. The generator will periodically yield timecode
        of currently processed part of the file (ie. at which second in the
        content is the conversion process currently).

        The optional timeout argument specifies how long should the operation
        be blocked in case ffmpeg gets stuck and doesn't report back. This
        doesn't limit the total conversion time, just the amount of time
        Converter will wait for each update from ffmpeg. As it's usually
        less than a second, the default of 10 is a reasonable default. To
        disable the timeout, set it to None. You may need to do this if
        using Converter in a threading environment, since the way the
        timeout is handled (using signals) has special restriction when
        using threads.

        >>> conv = Converter().convert('test1.ogg', '/tmp/output.mkv', {
        ...    'format': 'mkv',
        ...    'audio': { 'codec': 'aac' },
        ...    'video': { 'codec': 'h264' }
        ... })

        >>> for timecode in conv:
        ...   pass # can be used to inform the user about the progress
        """

        if not isinstance(options, dict):
            raise ConverterError('Invalid options')

        if not os.path.exists(infile):
            raise ConverterError("Source file doesn't exist: " + infile)

        info = self.ffmpeg.probe(infile)
        if info is None:
            raise ConverterError("Can't get information about source file")

        if not info.video and not info.audio:
            raise ConverterError('Source file has no audio or video streams')

        if info.video and 'video' in options:
            options = options.copy()
            v = options['video'] = options['video'].copy()
            v['src_width'] = info.video.video_width
            v['src_height'] = info.video.video_height

        if info.format.duration < 0.01:
            raise ConverterError('Zero-length media')

        if twopass:
            optlist1 = self.parse_options(options, 1)
            for timecode in self.ffmpeg.convert(infile, outfile, optlist1,
                                                timeout=timeout, preopts=preopts, postopts=postopts):
                yield int((50.0 * timecode) / info.format.duration)

            optlist2 = self.parse_options(options, 2)
            for timecode in self.ffmpeg.convert(infile, outfile, optlist2,
                                                timeout=timeout, preopts=preopts, postopts=postopts):
                yield int(50.0 + (50.0 * timecode) / info.format.duration)
        else:
            optlist = self.parse_options(options, twopass)
            for timecode in self.ffmpeg.convert(infile, outfile, optlist,
                                                timeout=timeout, preopts=preopts, postopts=postopts):
                yield int((100.0 * timecode) / info.format.duration)

    def probe(self, fname, posters_as_video=True):
        """
        Examine the media file. See the documentation of
        converter.FFMpeg.probe() for details.

        :param posters_as_video: Take poster images (mainly for audio files) as
            A video stream, defaults to True
        """
        return self.ffmpeg.probe(fname, posters_as_video)

    def thumbnail(self, fname, time, outfile, size=None, quality=FFMpeg.DEFAULT_JPEG_QUALITY):
        """
        Create a thumbnail of the media file. See the documentation of
        converter.FFMpeg.thumbnail() for details.
        """
        return self.ffmpeg.thumbnail(fname, time, outfile, size, quality)

    def thumbnails(self, fname, option_list):
        """
        Create one or more thumbnail of the media file. See the documentation
        of converter.FFMpeg.thumbnails() for details.
        """
        return self.ffmpeg.thumbnails(fname, option_list)
Exemple #7
0
class Converter(object):
    '''
    Converter class, encapsulates formats and codecs.

    >>> c = Converter()
    '''
    def __init__(self, ffmpeg_path=None, ffprobe_path=None):
        '''Initialize a new Converter object.'''
        self.ffmpeg = FFMpeg(ffmpeg_path=ffmpeg_path,
                             ffprobe_path=ffprobe_path)
        self.video_codecs = {}
        self.audio_codecs = {}
        self.subtitle_codecs = {}
        self.formats = {}

        for cls in codec_lists['audio']:
            name = cls.codec_name
            self.audio_codecs[name] = cls

        for cls in codec_lists['video']:
            name = cls.codec_name
            self.video_codecs[name] = cls

        for cls in codec_lists['subtitle']:
            name = cls.codec_name
            self.subtitle_codecs[name] = cls

        for cls in format_list:
            name = cls.format_name
            self.formats[name] = cls

    def parse_options(self, opt, twopass=None):
        '''Parse format/codec options and prepare raw ffmpeg option list.'''
        if not isinstance(opt, dict):
            raise ConverterError('Invalid output specification')

        if 'format' not in opt:
            raise ConverterError('Format not specified')

        f = opt['format']
        if f not in self.formats:
            raise ConverterError(f'Requested unknown format: {str(f)}')

        format_options = self.formats[f]().parse_options(opt)
        if format_options is None:
            raise ConverterError('Unknown container format error')

        if 'audio' not in opt and 'video' not in opt:
            raise ConverterError('Neither audio nor video streams requested')

        # audio options
        if 'audio' not in opt or twopass == 1:
            opt_audio = {'codec': None}
        else:
            opt_audio = opt['audio']
            if not isinstance(opt_audio, dict) or 'codec' not in opt_audio:
                raise ConverterError('Invalid audio codec specification')

        c = opt_audio['codec']
        if c not in self.audio_codecs:
            raise ConverterError(f'Requested unknown audio codec {str(c)}')

        audio_options = self.audio_codecs[c]().parse_options(opt_audio)
        if audio_options is None:
            raise ConverterError('Unknown audio codec error')

        # video options
        if 'video' not in opt:
            opt_video = {'codec': None}
        else:
            opt_video = opt['video']
            if not isinstance(opt_video, dict) or 'codec' not in opt_video:
                raise ConverterError('Invalid video codec specification')

        c = opt_video['codec']
        if c not in self.video_codecs:
            raise ConverterError(f'Requested unknown video codec {str(c)}')

        video_options = self.video_codecs[c]().parse_options(opt_video)
        if video_options is None:
            raise ConverterError('Unknown video codec error')

        if 'subtitle' not in opt:
            opt_subtitle = {'codec': None}
        else:
            opt_subtitle = opt['subtitle']
            if not isinstance(opt_subtitle,
                              dict) or 'codec' not in opt_subtitle:
                raise ConverterError('Invalid subtitle codec specification')

        c = opt_subtitle['codec']
        if c not in self.subtitle_codecs:
            raise ConverterError(f'Requested unknown subtitle codec {str(c)}')

        subtitle_options = self.subtitle_codecs[c]().parse_options(
            opt_subtitle)
        if subtitle_options is None:
            raise ConverterError('Unknown subtitle codec error')

        if 'map' in opt:
            m = opt['map']
            if not isinstance(m, int):
                raise ConverterError('map needs to be an integer.')
            else:
                format_options.extend(['-map', str(m)])

        # aggregate all options
        optlist = audio_options + video_options + subtitle_options + format_options

        if twopass == 1:
            optlist.extend(['-pass', '1'])
        elif twopass == 2:
            optlist.extend(['-pass', '2'])

        return optlist

    def convert(self, infile, outfile, options, twopass=False, timeout=10):
        '''
        Convert media file (infile) according to specified options, and save it to outfile. For two-pass encoding, specify the pass (1 or 2) in the twopass parameter.

        Options should be passed as a dictionary. The keys are:
            * format (mandatory, string) - container format; see
              formats.BaseFormat for list of supported formats
            * audio (optional, dict) - audio codec and options; see
              codecs.audio.AudioCodec for list of supported options
            * video (optional, dict) - video codec and options; see
              codecs.video.VideoCodec for list of supported options
            * map (optional, int) - can be used to map all content of stream 0

        Multiple audio/video streams are not supported. The output has to
        have at least an audio or a video stream (or both).

        Convert returns a generator that needs to be iterated to drive the
        conversion process. The generator will periodically yield timecode
        of currently processed part of the file (ie. at which second in the
        content is the conversion process currently).

        The optional timeout argument specifies how long should the operation
        be blocked in case ffmpeg gets stuck and doesn't report back. This
        doesn't limit the total conversion time, just the amount of time
        Converter will wait for each update from ffmpeg. As it's usually
        less than a second, the default of 10 is a reasonable default. To
        disable the timeout, set it to None. You may need to do this if
        using Converter in a threading environment, since the way the
        timeout is handled (using signals) has special restriction when
        using threads.

        >>> conv = Converter().convert('test1.ogg', '/tmp/output.mkv', {
        ...    'format': 'mkv',
        ...    'audio': { 'codec': 'aac' },
        ...    'video': { 'codec': 'h264' }
        ... })

        >>> for timecode in conv:
        ...   pass # can be used to inform the user about the progress
        '''
        if not isinstance(options, dict):
            raise ConverterError('Invalid options')

        if not os.path.exists(infile):
            raise ConverterError(f'Source file doesn\'t exist: {infile}')

        info = self.ffmpeg.probe(infile)
        if info is None:
            raise ConverterError('Can\'t get information about source file')

        if not info.video and not info.audio:
            raise ConverterError('Source file has no audio or video streams')

        preoptlist = None
        skinoptlist = None
        if info.video and 'video' in options:
            options = options.copy()
            v = options['video'] = options['video'].copy()
            v['src_width'] = info.video.video_width
            v['src_height'] = info.video.video_height
            v['display_aspect_ratio'] = info.video.video_display_aspect_ratio
            v['sample_aspect_ratio'] = info.video.video_sample_aspect_ratio
            v['rotate'] = info.video.metadata.get(
                'rotate') or info.video.metadata.get('ROTATE')
            preoptlist = options['video'].get('ffmpeg_custom_launch_opts',
                                              '').split(' ')
            # Remove empty arguments (make crashes)
            preoptlist = [arg for arg in preoptlist if arg]
            skinoptlist = options['video'].get('ffmpeg_skin_opts',
                                               '').split(' ')
            # Remove empty arguments (make crashes)
            skinoptlist = [arg for arg in skinoptlist if arg]
        if not info.format or not info.format.duration or not isinstance(
                info.format.duration,
            (float, int)) or info.format.duration < 0.01:
            raise ConverterError('Zero-length media')

        if twopass:
            optlist1 = self.parse_options(options, 1)
            for timecode in self.ffmpeg.convert(infile,
                                                outfile,
                                                optlist1,
                                                timeout=timeout,
                                                preopts=preoptlist,
                                                skinopts=skinoptlist):
                yield float(timecode) / info.format.duration

            optlist2 = self.parse_options(options, 2)
            for timecode in self.ffmpeg.convert(infile,
                                                outfile,
                                                optlist2,
                                                timeout=timeout,
                                                preopts=preoptlist,
                                                skinopts=skinoptlist):
                yield 0.5 + float(timecode) / info.format.duration
        else:
            optlist = self.parse_options(options, twopass)
            for timecode in self.ffmpeg.convert(infile,
                                                outfile,
                                                optlist,
                                                timeout=timeout,
                                                preopts=preoptlist,
                                                skinopts=skinoptlist):
                yield float(timecode) / info.format.duration

    def segment(self,
                infile,
                working_directory,
                output_file,
                output_directory,
                options,
                timeout=10):
        if not os.path.exists(infile):
            raise ConverterError(f'Source file doesn\'t exist: {infile}')

        info = self.ffmpeg.probe(infile)
        if info is None:
            raise ConverterError('Can\'t get information about source file')

        if not info.video and not info.audio:
            raise ConverterError('Source file has no audio or video streams')

        try:
            os.makedirs(os.path.join(working_directory, output_directory))
        except Exception as e:
            if e.errno != errno.EEXIST:
                raise e
        current_directory = os.getcwd()
        os.chdir(working_directory)
        if options.get('audio'):
            segment_time = max(
                1, math.ceil(options['audio'].get('start_time', 1)))
        else:
            segment_time = 1
        if segment_time > 1:
            logger.warning(
                'Warning : HLS fragments size will be upper than 1 seconds probably that audio channel start at %s seconds.'
                % (segment_time))
        optlist = [
            '-flags', '-global_header', '-f', 'segment', '-segment_time',
            f'{segment_time}', '-segment_list', output_file,
            '-segment_list_type', 'm3u8', '-segment_format', 'mpegts',
            '-segment_list_entry_prefix', f'{output_directory}/', '-map', '0',
            '-map', '-0:d', '-vcodec', 'copy', '-acodec', 'copy'
        ]
        try:
            if 'video' in info.streams[0].type:
                codec = info.streams[0].codec
            else:
                codec = info.streams[1].codec
        except Exception as e:
            print(f'Could not determinate encoder: {e}')
            codec = ''
        if 'h264' in codec:
            optlist.insert(-4, '-vbsf')
            optlist.insert(-4, 'h264_mp4toannexb')

        outfile = f'{output_directory}/media%%05d.ts'
        for timecode in self.ffmpeg.convert(infile,
                                            outfile,
                                            optlist,
                                            timeout=timeout):
            yield int((100.0 * timecode) / info.format.duration)
        os.chdir(current_directory)

    def probe(self, fname, posters_as_video=True):
        '''
        Examine the media file.

        See the documentation of converter.FFMpeg.probe() for details.

        :param posters_as_video: Take poster images (mainly for audio files) as
            A video stream, defaults to True
        '''
        return self.ffmpeg.probe(fname, posters_as_video)

    def thumbnail(self,
                  fname,
                  time,
                  outfile,
                  size=None,
                  quality=FFMpeg.DEFAULT_JPEG_QUALITY):
        '''
        Create a thumbnail of the media file.

        See the documentation of converter.FFMpeg.thumbnail() for details.
        '''
        return self.ffmpeg.thumbnail(fname, time, outfile, size, quality)

    def thumbnails(self, fname, option_list):
        '''
        Create one or more thumbnail of the media file.

        See the documentation of converter.FFMpeg.thumbnails() for details.
        '''
        return self.ffmpeg.thumbnails(fname, option_list)
class Converter(object):
    """
    Converter class, encapsulates formats and codecs.

    >>> c = Converter()
    """

    def __init__(self, ffmpeg_path=None, ffprobe_path=None):
        """
        Initialize a new Converter object.
        """

        self.ffmpeg = FFMpeg(ffmpeg_path=ffmpeg_path,
                             ffprobe_path=ffprobe_path)
        self.video_codecs = {}
        self.audio_codecs = {}
        self.subtitle_codecs = {}
        self.formats = {}

        for cls in audio_codec_list:
            name = cls.codec_name
            self.audio_codecs[name] = cls

        for cls in video_codec_list:
            name = cls.codec_name
            self.video_codecs[name] = cls

        for cls in subtitle_codec_list:
            name = cls.codec_name
            self.subtitle_codecs[name] = cls

        for cls in format_list:
            name = cls.format_name
            self.formats[name] = cls

    def parse_options(self, opt, twopass=None):
        """
        Parse format/codec options and prepare raw ffmpeg option list.
        """
        if not isinstance(opt, dict):
            raise ConverterError('Invalid output specification')

        if 'format' not in opt:
            raise ConverterError('Format not specified')

        f = opt['format']
        if f not in self.formats:
            raise ConverterError('Requested unknown format: ' + str(f))

        format_options = self.formats[f]().parse_options(opt)
        if format_options is None:
            raise ConverterError('Unknown container format error')

        if 'audio' not in opt and 'video' not in opt:
            raise ConverterError('Neither audio nor video streams requested')

        # audio options
        if 'audio' not in opt or twopass == 1:
            opt_audio = {'codec': None}
        else:
            opt_audio = opt['audio']
            if not isinstance(opt_audio, dict) or 'codec' not in opt_audio:
                raise ConverterError('Invalid audio codec specification')

        c = opt_audio['codec']
        if c not in self.audio_codecs:
            raise ConverterError('Requested unknown audio codec ' + str(c))

        audio_options = self.audio_codecs[c]().parse_options(opt_audio)
        if audio_options is None:
            raise ConverterError('Unknown audio codec error')

        # video options
        if 'video' not in opt:
            opt_video = {'codec': None}
        else:
            opt_video = opt['video']
            if not isinstance(opt_video, dict) or 'codec' not in opt_video:
                raise ConverterError('Invalid video codec specification')

        c = opt_video['codec']
        if c not in self.video_codecs:
            raise ConverterError('Requested unknown video codec ' + str(c))

        video_options = self.video_codecs[c]().parse_options(opt_video)
        if video_options is None:
            raise ConverterError('Unknown video codec error')

        if 'subtitle' not in opt:
            opt_subtitle = {'codec': None}
        else:
            opt_subtitle = opt['subtitle']
            if not isinstance(opt_subtitle, dict) or 'codec' not in opt_subtitle:
                raise ConverterError('Invalid subtitle codec specification')

        c = opt_subtitle['codec']
        if c not in self.subtitle_codecs:
            raise ConverterError('Requested unknown subtitle codec ' + str(c))

        subtitle_options = self.subtitle_codecs[c]().parse_options(opt_subtitle)
        if subtitle_options is None:
            raise ConverterError('Unknown subtitle codec error')

        if 'map' in opt:
            m = opt['map']
            if not type(m) == int:
                raise ConverterError('map needs to be int')
            else:
                format_options.extend(['-map', str(m)])

        if 'start' in opt:
            start = parse_time(opt['start'])
            format_options.extend(['-ss', start])

        if 'duration' in opt:
            duration = parse_time(opt['duration'])
            format_options.extend(['-t', duration])
        elif 'end' in opt:
            end = parse_time(opt['end'])
            format_options.extend(['-to', end])

        # aggregate all options
        optlist = audio_options + video_options + subtitle_options + format_options

        if twopass == 1:
            optlist.extend(['-pass', '1'])
        elif twopass == 2:
            optlist.extend(['-pass', '2'])

        return optlist

    def convert(self, infile, outfile, options, twopass=False, timeout=10, nice=None, title=None):
        """
        Convert media file (infile) according to specified options, and
        save it to outfile. For two-pass encoding, specify the pass (1 or 2)
        in the twopass parameter.

        Options should be passed as a dictionary. The keys are:
            * format (mandatory, string) - container format; see
              formats.BaseFormat for list of supported formats
            * audio (optional, dict) - audio codec and options; see
              avcodecs.AudioCodec for list of supported options
            * video (optional, dict) - video codec and options; see
              avcodecs.VideoCodec for list of supported options
            * map (optional, int) - can be used to map all content of stream 0

        Multiple audio/video streams are not supported. The output has to
        have at least an audio or a video stream (or both).

        Convert returns a generator that needs to be iterated to drive the
        conversion process. The generator will periodically yield timecode
        of currently processed part of the file (ie. at which second in the
        content is the conversion process currently).

        The optional timeout argument specifies how long should the operation
        be blocked in case ffmpeg gets stuck and doesn't report back. This
        doesn't limit the total conversion time, just the amount of time
        Converter will wait for each update from ffmpeg. As it's usually
        less than a second, the default of 10 is a reasonable default. To
        disable the timeout, set it to None. You may need to do this if
        using Converter in a threading environment, since the way the
        timeout is handled (using signals) has special restriction when
        using threads.

        >>> conv = Converter().convert('test1.ogg', '/tmp/output.mkv', {
        ...    'format': 'mkv',
        ...    'audio': { 'codec': 'aac' },
        ...    'video': { 'codec': 'h264' }
        ... })

        >>> for timecode in conv:
        ...   pass # can be used to inform the user about the progress
        """

        if not isinstance(options, dict):
            raise ConverterError('Invalid options')

        if not os.path.exists(infile) and not self.ffmpeg.is_url(infile):
            raise ConverterError("Source file doesn't exist: " + infile)

        info = self.ffmpeg.probe(infile, title=title)
        if info is None:
            raise ConverterError("Can't get information about source file")

        if 'video' not in info and 'audio' not in info:
            raise ConverterError('Source file has no audio or video streams')

        if 'video' in info and 'video' in options:
            options = options.copy()
            v = options['video'] = options['video'].copy()
            v['src_width'] = info['video']['width']
            v['src_height'] = info['video']['height']
            if 'tags' in info['video'] and 'rotate' in info['video']['tags']:
                v['src_rotate'] = info['video']['tags']['rotate']

        if info['format']['duration'] < 0.01:
            raise ConverterError('Zero-length media')

        if 'duration' in options:
            duration = timecode_to_seconds(options['duration'])
        elif 'start' in options:
            if 'end' in options:
                duration = timecode_to_seconds(options['end']) - timecode_to_seconds(options['start'])
            else:
                duration = info['format']['duration'] - timecode_to_seconds(options['start'])
        elif 'end' in options:
            duration = timecode_to_seconds(options['end'])
        else:
            duration = info['format']['duration']

        if twopass:
            optlist1 = self.parse_options(options, 1)
            for timecode in self.ffmpeg.convert(infile, outfile, optlist1,
                                                timeout=timeout, nice=nice):
                yield int((50.0 * timecode) / duration)

            optlist2 = self.parse_options(options, 2)
            for timecode in self.ffmpeg.convert(infile, outfile, optlist2,
                                                timeout=timeout, nice=nice):
                yield int(50.0 + (50.0 * timecode) / duration)
        else:
            optlist = self.parse_options(options, twopass)
            for timecode in self.ffmpeg.convert(infile, outfile, optlist,
                                                timeout=timeout, nice=nice):
                yield int((100.0 * timecode) / duration)

    def analyze(self, infile, audio_level=True, interlacing=True, crop=False, start=None, duration=None, end=None, timeout=10, nice=None, title=None):
        """
        Analyze the video frames to find if the video need to be deinterlaced.
        Or/and analyze the audio to find if the audio need to be normalize
        and by how much. Both analyses are together so FFMpeg can do both
        analyses in the same pass.

        :param audio_level: Set it to True to get by how much dB the audio need
        to be normalize, defaults to True.
        :param interlacing: Set it to True to check if the video need to be
        deinterlaced, defaults to True.
        :param timeout: How long should the operation be blocked in case ffmpeg
        gets stuck and doesn't report back, defaults to 10 sec.
        """
        if not os.path.exists(infile) and not self.ffmpeg.is_url(infile):
            raise ConverterError("Source file doesn't exist: " + infile)

        info = self.ffmpeg.probe(infile, title=title)
        if info is None:
            raise ConverterError("Can't get information about source file")

        if 'video' not in info and 'audio' not in info:
            raise ConverterError('Source file has no audio or video streams')

        if 'audio' not in info:
            audio_level = False

        if 'video' not in info:
            interlacing = False
            crop = False

        if info['format']['duration'] < 0.01:
            raise ConverterError('Zero-length media')
        for timecode in self.ffmpeg.analyze(infile, audio_level, interlacing,
                                            crop, start, duration, end, timeout, nice):
            if isinstance(timecode, float):
                yield int((100.0 * timecode) / info['format']['duration'])
            else:
                yield timecode

    def probe(self, *args, **kwargs):
        """
        Examine the media file. See the documentation of
        converter.FFMpeg.probe() for details.

        :param posters_as_video: Take poster images (mainly for audio files) as
            A video stream, defaults to True
        """
        return self.ffmpeg.probe(*args, **kwargs)

    def validate(self, source, duration=None, title=None):
        if not os.path.exists(source) and not self.ffmpeg.is_url(source):
            yield "Source file doesn't exist: " + source
            raise StopIteration

        info = self.ffmpeg.probe(source, title=title)
        if info is None:
            yield 'no info'
            raise StopIteration

        if 'video' not in info and 'audio' not in info:
            yield 'no stream'
            raise StopIteration

        opts = ['-f', 'null']
        if duration:
            opts += ['-t', str(duration)]
            duration = timecode_to_seconds(duration)
        else:
            duration = info['format']['duration']

        processed = self.ffmpeg.convert(source, '/dev/null', opts,
                                        timeout=100, nice=15, get_output=True, title=title)
        for timecode in processed:
            if isinstance(timecode, basestring):
                if 'rror while decoding' in timecode:
                    yield 'error'
                    raise StopIteration
            elif duration:
                yield int((100.0 * timecode) / duration)
            else:
                yield timecode

    def thumbnail(self, *args, **kwargs):
        """
        Create a thumbnail of the media file. See the documentation of
        converter.FFMpeg.thumbnail() for details.
        """
        return self.ffmpeg.thumbnail(*args, **kwargs)

    def thumbnails(self, *args, **kwargs):
        """
        Create one or more thumbnail of the media file. See the documentation
        of converter.FFMpeg.thumbnails() for details.
        """
        return self.ffmpeg.thumbnails(*args, **kwargs)

    def thumbnails_by_interval(self, *args, **kwargs):
        """
        Create one or more thumbnail of the media file. See the documentation
        of converter.FFMpeg.thumbnails() for details.
        """
        return self.ffmpeg.thumbnails_by_interval(*args, **kwargs)
class Converter(object):
    """
    Converter class, encapsulates formats and codecs.

    >>> c = Converter()
    """

    def __init__(self, ffmpeg_path=None, ffprobe_path=None):
        """
        Initialize a new Converter object.
        """

        self.ffmpeg = FFMpeg(ffmpeg_path=ffmpeg_path,
                             ffprobe_path=ffprobe_path)
        self.video_codecs = {}
        self.audio_codecs = {}
        self.subtitle_codecs = {}
        self.formats = {}

        for cls in audio_codec_list:
            name = cls.codec_name
            self.audio_codecs[name] = cls

        for cls in video_codec_list:
            name = cls.codec_name
            self.video_codecs[name] = cls

        for cls in subtitle_codec_list:
            name = cls.codec_name
            self.subtitle_codecs[name] = cls

        for cls in format_list:
            name = cls.format_name
            self.formats[name] = cls

    def parse_options(self, opt, twopass=None):
        """
        Parse format/codec options and prepare raw ffmpeg option list.
        """
        if not isinstance(opt, dict):
            raise ConverterError('Invalid output specification')

        if 'format' not in opt:
            raise ConverterError('Format not specified')

        f = opt['format']
        if f not in self.formats:
            raise ConverterError('Requested unknown format: ' + str(f))

        format_options = self.formats[f]().parse_options(opt)
        if format_options is None:
            raise ConverterError('Unknown container format error')

        if 'audio' not in opt and 'video' not in opt:
            raise ConverterError('Neither audio nor video streams requested')

        # audio options
        if 'audio' not in opt or twopass == 1:
            opt_audio = {'codec': None}
        else:
            opt_audio = opt['audio']
            if not isinstance(opt_audio, dict) or 'codec' not in opt_audio:
                raise ConverterError('Invalid audio codec specification')

        c = opt_audio['codec']
        if c not in self.audio_codecs:
            raise ConverterError('Requested unknown audio codec ' + str(c))

        audio_options = self.audio_codecs[c]().parse_options(opt_audio)
        if audio_options is None:
            raise ConverterError('Unknown audio codec error')

        # video options
        if 'video' not in opt:
            opt_video = {'codec': None}
        else:
            opt_video = opt['video']
            if not isinstance(opt_video, dict) or 'codec' not in opt_video:
                raise ConverterError('Invalid video codec specification')

        c = opt_video['codec']
        if c not in self.video_codecs:
            raise ConverterError('Requested unknown video codec ' + str(c))

        video_options = self.video_codecs[c]().parse_options(opt_video)
        if video_options is None:
            raise ConverterError('Unknown video codec error')

        if 'subtitle' not in opt:
            opt_subtitle = {'codec': None}
        else:
            opt_subtitle = opt['subtitle']
            if not isinstance(opt_subtitle, dict) or 'codec' not in opt_subtitle:
                raise ConverterError('Invalid subtitle codec specification')

        c = opt_subtitle['codec']
        if c not in self.subtitle_codecs:
            raise ConverterError('Requested unknown subtitle codec ' + str(c))

        subtitle_options = self.subtitle_codecs[c]().parse_options(opt_subtitle)
        if subtitle_options is None:
            raise ConverterError('Unknown subtitle codec error')

        if 'map' in opt:
            m = opt['map']
            if not type(m) == int:
                raise ConverterError('map needs to be int')
            else:
                format_options.extend(['-map', str(m)])

        generic_options = opt.get('params') or []

        # aggregate all options
        optlist = generic_options + audio_options + video_options + subtitle_options + format_options

        if twopass == 1:
            optlist.extend(['-pass', '1'])
        elif twopass == 2:
            optlist.extend(['-pass', '2'])

        return map(str, optlist)

    def _probe_or_raise(self, infile):
        if not os.path.exists(infile):
            raise ConverterError("Source file doesn't exist: " + infile)

        info = self.ffmpeg.probe(infile)
        if info is None:
            raise ConverterError("Can't get information about source file")

        if not info.video and not info.audio:
            raise ConverterError('Source file has no audio or video streams')

        if info.format.duration < 0.01 and info.video.codec != 'png':
            raise ConverterError('Zero-length media')

        return info

    def convert(self, infile, outfile, options, twopass=False, timeout=10):
        """
        Convert media file (infile) according to specified options, and
        save it to outfile. For two-pass encoding, specify the pass (1 or 2)
        in the twopass parameter.

        Options should be passed as a dictionary. The keys are:
            * format (mandatory, string) - container format; see
              formats.BaseFormat for list of supported formats
            * audio (optional, dict) - audio codec and options; see
              avcodecs.AudioCodec for list of supported options
            * video (optional, dict) - video codec and options; see
              avcodecs.VideoCodec for list of supported options
            * map (optional, int) - can be used to map all content of stream 0

        Multiple audio/video streams are not supported. The output has to
        have at least an audio or a video stream (or both).

        Convert returns a generator that needs to be iterated to drive the
        conversion process. The generator will periodically yield timecode
        of currently processed part of the file (ie. at which second in the
        content is the conversion process currently).

        The optional timeout argument specifies how long should the operation
        be blocked in case ffmpeg gets stuck and doesn't report back. This
        doesn't limit the total conversion time, just the amount of time
        Converter will wait for each update from ffmpeg. As it's usually
        less than a second, the default of 10 is a reasonable default. To
        disable the timeout, set it to None. You may need to do this if
        using Converter in a threading environment, since the way the
        timeout is handled (using signals) has special restriction when
        using threads.

        >>> conv = Converter().convert('test1.ogg', '/tmp/output.mkv', {
        ...    'format': 'mkv',
        ...    'audio': { 'codec': 'aac' },
        ...    'video': { 'codec': 'h264' }
        ... })

        >>> for timecode in conv:
        ...   pass # can be used to inform the user about the progress
        """

        info = self._probe_or_raise(infile)

        if info.video and 'video' in options:
            options = options.copy()
            v = options['video'] = options['video'].copy()
            v['src_width'] = info.video.video_width
            v['src_height'] = info.video.video_height

        if not isinstance(options, dict):
            raise ConverterError('Invalid options')

        if twopass:
            optlist1 = self.parse_options(options, 1)
            for timecode in self.ffmpeg.convert(infile, outfile, optlist1,
                                                timeout=timeout):
                yield int((50.0 * timecode) / info.format.duration)

            optlist2 = self.parse_options(options, 2)
            for timecode in self.ffmpeg.convert(infile, outfile, optlist2,
                                                timeout=timeout):
                yield int(50.0 + (50.0 * timecode) / info.format.duration)
        else:
            optlist = self.parse_options(options, twopass)
            for timecode in self.ffmpeg.convert(infile, outfile, optlist,
                                                timeout=timeout):
                yield int((100.0 * timecode) / info.format.duration)

    def concat(self, infiles, outfile, options, timeout=10, temp_dir=None):
        '''

        >>> conv = Converter().concat(['test1.ogg', 'test1.ogg'], '/tmp/output.mkv', {
        ...    'format': 'mkv',
        ...    'audio': { 'codec': 'aac' },
        ...    'video': { 'codec': 'h264' }
        ... })

        >>> for timecode in conv:
        ...   pass # can be used to inform the user about the progress
        '''
        if not isinstance(options, dict):
            raise ConverterError('Invalid options')

        info_list = []
        for in_file in infiles:
            if isinstance(in_file, (list, tuple)):
                in_file, opts = in_file

            info_list.append(self._probe_or_raise(in_file))

        result_duration = sum([i.format.duration for i in info_list])

        optlist = self.parse_options(options, None)



        for timecode in self.ffmpeg.concat(infiles, outfile, optlist,
                timeout=timeout, temp_dir=temp_dir):
            yield int((100.0 * timecode) / result_duration)


    def probe(self, fname, posters_as_video=True):
        """
        Examine the media file. See the documentation of
        converter.FFMpeg.probe() for details.

        :param posters_as_video: Take poster images (mainly for audio files) as
            A video stream, defaults to True
        """
        return self.ffmpeg.probe(fname, posters_as_video)

    def thumbnail(self, fname, time, outfile, size=None, quality=FFMpeg.DEFAULT_JPEG_QUALITY):
        """
        Create a thumbnail of the media file. See the documentation of
        converter.FFMpeg.thumbnail() for details.
        """
        return self.ffmpeg.thumbnail(fname, time, outfile, size, quality)

    def thumbnails(self, fname, option_list):
        """
        Create one or more thumbnail of the media file. See the documentation
        of converter.FFMpeg.thumbnails() for details.
        """
        return self.ffmpeg.thumbnails(fname, option_list)
Exemple #10
0
class Converter(object):
    """
    Converter class, encapsulates formats and codecs.

    >>> c = Converter()
    """
    def __init__(self, ffmpeg_path=None, ffprobe_path=None):
        """
        Initialize a new Converter object.
        """

        self.ffmpeg = FFMpeg(ffmpeg_path=ffmpeg_path,
                             ffprobe_path=ffprobe_path)
        self.video_codecs = {}
        self.audio_codecs = {}
        self.subtitle_codecs = {}
        self.formats = {}

        for cls in audio_codec_list:
            name = cls.codec_name
            self.audio_codecs[name] = cls

        for cls in video_codec_list:
            name = cls.codec_name
            self.video_codecs[name] = cls

        for cls in subtitle_codec_list:
            name = cls.codec_name
            self.subtitle_codecs[name] = cls

        for cls in format_list:
            name = cls.format_name
            self.formats[name] = cls

    def parse_options(self, opt, twopass=None):
        """
        Parse format/codec options and prepare raw ffmpeg option list.
        """
        if not isinstance(opt, dict):
            raise ConverterError('Invalid output specification')

        if 'format' not in opt:
            raise ConverterError('Format not specified')

        f = opt['format']
        if f not in self.formats:
            raise ConverterError('Requested unknown format: ' + str(f))

        format_options = self.formats[f]().parse_options(opt)
        if format_options is None:
            raise ConverterError('Unknown container format error')

        if 'audio' not in opt and 'video' not in opt:
            raise ConverterError('Neither audio nor video streams requested')

        # audio options
        if 'audio' not in opt or twopass == 1:
            opt_audio = {'codec': None}
        else:
            opt_audio = opt['audio']
            if not isinstance(opt_audio, dict) or 'codec' not in opt_audio:
                raise ConverterError('Invalid audio codec specification')

        c = opt_audio['codec']
        if c not in self.audio_codecs:
            raise ConverterError('Requested unknown audio codec ' + str(c))

        audio_options = self.audio_codecs[c]().parse_options(opt_audio)
        if audio_options is None:
            raise ConverterError('Unknown audio codec error')

        # video options
        if 'video' not in opt:
            opt_video = {'codec': None}
        else:
            opt_video = opt['video']
            if not isinstance(opt_video, dict) or 'codec' not in opt_video:
                raise ConverterError('Invalid video codec specification')

        c = opt_video['codec']
        if c not in self.video_codecs:
            raise ConverterError('Requested unknown video codec ' + str(c))

        video_options = self.video_codecs[c]().parse_options(opt_video)
        if video_options is None:
            raise ConverterError('Unknown video codec error')

        if 'subtitle' not in opt:
            opt_subtitle = {'codec': None}
        else:
            opt_subtitle = opt['subtitle']
            if not isinstance(opt_subtitle,
                              dict) or 'codec' not in opt_subtitle:
                raise ConverterError('Invalid subtitle codec specification')

        c = opt_subtitle['codec']
        if c not in self.subtitle_codecs:
            raise ConverterError('Requested unknown subtitle codec ' + str(c))

        subtitle_options = self.subtitle_codecs[c]().parse_options(
            opt_subtitle)
        if subtitle_options is None:
            raise ConverterError('Unknown subtitle codec error')

        if 'map' in opt:
            m = opt['map']
            if not type(m) == int:
                raise ConverterError('map needs to be int')
            else:
                format_options.extend(['-map', str(m)])

        # aggregate all options
        optlist = audio_options + video_options + subtitle_options + format_options

        if twopass == 1:
            optlist.extend(['-pass', '1'])
        elif twopass == 2:
            optlist.extend(['-pass', '2'])

        return optlist

    def convert(self, infile, outfile, options, twopass=False, timeout=10):
        """
        Convert media file (infile) according to specified options, and
        save it to outfile. For two-pass encoding, specify the pass (1 or 2)
        in the twopass parameter.

        Options should be passed as a dictionary. The keys are:
            * format (mandatory, string) - container format; see
              formats.BaseFormat for list of supported formats
            * audio (optional, dict) - audio codec and options; see
              avcodecs.AudioCodec for list of supported options
            * video (optional, dict) - video codec and options; see
              avcodecs.VideoCodec for list of supported options
            * map (optional, int) - can be used to map all content of stream 0

        Multiple audio/video streams are not supported. The output has to
        have at least an audio or a video stream (or both).

        Convert returns a generator that needs to be iterated to drive the
        conversion process. The generator will periodically yield timecode
        of currently processed part of the file (ie. at which second in the
        content is the conversion process currently).

        The optional timeout argument specifies how long should the operation
        be blocked in case ffmpeg gets stuck and doesn't report back. This
        doesn't limit the total conversion time, just the amount of time
        Converter will wait for each update from ffmpeg. As it's usually
        less than a second, the default of 10 is a reasonable default. To
        disable the timeout, set it to None. You may need to do this if
        using Converter in a threading environment, since the way the
        timeout is handled (using signals) has special restriction when
        using threads.

        >>> conv = Converter().convert('test1.ogg', '/tmp/output.mkv', {
        ...    'format': 'mkv',
        ...    'audio': { 'codec': 'aac' },
        ...    'video': { 'codec': 'h264' }
        ... })

        >>> for timecode in conv:
        ...   pass # can be used to inform the user about the progress
        """

        if not isinstance(options, dict):
            raise ConverterError('Invalid options')

        if not os.path.exists(infile) and not self.ffmpeg.is_url(infile):
            raise ConverterError("Source file doesn't exist: " + infile)

        info = self.ffmpeg.probe(infile)
        if info is None:
            raise ConverterError("Can't get information about source file")

        if not info.video and not info.audio:
            raise ConverterError('Source file has no audio or video streams')

        if info.video and 'video' in options:
            options = options.copy()
            v = options['video'] = options['video'].copy()
            v['src_width'] = info.video.video_width
            v['src_height'] = info.video.video_height
            if 'rotate' in info.video.metadata:
                v['src_rotate'] = info.video.metadata['rotate']

        if info.format.duration < 0.01:
            raise ConverterError('Zero-length media')

        if twopass:
            optlist1 = self.parse_options(options, 1)
            for timecode in self.ffmpeg.convert(infile,
                                                outfile,
                                                optlist1,
                                                timeout=timeout):
                yield int((50.0 * timecode) / info.format.duration)

            optlist2 = self.parse_options(options, 2)
            for timecode in self.ffmpeg.convert(infile,
                                                outfile,
                                                optlist2,
                                                timeout=timeout):
                yield int(50.0 + (50.0 * timecode) / info.format.duration)
        else:
            optlist = self.parse_options(options, twopass)
            for timecode in self.ffmpeg.convert(infile,
                                                outfile,
                                                optlist,
                                                timeout=timeout):
                yield int((100.0 * timecode) / info.format.duration)

    def probe(self, *args, **kwargs):
        """
        Examine the media file. See the documentation of
        converter.FFMpeg.probe() for details.

        :param posters_as_video: Take poster images (mainly for audio files) as
            A video stream, defaults to True
        """
        return self.ffmpeg.probe(*args, **kwargs)

    def thumbnail(self, *args, **kwargs):
        """
        Create a thumbnail of the media file. See the documentation of
        converter.FFMpeg.thumbnail() for details.
        """

        if 'time' and 'fname' in kwargs:

            print(kwargs['fname'])

            frame_time = kwargs['time']

            if not os.path.exists(kwargs['fname']) and not self.ffmpeg.is_url(
                    kwargs['fname']):
                raise ConverterError("Source file doesn't exist: " +
                                     kwargs['fname'])

            info = self.ffmpeg.probe(kwargs['fname'])
            if info is None:
                raise ConverterError("Can't get information about source file")

            if not info.video and not info.audio:
                raise ConverterError(
                    'Source file has no audio or video streams')

            duration = info.format.duration

            if frame_time > duration:
                if info.format.duration >= 60:
                    frame_time = 10
                elif duration >= 6 and duration < 60:
                    frame_time = 3
                elif duration > 1 and duration < 6:
                    frame_time = 1
                else:
                    frame_time = 0.01

            print('frane_time:: ' + str(frame_time))

            kwargs['time'] = frame_time

        return self.ffmpeg.thumbnail(*args, **kwargs)

    def thumbnails(self, *args, **kwargs):
        """
        Create one or more thumbnail of the media file. See the documentation
        of converter.FFMpeg.thumbnails() for details.
        """
        return self.ffmpeg.thumbnails(*args, **kwargs)

    def thumbnails_by_interval(self, *args, **kwargs):
        """
        Create one or more thumbnail of the media file. See the documentation
        of converter.FFMpeg.thumbnails() for details.
        """
        return self.ffmpeg.thumbnails_by_interval(*args, **kwargs)

    def crop(self, infile, outfile, options, timeout=10):
        """
        crop media file

        >>> conv = Converter().convert('test1.ogg', '/tmp/output.mkv', {
        ...    'format': 'mkv',
        ...    'audio': { 'codec': 'aac' },
        ...    'video': { 'codec': 'h264' }
        ... })

        >>> for timecode in conv:
        ...   pass # can be used to inform the user about the progress
        """

        if not isinstance(options, dict):
            raise ConverterError('Invalid options')

        if not os.path.exists(infile) and not self.ffmpeg.is_url(infile):
            raise ConverterError("Source file doesn't exist: " + infile)

        info = self.ffmpeg.probe(infile)
        if info is None:
            raise ConverterError("Can't get information about source file")

        if not info.video and not info.audio:
            raise ConverterError('Source file has no audio or video streams')

        if info.format.duration < 0.01:
            raise ConverterError('Zero-length media')

        for timecode in self.ffmpeg.crop(infile,
                                         outfile,
                                         options,
                                         timeout=timeout):
            yield int((100.0 * timecode) / info.format.duration)

    def get_stream_info(self, file):
        if not os.path.exists(file) and not self.ffmpeg.is_url(file):
            raise ConverterError("Source file doesn't exist: " + file)

        info = self.ffmpeg.probe(file)
        if info is None:
            raise ConverterError("Can't get information about source file")

        if not info.video and not info.audio:
            raise ConverterError('Source file has no audio or video streams')

        return info

    def get_duration(self, file):
        if not os.path.exists(file) and not self.ffmpeg.is_url(file):
            raise ConverterError("Source file doesn't exist: " + file)

        info = self.ffmpeg.probe(file)
        if info is None:
            raise ConverterError("Can't get information about source file")

        if not info.video and not info.audio:
            raise ConverterError('Source file has no audio or video streams')

        return math.ceil(info.format.duration)