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()
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()
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)
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)
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)
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)
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)
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()
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 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()