code before moving database code to its own file.

This commit is contained in:
2026-07-17 14:59:58 -04:00
parent 228cf37eba
commit b573ca05aa
4 changed files with 132 additions and 57 deletions
+50
View File
@@ -0,0 +1,50 @@
import sqlite3
# Stores data locally
CONN = sqlite3.connect("database.db")
CURSOR = CONN.cursor()
class Database():
def __init__():
file_path = Path("database.db")
if file_path.is_file():
self.create_database()
def create_database():
"""Creates a table structured explicitly for Twitch clip properties."""
CURSOR.execute(
"""
CREATE TABLE IF NOT EXISTS clips (
slug TEXT PRIMARY KEY,
date TEXT NOT NULL,
title TEXT NOT NULL,
gamename TEXT NOT NULL,
clip_by TEXT NOT NULL,
view_count INTEGER NOT NULL,
downloaded INTEGER NOT NULL,
uploaded_yt INTEGER NOT NULL
)
"""
)
CURSOR.execute(
"""
CREATE TABLE IF NOT EXISTS vods (
id INTEGER PRIMARY KEY,
date TEXT NOT NULL,
title TEXT NOT NULL,
gamename TEXT NOT NULL,
downloaded INTEGER NOT NULL,
uploaded_yt INTEGER NOT NULL,
chat_upload_yt INTEGER NOT NULL
)
"""
)
CONN.commit()
def close_database():
"""Commit before we close."""
CONN.commit()
CONN.close()
+6 -5
View File
@@ -70,14 +70,15 @@ def upload_clips():
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}" description = f"Game: {game_name}, Cliped By: {clip_by}, on {record_date}, #Shorts"
categoryId = CategoryId.SHORTS categoryId = CategoryId.GAMING
privatcyStatus = 'private' privatcyStatus = 'private'
tags = ['#SHORT', '#GAMING', '#TeamPGP', f'#{game_name}', f'#{clip_by}'] tags = ['shorts', 'gaming', 'TeamPGP', f'{game_name}', f'{clip_by}']
if (uploader.upload_video(file_path, title, description, categoryId, privatcyStatus, tags) is True): output = uploader.upload_video(file_path, title, description, categoryId, privatcyStatus, tags)
if output is True:
print(f"Slug: {slug} | Was successfully uploaded.") print(f"Slug: {slug} | Was successfully uploaded.")
mark_as_uploaded(slug) mark_as_uploaded(slug)
else: else:
print(f"Slug: {slug} | Upload Process failed.") print(f"Slug: {slug} | Upload Process failed.")
+21 -8
View File
@@ -21,7 +21,8 @@ def create_database():
title TEXT NOT NULL, title TEXT NOT NULL,
gamename TEXT NOT NULL, gamename TEXT NOT NULL,
downloaded INTEGER NOT NULL, downloaded INTEGER NOT NULL,
uploaded_yt INTEGER NOT NULL uploaded_yt INTEGER NOT NULL,
chat_upload_yt INTEGER NOT NULL
) )
""" """
) )
@@ -41,8 +42,8 @@ def download_vods():
for record_id, record_date, title, game_name, downloaded in undownload_vods: for record_id, record_date, title, game_name, downloaded in undownload_vods:
print(f"ID: {record_id} | Date: {record_date} | Game: {game_name} | Title: {title}") print(f"ID: {record_id} | Date: {record_date} | Game: {game_name} | Title: {title}")
output = run_linux_command(f"TwitchDownloaderCLI videodownload --id {record_id} -o save/{record_id}/{record_id}.mp4") output = run_linux_command(f"TwitchDownloaderCLI videodownload --id {record_id} -o save/vod/{record_id}/{record_id}.mp4")
output2 = run_linux_command(f"TwitchDownloaderCLI chatdownload --id {record_id} -o save/{record_id}/{record_id}_chat.json -E") output2 = run_linux_command(f"TwitchDownloaderCLI chatdownload --id {record_id} -o save/vod/{record_id}/{record_id}_chat.json -E")
if output["success"] is True: if output["success"] is True:
print(f"ID: {record_id} | Date: {record_date} | Game: {game_name} | Title: {title} | was successful") print(f"ID: {record_id} | Date: {record_date} | Game: {game_name} | Title: {title} | was successful")
mark_as_downloaded(record_id) mark_as_downloaded(record_id)
@@ -77,6 +78,20 @@ def mark_as_downloaded(record_id: int):
CONN.commit() CONN.commit()
def get_unuploaded_clips():
"""Retrieves all clip rows remaining to be chat uploaded."""
CURSOR.execute(
"SELECT id, date, title, gamename, clip_by, view_count, downloaded, uploaded_yt, chat_uploaded_yt FROM vods WHERE downloaded = 1 AND chat_uploaded_yt = 0"
)
return CURSOR.fetchall()
def get_unuploaded_clips():
"""Retrieves all clip rows remaining to be uploaded."""
CURSOR.execute(
"SELECT id, date, title, gamename, clip_by, view_count, downloaded, uploaded_yt FROM vods WHERE downloaded = 1 AND uploaded_yt = 0"
)
return CURSOR.fetchall()
def get_undownloaded_vods(): def get_undownloaded_vods():
"""Retrieves all rows where downloaded status is False (0).""" """Retrieves all rows where downloaded status is False (0)."""
@@ -84,9 +99,7 @@ def get_undownloaded_vods():
CURSOR.execute( CURSOR.execute(
"SELECT id, date, title, gamename, downloaded FROM vods WHERE downloaded = 0" "SELECT id, date, title, gamename, downloaded FROM vods WHERE downloaded = 0"
) )
records = CURSOR.fetchall() return CURSOR.fetchall()
return records
def insert_record(record_id: int, record_date: datetime_date, title: str, gamename: str): def insert_record(record_id: int, record_date: datetime_date, title: str, gamename: str):
"""Inserts a record with ID, date, gamename, and title into a SQLite database.""" """Inserts a record with ID, date, gamename, and title into a SQLite database."""
@@ -94,8 +107,8 @@ def insert_record(record_id: int, record_date: datetime_date, title: str, gamena
# Inserts data using parameterized queries to prevent SQL injection # Inserts data using parameterized queries to prevent SQL injection
CURSOR.execute( CURSOR.execute(
"INSERT OR IGNORE INTO vods (id, date, title, gamename, downloaded, uploaded_yt) VALUES (?, ?, ?, ?, ?, ?)", "INSERT OR IGNORE INTO vods (id, date, title, gamename, downloaded, uploaded_yt, chat_uploaded_yt) VALUES (?, ?, ?, ?, ?, ?, ?)",
(record_id, str(record_date), title, gamename, False, False), (record_id, str(record_date), title, gamename, False, False, False),
) )
# Saves changes and closes the connection # Saves changes and closes the connection
+55 -44
View File
@@ -2,7 +2,8 @@
import os import os
import json import json
import argparse import argparse
from enum import Enum, auto, unique from enum import Enum
from datetime import datetime
from google.oauth2.credentials import Credentials from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request from google.auth.transport.requests import Request
@@ -10,40 +11,24 @@ from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload from googleapiclient.http import MediaFileUpload
from googleapiclient.errors import HttpError from googleapiclient.errors import HttpError
@unique
class CategoryId(Enum): class CategoryId(Enum):
FILM_ANIMATION = 1 """Official YouTube Category IDs for API Uploads."""
AUTOS_VEHICLES = 2
MUSIC = 10 FILM_AND_ANIMATION = "1"
PETS_ANIMALS = 15 AUTOS_AND_VEHICLES = "2"
SPORTS = 17 MUSIC = "10"
SHORT_MOVIES = 18 PETS_AND_ANIMALS = "15"
TRAVEL_EVENTS = 19 SPORTS = "17"
GAMING = 20 TRAVEL_AND_EVENTS = "19"
VIDEOBLOGGING = 21 GAMING = "20"
PEOPLE_BLOGS = 22 PEOPLE_AND_BLOGS = "22"
COMEDY = 23 COMEDY = "23"
ENTERTAINMENT = 24 ENTERTAINMENT = "24"
NEWS_POLITICS = 25 NEWS_AND_POLITICS = "25"
HOWTO_STYLE = 26 HOWTO_AND_STYLE = "26"
EDUCATION = 27 EDUCATION = "27"
SCIENCE_TECHNOLOGY = 28 SCIENCE_AND_TECHNOLOGY = "28"
NONPROFITS_ACTIVISM = 29 NONPROFITS_AND_ACTIVISM = "29"
MOVIES = 30
ANIME_ANIMATION = 31
ACTION_ADVENTURE = 32
CLASSICS = 33
COMEDY_MOVIE = 34 # Renamed to avoid name collision
DOCUMENTARY = 35
DRAMA = 36
FAMILY = 37
FOREIGN = 38
HORROR = 39
SCI_FI_FANTASY = 40
THRILLER = 41
SHORTS = 42
SHOWS = 43
TRAILERS = 44
def load_credentials(): def load_credentials():
"""Load credentials from secrets.json""" """Load credentials from secrets.json"""
@@ -72,18 +57,24 @@ def load_credentials():
print(f"Error loading credentials: {str(e)}") print(f"Error loading credentials: {str(e)}")
return None return None
def upload_video(file_path: str, title: str, description: str, category: CategoryId, privacyStatus: str = 'private', tags: list = []): def upload_video(file_path: str, title: str, category: CategoryId, description: str = "", privacyStatus: str = 'private', tags: list = None, release_time: datetime = None):
""" """
Upload a video to YouTube Upload a video to YouTube
Args: Args:
file_path (str): Path to the video file file_path (str): Path to the video file
title (str): Title of the Video title (str): Title of the Video
description (str): Video description
categoryId (str): categoryId of the video categoryId (str): categoryId of the video
description (str): Video description
privacyStatus (str): privacyStatus of the video privacyStatus (str): privacyStatus of the video
tags (str): tags to be use on the video tags (str): tags to be use on the video
release_time (datetime): Optional timezone-aware UTC datetime object for timed release
schedule_date = datetime.now(timezone.utc) + timedelta(days=2)
""" """
if tags is None:
tags = []
try: try:
# Check if file exists # Check if file exists
if not os.path.exists(file_path): if not os.path.exists(file_path):
@@ -95,11 +86,27 @@ def upload_video(file_path: str, title: str, description: str, category: Categor
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)
# Get the filename without extension as default title # Configure the status object dynamically
title = os.path.splitext(os.path.basename(file_path))[0] status_body = {
'selfDeclaredMadeForKids': False
}
if release_time:
# 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')
print(f"Configuring timed release for: {status_body['publishAt']}")
else:
# Standard immediate upload
status_body['privacyStatus'] = privacyStatus
print(f"Configuring immediate upload with status: {privacyStatus}")
# Prepare the video upload request # Prepare the video upload request
body = { body = {
@@ -107,12 +114,9 @@ def upload_video(file_path: str, title: str, description: str, category: Categor
'title': title, 'title': title,
'description': description, 'description': description,
'tags': tags, 'tags': tags,
'categoryId': str(category.value) # Default to 'People & Blogs' category 'categoryId': category.value
}, },
'status': { 'status': status_body
'privacyStatus': privacyStatus, # Default to private
'selfDeclaredMadeForKids': False
}
} }
@@ -130,7 +134,7 @@ def upload_video(file_path: str, title: str, description: str, category: Categor
media_body=media media_body=media
) )
print("Starting upload...") print(f"Starting upload for '{title}'...")
response = None response = None
while response is None: while response is None:
status, response = insert_request.next_chunk() status, response = insert_request.next_chunk()
@@ -141,6 +145,13 @@ def upload_video(file_path: str, title: str, description: str, category: Categor
print(f"Video ID: {response['id']}") print(f"Video ID: {response['id']}")
print(f"Title: {response['snippet']['title']}") print(f"Title: {response['snippet']['title']}")
print(f"URL: https://youtu.be/{response['id']}") print(f"URL: https://youtu.be/{response['id']}")
# Output confirmation based on what was chosen
if 'publishAt' in response['status']:
print(f"Scheduled Release Time: {response['status']['publishAt']}")
else:
print(f"Current Privacy Status: {response['status']['privacyStatus']}")
return True return True
except HttpError as e: except HttpError as e: