Пример #1
0
def parse_shift_string(shift_string):
    try:
        if ':' in shift_string:
            negator = 1
            if shift_string.startswith('-'):
                negator = -1
                shift_string = shift_string[1:]
            parts = list(map(float, shift_string.split(':')))
            if len(parts) > 3:
                raise PrassError(
                    "Invalid shift value: '{0}'".format(shift_string))
            shift_seconds = sum(
                part * multiplier
                for part, multiplier in zip(reversed(parts), (1.0, 60.0,
                                                              3600.0)))
            return shift_seconds * 1000 * negator  # convert to ms
        else:
            if shift_string.endswith("ms"):
                return float(shift_string[:-2])
            elif shift_string.endswith("s"):
                return float(shift_string[:-1]) * 1000
            else:
                return float(shift_string) * 1000
    except ValueError:
        raise PrassError("Invalid shift value: '{0}'".format(shift_string))
Пример #2
0
def parse_resolution_string(resolution_string):
    if resolution_string == '720p':
        return 1280,720
    if resolution_string == '1080p':
        return 1920,1080
    for separator in (':', 'x', ","):
        if separator in resolution_string:
            width, _, height = resolution_string.partition(separator)
            try:
                return int(width), int(height)
            except ValueError:
                raise PrassError("Invalid resolution string: '{0}'".format(resolution_string))
    raise PrassError("Invalid resolution string: '{0}'".format(resolution_string))
Пример #3
0
def parse_fps_string(fps_string):
    if '/' in fps_string:
        parts = fps_string.split('/')
        if len(parts) > 2:
            raise PrassError('Invalid fps value')
        try:
            return float(parts[0]) / float(parts[1])
        except ValueError:
            raise PrassError('Invalid fps value')
    else:
        try:
            return float(fps_string)
        except ValueError:
            raise PrassError('Invalid fps value')
Пример #4
0
def shift(input_file, output_file, shift_by, shift_start, shift_end,
          multiplier):
    """Shift all lines in a script by defined amount and/or change speed.

    \b
    You can use one of the following formats to specify the time for shift:
        - "1.5s" or just "1.5" means 1 second 500 milliseconds
        - "150ms" means 150 milliseconds
        - "1:7:12.55" means 1 hour, 7 minutes, 12 seconds and 550 milliseconds. All parts are optional.
    Every format allows a negative sign before the value, which means "shift back", like "-12s"

    \b
    Optionally, specify multiplier to change speed:
        - 1.2 makes subs 20% faster
        - 7/8 makes subs 12.5% slower

    \b
    To shift both start end end time by one minute and 15 seconds:
    $ prass shift input.ass --by 1:15 -o output.ass
    To shift only start time by half a second back:
    $ prass shift input.ass --start --by -0.5s -o output.ass
    """
    if not shift_start and not shift_end:
        shift_start = shift_end = True

    shift_ms = parse_shift_string(shift_by)
    multiplier = parse_fps_string(multiplier)
    if multiplier < 0:
        raise PrassError('Speed multiplier should be a positive number')
    script = AssScript.from_ass_stream(input_file)
    script.shift(shift_ms, shift_start, shift_end, multiplier)
    script.to_ass_stream(output_file)
Пример #5
0
def tpp(input_file, output_file, styles, lead_in, lead_out, max_overlap,
        max_gap, adjacent_bias, keyframes_path, timecodes_path, fps,
        kf_before_start, kf_after_start, kf_before_end, kf_after_end):
    """Timing post-processor.
    It's a pretty straightforward port from Aegisub so you should be familiar with it.
    You have to specify keyframes and timecodes (either as a CFR value or a timecodes file) if you want keyframe snapping.
    All parameters default to zero so if you don't want something - just don't put it in the command line.

    \b
    To add lead-in and lead-out:
    $ prass tpp input.ass --lead-in 50 --lead-out 150 -o output.ass
    To make adjacent lines continuous, with 80% bias to changing end time of the first line:
    $ prass tpp input.ass --overlap 50 --gap 200 --bias 80 -o output.ass
    To snap events to keyframes without a timecodes file:
    $ prass tpp input.ass --keyframes kfs.txt --fps 23.976 --kf-before-end 150 --kf-after-end 150 --kf-before-start 150 --kf-after-start 150 -o output.ass
    """

    if fps and timecodes_path:
        raise PrassError(
            'Timecodes file and fps cannot be specified at the same time')
    if fps:
        timecodes = Timecodes.cfr(parse_fps_string(fps))
    elif timecodes_path:
        timecodes = Timecodes.from_file(timecodes_path)
    elif any((kf_before_start, kf_after_start, kf_before_end, kf_after_end)):
        raise PrassError(
            'You have to provide either fps or timecodes file for keyframes processing'
        )
    else:
        timecodes = None

    if timecodes and not keyframes_path:
        raise PrassError(
            'You have to specify keyframes file for keyframes processing')

    keyframes_list = parse_keyframes(
        keyframes_path) if keyframes_path else None

    actual_styles = []
    for style in styles:
        actual_styles.extend(x.strip() for x in style.split(','))

    script = AssScript.from_ass_stream(input_file)
    script.tpp(actual_styles, lead_in, lead_out, max_overlap, max_gap,
               adjacent_bias, keyframes_list, timecodes, kf_before_start,
               kf_after_start, kf_before_end, kf_after_end)
    script.to_ass_stream(output_file)
Пример #6
0
    def from_ass_stream(cls, file_object):
        sections = []
        current_section = None
        force_last_section = False
        for idx, line in enumerate(file_object):
            line = line.strip()
            # required because a line might be both a part of an attachment and a valid header
            if force_last_section:
                try:
                    force_last_section = current_section.parse_line(line)
                    continue
                except Exception as e:
                    raise PrassError(
                        u"That's some invalid ASS script: {0}".format(
                            e.message))

            if not line:
                continue
            low = line.lower()
            if low == u'[v4+ styles]':
                current_section = StylesSection()
                sections.append((line, current_section))
            elif low == u'[events]':
                current_section = EventsSection()
                sections.append((line, current_section))
            elif low == u'[script info]':
                current_section = ScriptInfoSection()
                sections.append((line, current_section))
            elif low == u'[graphics]' or low == u'[fonts]':
                current_section = AttachmentSection()
                sections.append((line, current_section))
            elif re.match(r'^\s*\[.+?\]\s*$', low):
                current_section = GenericSection()
                sections.append((line, current_section))
            elif not current_section:
                raise PrassError(
                    u"That's some invalid ASS script (no parse function at line {0})"
                    .format(idx))
            else:
                try:
                    force_last_section = current_section.parse_line(line)
                except Exception as e:
                    raise PrassError(
                        u"That's some invalid ASS script: {0}".format(
                            e.message))
        return cls(sections)
Пример #7
0
def parse_keyframes(path):
    with open(path) as file_object:
        text = file_object.read()
    if text.find('# XviD 2pass stat file')>=0:
        frames = parse_scxvid_keyframes(text)
    else:
        raise PrassError('Unsupported keyframes type')
    if 0 not in frames:
        frames.insert(0, 0)
    return frames
Пример #8
0
def parse_srt_time(string):
    m = re.match(r"(\d+):(\d+):(\d+)\,(\d+)", string)
    if m != None:
        groups = m.groups()
        hours, minutes, seconds, milliseconds = list(map(int, groups))
        return hours * 3600000 + minutes * 60000 + seconds * 1000 + milliseconds
    else:
        raise PrassError(
            "____\nFailed to parse ass time in the following line:\n{0}\n____".
            format(string))
    return None
Пример #9
0
def convert_srt(input_path, output_file, encoding):
    """Convert SRT script to ASS.

    \b
    Example:
    $ prass convert-srt input.srt -o output.ass --encoding cp1251
    """
    try:
        with click.open_file(input_path, encoding=encoding) as input_file:
            AssScript.from_srt_stream(input_file).to_ass_stream(output_file)
    except LookupError:
        raise PrassError("Encoding {0} doesn't exist".format(encoding))
