diff --git a/yledl/downloader.py b/yledl/downloader.py index c520784..50a460b 100644 --- a/yledl/downloader.py +++ b/yledl/downloader.py @@ -300,7 +300,7 @@ def pipe_stream(self, valid_streams, clip, io): def should_skip_downloading(self, outputfile, downloader, clip, io): limits = io.download_limits - slicing_active = limits.start_position or 0 > 0 or limits.duration + slicing_active = ((limits.start_position or 0) > 0) or limits.duration return (not io.overwrite and os.path.exists(outputfile)) or ( not slicing_active diff --git a/yledl/yledl.py b/yledl/yledl.py index 0313693..20771d5 100644 --- a/yledl/yledl.py +++ b/yledl/yledl.py @@ -109,51 +109,155 @@ def open_config_file_and_remember_opened(filename: str, mode: str = 'r'): description=description, formatter_class=configargparse.RawDescriptionHelpFormatter, ) - parser.add_argument( - '-V', - '--verbose', - action='count', - dest='verbosity', - default=0, - help='Increase output verbosity. -VV is really verbose.', + _add_parser_arguments(parser) + + io_group = parser.add_argument_group('Input and output') + url_group = io_group.add_mutually_exclusive_group() + _add_url_groups_arguments(url_group) + _add_io_group_arguments(io_group) + action_group = io_group.add_mutually_exclusive_group() + _add_action_group_arguments(action_group) + resume_group = io_group.add_mutually_exclusive_group() + # --resume is the new default, the option is still accepted but + # doesn't do anything + _add_resume_group_arguments(resume_group) + + qual_group = parser.add_argument_group('Stream type and quality') + _add_qual_group_arguments(qual_group) + + dl_group = parser.add_argument_group('Downloader backends') + _add_dl_group_arguments(dl_group) + + return parser + + +def _add_dl_group_arguments(dl_group): + dl_group.add_argument( + '--backend', + metavar='BE', + type=str, + default='ffmpeg,wget', + help='Downloaders that are tried until one of them ' + ' succeeds (a comma-separated list). ' + 'Possible values: ' + '"wget", ' + '"ffmpeg"', ) - parser.add_argument( - '--debug', - action='store_const', - const=2, - dest='verbosity', - help='Really verbose output, same as -VV', + dl_group.add_argument( + '--ffmpeg', + metavar='PATH', + type=str, + help='Set the path of the ffmpeg executable', ) - parser.add_argument( - '-q', - action='count', - dest='quietness', - default=0, - help='Reduce output verbosity. -qq prints only errors.', + dl_group.add_argument( + '--ffprobe', + metavar='PATH', + type=str, + help='Set the path of the ffprobe executable', ) - parser.add_argument( - '-c', - '--config', - metavar='FILENAME', - is_config_file=True, - help='config file path', + dl_group.add_argument( + '--wget', + metavar='PATH', + type=str, + default='', + help='Set the path of the wget executable', ) - io_group = parser.add_argument_group('Input and output') - url_group = io_group.add_mutually_exclusive_group() - url_group.add_argument( - 'url', - nargs='?', + +def _add_qual_group_arguments(qual_group): + qual_group.add_argument( + '--sublang', + metavar='LANG', type=str, - help='Address of an Areena, Elävä Arkisto, ' 'or Yle news web page', + choices=['none', 'fin', 'swe', 'all'], + default='all', + help='Download subtitles if LANG is "all" ' + '(default), "fin" or "swe". Disable subtitles ' + 'if LANG is "none".', ) - url_group.add_argument( - '-i', - metavar='FILENAME', - dest='inputfile', + qual_group.add_argument( + '--metadatalang', + metavar='LANG', type=str, - help='Read input URLs to process from the named ' 'file, one URL per line', + choices=['fin', 'swe', 'smi'], + help='Preferred metadata language, "fin", "swe" ' 'or "smi"', ) + qual_group.add_argument( + '--latestepisode', + action='store_true', + help='Download the latest episode of a series', + ) + qual_group.add_argument( + '--maxbitrate', + metavar='RATE', + type=str, + help='Maximum bitrate stream to download, ' + 'integer in kB/s or "best" or "worst".', + ) + qual_group.add_argument( + '--resolution', + metavar='RES', + type=str, + help='Maximum vertical resolution in pixels, ' + 'default: highest available resolution', + ) + qual_group.add_argument( + '--startposition', + metavar='S', + type=int, + help='Start recording at S seconds from the start ' 'of the stream', + ) + qual_group.add_argument( + '--duration', + metavar='S', + type=int, + help='Record only the first S seconds of ' 'the stream', + ) + qual_group.add_argument( + '--preferformat', + metavar='F', + type=str, + default='mkv', + help='Preferred video output format: ' + 'mkv (default) or mp4. Applies only when ' + 'downloading with ffmpeg', + ) + + +def _add_resume_group_arguments(resume_group): + resume_group.add_argument( + '--resume', + action='store_true', + dest='resume', + default=True, + help=configargparse.SUPPRESS, + ) + resume_group.add_argument( + '--no-resume', + action='store_false', + dest='resume', + help="Don't resume partial files, " 'download the whole stream again', + ) + + +def _add_action_group_arguments(action_group): + action_group.add_argument( + '--showurl', action='store_true', help="Print URL, don't download" + ) + action_group.add_argument( + '--showtitle', action='store_true', help="Print stream title, don't download" + ) + action_group.add_argument( + '--showepisodepage', action='store_true', help='Print web page for each episode' + ) + action_group.add_argument( + '--showmetadata', + action='store_true', + help='Print metadata about available streams', + ) + + +def _add_io_group_arguments(io_group): io_group.add_argument( '-o', metavar='FILENAME', @@ -166,36 +270,36 @@ def open_config_file_and_remember_opened(filename: str, mode: str = 'r'): metavar='TEMPLATE', default='${series_separator}${title}: ${episode_separator}${timestamp}', help='Template for generating an output file name ' - 'when not using -o. Put the argument in single quotes: ' - "--output-template '${title}'. " - 'The template supports following substitutions: ' - '${title} is replaced by the title of the episode, ' - '${series} is the series title, ' - '${series_separator} is the series title followed by ": ", ' - '${season} is the string "Season XX" where XX is the season, ' - '${episode} is the season and episode number ("S02E12"), ' - '${episode_separator} is the season and episode followed by "-", ' - '${timestamp} is stream publish timestamp ("2018-12-01T18:30"), ' - '${date} is the stream publish date ("2018-12-01"), ' - '${episode_or_date} is the same as "episode", or "date" if the episode ' - 'number is unavailable, ' - '${program_id} is an unique ID, ' - '$$ is an escape and will be replaced by a literal $. ' - '/ specifies a subdirectory, but any / in the beginning is removed. ' - 'Everything else will appear as-is.', + 'when not using -o. Put the argument in single quotes: ' + "--output-template '${title}'. " + 'The template supports following substitutions: ' + '${title} is replaced by the title of the episode, ' + '${series} is the series title, ' + '${series_separator} is the series title followed by ": ", ' + '${season} is the string "Season XX" where XX is the season, ' + '${episode} is the season and episode number ("S02E12"), ' + '${episode_separator} is the season and episode followed by "-", ' + '${timestamp} is stream publish timestamp ("2018-12-01T18:30"), ' + '${date} is the stream publish date ("2018-12-01"), ' + '${episode_or_date} is the same as "episode", or "date" if the episode ' + 'number is unavailable, ' + '${program_id} is an unique ID, ' + '$$ is an escape and will be replaced by a literal $. ' + '/ specifies a subdirectory, but any / in the beginning is removed. ' + 'Everything else will appear as-is.', ) io_group.add_argument( '--output-na-placeholder', metavar='PLACEHOLDER', help='Placeholder value for unavailable meta fields ' - 'in output filename template ' - '(default is an empty string)', + 'in output filename template ' + '(default is an empty string)', ) io_group.add_argument( '--pipe', action='store_true', help='Dump stream to stdout for piping to media ' - 'player. E.g. "yle-dl --pipe URL | vlc -"', + 'player. E.g. "yle-dl --pipe URL | vlc -"', ) io_group.add_argument( '--destdir', metavar='DIR', type=str, help='Save files to DIR' @@ -212,21 +316,7 @@ def open_config_file_and_remember_opened(filename: str, mode: str = 'r'): dest='create_dirs', help='Stop if an output directory does not exist', ) - action_group = io_group.add_mutually_exclusive_group() - action_group.add_argument( - '--showurl', action='store_true', help="Print URL, don't download" - ) - action_group.add_argument( - '--showtitle', action='store_true', help="Print stream title, don't download" - ) - action_group.add_argument( - '--showepisodepage', action='store_true', help='Print web page for each episode' - ) - action_group.add_argument( - '--showmetadata', - action='store_true', - help='Print metadata about available streams', - ) + io_group.add_argument( '--restrict-filename-no-spaces', action='store_true', @@ -246,22 +336,7 @@ def open_config_file_and_remember_opened(filename: str, mode: str = 'r'): dest='filenames_no_specials', help='Alias for --restrict-filename-no-specials', ) - resume_group = io_group.add_mutually_exclusive_group() - # --resume is the new default, the option is still accepted but - # doesn't do anything - resume_group.add_argument( - '--resume', - action='store_true', - dest='resume', - default=True, - help=configargparse.SUPPRESS, - ) - resume_group.add_argument( - '--no-resume', - action='store_false', - dest='resume', - help="Don't resume partial files, " 'download the whole stream again', - ) + io_group.add_argument( '--no-overwrite', action='store_false', @@ -294,99 +369,54 @@ def open_config_file_and_remember_opened(filename: str, mode: str = 'r'): help="Write metadata to the video file's xattrs", ) - qual_group = parser.add_argument_group('Stream type and quality') - qual_group.add_argument( - '--sublang', - metavar='LANG', - type=str, - choices=['none', 'fin', 'swe', 'all'], - default='all', - help='Download subtitles if LANG is "all" ' - '(default), "fin" or "swe". Disable subtitles ' - 'if LANG is "none".', - ) - qual_group.add_argument( - '--metadatalang', - metavar='LANG', - type=str, - choices=['fin', 'swe', 'smi'], - help='Preferred metadata language, "fin", "swe" ' 'or "smi"', - ) - qual_group.add_argument( - '--latestepisode', - action='store_true', - help='Download the latest episode of a series', - ) - qual_group.add_argument( - '--maxbitrate', - metavar='RATE', - type=str, - help='Maximum bitrate stream to download, ' - 'integer in kB/s or "best" or "worst".', - ) - qual_group.add_argument( - '--resolution', - metavar='RES', + +def _add_url_groups_arguments(url_group): + url_group.add_argument( + 'url', + nargs='?', type=str, - help='Maximum vertical resolution in pixels, ' - 'default: highest available resolution', - ) - qual_group.add_argument( - '--startposition', - metavar='S', - type=int, - help='Start recording at S seconds from the start ' 'of the stream', - ) - qual_group.add_argument( - '--duration', - metavar='S', - type=int, - help='Record only the first S seconds of ' 'the stream', + help='Address of an Areena, Elävä Arkisto, ' 'or Yle news web page', ) - qual_group.add_argument( - '--preferformat', - metavar='F', + url_group.add_argument( + '-i', + metavar='FILENAME', + dest='inputfile', type=str, - default='mkv', - help='Preferred video output format: ' - 'mkv (default) or mp4. Applies only when ' - 'downloading with ffmpeg', + help='Read input URLs to process from the named ' 'file, one URL per line', ) - dl_group = parser.add_argument_group('Downloader backends') - dl_group.add_argument( - '--backend', - metavar='BE', - type=str, - default='ffmpeg,wget', - help='Downloaders that are tried until one of them ' - ' succeeds (a comma-separated list). ' - 'Possible values: ' - '"wget", ' - '"ffmpeg"', + +def _add_parser_arguments(parser): + parser.add_argument( + '-V', + '--verbose', + action='count', + dest='verbosity', + default=0, + help='Increase output verbosity. -VV is really verbose.', ) - dl_group.add_argument( - '--ffmpeg', - metavar='PATH', - type=str, - help='Set the path of the ffmpeg executable', + parser.add_argument( + '--debug', + action='store_const', + const=2, + dest='verbosity', + help='Really verbose output, same as -VV', ) - dl_group.add_argument( - '--ffprobe', - metavar='PATH', - type=str, - help='Set the path of the ffprobe executable', + parser.add_argument( + '-q', + action='count', + dest='quietness', + default=0, + help='Reduce output verbosity. -qq prints only errors.', ) - dl_group.add_argument( - '--wget', - metavar='PATH', - type=str, - default='', - help='Set the path of the wget executable', + parser.add_argument( + '-c', + '--config', + metavar='FILENAME', + is_config_file=True, + help='config file path', ) - return parser - def read_urls_from_file(f): with codecs.open(f, 'r', 'utf-8') as infile: @@ -592,18 +622,7 @@ def main(argv=sys.argv): xattr=args.xattrs, ) - if args.showurl: - action = StreamAction.PRINT_STREAM_URL - elif args.showepisodepage: - action = StreamAction.PRINT_EPISODE_PAGES - elif args.showtitle: - action = StreamAction.PRINT_STREAM_TITLE - elif args.showmetadata: - action = StreamAction.PRINT_METADATA - elif args.pipe or (args.outputfile == '-'): - action = StreamAction.PIPE - else: - action = StreamAction.DOWNLOAD + action = _set_sction(args) if logger.isEnabledFor(logging.INFO) and action not in [ StreamAction.PIPE, @@ -628,27 +647,7 @@ def main(argv=sys.argv): warn_on_obsolete_ffmpeg(backends, io) warn_on_output_template_syntax_change(title_formatter) - for i, url in enumerate(urls): - if len(urls) > 1: - logger.info('') - logger.info(f'Now downloading from URL {i + 1}/{len(urls)}: {url}') - - if args.startposition is not None: - io.download_limits.start_position = args.startposition - elif start_position_from_url(url) is not None: - io.download_limits.start_position = start_position_from_url(url) - - res = execute_action( - url, - action=action, - io=io, - httpclient=httpclient, - title_formatter=title_formatter, - stream_filters=stream_filters, - ) - - if res != RD_SUCCESS: - exit_status = res + exit_status = _handle_urls(action, args, httpclient, io, stream_filters, title_formatter, urls) except FfmpegNotFoundError: logger.error('ffmpeg or ffprobe not found on PATH.') logger.error( @@ -661,5 +660,46 @@ def main(argv=sys.argv): return exit_status +def _set_sction(args): + if args.showurl: + action = StreamAction.PRINT_STREAM_URL + elif args.showepisodepage: + action = StreamAction.PRINT_EPISODE_PAGES + elif args.showtitle: + action = StreamAction.PRINT_STREAM_TITLE + elif args.showmetadata: + action = StreamAction.PRINT_METADATA + elif args.pipe or (args.outputfile == '-'): + action = StreamAction.PIPE + else: + action = StreamAction.DOWNLOAD + return action + + +def _handle_urls(action, args, httpclient, io, stream_filters, title_formatter, urls): + for i, url in enumerate(urls): + if len(urls) > 1: + logger.info('') + logger.info(f'Now downloading from URL {i + 1}/{len(urls)}: {url}') + + if args.startposition is not None: + io.download_limits.start_position = args.startposition + elif start_position_from_url(url) is not None: + io.download_limits.start_position = start_position_from_url(url) + + res = execute_action( + url, + action=action, + io=io, + httpclient=httpclient, + title_formatter=title_formatter, + stream_filters=stream_filters, + ) + + if res != RD_SUCCESS: + exit_status = res + return exit_status + + if __name__ == '__main__': sys.exit(main())