├── .gitattributes ├── README.md ├── client_secrets.json ├── comp.mp4 ├── count.txt ├── main.py └── youtube.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Automated-Youtube 2 | -------------------------------------------------------------------------------------------------------------------------------- 3 | Make sure that you create a folder named "memes" and another one named "output" 4 | -------------------------------------------------------------------------------------------------------------------------------- 5 | 6 | 7 | With this simple python script you are able to automate an entire youtube channel. 8 | 9 | 10 | 1 - It will scrape videos from a given subreddit and will download them on your "memes" folder. 11 | 12 | 2 - It will merge all the non corrupted videos into 1 single video. This video is saved on your "output" folder. 13 | 14 | 3 - It will upload the video to youtube. 15 | 16 | 17 | Thats all. 18 | 19 | You just need to run the script and in a few minutes the video will be uploaded to youtube. 20 | This is my test youtube channel: https://www.youtube.com/channel/UC3TKf4aKWhalYtPzfn2lbQw 21 | subscribe ;) 22 | -------------------------------------------------------------------------------- /client_secrets.json: -------------------------------------------------------------------------------- 1 | { 2 | "web": { 3 | "client_id": "", 4 | "client_secret": "", 5 | "redirect_uris": [], 6 | "auth_uri": "", 7 | "token_uri": "" 8 | } 9 | } -------------------------------------------------------------------------------- /comp.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gen1s/Automated-Youtube/53ed29148fb7d98c59523f566b9bb3de7ba7c4e5/comp.mp4 -------------------------------------------------------------------------------- /count.txt: -------------------------------------------------------------------------------- 1 | 2 -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import praw, os 2 | import requests, datetime 3 | from moviepy.editor import * 4 | import calendar 5 | 6 | # Pesonal Reddit Info 7 | client_id = "" 8 | client_secret = "" 9 | user_agent = "" 10 | username = "" 11 | password = "" 12 | 13 | folder = os.getcwd() 14 | 15 | # Get The First Posts 16 | reddit = praw.Reddit(client_id = client_id, client_secret = client_secret, user_agent = user_agent, username = username, password = password) 17 | print(calendar.weekday(int(datetime.datetime.now().strftime('%Y')), int(datetime.datetime.now().strftime('%m')), int(datetime.datetime.now().strftime('%d')))) 18 | weekday = calendar.weekday(int(datetime.datetime.now().strftime('%Y')), int(datetime.datetime.now().strftime('%m')), int(datetime.datetime.now().strftime('%d'))) 19 | 20 | if 0 == weekday or 4 == weekday: # 21 | subred = reddit.subreddit("ClashRoyale") 22 | new = subred.hot(limit = 50) 23 | game = "Clash Royale" 24 | 25 | elif 1 == weekday or 5 == weekday:## 26 | subred = reddit.subreddit("MinecraftMemes") 27 | new = subred.hot(limit = 50) 28 | game = "Minecraft" 29 | 30 | elif 2 == weekday: 31 | subred = reddit.subreddit("WarzoneClips") 32 | new = subred.new(limit = 50) 33 | game = "Warzone" 34 | 35 | elif 3 == weekday: 36 | subred = reddit.subreddit("GrandTheftAutoV") 37 | new = subred.new(limit = 50) 38 | game = "GTA" 39 | 40 | 41 | elif 6 == weekday: 42 | subred = reddit.subreddit("ValorantMemes") 43 | new = subred.hot(limit = 70) 44 | game = "Valorant" 45 | 46 | 47 | 48 | # Get The Video Count 49 | with open(folder+"/count.txt", "r") as f: 50 | count = int(f.read()) 51 | 52 | 53 | # Create The Folders To Download The Memes 54 | todayfolder = folder + "/memes" + "/{}".format(datetime.datetime.now().strftime('%Y')) + "/{}".format(datetime.datetime.now().strftime('%m')) + "/{}".format(datetime.datetime.now().strftime('%d')) 55 | try: 56 | os.mkdir(folder +"/memes/{}".format(datetime.datetime.now().strftime('%Y'))) 57 | os.mkdir(folder +"/memes/{}".format(datetime.datetime.now().strftime('%Y')) + "/{}".format(datetime.datetime.now().strftime('%m'))) 58 | os.mkdir(todayfolder) 59 | except OSError: 60 | try: 61 | os.mkdir(folder +"/memes/{}".format(datetime.datetime.now().strftime('%Y')) + "/{}".format(datetime.datetime.now().strftime('%m'))) 62 | os.mkdir(todayfolder) 63 | except OSError: 64 | try: 65 | os.mkdir(todayfolder) 66 | except OSError: 67 | print("something went wrong while creating the memes folder") 68 | os._exit(0) 69 | 70 | 71 | 72 | # Make sure that the video Is Not already created 73 | videos = [] 74 | try: 75 | output = VideoFileClip(folder +'/output/output{}.mp4'.format(datetime.datetime.now().strftime('%Y-%m-%d'))) 76 | print("video alredy created") 77 | os._exit(0) 78 | except OSError: 79 | # Download all the videos 80 | for i in new: 81 | print(i.title,"\n" ,i.url, "\n") 82 | format = i.url.split(".") 83 | 84 | 85 | if format[-1] != "gif" and format[-1] != "png" and format[-1] != "jpg": 86 | req = requests.get(i.url) 87 | try: 88 | postURL = req.content.split(b'canonicalUrl":"') 89 | postURL = postURL[1].split(b'"') 90 | postURL = postURL[0] 91 | postURL = postURL.decode("utf-8") 92 | 93 | dowloadURL = "https://sd.redditsave.com/download.php?permalink=" + postURL + "&video_url=" + i.url + "/DASH_720.mp4?source=fallback&audio_url=" + i.url + "/DASH_audio.mp4?source=fallback" 94 | print(dowloadURL, "\n\n\n") 95 | 96 | reqDWN = requests.get(dowloadURL) 97 | videos.append(reqDWN.content) 98 | with open(todayfolder + "/{}.mp4".format(datetime.datetime.now().strftime('%H-%M-%S')), "wb") as f: 99 | f.write(reqDWN.content) 100 | except IndexError: 101 | print("\nwrong URL skipping\n") 102 | 103 | 104 | # Merge all the clips into one video 105 | clips = [] 106 | memes = os.listdir(todayfolder) 107 | 108 | for meme in memes: 109 | try: 110 | # Make sure the file is not corrupted 111 | print(todayfolder +"/" + meme) 112 | comp = concatenate_videoclips([VideoFileClip(folder +"/comp.mp4"), VideoFileClip(todayfolder + "/" + meme)], method= "compose") 113 | #clips.append(VideoFileClip(todayfolder + "/" + meme)) 114 | 115 | except OSError: 116 | os.remove(todayfolder + "/" + meme) 117 | print("\nRemoving Corrupt Clip ...\n") 118 | 119 | memes = os.listdir(todayfolder) 120 | 121 | for meme in memes: 122 | clips.append(VideoFileClip(todayfolder + "/" + meme)) 123 | 124 | print("done selecting the clips ...") 125 | 126 | allclips = [] 127 | for clip in clips: 128 | allclips.append(clip) 129 | 130 | result_clip = concatenate_videoclips(allclips, method= "compose") 131 | result_clip.write_videofile(folder+'/output/output{}.mp4'.format(datetime.datetime.now().strftime('%Y-%m-%d'))) 132 | 133 | 134 | # Upload Video To Youtube 135 | vidtitle = f"Memes/Funny Clips {game} #{count}" 136 | description = f"Automated Memes/Funny Clips video #{count}\n\nContact Me: www.gen1s.tk" 137 | 138 | 139 | 140 | os.system('python youtube.py --file="'+ 'output/output{}.mp4'.format(datetime.datetime.now().strftime('%Y-%m-%d')) 141 | +'" --title="'+ vidtitle +'" --description="' + description + 142 | '" --keywords="funny,funny videos,funny compilation,funny videos compilation,video copilation" --category="24" --privacyStatus="public"') 143 | 144 | # Adds 1 to the count 145 | with open(folder+"/count.txt", "w") as f: 146 | f.write(str(count + 1)) 147 | 148 | # Done 149 | print("done") -------------------------------------------------------------------------------- /youtube.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import httplib2 4 | import os 5 | import random 6 | import sys 7 | import time 8 | 9 | from apiclient.discovery import build 10 | from apiclient.errors import HttpError 11 | from apiclient.http import MediaFileUpload 12 | from oauth2client.client import flow_from_clientsecrets 13 | from oauth2client.file import Storage 14 | from oauth2client.tools import argparser, run_flow 15 | 16 | 17 | # Explicitly tell the underlying HTTP transport library not to retry, since 18 | # we are handling retry logic ourselves. 19 | httplib2.RETRIES = 1 20 | 21 | # Maximum number of times to retry before giving up. 22 | MAX_RETRIES = 10 23 | 24 | # Always retry when these exceptions are raised. 25 | RETRIABLE_EXCEPTIONS = (httplib2.HttpLib2Error, IOError) 26 | 27 | # Always retry when an apiclient.errors.HttpError with one of these status 28 | # codes is raised. 29 | RETRIABLE_STATUS_CODES = [500, 502, 503, 504] 30 | 31 | # The CLIENT_SECRETS_FILE variable specifies the name of a file that contains 32 | # the OAuth 2.0 information for this application, including its client_id and 33 | # client_secret. You can acquire an OAuth 2.0 client ID and client secret from 34 | # the Google API Console at 35 | # https://console.developers.google.com/. 36 | # Please ensure that you have enabled the YouTube Data API for your project. 37 | # For more information about using OAuth2 to access the YouTube Data API, see: 38 | # https://developers.google.com/youtube/v3/guides/authentication 39 | # For more information about the client_secrets.json file format, see: 40 | # https://developers.google.com/api-client-library/python/guide/aaa_client_secrets 41 | CLIENT_SECRETS_FILE = "client_secrets.json" 42 | 43 | # This OAuth 2.0 access scope allows an application to upload files to the 44 | # authenticated user's YouTube channel, but doesn't allow other types of access. 45 | YOUTUBE_UPLOAD_SCOPE = "https://www.googleapis.com/auth/youtube.upload" 46 | YOUTUBE_API_SERVICE_NAME = "youtube" 47 | YOUTUBE_API_VERSION = "v3" 48 | 49 | # This variable defines a message to display if the CLIENT_SECRETS_FILE is 50 | # missing. 51 | MISSING_CLIENT_SECRETS_MESSAGE = """ 52 | WARNING: Please configure OAuth 2.0 53 | 54 | To make this sample run you will need to populate the client_secrets.json file 55 | found at: 56 | 57 | %s 58 | 59 | with information from the API Console 60 | https://console.developers.google.com/ 61 | 62 | For more information about the client_secrets.json file format, please visit: 63 | https://developers.google.com/api-client-library/python/guide/aaa_client_secrets 64 | """ % os.path.abspath(os.path.join(os.path.dirname(__file__), 65 | CLIENT_SECRETS_FILE)) 66 | 67 | VALID_PRIVACY_STATUSES = ("public", "private", "unlisted") 68 | 69 | 70 | def get_authenticated_service(args): 71 | flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, 72 | scope=YOUTUBE_UPLOAD_SCOPE, 73 | message=MISSING_CLIENT_SECRETS_MESSAGE) 74 | 75 | storage = Storage("%s-oauth2.json" % sys.argv[0]) 76 | credentials = storage.get() 77 | 78 | if credentials is None or credentials.invalid: 79 | credentials = run_flow(flow, storage, args) 80 | 81 | return build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, 82 | http=credentials.authorize(httplib2.Http())) 83 | 84 | def initialize_upload(youtube, options): 85 | tags = None 86 | if options.keywords: 87 | tags = options.keywords.split(",") 88 | 89 | body=dict( 90 | snippet=dict( 91 | title=options.title, 92 | description=options.description, 93 | tags=tags, 94 | categoryId=options.category 95 | ), 96 | status=dict( 97 | privacyStatus=options.privacyStatus 98 | ) 99 | ) 100 | 101 | # Call the API's videos.insert method to create and upload the video. 102 | insert_request = youtube.videos().insert( 103 | part=",".join(body.keys()), 104 | body=body, 105 | # The chunksize parameter specifies the size of each chunk of data, in 106 | # bytes, that will be uploaded at a time. Set a higher value for 107 | # reliable connections as fewer chunks lead to faster uploads. Set a lower 108 | # value for better recovery on less reliable connections. 109 | # 110 | # Setting "chunksize" equal to -1 in the code below means that the entire 111 | # file will be uploaded in a single HTTP request. (If the upload fails, 112 | # it will still be retried where it left off.) This is usually a best 113 | # practice, but if you're using Python older than 2.6 or if you're 114 | # running on App Engine, you should set the chunksize to something like 115 | # 1024 * 1024 (1 megabyte). 116 | media_body=MediaFileUpload(options.file, chunksize=-1, resumable=True) 117 | ) 118 | 119 | resumable_upload(insert_request) 120 | 121 | # This method implements an exponential backoff strategy to resume a 122 | # failed upload. 123 | def resumable_upload(insert_request): 124 | response = None 125 | error = None 126 | retry = 0 127 | while response is None: 128 | try: 129 | print("Uploading file...") 130 | status, response = insert_request.next_chunk() 131 | if response is not None: 132 | if 'id' in response: 133 | print("Video id '%s' was successfully uploaded." % response['id']) 134 | else: 135 | exit("The upload failed with an unexpected response: %s" % response) 136 | except HttpError as e: 137 | if e.resp.status in RETRIABLE_STATUS_CODES: 138 | error = "A retriable HTTP error %d occurred:\n%s" % (e.resp.status, 139 | e.content) 140 | else: 141 | raise 142 | except RETRIABLE_EXCEPTIONS as e: 143 | error = "A retriable error occurred: %s" % e 144 | 145 | if error is not None: 146 | print(error) 147 | retry += 1 148 | if retry > MAX_RETRIES: 149 | exit("No longer attempting to retry.") 150 | 151 | max_sleep = 2 ** retry 152 | sleep_seconds = random.random() * max_sleep 153 | print ("Sleeping %f seconds and then retrying..." % sleep_seconds) 154 | time.sleep(sleep_seconds) 155 | 156 | if __name__ == '__main__': 157 | argparser.add_argument("--file", required=True, help="Video file to upload") 158 | argparser.add_argument("--title", help="Video title", default="Test Title") 159 | argparser.add_argument("--description", help="Video description", 160 | default="Test Description") 161 | argparser.add_argument("--category", default="22", 162 | help="Numeric video category. " + 163 | "See https://developers.google.com/youtube/v3/docs/videoCategories/list") 164 | argparser.add_argument("--keywords", help="Video keywords, comma separated", 165 | default="") 166 | argparser.add_argument("--privacyStatus", choices=VALID_PRIVACY_STATUSES, 167 | default=VALID_PRIVACY_STATUSES[0], help="Video privacy status.") 168 | args = argparser.parse_args() 169 | 170 | if not os.path.exists(args.file): 171 | exit("Please specify a valid file using the --file= parameter.") 172 | 173 | youtube = get_authenticated_service(args) 174 | try: 175 | initialize_upload(youtube, args) 176 | except HttpError as e: 177 | print ("An HTTP error %d occurred:\n%s" % (e.resp.status, e.content)) --------------------------------------------------------------------------------