Пример #10
0
 def parse(cls, text):
     lines = text.splitlines()
     if not lines:
         return []
     first = lines[0].lower().lstrip()
     if first.startswith('# timecode format v2'):
         tcs = [x for x in lines[1:]]
         return Timecodes(tcs, None)
     elif first.startswith('# timecode format v1'):
         default = float(lines[1].lower().replace('assume ', ""))
         overrides = (x.split(',') for x in lines[2:])
         return Timecodes(cls._convert_v1_to_v2(default, overrides), default)
     else:
         raise PrassError('This timecodes format is not supported')
Пример #11
0
    def tpp(self, styles, lead_in, lead_out, max_overlap, max_gap,
            adjacent_bias, keyframes_list, timecodes, kf_before_start,
            kf_after_start, kf_before_end, kf_after_end):
        def get_closest_kf(frame, keyframes):
            idx = bisect.bisect_left(keyframes, frame)
            if idx == len(keyframes):
                return keyframes[-1]
            if idx == 0 or keyframes[idx] - frame < frame - (keyframes[idx -
                                                                       1]):
                return keyframes[idx]
            return keyframes[idx - 1]

        events_iter = (e for e in self._events if not e.is_comment)
        if styles:
            styles = set(s.lower() for s in styles)
            events_iter = (e for e in events_iter if e.style.lower() in styles)

        events_list = sorted(events_iter, key=lambda x: x.start)
        broken = next((e for e in events_list if e.start > e.end), None)
        if broken:
            raise PrassError(
                "One of the lines in the file ({0}) has negative duration. Aborting."
                .format(broken))

        if lead_in:
            sorted_by_end = sorted(events_list, key=lambda x: x.end)
            for idx, event in enumerate(sorted_by_end):
                initial = max(event.start - lead_in, 0)
                for other in reversed(sorted_by_end[:idx]):
                    if other.end <= initial:
                        break
                    if not event.collides_with(other):
                        initial = max(initial, other.end)
                event.start = initial

        if lead_out:
            for idx, event in enumerate(events_list):
                initial = event.end + lead_out
                for other in events_list[idx:]:
                    if other.start > initial:
                        break
                    if not event.collides_with(other):
                        initial = min(initial, other.start)
                event.end = initial

        if max_overlap or max_gap:
            bias = adjacent_bias / 100.0

            for previous, current in zip(events_list, events_list[1:]):
                distance = current.start - previous.end
                if (distance < 0 and -distance <= max_overlap) or (
                        distance > 0 and distance <= max_gap):
                    new_time = previous.end + distance * bias
                    current.start = new_time
                    previous.end = new_time

        if kf_before_start or kf_after_start or kf_before_end or kf_after_end:
            for event in events_list:
                start_frame = timecodes.get_frame_number(
                    event.start, timecodes.TIMESTAMP_START)
                end_frame = timecodes.get_frame_number(event.end,
                                                       timecodes.TIMESTAMP_END)

                closest_frame = get_closest_kf(start_frame, keyframes_list)
                closest_time = timecodes.get_frame_time(
                    closest_frame, timecodes.TIMESTAMP_START)

                if (end_frame > closest_frame >= start_frame and closest_time - event.start <= kf_after_start) or \
                        (closest_frame <= start_frame and event.start - closest_time <= kf_before_start):
                    event.start = max(0, closest_time)

                closest_frame = get_closest_kf(end_frame, keyframes_list) - 1
                closest_time = timecodes.get_frame_time(
                    closest_frame, timecodes.TIMESTAMP_END)
                if (start_frame < closest_frame <= end_frame and event.end - closest_time <= kf_before_end) or \
                        (closest_frame >= end_frame and closest_time - event.end <= kf_after_end):
                    event.end = closest_time
Пример #12
0
 def from_ass_file(cls, path):
     try:
         with codecs.open(path, encoding='utf-8-sig') as script:
             return cls.from_ass_stream(script)
     except IOError:
         raise PrassError("Script {0} not found".format(path))