fix and working Clips downloader/uploader
This commit is contained in:
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python Debugger: Python File",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "${file}",
|
||||
"args": [
|
||||
"--file", "my_video.mp4",
|
||||
"--title", "My Automated Video Title",
|
||||
"--category", "SCIENCE_AND_TECHNOLOGY",
|
||||
"--description", "Testing out the Data API!",
|
||||
"--privacy", "private"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+4
-3
@@ -37,7 +37,8 @@ class Database:
|
||||
clip_by TEXT NOT NULL,
|
||||
view_count INTEGER NOT NULL,
|
||||
downloaded INTEGER NOT NULL,
|
||||
uploaded_yt INTEGER NOT NULL
|
||||
uploaded_yt INTEGER NOT NULL,
|
||||
shorts_upload_yt INTEGER NOT NULL
|
||||
)
|
||||
"""
|
||||
)
|
||||
@@ -71,11 +72,11 @@ class Database:
|
||||
if self.table == "clips":
|
||||
id = "slug"
|
||||
|
||||
CURSOR.execute(
|
||||
self.CURSOR.execute(
|
||||
f"UPDATE {self.table} SET uploaded_yt = 1 WHERE {id} = ?",
|
||||
(record_id,)
|
||||
)
|
||||
CONN.commit()
|
||||
self.CONN.commit()
|
||||
|
||||
def mark_as_downloaded(self, record_id: int):
|
||||
"""Updates the downloaded status to True (1) for a specific record ID."""
|
||||
|
||||
@@ -24,21 +24,21 @@ def run_linux_command(command: str):
|
||||
|
||||
def download_clips():
|
||||
"""Loops over undownloaded metadata entries to write files down locally."""
|
||||
undownloaded_clips = get_undownloaded_clips()
|
||||
undownloaded_clips = DB.get_undownloaded()
|
||||
|
||||
print("Downloading Clips...")
|
||||
|
||||
for slug, record_date, title, game_name, clip_by, views, downloaded in undownloaded_clips:
|
||||
for slug, record_date, title, game_name, clip_by, views, downloaded, uploaded_yt in undownloaded_clips:
|
||||
print(f"Slug: {slug} | Date: {record_date} | Game: {game_name} | By: {clip_by} | Views: {views} | Title: {title}")
|
||||
|
||||
# Uses standard clipdownload directive
|
||||
output = run_linux_command(f"TwitchDownloaderCLI clipdownload --id {slug} -o save/clips/{slug}/{title}.mp4")
|
||||
output = run_linux_command(f"TwitchDownloaderCLI clipdownload --id {slug} -o save/clips/{slug}/{slug}.mp4")
|
||||
|
||||
if output["success"] is True:
|
||||
print(f"Slug: {slug} | Was successfully downloaded.")
|
||||
DB.mark_as_downloaded(slug)
|
||||
else:
|
||||
print(f"Slug: {slug} | Download Process failed.")
|
||||
print(f"Slug: {slug} | Download Process failed. {output["stdout"]}. Error: {output["stderr"]}")
|
||||
|
||||
print("Finished Downloading Clips...")
|
||||
|
||||
@@ -46,19 +46,21 @@ def upload_clips():
|
||||
"""Loops over the downloaded videos entries and uploaded them to youtube."""
|
||||
print("Uploading Clips...")
|
||||
|
||||
unpuloaded_clips = get_unuploaded_clips()
|
||||
unpuloaded_clips = DB.get_unuploaded()
|
||||
|
||||
categoryId = CategoryId.GAMING
|
||||
|
||||
for slug, record_date, title, game_name, clip_by, views, downloaded, uploaded_yt in unpuloaded_clips:
|
||||
print(f"Slug: {slug} | Date: {record_date} | Game: {game_name} | By: {clip_by} | Views: {views} | Title: {title}")
|
||||
|
||||
file_path = f"save/clips/{slug}/{slug}.mp4"
|
||||
title = title
|
||||
description = f"Game: {game_name}, Cliped By: {clip_by}, on {record_date}, #Shorts"
|
||||
description = f"Game: {game_name}, Cliped By: {clip_by}, on {record_date}, #Shorts #Clips #Twitch"
|
||||
categoryId = CategoryId.GAMING
|
||||
privatcyStatus = 'private'
|
||||
tags = ['shorts', 'gaming', 'TeamPGP', f'{game_name}', f'{clip_by}']
|
||||
tags = ['shorts', 'gaming', 'TeamPGP', f'{game_name}', f'{clip_by}', 'twitch_clips', 'clips']
|
||||
|
||||
output = uploader.upload_video(file_path, title, description, categoryId, privatcyStatus, tags)
|
||||
output = uploader.upload_video(file_path, title, categoryId, description, privatcyStatus, tags)
|
||||
|
||||
if output is True:
|
||||
print(f"Slug: {slug} | Was successfully uploaded.")
|
||||
|
||||
+22
-9
@@ -86,10 +86,6 @@ def upload_video(file_path: str, title: str, category: CategoryId, description:
|
||||
if not credentials:
|
||||
return False
|
||||
|
||||
# Format release time to ISO 8601 UTC format (Required by YouTube)
|
||||
# Example format generated: "2026-07-20T15:00:00Z"
|
||||
publish_at_iso = release_time.strftime('%Y-%m-%dT%H:%M:%S.000Z')
|
||||
|
||||
# Create YouTube API client
|
||||
youtube = build('youtube', 'v3', credentials=credentials)
|
||||
|
||||
@@ -98,7 +94,10 @@ def upload_video(file_path: str, title: str, category: CategoryId, description:
|
||||
'selfDeclaredMadeForKids': False
|
||||
}
|
||||
|
||||
if release_time:
|
||||
if release_time is not None:
|
||||
if release_time.tzinfo is None:
|
||||
release_time = release_time.replace(tzinfo=timezone.utc)
|
||||
|
||||
# If release_time is passed, YouTube forces privacyStatus to 'private'
|
||||
status_body['privacyStatus'] = 'private'
|
||||
status_body['publishAt'] = release_time.strftime('%Y-%m-%dT%H:%M:%S.000Z')
|
||||
@@ -119,7 +118,6 @@ def upload_video(file_path: str, title: str, category: CategoryId, description:
|
||||
'status': status_body
|
||||
}
|
||||
|
||||
|
||||
# Create media file upload
|
||||
media = MediaFileUpload(
|
||||
file_path,
|
||||
@@ -162,12 +160,27 @@ def upload_video(file_path: str, title: str, category: CategoryId, description:
|
||||
return False
|
||||
|
||||
def main():
|
||||
# FIXED: Reconstructed arguments to provide all required inputs for upload_video
|
||||
parser = argparse.ArgumentParser(description='Upload a video to YouTube')
|
||||
parser.add_argument('file', help='Path to the video file')
|
||||
parser.add_argument('description', help='Video description')
|
||||
parser.add_argument('--file', required=True, help='Path to the video file')
|
||||
parser.add_argument('--title', required=True, help='Title of the video')
|
||||
parser.add_argument('--category', default='PEOPLE_AND_BLOGS', choices=[c.name for c in CategoryId], help='Video category genre')
|
||||
parser.add_argument('--description', default='', help='Video description text')
|
||||
parser.add_argument('--privacy', default='private', choices=['public', 'private', 'unlisted'], help='Video privacy settings')
|
||||
|
||||
args = parser.parse_args()
|
||||
upload_video(args.file, args.description)
|
||||
|
||||
# Map the text input choice back to your CategoryId Enum object
|
||||
chosen_category = CategoryId[args.category]
|
||||
|
||||
# FIXED: Properly pass all required positional and optional parameters
|
||||
upload_video(
|
||||
file_path=args.file,
|
||||
title=args.title,
|
||||
category=chosen_category,
|
||||
description=args.description,
|
||||
privacyStatus=args.privacy
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,43 @@
|
||||
import sys
|
||||
from moviepy.editor import VideoFileClip
|
||||
|
||||
def convert_to_short(input_path, output_path="youtube_short.mp4"):
|
||||
# Load the video file
|
||||
clip = VideoFileClip(input_path)
|
||||
|
||||
# 1. Check video duration
|
||||
if clip.duration >= 60:
|
||||
print(f"❌ Error: Video is {clip.duration:.2f}s long. It must be under 60 seconds.")
|
||||
clip.close()
|
||||
return False
|
||||
|
||||
print(f"✅ Video length check passed ({clip.duration:.2f}s). Processing...")
|
||||
|
||||
# 2. Calculate the target width for a 9:16 aspect ratio based on height
|
||||
target_height = clip.h
|
||||
target_width = int(target_height * (9 / 16))
|
||||
|
||||
# 3. Crop the center of the video
|
||||
# x_center centers the crop horizontally
|
||||
short_clip = clip.crop(width=target_width, height=target_height, x_center=clip.w / 2, y_center=clip.h / 2)
|
||||
|
||||
# Optional: Resize to standard YouTube Short resolution (1080x1920)
|
||||
if short_clip.h != 1920:
|
||||
short_clip = short_clip.resize(newsize=(1080, 1920))
|
||||
|
||||
# 4. Export the final video file
|
||||
short_clip.write_videofile(
|
||||
output_path,
|
||||
codec="libx264",
|
||||
audio_codec="aac",
|
||||
fps=clip.fps
|
||||
)
|
||||
|
||||
# Clean up files
|
||||
clip.close()
|
||||
short_clip.close()
|
||||
print(f"🎉 Success! Short saved as: {output_path}")
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
convert_to_short()
|
||||
Reference in New Issue
Block a user