예제 #1
0
def download_subs_and_cache_results():
    import argparse

    parser = argparse.ArgumentParser(
        description='TV Series Tools: Download subtitles for movies specified'
        ' by passed IMDB IDs'
    )
    parser.add_argument('--input', '-i', dest='inputfile', required=True,
                        help='movies CSV file: 1st column = IMDB ID, 2nd column = title')
    parser.add_argument('--output', '-o', dest='outputdir', required=True,
                        help='path to a directory in which the subtitle SRT'
                        ' files will be downloaded')
    parser.add_argument('--cache', '-c', dest='cachedir', required=True,
                        help='cache location')
    parser.add_argument('--shuffle', '-s', dest='shuffle', action='store_true',
                        help='shuffle the list of IMDB IDs')
    parser.add_argument('--timeout', '-t', dest='timeout', type=int,
                        default=DEFAULT_TIMEOUT,
                        help='time in seconds between OpenSubtitles\'s API'
                        ' calls, if not set {} is used'
                        .format(DEFAULT_TIMEOUT))
    args = parser.parse_args()

    opensub = OpenSubtitles()
    opensub.login(os.environ['OPENSUB_USER'], os.environ['OPENSUB_PASSWD'])

    movies = listio.read_map(args.inputfile)
    if args.shuffle:
        movies = list(movies)
        random.shuffle(movies)

    excl_ids = []
    for f in (FILE_NAME_DOWNLOADED, FILE_NAME_FAILED):
        try:
            lines = listio.read_map(os.path.join(args.cachedir, f))
            for line in lines:
                excl_ids.append(line[0])
        except FileNotFoundError:
            pass

    if not os.path.isdir(args.cachedir):
        os.makedirs(args.cachedir)
    if not os.path.isdir(args.outputdir):
        os.makedirs(args.outputdir)

    write_result(
        os.path.join(args.cachedir, FILE_NAME_DOWNLOADED),
        os.path.join(args.cachedir, FILE_NAME_FAILED),
        download_subs(
            movies,
            excl_ids,
            args.outputdir,
            opensub,
            timeout=args.timeout
        )
    )

    sys.exit()
def check_positive_answers():
    import argparse

    parser = argparse.ArgumentParser(
        description='TV Series Tools: Check again positive answers')
    parser.add_argument(
        '--input',
        '-i',
        dest='inputfile',
        required=True,
        help='path to a file with the answers',
    )
    parser.add_argument(
        '--output',
        '-o',
        dest='outputfile',
        required=True,
        help='path to a file in which the new answers will be'
        ' stored',
    )
    parser.add_argument(
        '--totals',
        '-t',
        dest='totals',
        action='store_true',
        help='show total number of answers to check,'
        ' this will cause that if there are a lot of answers'
        ' it will take quite a lot of time before the first'
        ' question shows up',
    )
    args = parser.parse_args()

    answers = listio.read_map(args.inputfile)
    answers_positive = filter_positive_answers(answers)
    matches_positive = (convert_answer_to_match(answer)
                        for answer in answers_positive)
    answers_checked = listio.read_map(args.outputfile)
    # Using list instead of generator so that we can read it several times.
    new_matches_answered = [
        convert_answer_to_match(answer) for answer in answers_checked
    ]
    matches_not_checked = filter_matches(matches_positive,
                                         new_matches_answered)
    matches_with_context = add_subs_context_to_matches(matches_not_checked, 3)
    new_answers_checked = approve_matches(matches_with_context,
                                          totals=args.totals)

    listio.write_map(args.outputfile, new_answers_checked)

    sys.exit()
예제 #3
0
def search_subs_and_save_matches():
    import argparse

    parser = argparse.ArgumentParser(
        description='TV Series Tools: Search subtitles'
    )
    parser.add_argument('--input', '-i', dest='inputdir', required=True,
                        help='path to a directory with the subtitle SRT files')
    parser.add_argument('--output', '-o', dest='outputfile', required=True,
                        help='path to a file in which the matches will be'
                        ' stored')
    parser.add_argument('--patterns', '-p', dest='patterns', required=True,
                        help='path to a file with search patterns')
    args = parser.parse_args()

    excl = [
        common.convert_list_to_match(match)
        for match in listio.read_map(args.outputfile)
    ]
    regex_list = [
        compile_regex(pattern)
        for pattern in listio.read_list(args.patterns)
    ]
    paths = iter_subs_files(args.inputdir)
    subs = read_subs(paths)
    matches = search_subs(subs, excl, regex_list)
    matches_list = (
        common.convert_match_to_list(match)
        for match in matches
    )

    listio.write_map(args.outputfile, matches_list)

    sys.exit()
def approve_matches_and_save_answers():
    import argparse

    parser = argparse.ArgumentParser(
        description='TV Series Tools: Check matches and save answers')
    parser.add_argument(
        '--input',
        '-i',
        dest='inputfile',
        required=True,
        help='path to a file with the matches',
    )
    parser.add_argument(
        '--output',
        '-o',
        dest='outputfile',
        required=True,
        help='path to a file in which the answers will be'
        ' stored',
    )
    parser.add_argument(
        '--totals',
        '-t',
        dest='totals',
        action='store_true',
        help='show total number of matches to answer,'
        ' this will cause that if there are a lot of matches'
        ' it will take quite a lot of time before the first'
        ' question shows up',
    )
    args = parser.parse_args()

    matches_list = listio.read_map(args.inputfile)
    matches = (common.convert_list_to_match(l) for l in matches_list)
    answers_old = listio.read_map(args.outputfile)
    # Using list instead of generator so that we can read it several times.
    matches_answered = [
        convert_answer_to_match(answer) for answer in answers_old
    ]
    matches_not_answered = filter_matches(matches, matches_answered)
    matches_with_context = add_subs_context_to_matches(matches_not_answered, 2)
    answers_new = approve_matches(matches_with_context, totals=args.totals)
    answers = itertools.chain(answers_old, answers_new)

    listio.write_map(args.outputfile, answers)

    sys.exit()
def print_positive_answers():
    import argparse

    parser = argparse.ArgumentParser(
        description='TV Series Tools: Print positive answers to stdout'
    )
    parser.add_argument('--input', '-i', dest='inputfile', required=True,
                        help='path to a file with the answers')
    parser.add_argument('--format', '-f', dest='format', required=True,
                        choices=['clips', 'clips_comment', 'subtitles'],
                        default='clips',
                        help='output format, "clips": CSV path,start,end,text;'
                        ' "clips_comment": same as "clips" but with all lines'
                        ' prefixed with "# ";'
                        ' "subtitles": subtitle lines -- the matched one,'
                        ' 3 before and 3 after;'
                        ' defaults to "clips"')
    parser.add_argument('--no-sort', '-ns', dest='nosort', action='store_true',
                        help='do not sort the results; defaults to false,'
                        ' which means the results will be sorted by subtitles'
                        ' file path (case-insensitive)')
    args = parser.parse_args()

    answers = listio.read_map(args.inputfile)
    approved = filter_positive_answers(answers)
    matches = (
        convert_answer_to_match(answer)
        for answer in approved
    )

    if args.nosort:
        matches_sorted = matches
    else:
        matches_sorted = sorted(matches,
                                key=lambda match: match['file_path'].lower())

    if args.format == 'clips':
        [
            print(format_match_as_clips(match))
            for match in matches_sorted
        ]
    elif args.format == 'clips_comment':
        [
            print(format_match_as_clips(match, comment=True))
            for match in matches_sorted
        ]
    elif args.format == 'subtitles':
        matches_sorted_with_context = add_subs_context_to_matches(
            matches_sorted, 3
        )
        [
            print(format_match_with_context(match, color=False))
            for match in matches_sorted_with_context
        ]

    sys.exit()
def check_positive_answers():
    import argparse

    parser = argparse.ArgumentParser(
        description='TV Series Tools: Check again positive answers'
    )
    parser.add_argument('--input', '-i', dest='inputfile', required=True,
                        help='path to a file with the answers')
    parser.add_argument('--output', '-o', dest='outputfile', required=True,
                        help='path to a file in which the new answers will be'
                        ' stored')
    parser.add_argument('--totals', '-t', dest='totals', action='store_true',
                        help='show total number of answers to check,'
                        ' this will cause that if there are a lot of answers'
                        ' it will take quite a lot of time before the first'
                        ' question shows up')
    args = parser.parse_args()

    answers = listio.read_map(args.inputfile)
    answers_positive = filter_positive_answers(answers)
    matches_positive = (
        convert_answer_to_match(answer)
        for answer in answers_positive
    )
    answers_checked = listio.read_map(args.outputfile)
    # Using list instead of generator so that we can read it several times.
    new_matches_answered = [
        convert_answer_to_match(answer)
        for answer in answers_checked
    ]
    matches_not_checked = filter_matches(
        matches_positive,
        new_matches_answered
    )
    matches_with_context = add_subs_context_to_matches(matches_not_checked, 3)
    new_answers_checked = approve_matches(
        matches_with_context,
        totals=args.totals
    )

    listio.write_map(args.outputfile, new_answers_checked)

    sys.exit()
예제 #7
0
 def from_csv(cls, f: IO, limit: int = DEFAULT_LIMIT) -> 'ClipMetas':
     rows = listio.read_map(f)
     if not rows:
         raise CompositionError('Input CSV file is empty')
     metas = []
     for i, row in enumerate(rows):
         if i == limit:
             logger.info('Reached limit %d', limit)
             break
         metas.append(ClipMeta.from_row(row))
     return cls(metas)
예제 #8
0
 def test_read_map_custom_format(self):
     TMP_FILE_PATH = '_tmp_map.txt'
     with open(TMP_FILE_PATH, 'w') as f:
         f.write('First column,"second column",3\r\n'
                 '# this is a comment\r\n'
                 '"next;item,",foo,bar\r\n')
     result = list(listio.read_map(TMP_FILE_PATH, delimiter=u','))
     expected = [
         ['First column', 'second column', '3'],
         ['next;item,', 'foo', 'bar'],
     ]
     self.assertEqual(result, expected)
     os.remove(TMP_FILE_PATH)
예제 #9
0
 def test_read_map(self):
     TMP_FILE_PATH = '_tmp_map.txt'
     with open(TMP_FILE_PATH, 'w') as f:
         f.write('First column;"second column";3\n'
                 '# this is a comment\n'
                 '"next;item,";foo;bar\n')
     result = list(listio.read_map(TMP_FILE_PATH))
     expected = [
         ['First column', 'second column', '3'],
         ['next;item,', 'foo', 'bar'],
     ]
     self.assertEqual(result, expected)
     os.remove(TMP_FILE_PATH)
예제 #10
0
 def test_read_map_custom_format(self):
     TMP_FILE_PATH = '_tmp_map.txt'
     with open(TMP_FILE_PATH, 'w') as f:
         f.write(
             'First column,"second column",3\r\n'
             '# this is a comment\r\n'
             '"next;item,",foo,bar\r\n'
         )
     result = list(listio.read_map(TMP_FILE_PATH, delimiter=u','))
     expected = [
         ['First column', 'second column', '3'],
         ['next;item,', 'foo', 'bar'],
     ]
     self.assertEqual(result, expected)
     os.remove(TMP_FILE_PATH)
예제 #11
0
 def test_read_map(self):
     TMP_FILE_PATH = '_tmp_map.txt'
     with open(TMP_FILE_PATH, 'w') as f:
         f.write(
             'First column;"second column";3\n'
             '# this is a comment\n'
             '"next;item,";foo;bar\n'
         )
     result = list(listio.read_map(TMP_FILE_PATH))
     expected = [
         ['First column', 'second column', '3'],
         ['next;item,', 'foo', 'bar'],
     ]
     self.assertEqual(result, expected)
     os.remove(TMP_FILE_PATH)
예제 #12
0
def search_subs_and_save_matches():
    import argparse

    parser = argparse.ArgumentParser(
        description='TV Series Tools: Search subtitles')
    parser.add_argument(
        '--input',
        '-i',
        dest='inputdir',
        required=True,
        help='path to a directory with the subtitle SRT files',
    )
    parser.add_argument(
        '--output',
        '-o',
        dest='outputfile',
        required=True,
        help='path to a file in which the matches will be'
        ' stored',
    )
    parser.add_argument(
        '--patterns',
        '-p',
        dest='patterns',
        required=True,
        help='path to a file with search patterns',
    )
    args = parser.parse_args()

    if os.path.isfile(args.outputfile):
        excl = [
            common.convert_list_to_match(match)
            for match in listio.read_map(args.outputfile)
        ]
    else:
        excl = []
    regex_list = [
        compile_regex(pattern) for pattern in listio.read_list(args.patterns)
    ]
    paths = iter_subs_files(args.inputdir)
    subs = read_subs(paths)
    matches = search_subs(subs, excl, regex_list)
    matches_list = (common.convert_match_to_list(match)
                    for match in itertools.chain(excl, matches))

    listio.write_map(args.outputfile, matches_list)

    sys.exit()
예제 #13
0
def download_subs_and_cache_results():
    import argparse

    parser = argparse.ArgumentParser(
        description='TV Series Tools: Download subtitles for movies specified'
        ' by passed IMDB IDs')
    parser.add_argument(
        '--input',
        '-i',
        dest='inputfile',
        required=True,
        help='movies CSV file: 1st column = IMDB ID, 2nd column = title')
    parser.add_argument('--output',
                        '-o',
                        dest='outputdir',
                        required=True,
                        help='path to a directory in which the subtitle SRT'
                        ' files will be downloaded')
    parser.add_argument('--cache',
                        '-c',
                        dest='cachedir',
                        required=True,
                        help='cache location')
    parser.add_argument('--shuffle',
                        '-s',
                        dest='shuffle',
                        action='store_true',
                        help='shuffle the list of IMDB IDs')
    parser.add_argument(
        '--timeout',
        '-t',
        dest='timeout',
        type=int,
        default=DEFAULT_TIMEOUT,
        help='time in seconds between OpenSubtitles\'s API'
        ' calls, if not set {} is used'.format(DEFAULT_TIMEOUT))
    args = parser.parse_args()

    opensub = OpenSubtitles()
    opensub.login(os.environ['OPENSUB_USER'], os.environ['OPENSUB_PASSWD'])

    movies = listio.read_map(args.inputfile)
    if args.shuffle:
        movies = list(movies)
        random.shuffle(movies)

    excl_ids = []
    for f in (FILE_NAME_DOWNLOADED, FILE_NAME_FAILED):
        try:
            lines = listio.read_map(os.path.join(args.cachedir, f))
            for line in lines:
                excl_ids.append(line[0])
        except FileNotFoundError:
            pass

    if not os.path.isdir(args.cachedir):
        os.makedirs(args.cachedir)
    if not os.path.isdir(args.outputdir):
        os.makedirs(args.outputdir)

    write_result(
        os.path.join(args.cachedir, FILE_NAME_DOWNLOADED),
        os.path.join(args.cachedir, FILE_NAME_FAILED),
        download_subs(movies,
                      excl_ids,
                      args.outputdir,
                      opensub,
                      timeout=args.timeout))

    sys.exit()
