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,
|
clip_by TEXT NOT NULL,
|
||||||
view_count INTEGER NOT NULL,
|
view_count INTEGER NOT NULL,
|
||||||
downloaded 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":
|
if self.table == "clips":
|
||||||
id = "slug"
|
id = "slug"
|
||||||
|
|
||||||
CURSOR.execute(
|
self.CURSOR.execute(
|
||||||
f"UPDATE {self.table} SET uploaded_yt = 1 WHERE {id} = ?",
|
f"UPDATE {self.table} SET uploaded_yt = 1 WHERE {id} = ?",
|
||||||
(record_id,)
|
(record_id,)
|
||||||
)
|
)
|
||||||
CONN.commit()
|
self.CONN.commit()
|
||||||
|
|
||||||
def mark_as_downloaded(self, record_id: int):
|
def mark_as_downloaded(self, record_id: int):
|
||||||
"""Updates the downloaded status to True (1) for a specific record ID."""
|
"""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():
|
def download_clips():
|
||||||
"""Loops over undownloaded metadata entries to write files down locally."""
|
"""Loops over undownloaded metadata entries to write files down locally."""
|
||||||
undownloaded_clips = get_undownloaded_clips()
|
undownloaded_clips = DB.get_undownloaded()
|
||||||
|
|
||||||
print("Downloading Clips...")
|
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}")
|
print(f"Slug: {slug} | Date: {record_date} | Game: {game_name} | By: {clip_by} | Views: {views} | Title: {title}")
|
||||||
|
|
||||||
# Uses standard clipdownload directive
|
# 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:
|
if output["success"] is True:
|
||||||
print(f"Slug: {slug} | Was successfully downloaded.")
|
print(f"Slug: {slug} | Was successfully downloaded.")
|
||||||
DB.mark_as_downloaded(slug)
|
DB.mark_as_downloaded(slug)
|
||||||
else:
|
else:
|
||||||
print(f"Slug: {slug} | Download Process failed.")
|
print(f"Slug: {slug} | Download Process failed. {output["stdout"]}. Error: {output["stderr"]}")
|
||||||
|
|
||||||
print("Finished Downloading Clips...")
|
print("Finished Downloading Clips...")
|
||||||
|
|
||||||
@@ -46,19 +46,21 @@ def upload_clips():
|
|||||||
"""Loops over the downloaded videos entries and uploaded them to youtube."""
|
"""Loops over the downloaded videos entries and uploaded them to youtube."""
|
||||||
print("Uploading Clips...")
|
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:
|
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}")
|
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"
|
file_path = f"save/clips/{slug}/{slug}.mp4"
|
||||||
title = title
|
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
|
categoryId = CategoryId.GAMING
|
||||||
privatcyStatus = 'private'
|
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:
|
if output is True:
|
||||||
print(f"Slug: {slug} | Was successfully uploaded.")
|
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:
|
if not credentials:
|
||||||
return False
|
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
|
# Create YouTube API client
|
||||||
youtube = build('youtube', 'v3', credentials=credentials)
|
youtube = build('youtube', 'v3', credentials=credentials)
|
||||||
|
|
||||||
@@ -98,7 +94,10 @@ def upload_video(file_path: str, title: str, category: CategoryId, description:
|
|||||||
'selfDeclaredMadeForKids': False
|
'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'
|
# If release_time is passed, YouTube forces privacyStatus to 'private'
|
||||||
status_body['privacyStatus'] = 'private'
|
status_body['privacyStatus'] = 'private'
|
||||||
status_body['publishAt'] = release_time.strftime('%Y-%m-%dT%H:%M:%S.000Z')
|
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
|
'status': status_body
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Create media file upload
|
# Create media file upload
|
||||||
media = MediaFileUpload(
|
media = MediaFileUpload(
|
||||||
file_path,
|
file_path,
|
||||||
@@ -162,12 +160,27 @@ def upload_video(file_path: str, title: str, category: CategoryId, description:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
# FIXED: Reconstructed arguments to provide all required inputs for upload_video
|
||||||
parser = argparse.ArgumentParser(description='Upload a video to YouTube')
|
parser = argparse.ArgumentParser(description='Upload a video to YouTube')
|
||||||
parser.add_argument('file', help='Path to the video file')
|
parser.add_argument('--file', required=True, help='Path to the video file')
|
||||||
parser.add_argument('description', help='Video description')
|
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()
|
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__":
|
if __name__ == "__main__":
|
||||||
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