Example #1
0
def main():
    """Command line application to download youtube videos."""
    # noinspection PyTypeChecker
    parser = argparse.ArgumentParser(description=main.__doc__)
    args = _parse_args(parser)
    if args.verbosity:
        log_level = min(args.verbosity, 4) * 10
        setup_logger(logging.FATAL - log_level)

    if not args.url or "youtu" not in args.url:
        parser.print_help()
        sys.exit(1)

    if "/playlist" in args.url:
        print("Loading playlist...")
        playlist = Playlist(args.url)
        if not args.target:
            args.target = safe_filename(playlist.title())
        for youtube_video in playlist.videos:
            try:
                _perform_args_on_youtube(youtube_video, args)
            except PytubeError as e:
                print(f"There was an error with video: {youtube_video}")
                print(e)
    else:
        print("Loading video...")
        youtube = YouTubeItem(args.url)
        _perform_args_on_youtube(youtube, args)
Example #2
0
def _ffmpeg_downloader(audio_stream: Stream, video_stream: Stream,
                       target: str) -> None:
    """
    Given a YouTube Stream object, finds the correct audio stream, downloads them both
    giving them a unique name, them uses ffmpeg to create a new file with the audio
    and video from the previously downloaded files. Then deletes the original adaptive
    streams, leaving the combination.

    :param Stream audio_stream:
        A valid Stream object representing the audio to download
    :param Stream video_stream:
        A valid Stream object representing the video to download
    :param Path target:
        A valid Path object
    """
    video_unique_name = _unique_name(safe_filename(video_stream.title),
                                     video_stream.subtype,
                                     "video",
                                     target=target)
    audio_unique_name = _unique_name(safe_filename(video_stream.title),
                                     audio_stream.subtype,
                                     "audio",
                                     target=target)
    _download(stream=video_stream, target=target, filename=video_unique_name)
    print("Loading audio...")
    _download(stream=audio_stream, target=target, filename=audio_unique_name)

    video_path = os.path.join(target,
                              f"{video_unique_name}.{video_stream.subtype}")
    audio_path = os.path.join(target,
                              f"{audio_unique_name}.{audio_stream.subtype}")
    final_path = os.path.join(
        target, f"{safe_filename(video_stream.title)}.{video_stream.subtype}")

    subprocess.run(  # nosec
        [
            "ffmpeg",
            "-i",
            video_path,
            "-i",
            audio_path,
            "-codec",
            "copy",
            final_path,
        ])
    os.unlink(video_path)
    os.unlink(audio_path)
Example #3
0
    def default_filename(self) -> str:
        """Generate filename based on the video title.

        :rtype: str
        :returns:
            An os file system compatible filename.
        """
        filename = safe_filename(self.title)
        return f"{filename}.{self.subtype}"
Example #4
0
    def download(
        self,
        title: str,
        srt: bool = True,
        output_path: Optional[str] = None,
        filename_prefix: Optional[str] = None,
    ) -> str:
        """Write the media stream to disk.

        :param title:
            Output filename (stem only) for writing media file.
            If one is not specified, the default filename is used.
        :type title: str
        :param srt:
            Set to True to download srt, false to download xml. Defaults to True.
        :type srt bool
        :param output_path:
            (optional) Output path for writing media file. If one is not
            specified, defaults to the current working directory.
        :type output_path: str or None
        :param filename_prefix:
            (optional) A string that will be prepended to the filename.
            For example a number in a playlist or the name of a series.
            If one is not specified, nothing will be prepended
            This is separate from filename so you can use the default
            filename but still add a prefix.
        :type filename_prefix: str or None

        :rtype: str
        """
        if title.endswith(".srt") or title.endswith(".xml"):
            filename = ".".join(title.split(".")[:-1])
        else:
            filename = title

        if filename_prefix:
            filename = f"{safe_filename(filename_prefix)}{filename}"

        filename = safe_filename(filename)

        filename += f" ({self.code})"

        if srt:
            filename += ".srt"
        else:
            filename += ".xml"

        file_path = os.path.join(target_directory(output_path), filename)

        with open(file_path, "w", encoding="utf-8") as file_handle:
            if srt:
                file_handle.write(self.generate_srt_captions())
            else:
                file_handle.write(self.xml_captions)

        return file_path
Example #5
0
def test_safe_filename():
    """Unsafe characters get stripped from generated filename"""
    assert helpers.safe_filename("abc1245$$") == "abc1245"
    assert helpers.safe_filename("abc##") == "abc"