예제 #14
0
def main():
    import argparse

    parser = argparse.ArgumentParser(
        description='TV Series Tools: Video'
    )
    parser.add_argument('--input', '-i', dest='inputfile', required=True,
                        help='file path to a file containing info on how to'
                        ' cut the clips')
    parser.add_argument('--clips', '-c', dest='clipsdir', required=True,
                        help='clips video files location')
    parser.add_argument('--output', '-o', dest='outputdir', required=True,
                        help='directory name inside --clips directory in which'
                        ' the cut clips will be rendered, or path to a single'
                        ' output video file if --join is set')
    parser.add_argument('--join', '-j', dest='join', action='store_true',
                        help='concat cut video clips')
    parser.add_argument('--video-fps', '-vf', dest='video_fps', type=int,
                        help='video fps, defaults to {}'
                        .format(DEFAULT_FPS))
    parser.add_argument('--video-ext', '-ve', dest='video_ext',
                        help='video file extension, defaults to {}'
                        .format(DEFAULT_EXT))
    parser.add_argument('--video-codec', '-vc', dest='video_codec',
                        help='video codec, defaults to not set, which means'
                        ' that moviepy will chose the codec automatically')
    parser.add_argument('--video-params', '-vp', dest='video_params',
                        help='additional parameters for FFmpeg,'
                        ' example: --video-params="-vf eq=gamma=1.5"')
    parser.add_argument('--resize-width', '-rw', dest='resize_width',
                        type=int,
                        help='resize width; you must set both --resize-width'
                        ' and --resize-height')
    parser.add_argument('--resize-height', '-rh', dest='resize_height',
                        type=int,
                        help='resize height; you must set both --resize-width'
                        ' and --resize-height')
    parser.add_argument('--limit', '-l', dest='limit', type=int,
                        default=DEFAULT_LIMIT,
                        help='process only first <limit> clips')
    parser.add_argument('--speed', '-sp', dest='speed', type=float,
                        help='speed of the composition; the standard speed'
                        ' will be multiplied by this number, hence'
                        ' 1 = normal speed, 0.5 = half the normal speed,'
                        ' 3 = three times as fast, etc.')
    parser.add_argument('--subtitles', '-sb', dest='subtitles',
                        action='store_true',
                        help='render subtitles')
    parser.add_argument('--intertitles', '-it', dest='intertitles',
                        action='store_true',
                        help='render itertitles')
    parser.add_argument('--intertitle-color', '-ic', dest='intertitle_color',
                        default=DEFAULT_TEXT_COLOR,
                        help='itertitle color; default \'{}\''
                        .format(DEFAULT_TEXT_COLOR))
    parser.add_argument('--intertitle-font', '-if', dest='intertitle_font',
                        default=DEFAULT_TEXT_FONT,
                        help='itertitle font; default \'{}\''
                        .format(DEFAULT_TEXT_FONT))
    parser.add_argument('--intertitle-fontsize', '-is',
                        dest='intertitle_fontsize', type=int,
                        default=DEFAULT_INTERTITLE_FONTSIZE,
                        help='itertitle font size in px; default \'{}\''
                        .format(DEFAULT_INTERTITLE_FONTSIZE))
    parser.add_argument('--intertitle-position', '-ip',
                        dest='intertitle_position',
                        default=DEFAULT_INTERTITLE_POSITION,
                        help='itertitle position; default \'{}\''
                        .format(DEFAULT_INTERTITLE_POSITION))
    parser.add_argument('--intertitle-duration', '-id',
                        dest='intertitle_duration', type=int,
                        default=DEFAULT_INTERTITLE_DURATION,
                        help='itertitle duration in seconds; default \'{}\''
                        .format(DEFAULT_INTERTITLE_DURATION))
    parser.add_argument('--fadeout', '-fd', dest='fadeout', type=int,
                        help='duration in milliseconds of a fadeout after each'
                        ' clip; defaults to 0 meaning no fadeout')
    args = parser.parse_args()

    composition = listio.read_map(args.inputfile)
    if not composition:
        sys.exit(1)

    all_clips = []

    cache_video_clips = {}
    for i, composition in enumerate(composition):
        if i == args.limit:
            print('LIMIT {} HIT'.format(args.limit))
            break

        file_path = os.path.join(args.clipsdir, composition[0])
        print('CLIP {} "{}"'.format(i, file_path))
        cut_start = parse_duration(composition[1])
        cut_end = parse_duration(composition[2])
        if not composition[1] or not composition[2]:
            print('  SKIP no cut defined')
            continue
        print('  CUT {} --> {}'.format(cut_start, cut_end))

        if composition[0] in DEBUG_SKIP:
            print('  SKIP clip found in DEBUG_SKIP list')
            continue
        if not os.path.isfile(file_path):
            print('  SKIP file not found')
            continue
        if not args.join:
            params = []
            if args.intertitles:
                params.append('i')
            clip_file_path = format_clip_file_path(
                file_path,
                args.outputdir,
                cut_start,
                cut_end,
                ext=args.video_ext,
                params=params)
            print('  OUTPUT "{}"'.format(clip_file_path))
            if os.path.isfile(clip_file_path):
                print('  SKIP output exists "{}"'.format(clip_file_path))
                continue

        if file_path not in cache_video_clips:
            cache_video_clips[file_path] = VideoFileClip(file_path)
        video_clip = cache_video_clips[file_path]

        if cut_start and cut_end:
            video_sub_clip = video_clip.subclip(cut_start, cut_end)
        else:
            video_sub_clip = video_clip
        if args.video_fps:
            video_sub_clip = video_sub_clip.set_fps(args.video_fps)

        composite_clip = video_sub_clip
        if args.resize_width and args.resize_height:
            composite_clip = filter_resize(
                composite_clip,
                args.resize_width,
                args.resize_height)
        if args.subtitles:
            raise NotImplementedError
            # TODO: Figure out what subtitles path should be.
            # composite_clip = filter_add_subtitles(
            #     composite_clip,
            #     subtitles_path)
        if args.intertitles:
            text = composition[3]
            print('  INTERTITLE {}'.format(text))
            if args.resize_width and args.resize_height:
                intertitle_size_w = args.resize_width
                intertitle_size_h = args.resize_height
            else:
                intertitle_size_w = composite_clip.w
                intertitle_size_h = composite_clip.h
            composite_clip = filter_add_intertitle(
                composite_clip,
                text,
                args.intertitle_color,
                args.intertitle_font,
                args.intertitle_fontsize,
                args.intertitle_position,
                args.intertitle_duration,
                intertitle_size_w,
                intertitle_size_h)
        if args.speed:
            composite_clip = filter_adjust_speed(
                composite_clip,
                args.speed)
        if args.fadeout:
            composite_clip = filter_fadeout(
                composite_clip,
                args.fadeout)

        if args.join:
            all_clips.append(composite_clip)
        else:
            render(
                composite_clip,
                clip_file_path,
                fps=args.video_fps,
                ext=args.video_ext,
                codec=args.video_codec,
                ffmpeg_params=args.video_params)

    if args.join:
        joined_clip = concatenate_videoclips(all_clips)
        render(
            joined_clip,
            args.outputdir,
            fps=args.video_fps,
            ext=args.video_ext,
            codec=args.video_codec,
            ffmpeg_params=args.video_params)

    sys.exit()
def print_positive_answers():
    import argparse

    parser = argparse.ArgumentParser(
        description='TV Series Tools: Print positive answers to stdout')
    parser.add_argument(
        '--input',
        '-i',
        dest='inputfile',
        required=True,
        help='path to a file with the answers',
    )
    parser.add_argument(
        '--format',
        '-f',
        dest='format',
        required=True,
        choices=['clips', 'clips_comment', 'subtitles'],
        default='clips',
        help='output format, "clips": CSV path,start,end,text;'
        ' "clips_comment": same as "clips" but with all lines'
        ' prefixed with "# ";'
        ' "subtitles": subtitle lines -- the matched one,'
        ' 3 before and 3 after;'
        ' defaults to "clips"',
    )
    parser.add_argument(
        '--no-sort',
        '-ns',
        dest='nosort',
        action='store_true',
        help='do not sort the results; defaults to false,'
        ' which means the results will be sorted by subtitles'
        ' file path (case-insensitive)',
    )
    args = parser.parse_args()

    answers = listio.read_map(args.inputfile)
    approved = filter_positive_answers(answers)
    matches = (convert_answer_to_match(answer) for answer in approved)

    if args.nosort:
        matches_sorted = matches
    else:
        matches_sorted = sorted(matches,
                                key=lambda match: match['file_path'].lower())

    if args.format == 'clips':
        [print(format_match_as_clips(match)) for match in matches_sorted]
    elif args.format == 'clips_comment':
        [
            print(format_match_as_clips(match, comment=True))
            for match in matches_sorted
        ]
    elif args.format == 'subtitles':
        matches_sorted_with_context = add_subs_context_to_matches(
            matches_sorted, 3)
        [
            print(format_match_with_context(match, color=False))
            for match in matches_sorted_with_context
        ]

    sys.exit()